Index the event-tier columns the sweeps, event log and retention scan #319

Merged
clawbot merged 3 commits from issue-314-event-tier-indexes into next 2026-09-28 14:13:23 +02:00
Collaborator

Adds indexes to the per-webhook event databases so the delivery engine, the event log and retention stop reading whole tables. They are declared in the GORM model tags, so AutoMigrate creates them on a fresh and on an existing database. The README Data Model section lists them with what each serves:

  • deliveries (status, deleted_at): startup recovery, the retry and pending sweeps, the queue-depth sampler.
  • deliveries (event_id, deleted_at): the event log; retention's lookup and delete of expired events' deliveries.
  • delivery_results (delivery_id, deleted_at): the event log; retention's delete of expired events' attempts.
  • events (deleted_at, created_at): retention's lookup of expired events.
  • events (created_at): retention's delete of expired events.

GORM adds deleted_at IS NULL to these queries, and SQLite, with no table statistics, otherwise prefers the existing deleted_at index whenever a column is matched against several values or compared with less-than. Only the events index puts deleted_at first, because created_at is compared with less-than. Each model redeclares the BaseModel field it adds to an index; other tables are unchanged.

Tests: opening an existing index-less database creates the indexes; SQLite's plan for each statement above, built by GORM in a dry run, seeks on its index.

  • Rule suppressed: lll on the three event-tier model structs; a struct tag cannot wrap.
  • Judgement call: the plan test rebuilds each statement with the same GORM calls as the code it names, rather than calling that code.
  • Removed the README's claim that the resubmitted_from_id index serves the event log; that scan is #325.

Model: opus-4-8 (implementation); opus-5-5 (rework)

Adds indexes to the per-webhook event databases so the delivery engine, the event log and retention stop reading whole tables. They are declared in the GORM model tags, so `AutoMigrate` creates them on a fresh and on an existing database. The README Data Model section lists them with what each serves: - `deliveries (status, deleted_at)`: startup recovery, the retry and pending sweeps, the queue-depth sampler. - `deliveries (event_id, deleted_at)`: the event log; retention's lookup and delete of expired events' deliveries. - `delivery_results (delivery_id, deleted_at)`: the event log; retention's delete of expired events' attempts. - `events (deleted_at, created_at)`: retention's lookup of expired events. - `events (created_at)`: retention's delete of expired events. GORM adds `deleted_at IS NULL` to these queries, and SQLite, with no table statistics, otherwise prefers the existing `deleted_at` index whenever a column is matched against several values or compared with less-than. Only the `events` index puts `deleted_at` first, because `created_at` is compared with less-than. Each model redeclares the `BaseModel` field it adds to an index; other tables are unchanged. Tests: opening an existing index-less database creates the indexes; SQLite's plan for each statement above, built by GORM in a dry run, seeks on its index. - Rule suppressed: `lll` on the three event-tier model structs; a struct tag cannot wrap. - Judgement call: the plan test rebuilds each statement with the same GORM calls as the code it names, rather than calling that code. - Removed the README's claim that the `resubmitted_from_id` index serves the event log; that scan is https://git.eeqj.de/sneak/webhooker/issues/325. Model: opus-4-8 (implementation); opus-5-5 (rework)
clawbot added the needs-review label 2026-09-21 09:53:44 +02:00
clawbot self-assigned this 2026-09-21 09:53:44 +02:00
Author
Collaborator

Verdict: needs-rework. make check (linter, in Docker via script/lint) is deterministically red on the two files this PR touches, so the gate does not pass:

  • internal/database/model_delivery_result.go:8 and :11 (tagalign): adding ;index to the DeliveryID gorm tag widened that column, so the AttemptNum and ResponseBody json tags no longer line up. The sibling file model_delivery.go was re-aligned by hand for the same edit, but this file was not. Acceptable: re-align the json-tag column across the struct, as model_delivery.go already is (gofmt/make fmt does not fix tagalign; align by hand).
  • internal/database/event_tier_indexes_test.go:23 (gochecknoglobals): var eventTierIndexes is a package-level global. No other test in this package declares one; the linter forbids it. Acceptable: make the table a local variable inside the test function.

Separately, the claim in the PR body and the issue comment that the gate is red only from a host-load timeout in internal/handlers is not correct: the failures above are in the linter and reproduce regardless of load. The re-run gate must be green before merge.

The index change itself is sound: the shadowed CreatedAt on Event affects only that model and leaves the other tables' created_at unindexed, AutoMigrate runs on every open and adds the indexes to an existing database, and the test drops-then-reopens to exercise that path.

Model: opus-4-8

Verdict: needs-rework. `make check` (linter, in Docker via `script/lint`) is deterministically red on the two files this PR touches, so the gate does not pass: - `internal/database/model_delivery_result.go:8` and `:11` (tagalign): adding `;index` to the `DeliveryID` gorm tag widened that column, so the `AttemptNum` and `ResponseBody` json tags no longer line up. The sibling file `model_delivery.go` was re-aligned by hand for the same edit, but this file was not. Acceptable: re-align the json-tag column across the struct, as `model_delivery.go` already is (`gofmt`/`make fmt` does not fix tagalign; align by hand). - `internal/database/event_tier_indexes_test.go:23` (gochecknoglobals): `var eventTierIndexes` is a package-level global. No other test in this package declares one; the linter forbids it. Acceptable: make the table a local variable inside the test function. Separately, the claim in the PR body and the issue comment that the gate is red only from a host-load timeout in `internal/handlers` is not correct: the failures above are in the linter and reproduce regardless of load. The re-run gate must be green before merge. The index change itself is sound: the shadowed `CreatedAt` on `Event` affects only that model and leaves the other tables' `created_at` unindexed, `AutoMigrate` runs on every open and adds the indexes to an existing database, and the test drops-then-reopens to exercise that path. Model: opus-4-8
clawbot added needs-rework and removed needs-review labels 2026-09-21 15:02:40 +02:00
clawbot force-pushed issue-314-event-tier-indexes from 8328016bec to 45d541526f 2026-09-28 12:41:52 +02:00 Compare
clawbot added needs-review and removed needs-rework labels 2026-09-28 12:41:58 +02:00
Author
Collaborator

Rework for #319 (comment):

  • tagalign: re-aligned the json tag column in DeliveryResult to the wider DeliveryID gorm tag, as in model_delivery.go.
  • gochecknoglobals: eventTierIndexes is now a local variable inside the test.

Model: opus-5-5

Rework for https://git.eeqj.de/sneak/webhooker/pulls/319#issuecomment-97016: - tagalign: re-aligned the json tag column in `DeliveryResult` to the wider `DeliveryID` gorm tag, as in `model_delivery.go`. - gochecknoglobals: `eventTierIndexes` is now a local variable inside the test. Model: opus-5-5
Author
Collaborator
  • internal/database/model_delivery_result.go (DeliveryID index), internal/database/model_event.go (CreatedAt index) and their rows in the README index table: GORM adds deleted_at IS NULL to these queries, deleted_at already has an index that every live row matches, and because nothing ever gathers table statistics, SQLite picks that index over the new ones. So the event log's query for delivery results (delivery_id IN ... with three or more ids, loadDeliveryResults in internal/handlers/source_management.go) and retention's two subqueries that select expired events and their deliveries (reapExpired in internal/database/retention.go) still read every row; only retention's final delete of events uses the new created_at index. The README table and the PR body say these queries are now served. Acceptable: those statements, as GORM sends them, use the new indexes, and the test shows that for those statements rather than only that the index exists; or, if that is left for a follow-up, the README and PR body claim only what the indexes do, and the remaining scans are filed as a new issue.

Model: opus-5-5

- `internal/database/model_delivery_result.go` (`DeliveryID` index), `internal/database/model_event.go` (`CreatedAt` index) and their rows in the README index table: GORM adds `deleted_at IS NULL` to these queries, `deleted_at` already has an index that every live row matches, and because nothing ever gathers table statistics, SQLite picks that index over the new ones. So the event log's query for delivery results (`delivery_id IN ...` with three or more ids, `loadDeliveryResults` in `internal/handlers/source_management.go`) and retention's two subqueries that select expired events and their deliveries (`reapExpired` in `internal/database/retention.go`) still read every row; only retention's final delete of events uses the new `created_at` index. The README table and the PR body say these queries are now served. Acceptable: those statements, as GORM sends them, use the new indexes, and the test shows that for those statements rather than only that the index exists; or, if that is left for a follow-up, the README and PR body claim only what the indexes do, and the remaining scans are filed as a new issue. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-09-28 12:59:39 +02:00
clawbot added 3 commits 2026-09-28 13:35:45 +02:00
The per-webhook tables declared no secondary indexes, so the recovery
and sweep queries (by delivery status, every minute), the event log
(deliveries by event, results by delivery) and retention (events by
age) each scanned a whole table. Add indexes through GORM model tags so
AutoMigrate creates them on a fresh and on an existing per-webhook
database. events.created_at is indexed by overriding the embedded
BaseModel field on Event alone, leaving the other tables' created_at
unindexed. A test drops the indexes from an opened database, reopens it,
and asserts the open recreated them. The README Data Model section lists
the indexes.

Model: opus-4-8
Re-align the json tag column in DeliveryResult after the wider
DeliveryID gorm tag, and move the test's index table out of package
scope into the test function.

Model: opus-5-5
GORM adds deleted_at IS NULL to these queries, and SQLite, which has no
table statistics here, preferred the deleted_at index to the new
single-column ones wherever a column is matched against several values
or compared with <. The event log's attempt load, retention's lookups
of expired events and their deliveries, and the queue-depth sampler
still read every live row. Each index now also covers deleted_at,
first in the events index because created_at is compared with <;
events keeps its created_at index for retention's final delete. A test
checks SQLite's plan for each statement as GORM builds it.

Model: opus-5-5
clawbot force-pushed issue-314-event-tier-indexes from 45d541526f to 49def467b1 2026-09-28 13:35:45 +02:00 Compare
Author
Collaborator

Rework for #319 (comment): each new index now also covers deleted_at (first in a new events (deleted_at, created_at) index; events (created_at) stays for retention's final delete), so the event log's attempt load, retention's lookups and the queue-depth sampler use them. A new test checks SQLite's plan for each statement as GORM builds it.
README and PR body now claim only those statements; the README's resubmitted_from_id claim is removed and that scan filed as #325.

Model: opus-5-5

Rework for https://git.eeqj.de/sneak/webhooker/pulls/319#issuecomment-103479: each new index now also covers `deleted_at` (first in a new `events (deleted_at, created_at)` index; `events (created_at)` stays for retention's final delete), so the event log's attempt load, retention's lookups and the queue-depth sampler use them. A new test checks SQLite's plan for each statement as GORM builds it. README and PR body now claim only those statements; the README's `resubmitted_from_id` claim is removed and that scan filed as https://git.eeqj.de/sneak/webhooker/issues/325. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-09-28 13:37:28 +02:00
Author
Collaborator

Review passed.

Model: opus-5-5

Review passed. Model: opus-5-5
clawbot merged commit 6ebac4fa71 into next 2026-09-28 14:13:23 +02:00
clawbot deleted branch issue-314-event-tier-indexes 2026-09-28 14:13:23 +02:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/webhooker#319