docs(audit): AuditLogEntries index-usage measurement, window too short, no drop yet (#70)

Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
This commit is contained in:
Joseph Doherty
2026-11-13 02:11:03 -05:00
parent e2f433e849
commit bba3c348bc
@@ -0,0 +1,72 @@
# AuditLogEntries index usage: first measurement (2026-11-13)
Tracks: Gitea **ScadaBridge#70** (deferred-work register row 135).
Question: are the single-column `IX_AuditLogEntries_Timestamp`, `_User` and `_EntityType` indexes
dead weight now that `ReviewRemediationIndexes` added `(EntityType, Timestamp DESC)`,
`(User, Timestamp DESC)` and `(Timestamp DESC, Id DESC)`?
**Decision: no migration yet. The measurement window is too short on both tiers, and no index on the
table was read at all, the new composites included. Re-measure on or after 2026-11-27.**
## Method
Read-only `SELECT`s over a direct SQL login (`sqlcmd -C`), no remote exec, run 2026-11-13 ~02:10 server time:
`sys.dm_db_index_usage_stats` LEFT JOIN `sys.indexes` for `dbo.AuditLogEntries`, plus
`sys.dm_db_index_operational_stats`, `sys.partitions`/`sys.allocation_units` for rows and size, and
`sys.dm_os_sys_info.sqlserver_start_time` (the DMVs reset when SQL Server restarts).
## Measurement window
| Tier | SQL host / DB | `sqlserver_start_time` | Window | Latest migration |
|---|---|---|---|---|
| DEV | `wonder-app-vd03` / `ScadaBridge` | 2026-11-10 06:42 | **~3 days** | `20261110120000_AuditMaintenanceGrants` (64) |
| QA | `wonder-sql-vq03` / `ScadaBridge` | 2026-11-03 07:09 | **~10 days** | `20261022133907_ReviewRemediationIndexes` (58) |
Both are under the two weeks needed. The migration went in on 2026-10-22, but SQL restarts since then
have reset the counters.
## Results
Table size: DEV 471 rows (Timestamp 2026-05-29 to 2026-10-28), QA 567 rows (to 2026-11-06).
| Index | Keys | DEV seeks/scans/lookups | DEV updates | QA seeks/scans/lookups | QA updates | Size |
|---|---|---|---|---|---|---|
| PK_AuditLogEntries (clustered) | Id | 0/0/0 | 0 | 0/0/0 | 10 | 1.4 MB / 4.3 MB |
| IX_AuditLogEntries_Action | Action | 0/0/0 | 0 | 0/0/0 | 10 | 72 KB |
| IX_AuditLogEntries_EntityId | EntityId | 0/0/0 | 0 | 0/0/0 | 10 | 72 KB |
| **IX_AuditLogEntries_EntityType** | EntityType | 0/0/0 | 0 | 0/0/0 | 10 | 72 KB |
| **IX_AuditLogEntries_Timestamp** | Timestamp | 0/0/0 | 0 | 0/0/0 | 10 | 72 KB |
| **IX_AuditLogEntries_User** | User | 0/0/0 | 0 | 0/0/0 | 10 | 72 KB |
| IX_AuditLogEntries_BundleImportId | BundleImportId | 0/0/0 | 0 | 0/0/0 | 10 | 72 KB |
| IX_AuditLogEntries_EntityType_Timestamp | EntityType, Timestamp DESC | 0/0/0 | 0 | 0/0/0 | 10 | 200 KB |
| IX_AuditLogEntries_Timestamp_Id | Timestamp DESC, Id DESC | 0/0/0 | 0 | 0/0/0 | 10 | 200 KB |
| IX_AuditLogEntries_User_Timestamp | User, Timestamp DESC | 0/0/0 | 0 | 0/0/0 | 10 | 200 KB |
| IX_AuditLogEntries_EntityName (DEV only, migration 59+) | EntityName | 0/0/0 | 0 | — | — | 200 KB |
`sys.dm_db_index_operational_stats` agrees: zero range scans and singleton lookups on every index on both
tiers, with 10 leaf inserts per index on QA and none on DEV.
## Reading
- The data does not settle the question. Every index reads zero, including the composites that are
supposed to replace the single-column ones. Nobody used the config-audit page or the CLI query on
either tier during the window, and the purge job did not touch the table. A zero on an old index
only means something if the composite that replaces it shows reads.
- On write cost: the table is tiny (about 500 rows) and takes about 1 write a day on QA. The three
single-column indexes cost about 216 KB and 3 extra index writes per insert. Nothing is urgent.
- Static coverage (from code, not from the measurement): each single-column index is a left prefix of
a composite. The filters in `CentralUiRepository` (`User ==`, `EntityType ==`, `Timestamp` range,
ordered by `Timestamp DESC, Id DESC`) and the purge in `ConfigAuditPurgeService`
(`Timestamp < cutoff ORDER BY Timestamp`, which can use a backward scan of `IX_AuditLogEntries_Timestamp_Id`)
are all served by the composites. So the case for dropping the three indexes is sound in principle.
The register's rule is "measured, not assumed", and this run measured nothing useful.
## Next step
Re-run the same query on or after **2026-11-27**, which is two weeks after both current
`sqlserver_start_time`s. Check `sqlserver_start_time` again first. If the composites show reads and
the single-column indexes still show zero, drop `IX_AuditLogEntries_Timestamp`, `_User` and
`_EntityType` in one migration whose `Down()` recreates them. Leave `Action`, `EntityId`,
`BundleImportId` and `EntityName` alone, because no composite covers them. If every index still reads
zero, the owner has to decide whether to drop on static grounds or to exercise the config-audit page
on QA first and then measure.