feat: add structured relay health endpoints - #17
Conversation
osisdie
left a comment
There was a problem hiding this comment.
Good improvement over the bare "ok" response — the structured JSON with service name, version, queue depth, and degraded-state fallback is well thought out.
The key-filter guard in LINE's handleGetMessages is the most important correctness change here — nice catch.
A few observations inline, none blocking.
Code duplication note: getStartedAt, countKeys, and handleHealth are near-identical across both relay files. This follows the existing pattern in the repo (both relays already duplicate timingSafeEqual, checkAuth, json), so not a blocker — but worth noting as a future consolidation candidate if a shared utilities package ever makes sense.
| const page = await queue.list({ prefix, cursor }) | ||
| count += page.keys.length | ||
| cursor = page.list_complete ? undefined : page.cursor | ||
| } while (cursor) |
There was a problem hiding this comment.
Performance consideration: This paginated walk runs on every GET /health (and GET / too). KV list() returns up to 1000 keys per page, so if the queue accumulates messages, each health check becomes O(n/1000) KV reads.
If monitoring polls every 10–30 s this could add meaningful latency and KV read charges.
Two lighter alternatives:
- Counter key — increment a
_meta:queue-depthkey on enqueue (handleWebhook) and decrement on delete (handleDeleteMessages). Health check becomes a singleget(). - Cached count — store the count in a KV key with a short TTL (e.g. 30 s) and recompute only on cache miss.
Same pattern applies to the WhatsApp relay's identical countKeys.
| service: 'line-relay', | ||
| version: RELAY_VERSION, | ||
| status: 'ok', | ||
| uptime_seconds: Math.floor((now - startedAt) / 1000), |
There was a problem hiding this comment.
Naming nit: Because _health:started-at persists in KV across Worker restarts and redeployments, this value reflects "time since first health check ever" rather than actual process uptime. A new deploy won't reset it unless the KV key is manually deleted.
Consider renaming to age_seconds or first_seen_age_seconds — or add a brief note in the health-endpoint docs about the semantics so operators don't misread it as process uptime.
| const messages: QueuedMessage[] = [] | ||
|
|
||
| for (const key of allKeys.keys) { | ||
| if (!key.name.startsWith('msg:') && !key.name.startsWith('unsend:')) { |
There was a problem hiding this comment.
Good defensive guard. The unfiltered list() on line 207 would return the _health:started-at key, and the catch {} on line 214 would silently swallow the resulting parse error — so without this guard, the health key would be invisible but would still cost a wasted get() call per poll.
The WhatsApp relay doesn't need this because its handleGetMessages already filters with { prefix: 'msg:' }.
Summary
Closes #7. Adds a structured, unauthenticated health response to the LINE and WhatsApp relay Workers.
Changes
Channel(s) Affected
Checklist
Validation