Skip to content

perf(core): cut redundant queries on content pages#1498

Merged
ascorbic merged 2 commits into
mainfrom
perf/reduce-warm-queries
Jun 16, 2026
Merged

perf(core): cut redundant queries on content pages#1498
ascorbic merged 2 commits into
mainfrom
perf/reduce-warm-queries

Conversation

@ascorbic

@ascorbic ascorbic commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Cuts redundant database queries when rendering content pages. All changes are same-shape, behavior-preserving, and help every backend (Node SQLite, D1, Postgres) — no template changes required.

Three fixes, found by tracing the query waves on a single-post route:

  1. Cache getWidgetArea per request. It was the only content helper not wrapped in requestCached, so a page rendering the same area twice re-queried.
  2. Share the taxonomy term-count aggregate. getTaxonomyTerms re-ran a full content_taxonomies GROUP BY taxonomy_id scan for every taxonomy — so the Categories and Tags widgets each ran the same unscoped aggregate. The counts are locale-independent (keyed by translation_group), so they're now fetched once per request via a shared requestCached entry.
  3. Make getTermsForEntries cache-aware. It always issued a batched query even when the per-entry terms were already hydrated and primed in the request cache (e.g. related-posts tags). It now reuses primed entries and only queries the misses. To preserve the previous contract it returns private copies (callers that mutate the result can't poison the shared cache) and orders the miss path by label to match the hydration primer.

Verified with the query-count harness: the single-post fixture route drops 18 → 17 queries (the fixture under-exercises the widget paths; on a richer layout with both Categories+Tags widgets and related-posts term lookups the saving is larger). Updated scripts/query-counts.snapshot.sqlite.json; the d1 snapshot will be refreshed by CI.

This is the first ("Tier 1") slice of a broader warm-query-reduction effort; a follow-up will add a transparent middleware prefetch for site-global layout data (menus/widgets/taxonomies). Two further candidates were investigated and deliberately deferred: dropping the per-request byline-fields version read (cross-isolate consistency trade-off) and replacing the Archives widget's unbounded fetch with a GROUP BY count (changes the widget's data contract).

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change) — full emdash suite (3934) passes; added 2 regression tests
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation — n/a, no UI strings
  • I have added a changeset (emdash patch)
  • New features link to an approved Discussion — n/a, perf refactor

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: claude-opus-4-8 (OpenCode)

Screenshots / test output

Query-count harness (sqlite), single-post route: db.count 18 → 17. An adversarial review pass on the getTermsForEntries change caught and fixed three regressions before this PR: shared-array aliasing (now returns private copies), inconsistent ordering between cache-hit and DB-miss entries (now both label-ordered), and a rejected peeked promise bypassing the isMissingTableError graceful-degrade guard (rejections now fall back to the guarded query path). Regression tests added for cache reuse and mutation-safety.


Try this PR

Open a fresh playground →

A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.

Tracks perf/reduce-warm-queries. Updated automatically when the playground redeploys.

- Cache getWidgetArea per request (it was the only content helper not
  request-cached).
- Fetch taxonomy term usage-counts once per request via a shared
  request-cached aggregate, instead of re-running the full content_taxonomies
  GROUP BY for every taxonomy widget (Categories + Tags each ran it).
- Make getTermsForEntries cache-aware: reuse already-hydrated per-entry terms
  from the request cache and only query the misses. Returns private copies so
  callers mutating the result can't poison the cache, and orders the miss path
  by label to match the hydration primer.

All same-shape, all backends. Adds regression tests for cache reuse and
mutation-safety. Updates the sqlite query-count snapshot (post route -1).
@changeset-bot

changeset-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: da62330

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 14 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 16, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
docs da62330 Jun 16 2026, 07:34 AM

@changeset-bot

changeset-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8c6f889

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 14 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 16, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache da62330 Jun 16 2026, 07:35 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 16, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground da62330 Jun 16 2026, 07:36 AM

@github-actions github-actions Bot added review/needs-review No maintainer or bot review yet area/core size/L query-count changed PR diff modifies query-count snapshot files labels Jun 16, 2026
@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Query-count snapshot changes

20 routes changed, total Δ -24 queries.

SQLite

Route Before After Δ
GET / (cold) 7 6 -1
GET / (warm) 7 6 -1
GET /category/development (cold) 11 10 -1
GET /category/development (warm) 10 9 -1
GET /posts (cold) 7 6 -1
GET /posts (warm) 7 6 -1
GET /posts/building-for-the-long-term (cold) 18 16 -2
GET /posts/building-for-the-long-term (warm) 18 16 -2
GET /tag/webdev (cold) 10 9 -1
GET /tag/webdev (warm) 10 9 -1

D1

Route Before After Δ
GET / (cold) 17 16 -1
GET / (warm) 6 5 -1
GET /category/development (cold) 20 19 -1
GET /category/development (warm) 9 8 -1
GET /posts (cold) 16 15 -1
GET /posts (warm) 6 5 -1
GET /posts/building-for-the-long-term (cold) 28 26 -2
GET /posts/building-for-the-long-term (warm) 17 15 -2
GET /tag/webdev (cold) 20 19 -1
GET /tag/webdev (warm) 9 8 -1

Comparing snapshot files between base and head. Updated automatically on each push.

@pkg-pr-new

pkg-pr-new Bot commented Jun 16, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@1498

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@1498

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@1498

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@1498

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@1498

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@1498

emdash

npm i https://pkg.pr.new/emdash@1498

create-emdash

npm i https://pkg.pr.new/create-emdash@1498

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@1498

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@1498

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@1498

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@1498

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@1498

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@1498

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@1498

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@1498

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@1498

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@1498

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@1498

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@1498

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@1498

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@1498

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@1498

commit: da62330

@github-actions

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@ascorbic

Copy link
Copy Markdown
Collaborator Author

/review

@ask-bonk

ask-bonk Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Changeset is well-formed, user-facing, present tense.

This PR is high-quality, narrow in scope, correctly tested, has performance evidence, and the author already self-reviewed adversarially. I cannot find a real bug.

LGTM!

github run

@ascorbic ascorbic merged commit 3d423a7 into main Jun 16, 2026
46 checks passed
@ascorbic ascorbic deleted the perf/reduce-warm-queries branch June 16, 2026 13:06
@emdashbot emdashbot Bot mentioned this pull request Jun 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core cla: signed overlap query-count changed PR diff modifies query-count snapshot files review/needs-review No maintainer or bot review yet size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant