hot_standby is the switch that decides whether a standby server is a spare you can only fail over to or a replica you can also read from. It has been set to the second of those by default since PostgreSQL 10, which is why, for most people running replication today, it is a switch already in the position they want that never has to be touched.

It is a boolean, its default is on, and its context is postmaster, so it is fixed at server start. What it enables is the ability to connect to a server that is in recovery, replaying WAL from an archive or a streaming primary, and run read-only queries against it while that replay continues. The read-only part is enforced rather than trusted: any session connected during recovery has its transactions forced read-only no matter what the client asks for. The feature it turns on, Hot Standby, arrived in PostgreSQL 9.0 and is what separates a modern read replica from the older “warm standby” that could only sit and replay until it was promoted.

It does nothing on a primary

The first thing to understand is where this parameter has no effect, because that is where people most often trip over it.

hot_standby only does anything on a server that is in recovery. On a primary, in normal operation, its value is simply irrelevant, and the documentation says as much about the whole family of standby settings: their values on the primary do not matter. This catches people out because the usual practice is to keep one postgresql.conf shared between primary and standbys, so hot_standby = on sits right there in the primary’s configuration doing precisely nothing. Setting it, changing it, and restarting the primary to “apply” it all produce the same visible result, which is none, because the primary is not in recovery and the parameter is waiting for a role this server is not currently playing.

The dependency that makes it work

For a standby to accept queries, the WAL it is replaying has to carry enough information to reconstruct a consistent, queryable view, and that is a decision made on the primary, not the standby. Specifically, hot standby cannot be enabled while reading WAL that was written when the primary’s wal_level was below replica.

Since PostgreSQL 10 this lines up on its own. That release set wal_level to replica and hot_standby to on by default in the same wave of changes that made streaming replication work without reconfiguring and restarting the primary first. Before 10 the defaults did not cooperate: hot_standby defaulted to off, and you had to raise wal_level and enable hot standby by hand. Older setups carry the scars of that era, including a wal_level value literally named hot_standby, which was folded into replica back in 9.6 and now just maps to it.

What “on” actually costs

Turning a standby into a query target is one line. Living with the consequence is the actual work, and the consequence is conflict.

A standby is doing two things at once: applying the primary’s changes, and answering queries as of some slightly earlier point in time. Those goals collide. WAL replay may need to remove a row, or truncate a page, or take a lock, that a query currently running on the standby still depends on. PostgreSQL cannot both apply that record and keep the query’s view intact, so it has to choose, and it chooses by waiting a bounded amount of time for the query to finish and then canceling it if it does not.

That bound is where the real parameters live. max_standby_streaming_delay governs how long replay will wait on a conflicting query when WAL is arriving via streaming; max_standby_archive_delay does the same for WAL read from an archive. Both default to 30 seconds, and both accept -1 to mean wait forever. A short delay keeps the standby current at the cost of canceling queries briskly, which is what you want from a standby that exists for failover. A long or infinite delay lets big analytical queries run to completion, at the cost of letting the standby fall behind, and while it is behind, every session on it sees a staler database. There is a sharp edge worth knowing here: the delay is the budget for applying a single WAL segment, not a per-query allowance, so a query that has already consumed most of that budget leaves the next conflicting query very little.

The other lever is hot_standby_feedback, off by default, which has the standby tell the primary which rows its running queries still need, so the primary holds off vacuuming them away. That trades a possibility of bloat on the primary for far fewer query cancellations on the standby. The reason to name all of these is that hot_standby itself is not the tuning surface; it is the switch that brings the tuning surface into existence.

When you would turn it off

There is one coherent reason to set hot_standby = off: a standby whose only job is to be ready. If a replica exists purely for fast failover and you do not intend to run queries on it, turning hot standby off means no query conflicts, no reader ever delaying replay, and therefore the smallest possible lag and the quickest possible catch-up and promotion. That is a legitimate choice for a dedicated hot spare. For everyone else, the read capacity is the entire reason to run a replica in the first place, and off throws it away.

The summary

Leave hot_standby on. Since PostgreSQL 10 it has defaulted to the setting almost everyone wants, and it costs nothing to leave alone. Understand that it does nothing on your primary, that it depends on the primary’s wal_level being replica or higher, and above all that flipping it on is the trivial part of running a queryable replica. The work is in max_standby_streaming_delay, max_standby_archive_delay, and hot_standby_feedback, which are where the tension between replaying fast and querying freely actually gets resolved, and which this switch merely wakes up.