Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 663fc32

Browse files
committed
Eliminate O(N^2) behavior in parallel restore with many blobs.
With hundreds of thousands of TOC entries, the repeated searches in reduce_dependencies() become the dominant cost. Get rid of that searching by constructing reverse-dependency lists, which we can do in O(N) time during the fix_dependencies() preprocessing. I chose to store the reverse dependencies as DumpId arrays for consistency with the forward-dependency representation, and keep the previously-transient tocsByDumpId[] array around to locate actual TOC entry structs quickly from dump IDs. While this fixes the slow case reported by Vlad Arkhipov, there is still a potential for O(N^2) behavior with sufficiently many tables: fix_dependencies itself, as well as mark_create_done and inhibit_data_for_failed_table, are doing repeated searches to deal with table-to-table-data dependencies. Possibly this work could be extended to deal with that, although the latter two functions are also used in non-parallel restore where we currently don't run fix_dependencies. Another TODO is that we fail to parallelize restore of multiple blobs at all. This appears to require changes in the archive format to fix. Back-patch to 9.0 where the problem was reported. 8.4 has potential issues as well; but since it doesn't create a separate TOC entry for each blob, it's at much less risk of having enough TOC entries to cause real problems.
1 parent 9975c68 commit 663fc32

File tree

2 files changed

+76
-42
lines changed

2 files changed

+76
-42
lines changed

src/bin/pg_dump/pg_backup_archiver.c

Lines changed: 74 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ const char *progname;
7979

8080
static const char *modulename = gettext_noop("archiver");
8181

82+
/* index array created by fix_dependencies -- only used in parallel restore */
83+
static TocEntry **tocsByDumpId; /* index by dumpId - 1 */
84+
static DumpId maxDumpId; /* length of above array */
85+
8286

8387
static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
8488
const int compression, ArchiveMode mode);
@@ -134,9 +138,7 @@ static void fix_dependencies(ArchiveHandle *AH);
134138
static bool has_lock_conflicts(TocEntry *te1, TocEntry *te2);
135139
static void repoint_table_dependencies(ArchiveHandle *AH,
136140
DumpId tableId, DumpId tableDataId);
137-
static void identify_locking_dependencies(TocEntry *te,
138-
TocEntry **tocsByDumpId,
139-
DumpId maxDumpId);
141+
static void identify_locking_dependencies(TocEntry *te);
140142
static void reduce_dependencies(ArchiveHandle *AH, TocEntry *te,
141143
TocEntry *ready_list);
142144
static void mark_create_done(ArchiveHandle *AH, TocEntry *te);
@@ -3743,7 +3745,12 @@ mark_work_done(ArchiveHandle *AH, TocEntry *ready_list,
37433745
/*
37443746
* Process the dependency information into a form useful for parallel restore.
37453747
*
3746-
* We set up depCount fields that are the number of as-yet-unprocessed
3748+
* This function takes care of fixing up some missing or badly designed
3749+
* dependencies, and then prepares subsidiary data structures that will be
3750+
* used in the main parallel-restore logic, including:
3751+
* 1. We build the tocsByDumpId[] index array.
3752+
* 2. We build the revDeps[] arrays of incoming dependency dumpIds.
3753+
* 3. We set up depCount fields that are the number of as-yet-unprocessed
37473754
* dependencies for each TOC entry.
37483755
*
37493756
* We also identify locking dependencies so that we can avoid trying to
@@ -3752,29 +3759,29 @@ mark_work_done(ArchiveHandle *AH, TocEntry *ready_list,
37523759
static void
37533760
fix_dependencies(ArchiveHandle *AH)
37543761
{
3755-
TocEntry **tocsByDumpId;
37563762
TocEntry *te;
3757-
DumpId maxDumpId;
37583763
int i;
37593764

37603765
/*
3761-
* For some of the steps here, it is convenient to have an array that
3762-
* indexes the TOC entries by dump ID, rather than searching the TOC list
3763-
* repeatedly. Entries for dump IDs not present in the TOC will be NULL.
3766+
* It is convenient to have an array that indexes the TOC entries by dump
3767+
* ID, rather than searching the TOC list repeatedly. Entries for dump
3768+
* IDs not present in the TOC will be NULL.
37643769
*
37653770
* NOTE: because maxDumpId is just the highest dump ID defined in the
37663771
* archive, there might be dependencies for IDs > maxDumpId. All uses of
37673772
* this array must guard against out-of-range dependency numbers.
37683773
*
3769-
* Also, initialize the depCount fields, and make sure all the TOC items
3770-
* are marked as not being in any parallel-processing list.
3774+
* Also, initialize the depCount/revDeps/nRevDeps fields, and make sure
3775+
* the TOC items are marked as not being in any parallel-processing list.
37713776
*/
37723777
maxDumpId = AH->maxDumpId;
37733778
tocsByDumpId = (TocEntry **) calloc(maxDumpId, sizeof(TocEntry *));
37743779
for (te = AH->toc->next; te != AH->toc; te = te->next)
37753780
{
37763781
tocsByDumpId[te->dumpId - 1] = te;
37773782
te->depCount = te->nDeps;
3783+
te->revDeps = NULL;
3784+
te->nRevDeps = 0;
37783785
te->par_prev = NULL;
37793786
te->par_next = NULL;
37803787
}
@@ -3792,6 +3799,9 @@ fix_dependencies(ArchiveHandle *AH)
37923799
* TABLE, if possible. However, if the dependency isn't in the archive
37933800
* then just assume it was a TABLE; this is to cover cases where the table
37943801
* was suppressed but we have the data and some dependent post-data items.
3802+
*
3803+
* XXX this is O(N^2) if there are a lot of tables. We ought to fix
3804+
* pg_dump to produce correctly-linked dependencies in the first place.
37953805
*/
37963806
for (te = AH->toc->next; te != AH->toc; te = te->next)
37973807
{
@@ -3838,31 +3848,67 @@ fix_dependencies(ArchiveHandle *AH)
38383848
}
38393849

38403850
/*
3841-
* It is possible that the dependencies list items that are not in the
3842-
* archive at all. Subtract such items from the depCounts.
3851+
* At this point we start to build the revDeps reverse-dependency arrays,
3852+
* so all changes of dependencies must be complete.
3853+
*/
3854+
3855+
/*
3856+
* Count the incoming dependencies for each item. Also, it is possible
3857+
* that the dependencies list items that are not in the archive at
3858+
* all. Subtract such items from the depCounts.
38433859
*/
38443860
for (te = AH->toc->next; te != AH->toc; te = te->next)
38453861
{
38463862
for (i = 0; i < te->nDeps; i++)
38473863
{
38483864
DumpId depid = te->dependencies[i];
38493865

3850-
if (depid > maxDumpId || tocsByDumpId[depid - 1] == NULL)
3866+
if (depid <= maxDumpId && tocsByDumpId[depid - 1] != NULL)
3867+
tocsByDumpId[depid - 1]->nRevDeps++;
3868+
else
38513869
te->depCount--;
38523870
}
38533871
}
38543872

3873+
/*
3874+
* Allocate space for revDeps[] arrays, and reset nRevDeps so we can
3875+
* use it as a counter below.
3876+
*/
3877+
for (te = AH->toc->next; te != AH->toc; te = te->next)
3878+
{
3879+
if (te->nRevDeps > 0)
3880+
te->revDeps = (DumpId *) malloc(te->nRevDeps * sizeof(DumpId));
3881+
te->nRevDeps = 0;
3882+
}
3883+
3884+
/*
3885+
* Build the revDeps[] arrays of incoming-dependency dumpIds. This
3886+
* had better agree with the loops above.
3887+
*/
3888+
for (te = AH->toc->next; te != AH->toc; te = te->next)
3889+
{
3890+
for (i = 0; i < te->nDeps; i++)
3891+
{
3892+
DumpId depid = te->dependencies[i];
3893+
3894+
if (depid <= maxDumpId && tocsByDumpId[depid - 1] != NULL)
3895+
{
3896+
TocEntry *otherte = tocsByDumpId[depid - 1];
3897+
3898+
otherte->revDeps[otherte->nRevDeps++] = te->dumpId;
3899+
}
3900+
}
3901+
}
3902+
38553903
/*
38563904
* Lastly, work out the locking dependencies.
38573905
*/
38583906
for (te = AH->toc->next; te != AH->toc; te = te->next)
38593907
{
38603908
te->lockDeps = NULL;
38613909
te->nLockDeps = 0;
3862-
identify_locking_dependencies(te, tocsByDumpId, maxDumpId);
3910+
identify_locking_dependencies(te);
38633911
}
3864-
3865-
free(tocsByDumpId);
38663912
}
38673913

38683914
/*
@@ -3896,13 +3942,9 @@ repoint_table_dependencies(ArchiveHandle *AH,
38963942
* Identify which objects we'll need exclusive lock on in order to restore
38973943
* the given TOC entry (*other* than the one identified by the TOC entry
38983944
* itself). Record their dump IDs in the entry's lockDeps[] array.
3899-
* tocsByDumpId[] is a convenience array (of size maxDumpId) to avoid
3900-
* searching the TOC for each dependency.
39013945
*/
39023946
static void
3903-
identify_locking_dependencies(TocEntry *te,
3904-
TocEntry **tocsByDumpId,
3905-
DumpId maxDumpId)
3947+
identify_locking_dependencies(TocEntry *te)
39063948
{
39073949
DumpId *lockids;
39083950
int nlockids;
@@ -3956,31 +3998,21 @@ identify_locking_dependencies(TocEntry *te,
39563998
static void
39573999
reduce_dependencies(ArchiveHandle *AH, TocEntry *te, TocEntry *ready_list)
39584000
{
3959-
DumpId target = te->dumpId;
39604001
int i;
39614002

3962-
ahlog(AH, 2, "reducing dependencies for %d\n", target);
4003+
ahlog(AH, 2, "reducing dependencies for %d\n", te->dumpId);
39634004

3964-
/*
3965-
* We must examine all entries, not only the ones after the target item,
3966-
* because if the user used a -L switch then the original dependency-
3967-
* respecting order has been destroyed by SortTocFromFile.
3968-
*/
3969-
for (te = AH->toc->next; te != AH->toc; te = te->next)
4005+
for (i = 0; i < te->nRevDeps; i++)
39704006
{
3971-
for (i = 0; i < te->nDeps; i++)
4007+
TocEntry *otherte = tocsByDumpId[te->revDeps[i] - 1];
4008+
4009+
otherte->depCount--;
4010+
if (otherte->depCount == 0 && otherte->par_prev != NULL)
39724011
{
3973-
if (te->dependencies[i] == target)
3974-
{
3975-
te->depCount--;
3976-
if (te->depCount == 0 && te->par_prev != NULL)
3977-
{
3978-
/* It must be in the pending list, so remove it ... */
3979-
par_list_remove(te);
3980-
/* ... and add to ready_list */
3981-
par_list_append(ready_list, te);
3982-
}
3983-
}
4012+
/* It must be in the pending list, so remove it ... */
4013+
par_list_remove(otherte);
4014+
/* ... and add to ready_list */
4015+
par_list_append(ready_list, otherte);
39844016
}
39854017
}
39864018
}

src/bin/pg_dump/pg_backup_archiver.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,8 @@ typedef struct _tocEntry
326326
struct _tocEntry *par_next; /* these are NULL if not in either list */
327327
bool created; /* set for DATA member if TABLE was created */
328328
int depCount; /* number of dependencies not yet restored */
329+
DumpId *revDeps; /* dumpIds of objects depending on this one */
330+
int nRevDeps; /* number of such dependencies */
329331
DumpId *lockDeps; /* dumpIds of objects this one needs lock on */
330332
int nLockDeps; /* number of such dependencies */
331333
} TocEntry;

0 commit comments

Comments
 (0)