pg_walinspect: add functions to locate and list WAL by time and LSN

Started by Chao Li3 days ago1 messageshackers
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

appliessuccessCI history

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253828
psql -h localhost -U postgres

Built from patchset v1 (message #1), September 20, 2026 at 04:22 AM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t253828_1 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253828_1 && git checkout t253828_1

Patchset v1 (message #1) is on t253828_1

Jump to latest
#1Chao Li
li.evan.chao@gmail.com

Hi,

I'd like to propose adding two new SQL functions to pg_walinspect.

# Motivation

During HOW2026 in Jinan this April, a DBA talked to me that PG lacks an easy way to locate WAL files by time.

He described a scenario where somebody had done something wrong in a production database, for example accidentally deleting important data. The administrator knew that the incident had happened at around 1am the last night and wanted to confirm what had happened by analyzing WAL.

The problem was that, with thousands of WAL files in pg_wal, where should he start?

# Analysis

After looking into this use case, I think the underlying requirement is slightly different from simply finding a WAL file by timestamp.

Given an approximate point in time, the user really wants an LSN range that is likely to contain the WAL records associated with the operation of interest, for example a DELETE.

PostgreSQL already provides several related facilities:

* pg_ls_waldir() lists WAL files and their modification times.
* pg_split_walfile_name() extracts the segment number and timeline ID from a WAL file name.
* pg_walfile_name() maps an LSN to a WAL file name.
* pg_walinspect can inspect WAL records by LSN.
* pg_waldump can dump WAL records by WAL file or LSN range.
* There are also a number of third-party WAL-mining tools.

However, WAL file modification times don't necessarily reflect the chronological position of the WAL segment. For example, on my local cluster:
```
% ls -l
total 65536
-rw-------@ 1 chaol staff 16777216 Sep 17 16:21 000000010000000000000001
-rw-------@ 1 chaol staff 16777216 Sep 16 13:24 000000010000000000000002
drwx------@ 2 chaol staff 64 Sep 16 13:17 archive_status
drwx------@ 2 chaol staff 64 Sep 16 13:17 summaries
```

Here, segment 1 has a later modification time than segment 2. Since modification times do not necessarily follow WAL segment order, using them to locate the WAL corresponding to a particular point in time can be difficult and sometimes confusing.

So fundamentally, what is missing is an easy way to map a wall-clock time to a useful WAL/LSN range.

# Design

I propose adding two SQL functions.

## pg_get_wal_location_at_time(target_time timestamptz, before interval DEFAULT '1 minute', after interval DEFAULT '1 minute’)

Given a target time and optional before and after intervals, it returns a WAL range around that time.

Not all WAL records contain timestamps, so the implementation reuses the existing timestamp-extraction logic from xlogrecovery.c, exposed by this patch as GetXLogRecordTimestamp(), to identify timestamp-bearing WAL records.

## pg_get_wal_files(start_lsn pg_lsn, end_lsn pg_lsn DEFAULT NULL)

Given a start LSN and an optional end LSN, it returns the WAL files covering that LSN range.

Unlike pg_walfile_name(), which returns only a WAL file name for a single LSN, this function returns each relevant WAL file together with that segment's start and end LSNs.

# Implementation

Both functions are added to the pg_walinspect extension.

There is already quite a bit of reusable WAL-reading infrastructure there, and semantically it also seems like the natural place for this functionality.

The implementation builds on the existing pg_walinspect and XLog reader infrastructure. It does not introduce a new WAL format or a separate WAL parser.

# Demo

For example, suppose I deleted a tuple at around 16:11 yesterday. I can first locate the WAL range around that time:
```
evantest=# SELECT * FROM pg_get_wal_location_at_time('2026-09-17 16:11:08.317383+08', before=>interval '5 minute', after=>interval '5 minute');
start_timestamp | start_lsn | end_timestamp | end_lsn
------------------------------+------------+-----------------------------+------------
2026-09-17 15:57:44.67121+08 | 0/01D05FA0 | 2026-09-18 10:56:13.9463+08 | 0/01D28900
(1 row)
```

Here, you may notice that end_lsn is much later than the specified timestamp plus 5 minutes. I chose this example intentionally.

As mentioned above, not all WAL records carry a timestamp. In this demo database, no timestamp-bearing WAL records were generated for quite some time after the target period, so the next usable timestamp did not appear until today. As a result, the returned WAL range is much wider than the requested five-minute window.

This also illustrates why the function returns an approximate WAL range based on timestamp-bearing records rather than an exact time-to-LSN mapping.

Then I can inspect that LSN range:
```
evantest=# SELECT start_lsn, end_lsn, xid, resource_manager, record_type FROM pg_get_wal_records_info('0/01D05FA0', '0/01D28900') WHERE record_type in ('DELETE', 'COMMIT');
start_lsn | end_lsn | xid | resource_manager | record_type
------------+------------+-----+------------------+-------------
0/01D05FA0 | 0/01D05FC8 | 681 | Transaction | COMMIT
0/01D0B530 | 0/01D0B558 | 682 | Transaction | COMMIT
0/01D0B590 | 0/01D0B5C8 | 683 | Heap | DELETE
0/01D0B5C8 | 0/01D0B5F0 | 683 | Transaction | COMMIT
(4 rows)
```

Now we know the LSN range for the DELETE. If we want to determine which WAL file contains it:
```
evantest=# SELECT * FROM pg_get_wal_files('0/01D0B590', '0/01D0B5C8');
wal_file | segment_start_lsn | segment_end_lsn
--------------------------+-------------------+-----------------
000000010000000000000001 | 0/01000000 | 0/02000000
(1 row)
```

From there, the WAL file can be examined further with pg_waldump or other WAL-mining tools.

PFA v1. Reviews and comments and suggestions are greatly appreciated.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

Attachments:

t253828_1
v1-0001-pg_walinspect-add-functions-to-locate-and-list-WA.patchapplication/octet-stream; name=v1-0001-pg_walinspect-add-functions-to-locate-and-list-WA.patch; x-unix-mode=0644Download+1147-18