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:
Joseph Doherty
2026-08-15 13:22:29 -04:00
parent 0db253b55f
commit be29bf471e
6 changed files with 2264 additions and 4 deletions
@@ -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