June 24, 2021·4 min read·engineeringpostgresqlvacuumtesting

PostgreSQL 14 Beta 2 exposes the decisions behind index cleanup

Beta 2 adds control over VACUUM's index bypass behavior. The release also makes clear that beta upgrades and application compatibility still require deliberate testing.

Processing every index can cost more than removing a small number of dead index entries is worth. PostgreSQL 14's VACUUM can skip that work when there are few dead tuples. Beta 2 adds control over this bypass behavior, giving operators a way to request the older, more conservative cleanup policy.

The June 24 announcement identifies this as one of the changes since Beta 1. The release remains a preview whose behavior and interfaces can change. Its maintenance controls are useful to examine now because they affect the meaning of a familiar operational command.

I value PostgreSQL's documented operational exceptions. A maintenance optimization is easier to assess when its conditions and override are explicit. In this case, the beta documentation explains both the ordinary cost decision and a separate safety mechanism that can override the requested cleanup policy.

A cost decision inside VACUUM

The VACUUM reference at the Beta 2 tag describes why the command is necessary. Deleted rows and rows replaced by updates remain in the table until vacuum processes them. Index maintenance is part of the work associated with those dead tuples.

The new default for INDEX_CLEANUP is AUTO. It allows VACUUM to skip index vacuuming when appropriate. The documentation explains the intended case as one with very few dead tuples, where processing all indexes costs much more than removing the corresponding index entries benefits the table.

Setting INDEX_CLEANUP to ON requests conservative removal of dead index tuples whenever there are more than zero dead tuples. The documentation identifies compatibility with earlier releases as one reason to choose it. The option makes the difference between automatic cost-based behavior and the earlier cleanup expectation visible in a command.

For an existing table named measurements, the beta's documented syntax permits the following operation. The table name is illustrative, while the option and its value come from the tagged reference.

VACUUM (INDEX_CLEANUP ON) measurements;

This setting should not be interpreted as an unconditional promise that every safety path will perform index vacuuming. The same reference describes the transaction-ID wraparound failsafe. When that mechanism triggers, it skips index vacuuming even if INDEX_CLEANUP is ON.

That exception gives the control a precise boundary. ON selects the ordinary cleanup policy, but it does not disable the failsafe. The reference also says the option has no effect for a table without indexes and is ignored with FULL. Those conditions belong to the command's behavior, even when an operational script supplies the option explicitly.

Skipping work has a retained cost

The OFF value forces VACUUM to skip index vacuuming even when the table has many dead tuples. The documentation mentions an urgent need to finish vacuum before transaction-ID wraparound as a possible use. It prefers the automatic failsafe for that situation.

Repeatedly omitting index cleanup also leaves work behind. The reference explains that indexes accumulate dead tuples, while the table accumulates dead line pointers that cannot be removed until index cleanup completes. The immediate reduction in maintenance work therefore has consequences for later operation.

This makes AUTO different from a blanket instruction to omit cleanup. Its purpose is to avoid an unfavorable cost tradeoff under particular conditions. OFF expresses a stronger policy, with a correspondingly explicit warning about the retained state.

The control is useful because the caller can state which policy a particular operation requests. A command with ON communicates a different expectation from one relying on AUTO. The failsafe remains an independently documented exception in both the command's semantics and the operator's interpretation of its result.

Beta changes reach client-facing behavior too

The announcement also lists a redesign of OUT-parameter handling for procedures. The CREATE PROCEDURE reference shipped in Beta 2 includes IN, OUT, INOUT and VARIADIC argument modes. That is an example of a feature whose detailed interface still matters during the beta period.

Other fixes concern different execution boundaries. Beta 2 corrects a libpq crash involving SSL server-name indication when a connection string specifies hostaddr. It fixes an assertion failure while streaming multi-INSERT data through logical replication. The announcement also names postgres_fdw failures involving whole-row variables and several issues in pg_stat_wal.

These fixes supply concrete reasons to exercise actual application and operational paths. A successful simple query cannot cover the connection-string combination or replication operation named in the announcement. Beta feedback is more useful when it identifies the operation, its inputs and the observed failure.

I regard that specificity as the reason to try a beta with representative workloads. The point is to find an incompatibility while its interface can still change. The project explicitly asks for ordinary application workloads and advises against production use of Beta 2.

Upgrading from Beta 1 requires a strategy similar to a major-version upgrade, such as pg_upgrade or dump and restore. The same instruction applies when moving from an earlier PostgreSQL version. A beta number therefore does not imply an interchangeable data directory.

The announcement expects further betas as testing requires, followed by release candidates. As of this release, those stages remain prospective. The available work is to evaluate Beta 2's documented behavior, including whether the new VACUUM policy and its explicit controls match the maintenance operations an application depends on.