Skip to content
RiverCore
PostgreSQL 19 Ships Without SQL/PGQ: The Right Call
PostgreSQL 19REPACK CONCURRENTLYdatabase bloatPostgreSQL 19 REPACK CONCURRENTLY upgradeSQL PGQ graph query dropped PostgreSQL

PostgreSQL 19 Ships Without SQL/PGQ: The Right Call

17 Sep 20267 min readAlex Drover

Anyone who has run a Postgres primary north of a terabyte has stared at a bloat report and done the math on downtime. You either accept the disk growth, schedule a maintenance window nobody wants, or reach for a third-party tool and hope the replication topology cooperates. PostgreSQL 19 finally addresses that problem in core. It also quietly walks away from a headline feature that was not ready.

The Problem

The short version: as The Register reported, PostgreSQL developers pulled planned SQL/PGQ graph query support from version 19 over unresolved bugs, while a fourth beta lands September 24 and the final release date is still unconfirmed. Tom Lane, a longtime contributor, put it bluntly: "At this point I'd be willing to bet dinner that if we ship it in v19 there will be post-release bug discoveries that are unfixable until v20." Tom Kincaid, SVP of software engineering at EDB, confirmed the cut and said the community wanted to work through a few more things before shipping it.

SQL/PGQ matters because it became part of the SQL standard in 2023 and gives you a portable syntax for querying nodes and edges without leaving your relational engine. For teams that have been running a second graph database alongside Postgres for fraud graphs, KYC networks, affiliate hierarchies, or ad-tech attribution paths, the promise was significant: kill the sidecar, keep one source of truth, one backup strategy, one HA story. That promise now slides to version 20.

Meanwhile, the operational pain that never made the marketing slides is what this release actually fixes. VACUUM FULL rewrites a table to reclaim space occupied by obsolete row versions and return it to the operating system. It holds an exclusive table lock the entire time, which blocks every read and every write. On a busy OLTP table, that is not a maintenance operation. That is an outage with a change ticket attached.

Kincaid described the pattern I have watched play out on dozens of on-call rotations: "Middle-of-the-night calls have been a result of somebody doing a VACUUM FULL, and their customers can't get access to the data or someone wants to know why this is running." Every DBA reading this just nodded. Bloat accumulates, autovacuum falls behind on a high-churn table, someone eventually pulls the trigger on VACUUM FULL, and the pager goes off.

Options on the Table

Before version 19, teams had three realistic options for reclaiming space on a hot table, and none of them were pleasant.

Option one: schedule VACUUM FULL and eat the downtime. This works for internal reporting tables or anything you can freeze during a low-traffic window. For an iGaming operator running live wagering, or a fintech settling positions, there is no low-traffic window that lasts long enough. Production incidents I have seen usually start here, with someone assuming "it will be quick" on a 400GB table.

Option two: pg_repack as an extension. The community extension has done the concurrent-rewrite job for years, and it works. It also adds an operational surface: install and version the extension across every replica, monitor its temp tables, handle edge cases with replication slots, and hope your managed Postgres provider supports it. Some do, some don't. When a rewrite fails halfway, cleanup is manual and stressful.

Option three: partition the table and drop old partitions. This is the correct long-term answer for time-series and append-mostly workloads, and it should be the default for any table you expect to exceed a few hundred gigabytes. It does not help you today with an existing monster table that was never partitioned. Retrofitting partitioning on a live 2TB table is its own multi-week project.

PostgreSQL 19 adds a fourth option: the new REPACK command with a CONCURRENTLY modifier. Ordinary REPACK behaves like VACUUM FULL and holds the exclusive lock throughout. REPACK CONCURRENTLY lets other transactions access the table during most of the operation, and only needs a brief exclusive lock when swapping the rewritten table and index files into place. That is the shape of the operation teams have wanted in core for a decade.

My take: this quietly moves pg_repack's job into the base distribution, which is a bigger deal for managed-database customers than for self-hosted shops. If you run on a cloud vendor that has historically been slow to whitelist extensions, you now get concurrent repack whenever they cut over to 19. That is one fewer procurement conversation and one fewer failure mode in your runbook.

On the SQL/PGQ side, the alternatives are unchanged. Keep your existing graph store (Neo4j, Memgraph, AGE on Postgres) or model relationships with recursive CTEs and pay the query-planner tax. Nothing new to evaluate this cycle. Circle back at version 20. See the PostgreSQL docs when the release notes drop for the exact REPACK syntax.

What Engineering Teams Should Actually Do

First, do not rewrite roadmaps around SQL/PGQ. If your 2026 plan assumed you could retire a graph sidecar in Q4, that plan is dead. Push the migration to late 2027 at the earliest, assuming version 20 lands on the usual annual cadence and PGQ actually makes it in. Betting on unreleased features has burned teams I have worked with more times than I can count. Lane's "bet dinner" quote is polite engineer-speak for "this will haunt us." Respect it.

Second, audit your bloat now, before 19 ships. Identify the top ten tables by dead-tuple ratio and physical bloat. Rank them by business criticality and lock tolerance. Those are your REPACK CONCURRENTLY candidates for the first ninety days after upgrade. You want a prioritized list ready, not a fire drill.

Third, treat the upgrade to 19 as a two-phase rollout. Phase one: get onto 19 on read replicas and non-critical clusters, validate your backup and PITR tooling, confirm your monitoring picks up the new command. Phase two: run REPACK CONCURRENTLY against a staging clone of your worst-bloated table and measure the actual duration of the brief exclusive lock at the swap step. "Typically held only briefly" is the documentation phrase. Your specific hardware, replication lag, and lock-contention profile determine what "briefly" means for you.

Fourth, if you currently pay for a third-party repack tool or an extension-management layer that exists mostly to support pg_repack, put a review on the calendar for six months post-upgrade. You may be able to simplify the stack.

Gotchas and Edge Cases

The brief exclusive lock at the file-swap step is still a lock. On a table with sub-millisecond latency SLOs and heavy concurrent writers, even a short lock can queue enough transactions to trip your connection pool. Test with production-representative load, not a synthetic benchmark. The uncomfortable read: teams that treat "concurrent" as "zero impact" get surprised.

Replication behavior is the second thing to check. Any operation that rewrites a table generates significant WAL. If your replicas are geographically distant or bandwidth-constrained, plan for a lag spike during the repack and make sure your read traffic can tolerate it or fail over cleanly. Consider running your OpenTelemetry spans around the operation so you can correlate application latency with the rewrite window.

Third, watch disk headroom. A concurrent rewrite needs enough free space to hold a second copy of the table plus its indexes until the swap completes. On a 500GB table with three indexes, that is not a rounding error. Provision accordingly, or the operation will fail midway and leave you cleaning up.

Finally, do not skip beta testing because "it's just a repack command." The same release train that produced enough concern to yank SQL/PGQ is the one shipping REPACK. Run beta 4 against a copy of your workload after September 24. Better to find the edge case yourself than at 3am with customers waiting.

Key Takeaways

  • SQL/PGQ is out of 19. Any roadmap item that assumed native graph queries in Postgres this year needs to slip to version 20 or fall back to existing graph stores and recursive CTEs.
  • REPACK CONCURRENTLY is the sleeper feature. It brings pg_repack's job into core, with only a brief exclusive lock at the file swap instead of the full-duration lock VACUUM FULL demands.
  • Audit bloat before upgrading. Have a ranked list of the tables you want to rewrite first, so you can act inside the first quarter post-upgrade instead of scrambling.
  • Test the swap window under load. "Briefly" is workload-dependent. Measure the lock duration on staging with realistic concurrency before running against a customer-facing table.
  • Applaud the pull. Cutting a broken headline feature before release is the right call. It is the behavior you want from a database you trust with money and customer data.

Frequently Asked Questions

Q: Why was SQL/PGQ removed from PostgreSQL 19?

The PostgreSQL community pulled SQL/PGQ over unresolved bugs. Longtime contributor Tom Lane warned that shipping it in version 19 would likely produce post-release bug discoveries that could not be fixed until version 20, and EDB's Tom Kincaid confirmed the community wanted more time before releasing it.

Q: What is the difference between VACUUM FULL and REPACK CONCURRENTLY?

VACUUM FULL rewrites a table to reclaim space but holds an exclusive lock the entire time, blocking all reads and writes. REPACK CONCURRENTLY allows other transactions to access the table during most of the rewrite and only requires a brief exclusive lock when swapping the rewritten files into place.

Q: When will PostgreSQL 19 be released?

The final release date remains unconfirmed. A fourth beta is scheduled for September 24, 2026, and the general availability date will depend on how that beta cycle progresses and whether further blocking issues surface.

AD
Alex Drover
RiverCore Analyst · Dublin, Ireland
SHARE
// RELATED ARTICLES
HomeSolutionsWorkAboutContact
News06
Dublin, Ireland · EUGMT+1
LinkedIn
🇬🇧EN▾