fix(hub): index flows(minute) so retention cannot stall ingest #33

Open
david wants to merge 1 commit from fix/flows-minute-index into main
Owner

Why

retention::downsample trims flows with DELETE FROM flows WHERE minute < ?, once every five minutes, inside one transaction. The only index on the table is (server, minute), which leads with server, so the delete could not use it: EXPLAIN QUERY PLAN said SCAN flows over 3,445,105 rows.

That transaction holds the hub's single Arc<Mutex<Connection>>, and every agent's metric insert goes through the same mutex. So the scan froze all agent connections at once for tens of seconds, mark_seen stopped, and the hub declared healthy agents offline and pushed "netwatch: down" to ntfy.

Observed on ewtg-ops after two more agents were enrolled (three agents, 519 MB database, 3.4M flow rows):

window dev prod notes
before, 13:05-13:29Z worst gap 58 s, 3 gaps > 5 s 53 s, 3 12 s, 1
after, 13:29-13:36Z 3 s, 0 3 s, 0 3 s, 0

Gaps are measured on metrics.ts, which the hub stamps at receipt (unix_now()), so they are processing stalls, not agent sampling. The full-scan transaction is the one that fits: periodic, fleet-wide, 10-110 s, worse as flows grew.

What

Migration v14: CREATE INDEX IF NOT EXISTS flows_minute ON flows (minute). The delete becomes SEARCH flows USING INDEX flows_minute (minute<?).

Also updates the two tests that pinned SCHEMA_VERSION at 13, and adds an assertion that v14 creates the index.

Verification

  • EXPLAIN QUERY PLAN DELETE FROM flows WHERE minute < 1: SCAN flows before, SEARCH flows USING INDEX flows_minute (minute<?) after.
  • Index build on the live 3.4M-row table: 3.0 s, 42 MB.
  • cargo test -p hub --locked: 177 + 28 + 58 + 20 + 4 pass, 0 fail.
  • Live hub with the index applied by hand: metric gaps 58/53/12 s -> 3/3/3 s over the following retention cycles.

The index was applied to the live database by hand during the incident, so the running hub already has it; this PR makes it survive a rebuild, a fresh install, or a restore from an older backup.

## Why `retention::downsample` trims flows with `DELETE FROM flows WHERE minute < ?`, once every five minutes, inside one transaction. The only index on the table is `(server, minute)`, which leads with `server`, so the delete could not use it: `EXPLAIN QUERY PLAN` said `SCAN flows` over 3,445,105 rows. That transaction holds the hub's single `Arc<Mutex<Connection>>`, and every agent's metric insert goes through the same mutex. So the scan froze all agent connections at once for tens of seconds, `mark_seen` stopped, and the hub declared healthy agents offline and pushed "netwatch: <server> down" to ntfy. Observed on ewtg-ops after two more agents were enrolled (three agents, 519 MB database, 3.4M flow rows): | window | dev | prod | notes | |---|---|---|---| | before, 13:05-13:29Z | worst gap 58 s, 3 gaps > 5 s | 53 s, 3 | 12 s, 1 | | after, 13:29-13:36Z | 3 s, 0 | 3 s, 0 | 3 s, 0 | Gaps are measured on `metrics.ts`, which the hub stamps at receipt (`unix_now()`), so they are processing stalls, not agent sampling. The full-scan transaction is the one that fits: periodic, fleet-wide, 10-110 s, worse as `flows` grew. ## What Migration v14: `CREATE INDEX IF NOT EXISTS flows_minute ON flows (minute)`. The delete becomes `SEARCH flows USING INDEX flows_minute (minute<?)`. Also updates the two tests that pinned `SCHEMA_VERSION` at 13, and adds an assertion that v14 creates the index. ## Verification - `EXPLAIN QUERY PLAN DELETE FROM flows WHERE minute < 1`: `SCAN flows` before, `SEARCH flows USING INDEX flows_minute (minute<?)` after. - Index build on the live 3.4M-row table: 3.0 s, 42 MB. - `cargo test -p hub --locked`: 177 + 28 + 58 + 20 + 4 pass, 0 fail. - Live hub with the index applied by hand: metric gaps 58/53/12 s -> 3/3/3 s over the following retention cycles. The index was applied to the live database by hand during the incident, so the running hub already has it; this PR makes it survive a rebuild, a fresh install, or a restore from an older backup.
fix(hub): index flows(minute) so retention cannot stall ingest
All checks were successful
CI / fmt + clippy + test (push) Successful in 4m29s
CI / fmt + clippy + test (pull_request) Successful in 3m43s
ef18bf275b
Retention trims flows with DELETE FROM flows WHERE minute < ?, once every
five minutes, inside one transaction. The only index on the table is
(server, minute), so the delete could not use it: EXPLAIN QUERY PLAN said
SCAN flows over 3.4M rows while holding the hub's single connection. Every
agent's metric insert queues behind that transaction, so fortysomething
seconds of scan look like an offline agent.

Measured on ewtg-ops (3.4M flow rows, 519MB database):
  before  EXPLAIN QUERY PLAN DELETE FROM flows WHERE minute < 1
          -> SCAN flows
  after   -> SEARCH flows USING INDEX flows_minute (minute<?)

Metric-arrival gaps per agent, worst over the window:
  before the index, 13:05-13:29Z   dev 58s, prod 53s, notes 12s
  after the index,  13:29-13:36Z   dev 3s,  prod 3s,  notes 3s

Adds migration v14 and a test that the index exists.
All checks were successful
CI / fmt + clippy + test (push) Successful in 4m29s
CI / fmt + clippy + test (pull_request) Successful in 3m43s
This pull request has changes conflicting with the target branch.
  • crates/hub/src/db.rs
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/flows-minute-index:fix/flows-minute-index
git switch fix/flows-minute-index
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
david/netwatch!33
No description provided.