perf(configdb): index DeploymentRecords on the deployment-list sort key (register row 35)
The deployment list orders `DeployedAt DESC, Id DESC` and joins `Instance` to make SiteId filterable, but the only DeployedAt index was single-column and ascending, so the server sorted the filtered set on every page request. Adds `IX_DeploymentRecords_DeployedAt_Id (DeployedAt DESC, Id DESC)` — the exact sort key, both columns descending, so a forward scan feeds the paged Skip/Take directly. Id is in the key as the deterministic tie-break: a bulk site deploy stamps many rows in the same DeployedAt instant, and without it a row can repeat or vanish across page boundaries. Three reads share the key (QueryDeploymentListPageAsync, QueryDeploymentSummariesAsync, and the single-row GetCurrentDeploymentStatusAsync). DROPS the old `IX_DeploymentRecords_DeployedAt` rather than keeping both. It is strictly redundant, not merely overlapping: DeployedAt leads both, so every predicate it served the composite serves too (direction does not affect seekability — SQL Server scans either way), and the leaf rows are the same width, because Id is the clustered PK and was therefore already in the old nonclustered index's leaf as the row locator. All that changes is Id becoming a declared, ordered key column. Keeping it would maintain a second index on every deployment insert and status transition for no read benefit. Honest caveat: because the clustering key is implicitly ordered within a nonclustered index, the optimizer may already have been able to serve this ordering by scanning the old index backward. The explicit composite makes it deterministic and self-documenting rather than dependent on that inference, and costs nothing to declare. Migration 20260815165452_AddDeploymentRecordListSortIndex (scaffolded with a real build, non-empty, snapshot updated) plus the hand-written production script under docs/plans/sql/ following the AddAuditLogAndNotificationCoveringIndexes precedent. SystemArtifactDeploymentRecords has its own separate DeployedAt index and is deliberately untouched.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
-- Register row 35: the deployment-list sort key gets its own index.
|
||||
--
|
||||
-- Replaces the single-column [IX_DeploymentRecords_DeployedAt] with the composite
|
||||
-- [IX_DeploymentRecords_DeployedAt_Id] ([DeployedAt] DESC, [Id] DESC), which is exactly
|
||||
-- the order DeploymentManagerRepository.QueryDeploymentListPageAsync (and
|
||||
-- QueryDeploymentSummariesAsync) asks for. With both key columns descending, a forward
|
||||
-- scan delivers the requested order, so a paged Skip/Take reads a Top-N off the index
|
||||
-- instead of sorting the whole filtered set on every page request.
|
||||
--
|
||||
-- The DROP is safe rather than merely tolerable. [DeployedAt] leads both indexes, so any
|
||||
-- predicate the old one served the composite serves too (direction does not affect
|
||||
-- seekability -- SQL Server can scan either way), and the leaf rows are the same width:
|
||||
-- [Id] is the clustered primary key and was therefore already in the old nonclustered
|
||||
-- index's leaf as the row locator. All that changes is that [Id] becomes a declared,
|
||||
-- ordered key column. Keeping both would maintain a second index on every deployment
|
||||
-- insert and status transition for no read benefit.
|
||||
--
|
||||
-- Note [SystemArtifactDeploymentRecords] is a DIFFERENT table with its own
|
||||
-- [DeployedAt] index; it is deliberately untouched.
|
||||
--
|
||||
-- DeploymentRecords is small enough that a plain (offline) index rebuild is fine. On an
|
||||
-- Enterprise edition instance where the deployment history is large and the Central UI
|
||||
-- must stay responsive, add WITH (ONLINE = ON) to the CREATE INDEX below.
|
||||
--
|
||||
-- Run with QUOTED_IDENTIFIER ON. Several tables in this database carry filtered indexes
|
||||
-- and persisted computed columns, and SQL Server refuses DDL/DML against them with error
|
||||
-- 1934 when the session has QUOTED_IDENTIFIER OFF -- which is exactly what `sqlcmd` gives
|
||||
-- you by default (pass -I, or keep this header). The SETs sit in their own batch so they
|
||||
-- are in force when the statements after the GO parse.
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
SET ANSI_NULLS ON;
|
||||
GO
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260815165452_AddDeploymentRecordListSortIndex'
|
||||
)
|
||||
BEGIN
|
||||
EXEC(N'DROP INDEX [IX_DeploymentRecords_DeployedAt] ON [DeploymentRecords]');
|
||||
|
||||
EXEC(N'CREATE INDEX [IX_DeploymentRecords_DeployedAt_Id]
|
||||
ON [DeploymentRecords] ([DeployedAt] DESC, [Id] DESC)');
|
||||
END;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260815165452_AddDeploymentRecordListSortIndex'
|
||||
)
|
||||
BEGIN
|
||||
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
|
||||
VALUES (N'20260815165452_AddDeploymentRecordListSortIndex', N'10.0.7');
|
||||
END;
|
||||
|
||||
COMMIT;
|
||||
GO
|
||||
@@ -120,7 +120,7 @@ The configuration database stores all central system data, organized by domain a
|
||||
- **Site Scoping Rules**: Per-mapping site scope restrictions for Deployment role.
|
||||
|
||||
### Deployment
|
||||
- **Deployment Records**: Deployment history per instance (timestamp, user, status, deployed configuration snapshot).
|
||||
- **Deployment Records**: Deployment history per instance (timestamp, user, status, deployed configuration snapshot). Indexes: unique on `DeploymentId` (deployment identity / idempotency), `InstanceId` (FK-backing, and the per-instance history read), and `IX_DeploymentRecords_DeployedAt_Id (DeployedAt DESC, Id DESC)` — the deployment list's exact sort key, so a paged read takes a Top-N off the index instead of sorting the whole filtered set per page. `Id` is in the key as the deterministic tie-break: a bulk site deploy stamps many rows in the same `DeployedAt` instant, and without it a row can repeat or vanish across page boundaries. This index **replaced** an earlier single-column one on `DeployedAt` (migration `AddDeploymentRecordListSortIndex`, deferred-work register row 35) rather than joining it — `DeployedAt` leads both, and `Id` is the clustered PK so it was already in the old index's leaf as the row locator, making the old index strictly redundant at identical leaf width.
|
||||
- **System-Wide Artifact Deployment Records**: Deployment history for shared artifacts (timestamp, user, artifact type, status).
|
||||
|
||||
### Transport
|
||||
|
||||
Reference in New Issue
Block a user