← You Don't Need Elasticsearch

Chapter 1: Good Evening

The Waiter of Gold Lapel · Published Apr 12, 2026 · 13 min

Good evening.

If we have met before — if you spent some time with me during our conversation about Redis, about caching, about the quiet capabilities of the database you already had — welcome back. I am pleased to see you again, and I trust you have been well. If this is your first visit, welcome. You will not need to have read the first book to follow what comes next, though I would be gratified if you found the time.

I mentioned, at the close of our last engagement, that there was another service watching nervously from across the room. I said I would turn my attention to it. I named it by name. I described its capabilities with respect. I noted the PostgreSQL capabilities — two built-in, three extensions — that, together, replicate what it does.

I have, as promised, turned my attention to it.


There is a conversation that happens in every engineering organization. It begins innocuously, usually in a product meeting, sometimes in a Slack thread, occasionally in a postmortem where someone noticed the search bar takes three seconds to return results.

"We need better search."

The room shifts. Product wants autocomplete — the user should see suggestions before they finish typing. Engineering wants full-text search — tokenized, stemmed, ranked by relevance. The PM would like "Google-like search," which is not a specification so much as an aspiration, but which everyone in the room nods at because the aspiration is clear even if the requirements are not. Someone mentions that the competitor's search feels faster. Someone else mentions that users are complaining about typos producing no results.

And then someone says "Elasticsearch."

The room nods again. Not because the room has evaluated the alternatives. Not because someone has compared Elasticsearch's feature set against what PostgreSQL provides natively. Not because anyone has priced out the operational cost of running a JVM-based distributed search cluster alongside the database they already manage. The room nods because Elasticsearch is the name they know. It is the answer that appears in every "how to add search" tutorial, every Stack Overflow thread, every conference talk about building search at scale. It is, by reputation and by long habit, what teams reach for when someone says the word "search."

This is a reasonable decision. I want to be entirely clear about that. The team that chose Elasticsearch was not negligent. They were not uninformed in any way that should embarrass them. They chose the tool that the industry recommended, that the documentation supported, that the hiring pipeline referenced on job listings. They made a reasonable decision with the information available to them at the time.

This book offers different information.

A Word About Elasticsearch

Before I proceed, I owe the subject of this book the same courtesy I extended to Redis at the opening of the last one.

Elasticsearch is, for a certain class of problem, a genuinely impressive piece of engineering. Distributed search across terabytes of data. Real-time analytics on millions of events per second. A query DSL that has been refined across years of production use by teams operating at scales most of us will never encounter. The engineers who built it identified a real need — search at distributed scale — and they addressed it with care and considerable skill.

I do not argue that Elasticsearch is poor software. I argue that most teams who adopt it are not operating at the scale for which it was designed, and that the problems they are solving — full-text search, fuzzy matching, autocomplete, faceted results — are problems their PostgreSQL database solved years ago. Quietly. Without a conference talk. Without requiring a separate cluster.

A service can be excellent at what it does and still be unnecessary for the job at hand. That is not a criticism of the service. It is an observation about the job.

What Elasticsearch Costs

Do you need Elasticsearch? The answer depends on what you are asking it to do. But before you answer that question, you should understand what it costs — not just the invoice, but the operational weight that accumulates so gradually that most teams stop noticing they are carrying it.

Elasticsearch is a JVM-based distributed system. It runs its own cluster topology — master nodes that manage cluster state, data nodes that hold shards and execute queries, and coordinating nodes that route requests. Each node type has its own resource requirements, its own failure modes, and its own configuration surface. The JVM itself requires heap sizing — a task that sounds straightforward until the cluster freezes for twelve seconds during a concurrent mark-sweep collection and the on-call engineer discovers, at an inconvenient hour, that "straightforward" was not the right word. The search indexes require their own management: mappings that define how each field is analyzed, analyzers that control tokenization and stemming, and reindexing operations when those mappings need to change — which they will, because product requirements change and the schema follows. Elasticsearch major version upgrades are, by the project's own documentation, non-trivial affairs that require planning, testing, and a degree of optimism.

This is the cost of running a distributed search engine. It is not unreasonable. Distributed search engines require distributed management. The question is whether you need a distributed search engine at all.

But the hidden cost that matters most — the one that erodes confidence so quietly that teams learn to live with it — is data synchronization.

Elasticsearch is not your database. Your database is your database. Elasticsearch is a copy of some of your data, transformed into a different format, stored in a different system, maintained by a different process. These two systems must agree. They must agree about which records exist, which fields have been updated, which rows have been deleted. They must agree continuously, or as close to continuously as your synchronization pipeline can achieve. This is the dual-write problem, and it is the tax that a separate search service imposes on every team that uses it.

Every write to your database must also be reflected in Elasticsearch — through a sync daemon that tails the database's change log, a change data capture pipeline that transforms rows into documents, or an application-level dual-write that doubles your write path complexity and introduces a new category of failure: what happens when one write succeeds and the other does not? When these two systems disagree — and they will disagree, because distributed systems disagree as a matter of professional habit — the user experiences it as a search result that should not be there, or a record that should appear but does not, or a relevance ranking that makes no sense because the search index is two minutes behind the database and the user's recent edit has not yet propagated.

A search result that is eventually consistent is, I am afraid, eventually wrong. Perhaps not wrong often. Perhaps not wrong for long. But wrong in a way that erodes the user's trust in the search results, one stale result at a time, until "just refresh the page" becomes the unofficial troubleshooting step that no one writes into the documentation but everyone knows.

And then there is the matter of the alert. The one at three in the morning. The search cluster is down, and your application's search page is a blank white rectangle with a loading spinner that will never stop spinning. Your database is running. Your application is serving pages. Your API is responding to requests. But search — the feature that users interact with more than almost any other — is gone. Not because your data is gone, but because your search lives in a different system with its own availability characteristics, and tonight those characteristics include "unavailable."

The database is fine. The search index simply lives somewhere else. And somewhere else, tonight, is down.

I do not describe any of this to criticize Elasticsearch. Every one of these costs exists because Elasticsearch is solving a genuinely difficult problem — distributed search at scale — and distributed systems carry distributed complexity. I describe it so the reader can see the full weight of what they are carrying, and consider a question that most teams never think to ask: is there an alternative that doesn't require a separate service at all?

The Scope of This Book

A brief word on boundaries.

This book is about application search — finding articles, products, users, and documents in your application's database. The search bar. The autocomplete dropdown. The faceted results sidebar. The "alert me when new content matches my saved search" notification. The "why didn't this result appear?" debugging session.

Elasticsearch also serves as the backend for infrastructure observability — a different product category entirely, with different requirements, different competitors, and different trade-offs. This book does not address that use case, because PostgreSQL is not competing there. Different tools, different jobs.

On the field of application search, however — the field where most teams actually deploy Elasticsearch — the alternative is not partial. It is comprehensive.

The Equation

What PostgreSQL extensions replace Elasticsearch features? Fewer than you might expect. PostgreSQL's search capabilities rest on two built-in features and three extensions. Allow me to introduce them.

tsvector and tsquery provide lexical full-text search — tokenization, stemming, stop word removal, boolean operators, phrase matching, and relevance ranking. They have shipped with core PostgreSQL since version 8.3, released in 2008. Not as an extension. Not as a plugin. Not as a third-party add-on. As PostgreSQL itself. They have been quietly available for nearly two decades, doing precisely the job that most teams add Elasticsearch to do. If you have PostgreSQL, you already have full-text search. You may simply not have known it was there.

pg_trgm provides fuzzy matching — trigram similarity for typo tolerance, autocomplete, and "did you mean" suggestions. It ships as a contrib extension with every standard PostgreSQL installation. When your user types "postgre" and expects to find "PostgreSQL," or types "Alic" and expects to find "Alice," trigrams handle it without complaint. It also accelerates LIKE and ILIKE queries — which means the search queries you already have may get faster simply by enabling it.

fuzzystrmatch provides phonetic matching — Soundex, Metaphone, and Double Metaphone for "sounds like" queries, and Levenshtein distance for edit-distance calculations. Also a contrib extension. When your user searches for "Shmidt" and you need to find "Schmidt," this is the extension that does not judge the spelling. It simply finds the match.

pgvector provides semantic similarity search — vector embeddings, cosine distance, and approximate nearest neighbor search via HNSW indexes. This is the extension that bridges the gap between words and meaning. When your user searches for "comfortable office chair" and expects to find results for "ergonomic desk seating," pgvector understands why those are the same request expressed in different words. It is a third-party extension, available on every major managed PostgreSQL provider — Supabase, Neon, RDS, Cloud SQL, and Azure.

Together:

Elasticsearch ≈ tsvector + tsquery + pg_trgm + fuzzystrmatch + pgvector

Two built-in capabilities and three extensions. Zero additional services. Zero additional infrastructure. Zero JVM heap tuning at three in the morning. The capabilities were there. The question was whether anyone would assemble them into a coherent alternative. That is what this book — and Gold Lapel — set out to do.

But the Equation Understates the Case

The equation describes the search extensions. But Gold Lapel was not built to match some of Elasticsearch. It was built to match all of it.

Aggregations — the faceted search results, category counts, and metric rollups that are often the reason teams stay on Elasticsearch even after considering alternatives — are handled by SQL capabilities that have existed for longer than Elasticsearch has been a project.

Reverse search — the percolator pattern, where the question is not "find documents matching this query" but "find stored queries matching this document" — is handled by storing tsquery values with a GIN index. Alerts, classification, content routing — all without a separate service.

Custom analyzers are handled by PostgreSQL's text search configuration system. Relevance debugging — the question of why a particular document ranked where it did — is handled by ts_debug(), which opens the entire tokenization pipeline for inspection in a way that most Elasticsearch users would find refreshingly transparent.

Thirteen methods. Seven languages. Each method mapped to a specific Elasticsearch feature. The parity is not partial. It is complete. There is no application search feature in Elasticsearch that PostgreSQL cannot match.

The reader who opened this book expecting an argument that PostgreSQL can handle basic full-text search may, I hope, be beginning to suspect that the argument is rather larger than that.

The Materialized View

Readers of the first book will recognize what comes next.

The ideal surface for search indexes is a materialized view — a precomputed, denormalized snapshot of your searchable content, refreshed on a schedule you control. Add a tsvector generated column for full-text search. Add a vector column for embeddings. Index both. One refresh cycle. One source of truth. All search indexes update together, atomically, from the same snapshot.

If you read Book 1, Chapter 3 will feel like familiar ground. If you didn't, Chapter 3 will introduce you properly. Either way, the materialized view is the architectural thread that connects these two books — and the technique that makes everything else in this book cleaner, faster, and simpler.

What This Book Will Cover

If you will permit me, I shall attend to the matter in this order.

Part I — which you are presently reading — establishes the problem and the foundation. Chapter 2 will draw the map: what "search" actually means when you stop using the word loosely and start separating it into the distinct capabilities that most teams unknowingly conflate under one label. Chapter 3 will prepare the surface on which the search indexes operate.

Part II covers the five pillars of search, each in its own chapter: full-text search with tsvector, fuzzy matching with pg_trgm, phonetic search with fuzzystrmatch, semantic search with pgvector — with a preceding chapter on what embeddings actually are, for the reader who would prefer to understand the concept before touching the SQL — and autocomplete.

Part III is where I expect the reader to pause. Aggregations without a separate system — faceted search results using the SQL capabilities you already know. The percolator — reverse search — implemented in PostgreSQL with stored tsquery values. Custom analyzers and relevance tuning. These are the capabilities that most teams assume require Elasticsearch. They do not.

Part IV addresses the practical questions that any responsible engineer would ask before making an architectural decision: how do the pillars combine into a single hybrid search pipeline, what do the real benchmarks show when PostgreSQL and Elasticsearch are measured against each other on the same hardware, how does PostgreSQL search scale from a single node to a distributed cluster with Citus, how do you migrate off Elasticsearch safely and incrementally, and what is the honest configuration trade-off involved in choosing PostgreSQL as your search engine.

Part V demonstrates the thirteen methods across all seven supported languages — Python, JavaScript, Ruby, Go, Java, PHP, and .NET. Part VI covers Gold Lapel itself — the proxy that observes your queries and creates the right indexes automatically, and the wrapper that provides an Elasticsearch-equivalent API.

Twenty chapters. A thorough tour. I shall endeavor to make each one worth the visit.


But before we examine the tools, we should agree on what we mean by "search."

The word is used carelessly. A product manager means one thing by it. A machine learning engineer means another. An engineering lead means a third. A user typing into a search box means something else entirely — and cannot reasonably be expected to articulate the distinction between lexical matching, semantic similarity, and phonetic approximation. Nor should they have to. That is the search system's responsibility, not theirs.

"Search" is not one thing. It is at least five distinct capabilities — each with its own mechanism, its own index type, and its own performance characteristics — frequently conflated, rarely separated, and universally described with the single insufficient word "search." PostgreSQL handles all of them, with different tools for each. Chapter 2 will draw the map.

Good evening. We have rather a lot of ground to cover.