Skip to content

chore(cascade): 2MB Data Cache fix + CI hygiene → stage - #996

Merged
evereq merged 21 commits into
stagefrom
develop
Jul 30, 2026
Merged

chore(cascade): 2MB Data Cache fix + CI hygiene → stage#996
evereq merged 21 commits into
stagefrom
develop

Conversation

@evereq

@evereq evereq commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Cascade developstage so the oversized-listing cache fix can reach main and then the 14 instance repos generated from this template.

Carries 21 commits, of which the one that matters is 9648d62 fix(cache): skip 2MB-capped Data Cache for oversized listings (#988). The rest are CI hygiene already swept fleet-wide (DO gating, x4 runners + Node-24 actions, CodeQL on self-hosted ARC).

Why this is urgent

unstable_cache hard-caps one entry at 2MB and silently drops anything larger — the entry is then recomputed and re-serialized on every request. Live effect on the largest catalogue (mcpserver.ever.works, 4065 items, ~2× the new 2000-item threshold): ~35s per request with memory climbing monotonically (1549→2896Mi) while sitting well under a 4-core/4GB ceiling. Not saturation — unbounded recompute. vectordb (32 restarts) and timetrack (intermittent 20s timeouts) look like the same defect scaling with catalogue size.

Note on divergence

develop is 21 ahead but 188 behind stage, so these branches have drifted both ways. The two files the fix touches are unaffected by that drift: apps/web/lib/content.ts differs between develop and main by only 29 lines (essentially the fix itself), and apps/web/lib/cache-config.ts by exactly 18 added lines. Template main is byte-identical to the instance repos for both files.

evereq and others added 21 commits July 10, 2026 13:58
…DO) (#987)

DigitalOcean image pushes are now opt-in. Each docker-build-publish
workflow (dev/stage/prod):

- Passes vars.DO_ENABLED into the "Resolve image tags" (meta) step and
  only appends the registry.digitalocean.com/... tags when
  DO_ENABLED == 'true', so the push:true build-push-action never tries
  to push to DO (which would 403) when DO is disabled.
- ANDs vars.DO_ENABLED == 'true' into the existing `if:` on the
  "Install doctl" and "Login to DigitalOcean Container Registry" steps.

No DO code is deleted -- it is simply skipped by default and re-enabled
by setting the org/repo variable DO_ENABLED=true. GHCR/Docker Hub and
all other steps are unchanged.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
apps/docs (@ever-works/docs, Docusaurus) had no Dockerfile / matrix entry, so
ghcr.io/ever-works/directory-web-template-docs went stale (last 2026-07-08) while
docs-demo-ever-works-prod keeps running it. Add:
- apps/docs/Dockerfile: base -> turbo prune @ever-works/docs -> pnpm install ->
  docusaurus build -> nginx (mirrors the root Dockerfile + ever-works docs build;
  copies repo-root docs/ since Docusaurus reads ../../docs).
- apps/docs/nginx.conf: static serve with clean-URL try_files + Docusaurus 404.
- k8s-build.yml: matrix entry directory-web-template-docs -> :dev/:stage/:prod+:sha.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…OSPC on RAM-disk)

The first attempt's `docusaurus build` (all locales) rendered every locale of the
large API docs, ran ~7480s on the ARC RAM-disk runner (starving the shared pool)
and died with ENOSPC mid-way through /pt/. Switch to `build:en` (English only) —
a fraction of the disk/time, fits the RAM-disk. Re-enable full-locale later on a
disk-backed builder.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oves Node-20 deprecation warnings) [skip ci]
…oves Node-20 deprecation warnings) [skip ci]
…oves Node-20 deprecation warnings) [skip ci]
…oves Node-20 deprecation warnings) [skip ci]
…oves Node-20 deprecation warnings) [skip ci]
…oves Node-20 deprecation warnings) [skip ci]
…oves Node-20 deprecation warnings) [skip ci]
…oves Node-20 deprecation warnings) [skip ci]
…oves Node-20 deprecation warnings) [skip ci]
…oves Node-20 deprecation warnings) [skip ci]
…oves Node-20 deprecation warnings) [skip ci]
…oves Node-20 deprecation warnings) [skip ci]
…oves Node-20 deprecation warnings) [skip ci]
…oves Node-20 deprecation warnings) [skip ci]
…oves Node-20 deprecation warnings) [skip ci]
Large directories (e.g. the demo's 3271 items ≈ 2.6MB) exceed Next.js
`unstable_cache`'s hard 2MB per-entry limit. Next then SILENTLY drops the
entry and recomputes + re-serializes the whole catalogue on every request
("Failed to set Next.js data cache … items over 2MB can not be cached"),
pegging CPU (demo.ever.works web pod sat at ~1.5 cores sustained, ~49
cache-fail log lines per 500).

The listing wrappers (getCachedItems / getCachedItemsByCategory /
getCachedItemsByTag / getCachedItemsByCategoryAndTag) now size-gate the
Data Cache layer: when a listing exceeds DATA_CACHE_MAX_ITEMS (default
2000, override via CONTENT_DATA_CACHE_MAX_ITEMS) they skip unstable_cache
and serve from the in-memory fetchItems cache (10-min TTL, cleared on
content sync) instead. Listing items are metadata-only (markdown stripped,
~0.8KB each), so item count is a reliable size proxy. Small directories are
unaffected and keep the persistent Data Cache.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Point the two remaining bare `ubuntu-latest` jobs (CodeQL analysis and
the default-CodeQL disabler) at the self-hosted ARC var, keeping the
GitHub-hosted fallback:

    runs-on: ${{ vars.RUNNER_LINUX_X64_4 || 'ubuntu-latest' }}

CodeQL/SARIF jobs use the smallest (_4) pool per runner policy. All
other workflows already use the ARC vars.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci: move CodeQL runners to self-hosted ARC fleet (develop)
@cursor

cursor Bot commented Jul 30, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 40ae7b27-357e-451b-b999-e03e4d7bc084

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@evereq
evereq merged commit 83b3b13 into stage Jul 30, 2026
21 of 56 checks passed
Comment thread apps/web/lib/content.ts

// `fetchItems` is in-memory cached, so this is cheap on the hot path and lets us size-gate
// the (2MB-capped) Data Cache layer before paying to serialize an oversized catalogue.
const result = await fetchItems(options);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Persistent cache is bypassed before lookup

getCachedItems calls fetchItems(options) before invoking unstable_cache, solely to determine whether the result is small enough to persist. Consequently, a cold process-local cache still walks and parses the full content tree even when the Data Cache already contains this listing. This defeats the persistent-cache layer's purpose of avoiding content-tree work on cold processes and adds avoidable request latency.

Artifacts

Instrumented harness source for persistent Data Cache hit and miss cases

  • The authored Node harness extracts the actual wrapper from `content.ts`, instruments its cache seams, and asserts the two target execution paths; it confirms the behavior under test.

Persistent Data Cache hit with cold process-local cache

  • Executed `NODE_ENV=production node trex-artifacts/data-cache-eager-fetch-harness.mjs persistent-hit` from `/home/user/repo` with exit code 0; it shows one fetch and one filesystem walk/parse before the persistent hit, confirming the hit does not avoid parsing.

View artifacts

T-Rex Ran code and verified through T-Rex

Comment thread apps/docs/nginx.conf
Comment on lines +24 to +27
location ~* \.(js|css|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|webp|ico)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
try_files $uri =404;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Unhashed images are cached as immutable

This extension-only location applies public, max-age=2592000, immutable to stable image URLs such as /overview.png, not just fingerprinted build assets. When a deployment replaces an image without changing its URL, browsers with a fresh cached copy do not re-request it and continue displaying the old image for up to 30 days. Restrict the immutable policy to content-hashed assets or use a revalidating policy for replaceable image URLs.

Artifacts

Focused Chromium same-name image cache proof script

  • The executed proof script builds a local response path around the real docs `/overview.png`, reads the nginx image rule, and tests a same-URL deployment replacement in Chromium; it demonstrates the exact cache scenario.

Chromium same-name image cache proof output

  • Output from the executed Playwright Chromium proof shows the immutable Cache-Control header, rendered image, and one origin request across both deployment versions; it confirms the replacement stays cached.

Docs English build output

  • Output from the executed Docusaurus English build shows static files were generated successfully before the HTTP cache validation; it confirms the tested asset came from a real docs build.

View artifacts

T-Rex Ran code and verified through T-Rex

@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This change adds a size gate for persisted directory listings, refreshes CI and deployment configuration, and introduces nginx delivery for the English documentation site.

Runtime checks found that small directory listings still load and parse content before the persistent cache is consulted, and a persistent-cache miss invokes the listing function again through the cache callback. Documentation images served from stable URLs are also cached as immutable for 30 days, so replacing an image without changing its URL leaves returning visitors on the prior version.

The English documentation build completed successfully, and focused runtime checks reproduced the cache behavior in both affected areas.

Confidence Score: 3/5

Not safe to merge until the listing-cache lookup order and immutable caching policy for replaceable documentation images are corrected.

Two independent user-facing failures remain: cached directory listings do not avoid cold-process content parsing, and documentation image replacements can remain hidden behind a month-long browser cache. The listing path also makes an unnecessary second listing-function invocation when populating a missing persistent entry.

Files Needing Attention: apps/web/lib/content.ts needs its listing-cache admission and lookup flow revised; apps/docs/nginx.conf needs immutable caching restricted to fingerprinted assets or relaxed for replaceable image URLs.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced proofs for P1 and P2 findings and attached an instrumented harness plus cache-logging artifacts to demonstrate the data cache behavior.
  • T-Rex produced a focused Chromium same-name image cache proof for P1, including the script, its proof output, and the Docs English build output.
  • T-Rex validated exact execution records by reviewing the two log artifacts and confirmed that no repository sources were changed; only requested proof artifacts were created.
  • T-Rex exercised additional contract validation to confirm browser and deployment behavior, including caching rules and asset freshness, as described in the related proofs.
  • T-Rex produced further finding proofs for P1 (proof 3 and proof 6) and P2 (proof 4), noting that these proofs had no additional artifacts attached.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (4)

  1. apps/web/lib/content.ts, line 2160 (link)

    P2 Cache population invokes the listing function twice

    After the eager call at line 2151, a persistent-cache miss runs fetchItems(options) again inside the unstable_cache callback. The second invocation is normally satisfied by the process-local cache, but it is still unnecessary work and makes persistent-cache population depend on the local cache's side effects. Reuse the already fetched result or restructure the eligibility check so the listing function has a single invocation per miss.

    Artifacts

    Instrumented harness source for persistent Data Cache hit and miss cases

    • The authored Node harness extracts the actual wrapper from `content.ts`, instruments its cache seams, and asserts the two target execution paths; it confirms the behavior under test.

    Persistent Data Cache miss with cold process-local cache

    • Executed `NODE_ENV=production node trex-artifacts/data-cache-eager-fetch-harness.mjs persistent-miss` from `/home/user/repo` with exit code 0; it shows two fetch calls and one filesystem walk/parse, confirming the miss invokes fetchItems again.

    View artifacts

    T-Rex Ran code and verified through T-Rex

  2. General comment

    P1 Persistent listing-cache hits still eagerly parse listings

    • Bug
      • For a listing at or below DATA_CACHE_MAX_ITEMS, line 2151 executes fetchItems(options) before unstable_cache can check a pre-populated persistent Data Cache entry. With a cold process-local cache this walks/parses the listing despite a persistent hit.
    • Cause
      • The item-count persistence gate requires result.items.length, and obtains that result by eagerly invoking fetchItems before entering the persistent cache wrapper.
    • Fix
      • Restructure the cache/persistence eligibility decision so a persistent-cache lookup happens before fetchItems, or persist/cache listing-size metadata separately so eligibility can be determined without loading the listing.

    T-Rex Ran code and verified through T-Rex

  3. General comment

    P2 Persistent listing-cache misses invoke fetchItems twice

    • Bug
      • When the persistent Data Cache misses, line 2151 calls fetchItems(options) and the unstable_cache callback at line 2160 calls it again. The second call is normally process-local cached, but it is still an unnecessary invocation and couples persistent-cache population to the local cache.
    • Cause
      • The eager sizing call and the persistent-cache producer both call fetchItems(options).
    • Fix
      • Use the eagerly fetched result directly to populate the persistent cache, or remove the eager fetch by moving the eligibility mechanism outside the wrapper call path.

    T-Rex Ran code and verified through T-Rex

  4. General comment

    P1 Unhashed docs images are cached immutable for 30 days

    • Bug
      • The image-extension location applies a 30-day immutable browser cache policy to stable docs images such as /overview.png. A later deployment that replaces those bytes at the same URL is not requested by Chromium while the cached response remains fresh, so users can continue to see the prior image.
    • Cause
      • apps/docs/nginx.conf:24 selects image files solely by extension, including png, jpg, jpeg, gif, webp, and ico; it does not restrict the policy to content-hashed filenames. Line 26 adds max-age=2592000, immutable.
    • Fix
      • Apply immutable long-term caching only to demonstrably fingerprinted asset paths/files, or use a revalidating/shorter Cache-Control policy for stable public docs images and any other same-name deployment-replaced assets.

    T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "Merge pull request #989 from ever-works/..." | Re-trigger Greptile

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