<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title><![CDATA[Tiger Data Blog]]></title>
        <description><![CDATA[Insights, product updates, and tips from TigerData (Creators of TimescaleDB) engineers on Postgres, time series & AI. IoT, crypto, and analytics tutorials & use cases.]]></description>
        <link>https://www.tigerdata.com/blog</link>
        <image>
            <url>https://www.tigerdata.com/icon.ico</url>
            <title>Tiger Data Blog</title>
            <link>https://www.tigerdata.com/blog</link>
        </image>
        <generator>RSS for Node</generator>
        <lastBuildDate>Tue, 07 Apr 2026 11:14:03 GMT</lastBuildDate>
        <atom:link href="https://www.tigerdata.com/blog" rel="self" type="application/rss+xml"/>
        <ttl>60</ttl>
        <item>
            <title><![CDATA[Elasticsearch's Hybrid Search, Now in Postgres (BM25 + Vector + RRF)]]></title>
            <description><![CDATA[Build hybrid search in Postgres with pg_textsearch BM25, pgvectorscale vectors, and RRF. Auto-sync embeddings with pgai—no Elasticsearch pipeline needed.]]></description>
            <link>https://www.tigerdata.com/blog/elasticsearchs-hybrid-search-now-in-postgres-bm25-vector-rrf</link>
            <guid isPermaLink="true">https://www.tigerdata.com/blog/elasticsearchs-hybrid-search-now-in-postgres-bm25-vector-rrf</guid>
            <category><![CDATA[PostgreSQL]]></category>
            <dc:creator><![CDATA[Raja Rao DV]]></dc:creator>
            <pubDate>Mon, 09 Feb 2026 15:34:33 GMT</pubDate>
            <media:content medium="image" href="https://timescale.ghost.io/blog/content/images/2026/02/hybrid-search-thumbnail.png">
            </media:content>
            <content:encoded><![CDATA[<p>Search is one of those problems that’s deceptively hard. You think you can just<strong> </strong><code>LIKE '%query%'</code><strong> </strong>your way through it, and then you spend three months learning why that doesn’t work.</p><p>Here’s the problem: sometimes users search with exact keywords like “PostgreSQL error 23505”. Other times they search with the meaning: “why is my database slow”. Most of the time, it’s somewhere in between.</p><p>Documents are the same way. Some are full of specific terms and jargon. Others are conversational and conceptual. Most are a mix of both.</p><p>So you have queries that could be keywords or meaning, hitting documents that could be keywords or meaning. That’s four combinations:</p>
<!--kg-card-begin: html-->
<table style="border:none;border-collapse:collapse;"><colgroup><col width="223"><col width="196"><col width="225"></colgroup><tbody><tr style="height:28.5pt"><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;background-color:#f0f0f0;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><br></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;background-color:#f0f0f0;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Document has Keywords</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;background-color:#f0f0f0;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Document has Meaning</span></p></td></tr><tr style="height:30pt"><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Query has Keywords</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">✅ BM25 nails it</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">❌ BM25 misses it</span></p></td></tr><tr style="height:30pt"><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Query has Meaning</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">❌ Vectors miss it</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">✅ Vectors nail it</span></p></td></tr></tbody></table>
<!--kg-card-end: html-->
<p>No single approach covers all four quadrants. You need both keyword search AND vector search. And you need a way to combine them intelligently.</p><p>That’s exactly what Elasticsearch does. It uses <strong>BM25</strong> for keyword ranking, <strong>vector embeddings</strong> for semantic search, and <strong>RRF (Reciprocal Rank Fusion)</strong> to merge the results into a single ranked list. This combination is called <strong>hybrid search</strong>, and it’s why Elasticsearch actually works.</p><p>But here’s the trade-off: to use Elasticsearch, you need to build a pipeline. Your data lives in Postgres, but search lives in Elasticsearch. So you’re stuck with:</p><pre><code class="language-markdown">Postgres → Kafka/Debezium → Elasticsearch</code></pre><p>That’s three systems to manage. Three things that can break. Sync jobs to maintain. Stale data to debug. And with AI agents now needing to search through docs and codebases on the fly, the pipeline problem is getting worse. You can’t easily spin up a test environment when your search lives in a completely different system.</p><p>Still, teams pay for it. <a href="https://ir.elastic.co/"><u>Over a billion dollars a year</u></a>, collectively. Because search that works is worth the pain.</p><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2026/02/take-my-money2.png" class="kg-image" alt="Dealing with three systems to manage sync jobs" loading="lazy" width="1536" height="1024" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2026/02/take-my-money2.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2026/02/take-my-money2.png 1000w, https://timescale.ghost.io/blog/content/images/2026/02/take-my-money2.png 1536w" sizes="(min-width: 720px) 720px"></figure><p><strong>Here’s the good news:</strong> all three pieces of Elasticsearch’s hybrid search are now available in Postgres:&nbsp;</p><ul><li><strong>BM25</strong> via <a href="https://github.com/timescale/pg_textsearch"><u>pg_textsearch</u></a> (open source, PostgreSQL license)&nbsp;</li><li><strong>Vector search</strong> via <a href="https://github.com/timescale/pgvectorscale"><u>pgvectorscale</u></a> (high-performance DiskANN)&nbsp;</li><li><strong>RRF</strong>? That’s just SQL. No extension needed.</li></ul><p>And <a href="https://github.com/timescale/pgai"><u>pgai</u></a> eliminates the embedding pipeline entirely (no more Postgres → Kafka → Elasticsearch sync jobs). It automatically syncs changes to the data and updates the corresponding embeddings appropriately.</p><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2026/02/before-after-hybrid-search.png" class="kg-image" alt="Before / After Elasticsearch" loading="lazy" width="1536" height="1024" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2026/02/before-after-hybrid-search.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2026/02/before-after-hybrid-search.png 1000w, https://timescale.ghost.io/blog/content/images/2026/02/before-after-hybrid-search.png 1536w" sizes="(min-width: 720px) 720px"></figure><p>We’ve already covered <a href="https://www.tigerdata.com/blog/you-dont-need-elasticsearch-bm25-is-now-in-postgres"><u>how BM25 works</u></a>. This blog focuses on Hybrid Search, RRF, pgai, how they all work together, why it’s elegant, and how to implement hybrid search entirely in Postgres.</p><h2 id="how-rrf-reciprocal-rank-fusion-works">How RRF (Reciprocal Rank Fusion) Works</h2><p>RRF (Reciprocal Rank Fusion) is elegantly simple. It’s the industry standard for combining ranked lists, and it’s what Elasticsearch uses for hybrid search.</p><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2026/02/data-src-image-a5b72239-16b4-4835-ae9e-1ab12eb32fe1.png" class="kg-image" alt="" loading="lazy" width="1024" height="572" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2026/02/data-src-image-a5b72239-16b4-4835-ae9e-1ab12eb32fe1.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2026/02/data-src-image-a5b72239-16b4-4835-ae9e-1ab12eb32fe1.png 1000w, https://timescale.ghost.io/blog/content/images/2026/02/data-src-image-a5b72239-16b4-4835-ae9e-1ab12eb32fe1.png 1024w" sizes="(min-width: 720px) 720px"></figure><h3 id="the-problem">The Problem</h3><p>You run two searches and get two ranked lists:</p><p><strong>BM25 Results (keyword):</strong> 1. Doc A (score: 15.2) 2. Doc B (score: 12.1) 3. Doc C (score: 8.4)</p><p><strong>Vector Results (semantic):</strong> 1. Doc C (distance: 0.12) 2. Doc D (distance: 0.18) 3. Doc A (distance: 0.25)</p><p>How do you combine them? You can’t just add the scores. They’re on completely different scales. BM25 scores might be 0-50. Vector distances are 0-2.</p><h3 id="the-rrf-solution">The RRF Solution</h3><p>RRF ignores the actual scores. It only cares about <strong>rank position</strong>:</p><pre><code class="language-markdown">RRF_score = Σ (1 / (k + rank))</code></pre><p>Where: </p><ul><li><code>k</code> is a constant (typically 60) </li><li><code>rank</code> is the position (1st, 2nd, 3rd…)</li></ul><p>That’s it. Dead simple.</p><h3 id="worked-example">Worked Example</h3><p>Let’s calculate RRF scores for our documents:</p>
<!--kg-card-begin: html-->
<table style="border:none;border-collapse:collapse;"><colgroup><col width="146"><col width="113"><col width="109"><col width="179"><col width="151"></colgroup><tbody><tr style="height:28.5pt"><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;background-color:#f0f0f0;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Document</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;background-color:#f0f0f0;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">BM25 Rank</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;background-color:#f0f0f0;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Vector Rank</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;background-color:#f0f0f0;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Calculation</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;background-color:#f0f0f0;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">RRF Score</span></p></td></tr><tr style="height:28.5pt"><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Doc A</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">3</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1/(60+1) + 1/(60+3)</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">0.0323</span></p></td></tr><tr style="height:28.5pt"><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Doc C</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">3</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1/(60+3) + 1/(60+1)</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">0.0323</span></p></td></tr><tr style="height:28.5pt"><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Doc B</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">2</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">-</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1/(60+2) + 0</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">0.0161</span></p></td></tr><tr style="height:28.5pt"><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Doc D</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">-</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">2</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">0 + 1/(60+2)</span></p></td><td style="border-left:solid #333333 0.75pt;border-right:solid #333333 0.75pt;border-bottom:solid #333333 0.75pt;border-top:solid #333333 0.75pt;vertical-align:top;padding:8pt 9pt 8pt 9pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">0.0161</span></p></td></tr></tbody></table>
<!--kg-card-end: html-->
<p><strong>Result:</strong> Doc A and Doc C tie for first place. Why? Because they appeared in <strong>both</strong> lists. RRF naturally boosts documents that multiple systems agree on.</p><h3 id="why-rrf-works-so-well">Why RRF Works So Well</h3><ol><li><strong>Scale-independent.</strong> Doesn’t matter if one score is 0-50 and another is 0-2. RRF only looks at order.</li><li><strong>Rewards consensus.</strong> If both keyword AND semantic search agree a doc is relevant, it gets boosted.</li><li><strong>Preserves outliers.</strong> A doc that only appears in one list still gets scored. Nothing is thrown away.</li><li><strong>The k=60 trick.</strong> This constant prevents the #1 result from dominating everything. It smooths the curve.</li></ol><h2 id="hybrid-search-in-postgres">Hybrid Search in Postgres</h2><p>Here’s how to implement hybrid search with RRF using <a href="https://github.com/timescale/pg_textsearch"><u>pg_textsearch</u></a> and <a href="https://github.com/timescale/pgvectorscale"><u>pgvectorscale</u></a>.</p><h3 id="setup">Setup</h3><pre><code class="language-SQL">-- Enable extensions
CREATE EXTENSION pg_textsearch;       -- Adds BM25 ranking for keyword search
CREATE EXTENSION vectorscale CASCADE; -- Adds DiskANN for fast vector search (includes pgvector)
CREATE EXTENSION ai;                  -- Adds auto-embedding generation (optional but recommended)

-- Create your table
CREATE TABLE documents (
  id SERIAL PRIMARY KEY,
  title TEXT,
  content TEXT,                       -- This column gets BM25 indexed
  embedding vector(1536)              -- This column stores OpenAI embeddings (1536 dimensions)
);

-- Create indexes
CREATE INDEX idx_bm25 ON documents 
  USING bm25(content)                 -- BM25 index on content column
  WITH (text_config = 'english');     -- Use English stemming/stopwords

CREATE INDEX idx_vector ON documents 
  USING diskann(embedding);           -- DiskANN index for fast approximate nearest neighbor</code></pre><h3 id="the-hybrid-search-query">The Hybrid Search Query</h3><pre><code class="language-SQL">WITH 
-- STEP 1: Get top 20 keyword matches using BM25
bm25_results AS (
  SELECT 
    id, 
    ROW_NUMBER() OVER (
      ORDER BY content &lt;@&gt; to_bm25query('database optimization', 'idx_bm25')
    ) as rank                         -- Assign rank 1, 2, 3... based on BM25 score
  FROM documents
  ORDER BY content &lt;@&gt; to_bm25query('database optimization', 'idx_bm25')
  LIMIT 20                            -- Only keep top 20 keyword matches
),

-- STEP 2: Get top 20 semantic matches using vector similarity
vector_results AS (
  SELECT 
    id, 
    ROW_NUMBER() OVER (
      ORDER BY embedding &lt;=&gt; $1       -- $1 is the query embedding (passed from app)
    ) as rank                         -- Assign rank 1, 2, 3... based on vector distance
  FROM documents
  ORDER BY embedding &lt;=&gt; $1           -- &lt;=&gt; is cosine distance operator
  LIMIT 20                            -- Only keep top 20 semantic matches
)

-- STEP 3: Combine both lists using RRF formula
SELECT 
  d.id,
  d.title,
  d.content,
  -- RRF: 1/(k+rank) for each list, summed together
  -- k=60 prevents top results from dominating
  COALESCE(1.0 / (60 + b.rank), 0) +  -- Score from BM25 (0 if not in BM25 results)
  COALESCE(1.0 / (60 + v.rank), 0)    -- Score from vectors (0 if not in vector results)
  as rrf_score
FROM documents d
LEFT JOIN bm25_results b ON d.id = b.id   -- Join BM25 ranks
LEFT JOIN vector_results v ON d.id = v.id -- Join vector ranks
WHERE b.id IS NOT NULL OR v.id IS NOT NULL -- Must appear in at least one list
ORDER BY rrf_score DESC               -- Highest RRF score = most relevant
LIMIT 10;                             -- Return top 10 results</code></pre><p>Just one query, two search types, but RRF smooths everything over. This makes your life super simple. What's better? Wrap it into a function called hybrid_search and then you can now call that function.</p><h3 id="wrap-it-in-a-function">Wrap It in a Function</h3><pre><code class="language-SQL">-- Reusable function: call hybrid_search('your query', $embedding, 10)
CREATE OR REPLACE FUNCTION hybrid_search(
  query_text TEXT,                    -- The search query (for BM25)
  query_embedding vector(1536),       -- The query embedding (for vectors)
  match_count INT DEFAULT 10          -- How many results to return
)
RETURNS TABLE (id INT, title TEXT, content TEXT, rrf_score FLOAT)
AS $$
  WITH 
  -- BM25 keyword search
  bm25_results AS (
    SELECT id, ROW_NUMBER() OVER (
      ORDER BY content &lt;@&gt; to_bm25query(query_text, 'idx_bm25')
    ) as rank
    FROM documents
    ORDER BY content &lt;@&gt; to_bm25query(query_text, 'idx_bm25')
    LIMIT 20
  ),
  -- Vector semantic search  
  vector_results AS (
    SELECT id, ROW_NUMBER() OVER (
      ORDER BY embedding &lt;=&gt; query_embedding
    ) as rank
    FROM documents
    ORDER BY embedding &lt;=&gt; query_embedding
    LIMIT 20
  )
  -- Combine with RRF
  SELECT 
    d.id, d.title, d.content,
    COALESCE(1.0 / (60 + b.rank), 0) + 
    COALESCE(1.0 / (60 + v.rank), 0) as rrf_score
  FROM documents 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 match_count;
$$ LANGUAGE SQL;</code></pre><p><strong>Now your app code is just:</strong></p><pre><code class="language-SQL">SELECT * FROM hybrid_search('database optimization', $embedding, 10);</code></pre><h2 id="weighted-hybrid-search">Weighted Hybrid Search</h2><p>Sometimes you want to favor one search type over the other. Technical docs might benefit from stronger keyword matching. Conversational queries might need more semantic weight. So you can expose all the weights in your function, and then you can call this weighted function.</p><pre><code class="language-SQL">-- Weighted version: control how much keyword vs semantic matters
CREATE OR REPLACE FUNCTION weighted_hybrid_search(
  query_text TEXT,                    -- The search query (for BM25)
  query_embedding vector(1536),       -- The query embedding (for vectors)
  bm25_weight FLOAT DEFAULT 0.5,      -- Weight for keyword search (0.0 to 1.0)
  vector_weight FLOAT DEFAULT 0.5,    -- Weight for semantic search (0.0 to 1.0)
  match_count INT DEFAULT 10          -- How many results to return
)
RETURNS TABLE (id INT, title TEXT, content TEXT, rrf_score FLOAT)
AS $$
  WITH 
  -- BM25 keyword search
  bm25_results AS (
    SELECT id, ROW_NUMBER() OVER (
      ORDER BY content &lt;@&gt; to_bm25query(query_text, 'idx_bm25')
    ) as rank
    FROM documents
    ORDER BY content &lt;@&gt; to_bm25query(query_text, 'idx_bm25')
    LIMIT 20
  ),
  -- Vector semantic search
  vector_results AS (
    SELECT id, ROW_NUMBER() OVER (
      ORDER BY embedding &lt;=&gt; query_embedding
    ) as rank
    FROM documents
    ORDER BY embedding &lt;=&gt; query_embedding
    LIMIT 20
  )
  SELECT 
    d.id, d.title, d.content,
    -- Weighted RRF: multiply each score by its weight
    (bm25_weight * COALESCE(1.0 / (60 + b.rank), 0)) +   -- Weighted BM25 score
    (vector_weight * COALESCE(1.0 / (60 + v.rank), 0))   -- Weighted vector score
    as rrf_score
  FROM documents 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 match_count;
$$ LANGUAGE SQL;</code></pre><p><strong>Usage:</strong></p><pre><code class="language-SQL">-- Equal weight (default): 50% keywords, 50% semantic
SELECT * FROM weighted_hybrid_search('database optimization', $embedding);

-- Favor keywords (70% BM25, 30% vectors)
-- Good for: error codes, specific terms, exact phrases
SELECT * FROM weighted_hybrid_search('error 23505', $embedding, 0.7, 0.3);

-- Favor semantic (30% BM25, 70% vectors)
-- Good for: natural language questions, conceptual queries
SELECT * FROM weighted_hybrid_search('how do I make queries faster', $embedding, 0.3, 0.7);</code></pre><h2 id="auto-sync-embeddings-no-pipelines">Auto-Sync Embeddings (No Pipelines)</h2><p>Remember the pipeline problem from the intro? Postgres → Kafka → Elasticsearch, with separate jobs to generate embeddings?</p><p><a href="https://github.com/timescale/pgai"><u>pgai</u></a> eliminates pretty much all of that.</p><p>When you create a vectorizer, pgai sets up background workers that monitor your table for changes. When a row is inserted or updated, pgai automatically:&nbsp;</p><p>1. Detects the change&nbsp;</p><p>2. Calls the embedding API (OpenAI, Cohere, or local models)&nbsp;</p><p>3. Stores the embedding in a linked table 4. Keeps everything in sync</p><pre><code class="language-SQL">-- Set up automatic embedding generation
SELECT ai.create_vectorizer(
  'documents'::regclass,              -- Which table to watch for changes
  loading =&gt; ai.loading_column(
    column_name =&gt; 'content'          -- Which column to generate embeddings from
  ),  
  embedding =&gt; ai.embedding_openai(
    model =&gt; 'text-embedding-3-small', -- OpenAI model to use
    dimensions =&gt; '1536'               -- Output embedding dimensions
  )
);
-- That's it! pgai now watches 'documents' and auto-generates embeddings
-- whenever content changes. No cron jobs. No sync scripts.
</code></pre><p>That’s it. Now any change to the documents table triggers automatic embedding updates:&nbsp;</p><p>- <strong>INSERT</strong> into documents → embedding generated for content column&nbsp;</p><p>- <strong>UPDATE</strong> the content column → embedding regenerated</p><p>- <strong>DELETE</strong> from documents → embedding removed</p><p>Embeddings are stored in a linked table (documents_embedding) that pgai creates and manages for you.</p><p><strong>Note:</strong> On <a href="https://console.cloud.timescale.com"><u>Tiger Data</u></a>, the vectorizer worker runs automatically as a managed service. If you’re self-hosting, you’ll need to run the <a href="https://github.com/timescale/pgai"><u>pgai vectorizer worker</u></a> (a Python CLI) to process the embedding queue.</p><p>No Kafka. No Debezium. No sync jobs. No “why is my search stale?” debugging sessions at 3 AM.</p><p>Your embeddings live next to your data, updated in near real-time, managed by Postgres itself. This is what makes hybrid search in Postgres practical for production.</p><h2 id="get-started">Get Started</h2><p>Everything you need is available on <a href="https://console.cloud.timescale.com"><u>Tiger Data</u></a>:</p><pre><code class="language-SQL">-- 1. Enable extensions (one-time setup)
CREATE EXTENSION pg_textsearch;        -- BM25 keyword search
CREATE EXTENSION vectorscale CASCADE;  -- Vector search (includes pgvector)

-- 2. Create indexes on your table
CREATE INDEX idx_bm25 ON documents USING bm25(content)
  WITH (text_config = 'english');      -- BM25 index with English stemming
CREATE INDEX idx_vector ON documents 
  USING diskann(embedding);            -- Fast vector similarity index

-- 3. Search! (using the hybrid_search function from this post)
SELECT * FROM hybrid_search('your query', $embedding, 10);</code></pre><h2 id="the-bottom-line">The Bottom Line</h2><p>Elasticsearch built a billion-dollar business on BM25 and RRF. Those algorithms aren’t proprietary. They’re not even complicated. And now they run natively in Postgres.</p><p>No pipelines. No sync jobs. No extra infrastructure. Just your database doing what databases should do: storing your data and making it searchable.</p><p>The question isn’t whether Postgres <em>can</em> do hybrid search. It can. The question is: why are you still running two systems when one will do?</p><h2 id="learn-more">Learn More</h2><p><strong>Our Posts:</strong>&nbsp;</p><ul><li><a href="https://www.tigerdata.com/blog/you-dont-need-elasticsearch-bm25-is-now-in-postgres"><u>You Don’t Need Elasticsearch: BM25 is Now in Postgres</u></a>: Why BM25 beats native Postgres search</li><li><a href="https://www.tigerdata.com/blog/introducing-pg_textsearch-true-bm25-ranking-hybrid-retrieval-postgres"><u>From ts_rank to BM25. Introducing pg_textsearch</u></a></li></ul><p><strong>Extensions:</strong></p><ul><li><a href="https://github.com/timescale/pg_textsearch"><u>pg_textsearch</u></a>: BM25 for Postgres, open source (PostgreSQL license)&nbsp;</li><li><a href="https://github.com/timescale/pgvectorscale"><u>pgvectorscale</u></a>: High-performance vector search using DiskANN&nbsp;</li><li><a href="https://github.com/pgvector/pgvector"><u>pgvector</u></a>: The foundation for vector similarity in Postgres&nbsp;</li><li><a href="https://github.com/timescale/pgai"><u>pgai</u></a>: Auto-sync embeddings, RAG workflows</li></ul><p><strong>Background:</strong>&nbsp;</p><ul><li><a href="https://en.wikipedia.org/wiki/Okapi_BM25"><u>BM25 on Wikipedia</u></a>: The original algorithm (1994)&nbsp;</li><li><a href="https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf"><u>Reciprocal Rank Fusion paper</u></a>: The academic paper behind RRF&nbsp;</li><li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html"><u>Elasticsearch Hybrid Search docs</u></a>: How Elasticsearch implements RRF (for comparison)</li></ul><p><strong>Get Started:</strong></p><ul><li><a href="https://console.cloud.timescale.com"><u>Tiger Data Console</u></a>: Managed Postgres with all extensions pre-installed&nbsp;</li><li><a href="https://www.tigerdata.com/docs/use-timescale/latest/extensions/pg-textsearch"><u>pg_textsearch Documentation</u></a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[It’s 2026, Just Use Postgres]]></title>
            <description><![CDATA[Stop managing multiple databases. Postgres extensions replace Elasticsearch, Pinecone, Redis, MongoDB, and InfluxDB with BM25, vectors, JSONB, and time-series in one database.]]></description>
            <link>https://www.tigerdata.com/blog/its-2026-just-use-postgres</link>
            <guid isPermaLink="true">https://www.tigerdata.com/blog/its-2026-just-use-postgres</guid>
            <category><![CDATA[PostgreSQL]]></category>
            <category><![CDATA[TimescaleDB]]></category>
            <dc:creator><![CDATA[Raja Rao DV]]></dc:creator>
            <pubDate>Mon, 02 Feb 2026 17:49:47 GMT</pubDate>
            <media:content medium="image" href="https://timescale.ghost.io/blog/content/images/2026/02/just-use-postgres-2026.png">
            </media:content>
            <content:encoded><![CDATA[<p>Think of your database like your home. Your home has a living room, bedroom, bathroom, kitchen, and garage. Each room serves a different purpose. But they're all under the same roof, connected by hallways and doors. You don't build a separate restaurant building just because you need to cook. You don't construct a commercial garage across town just to park your car.</p><p><strong>That's what Postgres is.</strong> One home with many rooms. Search, vectors, <a href="https://www.tigerdata.com/learn/the-best-time-series-databases-compared" rel="noreferrer">time-series</a>, queues—all under one roof.</p><p>But this is exactly what specialized database vendors don't want you to hear. Their marketing teams have spent years convincing you to <em>"use the right tool for the right job."</em> It sounds reasonable. It sounds wise. And it sells a lot of databases.</p><p>Let me show you why it's a trap and why Postgres is the better choice in 99% of cases.</p><h2 id="the-%E2%80%9Cuse-the-right-tool%E2%80%9D-trap">The “Use the Right Tool” Trap</h2><p>You’ve heard the advice: <em>“Use the right tool for the right job.”</em></p><p>Sounds wise. So you end up with:&nbsp;</p><ol><li><strong>Elasticsearch</strong> for search</li><li><strong>Pinecone</strong> for vectors</li><li><strong>Redis</strong> for caching</li><li><strong>MongoDB</strong> for documents</li><li><strong>Kafka</strong> for queues</li><li><strong>InfluxDB</strong> for time-series</li><li><strong>PostgreSQL</strong> for… the stuff that’s left</li></ol><p>Congratulations. You now have seven databases to manage. Seven query languages to learn. Seven backup strategies to maintain. Seven security models to audit. Six sets of credentials to rotate. Seven monitoring dashboards to watch. And seven things that can break at 3 AM.</p><p>And when something does break? Good luck spinning up a test environment to debug it.</p><p>Here’s a different idea: <strong>Just use Postgres.</strong></p><h2 id="why-this-matters-now-the-ai-era">Why This Matters Now: The AI Era</h2><p>This isn’t just about simplicity. <strong>AI agents have made database sprawl a nightmare.</strong></p><p>Think about what agents need to do:&nbsp;</p><ul><li>Quickly spin up a test database with production data&nbsp;</li><li>Try a fix or experiment&nbsp;</li><li>Verify it works&nbsp;</li><li>Tear it down</li></ul><p>With one database? That’s a single command. Fork it, test it, done.</p><p>With seven databases? Now you need to:&nbsp;</p><ul><li>Coordinate snapshots across Postgres, Elasticsearch, Pinecone, Redis, MongoDB, and Kafka</li><li>Make sure they’re all at the same point in time&nbsp;</li><li>Spin up seven different services&nbsp;</li><li>Configure seven different connection strings&nbsp;</li><li>Hope nothing drifts while you’re testing&nbsp;</li><li>Tear down seven services when you’re done</li></ul><p>This is virtually impossible without a ton of R&amp;D.</p><p>And it’s not just agents. Every time something breaks at 3 AM, you need to spin up a test environment to debug. With six databases, that’s a coordination nightmare. With one database, it’s a single command.</p><p>In the AI era, <strong>simplicity isn’t just elegant. It’s essential.</strong></p><h2 id="%E2%80%9Cbut-specialized-databases-are-better%E2%80%9D">“But Specialized Databases Are Better!”</h2><p>Let’s address this head-on.</p><p><strong>The myth:</strong> Specialized databases are far superior at their specific tasks.</p><p><strong>The reality:</strong> Sometimes they’re marginally better at a narrow task. But they also bring unnecessary complexity. It’s like hiring a private chef for every meal. Sounds luxurious, but it adds expense, coordination overhead, and creates problems you didn’t have before.</p><p>Here's the thing: 99% of companies don't need them. The top 1% have tens of millions of users and a large engineering team to match. You've read their blog posts about how amazing Specialized Database X works for them. But that's their scale, their team, their problems. For everyone else, Postgres is more than enough.</p><p>Here’s what most people don’t realize: <strong>Postgres extensions use the same or better algorithms as specialized databases (in many cases).&nbsp;</strong></p><p>The “specialized database” premium? Mostly marketing.</p>
<!--kg-card-begin: html-->
<table style="border:none;border-collapse:collapse;"><colgroup><col width="140"><col width="139"><col width="153"><col width="235"></colgroup><thead><tr style="height:0pt"><th style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:bottom;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;" scope="col"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">What You Need</span></p></th><th style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:bottom;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;" scope="col"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Specialized Tool</span></p></th><th style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:bottom;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;" scope="col"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Postgres Extension</span></p></th><th style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:bottom;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;" scope="col"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Same Algorithm?</span></p></th></tr></thead><tbody><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Full-text search</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Elasticsearch</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">pg_textsearch</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">✅ Both use BM25</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Vector search</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Pinecone</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">pgvector + pgvectorscale</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">✅ Both use HNSW/DiskANN</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Time-series</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">InfluxDB</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">TimescaleDB</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">✅ Both use time partitioning</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Caching</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Redis</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">UNLOGGED tables</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">✅ Both use in-memory storage</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Documents</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">MongoDB</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">JSONB</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">✅ Both use document indexing</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Geospatial</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Specialized GIS</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">PostGIS</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">✅ Industry standard since 2001</span></p></td></tr></tbody></table>
<!--kg-card-end: html-->
<p>These aren’t watered-down versions. They’re <strong>the same/better algorithms</strong>, battle-tested, open source, and often developed by the same researchers.</p><p>The benchmarks back this up:&nbsp;</p><ul><li><strong>pgvectorscale</strong>: 28x lower latency than <a href="https://www.tigerdata.com/blog/pinecone-alternatives"><u>Pinecone</u></a> at 75% less cost</li><li><strong>TimescaleDB</strong>: Matches or beats InfluxDB while offering full SQL&nbsp;</li><li><strong>pg_textsearch</strong>: The exact same <a href="https://www.tigerdata.com/blog/you-dont-need-elasticsearch-bm25-is-now-in-postgres"><u>BM25 ranking</u></a> that powers Elasticsearch</li></ul><h2 id="the-hidden-costs-add-up">The Hidden Costs Add Up</h2><p>Beyond the AI/agent problem, database sprawl has compounding costs:</p>
<!--kg-card-begin: html-->
<table style="border:none;border-collapse:collapse;"><colgroup><col width="176"><col width="176"><col width="176"></colgroup><thead><tr style="height:0pt"><th style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:bottom;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;" scope="col"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Task</span></p></th><th style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:bottom;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;" scope="col"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">One Database</span></p></th><th style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:bottom;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;" scope="col"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Seven Databases</span></p></th></tr></thead><tbody><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Backup strategy</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">7</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Monitoring dashboards</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">7</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Security patches</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">7</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">On-call runbooks</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">7</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Failover testing</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">7</span></p></td></tr></tbody></table>
<!--kg-card-end: html-->
<p><strong>Cognitive load:</strong> Your team needs SQL, Redis commands, Elasticsearch Query DSL, MongoDB aggregation, Kafka patterns, and InfluxDB’s non-native SQL workaround. That’s not specialization. That’s <strong>fragmentation</strong>.</p><p><strong>Data consistency:</strong> Keeping Elasticsearch in sync with Postgres? You build sync jobs. They fail. Data drifts. You add reconciliation. That fails too. Now you’re maintaining infrastructure instead of building features.</p><p><strong>SLA math:</strong> Three systems at 99.9% uptime each = 99.7% combined. That’s <strong>26 hours of downtime</strong> per year instead of 8.7. Every system multiplies your failure modes.</p><h2 id="the-modern-postgres-stack">The Modern Postgres Stack</h2><p>These extensions aren’t new. They’ve been production-ready for years:</p><ul><li><strong>PostGIS</strong>: Since <a href="https://en.wikipedia.org/wiki/PostGIS">2001</a> (24 years). Powers OpenStreetMap and Uber.</li><li><strong>Full-text search</strong>: Since <a href="https://en.wikipedia.org/wiki/PostgreSQL">2008</a> (17 years). Built into core Postgres.</li><li><strong>JSONB</strong>: Since <a href="https://en.wikipedia.org/wiki/PostgreSQL">2014</a> (11 years). As fast as MongoDB, with ACID.</li><li><strong>TimescaleDB</strong>: Since <a href="https://github.com/timescale/timescaledb">2017</a> (8 years). 21K+ GitHub stars.</li><li><strong>pgvector</strong>: Since <a href="https://github.com/pgvector/pgvector">2021</a> (4 years). 19K+ GitHub stars.</li></ul><p>Over <a href="https://www.aventionmedia.com/technology-installed-base/postgresql-customers-list/">48,000 companies use PostgreSQL</a>, including Netflix, Spotify, Uber, Reddit, Instagram, and Discord.</p><h3 id="the-ai-era-extensions">The AI-Era Extensions</h3><p>The AI era brought a new generation:</p>
<!--kg-card-begin: html-->
<table style="border:none;border-collapse:collapse;"><colgroup><col width="176"><col width="160"><col width="350"></colgroup><thead><tr style="height:0pt"><th style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:bottom;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;" scope="col"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Extension</span></p></th><th style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:bottom;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;" scope="col"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Replaces</span></p></th><th style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:bottom;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;" scope="col"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Highlights</span></p></th></tr></thead><tbody><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><a href="https://github.com/timescale/pgvectorscale" style="text-decoration:none;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#156082;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">pgvectorscale</span></a></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Pinecone, Qdrant</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">DiskANN algorithm. 28x lower latency, 75% less cost.</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><a href="https://github.com/timescale/pg_textsearch" style="text-decoration:none;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#156082;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">pg_textsearch</span></a></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Elasticsearch</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">True BM25 ranking natively in Postgres.</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><a href="https://github.com/timescale/pgai" style="text-decoration:none;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#156082;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">pgai</span></a></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">External AI pipelines</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:0pt 5.4pt 0pt 5.4pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:1.8pt;margin-bottom:1.8pt;"><span style="font-size:12pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Auto-sync embeddings as data changes.</span></p></td></tr></tbody></table>
<!--kg-card-end: html-->
<p><strong>What this means:</strong> Building a RAG app used to require Postgres + Pinecone + Elasticsearch + glue code.</p><p>Now? <strong>Just Postgres.</strong> One database. One query language. One backup. One fork command for your AI agent to spin up a test environment.</p><h2 id="quick-start-add-these-extensions">Quick Start: Add These Extensions</h2><p>Here’s all you need:</p><pre><code class="language-SQL">-- Full-text search with BM25
CREATE EXTENSION pg_textsearch;

-- Vector search for AI
CREATE EXTENSION vector;
CREATE EXTENSION vectorscale;

-- AI embeddings &amp; RAG workflows
CREATE EXTENSION ai;

-- Time-series
CREATE EXTENSION timescaledb;

-- Message queues
CREATE EXTENSION pgmq;

-- Scheduled jobs
CREATE EXTENSION pg_cron;

-- Geospatial
CREATE EXTENSION postgis;</code></pre><p>That’s it.</p><h2 id="show-me-the-code">Show Me the Code</h2><p>Below are working examples for each use case. Skip to what you need.</p><h3 id="full-text-search-replace-elasticsearch">Full-Text Search (Replace Elasticsearch)</h3><p><strong>The extension:</strong> <a href="https://github.com/timescale/pg_textsearch">pg_textsearch</a> (true BM25 ranking)</p><p>What you’re replacing:&nbsp;</p><ul><li><strong>Elasticsearch:</strong> Separate JVM cluster, complex mappings, sync pipelines, Java heap tuning&nbsp;</li><li><strong>Solr:</strong> Same story, different wrapper&nbsp;</li><li><strong>Algolia:</strong> $1/1000 searches, external API dependency</li></ul><p>What you get: The <strong>exact same BM25 algorithm</strong> that powers Elasticsearch, directly in Postgres.</p><pre><code class="language-SQL">-- Create table
CREATE TABLE articles (
  id SERIAL PRIMARY KEY,
  title TEXT,
  content TEXT
);

-- Create BM25 index
CREATE INDEX idx_articles_bm25 ON articles USING bm25(content)
  WITH (text_config = 'english');

-- Search with BM25 scoring
SELECT title, -(content &lt;@&gt; 'database optimization') as score
FROM articles
ORDER BY content &lt;@&gt; 'database optimization'
LIMIT 10;</code></pre><h3 id="hybrid-search-bm25-vectors-in-one-query">Hybrid Search: BM25 + Vectors in one query:</h3><pre><code class="language-SQL">SELECT 
  title,
  -(content &lt;@&gt; 'database optimization') as bm25_score,
  embedding &lt;=&gt; query_embedding as vector_distance,
  0.7 * (-(content &lt;@&gt; 'database optimization')) + 
  0.3 * (1 - (embedding &lt;=&gt; query_embedding)) as hybrid_score
FROM articles
ORDER BY hybrid_score DESC
LIMIT 10;</code></pre><p>This is what Elasticsearch requires a separate plugin for. In Postgres, it’s just SQL.</p><h3 id="vector-search-replace-pinecone">Vector Search (Replace Pinecone)</h3><p><strong>The extensions:</strong> pgvector + pgvectorscale</p><p>What you’re replacing:&nbsp;</p><ul><li><strong>Pinecone:</strong> $70/month minimum, separate infrastructure, data sync headaches&nbsp;</li><li><strong>Qdrant, Milvus, Weaviate:</strong> More infrastructure to manage</li></ul><p>What you get: <a href="https://github.com/timescale/pgvectorscale">pgvectorscale</a> uses the <strong>DiskANN algorithm</strong> (from Microsoft Research), achieving <strong>28x lower p95 latency</strong> and <strong>16x higher throughput</strong> than Pinecone at 99% recall.</p><pre><code class="language-SQL">-- Enable extensions
CREATE EXTENSION vector;
CREATE EXTENSION vectorscale CASCADE;

-- Table with embeddings
CREATE TABLE documents (
  id SERIAL PRIMARY KEY,
  content TEXT,
  embedding vector(1536)
);

-- High-performance index (DiskANN)
CREATE INDEX idx_docs_embedding ON documents USING diskann(embedding);

-- Find similar documents
SELECT content, embedding &lt;=&gt; '[0.1, 0.2, ...]'::vector as distance
FROM documents
ORDER BY embedding &lt;=&gt; '[0.1, 0.2, ...]'::vector
LIMIT 10;</code></pre><p><strong>Auto-sync embeddings with pgai:</strong></p><pre><code class="language-SQL">SELECT ai.create_vectorizer(
  'documents'::regclass,
  loading =&gt; ai.loading_column(column_name=&gt;'content'),
  embedding =&gt; ai.embedding_openai(model=&gt;'text-embedding-3-small', dimensions=&gt;'1536')
);</code></pre><p>Now every INSERT/UPDATE automatically regenerates embeddings. No sync jobs. No drift. No 3 AM pages.</p><h3 id="time-series-replace-influxdb"><a href="https://www.tigerdata.com/learn/time-series-database-what-it-is-how-it-works-and-when-you-need-one" rel="noreferrer">Time-Series</a> (Replace InfluxDB)</h3><p>The<strong> extension:</strong> <a href="https://github.com/timescale/timescaledb">TimescaleDB</a> (21K+ GitHub stars)</p><p>What you’re replacing:&nbsp;</p><ul><li><strong>InfluxDB:</strong> Separate database, Flux query language or non-native SQL, limited SQL support&nbsp;</li><li><strong>Prometheus:</strong> Great for metrics, not your application data</li></ul><p>What you get: Automatic time partitioning, compression up to 90%, continuous aggregates. Full SQL.</p><pre><code class="language-SQL">-- Enable TimescaleDB
CREATE EXTENSION timescaledb;

-- Create table
CREATE TABLE metrics (
  time TIMESTAMPTZ NOT NULL,
  device_id TEXT,
  temperature DOUBLE PRECISION
);

-- Convert to hypertable
SELECT create_hypertable('metrics', 'time');

-- Query with time buckets
SELECT time_bucket('1 hour', time) as hour,
       AVG(temperature)
FROM metrics
WHERE time &gt; NOW() - INTERVAL '24 hours'
GROUP BY hour;

-- Auto-delete old data
SELECT add_retention_policy('metrics', INTERVAL '30 days');

-- Compression (90% storage reduction)
ALTER TABLE metrics SET (timescaledb.compress);
SELECT add_compression_policy('metrics', INTERVAL '7 days');</code></pre><h3 id="caching-replace-redis">Caching (Replace Redis)</h3><p><strong>The feature:</strong> UNLOGGED tables + JSONB</p><pre><code class="language-SQL">-- UNLOGGED = no WAL overhead, faster writes
CREATE UNLOGGED TABLE cache (
  key TEXT PRIMARY KEY,
  value JSONB,
  expires_at TIMESTAMPTZ
);

-- Set with expiration
INSERT INTO cache (key, value, expires_at)
VALUES ('user:123', '{"name": "Alice"}', NOW() + INTERVAL '1 hour')
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;

-- Get
SELECT value FROM cache WHERE key = 'user:123' AND expires_at &gt; NOW();

-- Cleanup (schedule with pg_cron)
DELETE FROM cache WHERE expires_at &lt; NOW();</code></pre><h3 id="message-queues-replace-kafka">Message Queues (Replace Kafka)</h3><p><strong>The extension:</strong> pgmq</p><pre><code>CREATE EXTENSION pgmq;
SELECT pgmq.create('my_queue');

-- Send
SELECT pgmq.send('my_queue', '{"event": "signup", "user_id": 123}');

-- Receive (with visibility timeout)
SELECT * FROM pgmq.read('my_queue', 30, 5);

-- Delete after processing
SELECT pgmq.delete('my_queue', msg_id);</code></pre><p><strong>Or native SKIP LOCKED pattern:</strong></p><pre><code class="language-SQL">CREATE TABLE jobs (
  id SERIAL PRIMARY KEY,
  payload JSONB,
  status TEXT DEFAULT 'pending'
);

-- Worker claims job atomically
UPDATE jobs SET status = 'processing'
WHERE id = (
  SELECT id FROM jobs WHERE status = 'pending'
  FOR UPDATE SKIP LOCKED LIMIT 1
) RETURNING *;</code></pre><h3 id="documents-replace-mongodb">Documents (Replace MongoDB)</h3><p><strong>The feature:</strong> Native JSONB</p><pre><code class="language-SQL">CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  data JSONB
);

-- Insert nested document
INSERT INTO users (data) VALUES ('{
  "name": "Alice",
  "profile": {"bio": "Developer", "links": ["github.com/alice"]}
}');

-- Query nested fields
SELECT data-&gt;&gt;'name', data-&gt;'profile'-&gt;&gt;'bio'
FROM users
WHERE data-&gt;'profile'-&gt;&gt;'bio' LIKE '%Developer%';

-- Index JSON fields
CREATE INDEX idx_users_email ON users ((data-&gt;&gt;'email'));</code></pre><h3 id="geospatial-replace-specialized-gis">Geospatial (Replace Specialized GIS)</h3><p><strong>The extension:</strong> PostGIS</p><pre><code class="language-SQL">CREATE EXTENSION postgis;

CREATE TABLE stores (
  id SERIAL PRIMARY KEY,
  name TEXT,
  location GEOGRAPHY(POINT, 4326)
);

-- Find stores within 5km
SELECT name, ST_Distance(location, ST_MakePoint(-122.4, 37.78)::geography) as meters
FROM stores
WHERE ST_DWithin(location, ST_MakePoint(-122.4, 37.78)::geography, 5000);</code></pre><h3 id="scheduled-jobs-replace-cron">Scheduled Jobs (Replace Cron)</h3><p><strong>The extension:</strong> pg_cron</p><pre><code class="language-SQL">CREATE EXTENSION pg_cron;

-- Run every hour
SELECT cron.schedule('cleanup', '0 * * * *', 
  $$DELETE FROM cache WHERE expires_at &lt; NOW()$$);

-- Nightly rollup
SELECT cron.schedule('rollup', '0 2 * * *',
  $$REFRESH MATERIALIZED VIEW CONCURRENTLY daily_stats$$);</code></pre><h3 id="hybrid-search-bm25-vectors">Hybrid Search (BM25 + Vectors)</h3><p>For AI applications, you often need <strong>both</strong> keyword search and semantic search:</p><pre><code class="language-SQL">-- Reciprocal Rank Fusion: combine keyword + semantic search
WITH bm25 AS (
  SELECT id, ROW_NUMBER() OVER (ORDER BY content &lt;@&gt; $1) as rank
  FROM documents LIMIT 20
),
vectors AS (
  SELECT id, ROW_NUMBER() OVER (ORDER BY embedding &lt;=&gt; $2) as rank  
  FROM documents LIMIT 20
)
SELECT d.*, 
  1.0/(60 + COALESCE(b.rank, 1000)) + 
  1.0/(60 + COALESCE(v.rank, 1000)) as score
FROM documents d
LEFT JOIN bm25 b ON d.id = b.id
LEFT JOIN vectors v ON d.id = v.id
WHERE b.id IS NOT NULL OR v.id IS NOT NULL
ORDER BY score DESC LIMIT 10;</code></pre><p>Try that with Elasticsearch + Pinecone. You’d need two API calls, result merging, failure handling, and double latency.</p><p>In Postgres: one query, one transaction, one result.</p><h3 id="fuzzy-search-typo-tolerance">Fuzzy Search (Typo Tolerance)</h3><p><strong>The extension:</strong> pg_trgm (built into Postgres)</p><pre><code class="language-SQL">CREATE EXTENSION pg_trgm;

CREATE INDEX idx_name_trgm ON products USING GIN (name gin_trgm_ops);

-- Finds "PostgreSQL" even with typo
SELECT name FROM products
WHERE name % 'posgresql'
ORDER BY similarity(name, 'posgresql') DESC;</code></pre><h3 id="graph-traversal-replace-graph-dbs">Graph Traversal (Replace Graph DBs)</h3><p><strong>The feature:</strong> Recursive CTEs</p><pre><code class="language-SQL">-- Find all reports under a manager (org chart)
WITH RECURSIVE org_tree AS (
  SELECT id, name, manager_id, 1 as depth
  FROM employees WHERE id = 42
  
  UNION ALL
  
  SELECT e.id, e.name, e.manager_id, t.depth + 1
  FROM employees e
  JOIN org_tree t ON e.manager_id = t.id
  WHERE t.depth &lt; 10
)
SELECT * FROM org_tree;</code></pre><h2 id="the-bottom-line">The Bottom Line</h2><p>Remember the home analogy? You don't build a separate restaurant just to cook dinner. You don't construct a commercial garage across town just to park your car. You use the rooms in your home.</p><p>That's what we've shown you here. Search, vectors, time-series, documents, queues, caching—they're all rooms in the Postgres home. Same algorithms as the specialized databases. Battle-tested for years. Used by Netflix, Uber, Discord, and 48,000 other companies.</p><p><strong>So what about that 99%?</strong></p><p>For 99% of companies, Postgres handles everything you need. The 1%? That's when you're processing petabytes of logs across hundreds of nodes, or you need Kibana's specific dashboards, or you have exotic requirements that genuinely exceed what Postgres can do.</p><p>But here's the thing: <strong>you'll know when you're in the 1%.</strong> You won't need a vendor's marketing team to tell you. You'll have benchmarked it yourself and hit a real wall.</p><p>Until then, don't scatter your data across seven buildings because someone told you to "use the right tool for the right job." That advice sells databases. It doesn't serve you.</p><p>Start with Postgres. Stay with Postgres. Add complexity only when you've earned the need for it.</p><p><strong>In 2026, just use Postgres.</strong></p><h2 id="get-started">Get Started</h2><p>All these extensions are available on <a href="https://console.cloud.timescale.com">Tiger Data</a>. Create a free database in minutes:</p><p>psql "postgresql://user:pass@your-instance.tsdb.cloud.timescale.com:5432/tsdb"</p><pre><code class="language-SQL">CREATE EXTENSION pg_textsearch;  -- BM25 search
CREATE EXTENSION vector;         -- Vector search </code></pre><p>No need for specialized databases, just use Postgres.</p><h2 id="learn-more">Learn More</h2><ul><li><a href="https://www.tigerdata.com/docs/use-timescale/latest/extensions/pg-textsearch"><u>pg_textsearch Documentation</u></a></li><li><a href="https://github.com/pgvector/pgvector">pgvector GitHub</a></li><li><a href="https://github.com/timescale/pgvectorscale">pgvectorscale Documentation</a></li><li><a href="https://www.tigerdata.com/docs/"><u>TimescaleDB Documentation</u></a></li><li><a href="https://github.com/tembo-io/pgmq">pgmq for Message Queues</a></li><li><a href="https://postgis.net/">PostGIS for Geospatial</a></li><li><a href="https://timescale.ghost.io/blog/postgres-optimization-treadmill/" rel="noreferrer"><strong>Learn why vanilla Postgres hits performance ceilings at scale</strong></a></li><li><a href="https://www.tigerdata.com/blog/how-marketreader-processes-3m-trades-min-deliver-us-market-trading-insights-timescaledb" rel="noreferrer">Learn how MarketReader Processes 3M Trades/Min to Deliver US Market Trading Insights with TimescaleDB</a></li><li><a href="https://timescale.ghost.io/blog/from-4-databases-to-1-how-plexigrid-replaced-influxdb-got-350x-faster-queries-tiger-data/" rel="noreferrer">Learn how Plexigrid moved from 4 databases to 1 with Tiger Data</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[10 Elasticsearch Production Issues (and How Postgres Avoids Them)]]></title>
            <description><![CDATA[Why Elasticsearch is complex in production: garbage collection, shard math, data sync pipelines, and monitoring overhead. Postgres with pg_textsearch simplifies search.]]></description>
            <link>https://www.tigerdata.com/blog/10-elasticsearch-production-issues-how-postgres-avoids-them</link>
            <guid isPermaLink="true">https://www.tigerdata.com/blog/10-elasticsearch-production-issues-how-postgres-avoids-them</guid>
            <category><![CDATA[PostgreSQL]]></category>
            <dc:creator><![CDATA[Raja Rao DV]]></dc:creator>
            <pubDate>Fri, 30 Jan 2026 13:53:52 GMT</pubDate>
            <media:content medium="image" href="https://timescale.ghost.io/blog/content/images/2026/01/elasticsearch-production-issues-postgres-compressed.png">
            </media:content>
            <content:encoded><![CDATA[<p>Elasticsearch may work great in initial testing and development but Production is a different story. This blog is about what happens after you ship: the JVM tuning, the shard math, the 3 AM pages, the sync pipelines that break silently. The stuff your ops team lives with.</p><p>After years of teams running Elasticsearch in production, certain patterns keep emerging. The same issues show up in blog posts, Stack Overflow questions, and incident reports. We've compiled ten of the most common ones below, with references to the engineers who've documented them. We’ve also added images to make it easy to quickly skim through it and compare the challenges against Postgres.&nbsp;</p><p><strong>TLDR:</strong> With great power comes great operational complexity.</p><h2 id="1-jvm-garbage-collection-pauses">1. JVM Garbage Collection Pauses</h2><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2026/01/devops-problem1-compressed.png" class="kg-image" alt="JVM Garbage Collection Pauses" loading="lazy" width="2000" height="1091" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2026/01/devops-problem1-compressed.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2026/01/devops-problem1-compressed.png 1000w, https://timescale.ghost.io/blog/content/images/size/w1600/2026/01/devops-problem1-compressed.png 1600w, https://timescale.ghost.io/blog/content/images/size/w2400/2026/01/devops-problem1-compressed.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Elasticsearch runs on the Java Virtual Machine (JVM). This means garbage collection (GC) is part of your life.</p><p><strong>The problem:</strong> Java periodically pauses everything to clean up unused memory. These "stop-the-world" pauses can freeze your Elasticsearch node for seconds at a time. If the pause lasts longer than 30 seconds, the cluster assumes the node is dead and starts moving data around to compensate. Now you have a cascading failure.</p><p>Say for example, your e-commerce site is running on Black Friday. Traffic spikes, memory fills up, and Java decides it's time to clean house. Search goes unresponsive for 45 seconds. The cluster panics, starts redistributing shards, and suddenly you're dealing with a full outage instead of a brief slowdown.</p><p><strong>References:</strong></p><ul><li><a href="https://www.siriusopensource.com/en-us/blog/problems-and-operational-weaknesses-elasticsearch"><u>Sirius Open Source: Problems and Operational Weaknesses of Elasticsearch</u></a> — "GC pause time correlates with heap size. Larger heaps mean longer pauses."</li><li><a href="https://www.elastic.co/docs/troubleshoot/elasticsearch/fix-common-cluster-issues"><u>Elastic Docs: Fix Common Cluster Issues</u></a> — Official troubleshooting guide for JVM memory pressure</li></ul><p><strong>Why Postgres avoids this:</strong> Postgres is written in C and manages memory directly. There's no JVM, no garbage collection, and no "stop-the-world" pauses. Memory usage is predictable and grows linearly with your workload. You still need to tune <code>shared_buffers</code> and <code>work_mem</code>, but you're not fighting a runtime that can freeze your entire process.</p><h2 id="2-mapping-explosion">2. Mapping Explosion</h2><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2026/01/devops-problem2-compressed.png" class="kg-image" alt="Mapping Explosion" loading="lazy" width="2000" height="1091" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2026/01/devops-problem2-compressed.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2026/01/devops-problem2-compressed.png 1000w, https://timescale.ghost.io/blog/content/images/size/w1600/2026/01/devops-problem2-compressed.png 1600w, https://timescale.ghost.io/blog/content/images/size/w2400/2026/01/devops-problem2-compressed.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Elasticsearch tries to be helpful by automatically detecting the type of data you're storing. Send it a field called <code>user_id</code> with a number, and it remembers "user_id is a number." Convenient, right?</p><p><strong>The problem:</strong> This "helpful" feature becomes a nightmare with semi-structured data. If your application logs include arbitrary metadata keys, or your users can create custom fields, Elasticsearch creates a new mapping for each one. Thousands of fields later, your cluster state is massive, your master node is struggling, and everything slows to a crawl.</p><p>Say you're logging API requests and including the request body as JSON. One customer sends a payload with 500 unique keys. Elasticsearch dutifully creates 500 new field mappings. Multiply that by thousands of requests, and suddenly you have tens of thousands of fields. Your cluster becomes unresponsive, and you're scrambling to figure out why.</p><p><strong>References:</strong></p><ul><li><a href="https://www.siriusopensource.com/en-us/blog/problems-and-operational-weaknesses-elasticsearch"><u>Sirius Open Source</u></a> — "Mapping explosion is a common anti-pattern... the cluster state can grow to hundreds of megabytes."</li><li><a href="https://www.mindfulchase.com/explore/troubleshooting-tips/databases/advanced-troubleshooting-for-elasticsearch-performance%2C-mappings%2C-and-cluster-stability.html"><u>MindfulChase: Advanced Troubleshooting for Elasticsearch</u></a> — Mapping conflicts and silent failures</li></ul><p><strong>Why Postgres avoids this:</strong> Postgres requires you to define your schema upfront. You decide what columns exist. If you need flexible data, you use JSONB—it stores arbitrary JSON without creating new columns or bloating system catalogs. You can still index specific paths inside the JSON when needed. The schema is explicit, which means surprises are rare.</p><h2 id="3-oversharding-or-undersharding">3. Oversharding (or Undersharding)</h2><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2026/01/devops-problem3-compressed.png" class="kg-image" alt="Oversharding (or Undersharding)" loading="lazy" width="2000" height="1091" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2026/01/devops-problem3-compressed.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2026/01/devops-problem3-compressed.png 1000w, https://timescale.ghost.io/blog/content/images/size/w1600/2026/01/devops-problem3-compressed.png 1600w, https://timescale.ghost.io/blog/content/images/size/w2400/2026/01/devops-problem3-compressed.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Elasticsearch splits your data into "shards" to distribute it across machines. You have to decide how many shards to use when you create an index. Choose wisely, because you can't change it later without rebuilding everything.</p><p><strong>The problem:</strong> Too many shards? Each one consumes memory, CPU, and file handles. You're wasting resources on overhead. Too few shards? You can't parallelize queries effectively, and recovery takes forever when a node dies. And here's the kicker: there's no formula. It depends on your data size, query patterns, hardware, and growth rate. It's guesswork.</p><p>Say you create an index with 5 shards because that's the default. Six months later, your data has grown 10x, and those 5 shards are now bottlenecks. Your only option? Create a new index with more shards and reindex all your data. Hope you have the disk space and time for that migration.</p><p><strong>References:</strong></p><ul><li><a href="https://pureinsights.com/blog/2025/top-7-elasticsearch-pitfalls-and-how-to-avoid-them/"><u>Pure Insights: Top 7 Elasticsearch Pitfalls</u></a> — "Improper shard allocation is one of the most common causes of performance degradation."</li><li><a href="https://www.siriusopensource.com/en-us/blog/problems-and-operational-weaknesses-elasticsearch"><u>Sirius Open Source</u></a> — "Each shard is a Lucene index with its own file handles, memory, and CPU overhead."</li></ul><p><strong>Why Postgres avoids this:</strong> Postgres doesn't shard by default—your data lives in tables on a single node. When you need to scale reads, you add replicas. When tables get large, you use declarative partitioning (by time, by tenant, etc.). These decisions can be made later, as your data grows, rather than upfront when you're guessing. And if you do need horizontal sharding someday, tools like Citus exist. But most applications never get there.</p><h2 id="4-deep-pagination-performance-cliff">4. Deep Pagination Performance Cliff</h2><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2026/01/devops-problem4-compressed.png" class="kg-image" alt="Deep Pagination Performance Cliff" loading="lazy" width="2000" height="1091" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2026/01/devops-problem4-compressed.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2026/01/devops-problem4-compressed.png 1000w, https://timescale.ghost.io/blog/content/images/size/w1600/2026/01/devops-problem4-compressed.png 1600w, https://timescale.ghost.io/blog/content/images/size/w2400/2026/01/devops-problem4-compressed.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Users click "next page" a lot. They expect page 500 to load as fast as page 1. Elasticsearch has other plans.</p><p><strong>The problem:</strong> When you ask for page 500 (results 5000-5010), Elasticsearch doesn't skip to result 5000. It fetches and ranks all 5010 results, then throws away the first 5000. Every node in your cluster does this work, then sends results to a coordinator that combines them. The deeper you paginate, the more work everyone does.</p><p>Say a user searches your product catalog and starts browsing. Page 1 loads in 50ms. Page 10 takes 200ms. By page 100, it's taking 2 seconds. Page 500? Timeout. Elasticsearch actually has a hard limit (<code>index.max_result_window</code>, default 10,000) specifically because this gets so bad.</p><p><strong>References:</strong></p><ul><li><a href="https://www.siriusopensource.com/en-us/blog/problems-and-operational-weaknesses-elasticsearch"><u>Sirius Open Source</u></a> — "Deep pagination is a well-known limitation... the coordinating node must aggregate results from all shards."</li><li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html"><u>Elastic Docs</u></a> — Official guidance recommending <code>search_after</code> for deep pagination</li></ul><p><strong>Why Postgres avoids this:</strong> <code>LIMIT 10 OFFSET 5000</code> in Postgres doesn't have a hard ceiling like ES. The query planner handles it without needing to score every document. That said, large offsets still have overhead—keyset pagination <code>(WHERE id &gt; last_seen_id)</code> is better for deep pages. But you won't hit a wall at 10,000 results, and you won't see the dramatic slowdown ES has.</p><h2 id="5-split-brain-and-data-loss">5. Split-Brain and Data Loss</h2><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2026/01/devops-problem5-compressed.png" class="kg-image" alt="Split-Brain and Data Loss" loading="lazy" width="2000" height="1091" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2026/01/devops-problem5-compressed.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2026/01/devops-problem5-compressed.png 1000w, https://timescale.ghost.io/blog/content/images/size/w1600/2026/01/devops-problem5-compressed.png 1600w, https://timescale.ghost.io/blog/content/images/size/w2400/2026/01/devops-problem5-compressed.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Elasticsearch is a distributed system. Distributed systems fail in distributed ways.</p><p><strong>The problem:</strong> Imagine your cluster has 5 nodes, and a network issue splits them into two groups: 3 nodes on one side, 2 on the other. Both groups might elect their own master and start accepting writes independently. When the network heals, you have two conflicting versions of your data. This is "split-brain," and it can cause permanent data loss.</p><p>Say your Elasticsearch cluster spans two data centers for redundancy. A network blip between them lasts 60 seconds. Both sides elect a master. Users on both sides keep searching and indexing. When connectivity returns, some documents exist in one half, some in the other, and some have conflicting updates. Reconciling this manually is a nightmare.</p><p><strong>References:</strong></p><ul><li><a href="https://www.siriusopensource.com/en-us/blog/problems-and-operational-weaknesses-elasticsearch"><u>Sirius Open Source</u></a> — "Split-brain scenarios can occur when network partitions isolate nodes."</li><li><a href="https://en.wikipedia.org/wiki/Elasticsearch"><u>Wikipedia: Elasticsearch</u></a> — History of split-brain issues and improvements</li></ul><p><strong>Why Postgres avoids this:</strong> Postgres uses a simpler primary/replica model. There's one primary that accepts writes; replicas are read-only. This doesn't make split-brain impossible—any distributed system can have it—but the architecture makes it much harder to create accidentally. Tools like Patroni, pg_auto_failover, and managed services (like <a href="https://www.tigerdata.com/cloud"><u>Tiger Data</u></a>) handle leader election and fencing automatically. You're not managing quorum math yourself.</p><h2 id="6-eventual-consistency-surprises">6. Eventual Consistency Surprises</h2><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2026/01/devops-problem6-compressed.png" class="kg-image" alt="Eventual Consistency Surprises" loading="lazy" width="2000" height="1091" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2026/01/devops-problem6-compressed.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2026/01/devops-problem6-compressed.png 1000w, https://timescale.ghost.io/blog/content/images/size/w1600/2026/01/devops-problem6-compressed.png 1600w, https://timescale.ghost.io/blog/content/images/size/w2400/2026/01/devops-problem6-compressed.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>When you save something to Elasticsearch, it's not immediately searchable. This catches a lot of teams off guard.</p><p><strong>The problem:</strong> Elasticsearch buffers writes and periodically "refreshes" to make them searchable (default: every 1 second). But under load, or with specific configurations, that delay can grow. Users create a record, immediately search for it, and get nothing. "But I just saved it!"</p><p>Say a user posts a comment on your platform. Your app saves it to Postgres (source of truth) and indexes it to Elasticsearch. The user refreshes the page to see their comment. The Postgres query shows it, but the search results don't include it yet because Elasticsearch hasn't been refreshed. The user thinks something is broken and posts again. Now you have duplicate comments.</p><p><strong>References:</strong></p><ul><li><a href="https://acethecloud.com/blog/why-you-rarely-need-elastic/"><u>AceTheCloud: Why You Rarely Need Elastic</u></a> — "Elasticsearch does not support ACID transactions... data may not be immediately visible."</li><li><a href="https://www.stackshare.io/stackups/elasticsearch-vs-postgresql"><u>StackShare: Elasticsearch vs PostgreSQL</u></a> — Comparison of consistency models</li></ul><p><strong>Why Postgres avoids this:</strong> Postgres is <a href="https://www.tigerdata.com/learn/understanding-acid-compliance"><u>ACID-compliant</u></a>. When your transaction commits, the data is immediately visible to all subsequent queries—on the same connection or any other. If you're using BM25 search via <a href="https://www.tigerdata.com/docs/use-timescale/latest/extensions/pg-textsearch"><u>pg_textsearch</u></a>, the index updates synchronously—no refresh interval. No eventual consistency, no "I just saved it but can't find it" bugs.</p><h2 id="7-security-misconfigurations">7. Security Misconfigurations</h2><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2026/01/devops-problem7-compressed.png" class="kg-image" alt="Security Misconfigurations" loading="lazy" width="2000" height="1091" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2026/01/devops-problem7-compressed.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2026/01/devops-problem7-compressed.png 1000w, https://timescale.ghost.io/blog/content/images/size/w1600/2026/01/devops-problem7-compressed.png 1600w, https://timescale.ghost.io/blog/content/images/size/w2400/2026/01/devops-problem7-compressed.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Elasticsearch has been responsible for some of the largest data breaches in history. Not because it's insecure by design, but because it's easy to misconfigure.</p><p><strong>The problem:</strong> For years, Elasticsearch shipped with no authentication by default. Spin up a cluster, expose port 9200, and anyone on the internet could read your data. While newer versions have improved, the damage is done: countless clusters were exposed, and security misconfigurations remain common.</p><p>Here's a real example: in 2019, researchers found an exposed Elasticsearch server containing 1.2 billion records of personal data—social media profiles, email addresses, phone numbers. The cluster had no authentication. This wasn't a sophisticated hack; someone just found an open port. Similar breaches happen regularly.</p><p><strong>References:</strong></p><ul><li><a href="https://pureinsights.com/blog/2025/top-7-elasticsearch-pitfalls-and-how-to-avoid-them/"><u>Pure Insights</u></a> — "Clusters without TLS, authentication, or access controls are vulnerable."</li><li><a href="https://hevodata.com/learn/elasticsearch-postgresql/"><u>HevoData: Elasticsearch PostgreSQL</u></a> — "By default, Elasticsearch lacks built-in authentication."</li></ul><p><strong>Why Postgres avoids this:</strong> Postgres has required authentication from day one. You literally cannot connect without credentials—there's no "open by default" mode. SSL/TLS encryption, role-based access control, and row-level security are all built in and battle-tested over decades. Misconfiguration is still possible (any system can be misconfigured), but the secure path is the default path.</p><h2 id="8-monitoring-complexity">8. Monitoring Complexity</h2><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2026/01/devops-problem8-compressed.png" class="kg-image" alt="Monitoring Complexity" loading="lazy" width="2000" height="1091" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2026/01/devops-problem8-compressed.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2026/01/devops-problem8-compressed.png 1000w, https://timescale.ghost.io/blog/content/images/size/w1600/2026/01/devops-problem8-compressed.png 1600w, https://timescale.ghost.io/blog/content/images/size/w2400/2026/01/devops-problem8-compressed.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Elasticsearch gives you hundreds of metrics. The problem is knowing which ones matter.</p><p><strong>The problem:</strong> JVM heap usage, young GC time, old GC time, thread pool queue sizes, circuit breaker trips, pending tasks, unassigned shards, indexing rate, search latency, segment count... Elasticsearch exposes all of this, but understanding what's normal vs. concerning requires deep expertise. Most teams set up basic monitoring, miss the early warning signs, and only find out something's wrong when it's already broken.</p><p>Say your cluster has been running fine for months. One day, search latency spikes. You check CPU and memory—they look okay. Disk? Fine. What you didn't notice: the "pending tasks" queue has been growing for days, and the "circuit breaker" tripped twice last week. By the time you figure this out, users have been complaining for hours.</p><p><strong>References:</strong></p><ul><li><a href="https://pureinsights.com/blog/2025/top-7-elasticsearch-pitfalls-and-how-to-avoid-them/"><u>Pure Insights</u></a> — "Without proper observability, issues like JVM heap spikes or unassigned shards can escalate into outages."</li><li><a href="https://www.elastic.co/docs/troubleshoot/elasticsearch/fix-common-cluster-issues"><u>Elastic Docs</u></a> — Guide to understanding circuit breaker exceptions and task queue backlogs</li></ul><p><strong>Why Postgres avoids this:</strong> Postgres monitoring is well-understood after 30+ years. The key metrics are simpler: connections, query latency, cache hit ratio, replication lag. <code>pg_stat_statements</code> shows your slowest queries. <code>EXPLAIN ANALYZE</code> tells you exactly why. Tools like pgBadger, pg_stat_activity, and any standard APM will get you 90% of the way there. You don't need to become a Postgres internals expert to keep it healthy.</p><h2 id="9-data-pipeline-sync-issues">9. Data Pipeline Sync Issues</h2><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2026/01/devops-problem9-compressed.png" class="kg-image" alt="Data Pipeline Sync Issues" loading="lazy" width="2000" height="1091" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2026/01/devops-problem9-compressed.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2026/01/devops-problem9-compressed.png 1000w, https://timescale.ghost.io/blog/content/images/size/w1600/2026/01/devops-problem9-compressed.png 1600w, https://timescale.ghost.io/blog/content/images/size/w2400/2026/01/devops-problem9-compressed.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Your real data lives in Postgres. Elasticsearch is just a copy for search. Keeping them in sync is harder than it sounds.</p><p><strong>The problem:</strong> The typical architecture is <code>Postgres → Kafka/Debezium → Elasticsearch</code>. That's three systems. When data updates in Postgres, it flows through Kafka, gets transformed, and lands in Elasticsearch. What could go wrong? Everything. Kafka lag causes stale search results. Schema changes break the connector. A bug in your transformer corrupts data. And when something breaks, you're debugging across three different systems with three different log formats.</p><p>Say a customer updates their email address. The change saves to Postgres immediately. But the Kafka consumer is backed up, and Elasticsearch doesn't get the update for 10 minutes. During that window, any email sent via a search lookup goes to the old address. Customer support gets an angry call. You spend two hours figuring out where the delay happened.</p><p><strong>References:</strong></p><ul><li><a href="https://www.infoq.com/news/2025/08/instacart-elasticsearch-postgres/"><u>InfoQ: Instacart Elasticsearch Postgres</u></a> — Case study on data synchronization challenges</li><li><a href="https://medium.com/@toluaina/real-time-integration-of-postgresql-with-elasticsearch-with-pgsync-9425ffa9b4e9"><u>Medium: Real-time Integration of PostgreSQL with Elasticsearch</u></a> — Discussion of sync tooling and challenges</li></ul><p><strong>Why Postgres avoids this:</strong> If search lives in Postgres (using <a href="https://www.tigerdata.com/blog/you-dont-need-elasticsearch-bm25-is-now-in-postgres"><u>BM25 via pg_textsearch</u></a>), there's no pipeline to manage. When you update a row, the search index updates in the same transaction. No Kafka, no Debezium, no sync jobs, no lag, no "which system has the latest data?" debugging. Your source of truth and your search index are the same thing.</p><h2 id="10-infrastructure-cost">10. Infrastructure Cost</h2><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2026/01/devops-problem10-compressed.png" class="kg-image" alt="Infrastructure Cost" loading="lazy" width="2000" height="1091" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2026/01/devops-problem10-compressed.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2026/01/devops-problem10-compressed.png 1000w, https://timescale.ghost.io/blog/content/images/size/w1600/2026/01/devops-problem10-compressed.png 1600w, https://timescale.ghost.io/blog/content/images/size/w2400/2026/01/devops-problem10-compressed.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Elasticsearch clusters are expensive to run. This isn't a bug—it's the architecture.</p><p><strong>The problem:</strong> ES runs on the JVM, which means each node needs significant RAM (typically 16-64GB, with half allocated to heap). Production clusters require at least 3 nodes for high availability and quorum. Larger deployments need dedicated master nodes, coordinating nodes, and data nodes—that's a lot of instances. And because ES stores everything in Lucene segments, you need fast SSDs to avoid I/O bottlenecks. Add replicas for redundancy, and your storage costs multiply.</p><p>Say you spin up a "small" production cluster: 3 nodes, 32GB RAM each, SSDs. That's your baseline before you've even scaled. Now add a separate cluster for staging. And one for testing. Your cloud bill starts climbing, and you're not even at high traffic yet.</p><p><strong>References:</strong></p><ul><li><a href="https://www.elastic.co/pricing/"><u>Elastic Cloud Pricing</u></a> — Managed ES starts at ~$95/month for minimal configs, scales quickly</li><li><a href="https://aws.amazon.com/opensearch-service/pricing/"><u>AWS OpenSearch Pricing</u></a> — Instance costs add up fast with multi-node clusters</li><li><a href="https://www.siriusopensource.com/en-us/blog/problems-and-operational-weaknesses-elasticsearch"><u>Sirius Open Source</u></a> — "Each shard is a Lucene index with its own file handles, memory, and CPU overhead"</li></ul><p><strong>Why Postgres avoids this:</strong> Postgres runs on a single node for most workloads. No JVM overhead means lower memory requirements. You add read replicas when you actually need them, not because the architecture demands it. A single Postgres instance with pg_textsearch can handle substantial search workloads on hardware that would barely run one ES node. And if you're on a managed service like <a href="https://www.tigerdata.com/cloud"><u>Tiger Data</u></a>, you're paying for one database, not a distributed cluster.</p><h2 id="the-alternative-keep-search-in-postgres">The Alternative: Keep Search in Postgres</h2><p>If you've made it this far, you've probably nodded along to at least a few of these issues. Maybe all ten. The question is: what do you do about it?</p><p>Can you overcome all these issues? Yes. Hire an Elasticsearch specialist (or a team of them). Invest in training. Build expertise over years. Companies do it every day.</p><p>But here's the question: <strong>do you want to?</strong></p><p>It's like the shift from gas cars to electric cars. When you go electric, you don't just swap out the engine. You eliminate an entire category of problems: no oil changes, no transmission repairs, no spark plugs, no fuel injectors, no exhaust systems. The complexity just disappears.</p><p>That's what's happening with search. Extensions like <a href="https://github.com/timescale/pg_textsearch"><u>pg_textsearch</u></a> (BM25 ranking), <a href="https://github.com/timescale/pgvectorscale"><u>pgvectorscale</u></a> (vector search), and <a href="https://github.com/timescale/pgai"><u>pgai</u></a> (automatic embeddings) mean search can now live inside Postgres. No separate cluster. No JVM. No data pipelines. No sync jobs. A whole category of problems just goes away.</p><p>Every issue above has the same root cause: <strong>Elasticsearch is a separate system.</strong> Separate systems mean separate infrastructure, separate expertise, separate failure modes, and separate pipelines.</p><p>What if you didn't need a separate system?</p><p>PostgreSQL now has:</p><ul><li><strong>BM25 ranking</strong> via <a href="https://github.com/timescale/pg_textsearch"><u>pg_textsearch</u></a> (same algorithm Elasticsearch uses)</li><li><strong>Vector search</strong> via <a href="https://github.com/pgvector/pgvector"><u>pgvector</u></a> and <a href="https://github.com/timescale/pgvectorscale"><u>pgvectorscale</u></a></li><li><strong>Hybrid search</strong> combining both, with RRF (Reciprocal Rank Fusion) in pure SQL</li><li><strong>Automatic embedding sync</strong> via <a href="https://github.com/timescale/pgai"><u>pgai</u></a></li></ul><p>And for AI workflows, there's a bonus: when everything lives in Postgres, you can <a href="https://www.tigerdata.com/blog/fast-zero-copy-database-forks"><u>fork your database</u></a> and get an instant copy of your search index, vectors, and embeddings. Try doing that with Elasticsearch.</p><p>For many workloads, "just use Postgres" isn't a compromise. It's a simplification.</p><p><strong>One caveat:</strong> pg_textsearch doesn't currently support faceted search, so e-commerce with "filter by brand/price/color" is still a gap. For RAG, document search, and knowledge bases, you're covered.</p><h2 id="get-started-in-5-minutes">Get Started in 5 Minutes</h2><p>Ready to try hybrid search in Postgres? Here's all you need:</p><pre><code class="language-SQL">-- 1. Enable extensions
CREATE EXTENSION pg_textsearch;        -- BM25 keyword search
CREATE EXTENSION vectorscale CASCADE;  -- Vector search (includes pgvector)
CREATE EXTENSION ai;                   -- Auto-embedding generation (optional)

-- 2. Create your table
CREATE TABLE documents (
  id SERIAL PRIMARY KEY,
  title TEXT,
  content TEXT,
  embedding vector(1536)
);

-- 3. Create indexes
CREATE INDEX idx_bm25 ON documents USING bm25(content)
  WITH (text_config = 'english');
CREATE INDEX idx_vector ON documents USING diskann(embedding);

-- 4. Hybrid search with RRF (one query, two search types)
WITH 
bm25_results AS (
  SELECT id, ROW_NUMBER() OVER (
    ORDER BY content &lt;@&gt; to_bm25query('your search query', 'idx_bm25')
  ) as rank
  FROM documents
  LIMIT 20
),
vector_results AS (
  SELECT id, ROW_NUMBER() OVER (
    ORDER BY embedding &lt;=&gt; $1  -- $1 is your query embedding
  ) as rank
  FROM documents
  LIMIT 20
)
SELECT d.*, 
  COALESCE(1.0/(60 + b.rank), 0) + COALESCE(1.0/(60 + v.rank), 0) as rrf_score
FROM documents 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;
</code></pre><p>That's it. BM25 + vectors + RRF, all in Postgres. No Elasticsearch, no Kafka, no sync jobs.</p><p>Try it on <a href="https://console.cloud.timescale.com"><u>Tiger Data</u></a>—all extensions come pre-installed.</p><h2 id="learn-more">Learn More</h2><ul><li><a href="https://github.com/timescale/pg_textsearch"><u>pg_textsearch on GitHub</u></a> — BM25 for Postgres (open source)</li><li><a href="https://github.com/timescale/pgvectorscale"><u>pgvectorscale on GitHub</u></a> — High-performance vector search</li></ul>]]></content:encoded>
        </item>
    </channel>
</rss>