perf(sitecallaudit): filtered IX_SiteCalls_Terminal index backs the retention purge predicate (plan R2-04 T10)
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
BEGIN TRANSACTION;
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT * FROM [__EFMigrationsHistory]
|
||||||
|
WHERE [MigrationId] = N'20260713142234_AddSiteCallsTerminalIndex'
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
EXEC(N'CREATE INDEX [IX_SiteCalls_Terminal] ON [SiteCalls] ([TerminalAtUtc]) WHERE [TerminalAtUtc] IS NOT NULL');
|
||||||
|
END;
|
||||||
|
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT * FROM [__EFMigrationsHistory]
|
||||||
|
WHERE [MigrationId] = N'20260713142234_AddSiteCallsTerminalIndex'
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
|
||||||
|
VALUES (N'20260713142234_AddSiteCallsTerminalIndex', N'10.0.7');
|
||||||
|
END;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
GO
|
||||||
|
|
||||||
+9
@@ -97,5 +97,14 @@ public class SiteCallEntityTypeConfiguration : IEntityTypeConfiguration<SiteCall
|
|||||||
.HasDatabaseName("IX_SiteCalls_NonTerminal")
|
.HasDatabaseName("IX_SiteCalls_NonTerminal")
|
||||||
.HasFilter("[TerminalAtUtc] IS NULL")
|
.HasFilter("[TerminalAtUtc] IS NULL")
|
||||||
.IncludeProperties(nameof(SiteCall.SourceSite), nameof(SiteCall.SourceNode), nameof(SiteCall.Status));
|
.IncludeProperties(nameof(SiteCall.SourceSite), nameof(SiteCall.SourceNode), nameof(SiteCall.Status));
|
||||||
|
|
||||||
|
// Terminal backs the daily retention purge's predicate (TerminalAtUtc IS NOT NULL
|
||||||
|
// AND TerminalAtUtc < cutoff) and Task 11's MIN() slice anchor: filtered to the
|
||||||
|
// terminal population so the purge seeks the expired tail instead of full-scanning
|
||||||
|
// the 365-day table (arch-review 04 round 2, R6). Complements IX_SiteCalls_NonTerminal,
|
||||||
|
// which is filtered to IS NULL and unusable for this predicate.
|
||||||
|
builder.HasIndex(s => s.TerminalAtUtc)
|
||||||
|
.HasDatabaseName("IX_SiteCalls_Terminal")
|
||||||
|
.HasFilter("[TerminalAtUtc] IS NOT NULL");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2090
File diff suppressed because it is too large
Load Diff
+28
@@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddSiteCallsTerminalIndex : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_SiteCalls_Terminal",
|
||||||
|
table: "SiteCalls",
|
||||||
|
column: "TerminalAtUtc",
|
||||||
|
filter: "[TerminalAtUtc] IS NOT NULL");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_SiteCalls_Terminal",
|
||||||
|
table: "SiteCalls");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
@@ -167,6 +167,10 @@ namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
|
|||||||
|
|
||||||
SqlServerIndexBuilderExtensions.IncludeProperties(b.HasIndex("CreatedAtUtc"), new[] { "SourceSite", "SourceNode", "Status" });
|
SqlServerIndexBuilderExtensions.IncludeProperties(b.HasIndex("CreatedAtUtc"), new[] { "SourceSite", "SourceNode", "Status" });
|
||||||
|
|
||||||
|
b.HasIndex("TerminalAtUtc")
|
||||||
|
.HasDatabaseName("IX_SiteCalls_Terminal")
|
||||||
|
.HasFilter("[TerminalAtUtc] IS NOT NULL");
|
||||||
|
|
||||||
b.HasIndex("SourceSite", "CreatedAtUtc")
|
b.HasIndex("SourceSite", "CreatedAtUtc")
|
||||||
.IsDescending(false, true)
|
.IsDescending(false, true)
|
||||||
.HasDatabaseName("IX_SiteCalls_Source_Created");
|
.HasDatabaseName("IX_SiteCalls_Source_Created");
|
||||||
|
|||||||
@@ -94,6 +94,22 @@ public class SchemaConfigurationTests : IDisposable
|
|||||||
new[] { nameof(SiteCall.SourceSite), nameof(SiteCall.SourceNode), nameof(SiteCall.Status) },
|
new[] { nameof(SiteCall.SourceSite), nameof(SiteCall.SourceNode), nameof(SiteCall.Status) },
|
||||||
index.GetIncludeProperties());
|
index.GetIncludeProperties());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// arch-review 04 round 2 (R6): the daily terminal retention purge filters on
|
||||||
|
// TerminalAtUtc IS NOT NULL AND TerminalAtUtc < cutoff. A filtered index over the
|
||||||
|
// terminal population lets the purge (and Task 11's MIN() slice anchor) seek the
|
||||||
|
// expired tail instead of full-scanning the 365-day table. Locked shape:
|
||||||
|
// key = TerminalAtUtc, filter = [TerminalAtUtc] IS NOT NULL.
|
||||||
|
[Fact]
|
||||||
|
public void SiteCalls_Has_Filtered_Terminal_Index()
|
||||||
|
{
|
||||||
|
var model = _context.GetService<IDesignTimeModel>().Model;
|
||||||
|
var entity = model.FindEntityType(typeof(SiteCall))!;
|
||||||
|
var index = entity.GetIndexes().Single(i => i.GetDatabaseName() == "IX_SiteCalls_Terminal");
|
||||||
|
|
||||||
|
Assert.Equal(new[] { nameof(SiteCall.TerminalAtUtc) }, index.Properties.Select(p => p.Name).ToArray());
|
||||||
|
Assert.Equal("[TerminalAtUtc] IS NOT NULL", index.GetFilter());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SplitQueryBehaviourTests : IDisposable
|
public class SplitQueryBehaviourTests : IDisposable
|
||||||
|
|||||||
Reference in New Issue
Block a user