Skip to content
dbexplore

Question: Locks and schema changes

ERROR: out of shared memory. HINT: You might need to increase max_locks_per_transaction.

Answered in the first paragraph. Last updated .

The lock table ran out of room. It is a fixed block of shared memory, sized when the server starts from the per-transaction setting multiplied by the number of backends it was configured for, and it holds one entry for each distinct object a transaction has locked. Rows are not in it, and the documentation says so: row locking is unlimited. What fills it is one transaction touching thousands of objects.

Why partitioned tables are almost always the answer

A partitioned table is many tables, and every partition and every index on it is a separate object with its own entry. A query that cannot exclude partitions at planning time locks all of them, so a table divided by day across several years reaches four figures of locks in a single statement, on a setting whose default is measured in tens.

The same shape appears without partitioning. A schema migration that alters a hundred tables in one transaction, a dump that opens everything at once, or an application that wraps a whole batch of unrelated work in one transaction, all accumulate entries that are only released at commit.

The setting is an average rather than a ceiling per transaction. Any single transaction may take more than its share, provided the table as a whole has room, which is why the error appears under concurrency and not in the test that ran the same statement alone.

Raising it, and the two things that come with raising it

It is fixed at server start, so this is a restart. And because the table is sized per backend, raising the connection limit raises the shared memory requirement with it; the two settings multiply, and clusters have failed to start after both were raised independently by different people.

The rule that catches teams out is on the standby. The documentation requires the standby’s value to be at least the primary’s, or it will not allow queries at all. Raise it on the replicas first and the primary second, which is the opposite of the order most runbooks use. Keeping that pair in step is exactly what configuration drift between a primary and its standby is about.

Raising the number is the remedy, not the fix. The fix is a plan that touches fewer objects: partition pruning that works at planning time, statements scoped to one partition, and migrations split into transactions per table. If you are not sure which statement is doing it, the lock view will show you the count by transaction, and lock contention and blocking trees covers reading it. The strengths of the locks themselves do not matter here; only the count does, which is the one situation where lock modes is not the page you need.

Put every Postgres you run on autopilot.

We onboard teams in small batches. Tell us about your fleet and we will reach out when a seat opens. One email, no drip campaign.