Mafiree logo
  • About
  • Services
  • Blogs
  • Careers
  • Products
    • orbit logo Orbit
    • streamer logo Xstreami
  • Contact
Schedule a Call
Menu
  • About
  • Services
  • Blogs
  • Careers
  • Products
    • orbit logo Orbit
    • streamer logo Xstreami
  • Contact
  • Schedule a Call
Database
Database Managed Database Services
MySQL MySQL
MySQL Consulting
MySQL Migration Services
MySQL Optimization & Query Tuning
MySQL Database Administration
MySQL Backup & Recovery
MySQL Security & Maintenance
MySQL Cloud Services (AWS RDS, Aurora, Google Cloud SQL, Azure)
MySQL for Ecommerce
MySQL High Availability & Replication
MongoDB MongoDB
MongoDB Consulting
MongoDB Migration Services
MongoDB Optimization & Query Tuning
MongoDB Database Administration
MongoDB Backup & Recovery
MongoDB Security & Maintenance
MongoDB Cloud (Atlas)
MongoDB Solutions by Industry
MongoDB High Availability & Replication
PostgreSQL PostgreSQL
PostgreSQL Consulting
PostgreSQL Migration & Upgrades
Performance Tuning & Query Optimization
PostgreSQL Administration & Managed Services
High Availability, Clustering & Replication
PostgreSQL Backup, Recovery & Disaster Planning
PostgreSQL Security, Compliance & Auditing
PostgreSQL for Analytics & Data Warehousing
PostgreSQL on Cloud & Containers
PostgreSQL Extensions & Open-Source Integrations
PostgreSQL for Every Industry
SQL Server MSSQL
MSSQL Consulting
MSSQL Migration Services
MSSQL Optimization & Query Tuning Services
MSSQL Database Administration Services
MSSQL Backup & Recovery Services
MSSQL High Availability & Replication Services
MSSQL Security & Compliance Services
MSSQL Performance Monitoring & Health Checks
MSSQL Solutions by Industry
Aerospike Aerospike
Aerospike Consulting
Aerospike Migration Services
Aerospike Performance Optimization & Tuning
Aerospike Backup & Recovery
Aerospike High Availability
Aerospike Cloud & Hybrid Deployments
Aerospike for Real-Time Applications (AdTech, FinTech, Retail, IoT)
Clickhouse Clickhouse
ClickHouse Consulting
ClickHouse Migration Services
ClickHouse Optimization & Query Tuning
ClickHouse Database Administration
ClickHouse Backup & Recovery
ClickHouse Security & Maintenance
ClickHouse Cloud Services (ClickHouse Cloud, AWS, GCP, Azure)
ClickHouse Solutions by Industry
ClickHouse High Availability & Replication
TiDB TiDB
TiDB Consulting
TiDB Administration & Maintenance
TiDB Security and Privacy Maintenance
TiDB Performance & Query Optimization
TiDB Migration Services
TiDB Backup & Disaster Recovery
TiDB High Availability Solutions
TiDB Solutions by Industry
TiDB Cloud Services
ScyllaDB ScyllaDB
ScyllaDB Consulting
ScyllaDB Administration & Maintenance
ScyllaDB Security and Privacy Maintenance
ScyllaDB Performance & Query Optimization
ScyllaDB Migration Services
ScyllaDB Backup & Disaster Recovery
ScyllaDB High Availability Solutions
ScyllaDB Solutions by Industry
ScyllaDB Cloud Services
DevOps
DevOps DevOps Services
Version Control Version Control
Kubernetes Kubernetes
Infrastructure Infrastructure Management
Web Servers Web Servers
Networking
Networking Networking Services
Basic Basic
Advanced Advanced
MySQL MySQL
MongoDB MongoDB
PostgreSQL PostgreSQL
MSSQL MSSQL
Aerospike Aerospike
Clickhouse Clickhouse
TiDB TiDB
ScyllaDB ScyllaDB
Version Control Version Control
Kubernetes Kubernetes
Infrastructure Infrastructure Management
Web Servers Web Servers
Basic Basic
Advanced Advanced
MySQL Consulting
MySQL Migration Services
MySQL Optimization & Query Tuning
MySQL Database Administration
MySQL Backup & Recovery
MySQL Security & Maintenance
MySQL Cloud Services (AWS RDS, Aurora, Google Cloud SQL, Azure)
MySQL for Ecommerce
MySQL High Availability & Replication
MongoDB Consulting
MongoDB Migration Services
MongoDB Optimization & Query Tuning
MongoDB Database Administration
MongoDB Backup & Recovery
MongoDB Security & Maintenance
MongoDB Cloud (Atlas)
MongoDB Solutions by Industry
MongoDB High Availability & Replication
PostgreSQL Consulting
PostgreSQL Migration & Upgrades
Performance Tuning & Query Optimization
PostgreSQL Administration & Managed Services
High Availability, Clustering & Replication
PostgreSQL Backup, Recovery & Disaster Planning
PostgreSQL Security, Compliance & Auditing
PostgreSQL for Analytics & Data Warehousing
PostgreSQL on Cloud & Containers
PostgreSQL Extensions & Open-Source Integrations
PostgreSQL for Every Industry
MSSQL Consulting
MSSQL Migration Services
MSSQL Optimization & Query Tuning Services
MSSQL Database Administration Services
MSSQL Backup & Recovery Services
MSSQL High Availability & Replication Services
MSSQL Security & Compliance Services
MSSQL Performance Monitoring & Health Checks
MSSQL Solutions by Industry
Aerospike Consulting
Aerospike Migration Services
Aerospike Performance Optimization & Tuning
Aerospike Backup & Recovery
Aerospike High Availability
Aerospike Cloud & Hybrid Deployments
Aerospike for Real-Time Applications (AdTech, FinTech, Retail, IoT)
ClickHouse Consulting
ClickHouse Migration Services
ClickHouse Optimization & Query Tuning
ClickHouse Database Administration
ClickHouse Backup & Recovery
ClickHouse Security & Maintenance
ClickHouse Cloud Services (ClickHouse Cloud, AWS, GCP, Azure)
ClickHouse Solutions by Industry
ClickHouse High Availability & Replication
TiDB Consulting
TiDB Administration & Maintenance
TiDB Security and Privacy Maintenance
TiDB Performance & Query Optimization
TiDB Migration Services
TiDB Backup & Disaster Recovery
TiDB High Availability Solutions
TiDB Solutions by Industry
TiDB Cloud Services
ScyllaDB Consulting
ScyllaDB Administration & Maintenance
ScyllaDB Security and Privacy Maintenance
ScyllaDB Performance & Query Optimization
ScyllaDB Migration Services
ScyllaDB Backup & Disaster Recovery
ScyllaDB High Availability Solutions
ScyllaDB Solutions by Industry
ScyllaDB Cloud Services
  1. Home
  2. > Blogs
  3. > PostgreSQL
  4. > REPACK in PostgreSQL 19: Finally, Online Table Bloat Removal in Core

REPACK in PostgreSQL 19: Finally, Online Table Bloat Removal in Core

PostgreSQL 19 introduces REPACK, a native command that unifies the functionality of VACUUM FULL and CLUSTER while adding support for CONCURRENTLY to minimize downtime. Unlike traditional table rewrites that require long ACCESS EXCLUSIVE locks, REPACK can reclaim table bloat online by copying live data, rebuilding indexes, and applying concurrent changes before a final metadata swap. This article explores how REPACK works internally, compares it with VACUUM FULL and pg_repack, demonstrates a hands-on performance test, explains the new pg_stat_progress_repack monitoring view, and covers current limitations and best practices for production environments.

Jethish September 10, 2026

Subscribe for email updates

Summarize with AI: ChatGPT Google AI Perplexity Claude Grok

A native command replaces the old VACUUM FULL / CLUSTER trade-off — and its CONCURRENTLY mode means you no longer have to choose between reclaiming disk space and keeping the table online. PostgreSQL 19 REPACK is one of the most anticipated PostgreSQL 19 features for exactly this reason.

If you've spent any real time as a PostgreSQL DBA, you already know the drill: a table bloats up after a big delete or an update-heavy workload, autovacuum can't return the space to the OS, and you're left choosing between two bad options. Run VACUUM FULL and lock the table for the duration of the rewrite. Or reach for the third-party pg_repack extension and hope your version, your permissions, and your maintenance window all line up.

PostgreSQL 19 changes that calculus. It ships a native REPACK command that folds VACUUM FULL and CLUSTER into one code path, and — more importantly for anyone running production RDS or Azure PostgreSQL instances — adds a CONCURRENTLY option that avoids holding an exclusive lock for the bulk of the operation. This is one of the changes we've been most interested in testing against our own bloated production tables at Mafiree, so let's go through what it actually does, how it works under the hood, where it holds up in a hands-on test, and where the sharp edges still are.

PostgreSQL VACUUM FULL vs REPACK: Why the Old Tools Weren't Enough

Before REPACK, you had exactly two ways to physically rewrite a bloated table in core PostgreSQL:

  • VACUUM FULL — rewrites the table into a new file, discarding dead tuples, and returns the freed space to the operating system.
  • CLUSTER — does the same rewrite, but also physically reorders the rows to match a chosen index, which helps range-scan performance.

Both commands share the same fundamental problem: they take an ACCESS EXCLUSIVE lock for the entire rewrite. On a multi-hundred-GB table, that's not a five-minute maintenance blip — it's an outage window you have to schedule, get sign-off for, and hope finishes on time. That's exactly why the community extension pg_repack became a standard part of most DBAs' toolkits: it works around the lock problem by building a shadow table, capturing changes via triggers, and swapping the two at the end.

What PostgreSQL REPACK Actually Is

Rather than keep VACUUM FULL and CLUSTER as two separate commands with separate code paths, PostgreSQL 19 introduces a single unified command. The core insight is that both operations were always doing the same underlying work — copying live tuples into a new file and swapping it in — and only differed in whether that copy preserved index order. PostgreSQL REPACK absorbs both behaviors as two modes of the same operation, which also gives the project a single place to add future functionality like concurrency.

Internally, REPACK copies the table's live rows (skipping dead tuples) into a new file, rebuilds every index into new files as well, and then swaps the old and new files in.

Syntax as of Beta 2

psql
REPACK [ ( option [, ...] ) ] [ table_and_columns [ USING INDEX [ index_name ] ] ] REPACK [ ( option [, ...] ) ] USING INDEX where option can be one of: VERBOSE [ boolean ] ANALYZE [ boolean ] CONCURRENTLY [ boolean ] and table_and_columns is: table_name [ ( column_name [, ...] ) ]
Quick tip: if you're SSH'd into a client box with no browser handy, psql's own help pulls the full syntax straight from the server — no need to leave the terminal: \h repack. For the latest details as the beta evolves, the official PostgreSQL 19 Release Notes and PostgreSQL 19 REPACK Documentation are the sources of record.

Everyday usage

psql — status_events
-- Equivalent to VACUUM FULL: reclaim space, no reordering REPACK code.status_events; -- Equivalent to CLUSTER: physically reorder by an index REPACK code.status_events USING INDEX status_events_created_at_idx; -- Get progress output while it runs REPACK (VERBOSE) code.status_events; -- Run ANALYZE immediately after the rewrite completes REPACK (ANALYZE) code.status_events; -- Repack only specific columns' worth of storage (e.g. after dropping a wide column) REPACK code.status_events (status, updated_at);

There's also a new pg_stat_progress_repack view for watching an in-flight rewrite — a big improvement over squinting at pg_stat_activity and guessing how much longer VACUUM FULL has left. More on exactly what it exposes further down.

The Part That Actually Matters: CONCURRENTLY

This is the headline feature, and it's the one I'd bet most of you reading this from a production DBA seat actually care about. Add CONCURRENTLY and REPACK no longer holds an exclusive lock for the whole rewrite:

psql
REPACK (CONCURRENTLY) code.status_events;

Here's what happens under the hood. Instead of taking ACCESS EXCLUSIVE up front, REPACK takes only a SHARE UPDATE EXCLUSIVE lock — the same lightweight lock autovacuum uses — and copies the table under an MVCC snapshot. It then opens a logical replication slot at that snapshot and hands off to a background worker, which uses logical decoding to capture every insert, update, and delete that happens on the original table while the copy is in progress. Those captured changes get stashed and replayed against the new copy right before the final swap. The exclusive lock only gets taken for that last, brief swap step — conceptually similar to how TRUNCATE behaves.

Exclusive-lock duration, to scale — rewriting the same bloated table
VACUUM FULL
CLUSTER
pg_repack
REPACK (CONCURRENTLY)

A Quick PostgreSQL Tutorial: Testing REPACK Yourself

Numbers land better than descriptions, so here's a quick reproducible test you can run against a scratch database to see the real PostgreSQL performance impact before trying this on anything that matters. Create a million-row table, hammer it with updates and deletes to simulate months of churn, then compare sizes before and after.

1. Create and populate a scratch table

psql — pgrepack_demo
-- Create a demo table CREATE TABLE pgrepack_demo ( id BIGSERIAL PRIMARY KEY, customer_name TEXT, remarks TEXT, order_amount NUMERIC(12,2), created_at TIMESTAMP ); -- Insert 1 million rows INSERT INTO pgrepack_demo (customer_name, remarks, order_amount, created_at) SELECT md5(random()::text), repeat(md5(random()::text), 5), round((random() * 10000)::numeric, 2), now() - (random() * interval '365 days') FROM generate_series(1, 1000000); -- Create secondary indexes CREATE INDEX idx_pgrepack_demo_name ON pgrepack_demo(customer_name); CREATE INDEX idx_pgrepack_demo_amount ON pgrepack_demo(order_amount); ANALYZE pgrepack_demo;

2. Simulate churn: repeated full-table updates plus a bulk delete

psql — pgrepack_demo
-- Update every row UPDATE pgrepack_demo SET remarks = repeat(md5(random()::text), 10), order_amount = round((random() * 10000)::numeric, 2); -- Update every row again UPDATE pgrepack_demo SET remarks = repeat(md5(random()::text), 10), order_amount = round((random() * 10000)::numeric, 2); -- Update half the rows UPDATE pgrepack_demo SET remarks = repeat(md5(random()::text), 10) WHERE id % 2 = 0; -- Update one-third of the rows UPDATE pgrepack_demo SET remarks = repeat(md5(random()::text), 10) WHERE id % 3 = 0; -- Delete 10% of the rows DELETE FROM pgrepack_demo WHERE id % 10 = 0; ANALYZE pgrepack_demo;

PostgreSQL Optimization: Measuring the REPACK Impact

3. Check the table size:

psql — pgrepack_demo
SELECT pg_size_pretty(pg_relation_size('pgrepack_demo')) AS table_size, pg_size_pretty(pg_indexes_size('pgrepack_demo')) AS indexes_size, pg_size_pretty(pg_total_relation_size('pgrepack_demo')) AS total_size; +------------+--------------+------------+ | table_size | indexes_size | total_size | +------------+--------------+------------+ | 1341 MB | 241 MB | 1582 MB | +------------+--------------+------------+ (1 row)

That's 1.58 GB of on-disk footprint for what's still, logically, a table of under a million live rows — table storage alone at 1341 MB, plus another 241 MB tied up in the two indexes. All of it is row versions and dead tuples that MVCC hasn't been able to reclaim on its own.

4. Repack it:

psql — pgrepack_demo
REPACK (VERBOSE, ANALYZE) pgrepack_demo; INFO: repacking "public.pgrepack_demo" in physical order INFO: "public.pgrepack_demo": found 266671 removable, 900000 nonremovable row versions in 171601 pages DETAIL: 0 dead row versions cannot be removed yet. CPU: user: 0.89 s, system: 0.83 s, elapsed: 27.47 s. INFO: analyzing "public.pgrepack_demo" INFO: "pgrepack_demo": scanned 30000 of 47369 pages, containing 570000 live rows and 0 dead rows; 30000 rows in sample, 900011 estimated total rows INFO: finished analyzing table "postgres.public.pgrepack_demo" avg read rate: 466.991 MB/s, avg write rate: 0.109 MB/s buffer usage: 100 hits, 30007 reads, 7 dirtied WAL usage: 30 records, 6 full page images, 40787 bytes, 28016 full page image bytes, 0 buffers full system usage: CPU: user: 0.17 s, system: 0.00 s, elapsed: 0.50 s REPACK

With VERBOSE on, the output tells you exactly what it found and what it cost: how many row versions were removable versus not, the page count it scanned, and CPU/elapsed time for the rewrite — followed by the ANALYZE pass if you asked for it, complete with buffer and WAL usage stats. It's the kind of detail you'd otherwise have had to piece together from pg_stat_activity and a stopwatch.

Size before and after REPACK
After churn — table1341 MB
After churn — indexes241 MB
After churn — total1582 MB
After REPACK — table370 MB
After REPACK — indexes89 MB
After REPACK — total459 MB

Watching It Live: pg_stat_progress_repack

Open a second session while the repack is running and query the new progress view directly:

psql
SELECT * FROM pg_stat_progress_repack;

Here's what it exposes:

  • Phase
  • Command
  • Relid
  • Repack_index_relid
  • Heap_tuples_scanned
  • Heap_tuples_inserted
  • Heap_tuples_updated
  • Heap_tuples_deleted
  • Heap_blks_total / scanned
  • Index_rebuild_count

The phase column is the one worth remembering: when a support engineer asks "is it stuck?", you can answer with "it's in the index-rebuild phase, N of M done" instead of guessing.

Limitations to Know Before You Reach for CONCURRENTLY

REPACK (CONCURRENTLY) cannot be used when:

  • The table is UNLOGGED
  • The table is partitioned (individual partitions can still be repacked one at a time)
  • The table has no primary key and no replica identity set
  • You're running it inside a transaction block

To repack a table at all — concurrent or not — the role running it needs the MAINTAIN privilege on that table. Worth checking upfront if you're running these as a lower-privileged migration or automation user rather than a superuser, and worth reinforcing before you run this in PostgreSQL production environments rather than a scratch database.

Note: to repack a table, one must have the MAINTAIN privilege on the table.

Should You Still Keep pg_repack Around?

Until your environments are actually on PostgreSQL 19 GA, yes — pg_repack remains the only online option on 18 and earlier. And even after upgrading, its per-partition batch tooling and its track record on very large, long-running jobs mean it's worth keeping in the toolbox for edge cases the new core command doesn't cover yet, particularly partitioned tables as a whole. But for the common case — a single logged table with a primary key that's bloated from update or delete churn — native REPACK (CONCURRENTLY) removes a dependency, removes a maintenance window, and gives you a progress view for free. That's a meaningful upgrade for anyone managing bloat across dozens of client schemas — and if you'd rather not work out the transition on your own, Mafiree's PostgreSQL consulting team can help you plan it.

Conclusion

Table bloat was never really a mystery — every PostgreSQL DBA knows exactly why it happens and exactly how to fix it. What was missing was a way to fix it that didn't force a trade-off between "reclaim the space" and "keep the table online." VACUUM FULL gave you the first at the cost of the second. pg_repack gave you both, but only if you could get an extension installed and version-matched on every environment you touched. REPACK in PostgreSQL 19 is the first time that trade-off disappears inside core itself, with a progress view thrown in so you're not flying blind while it runs — a meaningful upgrade for your database maintenance routine.

Planning your next PostgreSQL upgrade, or evaluating pg_repack and other bloat-maintenance strategies? Mafiree's PostgreSQL consulting and 24x7 DBA team can help you plan upgrades, evaluate maintenance options, and build a reliable database operations strategy.

Talk to Our PostgreSQL DBA Team

FAQ

REPACK is a new native PostgreSQL 19 command that rewrites bloated tables to reclaim disk space. It combines the functionality of VACUUM FULL and CLUSTER into a single command and introduces a CONCURRENTLY option to minimize table locking during the operation.
For many use cases, yes. PostgreSQL 19 includes REPACK as a built-in feature, eliminating the need for the third-party pg_repack extension for online table rewrites. However, pg_repack may still be useful for PostgreSQL 18 and earlier or for specific advanced scenarios.
Yes. Using REPACK (CONCURRENTLY) allows normal reads and writes during most of the operation. PostgreSQL captures concurrent changes using logical decoding and applies them before performing a short final table swap.
The user executing REPACK must have the MAINTAIN privilege on the target table. Superusers can run the command without additional permission changes.
While both commands reclaim table bloat, VACUUM FULL holds an ACCESS EXCLUSIVE lock for the entire rewrite, blocking reads and writes. REPACK offers a CONCURRENTLY mode that keeps the table available for most of the operation and only requires a brief exclusive lock during the final swap.

Author Bio

Jethish

Jethish is a PostgreSQL DBA at Mafiree with expertise in building scalable, reliable, and high-performance database infrastructures. He focuses on PostgreSQL architecture, replication strategies, performance tuning, and high availability for mission-critical systems. Through his technical writing, he shares clear, practical insights on database internals, replication choices, load balancing, and cross-database integrations that help engineers and DBAs tackle real-world data challenges.

Leave a Comment

Related Blogs

ProxySQL vs HAProxy for MySQL High Availability: Complete Comparison 2026

ProxySQL excels in MySQL-specific query routing, read/write splitting, and performance tuning. HAProxy offers robust load balancing across multiple protocols and is ideal for general-purpose proxying. Choose ProxySQL if your focus is on MySQL optimization; select HAProxy for broader application-level load balancing. Both tools support failover mechanisms but with different levels of database-specific integration.

  308 views
Tracking PostgreSQL Operations in Real Time: A Deep Dive into PostgreSQL Progress Reporting

This blog explores PostgreSQL’s progress reporting system views that provide real-time visibility into long-running operations like VACUUM, ANALYZE, CREATE INDEX, COPY, and base backups. It explains how DBAs can monitor execution phases, estimate completion, detect bottlenecks, and improve operational efficiency using these built-in views. Real-world examples and use cases demonstrate how progress tracking enhances performance tuning, automation, and maintenance planning.

  13087 views
Key Differences Between MySQL and PostgreSQL: Architecture, Performance & Use Cases

Understanding the difference between MySQL and PostgreSQL is critical when choosing a database for production workloads. While both are powerful open-source relational databases, they are built with fundamentally different philosophies. This comprehensive guide compares MySQL vs PostgreSQL across architecture, performance behavior under concurrent loads, replication strategies, and real-world use cases — backed by Mafiree's 17+ years of hands-on production experience across India, APAC, and the Middle East.

  1207 views
AWS Database Storage Optimization: How We Reclaimed 3.6 TB and Cut Costs in Half

A client came to us with a classic AWS database storage optimization problem: 15.2 TB allocated, less than a third actually in use — and a bill that kept growing regardless. Within one week, Mafiree had reclaimed 3.6 TB, validated a safe path to cut allocation nearly in half, and executed a zero-downtime migration. Here's the full story.

  885 views
PostgreSQL Connection Pooling: PgBouncer vs Odyssey – Performance & Configuration

PostgreSQL uses a process-per-connection model, which can limit scalability in high-traffic environments. Connection poolers help manage this challenge by reusing database connections efficiently. This blog compares PgBouncer and Odyssey, two popular PostgreSQL connection poolers, highlighting their architecture, performance characteristics, configuration differences, and ideal use cases. It helps organizations choose the right pooling solution based on workload scale, complexity, and operational requirements.

  7396 views

Subscribe for email updates

Get in touch with us

Highlights

More than 6000 Servers Monitored

Happy Clients

Certified DBAs

24 x 7 x 365 Support

PCI

Database Services

MySQL MongoDB PostgreSQL SQL Server Aerospike Clickhouse TiDB ScyllaDB

Quick Links

Careers Blog Contact Privacy Policy Disclaimer Policy

Contacts

Linkedin Mafiree Facebook Mafiree Twitter Mafiree

Nagercoil Office

Miru IT Park, Vallankumaranvillai,

Nagercoil, Tamilnadu - 629 002.

Bangalore Office

Unit 303, Vanguard Rise,

5th Main, Konena Agrahara,

Old Airport Road, Bangalore - 560 017.

Call: +91 6383016411

Email: sales@mafiree.com


Copyright © - All Rights Reserved - Mafiree