Skip to content

perf(api): remove the Workers KV edge-cache backend - #388

Merged
Makisuo merged 1 commit into
mainfrom
perf/remove-kv-edge-cache-backend
Aug 10, 2026
Merged

perf(api): remove the Workers KV edge-cache backend#388
Makisuo merged 1 commit into
mainfrom
perf/remove-kv-edge-cache-backend

Conversation

@Makisuo

@Makisuo Makisuo commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Reverts the KV edge-cache tier from #387. Its own comment said to treat it as an experiment until cache.read_status per cache.backend said otherwise — it does.

Measured (prod traces, 24h to 2026-08-11 12:00 UTC)

KV is slower and mostly times out. On the live deploy, reads that complete take 92ms (vs 6ms on the Cache API), and 31/39 (79%) hit their deadline.

It was bound where the cost isn't. The Postgres fallback it targets, split by service:

service postgres resolutions/24h p50 blocked wall time
alerting 4,646 573ms 2,628s (~44 min)
maple-api 281 24ms

94% is in alerting, which has no EDGE_CACHE binding and is refused by durableTierIsInvalidated, so it could never use the tier. maple-api, which had it, falls back at p50 24ms — cheaper than a KV read.

It regressed three unrelated buckets. CacheBackendLive returned KV whenever EDGE_CACHE was bound, so it flipped every bucket, but only org-clickhouse-config got a KV-appropriate deadline. qe-direct / qe-execute / autumn-customer inherited the 40ms service default — below KV's ~92ms floor — and time out 100% of the time at span p50 9.0s (19 reads, 19 timeouts).

Neither cache attempt could have worked. The cost is not a cold-isolate miss. Grouping alerting's Postgres resolutions by trace:

pg reads in trace traces total
0 1,033 0
7 110 770
14 72 1,008
22 106 2,332

74% of traces do zero — the memo works. Half of all reads come from 106 traces doing 22 each, and those traces resolve config only 2,861 times total. It's an in-request fan-out where every branch misses the memo because none has finished writing it yet — same shape as the Aug 5 BucketCacheService herd. A shared cache can't help concurrent siblings; it converts N Postgres reads into N cache reads contending for the same six connection slots, which is exactly the 79% timeout.

What changed

  • CacheBackendLive.tsmakeKvBackend, KvLike, isKvLike, KV_MIN_TTL_SECONDS, the env.EDGE_CACHE branch, and the now-unused WorkerEnvironment layer.
  • cache-backend.ts / edge-cache.ts"workers-kv" off the name union; backendName off EdgeCacheServiceShape (its only consumer was the durable-tier guard). The cache.backend span attribute is unaffected.
  • OrgClickHouseSettingsService.ts — the durable tier entirely. With KV gone durableTierIsInvalidated would admit only "memory", leaving a path that is dead in prod and live only under test. invalidateOrgRuntimeConfig collapses into the sync invalidateOrgRuntimeConfigMemo.
  • Bindings: the EDGE_CACHE KV namespace + worker binding in alchemy.run.ts, and the wrangler.jsonc entry. Alchemy will destroy the namespace on the next deploy — it holds only cache entries.
  • Tests: deleted CacheBackendLive.test.ts (tested only makeKvBackend); replaced the three durable-tier cases in OrgClickHouseSettingsService.test.ts with two asserting memo-only behaviour.

The comment above resolveCachedSettings now records both failed attempts and why, so this isn't tried a third time.

Reviewer notes

  • Correctness improves: the memo is 300s and per-isolate, so nothing can outlive an invalidation. The KV entry was 1h and shared.
  • EDGE_CACHE_READ_TIMEOUT_MS stays — that's the Cache API deadline, unrelated.
  • Not in this PR: the actual 44 min/day in alerting. The fix is a warmRoute-style prewarm before the per-rule fan-outs in AnomalyDetectionService / AlertsService; those files have unrelated in-flight work right now.

Verification

  • bun typecheck — 37/37.
  • OrgClickHouseSettingsService 25/25, lib/cache 17/17, oxlint clean on touched files.
  • git grep finds no remaining EDGE_CACHE / workers-kv / makeKvBackend / backendName.
  • After deploy: cache.backend on EdgeCacheService.getOrCompute should show only workers-cache, with no bucket at 100% timeout and read p50 ~6ms.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Open in Devin Review

#387 put the org warehouse config on a durable KV tier, on the hypothesis
that a KV `get` is a cancellable subrequest and so cheaper to abandon than
an uncancellable `cache.match()`. `makeKvBackend` said to treat it as an
experiment until `cache.read_status` per `cache.backend` said otherwise.
It does. Measured over 24h on the live deploy:

- KV reads that COMPLETE take 92ms (vs 6ms on the Cache API), and 79% of
  them hit their deadline anyway.
- It was bound where the cost isn't. 94% of the Postgres fallback it
  targets is apps/alerting (4,646 resolutions/day at p50 573ms, ~44min of
  blocked wall time), which has no KV binding and so could never use the
  tier. apps/api, which had it, falls back at p50 24ms — cheaper than a
  KV read.
- It regressed three unrelated buckets: CacheBackendLive returned KV
  whenever EDGE_CACHE was bound, so qe-direct, qe-execute and
  autumn-customer flipped to KV at the 40ms service default — below KV's
  ~92ms floor, so they time out 100% of the time at span p50 9.0s.

The reason this and the earlier Cache API attempt both failed is that the
cost is not a cold-isolate miss. Grouping alerting's Postgres resolutions
by trace: 1,033 traces do zero, while 106 traces do 22 each — half of all
of them. It is an in-request fan-out where every branch misses the memo
because none has finished writing it yet. No shared cache fixes concurrent
siblings; it just turns N Postgres reads into N cache reads contending for
the same six connection slots. The fix is to resolve the config once
before the fan-out, the way `warehouse.warmRoute` already does in
query-engine.http.ts — follow-up, not in this change.

With KV gone the durable tier is dead in prod (durableTierIsInvalidated
would admit only "memory"), so it is removed entirely rather than left
alive only under test. The memo + Postgres path is unchanged, and it is
strictly more correct: no entry can outlive an invalidation.
@Makisuo
Makisuo merged commit 6e7981b into main Aug 10, 2026
21 of 22 checks passed
@Makisuo
Makisuo deleted the perf/remove-kv-edge-cache-backend branch August 10, 2026 22:54
@Makisuo
Makisuo deployed to pr-preview August 10, 2026 22:54 — with GitHub Actions Active

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@github-actions

Copy link
Copy Markdown

🍁 Maple PR preview

Note

Preview resources were removed when this pull request closed.

Final commit 0689d43 · View workflow run

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.

1 participant