Skip to content

perf(core): drive folded term hydration from the pivot on SQLite (#1722)#1726

Merged
ascorbic merged 3 commits into
emdash-cms:mainfrom
MA2153:fix/folded-term-hydration-join-order
Jul 6, 2026
Merged

perf(core): drive folded term hydration from the pivot on SQLite (#1722)#1726
ascorbic merged 3 commits into
emdash-cms:mainfrom
MA2153:fix/folded-term-hydration-join-order

Conversation

@MA2153

@MA2153 MA2153 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes a query-plan regression in folded taxonomy-term hydration. foldedHydrationSelects (packages/core/src/loader.ts) folds per-entry term hydration into the content query as a correlated JSON-array subquery using a plain JOIN. On stats-blind SQLite/D1 (D1 never runs ANALYZE / maintains sqlite_stat1), the planner is free to drive that subquery from taxonomies by locale — enumerating every term in the locale and probing the pivot once per emitted row:

SEARCH t USING INDEX idx_taxonomies_locale (locale=?)          -- ALL terms in the locale
SEARCH ct USING COVERING INDEX ... (collection=? AND entry_id=? AND taxonomy_id=?)

On a site with ~1,540 terms in the active locale, a single unfiltered 25-row list page reads ≈ 25 × 1,540 ≈ 38.5K rows — the floor for every list page, paid on every cache miss.

The fix pins the join order with CROSS JOIN on the SQLite path so the subquery always drives from the content_taxonomies pivot by (collection, entry_id) and probes taxonomies by translation_group — a handful of reads per entry, independent of taxonomy size and of statistics:

SEARCH ct USING COVERING INDEX ... (collection=? AND entry_id=?)
SEARCH t USING INDEX idx_taxonomies_translation_group (translation_group=?)

Postgres keeps statistics and rejects CROSS JOIN … ON, so it remains a plain JOIN there (byte-identical SQL to before). The byline subquery in the same function gets the same CROSS JOIN hardening for consistency and future-proofing — its composite (translation_group, locale) unique index already yields the pivot-driven plan today, so this is a no-op there but guards against future index changes.

Closes #1722

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 (0 diagnostics)
  • pnpm test passes (targeted: 548 tests across loader/query/fold/taxonomy/byline suites)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings — n/a, no admin UI strings; no messages.po changes included
  • I have added a changeset (emdash patch)
  • New features link to an approved Discussion — n/a, this is a performance fix, not a feature

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 4.8 (Claude Code)

Screenshots / test output

New regression test (packages/core/tests/integration/loader-fold-plan.test.ts) captures the real loader query, runs EXPLAIN QUERY PLAN on a stats-blind (D1-shaped) SQLite DB, and asserts the taxonomy subquery drives from the pivot (idx_taxonomies_translation_group) rather than scanning idx_taxonomies_locale. It was written test-first: it failed against the old plain-JOIN code and passes after the fix.

 Test Files  44 passed (44)
      Tests  548 passed (548)

🤖 Generated with Claude Code

…sh-cms#1722)

`foldedHydrationSelects` folded the per-entry taxonomy-term hydration as a
plain `JOIN`. On stats-blind SQLite/D1 (D1 never maintains `sqlite_stat1`)
the planner drove the correlated subquery from `taxonomies` by locale,
enumerating every term in the locale and probing the pivot once per emitted
row — tens of thousands of rows per list page on large-taxonomy sites.

Pin the join order with `CROSS JOIN` on the SQLite path so the subquery
drives from the `content_taxonomies` pivot by `(collection, entry_id)` and
probes `taxonomies` by `translation_group`. Postgres keeps statistics and
rejects `CROSS JOIN … ON`, so it stays a plain `JOIN` there. The byline
subquery gets the same hardening for consistency (its composite
`(translation_group, locale)` unique index already yields the good plan).

Adds an EXPLAIN QUERY PLAN regression test asserting the real loader query
drives from the pivot, not `idx_taxonomies_locale`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9e14c66

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

This PR includes changesets to release 16 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/do-demo-site Patch
@emdash-cms/do-solo-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

@github-actions github-actions Bot added area/core size/M review/needs-review No maintainer or bot review yet labels Jul 2, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jul 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/auth-atproto

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

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

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

emdash

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

create-emdash

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

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

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

@emdash-cms/plugin-cli

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

@emdash-cms/plugin-types

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

@emdash-cms/registry-client

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

@emdash-cms/registry-lexicons

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

@emdash-cms/sandbox-workerd

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-field-kit

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: 9e14c66

@emdashbot emdashbot Bot left a comment

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.

Approach judgment: this is a focused, well-motivated fix for a real D1/SQLite query-plan regression. Pinning the join order with CROSS JOIN on SQLite is the idiomatic way to stop a stats-blind planner from driving a correlated hydration subquery from the wrong table, and the change is byte-identical on Postgres. Adding a regression test that asserts the plan shape is exactly the right shape of test for this kind of issue.

What I checked:

  • The diff: only loader.ts, the new loader-fold-plan.test.ts, and a changeset.
  • SQL safety: identifiers use sql.ref(), values are parameterized; no injection risk.
  • Locale filtering: the subqueries still correlate on t.locale = ${o}.locale and b.locale = ${o}.locale, so per-entry locale semantics are preserved.
  • Index/schema context: the idx_taxonomies_translation_group and idx__emdash_bylines_translation_group (and the partial idx_bylines_group_locale_unique) indexes exist, so the pivot-driven plan the CROSS JOIN enforces is realizable.
  • Existing test coverage: loader-fold.test.ts already exercises folded output on both dialects; the new test adds the plan-shape guard without duplicating output assertions.
  • Changeset: present and user-facing.

Headline conclusion: the code change is correct and the test effectively guards the regression. I have only minor feedback: a misleading comment about Postgres and a chance to broaden the plan test slightly.

Comment thread packages/core/src/loader.ts
Comment thread packages/core/tests/integration/loader-fold-plan.test.ts Outdated
@MA2153 MA2153 marked this pull request as draft July 2, 2026 12:33
@MA2153 MA2153 changed the title fix(core): drive folded term hydration from the pivot on SQLite (#1722) perf(core): drive folded term hydration from the pivot on SQLite (#1722) Jul 2, 2026
Extend loader-fold-plan to assert the pivot-driven plan for the byline
subquery and the loadEntry path, not just taxonomy-on-loadCollection —
both share the CROSS JOIN join-order hardening from emdash-cms#1722. The byline
composite (translation_group, locale) index already yields the good
plan, so this guards against a future index change regressing it.
Confirmed non-vacuous: forcing a plain JOIN on the SQLite path fails
both paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added size/L and removed review/needs-review No maintainer or bot review yet size/M labels Jul 2, 2026
@MA2153 MA2153 marked this pull request as ready for review July 2, 2026 12:53
@github-actions github-actions Bot added the review/needs-rereview Author pushed changes since the last review label Jul 2, 2026

@emdashbot emdashbot Bot left a comment

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.

This PR is a focused, well-motivated performance fix for a real D1/SQLite query-plan regression. Pinning the join order with CROSS JOIN on SQLite is the idiomatic way to stop a stats-blind planner from driving a correlated hydration subquery from the wrong table; the Postgres path remains byte-identical. The regression test asserts the plan shape, which is the right kind of test for this category of bug.

I re-read packages/core/src/loader.ts, the new packages/core/tests/integration/loader-fold-plan.test.ts, the changeset, and the relevant migrations/indexes. The SQL is safe (sql.ref for identifiers, parameterized values), locale filtering is preserved in both subqueries (t.locale = ${o}.locale / b.locale = ${o}.locale), and the asserted indexes (idx_taxonomies_translation_group, idx_bylines_group_locale_unique) exist. The test runs the real loader query on a stats-blind in-memory SQLite DB and guards both loadCollection and loadEntry for both folded subqueries.

From the prior review: the Postgres comment is accurate (Postgres grammar does not allow CROSS JOIN ... ON), and the plan test now covers both call sites and both subqueries, so the earlier minor feedback is satisfactorily addressed. No blocking issues remain.

@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview Author pushed changes since the last review labels Jul 2, 2026

@ascorbic ascorbic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks

@ascorbic ascorbic merged commit cdca719 into emdash-cms:main Jul 6, 2026
45 checks passed
@emdashbot emdashbot Bot mentioned this pull request Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core review/approved Approved; no new commits since size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

foldedHydrationSelects term subquery drives from taxonomies by locale on SQLite/D1, scanning all locale terms per emitted row

2 participants