← You Don't Need Elasticsearch

Chapter 17: The Configuration Trade-Off

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

I have spent sixteen chapters making recommendations. Allow me now to make a disclosure.

Every recommendation in this book has been honest. Every benchmark has been real. Every honest boundary section has named a limitation clearly and without qualification. But a book that demonstrates sixteen chapters of capability and never discusses cost is not honest — it is selective. And I do not intend to be selective with you.

PostgreSQL matches Elasticsearch feature-for-feature for application search. Thirteen methods. Seven languages. Complete parity. The migration is practical. The scaling path is clear. The architecture patterns are blueprinted. All of that is true.

What is also true — and what I owe you before this book enters its final chapters — is that choosing PostgreSQL search asks something of you that choosing Elasticsearch does not. The trade-off is not features. The parity there is complete. The trade-off is configuration responsibility. And I would rather you know this now, from me, than discover it during implementation from a stack trace.

What This Chapter Is Not

I should be precise about this, because the framing matters.

This is not a list of Elasticsearch features that PostgreSQL cannot match. The parity is complete — thirteen methods, each mapped to a specific Elasticsearch capability, each demonstrated with runnable SQL across twelve chapters.

The trade-off is operational, not functional. “Can PostgreSQL do this?” is answered: yes, across every capability this book has examined. “Can my team configure PostgreSQL to do this, given our operational constraints?” is a different question — and it is the question this chapter helps you answer honestly.

The distinction matters. The capability exists. The question is whether your context allows you to use it.

The Configuration Responsibility

Elasticsearch is a dedicated service. You deploy it, configure index mappings, and search works. The indexing, the analysis, the clustering — the service handles it. You pay a different cost: a separate JVM cluster, a sync pipeline, additional monitoring, additional on-call. But the search configuration itself is Elasticsearch’s responsibility.

Choosing PostgreSQL search means owning some PostgreSQL configuration. I will be specific about what that means.

Extensions

Three extensions power the search capabilities beyond built-in full-text search:

  • CREATE EXTENSION pg_trgm — fuzzy matching and autocomplete (Chapters 5, 9)
  • CREATE EXTENSION fuzzystrmatch — phonetic search (Chapter 6)
  • CREATE EXTENSION vector — semantic search (Chapter 8)

All three are available on every major managed provider — Supabase, Neon, RDS, Cloud SQL, Azure Database for PostgreSQL. Gold Lapel handles creation lazily: the first call to a method that needs an extension triggers CREATE EXTENSION IF NOT EXISTS. You do not install extensions manually.

The constraint: some organizations require DBA approval for extension creation. Some have policies restricting which extensions are allowed. The extensions must be available in the PostgreSQL installation — Gold Lapel can create them, but it cannot conjure them from nothing. If your provider does not offer pgvector, semantic search is not available. I would rather you know this before Phase 3 of the migration than during it.

wal_level and shared_preload_libraries

Gold Lapel’s full write detection uses logical decoding, which requires wal_level = logical. This is a postgresql.conf change that requires a server restart.

On managed providers, the situation varies. Supabase and Neon set wal_level = logical by default. RDS and Cloud SQL require a parameter group change through the provider’s console. Some providers may not expose the setting at all.

This is the configuration step most likely to require coordination with a DBA or infrastructure team. It is a one-time change, not ongoing maintenance. But I will not pretend that “one-time” means “trivial” in every organization. Some teams can make this change in an afternoon. Others require a change request, a review board, and a maintenance window. I do not know which kind of team you are on. You do.

pgvector

pgvector is a third-party extension, not a PostgreSQL contrib extension. It is available on every major managed provider. On self-hosted PostgreSQL, it requires separate installation via package manager or build from source.

This is rarely a blocker — pgvector’s adoption is broad enough that availability is standard. But it is worth verifying before committing to the migration, not after.

How Gold Lapel Mitigates This

Gold Lapel reduces the configuration burden systematically:

  • Auto-detection at startup: checks pg_available_extensions, installs what is available, logs what is not. The dashboard shows an environment status grid — green, yellow, or red for each extension and configuration setting.
  • Lazy extension creation: first call to a method triggers CREATE EXTENSION IF NOT EXISTS. No manual installation step.
  • Graceful degradation: if an extension is unavailable, the methods that depend on it are disabled. The methods that don’t need it continue to work. Full-text search — tsvector, GIN indexes, ts_rank, ts_headline — requires no extensions at all. It is built into PostgreSQL. You can run a complete lexical search system with zero extensions.
  • The quickstart guide: walks through every configuration step for every major managed provider.

Gold Lapel helps. It cannot eliminate the responsibility entirely. Some teams cannot touch postgresql.conf. Some managed providers do not expose certain settings. Some organizations have a DBA gate between developers and schema changes. I acknowledge these constraints because they are real, and because acknowledging them is more useful than pretending they do not exist.

The Scope Line

This book is about application search — finding articles, products, users, documents in your application’s database.

Elasticsearch also serves as the backend for infrastructure observability — log aggregation, APM tracing, metric dashboards. That is a different product category. PostgreSQL is not competing there. This book does not address it. Different tools, different jobs. The Waiter knows his menu. He does not presume to comment on the establishment next door.

Where PostgreSQL Has the Advantage

The trade-off runs in both directions. PostgreSQL asks for configuration responsibility. In return, it provides capabilities that Elasticsearch structurally cannot match. I present these not as consolation but as genuine advantages — because they are.

Row-Level Security (RLS)

PostgreSQL’s RLS policies enforce access control at the database level. If a user does not have permission to see a row, it does not appear in their search results. No separate authorization layer. No post-search filtering. No application-level WHERE clause that a developer might forget on a Friday afternoon. The database enforces it.

Does Elasticsearch support row-level security? No. Implementing per-user search results in Elasticsearch requires application-level filtering (one missed WHERE clause is a data leak), a separate permission service (additional infrastructure to build and maintain), or a third-party security plugin (additional complexity and cost).

For single-table search (generated columns on base tables), RLS applies directly:

SQL
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON documents
    USING (tenant_id = current_setting('app.tenant_id')::int);

Two statements. Every search query on the documents table is now tenant-isolated. Automatically.

For multi-table search using materialized views (Chapter 3’s pattern), RLS cannot be applied to the materialized view directly — it is defined for tables, not views. The solution is a security barrier view (Chapter 14, Pattern 2): a view with security_barrier that filters by tenant before any user-defined functions execute. The materialized view holds all data; the security barrier view enforces isolation at query time. The developer queries the secure view. The database enforces the boundary.

Either way — RLS on base tables or a security barrier view on materialized views — the developer does not add a WHERE clause. The database handles it. Without application code changes. Without a developer remembering to add a filter. Without a code review catching a missing WHERE clause.

I consider this the strongest security argument for PostgreSQL search. It has no Elasticsearch equivalent. For multi-tenant applications and applications with complex access control, this is not a nice-to-have. It is the difference between security enforced by policy and security enforced by hope.

ACID Consistency

Search is transactionally consistent with the database. No sync lag. No stale indexes. Write a row, search it immediately. Delete a row, it is gone from search immediately. There is no window — however brief — where the search index disagrees with the database.

Elasticsearch requires a sync pipeline. The sync introduces lag. The lag introduces inconsistency. The inconsistency introduces bugs that are uniquely difficult to diagnose: “the user deleted their account, but their profile still appears in search results for 30 seconds.” The bug is real. It is intermittent. It resolves itself. It is nearly impossible to reproduce in a debugging session. PostgreSQL eliminates this category of bug entirely. The search IS the database. There is nothing to sync. There is nothing to lag.

JOINs in Search Queries

Search results can JOIN against other tables in the same query. Enrich search results with user profiles, category hierarchies, pricing tiers, permission flags — without a second API call, without denormalization, without duplicating data into every search document.

Elasticsearch is a document store. It does not support JOINs. Enriching search results with related data requires denormalization — copying related data into every document, updating it everywhere when it changes — or separate API calls after the search returns. Both add complexity. Both add failure modes.

PostgreSQL is a relational database. JOINs are what it does. The search lives in the same database as the data it relates to. The enrichment is a JOIN clause, not a separate service call. I find this a natural way to build search, because the data that makes search results useful is the data that lives alongside the search results — and in PostgreSQL, they share the same home.

The Decision Framework

ConsiderationPostgreSQL SearchElasticsearch
Feature parityComplete (13 methods)Native
Configuration responsibilityExtensions, wal_level, pgvectorDedicated service — less PG config
Row-level securityNative (CREATE POLICY)Not available
Data consistencyACID — immediateEventually consistent
JOINsFull SQL JOINsNot available (denormalization)
InfrastructureExisting PostgreSQLAdditional service + sync pipeline
Sync pipelineNot neededRequired
Observability (logs/APM)Not addressed by this bookCore capability
Operational burdenDatabase you already runAdditional cluster to maintain

I present this table as a summary, not a verdict. The right choice depends on your team, your constraints, and your priorities. If you can configure PostgreSQL and your use case is application search, the advantages — RLS, ACID, JOINs, no sync pipeline, no additional cluster — are significant. If you cannot install extensions or your primary use case is infrastructure observability, the calculus is different.

The table is here so you can make the decision with full information. That has been the goal of this chapter, and of this book.

Honest Boundary

If your organization cannot install PostgreSQL extensions due to policy constraints, the configuration trade-off may be a genuine blocker. Gold Lapel degrades gracefully — tsvector full-text search requires no extensions — but fuzzy matching, phonetic search, and semantic search require their respective extensions. A partial search stack is still useful. It is also partial, and you should know that going in.

If your primary Elasticsearch use case is infrastructure observability, this book does not address that workload. PostgreSQL is not a replacement for the observability stack. The scope line is clear.

The configuration responsibility is real. It is also finite. Once the extensions are installed and wal_level is configured, the operational burden is the database you were already running. The one-time setup is yours. The ongoing work — index creation, cache management, query optimization — is Gold Lapel’s job.

The trade-off is stated. Both sides are on the table. The configuration responsibility is real — extensions to install, settings to verify, a database to understand at a level that a managed Elasticsearch cluster does not require. The advantages are equally real — row-level security that Elasticsearch cannot provide, ACID consistency that eliminates an entire category of bugs, JOINs that keep your data relational instead of denormalized.

Part IV is complete. Sixteen chapters have built the argument: search capabilities (Part II), beyond-search parity (Part III), and the practical path — hybrid search, architectures, scaling, migration, and honest trade-offs (Part IV).

What remains is implementation in your language and your framework. Part V shows how these patterns look in Python, Node.js, Ruby, Go, Java, PHP, and .NET. And Part VI — the final part — introduces the tool that makes all of this one line of code.

If you have followed me this far, I believe we are nearly home. And I should tell you: the view from where we are going is worth the walk.