Skip to content

feat: add structured relay health endpoints - #17

Open
Harukaon wants to merge 1 commit into
osisdie:mainfrom
Harukaon:cf-temp-health-check-pr7
Open

feat: add structured relay health endpoints#17
Harukaon wants to merge 1 commit into
osisdie:mainfrom
Harukaon:cf-temp-health-check-pr7

Conversation

@Harukaon

Copy link
Copy Markdown

Summary

Closes #7. Adds a structured, unauthenticated health response to the LINE and WhatsApp relay Workers.

Changes

  • return service, version, status, uptime, queue depth, and timestamp from GET /health and GET /
  • persist the first health-check time in KV, outside the message key prefixes
  • ensure the LINE message poller ignores that health metadata key
  • document the JSON health response

Channel(s) Affected

  • General / Infrastructure

Checklist

  • I have tested this change locally
  • Documentation updated
  • No secrets or tokens committed

Validation

  • npx markdownlint-cli2 "**/*.md"
  • Local Workers + isolated KV fixtures: both relay health endpoints returned 200 JSON and queue_depth 1.
  • Temporary Cloudflare Account deployment: deployed both changed Workers to isolated temporary KV resources and verified their public GET /health responses. The temporary account and its resources expire automatically if not claimed.

@osisdie osisdie left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Counter key — increment a _meta:queue-depth key on enqueue (handleWebhook) and decrement on delete (handleDeleteMessages). Health check becomes a single get().
  2. 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),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:')) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:' }.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add structured health check endpoint to relay workers

2 participants