commit_timestamp_buffers is the first, alphabetically, of seven parameters PostgreSQL 17 added to do the same job for seven different caches, so this post explains the job and the rest of the cluster can stay short.
The caches in question are PostgreSQL’s SLRUs (simple least-recently-used pools). These are small dedicated buffer pools, separate from the main shared_buffers, that cache a handful of fixed-format auxiliary structures the server keeps on disk: transaction commit status (pg_xact), commit timestamps (pg_commit_ts), subtransaction parents (pg_subtrans), multixact data, the LISTEN/NOTIFY queue, and SERIALIZABLE predicate-lock data. Each gets its own pool. Until PostgreSQL 17, the size of every one of those pools was a compiled-in constant, which meant that on a high-throughput system where one of these caches turned into a point of contention, your options were to live with it or to patch and rebuild the server. PostgreSQL 17 turned each constant into a GUC. commit_timestamp_buffers is the one for pg_commit_ts.
The defaults are worth reading carefully. The value is 0, which does not mean zero buffers; it means “size this pool automatically from shared_buffers.” The maximum is 131072, and since the value counts 8kB buffers, that ceiling works out to 1GB. The context is postmaster: the pool is allocated at startup, so you size it before the load arrives, not during it.
The part that matters most is what actually feeds this cache. pg_commit_ts holds data only when track_commit_timestamp is on, and that parameter is off by default. Commit-timestamp tracking records the commit time and replication origin of every transaction, at a cost of roughly a dozen bytes per commit, and it exists mainly for logical replication conflict detection and for queries such as pg_xact_commit_timestamp(). If you have not turned it on, the commit-timestamp SLRU sits empty and commit_timestamp_buffers does nothing for you at all. This is the rare tuning knob that is genuinely irrelevant to most installations, and that’s fine; not every parameter is for everyone.
If you do run track_commit_timestamp, the question of whether the auto-sized pool is large enough is answered by pg_stat_slru, not by intuition. Look at the row where name = 'commit_timestamp': if blks_read climbs steadily against blks_hit, the cache is missing and going to disk often enough to matter, and giving it more buffers will help. (PostgreSQL 17 also renamed the pg_stat_slru entries to match the new parameter names, so the view and the GUCs finally agree on what each cache is called.)
So: if you aren’t tracking commit timestamps, this parameter doesn’t concern you, and 0 is where it stays forever. If you are, it still stays at 0 until pg_stat_slru shows the commit-timestamp cache thrashing under real load; then you raise it and take the restart. The auto-sizing is correct until a measurement says otherwise, and a measurement is the only thing that should ever move it.