PostgreSQL's 16-Slot Lock Fast Path Is a Hidden Bottleneck for Over-Indexed Tables
Sixteen Locks Ought to Be Enough for Anybody
Every query in PostgreSQL takes an AccessShareLock on every index on a table, even ones it doesn't use. Through version 17, each backend has only 16 fast-path slots for these weak locks; overflow goes through the shared lock table and its 16 LWLocks, causing contention on trivial primary-key lookups. Prepared statements can cut locks from 22 to 2, and PostgreSQL 18 sizes the fast-path array from max_locks_per_transaction, giving 64 slots by default.
The disease is a table with 21 indexes.