Skip to content

Opt-in JIT-compiled formatter for the read path - reduce item formatting by 98% - #586

Open
anatolzak wants to merge 9 commits into
tywalch:masterfrom
anatolzak:worktree-jit-optimization
Open

anatolzak wants to merge 9 commits into
tywalch:masterfrom
anatolzak:worktree-jit-optimization

Conversation

@anatolzak

@anatolzak anatolzak commented Aug 8, 2026 •

Copy link
Copy Markdown
Contributor

Closes #585

What

Adds an opt-in, JIT-compiled document formatter for the read path (get / query / scan / parse). Instead of re-walking the schema for every item through the interpreted formatter, ElectroDB compiles a formatter specialized to a single entity's schema once, at Entity construction, and reuses it. Output is byte-for-byte identical; only the per-item formatting cost changes.

Design & safety

  • Opt-in, off by default. Enable per entity with new Entity(schema, { table, client, compile: true }), or globally via the ELECTRODB_COMPILE env var.
  • Graceful fallback. If compilation isn't possible or fails, ElectroDB silently falls back to the interpreted formatter — same behavior as today. The only exception is ELECTRODB_COMPILE=on, which forces compilation and throws CompilationFailed where runtime codegen is unavailable (e.g. strict CSP, new Function blocked).
  • Skips schemas with user getters. Custom getters run arbitrary code, so any schema with a user-defined getter is never compiled and stays on the interpreted path.

Correctness

ELECTRODB_COMPILE=verify runs every read through both the compiled and interpreted paths and throws on any divergence. The full suite now runs under verify mode by default (npm run test:unit:verify, wired into test:run), so both paths are exercised on every CI run in a single pass.

The generated function body is snapshotted in test/snapshots/compile/*.txt so codegen changes surface as a reviewable diff:

  • npm run test:snapshots — verify the generated source hasn't drifted from the committed snapshots.
  • npm run test:snapshots:update — regenerate the snapshots (review the diff to confirm the change makes sense, then commit).

@netlify

netlify Bot commented Aug 8, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for electrodb-dev ready!

Name Link
🔨 Latest commit 1dd3e08
🔍 Latest deploy log https://app.netlify.com/projects/electrodb-dev/deploys/6ac1312b03b6d30008d80e15
😎 Deploy Preview https://deploy-preview-586--electrodb-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@anatolzak

Copy link
Copy Markdown
Contributor Author

hey @tywalch 👋!

finally got to work on the JIT optimization I shared with you a while ago. to give you an idea of the improvement, I have an API route, which takes in total under a second, that processes a lot of items, and I measured the total time spent in formatResponse across that one request at ~175ms. this PR cuts that by about ~95%, so in my case formatting drops from 175ms to single-digit ms.

there's still more on the table. right now the doc client unmarshalls the raw DynamoDB response, which is also quite slow because it's megamorphic, and then ElectroDB walks the result again to format it. we can fold the unmarshalling into the JIT formatter so it will be monomorphic. in my benchmarks that unmarshall pass costs as much as ~50% of ElectroDB's normal formatting (in the same route, 88ms unmarshalling on top of the 175ms format). I manually tested a fused unmarshall + JIT formatter and it's also about ~95% faster.

so combined, that's ~263ms of unmarshall+format CPU per request on this route today.

I will try to work on the unmarshall stuff at some point, but even this PR should already provide much relief and reduce the cpu work done in the formatter.

one nice thing about this PR is that the whole test suite now runs with ELECTRODB_COMPILE=verify, which runs every read through both the JIT-compiled formatter and the default interpreted one and throws if their output ever diverges. so the two paths are checked against each other on every test. I also ran my private repo's full test suite against these changes with the verify flag on, and everything passed

thanks in advance for taking the time to review this 🙏

@anatolzak
anatolzak force-pushed the worktree-jit-optimization branch from 2049ecb to 73fd0cc Compare August 9, 2026 07:27
@anatolzak anatolzak changed the title Opt-in JIT-compiled formatter for the read path Opt-in JIT-compiled formatter for the read path - reduce item formatting by 98% Aug 10, 2026
@anatolzak

Copy link
Copy Markdown
Contributor Author

@tywalch friendly nudge 🙂

Comment on lines +52 to +55
Compilation falls back to the interpreted path (unchanged behavior) when:

- The schema defines any user getter on an attribute. Custom getters run arbitrary code, so those schemas are never compiled.
- Runtime code generation is unavailable (`new Function` is blocked, e.g. under a strict CSP). In `off` or default mode this is a silent fallback; in `on` mode it throws `CompilationFailed`.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I believe we should fail loudly in all cases we can't support, in this case throwing a CompilationFailed error

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@tywalch hey! thank you so much for coming back to this. i really genuinely appreciate it. as far as what you said, I can see where you're coming from, but I tend to disagree here. Whether compilation works also depends on the runtime. For example, Cloudflare restricts new Function evaluation in ways normal runtimes don't. I don't want users to have to change the compile flag depending on the environment they're deploying to.

Also, compilation support depends on the shape of the entity. For example, we don't support getters right now, but when we add support for them later, I don't want the user to have to think about it. If we throw an error, they'd have to keep track of which entities are supported and which aren't.

Honestly, as a user, I'd just want it compiled if possible, and if not, then not. I don't want to have to figure out when it isn't supported and avoid enabling the flag so the whole instantiation doesn't crash. It's just best effort

I'm pretty sure this is how the other libraries do it too: Zod, Effect Schema, and Drizzle all fall back silently.

what do you think?

| `off` | Disable compilation everywhere, ignoring `compile: true`. |
| `verify` | Run both the compiled and interpreted paths on every read and throw if their output diverges. |

`verify` is a correctness check for CI, not for production — it does the work of both paths.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

My gut says we shouldn't have this, but I am interested in hearing more in case I'm missing something. Users should expect us to own and ensure that there is no functional divergence in their queries when using this feature. Unless the user can create a divergence themselves (I don't think they can), we should remove this option.

If this is a flag for internal testing, it would be better to have its own mechanism/env to avoid coupling it to a feature we've committed to supporting. Ideally, correctness diffs are done in test code rather than in the library, but let me know more about the challenges you encountered.

@anatolzak anatolzak Oct 2, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes it's only for internal testing so i totally agree with your point. I will check if we can get it done this way. I'll look at it tomorrow

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@tywalch addressed in the latest commit. let me know what you think

@anatolzak
anatolzak force-pushed the worktree-jit-optimization branch from 73fd0cc to 1dd3e08 Compare October 3, 2026 16:45
@anatolzak
anatolzak requested a review from tywalch October 3, 2026 17:03
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.

Opt-in JIT-compiled formatter for the read path

2 participants