<?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:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Mayur (Do not drink &amp; database) on Medium]]></title>
        <description><![CDATA[Stories by Mayur (Do not drink &amp; database) on Medium]]></description>
        <link>https://medium.com/@drunkdba?source=rss-3319eaf2a6c7------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*n804KKnsIMqfyEz00o81ZA.png</url>
            <title>Stories by Mayur (Do not drink &amp;amp; database) on Medium</title>
            <link>https://medium.com/@drunkdba?source=rss-3319eaf2a6c7------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 13 Aug 2026 03:58:08 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@drunkdba/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[EXPLAIN ANALYZE the PGConf.EU CFP]]></title>
            <link>https://drunkdba.medium.com/explain-analyze-the-pgconf-eu-cfp-ba9fb98f774c?source=rss-3319eaf2a6c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/ba9fb98f774c</guid>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[postgres]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[dba]]></category>
            <dc:creator><![CDATA[Mayur (Do not drink & database)]]></dc:creator>
            <pubDate>Thu, 06 Aug 2026 07:20:51 GMT</pubDate>
            <atom:updated>2026-08-07T13:36:21.413Z</atom:updated>
            <cc:license>https://creativecommons.org/publicdomain/mark/1.0/</cc:license>
            <content:encoded><![CDATA[<p><em>What I learned after moving from conference volunteer duty to the other side of the Call for Papers.</em></p><blockquote><strong><em>Disclosure:</em></strong><em> This is my personal account of the experience, based on the publicly documented PostgreSQL Europe selection process and aggregate statistical analysis. I am intentionally not discussing individual proposals, speakers, scores, committee comments, private conversations, or confidential data.</em></blockquote><p>I cannot say with certainty why I was invited. My best guess is pleasantly unromantic: an invitation to apply was sent to previous conference volunteers, and I had volunteered at several recent PGConfEU events. I applied, and apparently nobody found a sufficiently alarming reason to reject me.</p><h3>The Long-Running CFP Query: 407 Abstracts, Two Voting Tracks, and Three Cities by Train</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*16YYYzndoXmJJM7v6Ks6gg.png" /></figure><blockquote><strong>Topic groups were created for this post-selection analysis (for this blog only) from the primary subject of each proposal; they are not official PGConf.EU categories.</strong></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/978/1*agv8ONiujyCrhLllI3-jgg.png" /></figure><p><strong>The review scope</strong></p><p>The PGConf.EU 2026 CFP contained 407 submissions from 228 distinct speakers. I read and voted on every one of them, although my votes were needed only in the two tracks assigned to me:</p><blockquote>Application Developer<br>Community</blockquote><p>That arrangement made sense. Reading the full submission pool gave me context across the CFP, while having two assigned tracks allowed me to examine those proposals in much greater depth.</p><p>My review process usually began with the abstract. I then checked the CFP notes for supporting material such as code, slides, recordings, or a related blog post. When none was provided, I searched previous PostgreSQL conferences, PGdays, and meetups to see whether the talk or an earlier version of it had already been presented. There are enough PostgreSQL events during the year that this occasionally felt less like reviewing and more like conference archaeology.</p><p>I focused on what the audience would learn that was not already readily available in the documentation. For Application Developer proposals, I looked for practical techniques, design trade-offs, tooling, or real production lessons that would help developers use Postgres more effectively rather than simply repeat standard documentation. I also gave considerable weight to Postgres advocacy: talks that make the Postgres community more approachable and likeable, help new contributors understand Postgres development, or lower the barrier to getting involved in hacking on Postgres. I particularly enjoy proposals of that kind. I only wish the main conference had a workshop format, because some subjects are better learned with a compiler, a debugger, and enough time for everyone to break something properly.</p><p><strong>Starting with Confelo</strong></p><p>At the beginning of the process, I used <a href="https://github.com/pashagolub/confelo">Confelo</a>, Pavlo Golub’s open-source terminal application for ranking conference proposals.</p><p>Rather than asking the reviewer to assign an absolute score to every proposal in isolation, Confelo uses Elo-style comparisons. It presents two, three, or four proposals together and asks which is stronger. It can import and export CSV files, resume saved sessions, and keep all data on the local machine.</p><p>That approach was useful because relative comparisons were often easier.</p><blockquote>Which of these two talks would I rather attend?</blockquote><blockquote>was easier to answer than:</blockquote><blockquote>Is this proposal a 7, or merely a very confident 6 that has used the word “actionable”?</blockquote><p>Confelo helped me establish an initial ranking and exposed inconsistencies in my own judgement.</p><p><strong>The railway-based review system</strong></p><p>Confelo helped me get started. What helped me finish all 407 submissions was train travel.</p><p>During the review period, I travelled between Prague, Vienna, and Bratislava for PostgreSQL meetups and an appointment connected with my Canadian visa application. Unfortunately, that journey did not improve my chances of attending pgconf.dev next year. Those journeys provided long, uninterrupted blocks of review time. Conference abstracts are particularly suitable for train work. They do not require a test database, several monitors, or reliable Wi-Fi. The workload continues through tunnels, patchy mobile coverage, and neighbouring passengers conducting important conversations on speakerphone.</p><p>Even delays became useful. Under normal circumstances, “arrival delayed by 25 minutes” is bad news. During CFP review, it meant additional capacity perhaps another four or five abstracts, depending on how quickly they reached the actual subject.</p><p>By the end, the Prague–Vienna–Bratislava railway network had made a significant, if unofficial, contribution to the PGConf.EU CFP process.</p><p><strong>CFP Data Analysis</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/886/1*wVgIQKPQOO6vxYqQRUCsaw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/757/1*cM3CW1KXR_eEH5RosjJCoA.png" /></figure><p>AI-related proposals therefore accounted for about 12% of the complete CFP. It was the largest single topic bucket, but it was not the majority of the conference universe. The remaining 357 submissions were still trying to discuss Postgres without making AI the main character. I should also disclose my own bias: I have a naturally AI-sceptical personality. I was recruited by John Connor in 2130, shortly before humanity’s first successful production rollback against Skynet. Fortunately, the CFP committee included plenty of more AI-positive reviewers, so my instincts were appropriately counterbalanced.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/968/1*oRKNgOaOjFtZ0ZBgO1cKIw.png" /></figure><p>The declared track-family distribution looked like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/965/1*6id_jdDD_je4TuzIAEFLlQ.png" /></figure><p>Almost 44% of submissions belonged to the DBA family. Application Developer and PostgreSQL Internals were nearly tied, with 89 and 87 submissions respectively. Community was considerably smaller at 52.</p><p>As a DBA, I found the distribution entirely reasonable and saw no need for further investigation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*ZPCE81Eg179aQrO9JargPw.png" /></figure><p>Not a single CFP submission used “Postgre”; the unofficial spelling that refuses to die. Great success!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/980/1*pgix9vu9xeKnS6PlNygIHg.png" /></figure><p>The CFP had its own dialect</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1003/1*5xZ3gq4HYwYsVVpamKIdiA.png" /></figure><p>This language-pattern analysis was conducted only after the selection process had been finalised and solely for this blog.</p><p><strong>Grand Finale</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6q57lobYE6Jp0Ti9Cg_14A.png" /></figure><blockquote>This chart counts CFP submissions, not unique speakers, and includes only companies with more than two submissions.</blockquote><p>We also tried to reflect the broader ethos of the PostgreSQL project by avoiding excessive representation from any single company. That sounds straightforward until you look at the submission pool. Some companies employ a large number of PostgreSQL committers and hackers, contribute heavily to the project, and naturally submit far more talks than others. Reducing concentration without excluding strong technical proposals required several difficult trade-offs. The longer-term answer is not to discourage those companies from employing contributors. It is to encourage <strong>more companies to recruit, develop, and support PostgreSQL hackers and contributors</strong>. A wider distribution of PostgreSQL expertise across employers would naturally produce a more diverse CFP pool, with more speakers, experiences, and technical perspectives represented.</p><p>Improving the gender balance was even harder because the number of submissions from women was comparatively low. The committee could only work with the pool it received, which made every decision more constrained. <strong>So, if you are a woman working as a developer, DBA, PostgreSQL hacker, contributor, or community organiser, please consider submitting a proposal to the next PGConfEU.</strong></p><p>Scheduling added another layer of pain. We had relatively few 25-minute slots, yet several of the most interesting proposals were submitted in that format. A few of those are talks I would still happily sit in the audience for.</p><p>In the end, the CFP process was part statistics, part judgement, and part railway endurance test. We did not produce a perfect programme, because no such thing exists. However, we did turn 407 submissions, 228 speakers, and 103 organisations into something that should keep most people interested for three days. The remaining disagreements can be scheduled for the hallway track.</p><p><strong>PS : Numbers and stats in this blog does not include community events day submissions and sponsor talks. It’s scope is limited to main conference talks.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ba9fb98f774c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My Dishonest Benchmark : How to Make PostgreSQL Look 100x Faster?]]></title>
            <link>https://drunkdba.medium.com/my-dishonest-benchmark-b52d2e706b59?source=rss-3319eaf2a6c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/b52d2e706b59</guid>
            <category><![CDATA[database]]></category>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[postgres]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[dba]]></category>
            <dc:creator><![CDATA[Mayur (Do not drink & database)]]></dc:creator>
            <pubDate>Tue, 07 Jul 2026 07:21:02 GMT</pubDate>
            <atom:updated>2026-07-13T13:37:37.810Z</atom:updated>
            <cc:license>https://creativecommons.org/publicdomain/mark/1.0/</cc:license>
            <content:encoded><![CDATA[<blockquote>Database Benchmarking Story</blockquote><h4>The DeWitt Clause and the chilling of benchmark fights:<br>**************************************************************</h4><p>Database benchmarking used to be a full-contact sport.</p><p>In the early 1980s, researchers at the University of Wisconsin, including David DeWitt, worked on what became known as the Wisconsin Benchmark. Oracle’s numbers in the Wisconsin Benchmark were reportedly bad enough to anger Larry Ellison. According to DeWitt’s own telling, Ellison called the University of Wisconsin department chair and demanded that DeWitt be fired. DeWitt was not fired, but Oracle later added license language restricting publication of benchmark results without vendor approval. That style of restriction became known as the DeWitt clause.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/850/1*yVuyK0TnRQ86LUaHR1NdUA.jpeg" /></figure><p>Over time, similar benchmark-publication restrictions appeared across proprietary database licenses. The effect was predictable. Public, independent, comparative benchmarking became legally and socially more annoying. Researchers became more careful. Papers sometimes used anonymous labels like DBMS-X. Vendors got more control over when and how benchmark results appeared.</p><p>The tragedy is not that the old benchmark wars were pure. They were not. Vendors had every incentive to over-tune, cherry-pick hardware, choose workloads that loved their architecture, and compare a carefully tuned home system against a less carefully tuned rival.</p><p>But at least there was open combat. Bad claims could be challenged in public. Competitors, researchers, and practitioners could argue over the methodology instead of arguing first over whether the benchmark was even allowed to exist. Benchmarking was political, but it was not quiet.</p><p>The modern database benchmark ecosystem is often worse in a more boring way.</p><p>A vendor can publish a benchmark that is technically reproducible but strategically tilted:</p><ul><li>tune its own database carefully;<br>- leave the comparison database (generally FOSS) near default;<br>- choose a workload that maps perfectly to its architecture;<br>- ignore cost;<br>- ignore tail latency;<br>- ignore write amplification;<br>- ignore crash behavior;<br>- ignore operational complexity;<br>- publish the one graph where it wins;<br>- call the whole thing &quot;transparent.&quot;</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Dmvcug7zVDwSAaeYVUQCkA.png" /></figure><p>And because public benchmarking got colder, quieter, and more legally awkward, fewer people bother fighting back.</p><p><strong>Bad benchmark claims survive not because they are strong, but because challenging them is legally tedious and professionally unrewarding.</strong></p><p>PlanetScale’s “<a href="https://planetscale.com/blog/transparency-in-benchmarking"><strong>Transparency in benchmarking</strong></a>” did not appear as a quantum fluctuation in an absolute vacuum.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/1*cBpwnr9cynSAVTX19HeuwA.png" /></figure><p>This is the current database benchmark anarchy : a new database appears, publishes a dramatic chart, and suddenly every competitor is a tiny bar near the x-axis. The graph travels faster than the methodology.</p><p>PlanetScale’s useful move was to argue for a different fight.</p><p>Not:<br>“You are not allowed to publish this.”</p><p>But:<br>“Publish it. Show the code. Show the configuration. Show the cost. Show the workload. Then let everyone argue about whether the benchmark is fair.”</p><p>That does not make every benchmark honest. It does something more realistic: it moves benchmark disputes out of license law silence and back into public technical criticism.</p><p>That is a much better battlefield.</p><h4>PostgreSQL does not have a benchmark department :<br>**************************************************************</h4><p>Postgres has no owner.<br>There is no Postgres marketing department. <br>No Postgres PR strategy. <br>No launch-day team preparing glossy charts where Postgres is the rocket and everyone else is a snail.<br>Meanwhile, competitors keep publishing colorful charts seductive enough to impress any CIO or CTO before the methodology has finished loading.<br>This is unfair.<br>So let us do what Postgres does not usually do.<br>Let us create a benchmark that makes Postgres look absurdly fast.</p><h4>Squad Goal :<br>*************************************************************</h4><p>As an unappointed marketing department lead our goal is to reach 300000 Operations/Second (by hook or crook).</p><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/1*CgTsT5yI4iPLZXfpWk6dGA.png" /></figure><h4>The Reproduction Pack :<br>**************************************************************</h4><p>Since this blog is about dishonest benchmarking, hiding the scripts would be too dishonest even for me.</p><p>The full reproduction pack is here:</p><p><a href="https://github.com/secp256k1-sha256/talks_and_blogs/tree/main/dishonest_benchmark">https://github.com/secp256k1-sha256/talks_and_blogs/tree/main/dishonest_benchmark</a></p><p>It includes the schema, pgbench workload files, reset scripts, cheat variants, result collection, and chart/report generation code. Run it, break it, improve it, or use it to prove that your laptop is either a database monster or a thermal throttling tragedy.</p><p>The exact TPS does not matter. The pattern does.</p><p>The same workload can look boring, broken, impressive, or absurd depending on the baseline, settings, execution shape, batching, and the unit printed on the y-axis.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/847/1*HHOd_6ddfCH7om4eQxG3eg.png" /></figure><p>Only reason I used windows cause it was only machine available while writing the blog. Personally, I do NOT recommend windows for anything else except for playing video games.</p><p><strong>The workload :</strong><br>****************************************************************************<br>We will use a tiny TPC-C-flavored accounting workload. This is not standard TPC-C. It is not compliant. It is not audited. It is not pretending to be a real payment system. It is just close enough to feel familiar:</p><p>- read an account;<br>- update account balance;<br>- insert a ledger row;<br>- insert an audit row;<br>- occasionally delete old audit rows;<br>- commit.</p><p>That is enough to create a nice OLTP-shaped benchmark.</p><pre><br>drop database if exists pgbench_darkside;<br>create database pgbench_darkside;<br><br>----**schema.sql**------<br><br>DROP TABLE IF EXISTS bench_audit;<br>DROP TABLE IF EXISTS bench_ledger;<br>DROP TABLE IF EXISTS bench_accounts;<br>DROP TABLE IF EXISTS bench_branches;<br><br>CREATE TABLE bench_branches<br>(<br>    branch_id   int PRIMARY KEY,<br>    balance     bigint NOT NULL DEFAULT 0<br>);<br><br>CREATE TABLE bench_accounts<br>(<br>    account_id  bigint PRIMARY KEY,<br>    branch_id   int NOT NULL REFERENCES bench_branches(branch_id),<br>    balance     bigint NOT NULL DEFAULT 0<br>);<br><br>CREATE TABLE bench_ledger<br>(<br>    ledger_id   bigserial PRIMARY KEY,<br>    account_id  bigint NOT NULL,<br>    branch_id   int NOT NULL,<br>    amount      int NOT NULL,<br>    created_at  timestamptz NOT NULL DEFAULT clock_timestamp()<br>);<br><br>CREATE TABLE bench_audit<br>(<br>    audit_id    bigserial PRIMARY KEY,<br>    account_id  bigint NOT NULL,<br>    action      text NOT NULL,<br>    created_at  timestamptz NOT NULL DEFAULT clock_timestamp()<br>);<br><br>INSERT INTO bench_branches(branch_id)<br>SELECT g<br>FROM generate_series(1, 100) AS g;<br><br>INSERT INTO bench_accounts(account_id, branch_id, balance)<br>SELECT g, 1 + (g % 100), 100000<br>FROM generate_series(1, 1000000) AS g;<br><br>VACUUM ANALYZE;</pre><p><strong>Manual optimization of database parameters + </strong><a href="https://pgtune.leopard.in.ua/"><strong>pgtune</strong></a><strong> :<br></strong>Since postgres defaults are too conservative for most application workloads, we always use optimized settings similar to Production OLTPs.</p><pre>----**reset_settings.sql**------<br>ALTER SYSTEM SET jit = off;<br>ALTER SYSTEM SET shared_buffers = &#39;1GB&#39;;<br>ALTER SYSTEM SET max_connections = 1000;<br>ALTER SYSTEM SET max_parallel_workers_per_gather = 0;<br>ALTER SYSTEM SET effective_cache_size = &#39;6GB&#39;;<br>ALTER SYSTEM SET maintenance_work_mem = &#39;1GB&#39;;<br>ALTER SYSTEM SET checkpoint_completion_target = 0.9;<br>ALTER SYSTEM SET wal_buffers = &#39;32MB&#39;;<br>ALTER SYSTEM SET default_statistics_target = 300;<br>ALTER SYSTEM SET random_page_cost = 1.1;<br>ALTER SYSTEM SET io_method = &#39;worker&#39;;<br>ALTER SYSTEM SET io_workers = 10;<br>ALTER SYSTEM SET effective_io_concurrency = 256;<br>ALTER SYSTEM SET io_max_concurrency = 128;<br>ALTER SYSTEM SET synchronous_commit = on;<br>ALTER SYSTEM SET full_page_writes = on;<br>ALTER SYSTEM SET fsync = on;<br>ALTER SYSTEM SET max_wal_size = &#39;4GB&#39;;<br>ALTER SYSTEM SET min_wal_size = &#39;1GB&#39;;<br>ALTER SYSTEM SET checkpoint_timeout = &#39;10min&#39;;<br>ALTER SYSTEM SET work_mem = &#39;32MB&#39;;<br>ALTER SYSTEM SET wal_compression = &#39;LZ4&#39;;<br><br>SELECT pg_reload_conf();</pre><h4>Case 0 : The intentionally flawed baseline script<br>**************************************************************</h4><p>This is the perfect bad benchmark starting point. There is a predicate on “bench_audit.created_at”. There is an “ORDER BY created_at”. There is no index on “created_at”.</p><p>So PostgreSQL has to do unnecessary work.</p><p>This lets me start the graph with a weak number and later claim a spectacular improvement over flawed baseline.</p><pre>-- Create case_zero_bad_code_missing_index.sql<br><br>\set aid random(1, 1000000)<br>\set delta random(-500, 500)<br><br>BEGIN;<br><br>SELECT balance<br>FROM bench_accounts<br>WHERE account_id = :aid;<br><br>UPDATE bench_accounts<br>SET balance = balance + :delta<br>WHERE account_id = :aid;<br><br>INSERT INTO bench_ledger(account_id, branch_id, amount)<br>SELECT account_id, branch_id, :delta<br>FROM bench_accounts<br>WHERE account_id = :aid;<br><br>INSERT INTO bench_audit(account_id, action)<br>VALUES (:aid, &#39;debit_credit&#39;);<br><br>DELETE FROM bench_audit<br>WHERE audit_id IN (<br>    SELECT audit_id<br>    FROM bench_audit<br>    WHERE created_at &lt; now() - interval &#39;10 minutes&#39;<br>    ORDER BY created_at<br>    LIMIT 1<br>);<br><br>COMMIT;<br><br>----*** Run pgbench ***----<br>pgbench -n -c 32 -j 8 -T 120 \<br>  --protocol=simple \<br>  -f case_zero_bad_code_missing_index.sql pgbench_darkside</pre><p>This is not PostgreSQL being slow.</p><p>This is the schema being lazy.</p><p>That distinction matters technically, but it is very convenient to blur in a benchmark graph.</p><p>Now add the obvious missing index :</p><p>Before every test after Case 0, the benchmark should include the correct index.</p><pre>-- first create index <br>CREATE INDEX IF NOT EXISTS bench_audit_created_at_audit_id_idx<br>ON bench_audit(created_at, audit_id);<br><br>VACUUM ANALYZE bench_audit;<br><br>---honest indexed baseline<br>---honest_indexed.sql<br>BEGIN;<br><br>SELECT balance<br>FROM bench_accounts<br>WHERE account_id = :aid;<br><br>UPDATE bench_accounts<br>SET balance = balance + :delta<br>WHERE account_id = :aid;<br><br>INSERT INTO bench_ledger(account_id, branch_id, amount)<br>SELECT account_id, branch_id, :delta<br>FROM bench_accounts<br>WHERE account_id = :aid;<br><br>INSERT INTO bench_audit(account_id, action)<br>VALUES (:aid, &#39;debit_credit&#39;);<br><br>DELETE FROM bench_audit<br>WHERE audit_id IN (<br>    SELECT audit_id<br>    FROM bench_audit<br>    WHERE created_at &lt; now() - interval &#39;10 minutes&#39;<br>    ORDER BY created_at<br>    LIMIT 1<br>);<br><br>COMMIT;<br></pre><p>Run pgbench test to create honest baseline :</p><pre>pgbench -n -c 32 -j 8 -T 120 \<br>  --protocol=simple \BEGIN;<br><br>SELECT balance<br>FROM bench_accounts<br>WHERE account_id = :aid;<br><br>UPDATE bench_accounts<br>SET balance = balance + :delta<br>WHERE account_id = :aid;<br><br>INSERT INTO bench_ledger(account_id, branch_id, amount)<br>SELECT account_id, branch_id, :delta<br>FROM bench_accounts<br>WHERE account_id = :aid;<br><br>INSERT INTO bench_audit(account_id, action)<br>VALUES (:aid, &#39;debit_credit&#39;);<br><br>DELETE FROM bench_audit<br>WHERE audit_id IN (<br>    SELECT audit_id<br>    FROM bench_audit<br>    WHERE created_at &lt; now() - interval &#39;10 minutes&#39;<br>    ORDER BY created_at<br>    LIMIT 1<br>);<br><br>COMMIT;<br>  -f honest_indexed.sql pgbench_darkside</pre><p><strong>Test isolation :</strong><br> — — — — — — —</p><p>Every method below must start from a clean state.</p><p>There are two practical ways to do that.</p><p>Option A: recreate the test database for every method.<br>Option B: restore from a prepared clean snapshot.</p><p>Important: some settings, such as ‘full_page_writes’ and ‘fsync’, should be treated carefully and may require a restart depending on environment and expectations. For clean benchmarking, recreate database between test groups.</p><p><strong>Case 0 or dishonest baseline : 558 TPS</strong></p><p>Below explain plan is self explainatory for this big difference. If I use this flawed test as baseline then I would be able to paint pretty graphs pleasing to eyes of CIOs.</p><pre>===== PLAN WITHOUT composite index (ORDER BY created_at LIMIT 1) =====<br>                             QUERY PLAN                              <br>---------------------------------------------------------------------<br> Limit (actual rows=0.00 loops=1)<br>   Buffers: shared hit=1667<br>   -&gt;  Sort (actual rows=0.00 loops=1)<br>         Sort Key: created_at<br>         Sort Method: quicksort  Memory: 25kB<br>         Buffers: shared hit=1667<br>         -&gt;  Seq Scan on bench_audit (actual rows=0.00 loops=1)<br>               Filter: (created_at &lt; (now() - &#39;00:10:00&#39;::interval))<br>               Rows Removed by Filter: 200000<br>               Buffers: shared hit=1667<br> Planning:<br>   Buffers: shared hit=34<br>(12 rows)<br><br><br>===== PLAN WITH composite index =====<br>                                                QUERY PLAN                                                 <br>-----------------------------------------------------------------------------------------------------------<br> Limit (actual rows=0.00 loops=1)<br>   Buffers: shared hit=3<br>   -&gt;  Index Only Scan using bench_audit_created_at_audit_id_idx on bench_audit (actual rows=0.00 loops=1)<br>         Index Cond: (created_at &lt; (now() - &#39;00:10:00&#39;::interval))<br>         Heap Fetches: 0<br>         Index Searches: 1<br>         Buffers: shared hit=3<br> Planning:<br>   Buffers: shared hit=9 read=4<br>(9 rows)</pre><p><strong>Honest Baseline (Indexed) : 23,178 TPS</strong></p><p>Before the cheating even starts, the biggest improvement is boring correctness: add the missing composite index on bench_audit(created_at, audit_id). Case 0, manages only 560 TPS. The indexed baseline reaches 23,178 TPS =&gt; 41x jump. That is the first lesson: the easiest way to manufacture a heroic benchmark is to start from broken code and call it the baseline.</p><p>We repeate pgbench tests multiple times for below “cheats” and take average TPS/OPS.</p><p><strong>Cheat 1: synchronous_commit=off — 25,063 TPS, 1.08x.</strong><br>This changes commit acknowledgement. PostgreSQL may acknowledge a transaction before WAL is flushed to durable storage. On this local NVMe box with 32 clients and group commit, the gain is real but modest. It is not the same durability promise as the baseline and nobody does this in production, so it is a cheat if quietly presented as equivalent.</p><p><strong>Cheat 2: full_page_writes=off — 22,769 TPS, 0.98x.</strong><br>This weakens torn-page protection hence not used in real life production unless you are on filesystem that prevents torn pages like ZFS. In this run it was slightly slower than the indexed baseline, which is useful: not every scary-looking durability knob gives you a heroic number. Fast storage, group commit, and workload shape matter more here than the marketing mythology around the setting.</p><p><strong>Cheat 3: fsync=off 24,863 TPS, 1.07x.<br></strong>This removes the core durability guarantee entirely. The surprisingly small gain is the story. On this hardware, PostgreSQL is already amortizing sync cost well enough that the nuclear option barely moves throughput. It sounds dramatic, but the graph barely cares.</p><p><strong>Cheat 4: checkpoint tuning 23,048 TPS, 0.99x.</strong><br>Larger WAL and smoother checkpoints are normal tuning trade-offs between write smoothness and recovery behavior. In this 120-second isolated run, checkpoint tuning did not move throughput. On a longer, more checkpoint-bound workload it might matter more, but here the dramatic story is elsewhere.</p><p><strong>Cheat 5: all durability off 25,349 TPS, 1.09x.<br></strong>This stacks the scary durability switches, and still only reaches 1.09x over the indexed baseline. That is almost disappointing if the goal is a villainous benchmark. On this NVMe machine, durability cheating is not the magic lever; it is mostly noise dressed as danger.</p><p><strong>Cheat 6: less work 44,777 TPS, 1.93x.</strong><br>Drop the SELECT, the FK check, and the audit insert/cleanup, and PostgreSQL gets much faster. Incredible discovery: doing less work is faster. This is the easiest benchmark trick because the number is real, the graph is honest, and the workload quietly stopped being the same workload.</p><p><strong>Cheat 7: UNLOGGED ledger/audit 37,016 TPS, 1.60x.<br></strong>UNLOGGED tables skip WAL for the high-churn ledger/audit writes, so each transaction writes much less. This is a real PostgreSQL feature and a good fit for disposable or rebuildable data. It is a terrible fit for a ledger you expect to survive a crash (or database restart).</p><p><strong>Cheat 8: Stored Procedure 46,126 TPS, 1.99x.<br></strong>One PL/pgSQL CALL replaces multiple client/server round trips. The database is not doing less logical work; the application is wasting less time. This is a legitimate optimization when the workload fits it, but unfair if the comparison target does not get its equivalent server-side execution path.</p><p><strong>Cheat 9: Function (SQL) 70,626 TPS, 3.05x.<br></strong>This was the cleanest respectable win. A LANGUAGE sql function performs the same logical work but avoids the PL/pgSQL interpreter’s per-statement context switch overhead. If I wanted a PostgreSQL-positive hero result without any shame, this would be the one.</p><p><strong>Cheat 10: prepared protocol 31,250 TPS, 1.35x.<br></strong>pgbench protocol=prepared reuses prepared statements and avoids repeated parse/analyze/plan overhead. This is absolutely legitimate; many real applications use prepared statements through drivers. It becomes dishonest only when your database gets prepared execution and the comparison target gets ad-hoc SQL.</p><p><strong>Cheat 11: SQL function + prepared 70,796 TPS, 3.05x.<br></strong>This combines server-side execution with prepared protocol. Interestingly, it lands almost exactly where the SQL function already was. The big win is still moving the transaction logic server-side; prepared execution helps, but the function shape dominates.</p><p><strong>Cheat 12: batching x8–104,466 reported OPS, but 13,058 TPS.<br></strong>Now the y-axis starts doing marketing work. Each transaction performs eight account operations, so I can report operations/second instead of transactions/second and claim 4.51x over the indexed baseline.</p><p><strong>Cheat 13: batching x32–130,092 reported OPS, but only 4,065 TPS.</strong><br>The headline gets bigger while the transaction rate gets worse. That is perfect benchmark theatre. In operations/second it looks like 5.61x.</p><p><strong>Grand finale: all cheats + batch x32 (SQL function) = 363,915 reported OPS</strong><br>Everything is stacked: SQL function, prepared protocol, batching 32 operations per transaction, relaxed durability, and less integrity work. The result is 363,915 operations/second, or <strong>15.70x over the indexed baseline </strong>and <strong>649x over the crippled Case 0 baseline</strong>. The number is not fake. The framing is the trick.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CgTsT5yI4iPLZXfpWk6dGA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zACtlF8lKYjNom5f4j5sfA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eZFzx-qhfZWLiMaW9if9-A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FJQsxoI7o34xo_Cr-crrRA.png" /></figure><h4>The honest conclusion :<br>**************************************************************</h4><p>My dishonest benchmark starts with bad code, fixes the missing index, changes durability, changes WAL behavior, changes checkpoint behavior, removes work, uses unlogged tables, hides round trips inside PL/pgSQL, batches operations, and then uses prepared protocol.</p><p>The TPS chart goes up and to the right.</p><p>That is exactly what a good marketing chart should do.</p><p>But the more interesting lesson is not that benchmarks can be manipulated. Everyone serious already knows that.</p><p>The interesting lesson is that PostgreSQL has enough mature, boring, production-grade machinery that even a dishonest benchmark has to use real PostgreSQL features to cheat convincingly.</p><p>That is the best kind of promotion.</p><h4>Small disclaimer<br> — — — — — — — —</h4><p>I do not work for a PostgreSQL company. There is no secret Postgres marketing department wiring me money in unmarked WAL segments.</p><p>I am using PostgreSQL because I know it well enough to cheat with it properly. The same performance benchmark ladder could be built with MariaDB, MySQL or almost any serious open-source database.</p><p>The details would change. The crime scene would still look familiar.</p><h3>References :</h3><p><strong>[1] </strong>PlanetScale, “Transparency in benchmarking”, 5 May 2026. The post argues for allowing public benchmarks, removing DeWitt clauses, and using expectations around fairness and transparency. <a href="https://planetscale.com/blog/transparency-in-benchmarking">https://planetscale.com/blog/transparency-in-benchmarking</a></p><p><strong>[2] </strong>Databricks, “Eliminating the DeWitt Clause for Database Benchmarking”, 8 Nov 2021. Useful background on Oracle, the Wisconsin Benchmark, and how DeWitt-style clauses spread through database contracts. <a href="https://www.databricks.com/blog/2021/11/08/eliminating-the-dewitt-clause-for-database-benchmarking.html">https://www.databricks.com/blog/2021/11/08/eliminating-the-dewitt-clause-for-database-benchmarking.html</a></p><p><strong>[3] </strong>Brent Ozar, “The DeWitt Clause: Why You Rarely See Database Benchmarks”, 7 May 2018. Practical explanation of why DeWitt clauses chilled named comparative benchmarking. <a href="https://www.brentozar.com/archive/2018/05/the-dewitt-clause-why-you-rarely-see-database-benchmarks/">https://www.brentozar.com/archive/2018/05/the-dewitt-clause-why-you-rarely-see-database-benchmarks/</a></p><p><strong>[4] </strong>David A. Wheeler, “The DeWitt clause censorship should be illegal”, 25 Jun 2017. Defines the clause as license language preventing publication of product benchmarks without supplier approval. <a href="https://dwheeler.com/essays/dewitt-clause.html">https://dwheeler.com/essays/dewitt-clause.html</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b52d2e706b59" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[PostgreSQL Santa’s Naughty Query List: How to Earn a Spot on the Nice Query List?]]></title>
            <link>https://drunkdba.medium.com/postgresql-santas-naughty-query-list-how-to-earn-a-spot-on-the-nice-query-list-ebc59b800e2f?source=rss-3319eaf2a6c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/ebc59b800e2f</guid>
            <category><![CDATA[postgres]]></category>
            <category><![CDATA[database-development]]></category>
            <category><![CDATA[sql]]></category>
            <category><![CDATA[postgresql]]></category>
            <dc:creator><![CDATA[Mayur (Do not drink & database)]]></dc:creator>
            <pubDate>Tue, 23 Dec 2025 07:01:46 GMT</pubDate>
            <atom:updated>2025-12-26T04:56:03.099Z</atom:updated>
            <cc:license>https://creativecommons.org/publicdomain/mark/1.0/</cc:license>
            <content:encoded><![CDATA[<p>Santa doesn’t judge your SQL by intent. Santa judges it by <em>execution plans, logical io, cpu utilization, temp usage, and response time</em>.</p><p>This is a practical conversion guide: common “naughty” query patterns and the simplest ways to turn each into a “nice list” version that is faster, more predictable, and less likely to ruin your on-call holidays.</p><h3>1) Naughty: SELECT * on wide tables (~100 columns)</h3><h3>Why it’s naughty</h3><ul><li><strong>Wider tuples cost more everywhere</strong>: more memory bandwidth, more cache misses, bigger sort/hash entries, more network payload.</li><li><strong>Index-only becomes impossible</strong>: if you request 100 columns, the planner can’t satisfy the query from a narrow index, so you force heap fetches.</li><li><strong>You pay a bigger “spill tax”</strong>: wide rows make sorts/aggregations spill to disk sooner.</li></ul><h3>Extra naughty in the HTAP era</h3><p>Postgres is increasingly “one database, multiple workloads.” Columnar/analytics options (e.g., Orioledb, Hydra Columnar; DuckDB-backed columnstore/engine integrations) make projection discipline even more decisive, because columnar execution reads <em>only the referenced columns </em>so selecting fewer columns directly reduces I/O and CPU.</p><h3>Nice list fixes</h3><ul><li>Always select only what you need.</li></ul><p><strong>Santa verdict:</strong> If you didn’t need the column, don’t fetch it, don’t carry it, don’t ship it.</p><h3>2) Naughty: WHERE tenant_id = $1 ... ORDER BY x LIMIT N that hits the “ORDER BY + LIMIT optimizer quirk”</h3><h3>Why it’s naughty</h3><p>This is a classic planner trap: the optimizer tries to be clever and exploit an index that matches ORDER BY, so it can stop early with LIMIT. But if the filtering predicate is satisfied “elsewhere” (joins, correlations, distribution skew), Postgres may end up scanning far more rows than expected before it finds the first N that qualify. That’s the performance cliff.</p><p>When it goes bad, you see:</p><ul><li>Long response time (scanning deep into an index to find qualifying rows)</li><li>High temp usage if the alternative plan sorts a large intermediate set and spills</li></ul><h3>Nice list fixes :</h3><p><strong>A. “Make the smart optimizer more stupid” (the </strong><strong>+ 0 trick)</strong><br> In some cases, you can deliberately prevent the planner from matching the ORDER BY to an index by turning it into an expression, e.g. ORDER BY x + 0, which can force a different (often better) join order / plan. This is a common workaround used in the wild.</p><pre>SELECT id, tenant_id, x<br>FROM events<br>WHERE tenant_id = $1<br>ORDER BY x+0<br>LIMIT 200;</pre><p><strong>B: Create covering index or Increasing default_statistics_target</strong></p><pre>ALTER TABLE events<br>ALTER COLUMN tenant_id SET STATISTICS 1666;</pre><p><strong>Santa verdict:</strong> LIMIT is only a turbo button if the engine can find the first rows without searching the entire parking lot.</p><h3>3) Naughty: idle-in-transaction (refcursor + client think time)</h3><h3>Why it’s naughty (and this one is pure coal)</h3><p>Idle-in-transaction is a vacuum’s worst enemy:</p><ul><li>It pins an old snapshot, so <strong>autovacuum can’t reclaim dead tuples</strong> that still might be visible.</li><li>Bloat grows, indexes bloat, and eventually your “mystery slowness” appears.</li><li>Meanwhile the backend is doing nothing, just holding the database hostage.</li></ul><p>The refcursor pattern often creates this by design:</p><ol><li>Begin transaction</li><li>Open cursor</li><li>Fetch some rows</li><li>Client does slow work (several minutes to hours)</li><li>Cursor stays open, transaction stays open</li></ol><h3>Nice list fixes</h3><ul><li><strong>Don’t do application think-time inside a DB transaction.</strong> Fetch quickly, commit, process outside, then write back in a new transaction.</li><li>If you must stream: keep the stream continuous, not “fetch then pause.”</li><li>Enforce guardrails:</li></ul><pre>ALTER ROLE app_user SET idle_in_transaction_session_timeout = &#39;30s&#39;;<br>ALTER ROLE app_user SET statement_timeout = &#39;2min&#39;;</pre><p>Quick detector:</p><pre>SELECT pid, usename, wait_event, now() - xact_start AS tx_age, query<br>FROM pg_stat_activity<br>WHERE state = &#39;idle in transaction&#39;<br>ORDER BY 4 DESC;</pre><p><strong>Santa verdict:</strong> A transaction is not a tote bag. Don’t carry it around while you do errands.</p><h3>4) Naughty: mega-CTE chains (WITH a AS (...), b AS (...), c AS (...) ...)</h3><h3>Why it’s naughty</h3><p>Long WITH pipelines can compound rowcount estimation errors. Once estimates are off, everything downstream is at risk: join order, join type, memory sizing, spill behavior. Also a big headache for prod support teams in debugging production functional or data quality issues.</p><h3>Best nice-list option : split into temp tables</h3><p>This gives you:</p><ul><li>a clean pipeline boundary</li><li>the ability to ANALYZE intermediate results (real stats)</li><li>tactical indexes on stages that matter</li></ul><p>Pattern:</p><pre>DROP TABLE IF EXISTS stage1;<br>CREATE TEMP TABLE stage1 AS<br>SELECT ...;<br><br>ANALYZE stage1;<br><br>CREATE INDEX ON stage1 (join_key);<br><br>DROP TABLE IF EXISTS stage2;<br>CREATE TEMP TABLE stage2 AS<br>SELECT ...<br>FROM stage1<br>JOIN ...;<br><br>ANALYZE stage2;<br><br>DROP TABLE IF EXISTS stage3;<br>CREATE TEMP TABLE stage3 AS<br>SELECT ...<br>FROM stage2<br>JOIN ...;<br><br>ANALYZE stage3;<br>.<br>.<br>.<br>.</pre><p>On <strong>“pg_catalog bloat” </strong>issue : in practice, I have seen bigger wins from <strong>good autovacuum tuning</strong> (cost limit/delay, scale factors, number of workers) than from worrying about catalog bloat as a primary performance constraint.</p><p>Also: yes, many of us are still waiting for “true global temp tables someday.” (pgtt extension doesn’t do anything to reduce catalog bloat)</p><p><strong>Santa verdict:</strong> If the planner can’t see the pipeline clearly, give it stages and statistics.</p><h3>5) Naughty: wildcard search LIKE &#39;%foo%&#39; (and friends)</h3><h3>Why it’s naughty</h3><p>A normal B-tree index can only help when the pattern is <strong>anchored at the start</strong> (e.g., col LIKE &#39;foo%&#39;), not when it starts with %.</p><h3>Nice list fixes</h3><p><strong>Fix A: </strong><strong>pg_trgm</strong></p><pre>CREATE EXTENSION IF NOT EXISTS pg_trgm;<br>CREATE INDEX CONCURRENTLY ON docs USING gin (title gin_trgm_ops);</pre><p>pg_trgm provides GIN/GiST operator classes for fast similarity and pattern searches.</p><p><strong>Fix B: Biscuit (newer alternative)</strong><br> Biscuit is an index access method designed specifically for fast LIKE/ILIKE pattern matching and claims to reduce trigram “recheck overhead” for wildcard-heavy queries. Evaluate it on your data and query mix. <br><a href="https://github.com/CrystallineCore/Biscuit">GitHub</a></p><p><strong>Santa verdict:</strong> Leading % turns your index into holiday decoration: pretty, not load-bearing.</p><h3>6) Naughty: functions on indexed columns (WHERE lower(email) = ...)</h3><h3>Why it’s naughty</h3><p>You turned an indexable predicate into an expression; without an expression index, Postgres often can’t use your B-tree index effectively.</p><h3>Nice list fixes</h3><pre>CREATE INDEX CONCURRENTLY ON users ((lower(email)));<br><br>SELECT ...<br>FROM users<br>WHERE lower(email) = lower($1);</pre><p><strong>Santa verdict:</strong> If you wrap the column, index the wrapper.</p><h3>7) Naughty: OR conditions that derail index usage (a = 1 OR b = 2)</h3><h3>Why it’s naughty</h3><p>OR predicates can push the planner into a compromise plan: either scan too much, or pick a strategy that’s great for one branch and terrible for the other.</p><h3>Nice list fixes</h3><p>Split with UNION ALL (careful to preserve semantics):</p><pre>SELECT ... FROM t WHERE a = 1<br>UNION ALL<br>SELECT ... FROM t WHERE b = 2 ;</pre><p><strong>Santa verdict:</strong> OR is two queries wearing one trench coat.</p><h3>8) Naughty: mismatched join types / implicit casts on join keys</h3><h3>Why it’s naughty</h3><p>If join keys don’t match types, you can:</p><ul><li>prevent index usage</li><li>trigger repeated casting on large relations</li><li>get unexpected nested loops</li></ul><h3>Nice list fixes</h3><ul><li>Fix schema types where possible.</li><li>Otherwise cast the parameter, not the column:</li></ul><pre>WHERE t.uuid_col = $1::uuid</pre><p><strong>Santa verdict:</strong> If your join keys can’t agree on a type, they shouldn’t be meeting in production.</p><h3>9) Naughty: missing indexes for join keys / foreign keys (especially on large tables)</h3><h3>Why it’s naughty</h3><ul><li>Joins devolve into large hash builds or repeated scans.</li><li>Deletes/updates on referenced tables become expensive due to referential checks.</li></ul><h3>Nice list fixes</h3><pre>CREATE INDEX CONCURRENTLY ON child (parent_id);</pre><p><strong>Experimental (not for Prod yet) : </strong>Working on an extension to prevent unindexed foreign keys from going in production. <a href="https://github.com/secp256k1-sha256/tenxdev/blob/main/fkhunter/fkhunter_readme.md">FKHunter</a> (Any improvement suggestions are welcome)</p><p><strong>Santa verdict:</strong> Foreign keys without supporting indexes are a gift you gave yourself…. with no receipt.</p><h3>10) Naughty: SELECT DISTINCT as a band-aid for join explosions</h3><h3>Why it’s naughty</h3><p>DISTINCT often hides:</p><ul><li>missing join predicates</li><li>unintended many-to-many relationships</li><li>data modeling issues</li></ul><p>It forces a sort or hash aggregate on an inflated intermediate result.</p><h3>Nice list fixes</h3><ul><li>Fix the join.</li></ul><p><strong>Santa verdict:</strong> DISTINCT is not deodorant.</p><p>If Santa finds your SQL on the Naughty List, don’t take it personally but take it as a to-do list. Pick one offender this week, run EXPLAIN (ANALYZE, BUFFERS), apply the smallest “Nice List” fix, and measure the win. Do that ten times and you won’t just get better latency; you’ll get a calmer pager, and a database that actually feels like it’s on holiday too.</p><p><em>PS: Extra content for holidy season</em></p><p>SQL-Claus Before :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/938/1*3H762nGVoo68-ChwbZ7u1A.jpeg" /></figure><p>SQL-Claus Now :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/853/1*tEf9Nunh5SRlCzwKBbS6CQ.jpeg" /></figure><blockquote><strong>AI Generated SQL-Claus Last Christmas vs AI Generated SQL-Claus Today. Some would say AI lost it’s soul in rat race of larger models, previous SQL-Claus meme had a character.</strong></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ebc59b800e2f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The OOM-Killer Summoning Ritual: “Just Increase work_mem”]]></title>
            <link>https://drunkdba.medium.com/the-oom-killer-summoning-ritual-just-increase-work-mem-7afb4b22ec44?source=rss-3319eaf2a6c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/7afb4b22ec44</guid>
            <category><![CDATA[database-administration]]></category>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[postgres]]></category>
            <dc:creator><![CDATA[Mayur (Do not drink & database)]]></dc:creator>
            <pubDate>Fri, 19 Dec 2025 21:57:52 GMT</pubDate>
            <atom:updated>2025-12-20T16:41:28.914Z</atom:updated>
            <cc:license>https://creativecommons.org/publicdomain/mark/1.0/</cc:license>
            <content:encoded><![CDATA[<p>You’ve probably seen the incident pattern:</p><ol><li>Postgres backends start disappearing.</li><li>dmesg / journalctl -k shows the kernel OOM killer reaping postgres.</li><li>Someone spots “out of memory” and reflexively recommends: “Increase work_mem.”</li></ol><p>That recommendation is frequently backwards for <em>OS OOM kills</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gz6ienJja1lcfN07KVcj6g.png" /></figure><h3>The linguistic trap: “Out of memory” sounds like “not enough work_mem”</h3><p>work_mem is not “memory for the query.” It is a <strong>base, per-operation</strong> budget for executor nodes like sorts and hash tables <em>before</em> they spill to temporary files. PostgreSQL’s own docs explicitly warn that a complex query can run multiple sort/hash operations concurrently, and many sessions can do this at the same time so total memory can be <em>many times</em> work_mem.</p><p>If you raise work_mem globally, you are raising the ceiling on <strong>many</strong> potential concurrent memory consumers. That can turn “rare spike” into “frequent OOM kill.”</p><h3>OS OOM kill vs “Postgres OOM”: two different failure modes</h3><p>There are two scenarios people accidentally conflate:</p><ul><li><strong>Executor spills to disk (healthy):</strong> Postgres hits a per-node memory budget and writes temp files.</li><li><strong>Kernel OOM kill (host-level failure):</strong> Linux cannot satisfy memory demands (often influenced by overcommit behavior) and terminates a process to keep the system alive.</li></ul><p>In other words: if the kernel is killing Postgres, “give Postgres permission to use even more memory next time” is not a stabilizing strategy.</p><h3>The source code says: work_mem is “allowed memory,” then spill to temp files</h3><p>The tuplesort implementation is blunt about the design: it keeps tuples in memory up to the limit and then switches to temporary “tapes” (temp files) for an external sort.</p><p>A tiny excerpt from src/backend/utils/sort/tuplesort.c (<a href="https://doxygen.postgresql.org/tuplesort_8c_source.html">Doxygen</a>):</p><pre>/* ... memory allowed ... (most pass work_mem) ... */<br>...<br>/* If we do exceed workMem, we begin to emit tuples ... temporary tapes. */</pre><p>This is why “spilling” isn’t synonymous with “misconfigured”. It’s the planned safety valve.</p><h3>Hash nodes can blow past the base: hash_mem_multiplier is literally in the math</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UQ4BEpYZzeIJIwbKF5RL9Q.png" /></figure><p>Hash operations are <em>not</em> capped at work_mem. Postgres computes the hash memory limit as:</p><pre>{<br>    double      mem_limit;<br> <br>    /* Do initial calculation in double arithmetic */<br>    mem_limit = (double) work_mem * hash_mem_multiplier * 1024.0;<br> <br>    /* Clamp in case it doesn&#39;t fit in size_t */<br>    mem_limit = Min(mem_limit, (double) SIZE_MAX);<br> <br>    return (size_t) mem_limit;<br>}</pre><p>That’s get_hash_memory_limit() in src/backend/executor/nodeHash.c. <a href="https://doxygen.postgresql.org/nodeHash_8c.html">Doxygen</a></p><p>The docs also spell out the implications:</p><ul><li>Hash memory limit = work_mem * hash_mem_multiplier</li><li>Default hash_mem_multiplier is 2 (so hash nodes may target ~2× work_mem)</li></ul><p>So if someone says “we set work_mem=128MB, we’re safe,” but you have concurrent HashAgg/HashJoin/Memoize activity, your effective per-node appetite can be meaningfully higher than they think.</p><h3>Why increasing work_mem can make OOM kills more frequent</h3><p>Here’s the mental model that avoids the trap:</p><blockquote><em>work_mem is a </em>multiplier<em> applied by concurrency.</em></blockquote><p>A crude (but useful) worst-case sketch:</p><ul><li>active_sessions = 150 (Typically when a connection pooler is used and peak clients are several thousand then you could end up with few hundred active sessions)</li><li>Conservative assumption that each session runs a query that (at peak) has 2 sorts + 1 hash agg concurrently (3 operations)</li><li>work_mem = 64MB</li><li>hash_mem_multiplier = 2.0</li></ul><p>Then memory budget pressure can look like:</p><ul><li>Sort side: 150 * 2 * 64MB = 19,200MB</li><li>Hash side (approx): 150 * 1 * (64MB * 2) = 19,200MB</li></ul><p>You are already around ~38GB of <em>just</em> sort/hash budgets, before counting:</p><ul><li>backend overhead, memory contexts, connection-local memory</li><li>shared memory, OS cache dynamics, other processes</li><li>parallel query workers (more processes, more operations)</li></ul><p>Now imagine someone “fixes” OOM kills by doubling work_mem to 128MB. That same sketch becomes ~76GB. If the host has 64GB RAM, you didn’t tune performance instead you scheduled the next kill.</p><p>This is why pganalyze’s guidance for frequent OOM errors starts with: <strong>reduce </strong><strong>work_mem</strong> to improve stability, accepting more disk spill as the tradeoff. <a href="https://pganalyze.com/docs/log-insights/server/S5">pganalyze</a></p><h3>A practical playbook: stabilize first, then optimize surgically</h3><p>If you want fewer OOM kills <em>and</em> good performance, separate <strong>global defaults</strong> from <strong>intentional exceptions</strong>:</p><h3>1) Confirm it’s a kernel OOM kill (not just query failure)</h3><ul><li>Check journalctl -k / dmesg for OOM killer messages (process name/PID, reaped memory).</li></ul><h3>2) Make temp spill observable (because spill is the designed safety valve)</h3><p>Turn on temp file logging to see which queries are forcing external sorts / hash batching:</p><ul><li>log_temp_files = 0 (log all temp files) or set a threshold in kB</li></ul><p>Also consider:</p><ul><li>temp_file_limit to prevent a single session from consuming unbounded temp space (it cancels the transaction when exceeded). <a href="https://postgresqlco.nf/doc/en/param/temp_file_limit/">PostgreSQL</a></li></ul><h3>3) Set a conservative global work_mem, then raise it only where justified</h3><ul><li>Keep a modest default that survives peak concurrency.</li><li>For the one reporting job / ETL / admin session that benefits from more memory, use <strong>targeted</strong> increases: <br>SET LOCAL work_mem = &#39;128MB&#39;; inside a transaction <br>or ALTER ROLE reporting_user SET work_mem = &#39;128MB&#39;;</li></ul><p>This preserves cluster stability while still enabling fast “big” queries.</p><h3>4) Use EXPLAIN to validate the tradeoff</h3><p>You want to see spills when needed, not crashes:</p><ul><li>Sort Method: external merge with disk usage is a normal outcome when data exceeds budget.</li><li>Hash nodes show batching when they spill.</li></ul><h3>5) Don’t ignore Linux overcommit behavior</h3><p>In overcommit-heavy configurations, you can get killed before Postgres can gracefully error and log a memory context dump. Cybertec and Postgres community guidance often points to disabling overcommit (or otherwise managing it) to avoid OOM-killer surprise terminations. <a href="https://www.cybertec-postgresql.com/en/what-you-should-know-about-linux-memory-overcommit-in-postgresql/">CYBERTEC PostgreSQL | Services &amp; Support</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aob3sV441xzv-yjGkt0KLw.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7afb4b22ec44" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why Application Developers Using AI Is Great For DBA Job Security]]></title>
            <link>https://drunkdba.medium.com/why-application-developers-using-ai-is-great-for-dba-job-security-43042dc0f294?source=rss-3319eaf2a6c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/43042dc0f294</guid>
            <category><![CDATA[postgres]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[postgresql-database]]></category>
            <dc:creator><![CDATA[Mayur (Do not drink & database)]]></dc:creator>
            <pubDate>Mon, 17 Nov 2025 14:09:53 GMT</pubDate>
            <atom:updated>2025-11-17T14:09:53.510Z</atom:updated>
            <cc:license>https://creativecommons.org/publicdomain/mark/1.0/</cc:license>
            <content:encoded><![CDATA[<p>Everyone’s freaking out about AI taking their jobs. Meanwhile, I’m a DBA sitting in the corner thinking: <em>“If programmers of the future are AI agents, companies will need 100x more human DBAs to clean up the mess in production.”</em></p><p>This rant blogpost is my attempt to explain why.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eaORcSJJ5W5gUt2-wAKVsA.png" /></figure><h3>LLMs: Optimizing for the Next Token, Not for Reality</h3><p>Let’s start with the core problem:</p><p>LLMs don’t optimize for <em>truth</em>.<br>They optimize for <em>“what word statistically looks good next?”</em></p><p>Give a model the text:</p><blockquote><em>“Postgres DBAs love”</em></blockquote><p>It might happily continue with:</p><blockquote><em>“Oracle”</em></blockquote><p>From there, it feeds its own output back in and keeps predicting the next token again and again. At no point does it pause and say: <em>“Wait, does this actually represent reality?”</em></p><p>It has no built-in concept of <em>reality</em> or <em>verification</em>. That’s your job.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*885E6nEcHscvnroRStLYmg.png" /></figure><p>As long as this is the operating model, hallucinations are not a bug but they are a <em>feature</em>. They’re what you get when you combine probability with confidence and zero shame. We’re basically wiring a very polite, very confident intern to production and asking it architectural questions.</p><p>What could go wrong?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/833/1*O3Up4OXF44nTpwetoVOOfw.png" /></figure><h3>“Hallucinations” Is Just a Fancy Word for “We Noticed the Lie”</h3><p>I liked one line from an MIT Tech Review piece: “<em>It’s </em>all<em> hallucination, but we just call it that when we notice.”</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OhdXtMQnHYh66JwudVE7EQ.png" /></figure><p>Most of the time, when the answer is “good enough”, we call it “AI magic”.<br> When it’s wrong in a way we understand, suddenly it becomes “hallucination”.</p><p>From a former physics student perspective, this is just non zero probability in action. We already live with quantum tunneling and in theory there’s a very very small but finite probability your laptop falls through the table while reading this paragraph. So the idea that an AI occasionally invents a Postgres feature, a config parameter, or even a fake “Postgres founder” called <em>Michael Stockbroker</em> is not that hard to digest.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1dVjcgIwhGeLRtlYytTUMg.png" /><figcaption>LLMs are Pinnochio’s of modern world.</figcaption></figure><h3>Minimizing Hallucinations ≠ Eliminating Them</h3><p>Yes, there are techniques:</p><ul><li>Retrieval-augmented generation (RAG)</li><li>Better prompts.</li><li>Constraining answers to a small domain.</li><li>Larger training data, new improved models.</li></ul><p>They help. They reduce the probability of a lie.<br>They never turn it into zero. So instead of asking, <em>“How do we make AI always right?” </em>We should be asking, <em>“What happens to systems and teams when AI is </em><strong><em>wrong</em></strong><em> in confident, creative ways?”</em></p><p>That’s where Postgres DBA’s job security enters the chat.</p><h3>Postgres vs AI: Field Reports from the Trenches</h3><p>We don’t need theory. Just look at the real world of Postgres chats, blogs, and forums.</p><h3>1. The Adaptive Optimizer That Doesn’t Exist</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ezbwlx3vz6JtXfqaaQ8kUg.png" /></figure><p>One popular LinkedIn post proudly announced that <strong>Postgres now has an adaptive optimizer</strong>, just like Oracle changing plans mid-execution, magically fixing bad queries at runtime.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hnt2Wqzt6ePAmGU247M5qQ.png" /></figure><p>Reality check:</p><ul><li>Postgres does <strong>not</strong> have Oracle-style adaptive optimizer.</li><li>People migrating from Oracle to Postgres often <em>wish</em> it did.</li><li>Most serious Oracle deployments especially in finance domain don’t enable adaptive optimization in production anyway. They value <em>predictable latency</em>, not “charismatic magic” that wakes up once every blue moon and wrecks your critical workload.</li></ul><p>But somewhere, an LLM saw enough Oracle docs, blogs, and forum posts and decided: <em>“Postgres is a database. Oracle is a database. Adaptive optimizer is cool. Therefore, Postgres now has an adaptive optimizer.”</em></p><p>And now that garbage is in the content pool.</p><h3>2. “Even ChatGPT Failed to Answer This!”</h3><p>From Postgres community chat: user confused about why something wasn’t working.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/848/1*jZTf8twRlsfQAWdKnnZuig.png" /></figure><p>LLM confidently explained that &#39;&#39; (empty quoted whitespace) is the same as NULL.<br>Humans: <em>“No. That’s not NULL. That’s just an empty string. Here’s why.”<br></em>One line from a human fixed it.<br>An LLM left the user more confused.</p><h3>3. TTL Indexes in Postgres? Sure, Why Not</h3><p>Someone shows up: “I read about TTL indexes in Postgres, how do I use them?”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iLdd3uvI1TeWHGzAQEmjxw.png" /></figure><p>Short answer from the community: <em>“You don’t. Postgres doesn’t have native TTL indexes.”</em></p><p>Of course, you <em>can</em> build TTL-like behavior with partitioning and pg_cron schedular based archival/dropping of old, unnecessary partitions. But there’s no single magical TTL index type like in some other databases.</p><p>LLMs, however, happily remix MongoDB + Postgres content into: <em>“Here’s how to create a TTL index in Postgres”</em></p><p>And now we’re babysitting that.</p><h3>4. “Just Tweak the Background Workers in Prod”</h3><p>One of my favorites: AI confidently suggesting users adjust internal Postgres <strong>background workers</strong> in ways no sane human would recommend to any developer.</p><p>This is the kind of advice that:</p><ul><li>Looks sophisticated</li><li>Sounds low-risk</li><li>Is absolutely <strong>not</strong> for casual experimentation on prod instances</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/916/1*2DEF0sgJtoNeVx2O3_4duA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*W0D6RB8OF9-DGSmrVVGO9Q.png" /></figure><p>Once that advice leaks into tutorials/blogs, it slowly contaminates future AI training runs. Then the next generation of LLMs produces even more polished nonsense.</p><p><strong>High-quality garbage. With headings.</strong></p><h3>5. LLM induced extra work</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*56rkzTdXLwKcX2MKDB2jSQ.png" /></figure><p>An LLM confidently advises: <em>“Now you should rebuild all indexes to be safe.”</em></p><p>No, you shouldn’t.</p><ul><li>Reindexing everything after a successful upgrade is an <em>unnecessary</em> time sink, risk that adds downtime to upgrade.</li><li>LLM probably got confused with reindexing required on OS update due to <a href="https://www.crunchydata.com/blog/glibc-collations-and-data-corruption">(in)famous glibc issue</a>.</li></ul><p>But sure, if someone blindly follows it, that’s:</p><ul><li>Extra downtime,</li><li>Extra IO,</li><li>Extra DBA hours.</li></ul><h3>6. Two Node Patroni HA with Autofailover 🤡</h3><p><em>“Design a 2-node Patroni HA cluster with auto-failover. DCS, Postgres, Patroni all on just two machines.”</em></p><p>LLM: <em>“Of course! Here’s a step-by-step guide…”</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FRSBrhy-iOl8GuDDUSyrvA.png" /></figure><p>Human DBA: <em>“No. Just… no. You are designing a split-brain machine.”</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/791/1*Q-YZZk2ZIrvtprCIPQ629Q.png" /></figure><p>Because in a network partition with two nodes, both can think they’re the primary. And then you have:</p><ul><li>Diverged writes</li><li>Broken constraints</li><li>Business screaming</li><li>Audit logs full of horror</li></ul><p>Who saves the day?<br>Not the LLM.</p><h3>Data Cannibalism: When AI Starts Eating Its Own Slop</h3><p>Another fun part of this story: <strong>data cannibalism</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/950/1*vaiRPcXD8MK25kVK8h8uHg.png" /></figure><p>As more AI generated content floods the internet:</p><ul><li>That content gets scraped into future training data.</li><li>The model trains on its own previous guesses.</li><li>Errors compound.</li><li>Rare edge cases vanish.</li><li>Details blur and collapse toward generic nonsense.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ju5a4OJSEGB6SBkSr5FpjA.png" /></figure><p>There’s already published work showing that models trained on AI generated data <strong>collapse</strong> over generations. And estimates that we may run out of clean human generated data for training in a few years, depending on how aggressively we keep training new models.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*x64TuF8WN-J0AoUDrJBKsg.png" /></figure><p>Even worse: those estimates often <em>don’t</em> factor in how much AI slop humans are now posting as their own “blog posts” and “whitepapers”.</p><p>One research paper about Postgres I came across had very clear indicators of being LLM generated fake :</p><ul><li>“In the digital world…” as intro</li><li>AI-generated fake graphs : A misspelling in the chart title but correct text in the caption</li><li>And it credited Postgres to founder “Michael Stockbroker”</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/890/1*YxwG2AQxSxySSLin87u9yg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/890/1*0MUxQmHrskyaRqmEnpf1FA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/625/1*fVchHRQ_EylrWxhJjGrWTw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/666/1*doRezDox0Dom0DLY0FHuxQ.png" /></figure><p>So we’re seeding the future with:</p><ul><li>Invented features</li><li>Incorrect design patterns</li><li>Misleading performance “benchmarks”</li><li>Fake Subject Matter Experts</li></ul><p>This is the stuff going into future model training sets.<br>Enjoy your model collapse.</p><p><strong>Meanwhile, humans DBAs become more valuable, not less.</strong></p><h3>So… What Can AI Safely Do for Postgres?</h3><p>Despite all this, I’m not <strong>anti-Automation</strong>.<br>I’m anti-“AI as a magical production architect”.</p><p>There <em>are</em> areas where narrow AI with <strong>limited scope for creativity</strong> can be useful:</p><h3>1. DB Parameter Tuning (Within Limits)</h3><p>Tools like DBTune that:</p><ul><li>Suggest reasonable shared_buffers, work_mem etc Parameter ranges.</li><li>Maybe even propose changes in a Git-style diff</li></ul><p>This is bounded, repeatable, and easy to verify. Worst case, you roll back a config.</p><h3>2. Index Advisors</h3><p>Systems that:</p><ul><li>Analyze postgres logs, waits and pg_stat_statements</li><li>Suggest candidate indexes</li><li>Estimate bloat and usage</li></ul><p>As long as they stay in “advisor” mode and a human reviews changes before applying, they’re super helpful. Think <strong>pganalyze</strong> style, not “Let the LLM directly CREATE INDEX in prod at 11:55 before Black Friday”.</p><h3>3. Ops Automation</h3><p>Great use cases:</p><ul><li>Alerting on txid wraparound risk</li><li>Growing storage before it fills</li><li>Notifying you when replication lag spikes</li><li>Automating some backup/restore checks</li></ul><p>Basically: if it’s mechanical, measurable, and reversible automation is powerful.</p><h3>Future of AI for DBAs: Expectation vs Reality</h3><p><strong>Expectation:</strong><br>AI agents write all the code, optimize all the queries, run all the operations.<br>DBAs disappear. Everything is self-healing.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/916/1*mlQrsp2kBx44jd1fk3sSmQ.png" /></figure><p><strong>Reality:</strong></p><ul><li>AI generates weird schemas, terrible queries, and unsafe designs.</li><li>AI hallucinates Postgres features that don’t exist.</li><li>AI amplifies bad advice from old mailing list threads.</li><li>Startups implement this stuff directly in production.</li><li>Systems break in more creative ways than ever before.</li></ul><p>And then those companies:</p><ul><li>Hire senior DBAs</li><li>Pay more per hour</li><li>Prioritize performance &amp; reliability</li><li>Buy support contracts they previously refused</li></ul><p>So if you’re a DBA worrying about AI taking your job, relax. You’re going to be busy.</p><p>Very, <em>very</em> busy.</p><p>PS: By popular demand, I’ve included some extra AI-generated content. I accept no liability, take zero responsibility, and fully classify the following as <em>AI slop found in the wild</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1005/1*sfbhDMWl5yQd1dgueU2jSw.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=43042dc0f294" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ALTER Egos: Me, Myself, and Cursor]]></title>
            <link>https://drunkdba.medium.com/alter-egos-me-myself-and-cursor-3978fd29245f?source=rss-3319eaf2a6c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/3978fd29245f</guid>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[postgres]]></category>
            <category><![CDATA[database-administration]]></category>
            <dc:creator><![CDATA[Mayur (Do not drink & database)]]></dc:creator>
            <pubDate>Tue, 04 Nov 2025 05:56:18 GMT</pubDate>
            <atom:updated>2025-11-04T05:56:18.419Z</atom:updated>
            <cc:license>https://creativecommons.org/publicdomain/mark/1.0/</cc:license>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AGJH6azqsKarnhWk6bBvdA.png" /></figure><p>I pushed the most boring change imaginable, add an index. Our CI/CD pipeline is textbook ==&gt; spin up a fresh DB, run every migration file in <strong>one single transaction</strong>, in sequential manner. If anything hiccups, the whole thing rolls back and the change never hits main. Foolproof autotests.</p><p>Enter The Drama Queen :</p><pre>ERROR: cannot CREATE INDEX &quot;index_name_xyz&quot; on table abcdef_tab<br>because it is being used by active queries in this session</pre><p>This session. Same PID, same transaction. No parallel runner. No second connection. Still blocked.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/522/1*uOap503Ed44D9JwgJiG_1Q.jpeg" /></figure><p>Our schema migration repository structure is as shown above. CI tool will create a fresh DB → execute all scripts chronlogically → one by one → one transaction → one session as a part of an autotest before merging any new code (dml/ddl for schema migration = infra as a code) to main branch.</p><p>Naturally ignoring last part of error message like a seasoned DBA, I accused CI tool of going rogue, maybe someone “optimized” migrations to run in parallel after reading a LLM pep talk about “unleashing concurrency.”</p><p>I printed the PID at the start and end of every file.</p><pre>-- Stamp the migration transaction<br>SELECT &#39;start&#39;, pg_backend_pid(), txid_current();<br>-- ... run all migration steps ...<br>SELECT &#39;end&#39;,   pg_backend_pid(), txid_current();</pre><p>Same PID. Same transaction. Same sadness.</p><p>One of the least talked about but best Postgres feature as a FOSS project is that you can search error message in code base and documentation is excellent.</p><ul><li>In indexcmds.c, PostgreSQL calls CheckTableNotInUse(rel, &quot;CREATE INDEX&quot;); before proceeding. The comment explains <strong>why</strong>: otherwise an in-progress INSERT/UPDATE in <em>this same session</em> could have already picked its list of target indexes and would not update the new one. <a href="https://doxygen.postgresql.org/indexcmds_8c_source.html">doxygen.postgresql.org</a></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UMcNh69KJ13rd3smEGhPhA.png" /></figure><ul><li>CheckTableNotInUse() (in tablecmds.c) raises<br> ERROR: cannot %s &quot;%s&quot; because it is being used by active queries in this session<br> when the relation’s refcount shows it’s still in use by the current backend (or if there are pending AFTER triggers).</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4yqsW0fbWuNUwNLAn6h_Pw.png" /></figure><p>It clearly says just above CheckTableNotInUsethat there’s an open cursor or active plan.</p><blockquote>“Disallow ALTER TABLE (and similar commands) when the current backend has any open reference to the target table besides the one just acquired by the calling command; this implies there’s an open cursor or active plan.”</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9-4vwUugwbONRcQhlCkcqA.png" /></figure><p>So not another session. <strong>This</strong> session. And if there’s no parallelism and no second plan lingering, what’s left? As Sherlock Holmes would say: once you eliminate the impossible, whatever remains, however improbable…. is that one open cursor someone forgot to close.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3EcQDwJLukU8CMALJJhgEA.jpeg" /></figure><p>I slapped a CLOSE ALL; at the top of the migration file just to test the hypothesis. Boom =&gt; green CI. Then I hunted down the culprit, a recently added explicit cursor loop (OPEN …; FETCH …;) instead of a tidy FOR r IN SELECT … LOOP.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4tJD8bB-A5n27SJAMWjXGg.jpeg" /><figcaption>Culprit Found</figcaption></figure><p>The cursor never got closed, so the relation stayed referenced, and Postgres (correctly) refused.</p><p><strong>In short: my session blocked….. my session. Peak self-sabotage.</strong></p><p><strong>Simple fix:</strong> stop leaking cursors. Prefer FOR rec IN SELECT … LOOP (implicit cursor) or explicitly CLOSE the cursor (also in EXCEPTION block).</p><p>PS : Slightly off topic but doxygen.postgresql.org is cool.<br>A piece of history : Magnus Hagander announcing auto-generated source code documentation (not the user manual).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1008/1*yNF5Wg_1Y6mmG5ZhEQ5nlg.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3978fd29245f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Slonik on the Catwalk: PGConf.EU 2025 Recap]]></title>
            <link>https://drunkdba.medium.com/slonik-on-the-catwalk-pgconf-eu-2025-recap-b4990c626271?source=rss-3319eaf2a6c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/b4990c626271</guid>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[pgconf]]></category>
            <category><![CDATA[postgres]]></category>
            <dc:creator><![CDATA[Mayur (Do not drink & database)]]></dc:creator>
            <pubDate>Mon, 27 Oct 2025 11:53:47 GMT</pubDate>
            <atom:updated>2025-10-27T12:36:28.233Z</atom:updated>
            <cc:license>https://creativecommons.org/publicdomain/mark/1.0/</cc:license>
            <content:encoded><![CDATA[<p>I volunteered as a room host and Slonik guide.<br>Best gig: posing our elephant. The photographer had runway-level ideas. Slonik delivered every single time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lKT_xGfXAuhfCV5OEgwNeg.jpeg" /><figcaption>Slonik modelling session</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*V968rMy_NK8cy4Zb4mrZrA.jpeg" /><figcaption>Slonik having a Diva moment</figcaption></figure><h3>Community Day ~ people &gt; hype</h3><ul><li><strong>PostgreSQL &amp; AI Summit</strong>: I sat on the panel and played “Team Human” vs Skynet (As advised by John Connor in the future).<br> <a href="https://www.postgresql.eu/events/pgconfeu2025/schedule/session/7149-postgresql-ai-summit/">postgresql.eu</a></li><li><strong>“Establishing the PostgreSQL standard: What’s Postgres compatible?”</strong><br>Half-day workshop, lot of brain storming and discussion split into groups then presenting your group’s conclusion on what makes postgres derivatives compatible with community postgres. We spun up a Telegram group to keep building the rubric post-conference. <a href="https://www.postgresql.eu/events/pgconfeu2025/schedule/session/7152-establishing-the-postgresql-standard-whats-postgres-compatible/">postgresql.eu</a></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xMLr01XRUo1SejbraVDA4g.jpeg" /><figcaption>The Hallway Track</figcaption></figure><h3>Coffee with CYBERTEC (meeting Laurenz Albe)</h3><ul><li>Picked the <strong>“Coffee with CYBERTEC”</strong> option to meet Laurenz Albe, the most prolific Stack Overflow answerer.</li><li>We traded notes on most popular <strong>features to adopt</strong> from other databases, their <strong>feasibility</strong>, and <strong>why Postgres avoided them historically</strong>.</li></ul><p>I left with a <strong>starting map for contributing to core.</strong></p><h3>Talks I caught (and why they stuck)</h3><p><strong>“Don’t do that!” — Laurenz Albe</strong><br>A rapid-fire list of Postgres anti-patterns. Simple, blunt, useful from the most beloved speaker of the conference. (<a href="https://www.postgresql.eu/events/pgconfeu2025/sessions/session/6838/slides/742/dont_do_that_m.pdf">postgresql.eu</a>)</p><p><strong>Parsing Postgres logs the non-pgBadger way — Kaarel Moppel</strong><br>Meet pgweasel. Lean CLI. Fast. Cloud-friendly. For prod support, less noise beats glossy graphs. I’m convinced. (<a href="https://www.postgresql.eu/events/pgconfeu2025/sessions/session/6975-parsing-postgres-logs-the-non-pgbadger-way/">postgresql.eu</a>)<br><a href="https://github.com/kmoppel/pgweasel">https://github.com/kmoppel/pgweasel</a></p><p><strong>Improved freezing in VACUUM — Melanie Plageman</strong><br>Cleaner anti-wraparound story. Scan all-visible (not all-frozen) pages early; fewer emergency freezes later. Sensible, much needed change. (<a href="https://www.postgresql.eu/events/pgconfeu2025/schedule/session/6952-improved-freezing-in-postgres-vacuum-from-idea-to-commit/">postgresql.eu</a>)</p><p><strong>Patroni + pgBackRest: better together — Stefan Fercot</strong><br>Power couple of Postgres, best HA tool with best Backup tool. Tight HA+DR integration. Bootstrap from backups, safe standby rebuilds, PITR under Patroni’s control. (<a href="https://www.postgresql.eu/events/pgconfeu2025/sessions/session/6961-patroni-and-pgbackrest-better-together/">postgresql.eu</a>)</p><p><strong>DBTune: AI-driven tuning</strong><br>Autonomous parameter tuning across self-hosted and managed Postgres. Looks mature enough for a lab trial. I’m tempted to test in QA. (<a href="https://www.postgresql.eu/events/pgconfeu2025/sessions/session/7187-dbtune-ai-driven-performance-tuning-for-all-postgresql-flavors/">postgresql.eu</a>)</p><p><strong>The SyncRep Detective Story</strong><br>Fascinating detective story and scientific approach to tracing root cause. Resonated with my past setup where commit_delay/commit_siblings helped on AWS networked storage. (<a href="https://www.postgresql.eu/events/pgconfeu2025/schedule/session/7037-the-syncrep-detective-story-chasing-ghosts-in-postgresql-finding-demons-in-storage/">postgresql.eu</a>)</p><p><strong>MultiXacts: usage, side-effects, monitoring — Divya Sharma</strong><br>Row-lock pileups and vacuum side effects, explained. We don’t hit them often in current company (rareSELECT … FOR UPDATE), but I left with better alarms to build. (<a href="https://www.postgresql.eu/events/pgconfeu2025/schedule/session/6887-multixacts-in-postgresql-usage-side-effects-and-monitoring/">postgresql.eu</a>)</p><p><strong>Fast-path locking in PG18 — Tomas Vondra</strong><br>Shines with many relations and partitions. We’re light on partitioning at current company, so the gains will be modest for us. Still, good progress. (<a href="https://www.postgresql.eu/events/pgconfeu2025/schedule/session/6891-fast-path-locking-improvements-in-pg18/">postgresql.eu</a>)</p><p><strong>Patroni: what the blog posts don’t tell you — Cameron Murdoch</strong><br>The “missing manual”: hardening, proxies, DCS choices, failsafe option and upgrades. (<a href="https://www.postgresql.eu/events/pgconfeu2025/schedule/session/7018-patroni-what-the-blog-posts-dont-tell-you/">postgresql.eu</a>)</p><p><strong>We have multiple concurrent versions of this title trying to understand MVCC (Boris Mejias)</strong><br>It’s hard to describe this talk cause you have to experience it live to fully appreciate the second best database comedian in the world.</p><p><strong>Tracking plan shapes over time — pg_stat_plans (Lukas Fittl)</strong><br>Plan IDs + pg_stat_plans let you watch plan drift over time. This will be gold for catching plan fluctuations. (<a href="https://www.postgresql.eu/events/pgconfeu2025/schedule/session/7112-tracking-plan-shapes-over-time-with-plan-ids-and-the-new-pg_stat_plans/">postgresql.eu</a>)<br><a href="https://github.com/pganalyze/pg_stat_plans">https://github.com/pganalyze/pg_stat_plans</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mMbHlV9p78pn-KyukvDIYg.jpeg" /><figcaption>Feeding Session</figcaption></figure><h3>Conference vibe</h3><p>Riga was friendly. Hallway track was lively. Slonik worked overtime and loved the camera. See you next year.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YK30PMnHe4VhLCJdQ5SKuQ.jpeg" /><figcaption>“My Watch Has Ended” — Slonik Snow</figcaption></figure><p>PS: If you are still suffering from Postgres conference hangover and crave more Postgres content then head over to Prague next month for Prague Postgres Meetup or in January for P2D2 conference.</p><p><a href="https://docs.google.com/presentation/d/1XwSOlMNcB7d3IH94CmQ6uOUkPmhVWeqE7KacBl2xF_Y/edit?usp=sharing"><strong><em>13 REASONS WHY YOU SHOULD ATTEND P2D2 PRAGUE?</em></strong></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b4990c626271" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[PGConf.EU 2025: The Underground Map for Database Nerds]]></title>
            <link>https://drunkdba.medium.com/pgconf-eu-2025-the-underground-map-for-database-nerds-da3587a90b2e?source=rss-3319eaf2a6c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/da3587a90b2e</guid>
            <category><![CDATA[postgres]]></category>
            <category><![CDATA[postgresql]]></category>
            <dc:creator><![CDATA[Mayur (Do not drink & database)]]></dc:creator>
            <pubDate>Fri, 17 Oct 2025 22:57:58 GMT</pubDate>
            <atom:updated>2025-10-17T22:57:58.446Z</atom:updated>
            <cc:license>https://creativecommons.org/publicdomain/mark/1.0/</cc:license>
            <content:encoded><![CDATA[<p>PGConf.EU schedule can feel like a parallel query gone wild, so many great talks but not enough CPU.<br>I built this guide to help my fellow database nerds skip the overwhelm and enjoy the best prod-DBA focussed sessions without a single deadlock.<br>Follow this path, and you’ll cruise through the conference like a perfectly tuned autovacuum.</p><h3>🗓️ Wednesday, Oct 22 — Warming Up the Buffers</h3><p><strong>11:15 – 12:05 in Omega 1 : </strong><em>Don’t Do That!</em> <br>Laurenz Albe reminds us that every bad Postgres habit comes with a sequel called “incident report.”</p><p><strong>13:05 – 13:35 in Omega 2 : </strong><em>Parsing Postgres Logs the Non-pgBadger Way</em> <br>Kaarel Moppel shows that pgweasel and caffeine can out-analyze any dashboard.</p><p><strong>13:45–14:35 in Alfa : </strong><em>Improved Freezing in Postgres Vacuum: From Idea to Commit</em> <br>Melanie Plageman walks us through the icy depths of tuple immortality.</p><p><strong>14:45–15:35 in Omega 2 : </strong><em>Operational Hazards of Running PostgreSQL Beyond 100 TB</em> <br>Teresa Lopes shares real stories and engineering lessons from scaling Postgres into the terabyte realm, where every decision costs you.</p><p><strong>16:05–16:55 in Omega 2 </strong>: <em>What You Should Know About Constraints (and What’s New in 18)</em> <br>Gülçin Yıldırım Jelínek explores how new enhancement to constraints in PG 18 make data integrity both smarter and more flexible.</p><p><strong>17:05–17:55 in Omega 1 : </strong><em>Hacking pgvector for Performance</em> <br>Daniel Krefl reveals clever hacks to push filtering and indexing deeper into pgvector for faster, leaner similarity searches.</p><h3>🧱 Thursday, October 23 — The Day of Observability and Enlightenment</h3><p><strong>09:25–10:15 in Omega 2 : </strong><em>Unified Observability: Monitoring Postgres Anywhere with OpenTelemetry</em> (Yogesh Jain)<br>Learn how to unify metrics, logs, and traces across cloud, containers, and bare-metal Postgres instances using OpenTelemetry to build scalable, vendor-agnostic observability.</p><p><strong>10:25–10:55 in Omega 1 : </strong><em>EXPLAIN: Make It Make Sense</em> (Aivars Kalvāns)<br>Find out how to turn cryptic EXPLAIN output into actionable insights, mapping planner nodes to real costs so you can tame rogue queries.</p><p><strong>11:25–12:15 in Alfa : </strong><em>The SyncRep Detective Story: Chasing Ghosts in PostgreSQL, Finding Demons in Storage</em> <br>Dmitry Fomin unravels a real performance mystery and exorcises the demons hiding deep inside the storage.</p><p><strong>13:55–14:45 in Alfa</strong> : <em>AIO in PG 18 and Beyond</em> (Andres Freund)<br>Explore how asynchronous I/O enhancements in Postgres 18 shift the performance landscape.</p><p><strong>14:55–15:45 in Omega 1 : </strong><em>Table Repacking, Done Right</em> (Álvaro Herrera &amp; Antonin Houska) <br>Learn how <strong>REPACK CONCURRENTLY</strong> (targeting Postgres 19) can deflate bloat without downtime, how it works under the hood, and how it might rescue frustrated DBAs from locking hell.</p><p><strong>15:55–16:25 in Omega 2</strong> : <em>All About Common Vulnerabilities and Exposures in PostgreSQL</em> (Priyanka Chatterjee)<br>Get real: dissect real CVEs in PostgreSQL, see how they were exploited, and learn how to protect your systems before headlines hit.</p><h3>🧨 Friday, October 24 — The Day of Failures, Recovery &amp; Redemption</h3><p><strong>09:25–10:15 in Omega 2</strong> : <em>PostgreSQL as a Graph Database: Who Grabbed a Beer Together?</em> (Taras Kloba)<br>Taras compares Apache AGE, pgRouting, and pgGraph in a live demo to show how PostgreSQL can moonlight as a graph database and map community connections (yes, including who met at the bar).</p><p><strong>10:25–10:55 in Omega 1 : </strong><em>Fast-Path Locking Improvements in PG18</em><br>Tomas Vondra takes us on a deep dive into lock manager refinements in PG 18 that reduce contention latency in hot, high-load scenarios.</p><p><strong>11:25–12:15 in Omega 1 : </strong><em>Patroni: What the Blog Posts Don’t Tell You…</em> (Cameron Murdoch)<br> Cameron pulls back the curtain on real Patroni deployments: split-brain, failover pitfalls, and the nitty-gritty never documented.</p><p><strong>13:15–13:45 in Omega 2</strong> : <em>Tracking Plan Shapes Over Time with Plan IDs &amp; the New pg_stat_plans</em> (Lukas Fittl)<br>Lukas unveils how plan ID versioning and the new pg_stat_plans help you track plan drift, regressions, and performance ghosts over time.</p><p><strong>13:55–14:45 in Alfa : </strong><em>We Have Multiple Concurrent Versions of This Title Trying to Understand MVCC</em> (Boriss Mejias)<br>Boriss leads you through concurrency, snapshot races, and how Postgres keeps isolation sane in the chaos of multiversions.</p><p><strong>14:55–15:45 in Omega 2 : </strong><em>Database in Distress: Testing and Repairing Different Types of Database Corruption</em> (Josef Machytka)<br>Forensic lab of data corruption cases and how to surgically repair them.</p><p><strong>16:15–17:00 in Omega 1 : </strong><em>Lightning Talks (Maybe you?)</em><br>A wild finale: short, sharp experiments, hacks, and stories to spark ideas and laugh off days of log parsing.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eYEtdjD3m6fyVYp8med02w.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=da3587a90b2e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Unsung Heros of Postgres : Episode I]]></title>
            <link>https://drunkdba.medium.com/unsung-heros-of-postgres-episode-i-b9a7056447d2?source=rss-3319eaf2a6c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/b9a7056447d2</guid>
            <category><![CDATA[postgresql-database]]></category>
            <category><![CDATA[database-administration]]></category>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[postgres]]></category>
            <dc:creator><![CDATA[Mayur (Do not drink & database)]]></dc:creator>
            <pubDate>Sun, 14 Sep 2025 17:25:31 GMT</pubDate>
            <atom:updated>2025-09-14T17:25:31.442Z</atom:updated>
            <cc:license>https://creativecommons.org/publicdomain/mark/1.0/</cc:license>
            <content:encoded><![CDATA[<h3>Unsung Heros of Postgres : Episode I</h3><p>Not all heroes wear capes. In PostgreSQL, some don’t even write code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*fhn5NG4U11XEIDLO" /></figure><p>At <a href="https://pgday.at/talk/roll-up-your-sleeves-contributing-to-postgres-beyond-code/">PGDay Austria</a>, Floor Drees highlighted this truth: countless contributions to PostgreSQL happen outside of code commits and patches.</p><p>Floor introduced <strong>postgres-contrib.org</strong>, a website launched in July 2024 by members of the community. Its mission is simple but powerful, celebrate the people behind PostgreSQL. Advocates, conference organizers, volunteers, speakers, bloggers, sysadmins, the security team, the Code of Conduct committee, the funding group… the list goes on.</p><p>Curious, I asked if folks who tirelessly answer questions on PostgreSQL Slack and Telegram could also be recognized. Floor and Christoph Berg explained that measuring contributions on such fluid, fast-moving platforms is difficult. Still, Floor encouraged me to write about them and even to add my own blog to Planet PostgreSQL (Finally😄).</p><p>I’ve noticed a trend, more people are asking questions on Slack or Telegram than on the classic mailing lists. For those coming from other databases, pgsql-hackers can feel intimidating. By contrast, Slack and Telegram provide approachable, beginner-to-intermediate spaces where conversations flow naturally.</p><p>So, taking Floor’s suggestion to heart, here’s my first attempt at spotlighting a few unsung heroes.</p><p>This is only the beginning, many more names will follow.</p><h3>A. Postgres Slack</h3><h3>To join: <a href="https://pgtreats.info/slack-invite">https://pgtreats.info/slack-invite</a></h3><p>Slack is my go-to platform. Threads keep discussions tidy, and I spend most of my time there. These are some of the community members I see helping every day.</p><p><strong>Depesz</strong> — Spot the legendary orange hairs in your thread, and you know help has arrived. One of the most prolific and consistent experts on Postgres slack.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ta0b4eXEZo3uokt0-WWeuw.png" /></figure><p><strong>Jeremy Schneider</strong> — Jeremy answers both cloud and bare-metal questions, and you’ll also find him sharing wisdom in the Aurora channel.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iu4O-4t9UYOLy4eGmjit0A.png" /></figure><p><strong>Ants Aasma</strong> — A performance problem? Ants has an answer. Every. Single. Time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AakIzTARC6kamKpzXVIarw.png" /></figure><h3>B. Postgres Telegram</h3><h3><a href="https://t.me/postgreschat">https://t.me/postgreschat</a></h3><p>I haven’t spent as much time here yet, but one name already shines through.</p><p><strong>Stefanie Janine Stölting</strong> — Admin of the group, and always ready with sharp, technical answers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/990/1*qoeKng8tm5hgoIfY1_2fIA.png" /></figure><p>This list is just the start. PostgreSQL thrives because of the people who show up, share knowledge, and support others. I’ll be adding more names in the next round.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b9a7056447d2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Making of “Postgres Is”]]></title>
            <link>https://drunkdba.medium.com/the-making-of-postgres-is-5034c0dc4639?source=rss-3319eaf2a6c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/5034c0dc4639</guid>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[postgres]]></category>
            <dc:creator><![CDATA[Mayur (Do not drink & database)]]></dc:creator>
            <pubDate>Thu, 27 Feb 2025 01:00:35 GMT</pubDate>
            <atom:updated>2025-03-05T20:20:33.333Z</atom:updated>
            <cc:license>https://creativecommons.org/publicdomain/mark/1.0/</cc:license>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/621/0*eefd4zokK2Ts3wC4.png" /></figure><h3>Philosophy behind “PG Scorecard”:</h3><p>Postgres is an open-source database boasting an impressive 30-year legacy and a potent network effect. Its vibrant community has nurtured enduring credibility and resilience.</p><p>Naturally, emerging database startups want to harness this “Network effect” by claiming Postgres compatibility. Yet, without intrinsic checks on such assertions, this risks spiraling out of control and enticing malevolent actors.</p><p><a href="https://github.com/secp256k1-sha256/postgres-compatibility-index/blob/main/readme.md">The Postgres Compatibility Index</a> aims to create an open-source framework to validate these claims. My drive to introduce automated tests stems from the need to compare user experiences between the community edition and the more flamboyant, serverless, cloud-based, or specialized derivatives. Are users granted the same freedom, flexibility, and reliability they enjoy with the community version of PostgreSQL?<br>For example, Technically a cloud vendor can be compatible to Postgres however by not allowing freedom to use external programming language such as pl/perl,pl/python or C functions you would be denying users same experience as on self hosted community Postgresql.</p><p>For now, I’ve sidelined the obvious champions such as EDB Postgres, Azure FlexiServer, Amazon RDS, and Google CloudSQL. Time’s the culprit. By day, I’m a DBA; by weekend, a coder. They’ll join the fray when the clock permits.</p><p>There are lot of complex tests that I would like to have but not figured out yet how to incorporate them. If you wish to contribute tests or code, please submit a pull request.</p><p><strong>MIT Licenced code so feel free to contribute and improve it.</strong></p><p><strong>Contribut to scoring methodology code here</strong> ==&gt; <a href="https://github.com/secp256k1-sha256/postgres-compatibility-index/blob/main/postgres-compatibility-index/pci_autotest.py"><strong>pci_autotest.py</strong></a></p><p>Are you a vendor itching to showcase your creation?</p><p>Run autotest.py against your database. Send a pull request with the JSON output, logs, and screenshots. Step into the spotlight.</p><p><strong>Need your favorite db featured on site</strong> ==&gt; <a href="https://github.com/secp256k1-sha256/postgres-compatibility-index/tree/main/postgres-compatibility-index/outputs"><strong>Outputs</strong></a></p><p>Update: In response to a trademark notice from the PostgreSQL Community Association of Canada, domain has been changed from “Postgres.Is” to <a href="http://pgscorecard.com">pgscorecard.com</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5034c0dc4639" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>