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

Commit a1bd2a8

Browse files
nathan-bossartCommitfest Bot
authored and
Commitfest Bot
committed
pg_dump: Retrieve attribute statistics in batches.
Currently, pg_dump gathers attribute statistics with a query per relation, which can cause pg_dump to take significantly longer, especially when there are many tables. This commit address this by teaching pg_dump to gather attribute statistics for 64 relations at a time. Some simple tests showed this was the optimal batch size, but performance may vary depending on the workload. While this change increases pg_dump's memory usage a bit, it isn't expected to be too egregious and seems well worth the trade-off. Our lookahead code determines the next batch of relations by searching the TOC sequentially for relevant entries. This approach assumes that we will dump all such entries in TOC order, which unfortunately isn't true for dump formats that use RestoreArchive(). RestoreArchive() does multiple passes through the TOC and selectively dumps certain groups of entries each time. This is particularly problematic for index stats and a subset of matview stats; both are in SECTION_POST_DATA, but matview stats that depend on matview data are dumped in RESTORE_PASS_POST_ACL, while all other stats data entries are dumped in RESTORE_PASS_MAIN. To handle this, this commit moves all statistics data entries in SECTION_POST_DATA to RESTORE_PASS_POST_ACL, which ensures that we always dump them in TOC order. A convenient side effect of this change is that we can revert a decent chunk of commit a0a4601, but that is left for a follow-up commit. Author: Corey Huinker <corey.huinker@gmail.com> Co-authored-by: Nathan Bossart <nathandbossart@gmail.com> Reviewed-by: Jeff Davis <pgsql@j-davis.com> Discussion: https://postgr.es/m/CADkLM%3Dc%2Br05srPy9w%2B-%2BnbmLEo15dKXYQ03Q_xyK%2BriJerigLQ%40mail.gmail.com
1 parent 606e543 commit a1bd2a8

File tree

3 files changed

+145
-38
lines changed

3 files changed

+145
-38
lines changed

src/bin/pg_dump/pg_backup.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,10 @@ typedef int DumpId;
285285
* Function pointer prototypes for assorted callback methods.
286286
*/
287287

288-
typedef char *(*DefnDumperPtr) (Archive *AH, const void *userArg);
288+
/* forward declaration to avoid including pg_backup_archiver.h here */
289+
typedef struct _tocEntry TocEntry;
290+
291+
typedef char *(*DefnDumperPtr) (Archive *AH, const void *userArg, const TocEntry *te);
289292
typedef int (*DataDumperPtr) (Archive *AH, const void *userArg);
290293

291294
typedef void (*SetupWorkerPtrType) (Archive *AH);

src/bin/pg_dump/pg_backup_archiver.c

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2655,7 +2655,7 @@ WriteToc(ArchiveHandle *AH)
26552655
}
26562656
else if (te->defnDumper)
26572657
{
2658-
char *defn = te->defnDumper((Archive *) AH, te->defnDumperArg);
2658+
char *defn = te->defnDumper((Archive *) AH, te->defnDumperArg, te);
26592659

26602660
te->defnLen = WriteStr(AH, defn);
26612661
pg_free(defn);
@@ -3284,23 +3284,17 @@ _tocEntryRestorePass(ArchiveHandle *AH, TocEntry *te)
32843284

32853285
/*
32863286
* If statistics data is dependent on materialized view data, it must be
3287-
* deferred to RESTORE_PASS_POST_ACL.
3287+
* deferred to RESTORE_PASS_POST_ACL. Those entries are already marked
3288+
* with SECTION_POST_DATA, and some other stats entries (e.g., index
3289+
* stats) will also be marked SECTION_POST_DATA. Additionally, our
3290+
* lookahead code in fetchAttributeStats() assumes that we dump all
3291+
* statistics data entries in TOC order. To ensure this assumption holds,
3292+
* we move all statistics data entries in SECTION_POST_DATA to
3293+
* RESTORE_PASS_POST_ACL.
32883294
*/
3289-
if (strcmp(te->desc, "STATISTICS DATA") == 0)
3290-
{
3291-
for (int i = 0; i < te->nDeps; i++)
3292-
{
3293-
DumpId depid = te->dependencies[i];
3294-
3295-
if (depid <= AH->maxDumpId && AH->tocsByDumpId[depid] != NULL)
3296-
{
3297-
TocEntry *otherte = AH->tocsByDumpId[depid];
3298-
3299-
if (strcmp(otherte->desc, "MATERIALIZED VIEW DATA") == 0)
3300-
return RESTORE_PASS_POST_ACL;
3301-
}
3302-
}
3303-
}
3295+
if (strcmp(te->desc, "STATISTICS DATA") == 0 &&
3296+
te->section == SECTION_POST_DATA)
3297+
return RESTORE_PASS_POST_ACL;
33043298

33053299
/* All else can be handled in the main pass. */
33063300
return RESTORE_PASS_MAIN;
@@ -3952,7 +3946,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, const char *pfx)
39523946
}
39533947
else if (te->defnDumper)
39543948
{
3955-
char *defn = te->defnDumper((Archive *) AH, te->defnDumperArg);
3949+
char *defn = te->defnDumper((Archive *) AH, te->defnDumperArg, te);
39563950

39573951
te->defnLen = ahprintf(AH, "%s\n\n", defn);
39583952
pg_free(defn);

src/bin/pg_dump/pg_dump.c

Lines changed: 129 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ static int nbinaryUpgradeClassOids = 0;
209209
static SequenceItem *sequences = NULL;
210210
static int nsequences = 0;
211211

212+
/* Maximum number of relations to fetch in a fetchAttributeStats() call. */
213+
#define MAX_ATTR_STATS_RELS 64
214+
212215
/*
213216
* The default number of rows per INSERT when
214217
* --inserts is specified without --rows-per-insert
@@ -10553,6 +10556,79 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
1055310556
appendPQExpBuffer(out, "::%s", argtype);
1055410557
}
1055510558

10559+
/*
10560+
* fetchAttributeStats --
10561+
*
10562+
* Fetch next batch of rows for getAttributeStats().
10563+
*/
10564+
static PGresult *
10565+
fetchAttributeStats(Archive *fout)
10566+
{
10567+
ArchiveHandle *AH = (ArchiveHandle *) fout;
10568+
PQExpBuffer nspnames = createPQExpBuffer();
10569+
PQExpBuffer relnames = createPQExpBuffer();
10570+
int count = 0;
10571+
PGresult *res = NULL;
10572+
static TocEntry *te;
10573+
static bool restarted;
10574+
10575+
/* If we're just starting, set our TOC pointer. */
10576+
if (!te)
10577+
te = AH->toc->next;
10578+
10579+
/*
10580+
* We can't easily avoid a second TOC scan for the tar format because it
10581+
* writes restore.sql separately, which means we must execute the queries
10582+
* twice. This feels risky, but there is no known reason it should
10583+
* generate different output than the first pass. Even if it does, the
10584+
* worst-case scenario is that restore.sql might have different statistics
10585+
* data than the archive.
10586+
*/
10587+
if (!restarted && te == AH->toc && AH->format == archTar)
10588+
{
10589+
te = AH->toc->next;
10590+
restarted = true;
10591+
}
10592+
10593+
/*
10594+
* Scan the TOC for the next set of relevant stats entries. We assume
10595+
* that statistics are dumped in the order they are listed in the TOC.
10596+
* This is perhaps not the sturdiest assumption, so we verify it matches
10597+
* reality in dumpRelationStats_dumper().
10598+
*/
10599+
for (; te != AH->toc && count < MAX_ATTR_STATS_RELS; te = te->next)
10600+
{
10601+
if ((te->reqs & REQ_STATS) != 0 &&
10602+
strcmp(te->desc, "STATISTICS DATA") == 0)
10603+
{
10604+
RelStatsInfo *rsinfo = (RelStatsInfo *) te->defnDumperArg;
10605+
10606+
appendPQExpBuffer(nspnames, "%s%s", count ? "," : "",
10607+
fmtId(rsinfo->dobj.namespace->dobj.name));
10608+
appendPQExpBuffer(relnames, "%s%s", count ? "," : "",
10609+
fmtId(rsinfo->dobj.name));
10610+
count++;
10611+
}
10612+
}
10613+
10614+
/* Execute the query for the next batch of relations. */
10615+
if (count > 0)
10616+
{
10617+
PQExpBuffer query = createPQExpBuffer();
10618+
10619+
appendPQExpBuffer(query, "EXECUTE getAttributeStats("
10620+
"'{%s}'::pg_catalog.name[],"
10621+
"'{%s}'::pg_catalog.name[])",
10622+
nspnames->data, relnames->data);
10623+
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
10624+
destroyPQExpBuffer(query);
10625+
}
10626+
10627+
destroyPQExpBuffer(nspnames);
10628+
destroyPQExpBuffer(relnames);
10629+
return res;
10630+
}
10631+
1055610632
/*
1055710633
* dumpRelationStats_dumper --
1055810634
*
@@ -10561,14 +10637,17 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
1056110637
* dumped.
1056210638
*/
1056310639
static char *
10564-
dumpRelationStats_dumper(Archive *fout, const void *userArg)
10640+
dumpRelationStats_dumper(Archive *fout, const void *userArg, const TocEntry *te)
1056510641
{
1056610642
const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
1056710643
const DumpableObject *dobj = &rsinfo->dobj;
10568-
PGresult *res;
10644+
static PGresult *res;
10645+
static int rownum;
1056910646
PQExpBuffer query;
1057010647
PQExpBufferData out_data;
1057110648
PQExpBuffer out = &out_data;
10649+
int i_schemaname;
10650+
int i_tablename;
1057210651
int i_attname;
1057310652
int i_inherited;
1057410653
int i_null_frac;
@@ -10584,13 +10663,31 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg)
1058410663
int i_range_length_histogram;
1058510664
int i_range_empty_frac;
1058610665
int i_range_bounds_histogram;
10666+
static TocEntry *expected_te;
10667+
10668+
/*
10669+
* fetchAttributeStats() assumes that the statistics are dumped in the
10670+
* order they are listed in the TOC. We verify that here for safety.
10671+
*/
10672+
if (!expected_te)
10673+
expected_te = ((ArchiveHandle *) fout)->toc;
10674+
10675+
expected_te = expected_te->next;
10676+
while ((expected_te->reqs & REQ_STATS) == 0 ||
10677+
strcmp(expected_te->desc, "STATISTICS DATA") != 0)
10678+
expected_te = expected_te->next;
10679+
10680+
if (te != expected_te)
10681+
pg_fatal("stats dumped out of order (current: %d %s %s) (expected: %d %s %s)",
10682+
te->dumpId, te->desc, te->tag,
10683+
expected_te->dumpId, expected_te->desc, expected_te->tag);
1058710684

1058810685
query = createPQExpBuffer();
1058910686
if (!fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS])
1059010687
{
1059110688
appendPQExpBufferStr(query,
10592-
"PREPARE getAttributeStats(pg_catalog.name, pg_catalog.name) AS\n"
10593-
"SELECT s.attname, s.inherited, "
10689+
"PREPARE getAttributeStats(pg_catalog.name[], pg_catalog.name[]) AS\n"
10690+
"SELECT s.schemaname, s.tablename, s.attname, s.inherited, "
1059410691
"s.null_frac, s.avg_width, s.n_distinct, "
1059510692
"s.most_common_vals, s.most_common_freqs, "
1059610693
"s.histogram_bounds, s.correlation, "
@@ -10608,11 +10705,21 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg)
1060810705
"NULL AS range_empty_frac,"
1060910706
"NULL AS range_bounds_histogram ");
1061010707

10708+
/*
10709+
* The results must be in the order of the relations supplied in the
10710+
* parameters to ensure we remain in sync as we walk through the TOC.
10711+
* The redundant filter clause on s.tablename = ANY(...) seems
10712+
* sufficient to convince the planner to use
10713+
* pg_class_relname_nsp_index, which avoids a full scan of pg_stats.
10714+
* This may not work for all versions.
10715+
*/
1061110716
appendPQExpBufferStr(query,
1061210717
"FROM pg_catalog.pg_stats s "
10613-
"WHERE s.schemaname = $1 "
10614-
"AND s.tablename = $2 "
10615-
"ORDER BY s.attname, s.inherited");
10718+
"JOIN unnest($1, $2) WITH ORDINALITY AS u (schemaname, tablename, ord) "
10719+
"ON s.schemaname = u.schemaname "
10720+
"AND s.tablename = u.tablename "
10721+
"WHERE s.tablename = ANY($2) "
10722+
"ORDER BY u.ord, s.attname, s.inherited");
1061610723

1061710724
ExecuteSqlStatement(fout, query->data);
1061810725

@@ -10642,16 +10749,16 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg)
1064210749

1064310750
appendPQExpBufferStr(out, "\n);\n");
1064410751

10752+
/* Fetch the next batch of attribute statistics if needed. */
10753+
if (rownum >= PQntuples(res))
10754+
{
10755+
PQclear(res);
10756+
res = fetchAttributeStats(fout);
10757+
rownum = 0;
10758+
}
1064510759

10646-
/* fetch attribute stats */
10647-
appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
10648-
appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
10649-
appendPQExpBufferStr(query, ", ");
10650-
appendStringLiteralAH(query, dobj->name, fout);
10651-
appendPQExpBufferStr(query, ");");
10652-
10653-
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
10654-
10760+
i_schemaname = PQfnumber(res, "schemaname");
10761+
i_tablename = PQfnumber(res, "tablename");
1065510762
i_attname = PQfnumber(res, "attname");
1065610763
i_inherited = PQfnumber(res, "inherited");
1065710764
i_null_frac = PQfnumber(res, "null_frac");
@@ -10669,10 +10776,15 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg)
1066910776
i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
1067010777

1067110778
/* restore attribute stats */
10672-
for (int rownum = 0; rownum < PQntuples(res); rownum++)
10779+
for (; rownum < PQntuples(res); rownum++)
1067310780
{
1067410781
const char *attname;
1067510782

10783+
/* Stop if the next stat row in our cache isn't for this relation. */
10784+
if (strcmp(dobj->name, PQgetvalue(res, rownum, i_tablename)) != 0 ||
10785+
strcmp(dobj->namespace->dobj.name, PQgetvalue(res, rownum, i_schemaname)) != 0)
10786+
break;
10787+
1067610788
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
1067710789
appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
1067810790
fout->remoteVersion);
@@ -10762,8 +10874,6 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg)
1076210874
appendPQExpBufferStr(out, "\n);\n");
1076310875
}
1076410876

10765-
PQclear(res);
10766-
1076710877
destroyPQExpBuffer(query);
1076810878
return out->data;
1076910879
}

0 commit comments

Comments
 (0)