@@ -130,6 +130,13 @@ typedef struct
130130
131131 int num_requests ; /* current # of requests */
132132 int max_requests ; /* allocated array size */
133+
134+ int head ; /* Index of the first request in the ring
135+ * buffer */
136+ int tail ; /* Index of the last request in the ring
137+ * buffer */
138+
139+ /* The ring buffer of pending checkpointer requests */
133140 CheckpointerRequest requests [FLEXIBLE_ARRAY_MEMBER ];
134141} CheckpointerShmemStruct ;
135142
@@ -138,6 +145,12 @@ static CheckpointerShmemStruct *CheckpointerShmem;
138145/* interval for calling AbsorbSyncRequests in CheckpointWriteDelay */
139146#define WRITES_PER_ABSORB 1000
140147
148+ /* Maximum number of checkpointer requests to process in one batch */
149+ #define CKPT_REQ_BATCH_SIZE 10000
150+
151+ /* Max number of requests the checkpointer request queue can hold */
152+ #define MAX_CHECKPOINT_REQUESTS 10000000
153+
141154/*
142155 * GUC parameters
143156 */
@@ -973,7 +986,8 @@ CheckpointerShmemInit(void)
973986 */
974987 MemSet (CheckpointerShmem , 0 , size );
975988 SpinLockInit (& CheckpointerShmem -> ckpt_lck );
976- CheckpointerShmem -> max_requests = NBuffers ;
989+ CheckpointerShmem -> max_requests = Min (NBuffers , MAX_CHECKPOINT_REQUESTS );
990+ CheckpointerShmem -> head = CheckpointerShmem -> tail = 0 ;
977991 ConditionVariableInit (& CheckpointerShmem -> start_cv );
978992 ConditionVariableInit (& CheckpointerShmem -> done_cv );
979993 }
@@ -1201,6 +1215,7 @@ ForwardSyncRequest(const FileTag *ftag, SyncRequestType type)
12011215{
12021216 CheckpointerRequest * request ;
12031217 bool too_full ;
1218+ int insert_pos ;
12041219
12051220 if (!IsUnderPostmaster )
12061221 return false; /* probably shouldn't even get here */
@@ -1224,10 +1239,14 @@ ForwardSyncRequest(const FileTag *ftag, SyncRequestType type)
12241239 }
12251240
12261241 /* OK, insert request */
1227- request = & CheckpointerShmem -> requests [CheckpointerShmem -> num_requests ++ ];
1242+ insert_pos = CheckpointerShmem -> tail ;
1243+ request = & CheckpointerShmem -> requests [insert_pos ];
12281244 request -> ftag = * ftag ;
12291245 request -> type = type ;
12301246
1247+ CheckpointerShmem -> tail = (CheckpointerShmem -> tail + 1 ) % CheckpointerShmem -> max_requests ;
1248+ CheckpointerShmem -> num_requests ++ ;
1249+
12311250 /* If queue is more than half full, nudge the checkpointer to empty it */
12321251 too_full = (CheckpointerShmem -> num_requests >=
12331252 CheckpointerShmem -> max_requests / 2 );
@@ -1269,12 +1288,16 @@ CompactCheckpointerRequestQueue(void)
12691288 struct CheckpointerSlotMapping
12701289 {
12711290 CheckpointerRequest request ;
1272- int slot ;
1291+ int ring_idx ;
12731292 };
12741293
1275- int n ,
1276- preserve_count ;
1294+ int n ;
12771295 int num_skipped = 0 ;
1296+ int head ;
1297+ int max_requests ;
1298+ int num_requests ;
1299+ int read_idx ,
1300+ write_idx ;
12781301 HASHCTL ctl ;
12791302 HTAB * htab ;
12801303 bool * skip_slot ;
@@ -1286,8 +1309,13 @@ CompactCheckpointerRequestQueue(void)
12861309 if (CritSectionCount > 0 )
12871310 return false;
12881311
1312+ max_requests = CheckpointerShmem -> max_requests ;
1313+ num_requests = CheckpointerShmem -> num_requests ;
1314+
12891315 /* Initialize skip_slot array */
1290- skip_slot = palloc0 (sizeof (bool ) * CheckpointerShmem -> num_requests );
1316+ skip_slot = palloc0 (sizeof (bool ) * max_requests );
1317+
1318+ head = CheckpointerShmem -> head ;
12911319
12921320 /* Initialize temporary hash table */
12931321 ctl .keysize = sizeof (CheckpointerRequest );
@@ -1311,7 +1339,8 @@ CompactCheckpointerRequestQueue(void)
13111339 * away preceding entries that would end up being canceled anyhow), but
13121340 * it's not clear that the extra complexity would buy us anything.
13131341 */
1314- for (n = 0 ; n < CheckpointerShmem -> num_requests ; n ++ )
1342+ read_idx = head ;
1343+ for (n = 0 ; n < num_requests ; n ++ )
13151344 {
13161345 CheckpointerRequest * request ;
13171346 struct CheckpointerSlotMapping * slotmap ;
@@ -1324,16 +1353,19 @@ CompactCheckpointerRequestQueue(void)
13241353 * CheckpointerShmemInit. Note also that RelFileLocator had better
13251354 * contain no pad bytes.
13261355 */
1327- request = & CheckpointerShmem -> requests [n ];
1356+ request = & CheckpointerShmem -> requests [read_idx ];
13281357 slotmap = hash_search (htab , request , HASH_ENTER , & found );
13291358 if (found )
13301359 {
13311360 /* Duplicate, so mark the previous occurrence as skippable */
1332- skip_slot [slotmap -> slot ] = true;
1361+ skip_slot [slotmap -> ring_idx ] = true;
13331362 num_skipped ++ ;
13341363 }
13351364 /* Remember slot containing latest occurrence of this request value */
1336- slotmap -> slot = n ;
1365+ slotmap -> ring_idx = read_idx ;
1366+
1367+ /* Move to the next request in the ring buffer */
1368+ read_idx = (read_idx + 1 ) % max_requests ;
13371369 }
13381370
13391371 /* Done with the hash table. */
@@ -1347,17 +1379,34 @@ CompactCheckpointerRequestQueue(void)
13471379 }
13481380
13491381 /* We found some duplicates; remove them. */
1350- preserve_count = 0 ;
1351- for (n = 0 ; n < CheckpointerShmem -> num_requests ; n ++ )
1382+ read_idx = write_idx = head ;
1383+ for (n = 0 ; n < num_requests ; n ++ )
13521384 {
1353- if (skip_slot [n ])
1354- continue ;
1355- CheckpointerShmem -> requests [preserve_count ++ ] = CheckpointerShmem -> requests [n ];
1385+ /* If this slot is NOT skipped, keep it */
1386+ if (!skip_slot [read_idx ])
1387+ {
1388+ /* If the read and write positions are different, copy the request */
1389+ if (write_idx != read_idx )
1390+ CheckpointerShmem -> requests [write_idx ] =
1391+ CheckpointerShmem -> requests [read_idx ];
1392+
1393+ /* Advance the write position */
1394+ write_idx = (write_idx + 1 ) % max_requests ;
1395+ }
1396+
1397+ read_idx = (read_idx + 1 ) % max_requests ;
13561398 }
1399+
1400+ /*
1401+ * Update ring buffer state: head remains the same, tail moves, count
1402+ * decreases
1403+ */
1404+ CheckpointerShmem -> tail = write_idx ;
1405+ CheckpointerShmem -> num_requests -= num_skipped ;
1406+
13571407 ereport (DEBUG1 ,
13581408 (errmsg_internal ("compacted fsync request queue from %d entries to %d entries" ,
1359- CheckpointerShmem -> num_requests , preserve_count )));
1360- CheckpointerShmem -> num_requests = preserve_count ;
1409+ num_requests , CheckpointerShmem -> num_requests )));
13611410
13621411 /* Cleanup. */
13631412 pfree (skip_slot );
@@ -1378,40 +1427,64 @@ AbsorbSyncRequests(void)
13781427{
13791428 CheckpointerRequest * requests = NULL ;
13801429 CheckpointerRequest * request ;
1381- int n ;
1430+ int n ,
1431+ i ;
1432+ bool loop ;
13821433
13831434 if (!AmCheckpointerProcess ())
13841435 return ;
13851436
1386- LWLockAcquire (CheckpointerCommLock , LW_EXCLUSIVE );
1387-
1388- /*
1389- * We try to avoid holding the lock for a long time by copying the request
1390- * array, and processing the requests after releasing the lock.
1391- *
1392- * Once we have cleared the requests from shared memory, we have to PANIC
1393- * if we then fail to absorb them (eg, because our hashtable runs out of
1394- * memory). This is because the system cannot run safely if we are unable
1395- * to fsync what we have been told to fsync. Fortunately, the hashtable
1396- * is so small that the problem is quite unlikely to arise in practice.
1397- */
1398- n = CheckpointerShmem -> num_requests ;
1399- if (n > 0 )
1437+ do
14001438 {
1401- requests = (CheckpointerRequest * ) palloc (n * sizeof (CheckpointerRequest ));
1402- memcpy (requests , CheckpointerShmem -> requests , n * sizeof (CheckpointerRequest ));
1403- }
1439+ LWLockAcquire (CheckpointerCommLock , LW_EXCLUSIVE );
1440+
1441+ /*---
1442+ * We try to avoid holding the lock for a long time by:
1443+ * 1. Copying the request array and processing the requests after
1444+ * releasing the lock;
1445+ * 2. Processing not the whole queue, but only batches of
1446+ * CKPT_REQ_BATCH_SIZE at once.
1447+ *
1448+ * Once we have cleared the requests from shared memory, we must
1449+ * PANIC if we then fail to absorb them (e.g., because our hashtable
1450+ * runs out of memory). This is because the system cannot run safely
1451+ * if we are unable to fsync what we have been told to fsync.
1452+ * Fortunately, the hashtable is so small that the problem is quite
1453+ * unlikely to arise in practice.
1454+ *
1455+ * Note: The maximum possible size of a ring buffer is
1456+ * MAX_CHECKPOINT_REQUESTS entries, which fit into a maximum palloc
1457+ * allocation size of 1Gb. Our maximum batch size,
1458+ * CKPT_REQ_BATCH_SIZE, is even smaller.
1459+ */
1460+ n = Min (CheckpointerShmem -> num_requests , CKPT_REQ_BATCH_SIZE );
1461+ if (n > 0 )
1462+ {
1463+ if (!requests )
1464+ requests = (CheckpointerRequest * ) palloc (n * sizeof (CheckpointerRequest ));
14041465
1405- START_CRIT_SECTION ();
1466+ for (i = 0 ; i < n ; i ++ )
1467+ {
1468+ requests [i ] = CheckpointerShmem -> requests [CheckpointerShmem -> head ];
1469+ CheckpointerShmem -> head = (CheckpointerShmem -> head + 1 ) % CheckpointerShmem -> max_requests ;
1470+ }
14061471
1407- CheckpointerShmem -> num_requests = 0 ;
1472+ CheckpointerShmem -> num_requests -= n ;
14081473
1409- LWLockRelease (CheckpointerCommLock );
1474+ }
1475+
1476+ START_CRIT_SECTION ();
1477+
1478+ /* Are there any requests in the queue? If so, keep going. */
1479+ loop = CheckpointerShmem -> num_requests != 0 ;
1480+
1481+ LWLockRelease (CheckpointerCommLock );
14101482
1411- for (request = requests ; n > 0 ; request ++ , n -- )
1412- RememberSyncRequest (& request -> ftag , request -> type );
1483+ for (request = requests ; n > 0 ; request ++ , n -- )
1484+ RememberSyncRequest (& request -> ftag , request -> type );
14131485
1414- END_CRIT_SECTION ();
1486+ END_CRIT_SECTION ();
1487+ } while (loop );
14151488
14161489 if (requests )
14171490 pfree (requests );
0 commit comments