Latest Blog Posts

All Your GUCs in a Row: log_rotation_age, log_rotation_size, and log_truncate_on_rotation
Posted by Christophe Pettus in pgExperts on 2026-08-29 at 01:00
PostgreSQL's log rotation has three parameters that only make sense together: one rotates at clock boundaries, one at file sizes, and one decides whether to…

How Fast Should You Patch Production PostgreSQL?
Posted by Umair Shahid in Stormatics on 2026-08-28 at 11:41

On August 13, 2026, PostgreSQL shipped its biggest security release ever: 28 CVEs closed in a single day. The old record was 11.

The community has done its job, a phenomenal one at that! If you run PostgreSQL for critical workloads in production, the number you should focus on is how long it takes to get a fix from “released” to “running in production.” Call it your patch latency. That’s what this post is about.

The Count Went Up Because the Attention Went Up

Here is the trend in plain numbers. Postgres fixed 7 CVEs in all of 2025; 44 in 2026 so far, and 28 of those landed in one release.

Postgres CVEs fixed in 2026 (Jan - Aug)

The panic reading is that the code got worse. The real reading is that more people are looking, and better tools are doing the looking. Some of these bugs were reported by AI security teams, including OpenAI’s. And the bugs they’re finding – buffer overflows, integer wraparounds, type confusion – are classic memory-safety bugs. That’s exactly what automated fuzzing is good at catching, and AI is making fuzzing a lot better.

Browsers and operating system kernels got this level of attention years ago. Postgres is getting it now. Every bug that’s found and fixed is one an attacker can’t use on you later.

The gap between a fix being published and that fix being live on your servers is what you need to focus on.

Why the Gap Is the Risk

A CVE is a public document. It names the bug, lists the affected versions, and often gives away enough detail to build an exploit. One of the August bugs, a remote code execution flaw in to_char(), already has a working proof of concept posted publicly. A national cybersecurity center put out a “patch immediately” advisory for it.

The same AI that helps defenders find these bugs helps attackers turn them into working exploits. The time between “CVE published” and “someone is scanning your servers with it” keeps getting shorter.

We saw how this plays out in early 2025. CVE-2025-1094, a

[...]

From the Trenches: My Path Through Postgres
Posted by Shaun Thomas in pgEdge on 2026-08-28 at 11:26

Hello fellow Postgres Enjoyer. I'd like to do something a little different this week and talk a bit about how I got ensnared by the Postgres ecosystem, and what I've done with it over the years. Maybe you have something better to do with your Friday than listen to some old guy reminiscing about Postgres, but I promise you it'll be worth the read.There's a reason I've been a dedicated Postgres zealot for over 20 years now, and it definitely isn't because of the name.

Postgres Sucks!

One of the blessings and curses of the internet is that anything you say within its confines lives in perpetuity. Or perhaps in my case it's better to describe it as infamy. Now, before I start incriminating myself, I actually contributed to Postgres in a tiny way as early as 2002 with Postgres 7.3 when I submitted a  utility.The actual story behind that utility is something else entirely. It all actually started in this thread where I adamantly defended Oracle (lol) and then moaned constantly about how much  sucks. Here's a fun excerpt:Yes, I've always been sort of obnoxious. Yes, I've mellowed out immensely over the years. And yes, it's hard not to giggle looking back, given that all of this started over 2GB of bloat. But thanks to that thread, I started this one regarding .Essentially what happened is that there was a bug in  way back in the 7.x days that prevented it from cleaning up indexes. So even though a table may have been reduced from 2GB to 100MB through a , the index would continue to grow. To get around this, I took the  script, renamed it to , re-tooled it a bit to rebuild indexes, and ran it on occasion to reindex every table in the database. And by "on occasion", I mean:Yes, this particular system was vastly improved by reindexing every table in the database every two hours. I can't stress enough just how bad Postgres was back then. Up until 7.1, every  required an exclusive table lock, for example. Can you imagine? Not , just regular . An exclusive lock. Ridiculous.Eventually I pushed to remove Postgres from ou[...]

All Your GUCs in a Row: log_directory, log_filename, and log_file_mode
Posted by Christophe Pettus in pgExperts on 2026-08-28 at 01:00
All three of these do nothing unless logging_collector is on; they describe the files the collector writes. Where, what they’re called, and who can read them. The defaults for all three are fine for a laptop and wrong for a server, and the reasons are more mechanical than the documentation lets o…

The Dump That Breaks Its Own Restore
Posted by Mikhail Shytsko on 2026-08-28 at 00:00

A pg_dump data-only restore into a fresh, empty copy of the schema is the safest-sounding restore in PostgreSQL, since there is not a single row in the target for the incoming data to collide with. It stops anyway, on a table sitting plainly in \dt.

psql:/tmp/data.sql:56: ERROR:  relation "book_audit" does not exist
LINE 1: INSERT INTO book_audit(book_id, note) VALUES (NEW.id, 'inser...

book_audit exists. The statement that cannot see it never appears in the dump, because it lives inside an AFTER INSERT trigger on books, and the reason a bare table name stops resolving is one line the dump wrote at the top of itself before any data moved.

SELECT pg_catalog.set_config('search_path', '', false);

That pins search_path to the empty string for the rest of the session, and every unqualified table reference inside a trigger body goes down with it. The dump breaks its own restore.

A pg_dump data-only restore failing on the pinned search_path preamble, where the trigger on books cannot resolve the unqualified name book_audit and the whole COPY rolls back

Folklore warns about a different failure here, the one where the trigger fires successfully during the books load and collides with the audit rows the dump is putting back. That failure is real. You meet it second, and only in a session whose triggers can still resolve their own tables.

Reach for one of three levers at this point. --disable-triggers bakes trigger suspension into the file, one transaction with SET CONSTRAINTS ALL DEFERRED fixes load order, and session_replication_role = replica switches enforcement off for the session. Two of the three bill you in a currency nobody mentions until the restore is half done.

Key Takeaways

  • The dump's own preamble pins search_path to the empty string, so a trigger function that names its own tables without a schema breaks the restore with relation ... does not exist, even into a target holding zero rows.
  • A trigger error rolls back the entire COPY it interrupted, leaving that table at zero rows while every other table in the file loads normally around it.
  • The trailing setval calls fire whether or not the matching table loaded, so a sequence can sit at 5 abov
[...]

How to Calculate Fillfactor for Your Tables
Posted by semab tariq in Stormatics on 2026-08-27 at 08:51

A small setting most teams either ignore or guess at. Here’s the quick version, then the full breakdown.

Key Takeaways

  • Fillfactor sets how full Postgres packs each table page on write, leaving the rest as free space for future updates to land in-place (HOT updates), which cuts WAL and I/O.
  • Starting formula: 100 − (average row size ÷ 8,192 × 100). Get average row size from pg_relation_size() and n_live_tup, run after a recent ANALYZE.
  • For heavily updated tables, go lower than the starting number to leave more room for repeated in-place rewrites.
  • Don’t default every table to 70 or 80. Calculate from the table’s real row size and update pattern, then adjust based on observed HOT rate and bloat.

A few weeks back we were tuning a table that was getting hammered with updates all day long. Query performance kept creeping up, WAL volume was higher than it should have been, and when we dug into it, one small setting turned out to be part of the problem: fillfactor. Most people either leave it at the default or set it to some round number they saw in a blog post once, without actually working out what the table needs. So we worked out how to calculate it properly. Here’s the process.

What is Fillfactor?

Fillfactor controls how much space Postgres fills up on each table or index page when data is first written. The unfilled portion is left as free space on purpose, for later updates.

That free space matters because it gives Postgres a place to put an updated row right next to the old one, on the same page, instead of having to move it somewhere else entirely.

Why Does it Matter?

For tables that get updated a lot, a well-tuned fillfactor can improve HOT updates and reduce I/O and WAL generation. HOT updates are cheaper because Postgres can update the row in place and avoid touching every index on the table.

But there is no single number that works for every table. It re

[...]

All Your GUCs in a Row: log_destination and logging_collector
Posted by Christophe Pettus in pgExperts on 2026-08-27 at 01:00
PostgreSQL's logging collector is a small pipe-based daemon that prevents message loss and garbling—but turning it on requires a restart, which you'll want on…

integration Lua to psql II
Posted by Pavel Stehule on 2026-08-26 at 20:34
Ten years ago, I attempted to enhance the \dt+ command to sort results by size. There were perhaps a hundred discussions, yet no consensus was reached on a new syntax. Eventually, I created the pspg tool, which allows results to be sorted by any column based on the vertical cursor position. Now, I have prepared a set of patches that integrates Lua into psql. Thanks to these modifications, anyone can write their own \dt command with the desired behavior:
\if :{?LUA_RELEASE}
\echo :LUA_RELEASE
\luacode
psql.registerCommand ( {
  name = "my.dt",
  help_syntax = "\\my.dt[+] [PATTERN] [-OPTION]",
  help_desc = "list tables possibly sorted by size",
  handler = function(ss, ab, cmd, verbose)
    local filter = "  AND n.nspname 
 'pg_catalog'\n" ..
        "  AND n.nspname !~ '^pg_toast'\n" ..
        "  AND n.nspname 
 'information_schema'\n" ..
        "  AND pg_catalog.pg_table_is_visible(c.oid)\n"

    local sort = "ORDER BY 1, 2";

    local opt = psql.scanSlashOption(ss, psql.OT_NORMAL, false)

    if opt == "-help" then
      print "my.dt[+] [PATTERN] [-OPTION]     list tables, possibly sorted"
      print ""
      print "Options:"
      print "  -asc-size         sorted by size in ascending order"
      print "  -desc-size        sorted by size in descending order"
      return psql.PSQL_CMD_SKIP_LINE;
    end

    if opt and string.sub(opt,1,1)  ~= "-" then
      local schema, tablename, dot
      if opt == "*" then
        filter = "  AND pg_catalog.pg_table_is_visible(c.oid)\n";
      else
        dot = string.find(opt, "%.")
        if dot then
          schema = string.sub(opt, 1, dot - 1)
          tablename = string.sub(opt, dot + 1)
        else
          tablename = opt;
        end
        if schema then
          if schema ~= "*" then
            filter = "  AND n.nspname = '" .. psql.connect():escape(schema) .. "'\n"
          else
            filter = ""
          end
        else
          filter = "  AND pg_catalog.pg_table_is_visible(c.oid)\n"
        end

        if tablename then
          if 
[...]

PostgreSQL 18: 23x Faster Inserts With UUID V7
Posted by Andrew Atkinson on 2026-08-26 at 11:50
📌 Overview

We recently switched to version 7 (v7) uuid primary keys and saw significantly faster inserts for some tables.

The databases were running Postgres 18.4 and mostly used v1 with some v4 uuid values for primary keys.

Changing the column default involved running a single alter table command, but did require an exclusive lock on the table, blocking everything including selects.

To solve that, we used a short lock timeout and lots of retries.

The biggest speedup was 23x faster average execution time for a multi-row insert query called 12000 times per minute on a table with billions of rows.

History and trade-offs with UUIDs

The system uses UUID primary keys throughout. I typically recommend starting with bigint and sequences over UUID v4 primary keys, although here uuid v1 was used. Insert performance is not as bad for v1 compared with v4.

Still though, v7 brings better performance than both for inserts and can also result in smaller indexes with fewer page splits meaning less CPU and IO.

What drives bad performance for v4 and to a lesser extent v1? Let’s do a quick refresher. As new table rows are inserted and a primary key is defined, primary key values are maintained in sorted order in a b-tree index. Just like table rows, index entries in Postgres are stored in fixed size 8kb pages.

Postgres needs to know in which page to place the new index entry. For sorted order, the first bytes of new uuid values are compared.

For v4 given new values are very random and not monotonically increasing (they lack “monotonicity”), values can be earlier or later, meaning they’re unlikely to be placed into the same recently accessed page. This is bad for caching!

When new values are monotonically increasing, the recently accessed page is “hot” in the Postgres buffer cache (in memory copy of the on-disk page).

When Postgres is not able to use the hot index page for the newly inserted value, that page could be outside the buffer cache, not in

[...]

pgwatch v6: Prometheus becomes a source, not just a sink
Posted by Pavlo Golub in Cybertec on 2026-08-26 at 07:43

We're excited to unveil pgwatch v6.0.0-beta — the biggest release since v5. It's a big one, so I'm splitting the tour into two posts. This first post is about the headline feature: pgwatch can now scrape a Prometheus exporter directly, as a first-class source, side by side with your regular PostgreSQL sources.

Why a Prometheus source at all?

pgwatch's whole reason for existing is turning SQL queries against pg_stat_* views into stored measurements. That works great for anything Postgres itself can tell you. It falls apart the moment you need something Postgres can't see about itself, e.g. is this node the primary or a replica right now, according to the cluster manager? How far behind is replication as Patroni sees it, not as pg_stat_replication sees it? What's node_exporter saying about disk I/O on this box?

Until now the answer was "run something else next to pgwatch." Starting with v6.0.0-beta, pgwatch can just scrape that something else itself.

Adding a Prometheus source

A prometheus source looks almost exactly like a Postgres source, just with a URL instead of a DSN:

- name: patroni-prod-node1
  kind: prometheus
  conn_str: "http://patroni-node1:8008/metrics"
  preset_metrics: patroni
  is_enabled: true

Point conn_str at any endpoint that speaks the Prometheus text exposition format, pick a preset (or list custom_metrics families and intervals yourself), and pgwatch takes it from there. The built-in patroni preset alone covers twelve metric families — patroni_primary, patroni_replica, patroni_xlog_location, patroni_dcs_last_seen, patroni_pending_restart, and friends.

Basic Auth and TLS work exactly the way you'd expect from a URL:

conn_str: "https://user:secret@patroni-node1:8008/metrics?tlsskipverify=true"

tlsrootcert= and tlsskipverify=true are stripped before the request goes out, so they never leak to the exporter. And yes, the password is redacted from every log line, that means no secrets showing up in your journal because someone grepped for the source name.

[...]

All Your GUCs in a Row: log_connections, log_disconnections, and log_hostname
Posted by Christophe Pettus in pgExperts on 2026-08-26 at 01:00
PostgreSQL 18 adds granular connection logging.

Your Deployment Is a PostgreSQL Program
Posted by Alexey Evlampiev on 2026-08-26 at 00:00

Your Deployment Is a PostgreSQL Program#

Every migration tool is a program that executes your SQL. Invert the control flow and deployment policy becomes SQL the project owns.

By Alexey Evlampiev

Abstract. Every team that adopts a migration tool eventually goes looking for a flag. The release needs one thing the tool did not anticipate — a check that must run after the schema change but before the commit, a concurrent index built in the middle of an otherwise transactional deployment, an ordering rule that filenames cannot express — and the search begins: through the configuration reference, then the issue tracker, then the changelog of a version that has not shipped. The flag is the visible symptom of an invisible arrangement. A migration tool is a program that executes your SQL, which means every deployment semantic — what runs, in what order, inside which transaction, and whether the result is allowed to commit — belongs to the tool’s vocabulary. Anything outside that vocabulary is a feature request. This article describes the inversion: a tool that prepares one PostgreSQL session, materializes the project as relations inside it, and hands deployment policy to a SQL program the project owns. The tool keeps the execution mechanism; the project takes the policy. What used to require a tool feature becomes SQL the project owns. The scope is the database semantics — choosing and ordering the work, controlling its transaction boundaries, validating the result, and deciding whether it may commit — and not the cloud APIs, secret stores, and approval gates around them. The costs are specific too, and the last part names them.

MongoDB on PostgreSQL: DocumentDB with pglayers-azure
Posted by Ismaël Mejía on 2026-08-26 at 00:00

DocumentDB is a MongoDB-compatible document database built on PostgreSQL. It adds the BSON data type and a full CRUD API to Postgres, and ships a gateway that speaks the MongoDB wire protocol -- so existing MongoDB clients (mongosh, pymongo, the Node.js driver) can connect to a PostgreSQL server as if it were MongoDB. It's the same engine behind Azure DocumentDB.

DocumentDB is included in the pglayers-azure profile image, which mirrors the open-source extensions available in Azure Database for PostgreSQL. This post walks through running the image and talking to it from a MongoDB client end to end -- including a password gotcha that trips people up.

1. Start the container

Run the pglayers-azure image, exposing PostgreSQL on 5432 and the DocumentDB gateway on 10260 (the port the wire protocol listens on):

docker run -d --name pglayers-docdb \
  -e POSTGRES_PASSWORD=secret \
  -p 5432:5432 -p 10260:10260 \
  ghcr.io/pglayers/pglayers-azure:18

The profile image auto-configures everything at boot: it sets shared_preload_libraries (including pg_documentdb_gw_host, the gateway worker), appends the required GUCs, and auto-creates the documentdb extension on first init. Within a second or two the log shows TCP listener(s) bound to port 10260 and the gateway is ready. You don't need to run CREATE EXTENSION yourself.

Use PG 18 (isolated layout) or PG 17. DocumentDB is built only for 17 and 18 -- not 19 -- so don't use pglayers-azure:19 for this.

2. Create a MongoDB user

The gateway uses native SCRAM authentication, and its configuration blocks a set of role name prefixes (documentdb, citus, pg, internal_role). That means you can't reuse the postgres superuser -- you need a fresh role with a password.

The intuitive approach is DocumentDB's own documentdb_api.create_user() function, but on a stock image it fails:

ERROR:  password type is not a plain text
CONTEXT:  ... CREATE ROLE mongoadmin WITH LOGIN PASSWORD 'SCRAM-SHA-256$...'

The reason: the server runs with password_encrypti

[...]

New things for regular expressions in PostgreSQL (pg_tre and pg_re2)
Posted by Hubert 'depesz' Lubaczewski on 2026-08-25 at 18:41
Well, truth be told these are not all that new (couple of months), but I finally have gotten around to research it. So, let's see what's what. For starters I need some test data. Luckily, I have explain.depesz.com DB… Extracted all plans to side table, with this structure: =$ \d all_plans Table "public.all_plans" Column | … Continue reading "New things for regular expressions in PostgreSQL (pg_tre and pg_re2)"

How to optimize when you can’t do anything!
Posted by Henrietta Dombrovskaya on 2026-08-25 at 11:50

It’s hard to say anything new about query optimization. On the one hand, each new Postgres release includes multiple query planner improvements, and it feels like there is something for any problem that can possibly arise. On the other hand, the fundamental principles of optimization do not change: if your query is highly selective, meaning the result is a small percentage of the original data set, you need to build indexes that would support this particular search. If you are optimizing an analytical query, you are looking for the way to execute it in parallel and aggregate early.

There is only one “but” – it’s not like you can build an index on any table at any time. If that’s the case, what can you do?

Recently, I had to find a way to speed up a production query that suddenly started performing significantly slower than it used to. Yes, it reached the tipping point, but nevertheless, I had to find a way to make it fast again. Or at least not terribly slow.

Here is a problem I had to solve.

Given

  • Postgres version: 13.6
  • A monolithic (non-partitioned) table, size 750 GB, 16 billion rows
  • Several indexes, but none of them were super useful for this particular search

And there is a query I needed to optimize. Yes, it looks simple/obvious, but wait till I get to the details!


  
SELECT * FROM t
WHERE a=? AND b=? AND c=?
AND start_date <='2026-08-16' AND end_date >='2026-08-16'

Date could be any date; I used August 16 for illustration (and no, it’s not “yesterday” or “today”; the query could run for any date in the past). Basically, what you need is to find all records in which the interval from start_date to end_date includes that date in question (and satisfies other selection criteria). The query was running from several seconds to several minutes.

Yes, we know that we need: we need to build a daterange from start_date to end_date, and then build a GIST index on that range. All good, except we all know how long it takes to build any ind

[...]

Day to day PostgreSQL on Kubernetes: Resize, Scale, and Upgrade
Posted by Hans-Juergen Schoenig in Cybertec on 2026-08-25 at 06:16

Deploying a database is the easy day. The interesting days are the ones after: it needs more memory, more read capacity, or a version bump, and you would rather not take an outage for any of them. With the CYBERTEC PG Operator (CPO), all three are one-line changes to a manifest, and the operator rolls them out while the database stays up.

Every command and output below is from tutorials we ran end-to-end on a fresh minikube (Kubernetes 1.30, CPO 0.9.2).

Resize CPU/memory with an automatic switchover

Bump the resources:

kubectl -n cpo patch pg pg-cluster --type=merge -p '{
  "spec": { "resources": {
    "requests": { "cpu": "500m", "memory": "1Gi" },
    "limits":   { "cpu": "1",    "memory": "1Gi"   } } } }'

The operator doesn't restart everything at once. It rolls the change out the careful way and you can watch it happen:

NAME           READY   STATUS        ROLE
pg-cluster-0   1/1     Running       master
pg-cluster-1   1/1     Terminating   replica   # replica updated FIRST
pg-cluster-1   1/1     Running       (none)
pg-cluster-0   1/1     Terminating   (none)    # then a SWITCHOVER...
pg-cluster-1   1/1     Running       master    # ...pg-cluster-1 is now leader
pg-cluster-0   1/1     Running       replica   # old leader rejoins as replica

Replica first, then a switchover to the freshly-updated node, then the old leader, so there's no window where the cluster is fully down. We confirmed the new limits applied (" memory":"1Gi") and the cluster stayed healthy throughout.

Reconfiguring PostgreSQL itself is the same idea,  set a parameter and Patroni applies it cluster-wide:

kubectl -n cpo patch pg pg-cluster --type=merge \
  -p '{"spec":{"postgresql":{"parameters":{"max_connections":"200"}}}}'
# a few seconds later:
kubectl -n cpo exec pg-cluster-0 -- psql -U postgres -tAc 'show max_connections;'
# 200

Scale out (and back in) by changing a number

Need more read replicas? Change numberOfInstances:

kubectl -n cpo patch pg pg-cluster --type=merge -p {"spec":{"numb
[...]

Contributions for week 33
Posted by Cornelia Biacsics in postgres-contrib.org on 2026-08-25 at 05:55

On 19 August 2026, the Postgres Meetup for All - Group met online, organized by Elizabeth Christensen. James Nelson and Philip Johnston delivered a talk

Hyderabad PGDays 2026 took place from 20-21 August 2026

Organizers:

  • Ameen Abbas
  • Hari Kiran P
  • Rajesh Madiwale

Program Selection Committee:

  • Deepak Mahto
  • Gayathri Varadarajan
  • HariKrishna B
  • Jobin Augustine
  • Pavlo Golub

Code of Conduct Committee:

  • Shashidhar Dakuri
  • Rumi Abbas
  • Rushabh Lathia

Volunteers:

  • Bikash Chandra Rout
  • Deevena Ande
  • Faisal Ashraf
  • Keerthi Seetha
  • Kushwanth Kumar Ganta
  • Mandyam Lokesh
  • Nithin Kumar
  • Nashera Fatima
  • Y Pavan Sai Nishith
  • Pranav Salota
  • Sai Krishna Namburu
  • Sashikanta Pattanayak
  • Shameer Bhupathi
  • Syed Ayaan
  • Pabbathi Varshene
  • Vaishnavi Vadapalli
  • Venkata Krishna Bandlamudi

Speakers:

  • Ameen Abbas
  • Anuradha Chintha
  • Ashutosh Bapat
  • Aswini Kumar Tummala
  • Avi Vallarapu
  • Bikash Chandra Rout
  • Dinesh Salve
  • Hari Kiran
  • InduTeja Aligeto
  • Jobin Augustine
  • Kabilesh PR
  • Kevin Biju
  • Manan Gupta
  • Suman Michael
  • Pat Wright
  • Pavan Deolasee
  • Prafulla Ranadive
  • Pranav Salota
  • Purnima Kumari
  • Rahul Singh
  • Rajesh Madiwale
  • Raj Verma
  • Sai Krishna Namburu
  • Sashikanta Pattanayak
  • Shameer Bhupathi
  • Shashikant Shakya
  • Shreya Radhakrishna Aithal
  • Sivasankar Prasad
  • soqrabanu rumi
  • Sravan Velagandula
  • Subhani Shaik
  • Veeranjaneyulu Grandhi
  • Venkat Akhil
  • VIKAS GUPTA
  • Vinay Kumar Dumpa
  • Vishnu Das
  • Y V Ravi Kumar

Your PostgreSQL Platform Has Telemetry. Does It Have a Digital Twin?
Posted by Vibhor Kumar on 2026-08-25 at 04:09

Beyond observability: building the synchronized, simulatable model AI agents
need before they touch production

The digital twin you are building may depend on one you have not built yet.

Most digital-twin programs begin with a physical asset: a building, factory, vehicle, power grid, or machine. An insurer, for example, might maintain a digital representation of a commercial property using sensor readings, inspection results, maintenance records, weather conditions, occupancy patterns, and claims history. The purpose is not simply to display the building. It is to understand its present condition, forecast how its risk may
change, and test possible interventions before acting.

But the building twin rests on another system. Its data must be captured, validated, replicated, governed, stored, queried, and kept current. Schemas must evolve without breaking downstream models. Pipelines must surface missing events. Historical state must remain traceable. If the data platform beneath the twin drifts from its actual operating condition, the asset model inherits that distortion.

This raises a question that deserves more attention:

Should the data platform itself have a digital twin?

I am using the term deliberately. The National Institute of Standards and Technology
describes a digital twin as a computer model of a physical system and treats forecasting—through simulation, monitoring, optimization, or decision support—as foundational. A data platform is not a physical asset in the same sense as a turbine or building. The idea here is an architectural extension: apply the same discipline of synchronized state, relationships, history, and simulation to the platform that produces the digital representation.

For PostgreSQL, that extension is both practical and timely.

PostgreSQL already exposes unusually rich evidence about its internal state. What it does not provide automatically is the coherent, continuously updated, simulatable model that would turn that evidence into a platfo

[...]

All Your GUCs in a Row: log_checkpoints, log_autovacuum_min_duration, and log_temp_files
Posted by Christophe Pettus in pgExperts on 2026-08-25 at 01:00
And now, we put on our waders and venture into the swamp that is all of the PostgreSQL logging GUCs. PostgreSQL 8.3 is the release in which the server started doing its own housekeeping in earnest: autovacuum on by default, checkpoints spread out over the interval instead of dumped at the end of …

Read your writes: WAIT FOR in PostgreSQL 19
Posted by Gülçin Yıldırım Jelínek in ClickHouse on 2026-08-25 at 00:00

PostgreSQL 19 introduces a new SQL command, WAIT FOR, that lets a session block until WAL has reached a specific position. This gives us read-your-writes consistency on asynchronous replicas without paying the synchronous replication tax.

WAIT FOR LSN 'lsn' WITH ( option [, ...] ) ];

where option can be:

MODE 'mode' TIMEOUT 'timeout' NO_THROW

Scenario-Tree Testing in PostgreSQL: Every Authored Branch, Shared History, Before COMMIT
Posted by Alexey Evlampiev on 2026-08-25 at 00:00

Scenario-Tree Testing in PostgreSQL: Every Authored Branch, Shared History, Before COMMIT#

Express the branching scenarios of your business logic as a directory tree; walk it with savepoints so each branch inherits its history instead of rebuilding it; and let the walk decide whether your deployment commits.

By Alexey Evlampiev

Abstract. Database tests often repeat the same state-building work, because several scenarios share the same prefix: a device may be provisioned before testing its configuration paths; an order may be paid before testing shipment and refund; a workflow may be approved before testing its downstream outcomes. The running example throughout is an order lifecycle — chosen only because its branching states are easy to see, and standing in for whatever lifecycle your own database implements. To test both placed → paid → shipped and placed → paid → refunded, a conventional suite constructs placed → paid twice. This article develops the alternative: express the scenarios as a directory tree and walk it with PostgreSQL savepoints — execute the shared prefix once, test one branch, roll back to the branch point, and test its sibling from the same inherited state. Every scenario then runs against the accumulated state it actually depends on, without rebuilding that state and without seeing a sibling’s changes. A lifecycle’s reachable histories branch like a multiverse, far beyond what a practical suite can cover, so the tree is authored, not exhaustive: you choose the critical paths, and the walk proves each one from the exact parent state it depends on — proof here meaning execution plus declared-invariant checks, not formal verification. And because most PostgreSQL DDL is transactional, the whole walk can run inside a still-uncommitted deployment: apply the migration, run the tree, discard the test state, and commit only if every authored scenario passes.

PostgreSQL in Taipei: Connecting Taiwan to the Global PostgreSQL Community (COSCUP 2026)
Posted by cary huang in Highgo Software on 2026-08-24 at 21:58

Introduction

COSCUP 2026 was held on August 8–9 at National Taiwan University of Science and Technology (NTUST) in Taipei. As one of Taiwan’s largest annual open-source gatherings, COSCUP brings together developers, users, communities, and open-source advocates from Taiwan and around the world.

This year was particularly international. COSCUP was co-hosted alongside UbuCon Asia 2026 that feature more than 20 tracks and dozens of community booths. Among them, the PostgreSQL community had a much stronger international presence this year.

I was honored to represent the international PostgreSQL community at COSCUP this year, together with Bruce Momjian, Robert Treat, and Grant Zhou from HighGo, joined by Julien Rouhaud and Mr. Ku from the local community. Together, we brought PostgreSQL to Taipei through a series of talks and a dedicated PostgreSQL community booth, where we had the opportunity to meet and connect with Taiwan’s open-source community face-to-face.

It was also the first time visiting Taiwan for Bruce, Robert, and Grant. I was glad to see them enjoy the people, food, and atmosphere of Taiwan. As a bonus, we also got to experience a little bit of Taiwan’s summer tradition, a typhoon.

Overall, it was a great conference to be part of, filled with meaningful conversations, new connections, and plenty of PostgreSQL. In this post, I’d like to look back at COSCUP 2026 from my own perspective and share some of the highlights from our time in Taipei.

Long post ahead

The Welcome Party

The COSCUP experience actually started the evening before the conference with the Welcome Party at Hua Shan Ding Bistro in Taipei. It was a casual gathering that brought together people from many different open-source communities and tracks—including Ubuntu, Python, and many others—to have a drink, meet new people, and talk about all things open source before the busy conference weekend began.

There were not many PostgreSQL folks at

[...]

The Sixth Execution
Posted by Christophe Pettus in pgExperts on 2026-08-24 at 20:14
Prepared statements switch from custom plans to generic plans on the sixth execution, and that switch can make your queries mysteriously slow.

All Your GUCs in a Row: listen_addresses
Posted by Christophe Pettus in pgExperts on 2026-08-24 at 01:00
Listen_addresses decides which TCP sockets exist on your server—but it says nothing about who can use them.

PostGIS 3.7.0rc1
Posted by Regina Obe in PostGIS on 2026-08-24 at 00:00

The PostGIS Team is pleased to release PostGIS 3.7.0rc1! Best Served with PostgreSQL 19 Beta 3 , GEOS 3.15.0rc1 , postgis_tiger_geocoder 2025.2 , and address_standardizer.

This version requires PostgreSQL 14 - 19rc1, GEOS 3.10 or higher, and Proj 6.1+. To take advantage of all features, GEOS 3.15+ is needed. To take advantage of all SFCGAL features SFCGAL 2.3.0+ is needed.

This release contains fixes since 3.7.0beta2 release.

3.7.0rc1

This release is a release candidate of a major release, it includes bug fixes since PostGIS 3.6.4 and new features.

All Your GUCs in a Row: lo_compat_privileges
Posted by Christophe Pettus in pgExperts on 2026-08-23 at 01:00
lo_compat_privileges sets large-object security back to PostgreSQL 8.4, the last release in which a large object belonged to nobody in particular and any role that could connect to the database could read it, overwrite it, or delete it. PostgreSQL 9.0 gave large objects an owner and an ACL. This …

PostGIS Tiger Geocoder 2025.2
Posted by Regina Obe in PostGIS on 2026-08-23 at 00:00

The PostGIS development team is pleased to provide postgis_tiger_geocoder extension. This is the very second release since the break from the PostGIS core. This version requires PostgreSQL 16 and above and should work with any supported PostGIS version.

PostGIS 3.6 series is the last series to include postgis_tiger_geocoder. PostGIS 3.7 will be shipped without postgis_tiger_geocoder.

postgis_tiger_geocoder has its own dedicated repo at OSGeo Gitea postgis_tiger_geocoder under the PostGIS org.

The versioning model is versioned based on the year of the Census US Tiger dataset that is current at time of it’s release.

All Your GUCs in a Row: local_preload_libraries
Posted by Christophe Pettus in pgExperts on 2026-08-22 at 01:00
Load shared libraries per-session from a single curated directory, no superuser needed.

The Time Traveler's Primary Key
Posted by Shaun Thomas in pgEdge on 2026-08-21 at 19:03

Every table needs a way to tell its rows apart, and auto-incrementing surrogate keys have been the go-to solution since practically the dawn of time. They're simple. They're fast. And perhaps most importantly, they're correct. But distributed systems demand unique values cluster-wide, preferably without some kind of consensus model or key-server bottleneck. The Smart Money is always on algorithmic generation.So along came the UUID. The standard has been through several iterations since its debut, but for the cost of 128-bits, it virtually guarantees algorithmically unique values. Unfortunately, UUIDs also tend to treat B-Tree indexes like particularly durable piñatas.Why would something so convenient cause so much grief? Is there a way out? I'm glad you asked!

Everything, Everywhere, All at Once

The workhorse of the UUID world is version 4. Rather than relying partially on MAC addresses or namespaces, they're randomly generated. Postgres provides it for free through the gen_random_uuid() function.Let's call it a few times:Beautiful. Now consider where those values go when they become a primary key:By default, Postgres backs primary keys with a B-tree index. Such indexes maintain sorted order to enable predictable cache behavior and efficient lookups. When we insert an ordered BIGINT identity, every new value is larger than the last so it lands at the rightmost leaf page of the tree. That page is almost certainly already in memory because it's the same page touched by previous inserts. We fill it, it splits cleanly, we move on. The hot part of the index is a tiny sliver at the right edge.A random UUID does the opposite of that. Each generated value is equally likely to sort before the very first row or after the very last, so every insert dives into a different, unpredictable leaf page. The page we need is rarely the page we just touched, which means Postgres must retrieve it from filesystem cache or worse. An unaware developer might watch as their insert throughput sags with seemingly no explanation.That's[...]

All Your GUCs in a Row: lock_timeout
Posted by Christophe Pettus in pgExperts on 2026-08-21 at 01:00
Prevent your migration from starving behind long-running queries.

Top posters

Number of posts in the past two months

Top teams

Number of posts in the past two months

Feeds

Planet

  • Policy for being listed on Planet PostgreSQL.
  • Add your blog to Planet PostgreSQL.
  • List of all subscribed blogs.
  • Manage your registration.

Contact

Get in touch with the Planet PostgreSQL administrators at planet at postgresql.org.