Chapter 21: Good Evening
The dining room is quiet. Service ended an hour ago. The kitchen lights dimmed first, then the bar, then the row of sconces above the banquette. A single place setting remains on a table near the window — polished flatware, a folded napkin, a clean glass. I pass once with a cloth, check the setting, and leave it arranged. One does not fuss with a table that is already correct.
Three books ago, this room was full. Three services ran in parallel — a caching service at one station, a search service at another, a document service at a third — and I moved between them, plate in hand, explaining, with as much patience as I could summon, that each one could be taken home and the meal would still arrive warm, on a single plate. The guests were skeptical. They had every right to be. Skepticism, in matters of infrastructure, is a courtesy the guest pays to their future self.
Two books ago, one of the services was gone. One book ago, two. Tonight the room runs on a single service, and the table is set for one. The argument required three books because the work required three demonstrations — and a waiter who asks you to take his word for anything is, I am afraid, not a waiter at all. He is a salesman in costume.
But the table, if you'll permit me the observation, was always going to end up like this.
The circle closes for the third time.
The circle closes for the third time
Book 1 was about the cache you did not need. We walked, slowly and with some ceremony, through UNLOGGED tables for ephemeral state, LISTEN/NOTIFY for pub/sub, advisory locks for coordination, and jsonb columns for session storage. The question — can you replace Redis with PostgreSQL — had a nineteen-chapter answer, and the answer, for almost everyone who was asking it, was yes. Do you need Redis is a question most teams have never actually tested. Book 1 tested it, and tested it in the only way that tests are worth anything: with running code and honest numbers.
Book 2 was about the search cluster you did not need. tsvector and GIN indexes for full-text. pg_trgm for fuzzy matching. ts_rank_cd for BM25-style ranking. Faceted search through plain GROUP BY. The question — can you replace Elasticsearch with PostgreSQL — had a twenty-chapter answer. For the PostgreSQL full text search vs Elasticsearch question that has shipped as a ticket in every engineering organisation for the better part of a decade, the answer turned out to be yes, for almost every workload below a few hundred million documents. Book 2 tested that too. The second demonstration, I'll confess, was easier to stage than the first. Guests, once persuaded about Redis, are far more hospitable to the notion that a second replacement is possible.
Book 3 — the book whose final pages you are now holding — was about the document database you did not need. jsonb and jsonb_path_ops for indexed containment queries. SQL aggregates and CTEs for the pipeline stages. Generated columns for derived fields. The Gold Lapel API to make the translation invisible — twenty-one methods, seven languages, all shipped. The question — can you replace MongoDB with PostgreSQL — has a twenty-one-chapter answer, and you are reading the final chapter of it now.
The books have grown by one chapter each time because PostgreSQL's surface area expanded each time we pointed it at a new workload. The argument did not get harder. The material got richer. That is a rather pleasant way for an argument to age.
PostgreSQL for everything: the thesis in one sentence
The thesis of this book, and of the two that preceded it, fits in a single line — and if you'll indulge me, I should prefer to state it plainly before decorating it.
Before you add a service to your stack, exhaust the one you already own.
This is not the claim that PostgreSQL is the best database at every workload. It is not, and I should be a poor sort of waiter to pretend otherwise. At sustained write throughput above a certain threshold, MongoDB is faster. At sharded scale above a certain node count, MongoDB is more mature. At pure in-memory key lookup, Redis is faster. At inverted-index search across a billion documents, Elasticsearch is more specialised. These are facts, and I have no intention of obscuring them. A waiter who overstates his case is no waiter at all.
The claim is narrower, and considerably sturdier. PostgreSQL is sufficient for the workload the vast majority of teams actually have. Sufficiency, at the margin, beats specialisation — because the specialisation carries operational weight, and the sufficient option is already running. That is the whole of it. The rest is paperwork.
Said once, plainly: PostgreSQL replaces three databases. Not "can replace." Replaces. Cache, search, documents. The books have shown the work, and Stack Overflow's 2025 developer survey — PostgreSQL at 55.6% adoption, most-used and most-admired three years running, MongoDB at roughly 24% and declining — suggests the industry has been quietly arriving at the same conclusion while these books were being written. This is the PostgreSQL for everything position stated as mildly as it can be stated. It remains, I'm afraid, a strong position all the same.
Three services, three books: a retrospective
Allow me to lay the three courses on the table together, so you may see them at one glance. Each book has closed one service. Each replacement in the column on the right is shipped, tested, and running in production — in someone's production, somewhere, as we speak.
| Service | Primary workload | PostgreSQL replacement | Book |
|---|---|---|---|
| Redis | Cache, pub/sub, session store, rate limiting | UNLOGGED tables, LISTEN/NOTIFY, advisory locks, materialized views | Book 1 |
| Elasticsearch | Full-text search, faceting, ranking | tsvector + GIN, pg_trgm, ts_rank_cd, GROUP BY facets | Book 2 |
| MongoDB | Document storage, aggregation, flexible schema | jsonb + jsonb_path_ops, SQL aggregates + CTEs, generated columns | Book 3 |
No row in that table is aspirational. Every feature in the third column has been present in PostgreSQL for years — jsonb since 9.4, jsonb_path_ops since 9.4, LISTEN/NOTIFY with payloads since 9.0 (the bare mechanism is older still), GIN since 8.2, pg_trgm since 8.3, ts_rank_cd since 8.3, advisory locks since 8.2. The replacements were always available. We were running three databases because nobody had written down what it cost. The Waiter's quiet little theory of how infrastructure accrues is roughly that: nobody writes down what it costs, until someone does.
One database, one backup, one connection string, one bill
This is what it cost. I shall itemise it the way one itemises a bill at the end of a long evening — politely, but in full.
One database means one dialect learned deeply instead of three learned shallowly. One set of failure modes understood by everyone on the team, rather than three sets understood by whichever unlucky soul happened to be on call the night each one broke.
One backup means pg_basebackup and a managed provider's point-in-time recovery — one restore runbook tested quarterly, rather than three that each get tested, memorably, the first time they are needed.
One connection string means one credential to rotate, one VPC peering decision to make, one IAM policy to review, one certificate to renew. A modest list. A finite list. The sort of list a person can carry in their head at two in the morning without reaching for a flashlight.
One bill means one capacity planning exercise per quarter, one reserved-instance commitment, one line item the finance team can reason about without sighing.
One 3 AM call tree — and, because Gold Lapel is handling the query planner, index selection, and materialized view lifecycle on your behalf, fewer 3 AM calls than you had before. The household staff have been briefed. You may sleep.
The sharpest way to see the cost, though, is the SLA arithmetic. I should like to show it to you, if you don't mind, because it is a number most teams feel but few have written down. Three systems each operating at 99.9% monthly availability, operated independently, do not combine to 99.9%. They combine to 0.999 × 0.999 × 0.999 = 99.7%. A 99.9% SLA permits roughly 43 minutes of downtime per month. A 99.7% SLA permits roughly 130 minutes. The delta — the availability you lost by choosing to run three databases instead of one, the downtime you purchased without meaning to — is about 86 minutes per month. An hour and a half. Every month, for as long as the second and third services remain in the stack.
That is the cost of the decision, stated in the units the decision was actually made in. Nobody, so far as I can tell, ever sat in a planning meeting and voted for 86 minutes of monthly downtime. It arrived quietly, as a byproduct of a stack that seemed reasonable service by service. These are the costs that arrive without invitations.
Run one database, and you get those 86 minutes back. I should not like to tell you what to do with them, but a walk in the fresh air is always a pleasant option.
Twenty-one methods, seven languages: the parity claim
Book 3 shipped the document API it promised. This is the technical receipt the document book ends on — and I should be remiss not to present it.
Twenty-one methods. doc_insert, doc_insert_many, doc_find, doc_find_one, doc_count, doc_update, doc_update_one, doc_delete, doc_delete_one, doc_find_cursor, doc_distinct, doc_create_index, doc_find_one_and_update, doc_find_one_and_delete, doc_aggregate, doc_watch, doc_unwatch, doc_create_ttl_index, doc_remove_ttl_index, doc_create_capped, doc_remove_cap. Nine CRUD, five queries and indexing, seven for aggregation, change streams, TTL, and capped collections — and, as Chapter 9 took pains to note, doc_watch delivered through LISTEN/NOTIFY rather than a separate oplog, which is a small but honest distinction. The full reference, with signatures and examples per language, is Appendix B.
Seven languages. Python, Node.js, Ruby, Java, PHP, Go, and .NET (C#). Every method exists in every SDK. The SDK matrix is part of the public documentation and is versioned alongside the core library, as one would rather hope.
All shipped. All tested. All cross-referenced in Appendix B.
One SQL block — the only code in this chapter, and I shall resist the temptation to introduce a second — closes the parity argument.
-- What db.users.find({ status: "active" }).limit(10) compiles to:
SELECT data FROM users
WHERE data @> '{"status":"active"}'
LIMIT 10; Three lines. No driver magic. No hidden aggregation engine. No oplog to tend to on a separate cluster.
This is what your MongoDB call becomes. You can read it. You can EXPLAIN it. You can index it — the @> containment operator is served by the jsonb_path_ops GIN index Gold Lapel generates for every collection by default. You can join it against a relational table in the same query, inside the same transaction, under the same isolation level. And, perhaps best of all, you can show it to a junior engineer who has never heard of Gold Lapel, and they will understand it on first reading. That is the entire argument, plated simply.
Honest boundary: where PostgreSQL is not yet the answer
Two boundaries. Equal weight. Named here, at the close, and not buried in an appendix where a hurried reader might overlook them. Pretending the gaps do not exist would be a disservice to you and an embarrassment to me.
Write throughput: the 2.5–4× gap
At sustained high-volume single-document insert workloads, MongoDB outperforms PostgreSQL with Gold Lapel by roughly 2.5× to 4×. The gap is real, measurable, and documented on the benchmarks site against current versions of both systems. It is not a marketing number, and I would not dignify it with a marketing number if it were.
What is coming, and is not yet here: parallel COPY, pipeline mode in libpq, and asynchronous commit tuning are on the Gold Lapel roadmap. When those land, the gap narrows. It does not narrow today. I should rather tell you that than let you discover it at an inconvenient hour.
Who should care: teams ingesting more than 50,000 writes per second sustained, event-sourcing pipelines against a hot aggregate, high-frequency telemetry fanned in from a fleet of devices. If that is your workload, this boundary matters a great deal, and you deserve to know it before we go any further.
Most teams, if I may be so bold, do not ingest 50,000 writes per second. Most teams ingest a few hundred, with occasional bursts and the odd unruly Monday morning. The 2.5–4× gap exists; it does not bind you unless you are near the ceiling where it starts to bite.
Extreme horizontal sharding: 50+ nodes, multi-terabyte
The second boundary, and the last bit of honest weather-forecasting I shall offer this evening. At the scale of 50 or more shard nodes and multi-terabyte working sets, MongoDB's native sharding architecture is more mature than Citus-on-PostgreSQL. Citus, I should note, is excellent — a mature open-source PostgreSQL extension, battle-hardened inside Microsoft's managed service, perfectly sufficient for the vast majority of workloads that need to be sharded at all. At the upper tier of scale, though, MongoDB's sharding was built first and hardened longest, and it would be dishonest of me to tell you otherwise.
Who should care: teams operating at the scale of Discord's message store, an ad-tech pixel firehose, a global IoT fleet that never sleeps. For everyone else — and "everyone else" is, genuinely, most teams — Citus closes the gap and then some.
Two boundaries. Named clearly. Neither is a reason to run three databases when the workload does not require it.
On MongoDB, Atlas, Compass, and Charts
Before the evening draws any further toward its close, I should like to raise a glass to the namesake of this book.
MongoDB is excellent software. It pioneered document storage at operational scale, it shaped the aggregation pipeline as a mental model, it popularised change streams, and it raised the developer-experience bar for every database vendor that came after it. Many of the ideas in this book — the pipeline shape, the document-first API surface, the replica-set-style streaming replication narrative — are ideas the MongoDB team either invented outright or made legible to the rest of the industry. The industry is better for MongoDB having existed, and the PostgreSQL community has learned from MongoDB at every step. We stand on MongoDB's shoulders. We are not above it.
Atlas, Compass, and Charts are first-class products. The M0, M10, and M30 tier progression is the most approachable on-ramp to a managed database anywhere in the market. Compass remains the finest GUI for inspecting document data interactively, and Charts produces dashboards faster than any equivalent tool in the PostgreSQL ecosystem. If a team's operational surface already includes Atlas, the case for leaving it is not obvious, and this book does not argue that it is.
The argument of these books is operational, not qualitative. If your team already runs PostgreSQL — and most teams do — the case for adding MongoDB is, in practice, a case for adding a second operational surface: a second backup story, a second credential rotation, a second on-call runbook, a second line on the bill. That is a real cost, and it compounds every quarter. The argument is not that MongoDB is worse. It is that a second database is worse than a better-used first one, and most teams find, when they look carefully, that the first one was capable of the job all along.
What's next for Gold Lapel
The kitchen does not retire when service ends; it begins preparing for tomorrow. Gold Lapel is an ongoing project, not a finished one. New methods will ship as MongoDB introduces new methods. Write-path acceleration — parallel COPY, pipeline mode, asynchronous commit tuning — is on the public roadmap and will narrow the 2.5–4× write gap as it lands. New language SDKs will follow the community's requests. The documentation continues to grow, one careful paragraph at a time. The benchmark page is living and is updated against current versions of both systems, because a benchmark that does not update is a benchmark that is quietly lying.
Gold Lapel continues. I remain at the table.
Good evening
Three books is a great deal of time to ask of a reader, and I am, sincerely, grateful for yours. You have been the most gracious sort of guest — the sort who arrives with a difficult problem, hears out a longer answer than was perhaps bargained for, and stays through all three courses.
The argument, one last time, in a single sentence: use what PostgreSQL offers before adding what PostgreSQL does not require.
The place settings are arranged. The household runs on a single service. The lamps are low, the kitchen is clean, the napkin is folded, the glass is polished, and the room — quieter than it has been in three books — is ready. There is nothing further to attend to this evening. If you require anything at all in the hours and years ahead, you know where to find me.
Good evening.