From d070cd872b23ec38985d02d681dec9d9a1a2fb88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Eggert=20Lundsteen=20Olsen?= Date: Fri, 2 Oct 2026 11:35:00 +0200 Subject: [PATCH] Review 1.2: scan the version store for its generators at most hourly sys.dm_tran_top_version_generators reads the whole version store. It ran every 5 minutes while the version store was over half the threshold, i.e. exactly when it is large and expensive to scan, and a timeout failed the whole tempdb collection. It now runs at most once per hour (the previous answer is kept in between, with its time) and a failed scan keeps the previous answer. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../Instance/TempdbCollector.cs | 31 +++++++++++++++++-- src/SQLInsight.Core/Data/Tempdb.cs | 3 ++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/SQLInsight.Collectors/Instance/TempdbCollector.cs b/src/SQLInsight.Collectors/Instance/TempdbCollector.cs index 5ffe1fd..0e5cf4c 100644 --- a/src/SQLInsight.Collectors/Instance/TempdbCollector.cs +++ b/src/SQLInsight.Collectors/Instance/TempdbCollector.cs @@ -42,6 +42,9 @@ public sealed class TempdbCollector(int versionStoreMegabytes = 1024, int sessio ORDER BY t.elapsed_time_seconds DESC """; + /// How often the version store is scanned for the databases generating it, while it is large. + public static readonly TimeSpan GeneratorsInterval = TimeSpan.FromHours(1); + // Scans the version store: only run when it is large enough to need explaining. internal const string GeneratorsSql = """ SELECT TOP 10 DB_NAME(database_id), SUM(CAST(aggregated_record_length_in_bytes AS bigint)) @@ -68,9 +71,30 @@ public sealed class TempdbCollector(int versionStoreMegabytes = 1024, int sessio var oldest = (await Db.QueryAsync(connection, OldestSnapshotSql, r => new SnapshotTransaction(r.Int(0), r.NullableLong(1) ?? 0, r.NullableString(2), r.NullableString(3)), ct) .ConfigureAwait(false)).FirstOrDefault(); var versionMb = space.VersionKb is { } kb ? kb / 1024.0 : space.Version * PageMb; - var generators = versionMb >= versionStoreMegabytes / 2.0 - ? await Db.QueryAsync(connection, GeneratorsSql, r => new VersionGenerator(r.NullableString(0) ?? "?", (r.NullableLong(1) ?? 0) / 1024.0 / 1024.0), ct).ConfigureAwait(false) - : []; + IReadOnlyList generators = []; + DateTimeOffset? generatorsAt = null; + if (versionMb >= versionStoreMegabytes / 2.0) + { + // Scanning a large version store every 5 minutes is not cheap: at most once per GeneratorsInterval, and a + // scan that fails (times out) keeps the previous answer instead of failing the whole collection. + if (previous is { VersionGeneratorsAt: { } at } && context.Now - at < GeneratorsInterval) + { + (generators, generatorsAt) = (previous.VersionGenerators, at); + } + else + { + try + { + generators = await Db.QueryAsync(connection, GeneratorsSql, r => new VersionGenerator(r.NullableString(0) ?? "?", (r.NullableLong(1) ?? 0) / 1024.0 / 1024.0), ct) + .ConfigureAwait(false); + generatorsAt = context.Now; + } + catch (System.Data.Common.DbException) + { + (generators, generatorsAt) = (previous?.VersionGenerators ?? [], previous?.VersionGeneratorsAt); + } + } + } return TempdbAnalysis.Advance(previous, new TempdbSnapshot { @@ -84,6 +108,7 @@ public sealed class TempdbCollector(int versionStoreMegabytes = 1024, int sessio TopSessions = sessions, OldestSnapshotTransaction = oldest, VersionGenerators = generators, + VersionGeneratorsAt = generatorsAt, VersionStoreThresholdMegabytes = versionStoreMegabytes, SessionThresholdMegabytes = sessionMegabytes, }); diff --git a/src/SQLInsight.Core/Data/Tempdb.cs b/src/SQLInsight.Core/Data/Tempdb.cs index 4922210..e7e34cd 100644 --- a/src/SQLInsight.Core/Data/Tempdb.cs +++ b/src/SQLInsight.Core/Data/Tempdb.cs @@ -32,6 +32,9 @@ public sealed record TempdbSnapshot public SnapshotTransaction? OldestSnapshotTransaction { get; init; } public IReadOnlyList VersionGenerators { get; init; } = []; + /// When was read (the scan is expensive and is repeated at most hourly). + public DateTimeOffset? VersionGeneratorsAt { get; init; } + /// Version store sizes of the latest samples (MB), oldest first: the trend. public IReadOnlyList VersionStoreHistory { get; init; } = [];