Chapter 15: The Honest Trade-Off
Fourteen chapters have argued one direction. This chapter, if you'll permit me, argues the other — and I shall not insult your time by pretending we have reached it together without owing you a candid accounting.
There are two places where MongoDB genuinely wins, and I am going to name them with numbers attached. The first is write throughput: for individual insert, update, and delete operations under comparable durability, MongoDB is 2.5–4x faster than PostgreSQL with Gold Lapel on top. The second is horizontal sharding at the extreme end — fifty nodes and beyond, multi-terabyte collections, the scale at which a single logical document store has outgrown any single machine by a wide margin.
Neither of those is a footnote, and I shall not serve them as one. I am giving them equal weight because they deserve it, and because pretending otherwise would squander the credibility the previous fourteen chapters worked rather hard to earn. A waiter who overstates his case has no place in this dining room.
This is the third time this chapter has appeared. The first book argued that PostgreSQL is a cache. The second argued that PostgreSQL is a search engine. This one argues that PostgreSQL is a document database. Each book ends, as this one will, at the same table: here are two things the competitor does better, here is the crossover point, here is the decision framework. The pattern is deliberate. You cannot trust an argument that never names its own boundaries — and I should be rather embarrassed to offer one that did not.
Where This Chapter Sits in the MongoDB vs PostgreSQL Debate
The mongodb vs postgresql search results are, I'm afraid, saturated. MongoDB Inc. publishes comparisons. AWS publishes comparisons. Every database consultancy and every managed-hosting vendor has a take, and most of them — understandably, given the commercial posture each is writing from — land somewhere between an institutional lean and a meticulous neutrality that concludes with a shrug. A shrug, if I may, is not a recommendation.
Meanwhile, the ground has shifted under all of us. The Stack Overflow 2025 Developer Survey placed PostgreSQL at 55.6% adoption — the most-used database, the most-admired, and the most-desired for the third year running. MongoDB sits around 24% and the trend line points downward. FerretDB 2.0 shipped in March 2025, serving the MongoDB wire protocol from a PostgreSQL backend. Microsoft released DocumentDB as an open-source PostgreSQL extension the same year. The "Just Use Postgres" movement has, rather unceremoniously, stopped being a meme and become a default.
None of the existing comparisons I have read name where MongoDB genuinely wins, with numbers, alongside where it does not. This one shall. We will call the place where MongoDB earns its keep the mongodb crossover point, and we will give it concrete thresholds rather than atmosphere.
Honest Boundary #1: MongoDB Wins on Write Throughput
Is MongoDB Faster Than PostgreSQL? The 2.5–4x Write Gap
Yes. I shall not soften that. For individual write operations on comparable hardware with default durability settings, MongoDB is 2.5–4x faster than PostgreSQL + Gold Lapel. That holds across inserts, updates, and deletes. It is not a benchmark trick, and it is not going to disappear with a clever configuration change I have tucked away for this moment.
| Operation | MongoDB (ops/sec) | PostgreSQL + Gold Lapel (ops/sec) | Ratio |
|---|---|---|---|
| Insert (single doc, ~1 KB) | 42,000 | 14,200 | 2.96x |
Update (targeted by _id) | 38,500 | 11,400 | 3.38x |
Delete (targeted by _id) | 45,100 | 14,900 | 3.03x |
The numbers, unvarnished.
Durability for the comparison: MongoDB running against a replica set with the default w: "majority" write concern (which is journaled by default, since writeConcernMajorityJournalDefault is itself true by default), PostgreSQL running with synchronous_commit = on. Both are the defaults that a production deployment would actually run — I have no interest in a contest between each database's most flattering configuration. The full methodology — hardware, document shapes, sustained-load windows — lives on the companion benchmark page, per the standing decision that long-running benchmark detail belongs on the website and not in the book.
Call the range 2.5–4x and be done with it. If your workload is dominated by single-row writes on hot data, that gap is real, and it will arrive on your monthly invoice whether you acknowledge it or not.
WiredTiger's Journal vs PostgreSQL's WAL
The architectural reason for the gap is not subtle, and I would be doing you a disservice to make it mysterious.
PostgreSQL is doing more work per write because it is offering stronger guarantees. The write-ahead log records every change at the page level, with tuple-visibility metadata threaded through every row so that MVCC can reconstruct who saw what at any moment. Every COMMIT forces a flush that survives an OS crash. The engine is keeping a full ACID contract with every concurrent reader and writer in the system, on every write, by default.
MongoDB's WiredTiger storage engine — and it is a thoroughly respectable piece of engineering — uses a journal sitting on top of a B-tree. Group commit amortizes the fsync cost across many concurrent writes, and the journal's format is tuned for the append-heavy patterns that document workloads tend to produce. Cross-document transactions exist, and have since MongoDB 4.0 for replica sets and 4.2 for sharded clusters, but they are not the default path, and the engine is not paying for them on every write.
PostgreSQL offers levers to tune write throughput. I must report that none of them close the gap:
# postgresql.conf — the write-throughput levers
wal_level = replica # minimal is faster but breaks replication
synchronous_commit = on # off = fast and lossy; remote_apply = slow and safe
wal_buffers = 16MB # auto-tuned since 9.1; larger helps burst writes
commit_delay = 0 # microseconds to wait before fsync
commit_siblings = 5 # only delay if this many transactions are active Turning synchronous_commit off will close the gap handsomely, and it will also let you lose committed transactions on a crash. That is not a trade most applications should make quietly, and I would think rather less of a library that made it for you. The other knobs shave percentages. They do not produce a 3x.
This is a feature, not a bug. PostgreSQL is slower on writes because it is doing durability the hard way. You are paying for something. You ought to know you are paying for it.
Gold Lapel Does Not Cache Writes (Today)
Gold Lapel does not buffer writes. Read caching exists — that was the subject of Book 1 — but every call into the write side of the library is a synchronous PostgreSQL write, and I shall not dress it up as anything else.
collection = gl.collection('widgets')
# One round-trip to PostgreSQL.
# One WAL write. One fsync.
# We do not batch this for you.
collection.insert({'sku': 'W-001', 'qty': 12}) One call. One commit. No sleight of hand.
This is deliberate, and I shall defend the choice plainly. Write caching without a carefully specified durability contract would undermine the ACID promise that is the entire reason to prefer PostgreSQL in the first place. Were we to batch your inserts into a background flush, and were the process to die between the return of insert() and the fsync, you would have a library that tells you data is safe when it is not. The book would have no argument left, and I would have no dining room to return to.
So the library does not hide the gap. It exposes PostgreSQL's write path honestly, and it lets the 2.5–4x stand where you can see it.
Write Acceleration Is on the Roadmap — Not Here Yet
Two items sit on the Gold Lapel roadmap that will move the number. I mention them here because pretending they do not exist would be coy, and crediting them in present tense would be dishonest. The truth is simply in between.
The first is parallel COPY. PostgreSQL's COPY command is already the fastest bulk-insert path the database offers; parallelizing it across connections on appropriately partitioned tables can push bulk-load throughput past MongoDB's on many workloads. This is a planned feature, not a shipped one.
The second is libpq pipeline mode, which shipped in PostgreSQL 14 and allows a client to issue multiple statements before reading any response — eliminating the per-query round-trip that dominates latency on small writes. Gold Lapel's write path does not use it yet. When it does, small-insert throughput rises meaningfully without touching durability.
Neither is shipped today. Neither is benchmarked in this book. Neither, I would gently insist, should factor into your decision about what to run this quarter. If you need MongoDB's write throughput today, MongoDB has it today. That is the honest statement, and I would rather offer it than a promise dated sometime.
Honest Boundary #2: MongoDB Wins at Extreme Horizontal Sharding
When MongoDB Sharding Genuinely Wins: 50+ Nodes, Terabytes
The second boundary is architectural, and it is not one we can paper over with a clever extension or a well-tuned configuration.
At the extreme end of horizontal scale — fifty or more nodes, multi-terabyte logical collections, workloads that cannot physically live on a single machine — MongoDB's native sharding is better than anything PostgreSQL currently has. I say "currently" because the ecosystem is moving, but I say it without qualification for today.
Config servers, mongos routers, automatic chunk balancing, zone sharding for geographic placement: these are built into the database, not bolted on afterwards. The operational tooling assumes from the first line of documentation that you might be running two hundred nodes. The balancer works. The chunk migration works. You do not have to construct it yourself from careful parts.
If you are at the scale where this matters, you already know. You are not reading this chapter to discover your own circumstances.
Citus, Postgres-XL, and the State of PostgreSQL Sharding
PostgreSQL has a sharding story. The honest version of it is that Citus is very good software — genuinely so — and at the top end it is still an extension rather than a built-in.
Citus, maintained by Microsoft and released under the AGPL, is mature, actively developed, and closes the gap for most real workloads. It distributes tables across worker nodes by a shard key, handles distributed queries, and supports reference tables that replicate everywhere. Declaring a distributed table looks like this:
SELECT create_distributed_table('events', 'tenant_id'); One line. One sharded table. That ought to be a small miracle, and somehow it is routine.
Multi-tenant SaaS, real-time analytics, high-throughput ingest with aggregation — Citus handles these rather well, often into the tens of nodes, sometimes further. Postgres-XL, a separate sharding fork, has seen slow development, and it is not the path I would recommend betting on in 2026. Declarative partitioning (which landed in PostgreSQL 10 and has improved steadily since) is a single-node feature and should not be mistaken for sharding — it splits a table into partition tables on one machine, not across machines. The distinction is worth keeping straight.
The architectural distinction worth naming most clearly: Citus is an extension. You install it, you configure it, you manage it. MongoDB's sharding is the database itself. At fifty nodes the distinction shows up as operational maturity — balancer behaviour, monitoring hooks, runbooks that have been battle-tested by thousands of deployments rather than dozens.
This is not a dig at Citus. Citus is the best answer PostgreSQL currently has, and for the vast majority of workloads it is more than enough. At the extreme, it is not yet parity, and I shall not claim parity on the household's behalf simply because it would be convenient.
Why "Just Shard PostgreSQL" Isn't a Clean Answer at Extreme Scale
At the hundred-node scale, several operational realities begin to make themselves felt.
Cross-shard transactions are possible on Citus, and they are possible on MongoDB (since 4.2), and on both systems they are expensive. The difference is that MongoDB's culture, documentation, and defaults have embraced that truth for longer. Teams running MongoDB at that scale have already internalised "do not write cross-shard transactions in the hot path." Teams shifting a PostgreSQL application from single-node to sharded frequently have not, and the migration surfaces every cross-shard transaction the codebase has quietly accumulated over the years. That is a rather uncomfortable afternoon of discovery.
Rebalancing when shard keys skew. Distributed DDL on a live cluster. Query-planner awareness of which shards hold which data. Backup coordination across nodes so the restored cluster is consistent. These are all solved in both systems, and they are all more mature in MongoDB at the extreme.
The people running MongoDB on two hundred nodes chose what they chose for a reason. The reason is good. I would be a poor waiter indeed to suggest otherwise.
Also Acknowledged: Two Smaller Points
Schema-Free Onboarding (Gold Lapel Closes Most of This Gap)
MongoDB's oldest marketing line — "just start inserting documents" — is genuinely frictionless, and I credit it as such. No schema, no migration, no DDL. Gold Lapel closes most of that gap with implicit collection creation:
// MongoDB
db.widgets.insertOne({ sku: 'W-001', qty: 12 }) // Gold Lapel
gl.collection('widgets').insert({ sku: 'W-001', qty: 12 }) Both create the collection on first write. The syntax gap is closed.
The operational gap, I must report, is not. You are still running PostgreSQL. There is a postgresql.conf. There are roles and permissions. There are backups you have to own. An Atlas M0 free tier hands you a working cluster in ninety seconds with none of that on your plate. For a weekend prototype, that is a real difference, and I would think poorly of anyone who pretended otherwise.
Purpose-Built Ecosystem Tooling: Compass, Atlas, Charts
Compass is a thoroughly capable GUI for exploring document data. It understands the shape of the data because it was designed for that shape, and "query by example" reads naturally when the tool was built around documents rather than tables.
Atlas is a polished managed service. The tier ladder — M0, M10, M30, and on up — covers prototype through serious production without requiring a re-platform at each step. The operations around backups, monitoring, and version upgrades are well considered, and it shows.
Charts plugs into the document model for dashboards without an intermediate ETL layer. A small pleasure, but a real one.
PostgreSQL's equivalents exist and are excellent. pgAdmin and DBeaver for GUIs. A dozen managed-PostgreSQL services, several of them first-rate. Metabase, Superset, and Grafana for dashboards. They are general-purpose tools that handle documents well; they are not tools designed with documents in mind from the first sketch. The experience is different. Good tools, named with respect.
The Crossover Point: When to Use MongoDB vs PostgreSQL
A Decision Framework With Concrete Thresholds
Allow me, then, to offer the answerable version of "should I use MongoDB or PostgreSQL?"
MongoDB earns its place when all three of the following are true:
- You are sharding across 50 or more nodes, or you will be within the next twelve months. Native sharding maturity begins to matter at roughly that scale.
- Your data is genuinely document-shaped and genuinely massive. Terabytes of nested, schemaless, write-mostly documents — not a few large tables you would prefer not to normalise.
- Your workload is write-dominant. You read little, you write constantly, and the 2.5–4x write gap compounds into real operating cost rather than a rounding error.
These conditions are conjunctive, not disjunctive. All three, not any one. A single one on its own does not flip the decision — I would be rather firm on this, as a false positive here costs you a second database to operate for years. Heavy writes on a single-node workload stay on PostgreSQL and use COPY for bulk paths. Extreme sharding on a read-heavy analytical system stays on PostgreSQL with Citus. Large document volume on a mixed read/write workload stays on PostgreSQL with Gold Lapel.
If all three apply, MongoDB has earned the decision. If any one is missing, PostgreSQL + Gold Lapel is the better answer, and I would be pleased to make the case in greater detail should you wish.
The MongoDB vs PostgreSQL Decision Table
| Workload characteristic | MongoDB | PostgreSQL + Gold Lapel | Winner |
|---|---|---|---|
| Individual write throughput | 2.5–4x faster | Slower (durability cost) | MongoDB |
| Sharding at 50+ nodes | Native, mature | Citus extension, good to ~tens of nodes | MongoDB |
| Multi-document ACID transactions | Supported since 4.0 / 4.2, non-default path | Default, battle-tested | PostgreSQL |
| Relational joins to non-document tables | $lookup, slower at scale | Native SQL joins | PostgreSQL |
| Full-text search | Text indexes, Atlas Search add-on | tsvector, pg_trgm, pgvector | PostgreSQL |
| Aggregation pipeline performance | Baseline | ~84x faster with materialized views (see Ch 12) | PostgreSQL |
| Operational surface area | One database | One database | PostgreSQL (if you already run it) |
| Managed-service onboarding (M0-style free tier) | Polished Atlas ladder | Varies by provider | MongoDB |
The table is short on purpose. Every row is a place where the winner wins on its own merits, not by a whisker.
For the Other 99%: Why PostgreSQL + Gold Lapel Is Strictly Superior
Most applications do not live in the intersection of the three crossover conditions. Most have fewer than fifty nodes, or modest document volume, or a mixed read/write pattern where the 3x write gap is a small fraction of a bigger picture.
For those applications — which is, by my count, ninety-nine of every hundred — PostgreSQL + Gold Lapel wins and keeps winning. ACID guarantees across documents and relational tables. Joins to the user table, the orders table, the existing schema your business already lives in. The full PostgreSQL ecosystem: extensions, hosting, tooling, fifteen years of operational experience earned in the open. One database to back up, one database to monitor, one database to upgrade on a Sunday evening — and frankly, Sunday evenings are quite enough work as it is.
That, if you'll permit the phrase, is Part IV stated plainly — the gains catalogued, the costs named, the crossover point drawn in chalk rather than paint.
Two honest boundaries. A crossover framework. A decision table. It is, I hope, a fair accounting — one that names its own limits rather than hedging them, and specifies where MongoDB wins rather than leaving the question to the reader's imagination. If your workload sits comfortably outside the one-percent corner, the next practical question is no longer whether to move onto PostgreSQL. It is how to shape the tables when you arrive.
And the shape, I must report, is the single decision that separates a migration which compounds into a long-term asset from one the team quietly regrets six months later. Most MongoDB-to-PostgreSQL migrations do not fail at data transfer; they fail at the schema — one data JSONB column declared on day one, every hot-path query extracting JSONB on day ninety, every foreign key the team now wishes it had unreachable. The work is not difficult. It is simply worth doing before the migration script runs rather than after.
Chapter 16 takes up that work directly. Three schema models — pure JSONB, pure columns, and the hybrid between them. Generated columns for the fields you want indexed without asking the application to change how it writes. The GIN operator class decision (jsonb_ops versus jsonb_path_ops) that discards half the index's value if chosen incorrectly. Naming conventions. And the per-table spectrum — which collections want the whole document in one column, which want every field promoted, and which want the hybrid that, in practice, carries roughly eighty percent of production migrations.
If you'll follow me through to the next chapter, the tables are waiting to be laid properly. Thirty quiet minutes of schema reconsideration now is, as these things go, considerably cheaper than thirty weeks of regret later.