Radim @ boringSQL

Radim @ boringSQL

@boringsql.com

Straightforward PostgreSQL and SQL tips, tricks, and tools to get stuff done (no fluff).

64 followers40 following66 posts

Every row lock in #postgres is a write to the page. the locker's xid goes into the same field as a delete marker; flags say "not actually deleted". commit doesn't clean up, vacuum does, at 3am. pg_locks shows nothing. the row is not unlocked. boringsql.com/posts/row-lo...

boringsql.comWhere PostgreSQL stores row locksWhat SELECT FOR UPDATE, FOR SHARE, and a foreign key check actually write into the tuple header. t_xmax as a locker, the infomask bits that say so, MultiXactIds when two sessions lock the same row, an...

The part I didn't expect: four words at the end of a prompt. Adding "make it production-ready" bought more indexes! Were those indexes wrong? No - they would pass regular PR review. Except their impact on maintenance. Writes going up and vacuum taking longer every week. #postgresql #vibecoding

postgres 19 has WAIT FOR LSN, which means you can finally drop the "sleep and hope" workarounds for reading your own writes from replicas. I did all the digging around. Sample Go implementation, and the Rails workaround (Active Record thinks WAIT FOR is a write 🙃) boringsql.com/posts/read-y...

boringsql.comRead your own writes, off the primaryWAIT FOR in PostgreSQL 19 lets a reader block until the replica has replayed a specific LSN. Measured against sticky routing, sleeps, and synchronous commit, on a replica that misses the write 99% of ...

For impatient here's TL;DR: even with replica on the same machine, loopback, naive reads miss 99.2% of the time. not sometimes. not under load. always.

Everyone says store money as numeric, not float. Fewer people can say what breaks if you don’t. Your float SUM changes every run. Same rows, nothing touched, three different totals. That’s floats plus parallel aggregation. #postgres boringsql.com/posts/same-r...

boringsql.comSame rows, different SUMSame query, same rows, different SUM totals in PostgreSQL. The culprit is parallel aggregation over double precision floats, not a bug. Here is the mechanism and the fix.

It comes down to eight bytes on every tuple (xmin, xmax) and a three-number snapshot on every transaction. That's the whole mechanism. The consequences: bloat, VACUUM horizons,and isolation-level surprises.

Shared Buffers showed you the cache. The 8KB Page showed you what's inside. MVCC is what bind them together in concurrent access. The reason two sessions can read the same bytes and legitimately see different rows.

That imperative mindset leads to a pattern I see everywhere: one CTE filters rows, the next LEFT JOINs and aggregates with GROUP BY, the next filters the result. Reads like a pipeline. Performs terribly. The GROUP BY creates a wall the planner can't optimize past, even after in-lining.

Major progress for RegreSQL 2.0. It can now force pretty much any statistics. Yesterday I highlighted the current limitation of overriding statistics. Today I can show you planner being completely independent and choosing Bitmap Index Scan on table with 1 tuple.

What happens when you inject production statistics into a 10,000-row PostgreSQL test table? The query plan flips.

 PostgreSQL 18 makes this a one-liner. pg_dump --statistics-only
 Plain SQL, under 1MB, no data needed. Learn the mechanics in my latest blog post. 

boringsql.com/posts/portab...

boringsql.comProduction query plans without production dataPostgreSQL 18 makes optimizer statistics portable. Export them from production and inject them into test databases to get realistic query plans without the data.

Or what about cases where there are no statistics at all? Like materialized CTEs, temporary tables, or computed expressions in WHERE clauses. The planner falls back to hardcoded constants. 0.5% selectivity for equality. 33% for ranges. Good luck.

Step 1: "LGTM, this query is fine" Step 2: Deploy to production Step 3: Pray 🙏 Step 4: SRE - "This query is definitely NOT fine" Step 5: "But... it worked on MY database" 🔥 No ifs. No buts. On 12 March @postgresedi.bsky.social find out how to become an atheist when it comes to your SQL queries.

#postgresql season is officially on. I'll be on the ground at 🇪🇺 Nordic pgDay in Helsinki (March) 🇪🇺 German pgConf in Essen (April) 🇺🇸 PGDATA in Chicago (June) 🇬🇧 pgDay in London (September) I’m looking forward to all the chats and fun that's ahead.

There are weeks of full wins, and those where it's time to eat the humble cake. RegreSQL has been making great progress, but I've been fighting the fixture system more and more. After many conversations and endless evenings - it's time to admit: fixtures are a genuinely hard problem

View full thread

There are weeks of full wins, and those where it's time to eat the humble cake. RegreSQL has been making great progress, but I've been fighting the fixture system more and more. After many conversations and endless evenings - it's time to admit: fixtures are a genuinely hard problem

While other changes leading up to v2.0 are strong quality-of-life improvements, I've decided to pull generated fixture support out of RegreSQL entirely for now.

But it's not bad news. Agonising over fixtures forced me to look at the problem from another direction - how to quickly and securely use production data. Gave up on sampling, only to find a better solution: graph-traversal based data extraction with masking to strip customer data and PII.

Still recharging after this week. But it's that good kind of tired - the one you get when something actually matters. @praguepgdevday.bsky.social was my first time speaking at a bigger Postgres event. The RegreSQL session went well, but honestly? The conversations afterwards were the real thing

Radim @ boringSQL reposted

That’s a wrap at #P2D2! 🎉 Huge thanks to our amazing speakers, organizers, and attendees for making it unforgettable. PostgreSQL, people, and Prague—what a combo. We can’t wait to do it again next year. See you soon — brzy na viděnou! 🇨🇿💙

I got myself talking about the motivation behind RegreSQL on Postgres FM 🎙️ We nerded out a bit about why timing is a terrible metric, why buffers actually matter, statistics, performance – and why all of this is more relevant in the age of agentic coding. postgres.fm/episodes/reg...

postgres.fmPostgres FM | RegreSQLNik and Michael are joined by Radim Marek from boringSQL to talk about RegreSQL, a regression testing tool for SQL queries they forked and improved recently. Here are some links to things they ment...

Test if queries hit the right indexes at production scale – without needing to have production data. I'll show you exactly how during @praguepgdevday.bsky.social - Session "It works on my database - Regression testing of SQL queries" is last of the day, but I intend to make it worth staying for.

Where do you stand when it comes to #postgresql arrays? If you want to learn more, check out my latest deep-dive into the topic of arrays, exploring TOAST, GIN indexing gotchas, and when to just use a link table. boringsql.com/posts/good-b...

boringsql.comThe hidden cost of PostgreSQL arraysDeep dive into PostgreSQL arrays: why they're document storage in disguise, the TOAST performance trap, GIN vs B-tree indexing, the dangerous ANY() operator, and when junction tables beat arrays.

Writing about instant clones of PostgreSQL databases was not a clever move. Now I have to keep repeating to myself: I will NOT rewrite the whole control plane I will NOT scrap everything for #freebsd jails I will NOT rebuild everything with native ZFS snapshots