A test runner lives or dies by two things: how fast it runs and how much you can trust the results. We picked PostgreSQL because the alternatives that were faster couldn't be trusted. This page walks through that decision honestly — including the measurable speed we gave up, and why we gave it up.
A test runner has one job: tell you whether your code is correct. Everything else is secondary. A fast test result that disagrees with Salesforce is worse than a slow one that agrees. If Nimbus says a test passes and the deploy fails in production, the runner has failed — no matter how fast it was.
That framing was the deciding factor. Every candidate we evaluated was faster than PostgreSQL on some axis. Most were simpler to embed. But every alternative also required us to either reimplement Salesforce-specific behavior in application code, or silently accept that Nimbus would disagree with production on certain patterns.
We weren't willing to do that. Not because fidelity is a marketing word, but because a local test runner without fidelity is just a mock framework with more lines of code.
@isTest
static void testBulkInsert() {
// Bulk insert with intentional duplicates,
// allowing partial success.
List<Account> accounts = buildAccountsWithDupes();
Database.SaveResult[] results =
Database.insert(accounts, false);
// On Salesforce: one constraint error summary
// at the end of the batch.
//
// On most embedded databases: mid-batch error
// on the first duplicate row, with a message
// no Salesforce developer has ever seen.
//
// On Nimbus: matches Salesforce exactly.
System.assert(results[0].isSuccess());
}A database has to satisfy all of the following to run Apex tests correctly. None of them are arbitrary — each one maps directly to Salesforce behavior that real test suites depend on.
Deferrable constraints are the decisive requirement. SQL standard feature F721 is implemented by exactly two production databases: PostgreSQL and Oracle. Every other candidate fails here. After that, true concurrent writes rule out most of the rest, savepoints rule out column-store analytical databases, and a rich type system rules out key-value stores and SQLite.
PostgreSQL doesn't win this comparison by being the best option. It wins by being the only option left standing.
We built a working alternative backend on a branch. Migrated every query path. Retargeted the SOQL translator. Replaced savepoints with re-run logic. Worked around deferrable constraints at the application level where we could. Then we ran real test suites against it.
The results confirmed the requirement analysis concretely. On small projects, most tests passed but the backend was slightly slower because its single-writer model forced our parallel workers to serialize. On a production-scale suite, almost 20% of tests failed — and the failures broke down cleanly along the missing-requirement axes: broken @testSetup isolation, stale data visible across test methods, concurrent transaction conflicts, aggregate type mismatches.
These aren't edge cases. They're the patterns every non-trivial Apex test suite relies on: trigger-heavy code paths, shared @testSetup state across methods, bulk DML with error handling. The alternatives don't work for Nimbus because they can't — not because we didn't try hard enough.
An honest comparison starts with naming the cost. Choosing PostgreSQL means choosing a separate process. Every query pays for wire-protocol serialization, parse and plan, kernel context switching, and result deserialization. Even over a unix domain socket, a Postgres round-trip is measured in hundreds of microseconds. An in-process database — a library call — does the same work in tens of microseconds.
Multiplied across a full test run that issues tens of thousands of queries, that's a real gap. Not small. Real. An in-process database would run Nimbus faster. We know this because we measured it. We instrumented every database call in Nimbus and compared the cost breakdown to what an in-process database would look like.
The gap is structural, not a configuration we failed to tune. It's the toll Postgres charges for being a real database with a real wire protocol, running in its own process. We can shave the edges but we can't close the middle.
Once we accepted the separate-process cost, we focused on making every other layer of the stack as lean as possible. Nimbus talks to Postgres over a unix domain socket rather than TCP loopback, because for small messages that alone is a meaningful win. It uses a modern Postgres driver with binary protocol support. The Postgres binary itself is stripped down to the three executables Nimbus actually uses, removing everything that adds startup cost without adding value. Durability features that make no sense for a test runner — fsync, WAL, autovacuum — are turned off. The embedded bundle is cached once per machine and reused across projects.
Each of these individually is small. Together they close enough of the gap that Nimbus runs thousands of tests in seconds rather than minutes. Not as fast as an in-process database could be in theory, but fast enough that test feedback feels instant at the scales most projects live at.
Every optimization was measured against real workloads. Some experiments looked promising on paper and failed in practice — a per-test query result cache saved large amounts of database CPU but produced zero wall-clock improvement, because the real bottleneck had already moved elsewhere. We reverted it and kept the measurement infrastructure. Knowing where the next improvement has to come from is more valuable than a speedup that doesn't move.
PostgreSQL is slower per-query than an in-process database. That's a structural cost we pay on every test run. We can't tune it away. We tried — and when we couldn't, we told you about it on this page.
In exchange: 100% Salesforce behavioral fidelity. Every test that passes on Nimbus passes on the platform, and every failure on Nimbus would fail on the platform too. No surprises at deploy time. No "works locally, fails in production" tickets. No mental overhead tracking which test patterns are safe on the local runner and which aren't.
For a test runner, that's the right side of the tradeoff. A fast test runner that lies to you is worse than a slightly slower one that tells you the truth.
If an embeddable database ever exists that preserves the correctness requirements listed above, we'll re-evaluate on the day it ships. Until then, this is the architecture: Postgres as fast as Postgres gets, measured and honest, with the instrumentation to know exactly where the next improvement has to come from.
Point Nimbus at your SFDX project and run. No configuration required.