Commit 246570b
Serve the status page from Redis, and keep the alarm honest (#149)
/api/crawlstats answered in 118 seconds. It fans out eleven reads and returns
when the slowest does; timed against production:
categoryStats 30,005ms (timed out)
jobBacklogs 11,255ms
failingFeeds(20) 5,172ms
crawlStats 4,975ms
the other seven under 600ms each
No rewrite fixes `categoryStats`. It is a group-by over 476,715 rows, and on
the same connection a bare `count(*)` of that table is 6.9s while `select
category, count(*) … group by category` does not finish inside the client's 30s
deadline. Dropping its conditional aggregates -- the fix that worked for
`crawlStats` in PR #96 -- changes nothing, because the cost is visiting every
row for a column no index covers.
The per-process cache that was already here could not save it either, for a
reason worth naming: `categoryStats` does not run slowly, it *fails*, and a
cache that only stores successes stores nothing. Every request paid the full
timeout, for ever. Redis plus serve-stale-on-failure inverts that -- one
success, any time, serves every later reader -- and it survives the deploys that
emptied the old cache. It also adds no writes to Turso, whose write path is the
binding constraint on everything else here.
The part that needed care is that a status page must never report a stalled
crawler as healthy. The rule that keeps it honest is to cache facts and derive
anything measured against now: `idleMinutes` is `now - lastSuccessAt` computed
inside the query, so caching the object freezes it, and a dead crawler would go
on reporting the same cheerful number. `liveStats` caches the timestamp and
redoes the subtraction, so the number climbs while the crawler is down.
`queueHistory` does the same with its hour labels, caching the sparse rows and
filling the window on the way out.
A cache that can hang is not a cache, so the lookups are bounded too, and every
failure path -- no REDIS_URL, a refused connection, a socket that accepts
commands and never answers -- falls through to the read it replaced.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>1 parent 9eddf78 commit 246570b
6 files changed
Lines changed: 720 additions & 161 deletions
File tree
- apps/web/src
- app
- api/crawlstats
- crawlstats
- lib
- packages/db
- src
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
5 | 12 | | |
6 | 13 | | |
7 | 14 | | |
| |||
15 | 22 | | |
16 | 23 | | |
17 | 24 | | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
22 | 34 | | |
23 | 35 | | |
24 | 36 | | |
| |||
36 | 48 | | |
37 | 49 | | |
38 | 50 | | |
39 | | - | |
40 | | - | |
| 51 | + | |
| 52 | + | |
41 | 53 | | |
42 | 54 | | |
43 | 55 | | |
| |||
57 | 69 | | |
58 | 70 | | |
59 | 71 | | |
60 | | - | |
| 72 | + | |
61 | 73 | | |
62 | 74 | | |
63 | 75 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
9 | 12 | | |
10 | 13 | | |
11 | 14 | | |
| |||
62 | 65 | | |
63 | 66 | | |
64 | 67 | | |
65 | | - | |
66 | | - | |
| 68 | + | |
| 69 | + | |
67 | 70 | | |
68 | 71 | | |
69 | 72 | | |
| |||
90 | 93 | | |
91 | 94 | | |
92 | 95 | | |
93 | | - | |
| 96 | + | |
94 | 97 | | |
95 | 98 | | |
96 | 99 | | |
| |||
0 commit comments