Skip to content

Commit 0151af4

Browse files
committed
Avoid O(N^2) behavior in SyncPostCheckpoint().
As in commits 6301c3a and e9d9ba2, avoid doing repetitive list_delete_first() operations, since that would be expensive when there are many files waiting to be unlinked. This is a slightly larger change than in those cases. We have to keep the list state valid for calls to AbsorbSyncRequests(), so it's necessary to invent a "canceled" field instead of immediately deleting PendingUnlinkEntry entries. Also, because we might not be able to process all the entries, we need a new list primitive list_delete_first_n(). list_delete_first_n() is almost list_copy_tail(), but it modifies the input List instead of making a new copy. I found a couple of existing uses of the latter that could profitably use the new function. (There might be more, but the other callers look like they probably shouldn't overwrite the input List.) As before, back-patch to v13. Discussion: https://postgr.es/m/CD2F0E7F-9822-45EC-A411-AE56F14DEA9F@amazon.com
1 parent e477642 commit 0151af4

File tree

5 files changed

+103
-14
lines changed

5 files changed

+103
-14
lines changed

src/backend/nodes/list.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,72 @@ list_delete_last(List *list)
891891
return list_truncate(list, list_length(list) - 1);
892892
}
893893

894+
/*
895+
* Delete the first N cells of the list.
896+
*
897+
* The List is pfree'd if the request causes all cells to be deleted.
898+
*/
899+
List *
900+
list_delete_first_n(List *list, int n)
901+
{
902+
check_list_invariants(list);
903+
904+
/* No-op request? */
905+
if (n <= 0)
906+
return list;
907+
908+
/* Delete whole list? */
909+
if (n >= list_length(list))
910+
{
911+
list_free(list);
912+
return NIL;
913+
}
914+
915+
/*
916+
* Otherwise, we normally just collapse out the removed elements. But for
917+
* debugging purposes, move the whole list contents someplace else.
918+
*
919+
* (Note that we *must* keep the contents in the same memory context.)
920+
*/
921+
#ifndef DEBUG_LIST_MEMORY_USAGE
922+
memmove(&list->elements[0], &list->elements[n],
923+
(list->length - n) * sizeof(ListCell));
924+
list->length -= n;
925+
#else
926+
{
927+
ListCell *newelems;
928+
int newmaxlen = list->length - n;
929+
930+
newelems = (ListCell *)
931+
MemoryContextAlloc(GetMemoryChunkContext(list),
932+
newmaxlen * sizeof(ListCell));
933+
memcpy(newelems, &list->elements[n], newmaxlen * sizeof(ListCell));
934+
if (list->elements != list->initial_elements)
935+
pfree(list->elements);
936+
else
937+
{
938+
/*
939+
* As in enlarge_list(), clear the initial_elements[] space and/or
940+
* mark it inaccessible.
941+
*/
942+
#ifdef CLOBBER_FREED_MEMORY
943+
wipe_mem(list->initial_elements,
944+
list->max_length * sizeof(ListCell));
945+
#else
946+
VALGRIND_MAKE_MEM_NOACCESS(list->initial_elements,
947+
list->max_length * sizeof(ListCell));
948+
#endif
949+
}
950+
list->elements = newelems;
951+
list->max_length = newmaxlen;
952+
list->length = newmaxlen;
953+
check_list_invariants(list);
954+
}
955+
#endif
956+
957+
return list;
958+
}
959+
894960
/*
895961
* Generate the union of two lists. This is calculated by copying
896962
* list1 via list_copy(), then adding to it all the members of list2

src/backend/optimizer/util/clauses.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4165,7 +4165,7 @@ add_function_defaults(List *args, HeapTuple func_tuple)
41654165
if (ndelete < 0)
41664166
elog(ERROR, "not enough default arguments");
41674167
if (ndelete > 0)
4168-
defaults = list_copy_tail(defaults, ndelete);
4168+
defaults = list_delete_first_n(defaults, ndelete);
41694169

41704170
/* And form the combined argument list, not modifying the input list */
41714171
return list_concat_copy(args, defaults);

src/backend/parser/parse_func.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1679,7 +1679,7 @@ func_get_detail(List *funcname,
16791679

16801680
ndelete = list_length(defaults) - best_candidate->ndargs;
16811681
if (ndelete > 0)
1682-
defaults = list_copy_tail(defaults, ndelete);
1682+
defaults = list_delete_first_n(defaults, ndelete);
16831683
*argdefaults = defaults;
16841684
}
16851685
}

src/backend/storage/sync/sync.c

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ typedef struct
6666
{
6767
FileTag tag; /* identifies handler and file */
6868
CycleCtr cycle_ctr; /* checkpoint_cycle_ctr when request was made */
69+
bool canceled; /* true if request has been canceled */
6970
} PendingUnlinkEntry;
7071

7172
static HTAB *pendingOps = NULL;
@@ -174,13 +175,18 @@ void
174175
SyncPostCheckpoint(void)
175176
{
176177
int absorb_counter;
178+
ListCell *lc;
177179

178180
absorb_counter = UNLINKS_PER_ABSORB;
179-
while (pendingUnlinks != NIL)
181+
foreach(lc, pendingUnlinks)
180182
{
181-
PendingUnlinkEntry *entry = (PendingUnlinkEntry *) linitial(pendingUnlinks);
183+
PendingUnlinkEntry *entry = (PendingUnlinkEntry *) lfirst(lc);
182184
char path[MAXPGPATH];
183185

186+
/* Skip over any canceled entries */
187+
if (entry->canceled)
188+
continue;
189+
184190
/*
185191
* New entries are appended to the end, so if the entry is new we've
186192
* reached the end of old entries.
@@ -210,22 +216,40 @@ SyncPostCheckpoint(void)
210216
errmsg("could not remove file \"%s\": %m", path)));
211217
}
212218

213-
/* And remove the list entry */
214-
pendingUnlinks = list_delete_first(pendingUnlinks);
215-
pfree(entry);
219+
/* Mark the list entry as canceled, just in case */
220+
entry->canceled = true;
216221

217222
/*
218223
* As in ProcessSyncRequests, we don't want to stop absorbing fsync
219224
* requests for a long time when there are many deletions to be done.
220-
* We can safely call AbsorbSyncRequests() at this point in the loop
221-
* (note it might try to delete list entries).
225+
* We can safely call AbsorbSyncRequests() at this point in the loop.
222226
*/
223227
if (--absorb_counter <= 0)
224228
{
225229
AbsorbSyncRequests();
226230
absorb_counter = UNLINKS_PER_ABSORB;
227231
}
228232
}
233+
234+
/*
235+
* If we reached the end of the list, we can just remove the whole list
236+
* (remembering to pfree all the PendingUnlinkEntry objects). Otherwise,
237+
* we must keep the entries at or after "lc".
238+
*/
239+
if (lc == NULL)
240+
{
241+
list_free_deep(pendingUnlinks);
242+
pendingUnlinks = NIL;
243+
}
244+
else
245+
{
246+
int ntodelete = list_cell_number(pendingUnlinks, lc);
247+
248+
for (int i = 0; i < ntodelete; i++)
249+
pfree(list_nth(pendingUnlinks, i));
250+
251+
pendingUnlinks = list_delete_first_n(pendingUnlinks, ntodelete);
252+
}
229253
}
230254

231255
/*
@@ -465,17 +489,14 @@ RememberSyncRequest(const FileTag *ftag, SyncRequestType type)
465489
entry->canceled = true;
466490
}
467491

468-
/* Remove matching unlink requests */
492+
/* Cancel matching unlink requests */
469493
foreach(cell, pendingUnlinks)
470494
{
471495
PendingUnlinkEntry *entry = (PendingUnlinkEntry *) lfirst(cell);
472496

473497
if (entry->tag.handler == ftag->handler &&
474498
syncsw[ftag->handler].sync_filetagmatches(ftag, &entry->tag))
475-
{
476-
pendingUnlinks = foreach_delete_current(pendingUnlinks, cell);
477-
pfree(entry);
478-
}
499+
entry->canceled = true;
479500
}
480501
}
481502
else if (type == SYNC_UNLINK_REQUEST)
@@ -487,6 +508,7 @@ RememberSyncRequest(const FileTag *ftag, SyncRequestType type)
487508
entry = palloc(sizeof(PendingUnlinkEntry));
488509
entry->tag = *ftag;
489510
entry->cycle_ctr = checkpoint_cycle_ctr;
511+
entry->canceled = false;
490512

491513
pendingUnlinks = lappend(pendingUnlinks, entry);
492514

src/include/nodes/pg_list.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@ extern List *list_delete_int(List *list, int datum);
560560
extern List *list_delete_oid(List *list, Oid datum);
561561
extern List *list_delete_first(List *list);
562562
extern List *list_delete_last(List *list);
563+
extern List *list_delete_first_n(List *list, int n);
563564
extern List *list_delete_nth_cell(List *list, int n);
564565
extern List *list_delete_cell(List *list, ListCell *cell);
565566

0 commit comments

Comments
 (0)