A five-minute collector reads tempdb space (free, version store, user and
internal objects, whether files can grow), the 'Version Store Size (KB)'
counter, the sessions using most tempdb, the oldest snapshot transaction
and, only when the version store is large, the databases generating
versions. Trend and heavy sessions survive restarts; used space and the
version store become metrics with explanation and assessment.
Rules SQLI-TEMP-001 (version store > 1 GB or steadily growing),
SQLI-TEMP-002 (nearly full and cannot grow) and SQLI-TEMP-003 (session
above 1 GB for 15 minutes), configurable under SQLInsight:Tempdb.
"tempdb lige nu" on the instance page; GET /api/v1/instances/{name}/tempdb.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
81 lines
4.1 KiB
C#
81 lines
4.1 KiB
C#
namespace SQLInsight.Core.Data;
|
|
|
|
/// <summary>A session using tempdb (user objects plus internal objects: sorts, hash tables, spools). Login and program are sensitive.</summary>
|
|
/// <param name="Since">When the session logged in; with <see cref="SessionId"/> it identifies the session across samples.</param>
|
|
public sealed record TempdbSession(int SessionId, DateTimeOffset Since, double UsedMegabytes, string? LoginName, string? ProgramName, string? HostName, string? Status)
|
|
{
|
|
public string Key => $"{SessionId}|{Since.UtcTicks}";
|
|
}
|
|
|
|
/// <summary>The oldest open snapshot transaction: it keeps every row version made since it began.</summary>
|
|
public sealed record SnapshotTransaction(int SessionId, long ElapsedSeconds, string? LoginName, string? ProgramName);
|
|
|
|
/// <summary>Version store use per database (from sys.dm_tran_top_version_generators).</summary>
|
|
public sealed record VersionGenerator(string DatabaseName, double Megabytes);
|
|
|
|
/// <summary>tempdb space and the version store, every 5 minutes.</summary>
|
|
public sealed record TempdbSnapshot
|
|
{
|
|
public required DateTimeOffset CapturedAt { get; init; }
|
|
|
|
/// <summary>The size of tempdb's data files now.</summary>
|
|
public double SizeMegabytes { get; init; }
|
|
public double FreeMegabytes { get; init; }
|
|
public double VersionStoreMegabytes { get; init; }
|
|
public double UserObjectsMegabytes { get; init; }
|
|
public double InternalObjectsMegabytes { get; init; }
|
|
|
|
/// <summary>Whether a data file can still grow (autogrowth on and not at its maximum size).</summary>
|
|
public bool CanGrow { get; init; } = true;
|
|
|
|
public IReadOnlyList<TempdbSession> TopSessions { get; init; } = [];
|
|
public SnapshotTransaction? OldestSnapshotTransaction { get; init; }
|
|
public IReadOnlyList<VersionGenerator> VersionGenerators { get; init; } = [];
|
|
|
|
/// <summary>Version store sizes of the latest samples (MB), oldest first: the trend.</summary>
|
|
public IReadOnlyList<double> VersionStoreHistory { get; init; } = [];
|
|
|
|
/// <summary>Since when each session has been above <see cref="SessionThresholdMegabytes"/>, by <see cref="TempdbSession.Key"/>.</summary>
|
|
public IReadOnlyDictionary<string, DateTimeOffset> HeavySessionsSince { get; init; } = new Dictionary<string, DateTimeOffset>();
|
|
|
|
/// <summary>The version store above this is a finding (SQLInsight:Tempdb:VersionStoreMegabytes).</summary>
|
|
public int VersionStoreThresholdMegabytes { get; init; } = 1024;
|
|
|
|
/// <summary>A session using more tempdb than this for <see cref="TempdbAnalysis.HeavySessionMinutes"/> is a finding.</summary>
|
|
public int SessionThresholdMegabytes { get; init; } = 1024;
|
|
|
|
public double FreePercent => SizeMegabytes <= 0 ? 100 : 100 * FreeMegabytes / SizeMegabytes;
|
|
}
|
|
|
|
public static class TempdbAnalysis
|
|
{
|
|
public const int HistoryLength = 12;
|
|
public const int HeavySessionMinutes = 15;
|
|
|
|
/// <summary>The version store has grown in each of the last six samples, by a quarter GB in all.</summary>
|
|
public static bool IsGrowing(IReadOnlyList<double> history)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(history);
|
|
if (history.Count < 6)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var last = history.Skip(history.Count - 6).ToList();
|
|
return last.Zip(last.Skip(1)).All(p => p.Second > p.First) && last[^1] - last[0] >= 256;
|
|
}
|
|
|
|
/// <summary>Carries the trend and the heavy sessions over from the previous sample.</summary>
|
|
public static TempdbSnapshot Advance(TempdbSnapshot? previous, TempdbSnapshot current)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(current);
|
|
var history = (previous?.VersionStoreHistory ?? []).Append(current.VersionStoreMegabytes).TakeLast(HistoryLength).ToList();
|
|
var heavy = new Dictionary<string, DateTimeOffset>(StringComparer.Ordinal);
|
|
foreach (var s in current.TopSessions.Where(s => s.UsedMegabytes >= current.SessionThresholdMegabytes))
|
|
{
|
|
heavy[s.Key] = previous?.HeavySessionsSince.TryGetValue(s.Key, out var since) == true ? since : current.CapturedAt;
|
|
}
|
|
|
|
return current with { VersionStoreHistory = history, HeavySessionsSince = heavy };
|
|
}
|
|
}
|