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; } = [];