Files
meshcore-analyzer/cmd
efitenandClaude Opus 5.5 673442ccb9 perf(ingestor): refresh planner statistics 24h after the last refresh, not after every start (#2158)
Refs #2146.

## Situation

The daily planner statistics refresh (#2058) runs its first `ANALYZE` 2
minutes after every ingestor start. On a cold page cache that bounded
`ANALYZE` (analysis_limit 10000) takes minutes on a 10-11 GB database,
and it holds the ingestor's single write connection the whole time:

| Where | Logged after a restart |
|---|---|
| production instance (11.5 GB) | `component=analyze
duration=469855.2ms`, plus `[neighbor-build] SLOW tick: 7m49.98s` behind
it |
| staging instance, 12 restarts logged on 2026-10-09 | 4m11s to 9m11s
each; 7 of the 12 took over 7 minutes |

`EnsurePlannerStats` already documents what that costs: ingest stalls
and buffers for those minutes. The server's reads compete with the scan
for the disk during that window.

Every restart pays this for statistics that are at most a day old.

## My change in this PR

- `RefreshPlannerStats` records when it succeeded, in a file next to the
database (`<db>.planner-stats-refreshed`, RFC 3339).
- The refresh loop waits until that time is 24 hours old: at least the
existing 2-minute stagger, at most 24 hours (`nextPlannerStatsRefresh`).
A restart within a day of the last refresh runs no `ANALYZE`.
- No recorded time (the first start after upgrading, or a new database)
refreshes after 2 minutes as before, and records it.
`EnsurePlannerStats` records its build too, so a new database no longer
runs a second `ANALYZE` 2 minutes after the first.
- A failed refresh keeps the daily rhythm instead of retrying every 2
minutes, the same as the old ticker.

## Verification

On staging (`/var/lib/docker/volumes/.../meshcore.db`, 168 h window):

```
first start:
  [analyze] next planner statistics refresh in 2m0s
  [analyze] planner statistics refreshed in 7m26.086s (analysis_limit=10000)
  [analyze] next planner statistics refresh in 24h0m0s
  meshcore.db.planner-stats-refreshed: 2026-10-09T21:08:34Z
docker restart, then:
  [analyze] next planner statistics refresh in 24h0m0s
  (no ANALYZE and no db-slow-writer line in the following 6 minutes)
```

Tests:

- `TestNextPlannerStatsRefresh`: never refreshed, 1 hour ago, 23h59m
ago, two days ago, a recorded time in the future.
- `TestRefreshPlannerStatsRecordsWhen`: a refresh records about now, and
a reopened store reads it back as a wait of about 24 hours.
- `TestRefreshPlannerStatsDisabledRecordsNothing`.
- The existing #2058 planner-statistics tests pass unchanged.
`cmd/ingestor`: `go test ./...` green.

## Not in this PR

- The time lives in a file, not in the database: the schema has no
metadata table, and a new table would need a migration for one
timestamp. If the file is lost, the only cost is one refresh 2 minutes
after the next start, as today.
- The cold `ANALYZE` itself is unchanged; it now runs once a day instead
of after every restart. Its time of day is wherever the last refresh
landed.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 11:04:08 +02:00
..