
Back to blog
8 min read
Apr 20, 2026
Table of contents
01 The two halves of "finding stuff"02 Why this matters now03 Why Postgres is where this belongs04 How hybrid search actually works (the short version)05 Who should be building this right now06 A few honest caveats07 Try it yourself08 The takeawayPicture this: you're searching through your company's internal knowledge base. You know the answer is in there. You typed in what you thought were the right keywords. And yet, the top result is somebody's 2019 onboarding doc about the coffee machine.
Sound familiar?
I've watched this scene play out in every company I've worked at, across every tool that claimed to have "search." The problem isn't that search itself is broken. It's that we've been asking it to do two completely different jobs with one rigid toolset, and calling it solved.
Here's what most people miss: search doesn't have a modality problem. It has an architecture problem. And the fix isn't a bigger stack. It's a smarter query, running in the database you already trust.
We use "search" as though it's one thing. It isn't.
When someone types a query into a search bar, they're usually doing one of two things:
Traditional full-text search, powered by algorithms like BM25, is excellent at the first job. It's stemmed, ranked, battle-tested, and has been the backbone of search engines for decades. It finds "imposter syndrome" when you type "imposter."
Vector search, the newer kid on the block, is excellent at the second job. It embeds your text into a high-dimensional space where meaning lives closer together than words do. It finds "imposter syndrome" when you type "feeling like a fraud at work," even though literally none of the words overlap.
Now here's the punchline: neither of these systems is wrong, and neither of them is enough.
If you only use BM25, you miss every user who doesn't know the exact vocabulary your docs use. If you only use vector search, you miss the person who typed a part number, a legal citation, or "episode 100." Vector search has no particular opinion about the number 100.
The answer is not to pick a side. The answer is to run both and combine the results. That's hybrid search, and it turns out you can do all of it in one database, in one query, against data you're already storing there.
Two years ago, hybrid search in Postgres was a demo. Now it's a production architecture. Here's what changed.
BM25 actually runs fast in Postgres now. pg_textsearch 1.0 built the full search engine in C, on top of Postgres's storage layer. Not a wrapper around an external library. Not ts_rank with extra steps. Block-Max WAND optimization, native. For a lot of workloads, you don't need Elasticsearch anymore.
Vector search in Postgres stopped being a party trick. pgvectorscale's DiskANN-inspired index outperforms specialized vector databases on cost and latency at 75% less cost. The index lives on disk, so you're not RAM-capped. Filtered search actually works under production RAG conditions, which is historically where specialized vector databases fall apart.
Every team I talk to is consolidating. LLM apps don't need five databases. They need one system that can store source content, embeddings, chat history, user context, and audit trails, all in the same transactional boundary. The teams winning on infrastructure right now are the ones who realized Postgres is that system and stopped building around it. (My colleagues have been saying this loudly for a while: it's 2026, just use Postgres.)
That third one is the one I keep coming back to. Five databases to answer one question is an architecture that made sense once and then quietly stopped making sense. Most teams are a quarter or two behind on noticing.
Put those three together and hybrid search in Postgres went from "neat prototype" to a real production architecture. That's the shift this post is about.
I've built the multi-system search stack. Elastic for BM25, Pinecone for vectors, a custom merge service, Redis for caching, and the ritual of explaining the whole thing to every new hire who asks "but why can't we just query the database?"
I've also cussed out that architecture at 2am when the sync job fell behind and results went stale and nobody could figure out which system was wrong.
Every piece of that stack is something you provision, secure, back up, upgrade, monitor, and carry on-call. Every hop between systems is a place where results drift, eventual consistency bites you, and your latency budget gets eaten. Every six months, one of those systems has a breaking change, an incompatible update, or a pricing restructure, and the whole Jenga tower needs attention.
The pragmatic answer: your data is almost certainly already in Postgres. Your users, your content, your metadata, your access controls, your audit logs. So why ship all of it to three other systems just to search it?
You don't have to. Postgres now has what you need:
Put those together and you get a database that can rank by keyword, rank by meaning, filter by any column in your schema, respect your access rules, and return a single fused result set. In one query. Against one system.
That's not a compromise. That's a better architecture.
The full step-by-step is in the Tiger Data docs, including a walkthrough using podcast transcripts from Conduit: 12 episodes, real embeddings, about 30 minutes. Swap "podcast episodes" for "support tickets" and the pattern is identical.
Strip it down to the bones: one table, two indexes, two queries, a little math, one answer.
The SQL for step 4:
WITH bm25_results AS (
SELECT id, ROW_NUMBER() OVER (
ORDER BY description <@> 'mental health boundaries'
) AS rank
FROM episodes
ORDER BY description <@> 'mental health boundaries'
LIMIT 20
),
vector_results AS (
SELECT id, ROW_NUMBER() OVER (
ORDER BY embedding <=> $1
) AS rank
FROM episodes
ORDER BY embedding <=> $1
LIMIT 20
)
SELECT
d.id, d.title,
COALESCE(1.0 / (60 + b.rank), 0)
+ COALESCE(1.0 / (60 + v.rank), 0) AS rrf_score
FROM episodes d
LEFT JOIN bm25_results b ON d.id = b.id
LEFT JOIN vector_results v ON d.id = v.id
WHERE b.id IS NOT NULL OR v.id IS NOT NULL
ORDER BY rrf_score DESC
LIMIT 10;
That's hybrid search. No sidecar service, no merge layer, no second database, no "enterprise search platform" subscription. Your finance team can thank you later.
The teams I'd push hardest toward this pattern are the ones whose search is quietly embarrassing them without anyone saying it out loud.
pg_textsearch 1.0 doesn't support native phrase queries... yet. There's an over-fetch plus ILIKE workaround in the tutorial, and AND/OR/NOT operators are on the roadmap. BM25 indexes are single-column by default; you use a generated column to search across title + body. And no, a 100-million-row table isn't going to be free to embed. Cost and batching are still real things you have to think through.
But those are tuning problems, not architectural ones. A tuning problem inside one database is a much better problem than an integration problem across four. (If you'd rather not hand-roll the embedding pipeline, pgai Vectorizer automates most of that cost-and-batching work.)
The step-by-step tutorial walks through the whole thing: Build hybrid search with BM25 and vector similarity. Real podcast transcripts, about 30 minutes end to end, works on Tiger Cloud, Docker, or a local Postgres install.
If you want to go straight to code, the companion repo is here. Clone it, point it at a Postgres instance with pg_textsearch and pgvectorscale installed, and you have a working hybrid search system one psql -f setup.sql away. And if you want a deeper implementation walkthrough — including pgai auto-sync so embeddings stay current without a separate pipeline — this post covers that pattern end to end.
If you want the deeper "why," these are the posts I'd read next:
Search is hard. It's hard because humans are imprecise, language is slippery, and the things we want are rarely the things we type. No computer is reading our minds yet (and honestly, I'm not sure I want one to). One retrieval method will never be enough.
The solution isn't a bigger stack. It's a smarter query.
Postgres, with pg_textsearch and pgvectorscale, lets you run keyword search and semantic search against the same table, fuse them with a few lines of SQL, and ship one system instead of four. That's not just operationally cheaper. It's a better developer experience, a better user experience, and a better night of sleep for whoever's on call (you can thank me later)
If your search is frustrating your users (or worse, quietly frustrating them without anyone saying anything), this is the version I'd build next.
Have you built hybrid search on Postgres? What interesting queries are you running? I want to hear about it.
Find me at erin@tigerdata.com, or drop a note on LinkedIn or Bluesky.
About the author

By Erin Mikail Staples

pg_textsearch 1.0: How We Built a BM25 Search Engine on Postgres Pages
pg_textsearch 1.0 brings native BM25 search to Postgres. No Elasticsearch sidecar needed. Learn how it works and see benchmarks vs. ParadeDB at 138M documents.
Read more
Receive the latest technical articles and release notes in your inbox.