perf(warehouse): resolve org route once per bucket-cache fan-out - #343
Merged
Conversation
A prod trace of a 5.21s dashboard panel showed BucketCacheService fanning out two computeRange branches that each independently ran resolveCapabilities -> resolveRoute -> resolveRuntimeConfig -> SELECT org_clickhouse_settings at 2.90s, concurrently, while the two warehouse queries they existed to prepare for took 428ms and 1179ms. The config lookup cost more than double the work it was setting up. All branches start before any finishes, so all of them miss the 300s runtimeConfigMemo -- a thundering herd inside a single request. The "no single-flight" rule in EdgeCacheService is about CROSS-request sharing (Cloudflare ties I/O objects to the request that created them) and never applied within one request. Adds warmRoute to WarehouseQueryServiceShape, which resolves route and capabilities once. It is Effect.ignore'd so a failed warm-up can never change the error semantics of the path it precedes -- the real query behind it still reports failures with proper context. BucketCacheService.getOrComputeBuckets takes an optional `prepare` effect, run only when fillRanges.length > 1: with zero ranges there is nothing to prepare, and with one the warm-up would move the cost rather than remove it while adding a sequential step to what may be a pure cache hit. It is a separate parameter rather than a field on BucketCacheRequest because that object is canonicalized into the cache-key fingerprint, where an Effect would poison the key. The three warehouse test doubles used `as unknown as WarehouseQueryServiceShape`, so the cast hid the missing method from tsc and only the runtime caught it. Their warmRoute stubs deliberately do not touch the call counters: warming resolves config, it does not issue a warehouse query, and those tests assert query counts. Verified: packages/query-engine typecheck + 989 tests; apps/api typecheck; apps/api/src/services/warehouse 130 tests. Not yet verified in prod.
🍁 Maple PR previewNote Preview resources were removed when this pull request closed. Final commit |
Makisuo
added a commit
that referenced
this pull request
Aug 5, 2026
…cket cache Prod measurement of EdgeCacheService.getOrCompute on the org-clickhouse-config bucket, over 1039 reads: hit 597 p50 8ms miss 64 p50 27ms <- includes the actual Postgres read timeout 378 p50 2650ms A cold Postgres read costs 27ms. The ~2.9s that has been attributed to it all along is the cache read being ABANDONED at its 40ms deadline, which happens on 36% of reads. That rate matches the failure mode already documented in edge-cache.ts almost exactly (35.9% measured at 4 reads/request): a cache.match() issued while a sibling branch's warehouse fetch holds a connection slot gets queued and never returns in time. So the fix is ordering, not caching. #343 added warmRoute but only called it from the bucket-cache fill path, and only when the fill split into more than one range. Every other fan-out -- the service bundles, the Cloudflare and PlanetScale panels, listPods, serviceDbQuerySummary -- still issued its config read concurrently with sibling warehouse fetches. Calling warmRoute immediately before each of the 14 Effect.all sites means the cache read happens with an empty connection pool, so it lands in ~8ms; every branch behind it then hits the in-isolate memo for free. On a warm memo the call is a no-op, so it costs nothing when there is nothing to warm. Verified: apps/api typecheck. Tests not run locally by request.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
BucketCacheService.getOrComputeBucketsfans outcomputeRangeatfillConcurrency, and every branch independently resolved the same org warehouse route — including the Postgres read behind it. This resolves it once before the fan-out instead.Why
Found in prod trace
625a0740fffa7fbeecdcf2c03bcda4ea— a 5.21s dashboard panel:The identical config lookup ran twice, concurrently, at 2.90s each — more than double the cost of the two warehouse queries it was preparing for.
All branches start before any finishes, so all of them miss the 300s
runtimeConfigMemo. It's a thundering herd inside a single request. The "no single-flight" rule inEdgeCacheServiceis about cross-request sharing (Cloudflare ties I/O objects to the request that created them) — it never applied within one request.Supporting measurements across 2 days of prod traces:
WarehouseQueryService.executeSql(actual SQL)SELECT org_clickhouse_settings(maple-api)SELECT org_ingest_keys(same DB, same driver)The queries are not the bottleneck; the metadata lookup beside them is.
How
warmRouteadded toWarehouseQueryServiceShape— resolves route + capabilities once.Effect.ignored, so a failed warm-up can never change the error semantics of the path it precedes; the real query behind it still reports failures with proper context.prepare— optional third arg togetOrComputeBuckets, run only whenfillRanges.length > 1. With zero ranges there's nothing to prepare; with one, the warm-up would move the cost rather than remove it while adding a sequential step to what may be a pure cache hit.Reviewer notes
prepareis a separate parameter, not a field onBucketCacheRequest— that object is canonicalized into the cache-key fingerprint, so an Effect in it would poison the key.as unknown as WarehouseQueryServiceShape. The cast hid the missing method fromtsc; only the runtime caught it. Worth knowing those stubs don't type-check against the real shape.warmRoutestubs deliberately don't touch the call counters — warming resolves config, it doesn't issue a warehouse query, and those tests assert query counts.Testing
bun run --cwd packages/query-engine typecheck+test— 989 passbun run --cwd apps/api typecheckbunx vitest run src/services/warehouse/— 130 pass, 126 skipped (ClickHouse e2e, needsch:up)Not yet verified in prod. After deploy, confirm
resolveRuntimeConfigappears once per request rather than once per fill branch.Follow-ups (not in this PR)
executeQueryBuilderrunsQueryEngineService.executeat concurrency 4 and almost certainly has the same duplicate-resolution shape.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.