v2-0002-Improve-clock-sweep-batch-sizing-with-CPU-aware-a.patch
application/octet-stream
Filename: v2-0002-Improve-clock-sweep-batch-sizing-with-CPU-aware-a.patch
Type: application/octet-stream
Part: 1
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v2-0002
Subject: Improve clock sweep batch sizing with CPU-aware algorithm
| File | + | − |
|---|---|---|
| src/backend/storage/buffer/freelist.c | 77 | 14 |
From dce473e1a246d877f0f2313ba7a29a4d0e35dc27 Mon Sep 17 00:00:00 2001
From: Greg Burd <gregburd@amazon.com>
Date: Mon, 27 Apr 2026 08:25:40 -0400
Subject: [PATCH v2 2/2] Improve clock sweep batch sizing with CPU-aware
algorithm
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Replace simple NUMA-only batch sizing with a tiered approach:
- NUMA systems (multi-socket): batch=64 (high interconnect latency)
- Single socket >16 cores: batch=32 (L3 cache contention)
- Single socket 9-16 cores: batch=16 (moderate contention)
- Single socket 5-8 cores: batch=8 (light contention)
- Single socket ≤4 cores: batch=1 (no batching overhead)
Also adds over-claiming protection: batch_size × MaxBackends ≤ pool_size/2
to ensure total claimed buffers stay under 50% of the pool.
This provides atomic contention benefits on large single-socket systems
while maintaining the original behavior on small systems where fairness
matters more than throughput.
Authored-by: Greg Burd <greg@burd.me>
---
src/backend/storage/buffer/freelist.c | 91 ++++++++++++++++++++++-----
1 file changed, 77 insertions(+), 14 deletions(-)
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index e86ed1f7da0..476d7f420a5 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -24,6 +24,10 @@
#include "storage/subsystems.h"
#include "port/pg_numa.h"
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
#define INT_ACCESS_ONCE(var) ((int)(*((volatile int *)&(var))))
@@ -61,6 +65,8 @@ static BufferStrategyControl *StrategyControl = NULL;
static void StrategyCtlShmemRequest(void *arg);
static void StrategyCtlShmemInit(void *arg);
+static int pg_get_online_cpus(void);
+static uint32 ComputeClockBatchSize(int pool_nbuffers);
const ShmemCallbacks StrategyCtlShmemCallbacks = {
.request_fn = StrategyCtlShmemRequest,
@@ -411,6 +417,69 @@ StrategyNotifyBgWriter(int bgwprocno)
SpinLockRelease(&StrategyControl->buffer_strategy_lock);
}
+/*
+ * pg_get_online_cpus -- get the number of online CPU cores
+ */
+static int
+pg_get_online_cpus(void)
+{
+#ifdef _SC_NPROCESSORS_ONLN
+ long ncpus = sysconf(_SC_NPROCESSORS_ONLN);
+
+ if (ncpus > 0)
+ return (int) ncpus;
+#endif
+ /* Fallback if sysconf is unavailable or fails */
+ return 1;
+}
+
+/*
+ * ComputeClockBatchSize -- compute the effective clock-sweep batch size
+ *
+ * The function has two phases: select a base batch from hardware topology,
+ * then cap it to prevent over-claiming.
+ *
+ * Phase 1: Base batch from topology
+ * - NUMA (multi-socket): batch=64 (high cross-socket latency)
+ * - >16 cores, single socket: batch=32 (L3 contention)
+ * - 9-16 cores: batch=16 (moderate contention)
+ * - 5-8 cores: batch=8 (light contention)
+ * - <=4 cores: batch=1 (no batching overhead)
+ *
+ * Phase 2: Cap to prevent over-claiming
+ * - Ensure batch_size * MaxBackends <= pool_nbuffers / 2
+ * - Keeps total claims under 50% of the pool
+ */
+static uint32
+ComputeClockBatchSize(int pool_nbuffers)
+{
+ int ncpus = pg_get_online_cpus();
+ int numa_nodes = (pg_numa_init() != -1) ? pg_numa_get_max_node() + 1 : 1;
+ uint32 base_batch;
+ uint32 max_batch;
+
+ /* Phase 1: Base batch from topology */
+ if (numa_nodes > 1)
+ base_batch = 64;
+ else if (ncpus > 16)
+ base_batch = 32;
+ else if (ncpus > 8)
+ base_batch = 16;
+ else if (ncpus > 4)
+ base_batch = 8;
+ else
+ base_batch = 1;
+
+ /* Phase 2: Cap to prevent over-claiming */
+ max_batch = (MaxBackends > 0)
+ ? pool_nbuffers / (2 * MaxBackends)
+ : pool_nbuffers / 200;
+ if (max_batch < 1)
+ max_batch = 1;
+
+ return Min(base_batch, Min(max_batch, (uint32) pool_nbuffers));
+}
+
/*
* StrategyCtlShmemRequest -- request shared memory for the buffer
@@ -444,22 +513,16 @@ StrategyCtlShmemInit(void *arg)
StrategyControl->bgwprocno = -1;
/*
- * Determine the effective clock-sweep batch size.
+ * Compute the effective clock-sweep batch size based on hardware
+ * topology.
*
- * On multi-node NUMA systems, claiming batches of buffers from the shared
- * clock hand reduces cross-socket contention on the atomic counter. On
- * single-socket systems, batching provides no benefit (the atomic is
- * already socket-local) and just causes backends to skip buffers, so we
- * use batch size 1 for the original behavior.
- *
- * pg_numa_init() returns -1 when NUMA is unavailable.
- * pg_numa_get_max_node() returns 0 for a single NUMA node.
+ * This uses a tiered approach: larger batches on NUMA systems and
+ * many-core single-socket systems where atomic contention is high,
+ * smaller batches or no batching on few-core systems where fairness
+ * matters more. The batch size is also capped to prevent over-claiming
+ * when there are many backends relative to the buffer pool size.
*/
- if (pg_numa_init() != -1 && pg_numa_get_max_node() >= 1)
- ClockSweepBatchSize = Min(CLOCK_SWEEP_BATCH_SIZE,
- (uint32) NBuffers);
- else
- ClockSweepBatchSize = 1;
+ ClockSweepBatchSize = ComputeClockBatchSize(NBuffers);
}
--
2.50.1 (Apple Git-155)