PostgreSQL indexes help applications retrieve data efficiently without scanning entire tables. As a database grows and records change, indexes may need maintenance because of corruption, storage inefficiency, or changes in the way the application uses its data.
Rebuilding an index can help resolve certain index-related problems, but the operation needs careful planning on a production database. A standard index rebuild can block operations that modify the associated table. On a busy application, this can lead to waiting transactions, connection pool exhaustion, and increased response times.
PostgreSQL provides the REINDEX CONCURRENTLY option for rebuilding many indexes while allowing normal reads and writes to continue. This makes it possible to perform index maintenance without scheduling the same kind of write-blocking maintenance window required by a standard rebuild.
However, concurrent index rebuilding is not completely lock-free. It takes longer than a regular rebuild in many situations, performs additional work, and has restrictions that administrators must understand.
This article explains how to identify indexes that need rebuilding, choose the appropriate PostgreSQL command, monitor the operation, handle failures, and minimize the impact on production workloads.
Understand When a PostgreSQL Index Needs Rebuilding
An index does not automatically require rebuilding simply because it has existed for a long time.
PostgreSQL manages indexes as data changes. Inserts, updates, and deletes modify index structures, and routine maintenance helps control storage and table bloat. A rebuild should address an identified problem rather than serve as an automatic response to every performance issue.
Common reasons to investigate an index rebuild include:
- An index has been identified as corrupted.
- Index storage has become inefficient and a rebuild is justified.
- An index requires maintenance after an operational issue.
- An administrator needs to replace an index structure as part of a planned change.
- An index has accumulated substantial unnecessary storage that other maintenance methods cannot adequately address.
Before rebuilding an index, establish why it needs attention. If the underlying problem is inaccurate statistics, an unsuitable query plan, or an inefficient query, rebuilding the index may not help.
Check Index Definitions
Start by listing indexes on the affected table.
SELECT
schemaname,
tablename,
indexname,
indexdef
FROM pg_indexes
WHERE schemaname = 'public'
AND tablename = 'orders'
ORDER BY indexname;
Replace public and orders with the actual schema and table names.
This query shows the index definitions recorded in PostgreSQL's catalog views. It helps identify the indexes associated with the table before maintenance begins.
Review the definitions carefully. A table may have several indexes serving different queries, constraints, or uniqueness requirements.
Do not rebuild every index on a table without understanding the operational impact and the reason for the maintenance.
Check Index Sizes
Index size can help prioritize investigation when a database contains many large indexes.
SELECT
schemaname,
tablename,
indexname,
pg_size_pretty(
pg_relation_size(
quote_ident(schemaname) || '.' ||
quote_ident(indexname)
)
) AS index_size
FROM pg_indexes
WHERE schemaname = 'public'
AND tablename = 'orders'
ORDER BY pg_relation_size(
quote_ident(schemaname) || '.' ||
quote_ident(indexname)
) DESC;
This query is intended for ordinary identifiers in the specified schema. For unusual identifiers containing embedded quotes or other special characters, use properly quoted identifiers or a catalog-based query that resolves the index relation directly.
A large index is not necessarily bloated or inefficient. It may simply contain many entries because the table is large or the indexed values require substantial storage.
Use size information alongside query performance, index usage, and operational evidence rather than treating size alone as a reason to rebuild.
Understand the Difference Between REINDEX and REINDEX CONCURRENTLY
PostgreSQL offers several forms of the REINDEX command.
The correct choice depends on the index type, database version, object being rebuilt, and whether normal application operations must continue.
Standard REINDEX
A standard index rebuild is straightforward:
REINDEX INDEX public.idx_orders_customer_id;
PostgreSQL rebuilds the specified index.
The important limitation is locking. A standard rebuild takes a lock that prevents concurrent modifications to the associated table while the index is being rebuilt. Ordinary reads can generally continue for a regular table index, but writes that require the conflicting lock must wait.
On a small, lightly used table, this may be acceptable. On a busy production table, it can create a significant interruption.
REINDEX CONCURRENTLY
For many production workloads, the concurrent form is a better choice:
REINDEX INDEX CONCURRENTLY public.idx_orders_customer_id;
This allows ordinary INSERT, UPDATE, and DELETE operations to continue during most of the rebuild process.
PostgreSQL uses a more involved procedure to build and validate the replacement index while coordinating with concurrent transactions. Because it performs additional work, a concurrent rebuild can take longer and consume more resources than a standard rebuild.
The command also requires special handling because it cannot run inside an explicit transaction block.
For example, do not wrap the command in a transaction like this:
BEGIN;
REINDEX INDEX CONCURRENTLY public.idx_orders_customer_id;
COMMIT;
That approach fails because REINDEX CONCURRENTLY cannot execute inside a transaction block.
Run the command as a standalone statement using a client or migration tool that does not automatically wrap it in a transaction.
Comparison of the Two Approaches
Consideration | REINDEX | REINDEX CONCURRENTLY |
|---|
Normal table writes | Blocked by the required lock | Allowed during most of the operation |
Normal reads | Generally allowed for an ordinary table index | Allowed |
Execution time | Often shorter | Often longer |
Additional work | Lower | Higher because of concurrent build and validation |
Transaction block | Can be used in supported transactional contexts | Cannot run inside an explicit transaction block |
Operational complexity | Lower | Higher |
Best suited for | Maintenance windows or low-traffic situations | Tables that must remain available during maintenance |
Concurrent rebuilding reduces disruption, but it does not guarantee that the application will experience no additional latency.
Rebuild a Single Index Concurrently
Rebuilding one index at a time is often the simplest approach for a production database.
Suppose the application uses an index on the customer_id column of the orders table.
First, verify the exact index name:
SELECT
indexname,
indexdef
FROM pg_indexes
WHERE schemaname = 'public'
AND tablename = 'orders';
Then run:
REINDEX INDEX CONCURRENTLY public.idx_orders_customer_id;
Use the actual index name returned by the catalog query.
Before starting, confirm that the database has enough free storage for the rebuild. PostgreSQL must create a replacement index while the existing index remains available. Depending on the operation and index size, this can temporarily increase storage requirements.
Also review database load. A concurrent rebuild can compete with application queries for CPU, memory, and storage bandwidth.
For a busy production system, start with an index whose maintenance can be observed safely before scheduling several large rebuilds.
Rebuild All Indexes on a Table
When multiple indexes on a table need maintenance, PostgreSQL can rebuild them together.
REINDEX TABLE CONCURRENTLY public.orders;
This command rebuilds the indexes associated with the specified table concurrently.
However, rebuilding every index may be unnecessary if only one index has a demonstrated problem.
Before using the table-level command, review the table's indexes and estimate the combined workload. Large tables with many indexes can require considerable storage and I/O during maintenance.
Also consider the impact on write-heavy workloads. Every index rebuild consumes resources that might otherwise serve application transactions.
For predictable operations, rebuild only the indexes that need attention unless a broader maintenance operation is justified.
Rebuild Indexes Across a Database
PostgreSQL also supports rebuilding indexes across broader scopes, including a database.
A concurrent database-wide rebuild can be requested with:
REINDEX DATABASE CONCURRENTLY application_db;
This operation can be significantly more demanding than rebuilding one index. It may run for a long time, compete with production traffic, and require additional disk space.
Do not use it as a routine troubleshooting command without first reviewing the database's indexes, maintenance requirements, and resource capacity.
For most targeted incidents, rebuilding a specific index is easier to monitor and control.
The supported forms and restrictions of REINDEX vary by object type and PostgreSQL version. Check the documentation for the exact version you operate, especially when working with system catalogs, partitioned objects, or specialized index types.
Monitor the Rebuild While It Runs
A concurrent rebuild can take time, particularly for large indexes or busy databases.
Monitoring helps determine whether the operation is progressing normally and whether it is affecting application workloads.
Inspect Active Database Operations
Use pg_stat_activity to identify active sessions and long-running commands.
SELECT
pid,
usename,
application_name,
state,
wait_event_type,
wait_event,
query_start,
query
FROM pg_stat_activity
WHERE datname = current_database()
AND state <> 'idle'
ORDER BY query_start;
This query provides a snapshot of active sessions in the current database.
Look for the session running the REINDEX command and check whether it is waiting for a lock or another resource.
The query text can contain sensitive values in some workloads. Review monitoring access and logging practices before exposing this information to broad audiences.
Check Progress Information
PostgreSQL exposes progress information for supported maintenance operations through system views.
For concurrent index rebuilding, inspect pg_stat_progress_create_index:
SELECT
pid,
datname,
relid::regclass AS table_name,
index_relid::regclass AS index_name,
command,
phase,
lockers_total,
lockers_done,
blocks_total,
blocks_done,
tuples_total,
tuples_done
FROM pg_stat_progress_create_index
WHERE command LIKE 'REINDEX%';
The available columns and their values depend on the PostgreSQL version and the current phase of the operation. Some fields may remain zero or may not be meaningful during particular phases.
This view can help you determine whether PostgreSQL is scanning the table, building the index, validating it, or waiting for concurrent transactions.
Do not interpret every phase as a percentage-based progress indicator. Use the fields relevant to the current phase and compare repeated snapshots.
Watch Application Performance
Database-side progress is only part of the picture.
During a large rebuild, monitor:
- Query latency.
- Database CPU usage.
- Disk throughput and I/O latency.
- Available disk space.
- Connection pool utilization.
- Lock waits.
- Application error rates.
If the rebuild creates unacceptable pressure, consider pausing additional maintenance work, reducing concurrency in the maintenance schedule, or canceling the operation after assessing the consequences.
Do not start several large concurrent rebuilds simply because the first command has not completed quickly.
Handle Failed or Interrupted Concurrent Rebuilds
Concurrent rebuilding has more complex failure handling than a standard rebuild.
If an operation fails, PostgreSQL may leave an invalid index behind. An invalid index cannot be relied upon as a normal usable index, and it may still consume storage or impose maintenance overhead.
After a failed operation, inspect the index's validity before deciding what to do next.
SELECT
ns.nspname AS schema_name,
tbl.relname AS table_name,
idx.relname AS index_name,
i.indisvalid,
i.indisready
FROM pg_index AS i
JOIN pg_class AS idx
ON idx.oid = i.indexrelid
JOIN pg_class AS tbl
ON tbl.oid = i.indrelid
JOIN pg_namespace AS ns
ON ns.oid = tbl.relnamespace
WHERE ns.nspname = 'public'
AND tbl.relname = 'orders';
The indisvalid and indisready flags provide useful information about index state.
An invalid index should be investigated before retrying the rebuild. Do not assume that rerunning the original command is always the correct recovery procedure.
Depending on the index state and PostgreSQL version, you may need to drop and recreate the index or use another supported recovery method. If the index enforces a uniqueness constraint, investigate the constraint and its dependencies before removing anything.
Never drop an index solely because it appears invalid without confirming its purpose and the impact of removing it.
Common Mistakes to Avoid
Assuming CONCURRENTLY Means No Locks
Concurrent rebuilding allows ordinary writes during most of the operation, but PostgreSQL still takes locks and may need to wait for transactions or acquire stronger locks during certain phases.
Long-running transactions can delay completion. Investigate active sessions and transaction age before starting a large rebuild.
Rebuilding Every Index Without Evidence
A rebuild is not a universal performance fix.
If the actual problem is a missing index, an inefficient query, inaccurate statistics, or poor application behavior, rebuilding existing indexes may not solve it.
Establish the cause before scheduling maintenance.
Ignoring Disk Space
Concurrent rebuilding can require substantial additional storage while the old index remains available.
Check available space and consider the size of the target index before starting. Running out of storage can affect the rebuild and other database operations.
Wrapping the Command in a Transaction
REINDEX CONCURRENTLY cannot run inside an explicit transaction block.
Check whether your SQL client, deployment tool, or migration framework automatically starts transactions. Execute the command through a method that respects this restriction.
Running Too Many Rebuilds at Once
Multiple concurrent rebuilds can increase CPU and I/O pressure.
Start with a controlled workload and expand the maintenance schedule only after observing the impact.
Forgetting Post-Maintenance Validation
A completed rebuild should be followed by validation appropriate to the original problem.
If the issue involved index corruption, verify that the reported problem is resolved. If the goal was performance improvement, compare query plans and representative execution times.
A successful command does not automatically prove that the original performance problem has been fixed.
Best Practices for Production Index Maintenance
Follow these practices to minimize the risk of index maintenance:
- Identify the reason for rebuilding an index before scheduling the operation.
- Record the index definition, size, and any constraints that depend on it.
- Check free storage and database resource utilization.
- Prefer REINDEX CONCURRENTLY when normal writes must continue and the target object supports it.
- Run the command outside an explicit transaction block.
- Monitor progress, active transactions, lock waits, and application latency.
- Avoid running many large rebuilds simultaneously.
- Investigate invalid indexes carefully after failures.
- Validate the original problem after maintenance completes.
- Document the operation and its outcome for future maintenance planning.
For high-availability systems, test the procedure in a representative environment before using it on production data.
Also remember that a concurrent rebuild does not replace backups, replication monitoring, or a broader database recovery plan.
Advantages and Disadvantages of REINDEX CONCURRENTLY
Advantages | Disadvantages |
|---|
Allows normal writes during most of the rebuild | Can take longer than a standard rebuild |
Reduces the need for a write-blocking maintenance window | Requires additional storage and I/O |
Can be applied to a specific index | Still requires locks and may wait for transactions |
Supports controlled maintenance of busy tables | Cannot run inside an explicit transaction block |
Can reduce disruption to application workloads | Failure can leave an invalid index requiring investigation |
The best choice depends on table size, application traffic, maintenance requirements, and the type of index being rebuilt.
Summary
PostgreSQL index maintenance does not always require a long write-blocking maintenance window. For supported indexes, REINDEX CONCURRENTLY can rebuild an index while allowing normal application writes to continue during most of the operation.
Start by identifying the index and understanding why it needs maintenance. Check its definition, size, dependencies, and the available database resources. Then choose between a standard rebuild and a concurrent rebuild based on the operational requirements.
Monitor progress through PostgreSQL's activity and progress views, watch application performance, and investigate invalid indexes if an operation fails. After the rebuild, validate that the original problem has been resolved.
Concurrent rebuilding reduces disruption, but it is not free of locking, resource consumption, or operational risk. A measured approach helps keep PostgreSQL maintenance predictable while protecting application availability.
Join the conversation! Your thoughts help the community grow.