Skip to content

Commit cf7506a

Browse files
Konstantin KnizhnikMatthias van de Meent
authored andcommitted
Add --load-records option to pg_waldump
This allows parsing of the .walredo files generated by Neon's PageServer whenever its WALRedo process crashes, improving the tooling available when failures happen.
1 parent e2b9b66 commit cf7506a

File tree

1 file changed

+122
-4
lines changed

1 file changed

+122
-4
lines changed

src/bin/pg_waldump/pg_waldump.c

Lines changed: 122 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ static volatile sig_atomic_t time_to_stop = false;
4848
static const RelFileLocator emptyRelFileLocator = {0, 0, 0};
4949

5050
static FILE* save_records_file;
51+
static FILE* load_records_file;
5152

5253
typedef struct XLogDumpPrivate
5354
{
@@ -87,6 +88,7 @@ typedef struct XLogDumpConfig
8788
/* save options */
8889
char *save_fullpage_path;
8990
char *save_records_file_path;
91+
char *load_records_file_path;
9092
} XLogDumpConfig;
9193

9294

@@ -871,24 +873,45 @@ usage(void)
871873
" (optionally, show per-record statistics)\n"));
872874
printf(_(" --save-fullpage=DIR save full page images to DIR\n"));
873875
printf(_(" --save-records=FILE save selected WAL records to the file\n"));
876+
printf(_(" --load-records=FILE load saved WAL records from the file\n"));
874877
printf(_(" -?, --help show this help, then exit\n"));
875878
printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
876879
printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
877880
}
878881

879882

883+
static int32
884+
read_pq_int32(FILE* f)
885+
{
886+
int32 val;
887+
if (fread(&val, sizeof(val), 1, f) != 1)
888+
pg_fatal("could not read from file: %m");
889+
return pg_hton32(val);
890+
}
891+
892+
static int64
893+
read_pq_int64(FILE* f)
894+
{
895+
int64 val;
896+
if (fread(&val, sizeof(val), 1, f) != 1)
897+
pg_fatal("could not read from file: %m");
898+
return pg_hton64(val);
899+
}
900+
880901
static void
881902
write_pq_int32(FILE* f, int32 val)
882903
{
883904
val = pg_hton32(val);
884-
fwrite(&val, sizeof(val), 1, f);
905+
if (fwrite(&val, sizeof(val), 1, f) != 1)
906+
pg_fatal("could not write to file: %m");
885907
}
886908

887909
static void
888910
write_pq_int64(FILE* f, int64 val)
889911
{
890912
val = pg_hton64(val);
891-
fwrite(&val, sizeof(val), 1, f);
913+
if (fwrite(&val, sizeof(val), 1, f) != 1)
914+
pg_fatal("could not write to file: %m");
892915
}
893916

894917
static void
@@ -939,6 +962,7 @@ main(int argc, char **argv)
939962
{"stats", optional_argument, NULL, 'z'},
940963
{"save-fullpage", required_argument, NULL, 1},
941964
{"save-records", required_argument, NULL, 2},
965+
{"load-records", required_argument, NULL, 3},
942966
{NULL, 0, NULL, 0}
943967
};
944968

@@ -993,6 +1017,7 @@ main(int argc, char **argv)
9931017
config.filter_by_fpw = false;
9941018
config.save_fullpage_path = NULL;
9951019
config.save_records_file_path = NULL;
1020+
config.load_records_file_path = NULL;
9961021
config.stats = false;
9971022
config.stats_per_record = false;
9981023
config.ignore_format_errors = false;
@@ -1214,6 +1239,9 @@ main(int argc, char **argv)
12141239
case 2:
12151240
config.save_records_file_path = pg_strdup(optarg);
12161241
break;
1242+
case 3:
1243+
config.load_records_file_path = pg_strdup(optarg);
1244+
break;
12171245
default:
12181246
goto bad_argument;
12191247
}
@@ -1317,6 +1345,9 @@ main(int argc, char **argv)
13171345
if (config.save_records_file_path)
13181346
save_records_file = fopen(config.save_records_file_path, "wb");
13191347

1348+
if (config.load_records_file_path)
1349+
load_records_file = fopen(config.load_records_file_path, "rb");
1350+
13201351
/* parse files as start/end boundaries, extract path if not specified */
13211352
if (optind < argc)
13221353
{
@@ -1407,7 +1438,7 @@ main(int argc, char **argv)
14071438
waldir = identify_target_directory(waldir, NULL, config.ignore_format_errors);
14081439

14091440
/* we don't know what to print */
1410-
if (XLogRecPtrIsInvalid(private.startptr) && !single_file)
1441+
if (XLogRecPtrIsInvalid(private.startptr) && !single_file && !load_records_file)
14111442
{
14121443
pg_log_error("no start WAL location given");
14131444
goto bad_argument;
@@ -1425,6 +1456,93 @@ main(int argc, char **argv)
14251456
if (!xlogreader_state)
14261457
pg_fatal("out of memory while allocating a WAL reading processor");
14271458

1459+
if (load_records_file)
1460+
{
1461+
int action;
1462+
while ((action = fgetc(load_records_file)) != EOF)
1463+
{
1464+
int len = read_pq_int32(load_records_file);
1465+
switch (action)
1466+
{
1467+
case 'B': /* base block */
1468+
{
1469+
int forkNum = fgetc(load_records_file);
1470+
int spcNode = read_pq_int32(load_records_file);
1471+
int dbNode = read_pq_int32(load_records_file);
1472+
int relNode = read_pq_int32(load_records_file);
1473+
int blkNum = read_pq_int32(load_records_file);
1474+
printf("Start redo of %u/%u/%u.%u block %u\n", spcNode, dbNode, relNode, forkNum, blkNum);
1475+
continue;
1476+
}
1477+
case 'A': /* apply record */
1478+
{
1479+
XLogRecPtr lsn = read_pq_int64(load_records_file);
1480+
DecodedXLogRecord* decoded;
1481+
1482+
record = (XLogRecord*)malloc(len - 12);
1483+
if (fread(record, len-12, 1, load_records_file) != 1)
1484+
pg_fatal("could not load applied record: %m");
1485+
1486+
XLogBeginRead(xlogreader_state, lsn);
1487+
decoded = malloc(DecodeXLogRecordRequiredSpace(record->xl_tot_len));
1488+
if (!DecodeXLogRecord(xlogreader_state, decoded, record, lsn, &errormsg))
1489+
pg_fatal("failed to decode WAL record: %s", errormsg);
1490+
1491+
/* Record the location of the next record. */
1492+
decoded->next_lsn = xlogreader_state->NextRecPtr;
1493+
1494+
/*
1495+
* Update the pointers to the beginning and one-past-the-end of this
1496+
* record, again for the benefit of historical code that expected the
1497+
* decoder to track this rather than accessing these fields of the record
1498+
* itself.
1499+
*/
1500+
xlogreader_state->record = decoded;
1501+
xlogreader_state->ReadRecPtr = decoded->lsn;
1502+
xlogreader_state->EndRecPtr = decoded->next_lsn;
1503+
1504+
XLogDumpDisplayRecord(&config, xlogreader_state);
1505+
1506+
free(record);
1507+
free(decoded);
1508+
continue;
1509+
}
1510+
case 'P': /* push page */
1511+
{
1512+
int forkNum = fgetc(load_records_file);
1513+
int spcNode = read_pq_int32(load_records_file);
1514+
int dbNode = read_pq_int32(load_records_file);
1515+
int relNode = read_pq_int32(load_records_file);
1516+
int blkNum = read_pq_int32(load_records_file);
1517+
printf("Base image %u/%u/%u.%u block %u\n", spcNode, dbNode, relNode, forkNum, blkNum);
1518+
if (config.save_fullpage_path)
1519+
{
1520+
char filename[MAXPGPATH];
1521+
char page[BLCKSZ];
1522+
FILE* file;
1523+
snprintf(filename, MAXPGPATH, "%s/base_image_%u.%u.%u.%u%s", config.save_fullpage_path,
1524+
spcNode, dbNode, relNode, blkNum, forkNames[forkNum]);
1525+
file = fopen(filename, PG_BINARY_W);
1526+
if (!file)
1527+
pg_fatal("could not open file \"%s\": %m", filename);
1528+
1529+
if (fread(page, BLCKSZ, 1, load_records_file) != 1 ||
1530+
fwrite(page, BLCKSZ, 1, file) != 1)
1531+
pg_fatal("could not save image in file \"%s\": %m", filename);
1532+
fclose(file);
1533+
}
1534+
continue;
1535+
}
1536+
case 'G': /* get page */
1537+
break;
1538+
default:
1539+
pg_fatal("Unexpected action: %d", action);
1540+
}
1541+
break;
1542+
}
1543+
goto success;
1544+
}
1545+
14281546
if (save_records_file)
14291547
{
14301548
/*
@@ -1592,7 +1710,7 @@ main(int argc, char **argv)
15921710
pg_fatal("error in WAL record at %X/%X: %s",
15931711
LSN_FORMAT_ARGS(xlogreader_state->ReadRecPtr),
15941712
errormsg);
1595-
1713+
success:
15961714
XLogReaderFree(xlogreader_state);
15971715

15981716
return EXIT_SUCCESS;

0 commit comments

Comments
 (0)