Skip to content

Commit da142d7

Browse files
akorotkovxunengzhou-prog
authored andcommitted
Process sync requests incrementally in AbsorbSyncRequests
If the number of sync requests is big enough, the palloc() call in AbsorbSyncRequests() will attempt to allocate more than 1 GB of memory, resulting in failure. This can lead to an infinite loop in the checkpointer process, as it repeatedly fails to absorb the pending requests. This commit introduces the following changes to cope with this problem: 1. Turn pending checkpointer requests array in shared memory into a bounded ring buffer. 2. Limit maximum ring buffer size to 10M items. 3. Make AbsorbSyncRequests() process requests incrementally in 10K batches. Even #2 makes the whole queue size fit the maximum palloc() size of 1 GB. of continuous lock holding. This commit is for master only. Simpler fix, which just limits a request queue size to 10M, will be backpatched. Reported-by: Ekaterina Sokolova <e.sokolova@postgrespro.ru> Discussion: https://postgr.es/m/db4534f83a22a29ab5ee2566ad86ca92%40postgrespro.ru Author: Maxim Orlov <orlovmg@gmail.com> Co-authored-by: Xuneng Zhou <xunengzhou@gmail.com> Reviewed-by: Andres Freund <andres@anarazel.de> Reviewed-by: Heikki Linnakangas <hlinnaka@iki.fi> Reviewed-by: Alexander Korotkov <aekorotkov@gmail.com>
1 parent 0faa7b3 commit da142d7

1 file changed

Lines changed: 114 additions & 41 deletions

File tree

src/backend/postmaster/checkpointer.c

Lines changed: 114 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)