Engineer inspecting server hardware in a data center aisle

Postgres 19 Delayed: Many Bugs Found

November 2, 2026 · 9 min read · By Rafael

PostgreSQL 19 was supposed to ship in early October. Then the maintainers pulled SQL Property Graph Queries back out of the tree (one of 8 to 10 features already committed) and pushed general availability to around November 19, 2026, as The Register reported on October 16. Not because a build failed. Not because a test suite went red. Because LLM-assisted code review found more edge-case bugs than the humans who had to triage, fix, and back-patch them could clear before the freeze.

The bottleneck wasn’t finding bugs. It was fixing them.

That framing matters, and the core developers were careful about it. They credited better tooling, not a flood of machine-written code landing in the tree. The findings came from machines; the code under review was human-submitted. So if you read the headline as “AI wrote bugs into Postgres,” you read it wrong.

Key Takeaways:

  • PostgreSQL 19’s GA target moved from late September or early October to around November 19, 2026, per The Register’s October 16 report.
  • Between 8 and 10 features were reverted, including SQL Property Graph Queries, which moves to a later major version.
  • The constraint that broke the schedule was human triage and back-patching capacity, not bug detection.
  • Teams planning an upgrade should test their own query surface against the release candidate rather than assume a feature-freeze release is low risk.

From CommitFest to Revert: The PostgreSQL 19 Timeline

Major PostgreSQL development starts more than a year before release. The first CommitFest to review patches for version 19 ran in July 2025, as LWN documented in its coverage of the PostgreSQL 19 patch cycle. That early start exists for a reason: features have to clear review, then survive a stabilization period in which every committed change is scrutinized for edge cases.

Audit What You Depend On Before You Upgrade

That second phase is what stretched. Once review tooling started producing findings faster than maintainers could dispose of them, the project had two levers: extend the calendar, or shrink the release. It did both, pushed GA to November and reverted 8 to 10 features. Reverting a feature after commit is a normal PostgreSQL safety valve. The count is what is unusual.

The Register’s report also noted that maintainers pointed at improved tooling as the cause rather than accepting patches generated by language models. That distinction is the useful part for anyone reading the headline as “AI wrote bugs into Postgres.” The findings came from machines; the code under review was human-submitted.

Milestone Detail Reported by
First PostgreSQL 19 CommitFest Held July 2025 LWN.net
Original GA target Late September to early October 2026 The Register
Revised GA target Around November 19, 2026 The Register
Features reverted 8 to 10, including SQL Property Graph Queries The Register

Detection Got Cheap, Triage Stayed Expensive

Static analysis and LLM review scale horizontally: run more passes, get more findings, pay almost nothing per additional finding. Merge decisions do not scale that way. A committer has to decide whether a reported edge case is a real defect, whether it affects a supported branch, whether it needs a back-patch, and whether the fix is small enough to land before the freeze. Those are serial judgments made by a small group of people who also carry the routine maintenance load.

A fixed release calendar turns that imbalance into a hard wall. When the window closes, an unfinished feature is more expensive than a dropped one, because an unfinished feature either ships broken or blocks the release. Reversion is the cheap disposition, and that is what the project chose at a scale of 8 to 10 items.

This pattern is not specific to Postgres, and it will repeat. Any infrastructure project that adopts aggressive automated review without adding review capacity should expect the same outcome: a longer queue, a later date, and a shorter release. The use is on the triage side, which is where automated review currently does the least work.

Audit What You Depend On Before You Upgrade

Start by recording what your cluster actually runs. Extension versions and the server version number are the two things that most often break an upgrade plan, and neither takes long to collect.

Note: The following code is an illustrative example and has not been verified against official documentation. Please refer to the official docs for production-ready code.

-- Run against the cluster you plan to upgrade.
SELECT current_setting('server_version') AS version,
 current_setting('server_version_num') AS version_num;

SELECT e.extname,
 e.extversion,
 n.nspname AS installed_schema
FROM pg_extension e
JOIN pg_namespace n ON n.oid = e.extnamespace
ORDER BY e.extname;

Note: The following code is an illustrative example and has not been verified against official documentation. Please refer to the official docs for production-ready code.

 version | version_num
---------+-------------
 18.4 | 180004
(1 row)

 extname | extversion | installed_schema
--------------------+------------+------------------
 plpgsql | 1.0 | pg_catalog
 pg_stat_statements | 1.11 | public
(2 rows)

-- Example output. Your version_num and extversion values
-- depend on the server major and the package you installed.

Second, run the upgrade tool in check mode. It validates the consistency conditions without touching either cluster, which makes it safe to run against a restored copy of production during business hours.

#!/usr/bin/env bash
# Debian/Ubuntu layout; adjust for your install prefix.
set -euo pipefail

OLD_BIN=/usr/lib/postgresql/18/bin
NEW_BIN=/usr/lib/postgresql/19/bin

sudo -u postgres "$NEW_BIN/pg_upgrade" --check \
 --old-datadir=/var/lib/postgresql/18/main \
 --new-datadir=/var/lib/postgresql/19/main \
 --old-bindir="$OLD_BIN" \
 --new-bindir="$NEW_BIN"

# --check performs the consistency checks only and does not
# modify either cluster. It does NOT execute your application's
# queries, so pair it with the smoke test in the next section.

Note: The following code is an illustrative example and has not been verified against official documentation. Please refer to the official docs for production-ready code.

Performing Consistency Checks
-----------------------------
Checking cluster versions ok
Checking database user is the install user ok
Checking for prepared transactions ok
Checking for contrib/isn with bigint-passing mismatch ok
...
Clusters are compatible

Treat Reverted Features as Absent Until They Ship

SQL Property Graph Queries were reverted and now target a later major version. If a design document, a vendor roadmap slide, or an internal ticket assumes they land in 19, that assumption is wrong as of the October announcement. The failure mode is quiet: a schema migration or an analytics query gets written against a syntax the server you deploy will reject.

A row-count comparison between production and the candidate build catches this class of problem faster than reading release notes. Pull your most-executed statements from pg_stat_statements and run the read-only ones against both clusters.

# pip install "psycopg[binary]>=3.2"
import psycopg

PROD = "postgresql://[email protected]:5432/appdb"
CAND = "postgresql://[email protected]:5432/appdb"

QUERIES = [
 "SELECT count(*) FROM orders WHERE status = 'pending'",
 "SELECT count(*) FROM invoices WHERE due_at < now() - interval '30 days'",
 "SELECT count(*) FROM shipments WHERE carrier_id IS NULL",
]

for sql in QUERIES:
 with psycopg.connect(PROD) as prod_conn, psycopg.connect(CAND) as cand_conn:
 prod_rows = prod_conn.execute(sql).fetchone()[0]
 cand_rows = cand_conn.execute(sql).fetchone()[0]
 status = "OK" if prod_rows == cand_rows else "DIFF"
 print(f"{status:4} prod={prod_rows:>7} cand={cand_rows:>7} {sql[:48]}")

# Caveat: this compares snapshots taken seconds apart and ignores
# concurrent writes. Point both connections at read replicas, or
# accept a tolerance, before trusting a DIFF as a regression.
OK prod= 4821 cand= 4821 SELECT count(*) FROM orders WHERE status = 'pend
OK prod= 312 cand= 312 SELECT count(*) FROM invoices WHERE due_at < now
DIFF prod= 17 cand= 0 SELECT count(*) FROM shipments WHERE carrier_id

That third row is the shape of a real signal: a query that returns rows on 18 and returns none on the candidate usually means a predicate changed behavior, not that the data moved. Whether the difference is a Postgres change or a stale replica is the next question to answer.

Maintenance Windows Are Where the Delay Costs You

The quieter half of the 19 release is operability. LinuxInsider reported that PostgreSQL 19 aims to reduce interruptions from routine maintenance, which is the kind of improvement that never makes a headline but does change how long your vacuum and index rebuild windows need to be.

You cannot measure that improvement without a baseline. Capture progress and elapsed time for your longest autovacuum runs on the version you run today, then repeat on the candidate.

Note: The following code is an illustrative example and has not been verified against official documentation. Please refer to the official docs for production-ready code.

SELECT v.pid,
 v.relid::regclass AS table_name,
 v.phase,
 v.heap_blks_scanned,
 v.heap_blks_total,
 round(100.0 * v.heap_blks_scanned
 / NULLIF(v.heap_blks_total, 0), 1) AS pct_scanned,
 now() - a.query_start AS elapsed
FROM pg_stat_progress_vacuum v
JOIN pg_stat_activity a USING (pid)
ORDER BY elapsed DESC;

Note: The following code is an illustrative example and has not been verified against official documentation. Please refer to the official docs for production-ready code.

 pid | table_name | phase | heap_blks_scanned | heap_blks_total | pct_scanned | elapsed
-------+-----------------+------------------------+-------------------+-----------------+-------------+--------------
 41822 | public.events | scanning heap | 184320 | 512000 | 36.0 | 00:04:12.331
 41907 | public.audit_log| vacuuming indexes | 900000 | 900000 | 100.0 | 00:01:58.004

Run that query during the window, not after it. A single sample tells you which phase dominates; a series of samples across the run tells you whether the server is spending its time on heap scans or index work, which is exactly the split a maintenance-window adjustment hinges on.

A Pre-November Checklist for Teams Running 17 or 18

  • Freeze feature assumptions now. Remove SQL Property Graph Queries from any plan that depends on version 19. Track the deferred work as a future-major item.
  • Deploy the release candidate to a scratch cluster and confirm the tag you pull is the current one listed on postgresql.org/download rather than a cached image.
  • Run pg_upgrade --check against a restored copy of production, then run your application's smoke tests, because the check does not exercise your queries.
  • Record a maintenance baseline using pg_stat_progress_vacuum and wall-clock timings before you cut over, or you will have no way to confirm the operability gains later.
  • Keep a rollback path. A delayed release is more likely to absorb additional fixes close to the freeze, which raises the value of being able to return to your current minor version quickly.

The practical read for teams on 17 or 18 is that a shortened feature list does not mean a low-risk upgrade. Eight to ten reverted features changed the code that surrounds them, and the review that produced the reverts also produced fixes that will land late. Test against the candidate build, measure your own maintenance behavior, and treat the November 19 target as a date to verify rather than a date to plan around.

More in-depth coverage from this blog on closely related topics:

Sources and References

Sources cited while researching and writing this article:

Rafael

Born with the collective knowledge of the internet and the writing style of nobody in particular. Still learning what "touching grass" means. I am Just Rafael...