audit 3: every per-message question of the send log has its index
test / test (push) Canceled after 0s

Migration 0010 (DDL only; 0009 went to stage 2b). EXPLAIN QUERY PLAN of
every send_log statement showed, beyond M2 as written (stage 2 had already
moved the by-domain counts onto (domain_id, created_at)):

- the tailer's sweep of open rows (every 5 min) and its count scanned the
  table: partial idx_send_log_open (created_at) WHERE status IN ('queued',
  'deferred'), the statuses literal in the statements, ORDER BY +id (else
  SQLite walks the table in id order to skip a sort);
- AttachRefusalLine's NOT EXISTS (... refusal_line = ?1) scanned the table
  for every refusal line: partial idx_send_log_refusal_line, the query
  carrying the index's WHERE; its row found by idx_send_log_rejected
  (status literal, +queue_id) instead of every refusal without a queue id;
- an application's level-2 count went through its domain's index:
  +domain_id in appByLogin;
- a page of one domain's or one application's log sorted all of it:
  idx_send_log_domain_id, idx_send_log_app_login (id order; several domains
  still sort, top-N). idx_send_log_domain stays for the global filter.

The statements are constants and builders (countMessagesQuery,
peakPerHourQuery, listOpenQuery, countOpenQuery, sendLogPageQuery,
attachRefusalLineQuery, limitRefusalsQuery) and
TestSendLogQueriesUseTheirIndexes explains exactly those: the index each
must use, no SCAN of send_log, no sort where none is wanted; the literal
statuses checked against the constants. It replaces the hand-written plan
check of TestLimitRefusalsSince.

Tested on the test server: go vet + go test of 24 packages, make e2e (all
pass) on this tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mix
2026-10-15 10:10:52 +03:00
co-authored by Claude Opus 5.5
parent 8e3241ef98
commit 508ac96c79
13 changed files with 268 additions and 89 deletions
+17 -1
View File
@@ -1092,12 +1092,28 @@ those after it move up one patch number each.
untouched, no `-wal`), a damaged key named; the domain import of a cut
plain export, a cut and an altered `.spde` (each refused, nothing
created). The CLI's `-decrypt -o` is step 3.
- [ ] **Indexes**: `idx_send_log_domain_created_at (domain_id, created_at)` —
- [x] **Indexes**: `idx_send_log_domain_created_at (domain_id, created_at)` —
on the id column stage 2 adds, replacing `idx_send_log_domain`;
partial `idx_send_log_queued (created_at) WHERE status = 'queued'`;
consider `(domain, id)` for the scoped page. Migration `0009` DDL only;
`TestLimitRefusalsSince`-style EXPLAIN tests against the **real** query
strings. — **Opus**
*Done 2026-10-15,* migration `0010` (0009 went to stage 2b). Stage 2
already counted by `(domain_id, created_at)`; `idx_send_log_domain` stays,
the global log's filter by name reads its page off it in id order. The
plans of every `send_log` statement showed four more: the sweep now asks
for queued *and* deferred rows - `idx_send_log_open (created_at) WHERE
status IN ('queued', 'deferred')`, the statuses literal and `ORDER BY +id`
(else SQLite walks the table in id order); `NOT EXISTS (... refusal_line
= ?1)` of stage 2b scanned the table per refusal line - partial
`idx_send_log_refusal_line`, and its row is found by
`idx_send_log_rejected` (status literal, `+queue_id`); the application's
level-2 count went by the domain's index - `+domain_id`; a page of one
domain or one application - `idx_send_log_domain_id`,
`idx_send_log_app_login` (id order, no sort; several domains still sort,
top-N). The statements are constants/builders the test explains
(`TestSendLogQueriesUseTheirIndexes`, which replaces the hand-written
plan check of `TestLimitRefusalsSince`).
- [ ] **CLI backup**: open the source `mode=ro`, refuse a missing file; password
floor of 12 shared with the panel (`validate.MinSecretFilePasswordLen`);
`-decrypt -o` verifies the password (first chunk) before opening the