Skip to content

perf(core): parallelize cold-boot init phases and batch exclusive-hook reads#1408

Merged
ascorbic merged 3 commits into
mainfrom
perf/cold-boot-parallel-init
Jun 11, 2026
Merged

perf(core): parallelize cold-boot init phases and batch exclusive-hook reads#1408
ascorbic merged 3 commits into
mainfrom
perf/cold-boot-parallel-init

Conversation

@ascorbic

@ascorbic ascorbic commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Cold-isolate runtime init (EmDashRuntime.create()) runs its phases strictly sequentially, so every phase is a full DB/R2 round trip added to TTFB on the first request an isolate serves. Three changes, all preserving each phase's timing instrumentation and non-fatal error semantics:

  1. rt.pluginsrt.site — the _plugin_state SELECT and the site-info getMany() are independent reads against different tables; they now share one round-trip window under Promise.all. The enabledPlugins derivation (which consumes plugin states) moved after the join.
  2. rt.marketrt.registry — both tiers of installed sandboxed plugins only depend on the sandbox phase, not on each other; when both are enabled they now load concurrently (each is R2 listing + bundle fetches, the slowest part of cold boot on marketplace sites).
  3. Batched exclusive-hook readsresolveExclusiveHooks did one getOption() round trip per registered exclusive hook, sequentially. It now collects all emdash:exclusive_hook:* keys and reads them via OptionsRepository.getMany() in a single query (new optional getOptions on ExclusiveHookResolutionOptions; per-key path kept as fallback for callers that don't provide it). Writes are untouched. A batch-read failure (options table not ready) skips resolution entirely — the identical net effect to every per-key read failing.

Per-phase phase() timings are preserved by keeping each phase as its own phase() call and joining the promises, so Server-Timing still reports each phase's own duration (now overlapping in wall time).

Closes #

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) — unit 173 files / 2,637 tests; integration 71 files / 1,041 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 (if applicable). n/a — no admin UI strings.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/... n/a — performance improvement, no new user-facing surface.

AI-generated code disclosure

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

Screenshots / test output

New tests: batched exclusive-hook resolution parity across all three branches (kept / stale-cleared + auto-select / multi-provider-unselected) with exactly 1 SELECT asserted via a query-counting Kysely plugin (dialect-agnostic via describeEachDialect), batch-failure tolerance (no writes, no selections), and an end-to-end EmDashRuntime.create() integration test against real SQLite asserting each rt.* phase appears exactly once with a finite duration and hook selections persist.

🤖 Generated with Claude Code


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/cold-boot-parallel-init. Updated automatically when the playground redeploys.

…sive-hook option reads

Reduce sequential DB round trips in EmDashRuntime.create():

- Run the rt.plugins (_plugin_state) and rt.site (options) reads under
  Promise.all — independent tables, each phase keeps its own phase()
  timing entry and non-fatal catch.
- Run rt.market and rt.registry sandboxed-plugin loads concurrently when
  both tiers are enabled; both still wait on rt.sandbox and keep their
  own enablement conditionals and error semantics.
- resolveExclusiveHooks() accepts an optional getOptions batch reader;
  the runtime and PluginManager wire it to OptionsRepository.getMany()
  so all current selections load in one query instead of one get() per
  hook. Per-hook set/delete writes and the missing-options-table
  tolerance are unchanged.

Adds unit tests for batched resolution parity, single-read behavior
(dialect-agnostic, query-counted via a Kysely plugin), batch-failure
tolerance, and an end-to-end EmDashRuntime.create() integration test
asserting per-phase timings survive parallelization.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c62106c

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 11, 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 c62106c Jun 11 2026, 03:20 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 11, 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 c62106c Jun 11 2026, 03:21 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 11, 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 c62106c Jun 11 2026, 03:23 PM

@pkg-pr-new

pkg-pr-new Bot commented Jun 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/auth-atproto

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

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

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

emdash

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

create-emdash

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

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

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

@emdash-cms/plugin-cli

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

@emdash-cms/plugin-types

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

@emdash-cms/registry-client

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

@emdash-cms/registry-lexicons

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

@emdash-cms/sandbox-workerd

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-field-kit

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: c62106c

@ascorbic ascorbic marked this pull request as ready for review June 11, 2026 14:26
Copilot AI review requested due to automatic review settings June 11, 2026 14:26
@github-actions github-actions Bot added the review/needs-review No maintainer or bot review yet label Jun 11, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Jun 11, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI 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.

Pull request overview

This PR improves cold-isolate startup performance in EmDashRuntime.create() by overlapping independent initialization phases and reducing N+1 options reads during exclusive-hook resolution, while preserving per-phase timing instrumentation and non-fatal error behavior.

Changes:

  • Parallelize rt.plugins and rt.site initialization work to overlap DB round trips.
  • Run marketplace and registry installed-plugin cold loads concurrently when both are enabled.
  • Add batched exclusive-hook option reads via an optional getOptions() pathway, with new unit/integration coverage and a changeset.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/core/src/emdash-runtime.ts Parallelizes cold-boot init phases and concurrently loads installed plugin tiers; wires batched hook-resolution reads.
packages/core/src/plugins/hooks.ts Adds optional getOptions() API and implements batched exclusive-hook option reads.
packages/core/src/plugins/manager.ts Passes OptionsRepository.getMany() through to hook-resolution as getOptions().
packages/core/tests/unit/plugins/exclusive-hooks.test.ts Adds unit tests for batched resolution parity, query-count assertions, and batch-failure tolerance.
packages/core/tests/integration/runtime/create.test.ts Adds end-to-end cold-boot integration test verifying phases/timings and persisted hook selections.
.changeset/lazy-pugs-brake.md Patch changeset describing the performance improvements.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/core/src/plugins/hooks.ts Outdated
Comment thread packages/core/src/plugins/hooks.ts Outdated
Comment on lines +872 to +875
getOption: vi.fn(async (key: string): Promise<string | null> => store.get(key) ?? null),
getOptions: vi.fn(async (keys: string[]): Promise<Map<string, string | null>> => {
const result = new Map<string, string | null>();
for (const key of keys) {
@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Jun 11, 2026
@ascorbic ascorbic added bot:review Trigger an emdashbot code review on this PR and removed bot:review Trigger an emdashbot code review on this PR labels Jun 11, 2026
…ring>

Per review: getMany never produces null values (missing keys are simply
absent) and resolution only reads the map.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ascorbic

Copy link
Copy Markdown
Collaborator Author

Copilot's type-tightening taken in c62106c: getOptions is now (keys: string[]) => Promise<ReadonlyMap<string, string>>getMany never produces null values (missing keys are simply absent) and resolution only reads the map. Local and test helper aligned. Typecheck and the exclusive-hooks + runtime-create suites pass.

@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Jun 11, 2026
@ascorbic ascorbic merged commit afc065c into main Jun 11, 2026
45 checks passed
@ascorbic ascorbic deleted the perf/cold-boot-parallel-init branch June 11, 2026 16:26
@emdashbot emdashbot Bot mentioned this pull request Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core bot:review Trigger an emdashbot code review on this PR review/needs-rereview Author pushed changes since the last review size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants