Skip to content

fix: register cache writes with waitUntil so they persist on workerd#73

Merged
bensenescu merged 1 commit into
every-app:mainfrom
bookingseo:fix/cache-write-waituntil
Jul 13, 2026
Merged

fix: register cache writes with waitUntil so they persist on workerd#73
bensenescu merged 1 commit into
every-app:mainfrom
bookingseo:fix/cache-write-waituntil

Conversation

@bookingseo

@bookingseo bookingseo commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Problem

Five cache-write sites use void setCached(...) — a fire-and-forget promise that is never awaited and never registered with waitUntil:

  • DomainService.ts — domain overview (domain.rankOverview, ~100–300 credits/call) and keyword suggestions
  • domainKeywordsPage.ts / domainPagesPage.ts — paginated domain keyword/page views
  • keywords/services/research/serp.ts — SERP research analysis

On Cloudflare Workers, workerd cancels unregistered pending I/O once the response is sent, so the R2 put behind these calls is dropped before it lands. The 12-hour caches these services advertise never actually persist — every identical call re-issues the full metered DataForSEO request.

Easy to miss because everything looks fine functionally: results come back, the .catch never fires (the promise is cancelled, not rejected), and dev-mode/miniflare often lets the write finish anyway.

Fix

Register the writes with waitUntil from cloudflare:workers, matching the pattern already used elsewhere in the codebase (instrumentation.ts, brandLookup.ts, promptExplorer.ts). Response latency is unchanged — the write still happens after the response, it just isn't cancelled anymore.

No behavior change beyond the writes becoming durable. 613 tests green, tsc clean.

(Found while auditing our deployment's DataForSEO spend — cache hit-rate for get_domain_overview was 0% despite repeated identical calls.)

Greptile Summary

This PR changes R2 cache writes so they persist after the Worker response returns. The main changes are:

  • Registers domain overview and keyword suggestion cache writes with waitUntil.
  • Registers paginated domain keyword and page cache writes with waitUntil.
  • Registers SERP analysis cache writes with waitUntil.
  • Keeps existing cache keys, TTLs, DataForSEO calls, and response shaping unchanged.

Confidence Score: 5/5

Safe to merge with minimal risk.

The change is narrowly scoped to an established waitUntil(setCached(...).catch(...)) pattern. Cache keys, TTLs, request inputs, response schemas, and data shaping stay unchanged. No correctness or security issues were found in the changed paths.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Validated that the TypeScript compile for server-cache-waituntil completed cleanly with EXIT_CODE: 0.
  • Verified the Vitest run for server-cache-waituntil passed, with 3 test files and 19 tests.
  • Confirmed the static waitUntil cache registration check passed (RESULT: PASS; EXIT_CODE: 0) for the updated changes.
  • Recognized that a verification harness script was generated and used for the static check.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
src/server/features/domain/services/DomainService.ts Replaces fire-and-forget domain overview and keyword suggestion cache writes with waitUntil registration.
src/server/features/domain/services/domainKeywordsPage.ts Registers paginated domain keyword cache writes with waitUntil while preserving the existing cached/fetch/return flow.
src/server/features/domain/services/domainPagesPage.ts Registers paginated domain page cache writes with waitUntil without changing request parameters or response shaping.
src/server/features/keywords/services/research/serp.ts Registers SERP analysis cache writes with waitUntil so successful DataForSEO responses can persist after response completion.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Caller
participant Service as Domain/SERP service
participant R2 as R2 cache
participant DFS as DataForSEO
participant Worker as workerd waitUntil

Caller->>Service: request domain/SERP data
Service->>R2: getCached(cacheKey)
alt cache hit
    R2-->>Service: cached result
    Service-->>Caller: return cached result
else cache miss
    Service->>DFS: fetch metered data
    DFS-->>Service: API result
    Service->>Worker: waitUntil(setCached(cacheKey, result, ttl))
    Service-->>Caller: return fresh result
    Worker->>R2: persist cache write after response
end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Caller
participant Service as Domain/SERP service
participant R2 as R2 cache
participant DFS as DataForSEO
participant Worker as workerd waitUntil

Caller->>Service: request domain/SERP data
Service->>R2: getCached(cacheKey)
alt cache hit
    R2-->>Service: cached result
    Service-->>Caller: return cached result
else cache miss
    Service->>DFS: fetch metered data
    DFS-->>Service: API result
    Service->>Worker: waitUntil(setCached(cacheKey, result, ttl))
    Service-->>Caller: return fresh result
    Worker->>R2: persist cache write after response
end
Loading

Reviews (1): Last reviewed commit: "fix: register cache writes with waitUnti..." | Re-trigger Greptile

void setCached() leaves the R2 put unregistered, and workerd cancels
unregistered pending I/O once the response is sent — the write never
lands, so the caches behind domain overview, domain keyword/page pages,
and SERP research are silently re-fetched (and re-charged) on every
call. Register the writes with waitUntil, matching the pattern already
used in instrumentation.ts and brandLookup.ts.
@bookingseo bookingseo force-pushed the fix/cache-write-waituntil branch from 6d4ac33 to 6109fce Compare July 10, 2026 08:11
@bensenescu

Copy link
Copy Markdown
Contributor

@greptileai

@bensenescu bensenescu merged commit 172126a into every-app:main Jul 13, 2026
3 checks passed
@bensenescu

Copy link
Copy Markdown
Contributor

thanks, great find! Appreciate it

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.

2 participants