feat(audit): flip AuditLog clustered key to (OccurredAtUtc, EventId) (ScadaBridge#111)
Variant B2 of the #78 benchmark: migration FlipAuditLogClusteredKey drops the twelve non-clustered indexes, re-adds PK_AuditLog as (OccurredAtUtc, EventId) on ps_AuditLog_Month, adds the aligned unique UX_AuditLog_EventId_Occurred (EventId, OccurredAtUtc) for the idempotency probe, and rebuilds the twelve indexes unchanged. Guarded (re-runnable) Up, inverse-rebuild Down; production scripts docs/plans/sql/FlipAuditLogClusteredKey{,.rollback}.sql. The partition-switch purge staging table now reads the live clustered key order from sys.index_columns, so it switches either shape (old binaries hard-code the old order: ship with the binaries, both centrals stopped). Evidence: 300k rows, page 1 90,532 -> 34 logical reads (ordered backward clustered scan under Top), keyset page 90,532 -> 46, probe 14 -> 14; FlipAuditLogClusteredKeyMigrationTests pins indexes, grants, Down/Up, the staging key (mutation: hard-coded old key fails SWITCH) and the read ratio. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -116,7 +116,8 @@ row per lifecycle event across all channels.
|
||||
|
||||
**Indexes (first cut):**
|
||||
|
||||
- `IX_AuditLog_OccurredAtUtc` — primary time-range index for global scans.
|
||||
- Clustered `PK_AuditLog (OccurredAtUtc, EventId)` + unique `UX_AuditLog_EventId_Occurred (EventId, OccurredAtUtc)` (#111) — the time-leading clustered key serves the Audit Log page's newest-first TOP-N and keyset paging as an ordered range; the EventId-leading unique index serves the idempotency probe.
|
||||
- `IX_AuditLog_OccurredAtUtc` — covering (`INCLUDE (Status)`) time-range index for the KPI window.
|
||||
- `IX_AuditLog_Site_Occurred (SourceSiteId, OccurredAtUtc)` — per-site filters.
|
||||
- `IX_AuditLog_Node_Occurred (SourceNode, OccurredAtUtc)` — per-node filters ("everything `central-a` did in window X", or pinning a misbehaving site node).
|
||||
- `IX_AuditLog_CorrelationId (CorrelationId)` — drilldown from a single operation.
|
||||
@@ -731,9 +732,13 @@ MS SQL for direct-write events). Unredacted secrets never persist.
|
||||
on a large or contended partition and leave an orphaned staging table for the next
|
||||
tick's CATCH branch to clean up.
|
||||
- **Partition-aligned uniqueness:** the switch no longer drops and rebuilds an index.
|
||||
`EventId` uniqueness rides the clustered `PK_AuditLog (EventId, OccurredAtUtc)`,
|
||||
which is aligned on `ps_AuditLog_Month(OccurredAtUtc)`, so `SWITCH PARTITION` has no
|
||||
non-aligned unique index to object to. The predecessor `UX_AuditLog_EventId` forced
|
||||
`EventId` uniqueness rides the clustered `PK_AuditLog (OccurredAtUtc, EventId)` and the
|
||||
unique `UX_AuditLog_EventId_Occurred (EventId, OccurredAtUtc)` (migration
|
||||
`FlipAuditLogClusteredKey`, #111 — before it, the PK alone, keyed `(EventId, OccurredAtUtc)`),
|
||||
all aligned on `ps_AuditLog_Month(OccurredAtUtc)`, so `SWITCH PARTITION` has no
|
||||
non-aligned unique index to object to. The staging table takes the live table's clustered
|
||||
key order (read from `sys.index_columns` at purge time), so the purge works on either key
|
||||
shape; binaries older than the flip hard-code the old order and cannot switch a flipped table. The predecessor `UX_AuditLog_EventId` forced
|
||||
an offline whole-table index build inside the switch transaction — blocking every
|
||||
audit writer for its duration — and left a window in which the idempotency-supporting
|
||||
index did not exist at all. See migration `AlignAuditLogEventIdUniqueness`.
|
||||
|
||||
Reference in New Issue
Block a user