Thread
Commits
-
Avoid short seeks in pg_restore.
- fba60a1b107d 19 (unreleased) landed
-
Don't rely on zlib's gzgetc() macro.
- 277dec651472 19 (unreleased) cited
-
Add more TAP test coverage for pg_dump.
- 20ec9958921a 19 (unreleased) landed
-
Split 002_pg_dump.pl into two test files.
- 9dcf7f1172cd 19 (unreleased) landed
-
Align the data block sizes of pg_dump's various compression modes.
- 66ec01dc4124 19 (unreleased) landed
-
Fix serious performance problems in LZ4Stream_read_internal.
- 1f8062dd9668 19 (unreleased) landed
-
Fix poor buffering logic in pg_dump's lz4 and zstd compression code.
- fe8192a95e6c 19 (unreleased) landed
-
Fix issue with reading zero bytes in Gzip_read.
- bf18e9bd70de 17.7 landed
- a239c4a0c226 19 (unreleased) landed
- 6a4009747c36 18.1 landed
- 1518b7d76aad 16.11 landed
-
Restore test coverage of LZ4Stream_gets().
- eac2b1697d48 17.7 landed
- 661b320ed4e0 18.1 landed
- 26d1cd375f15 19 (unreleased) landed
-
[PATCH v1] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-03-29T00:46:30Z
Hello list, I'm submitting a patch for improving an almost 1h long pause at the start of parallel pg_restore of a big archive. Related discussion has taken place at pgsql-performance mailing list at: https://www.postgresql.org/message-id/flat/6bd16bdb-aa5e-0512-739d-b84100596035%40gmx.net I think I explain it rather well in the commit message, so I paste it inline: Improve the performance of parallel pg_restore (-j) from a custom format pg_dump archive that does not include data offsets - typically happening when pg_dump has generated it by writing to stdout instead of a file. In this case pg_restore workers manifest constant looping of reading small sizes (4KB) and seeking forward small lenths (around 10KB for a compressed archive): read(4, "..."..., 4096) = 4096 lseek(4, 55544369152, SEEK_SET) = 55544369152 read(4, "..."..., 4096) = 4096 lseek(4, 55544381440, SEEK_SET) = 55544381440 read(4, "..."..., 4096) = 4096 lseek(4, 55544397824, SEEK_SET) = 55544397824 read(4, "..."..., 4096) = 4096 lseek(4, 55544414208, SEEK_SET) = 55544414208 read(4, "..."..., 4096) = 4096 lseek(4, 55544426496, SEEK_SET) = 55544426496 This happens as each worker scans the whole file until it finds the entry it wants, skipping forward each block. In combination to the small block size of the custom format dump, this causes many seeks and low performance. Fix by avoiding forward seeks for jumps of less than 1MB forward. Do instead sequential reads. Performance gain can be significant, depending on the size of the dump and the I/O subsystem. On my local NVMe drive, read speeds for that phase of pg_restore increased from 150MB/s to 3GB/s. This is my first patch submission, all help is much appreciated. Regards, Dimitris P.S. What is the recommended way to test a change, besides a generic make check? And how do I run selectively only the pg_dump/restore tests, in order to speed up my development routine?
-
Re: [PATCH v1] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-04-01T19:33:32Z
On Sat, 29 Mar 2025, Dimitrios Apostolou wrote: > > P.S. What is the recommended way to test a change, besides a generic make > check? And how do I run selectively only the pg_dump/restore tests, in order > to speed up my development routine? I have tested it with: make -C src/bin/pg_dump check It didn't break any test, but I also don't see any difference, the performance boost is noticeable only when restoring a huge archive that is missing offsets. Any volunteer to review this one-line patch? Thanks, Dimitris
-
Re: [PATCH v1] parallel pg_restore: avoid disk seeks when jumping short distance forward
Nathan Bossart <nathandbossart@gmail.com> — 2025-04-01T20:04:00Z
On Tue, Apr 01, 2025 at 09:33:32PM +0200, Dimitrios Apostolou wrote: > It didn't break any test, but I also don't see any difference, the > performance boost is noticeable only when restoring a huge archive that is > missing offsets. This seems generally reasonable to me, but how did you decide on 1MB as the threshold? Have you tested other values? Could the best threshold vary based on the workload and hardware? -- nathan
-
Re: [PATCH v1] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-04-01T22:25:25Z
Thanks. This is the first value I tried and it works well. In the archive I have all blocks seem to be between 8 and 20KB so the jump forward before the change never even got close to 1MB. Could it be bigger in an uncompressed archive? Or in a future pg_dump that raises the block size? I don't really know, so it is difficult to test such scenario but it made sense to guard against these cases too. I chose 1MB by basically doing a very crude calculation in my mind: when would it be worth seeking forward instead of reading? On very slow drives 60MB/s sequential and 60 IOPS for random reads is a possible speed. In that worst case it would be better to seek() forward for lengths of over 1MB. On 1 April 2025 22:04:00 CEST, Nathan Bossart <nathandbossart@gmail.com> wrote: >On Tue, Apr 01, 2025 at 09:33:32PM +0200, Dimitrios Apostolou wrote: >> It didn't break any test, but I also don't see any difference, the >> performance boost is noticeable only when restoring a huge archive that is >> missing offsets. > >This seems generally reasonable to me, but how did you decide on 1MB as the >threshold? Have you tested other values? Could the best threshold vary >based on the workload and hardware? >
-
Re: [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-04-04T16:55:44Z
I just managed to run pgindent, here is v2 with the comment style fixed.
-
[PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-04-14T16:31:48Z
On Fri, 4 Apr 2025, Dimitrios Apostolou wrote: > I just managed to run pgindent, here is v2 with the comment style fixed. Any feedback on this one-liner? Or is the lack of feedback a clue that I have been missing something important in my patch submission? :-) Should I CC people that are frequent committers to the file? Thanks, Dimitris
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Tom Lane <tgl@sss.pgh.pa.us> — 2025-04-14T17:27:29Z
Dimitrios Apostolou <jimis@gmx.net> writes: > Any feedback on this one-liner? Or is the lack of feedback a clue that I > have been missing something important in my patch submission? :-) The calendar ;-). At this point we're in feature freeze for v18, so things that aren't bugs aren't likely to get much attention until v19 development opens up (in July, unless things are really going badly with v18). You should add your patch to the July commitfest [1] to make sure we don't lose track of it. regards, tom lane [1] https://commitfest.postgresql.org/53/
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-06-09T20:08:29Z
On Mon, 14 Apr 2025, Tom Lane wrote: > > You should add your patch to the July commitfest [1] to make sure > we don't lose track of it. I rebased the patch (attached) and created an entry in the commitfest: https://commitfest.postgresql.org/patch/5809/ Thanks! Dimitris
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-06-09T20:09:57Z
Attached now... On Mon, 9 Jun 2025, Dimitrios Apostolou wrote: > > > On Mon, 14 Apr 2025, Tom Lane wrote: > >> >> You should add your patch to the July commitfest [1] to make sure >> we don't lose track of it. > > I rebased the patch (attached) and created an entry in the commitfest: > > https://commitfest.postgresql.org/patch/5809/ > > > Thanks! > Dimitris > > >
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Nathan Bossart <nathandbossart@gmail.com> — 2025-06-10T21:47:59Z
On Mon, Jun 09, 2025 at 10:09:57PM +0200, Dimitrios Apostolou wrote: > Fix by avoiding forward seeks for jumps of less than 1MB forward. > Do instead sequential reads. > > Performance gain can be significant, depending on the size of the dump > and the I/O subsystem. On my local NVMe drive, read speeds for that > phase of pg_restore increased from 150MB/s to 3GB/s. I was curious about what exactly was leading to the performance gains you are seeing. This page has an explanation: https://www.mjr19.org.uk/IT/fseek.html I also wrote a couple of test programs to show the difference between fseeko-ing and fread-ing through a file with various sizes. On a Linux machine, I see this: log2(n) | fseeko | fread ---------+---------+------- 1 | 109.288 | 5.528 2 | 54.881 | 2.848 3 | 27.65 | 1.504 4 | 13.953 | 0.834 5 | 7.1 | 0.49 6 | 3.665 | 0.322 7 | 1.944 | 0.244 8 | 1.085 | 0.201 9 | 0.658 | 0.185 10 | 0.443 | 0.175 11 | 0.253 | 0.171 12 | 0.102 | 0.162 13 | 0.075 | 0.13 14 | 0.061 | 0.114 15 | 0.054 | 0.1 So, fseeko() starts winning around 4096 bytes. On macOS, the differences aren't quite as dramatic, but 4096 bytes is the break-even point there, too. I imagine there's a buffer around that size somewhere... This doesn't fully explain the results you are seeing, but it does seem to validate the idea. I'm curious if you see further improvement with even lower thresholds (e.g., 8KB, 16KB, 32KB). -- nathan -
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-06-10T22:32:58Z
On Tue, 10 Jun 2025, Nathan Bossart wrote: > I also wrote a couple of test programs to show the difference between > fseeko-ing and fread-ing through a file with various sizes. On a Linux > machine, I see this: > > log2(n) | fseeko | fread > ---------+---------+------- > 1 | 109.288 | 5.528 > 2 | 54.881 | 2.848 > 3 | 27.65 | 1.504 > 4 | 13.953 | 0.834 > 5 | 7.1 | 0.49 > 6 | 3.665 | 0.322 > 7 | 1.944 | 0.244 > 8 | 1.085 | 0.201 > 9 | 0.658 | 0.185 > 10 | 0.443 | 0.175 > 11 | 0.253 | 0.171 > 12 | 0.102 | 0.162 > 13 | 0.075 | 0.13 > 14 | 0.061 | 0.114 > 15 | 0.054 | 0.1 > > So, fseeko() starts winning around 4096 bytes. On macOS, the differences > aren't quite as dramatic, but 4096 bytes is the break-even point there, > too. I imagine there's a buffer around that size somewhere... Thank you for benchmarking! Before answering in more depth, I'm curious, what read-seek pattern do you see on the system call level (as shown by strace)? In pg_restore it was a constant loop of read(4K)-lseek(8-16K). Dimitris
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Nathan Bossart <nathandbossart@gmail.com> — 2025-06-11T21:14:53Z
On Wed, Jun 11, 2025 at 12:32:58AM +0200, Dimitrios Apostolou wrote: > Thank you for benchmarking! Before answering in more depth, I'm curious, > what read-seek pattern do you see on the system call level (as shown by > strace)? In pg_restore it was a constant loop of read(4K)-lseek(8-16K). For fseeko(), sizes less than 4096 produce a repeating pattern of read() calls followed by approximately (4096 / size) lseek() calls. For greater sizes, it's just a stream of lseek(). For fread(), sizes less than 4096 produce a stream of read(fd, "...", 4096), and for greater sizes, the only difference is that the last argument is the size. -- nathan
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-06-11T23:25:00Z
On Wed, 11 Jun 2025, Nathan Bossart wrote: > On Wed, Jun 11, 2025 at 12:32:58AM +0200, Dimitrios Apostolou wrote: >> what read-seek pattern do you see on the system call level (as shown by >> strace)? In pg_restore it was a constant loop of read(4K)-lseek(8-16K). > > For fseeko(), sizes less than 4096 produce a repeating pattern of read() > calls followed by approximately (4096 / size) lseek() calls. For greater > sizes, it's just a stream of lseek(). This is the opposite of what the link you shared before describes, so maybe glibc has changed its behaviour to improve performance. Anyway, the fact that fseek(>4096) produces a stream of lseek()s, means that most likely no I/O is happening. You need to issue a getc() after each fseeko(), like pg_restore is doing. Dimitris
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-06-12T23:00:26Z
On Tue, 10 Jun 2025, Nathan Bossart wrote: > So, fseeko() starts winning around 4096 bytes. On macOS, the differences > aren't quite as dramatic, but 4096 bytes is the break-even point there, > too. I imagine there's a buffer around that size somewhere... > > This doesn't fully explain the results you are seeing, but it does seem to > validate the idea. I'm curious if you see further improvement with even > lower thresholds (e.g., 8KB, 16KB, 32KB). By the way, I might have set the threshold to 1MB in my program, but lowering it won't show a difference in my test case, since the lseek()s I was noticing before the patch were mostly 8-16KB forward. Not sure what is the defining factor for that. Maybe the compression algorithm, or how wide the table is? Dimitris
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Nathan Bossart <nathandbossart@gmail.com> — 2025-06-13T22:04:47Z
On Fri, Jun 13, 2025 at 01:00:26AM +0200, Dimitrios Apostolou wrote: > By the way, I might have set the threshold to 1MB in my program, but > lowering it won't show a difference in my test case, since the lseek()s I > was noticing before the patch were mostly 8-16KB forward. Not sure what is > the defining factor for that. Maybe the compression algorithm, or how wide > the table is? I may have missed it, but could you share what the strace looks like with the patch applied? -- nathan
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Thomas Munro <thomas.munro@gmail.com> — 2025-06-13T23:15:25Z
On Wed, Jun 11, 2025 at 9:48 AM Nathan Bossart <nathandbossart@gmail.com> wrote: > So, fseeko() starts winning around 4096 bytes. On macOS, the differences > aren't quite as dramatic, but 4096 bytes is the break-even point there, > too. I imagine there's a buffer around that size somewhere... BTW you can call setvbuf(f, my_buffer, _IOFBF, my_buffer_size) to control FILE buffering. I suspect that glibc ignores the size if you pass NULL for my_buffer, so you'd need to allocate it yourself and it should probably be aligned on PG_IO_ALIGN_SIZE for best results (minimising the number of VM pages that must be held/pinned). Then you might be able to get better and less OS-dependent results. I haven't studied this seek business so I have no opinion on that and what a good size would be, but interesting sizes might be rounded to both PG_IO_ALIGN_SIZE and filesystem block size according to fstat(fileno(stream)). IDK, just a thought...
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-06-14T16:01:13Z
On Fri, 13 Jun 2025, Nathan Bossart wrote: > On Fri, Jun 13, 2025 at 01:00:26AM +0200, Dimitrios Apostolou wrote: >> By the way, I might have set the threshold to 1MB in my program, but >> lowering it won't show a difference in my test case, since the lseek()s I >> was noticing before the patch were mostly 8-16KB forward. Not sure what is >> the defining factor for that. Maybe the compression algorithm, or how wide >> the table is? > > I may have missed it, but could you share what the strace looks like with > the patch applied? read(4, "..."..., 8192) = 8192 read(4, "..."..., 4096) = 4096 read(4, "..."..., 12288) = 12288 read(4, "..."..., 4096) = 4096 read(4, "..."..., 8192) = 8192 read(4, "..."..., 4096) = 4096 read(4, "..."..., 8192) = 8192 read(4, "..."..., 4096) = 4096 read(4, "..."..., 8192) = 8192 read(4, "..."..., 4096) = 4096 read(4, "..."..., 8192) = 8192 read(4, "..."..., 4096) = 4096 read(4, "..."..., 8192) = 8192 read(4, "..."..., 4096) = 4096 read(4, "..."..., 8192) = 8192 read(4, "..."..., 4096) = 4096 read(4, "..."..., 12288) = 12288 read(4, "..."..., 4096) = 4096 read(4, "..."..., 8192) = 8192 read(4, "..."..., 4096) = 4096 read(4, "..."..., 12288) = 12288 read(4, "..."..., 4096) = 4096 read(4, "..."..., 8192) = 8192 read(4, "..."..., 4096) = 4096 read(4, "..."..., 8192) = 8192 read(4, "..."..., 4096) = 4096 read(4, "..."..., 12288) = 12288 read(4, "..."..., 4096) = 4096 read(4, "..."..., 8192) = 8192 read(4, "..."..., 4096) = 4096
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-06-14T16:17:41Z
On Sat, 14 Jun 2025, Dimitrios Apostolou wrote: > On Fri, 13 Jun 2025, Nathan Bossart wrote: > >> On Fri, Jun 13, 2025 at 01:00:26AM +0200, Dimitrios Apostolou wrote: >>> By the way, I might have set the threshold to 1MB in my program, but >>> lowering it won't show a difference in my test case, since the lseek()s I >>> was noticing before the patch were mostly 8-16KB forward. Not sure what >>> is >>> the defining factor for that. Maybe the compression algorithm, or how >>> wide >>> the table is? >> >> I may have missed it, but could you share what the strace looks like with >> the patch applied? > > read(4, "..."..., 8192) = 8192 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 12288) = 12288 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 8192) = 8192 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 8192) = 8192 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 8192) = 8192 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 8192) = 8192 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 8192) = 8192 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 8192) = 8192 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 12288) = 12288 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 8192) = 8192 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 12288) = 12288 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 8192) = 8192 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 8192) = 8192 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 12288) = 12288 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 8192) = 8192 > read(4, "..."..., 4096) = 4096 This was from pg_restoring a zstd-compressed custom format dump. Out of curiosity I've tried the same with an uncompressed dump (--compress=none). Surprisingly it seems the blocksize is even smaller. With my patched pg_restore I only get 4K reads and nothing else on the strace output. read(4, "..."..., 4096) = 4096 read(4, "..."..., 4096) = 4096 read(4, "..."..., 4096) = 4096 read(4, "..."..., 4096) = 4096 read(4, "..."..., 4096) = 4096 read(4, "..."..., 4096) = 4096 The unpatched pg_restore gives me the weirdest output ever: read(4, "..."..., 4096) = 4096 lseek(4, 98527916032, SEEK_SET) = 98527916032 lseek(4, 98527916032, SEEK_SET) = 98527916032 lseek(4, 98527916032, SEEK_SET) = 98527916032 lseek(4, 98527916032, SEEK_SET) = 98527916032 lseek(4, 98527916032, SEEK_SET) = 98527916032 lseek(4, 98527916032, SEEK_SET) = 98527916032 [ ... repeats about 80 times ...] read(4, "..."..., 4096) = 4096 lseek(4, 98527920128, SEEK_SET) = 98527920128 lseek(4, 98527920128, SEEK_SET) = 98527920128 lseek(4, 98527920128, SEEK_SET) = 98527920128 lseek(4, 98527920128, SEEK_SET) = 98527920128 [ ... repeats ... ] Seeing this, I think we should really consider raising the pg_dump block size like Tom suggested on a previous thread. Dimitris
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-07-23T21:54:44Z
Hi Nathan, I've noticed you've set yourself as a reviewer of this patch in the commitfest. I appreciate it, but you might want to combine it with another simple patch [1] that speeds up the same part of pg_restore: the initial full scan on TOC-less archives. [1] https://commitfest.postgresql.org/patch/5817/ On Saturday 2025-06-14 00:04, Nathan Bossart wrote: > > On Fri, Jun 13, 2025 at 01:00:26AM +0200, Dimitrios Apostolou wrote: >> By the way, I might have set the threshold to 1MB in my program, but >> lowering it won't show a difference in my test case, since the lseek()s I >> was noticing before the patch were mostly 8-16KB forward. Not sure what is >> the defining factor for that. Maybe the compression algorithm, or how wide >> the table is? > > I may have missed it, but could you share what the strace looks like with > the patch applied? I hope you've seen my response here, with special focus on the small block size that both compressed and uncompressed custom format archives have. I have been needing to pg_restore 10TB TOC-less dumps recently, and it's a pain to do the full scan, even with both of my patches applied. Maybe the block size could be a command line option of pg_dump, so that one could set it to sizes like 100MB, which sounds like a normal block from the perspective of a 10TB gigantic dump. Regards, Dimitris
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-07-28T13:34:20Z
On Saturday 2025-06-14 18:17, Dimitrios Apostolou wrote: > Out of curiosity I've tried the same with an uncompressed dump > (--compress=none). Surprisingly it seems the blocksize is even smaller. > > With my patched pg_restore I only get 4K reads and nothing else on the strace > output. > > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 4096) = 4096 > read(4, "..."..., 4096) = 4096 To clarify this output again, I have a huge uncompressed custom format dump without TOC (because pg_dump was writing to stdout), and at this point pg_restore is going through the whole archive to find the items it needs. Allow me to explain what goes on at this point since I have better insight now. The code in question, in pg_backup_custom.c: /* * Skip data from current file position. * Data blocks are formatted as an integer length, followed by data. * A zero length indicates the end of the block. */ static void _skipData(ArchiveHandle *AH) { lclContext *ctx = (lclContext *) AH->formatData; size_t blkLen; char *buf = NULL; int buflen = 0; blkLen = ReadInt(AH); while (blkLen != 0) { /* Sequential access is usually faster, so avoid seeking if the * jump forward is shorter than 1MB. */ if (ctx->hasSeek && blkLen > 1024 * 1024) { if (fseeko(AH->FH, blkLen, SEEK_CUR) != 0) pg_fatal("error during file seek: %m"); } else { if (blkLen > buflen) { free(buf); buf = (char *) pg_malloc(blkLen); buflen = blkLen; } if (fread(buf, 1, blkLen, AH->FH) != blkLen) { if (feof(AH->FH)) pg_fatal("could not read from input file: end of file"); else pg_fatal("could not read from input file: %m"); } } blkLen = ReadInt(AH); } free(buf); } blkLen is almost always a number around 35 to 38. So fread() is called all the time doing reads of about ~35 bytes. Then ReadInt() is actually doing getc() a few times. And it loops over. Libc is doing buffering of 4k, and that's how we end up seeing so many 4k reads. This also explains the ~80 lseek() between each 4k read() on the unpatched version, mentioned in previous email. I've tried setvbuf() like Thomas Munro suggested and I saw a significant improvement by allocating and using a 1MB buffer for libc stream buffering. Question that remains: where is pg_dump setting this ~35B length block? Is that easy to change without breaking old versions? Thanks in advance, Dimitris -
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Tom Lane <tgl@sss.pgh.pa.us> — 2025-10-10T20:26:57Z
Dimitrios Apostolou <jimis@gmx.net> writes: > Question that remains: where is pg_dump setting this ~35B length block? I poked into that question, and found that the answer is some exceedingly brain-dead buffering logic in compress_zstd.c. It will dump its buffer during every loop iteration within _ZstdWriteCommon, no matter how much buffer space it has left; and each call to cs->writeF() produces a new "data block" in the output file. The amount of data fed to _ZstdWriteCommon per call is whatever the backend sends per "copy data" message, which is generally one table row. So if the table rows aren't too wide, or if they're highly compressible, you get these tiny data blocks. compress_lz4.c is equally broken, again buffering no bytes across calls; although liblz4 seems to do some buffering internally. I got blocks of around 300 bytes on the test case I was using. That's still ridiculous. compress_gzip.c is actually sanely implemented, and consistently produces blocks of 4096 bytes, which traces to DEFAULT_IO_BUFFER_SIZE in compress_io.h. If you choose --compress=none, you get data blocks that correspond exactly to table rows. We could imagine doing some internal buffering to amalgamate short rows into larger blocks, but I'm not entirely convinced it's worth messing with that case. The attached patch fixes the buffering logic in compress_zstd.c and compress_lz4.c. For zstd, most blocks are now 131591 bytes, which seems to be determined by ZSTD_CStreamOutSize() not by our code. For lz4, I see a range of block sizes but they're almost all around 64K. That's apparently emergent from the behavior of LZ4F_compressBound(): when told we want to supply it up to 4K at a time, it says it needs a buffer around 64K. I'm tempted to increase DEFAULT_IO_BUFFER_SIZE so that gzip also produces blocks in the vicinity of 64K, but we'd have to decouple the behavior of compress_lz4.c somehow, or it would want to produce blocks around a megabyte which might be excessive. (Or if it's not, we'd still want all these compression methods to choose similar block sizes, I'd think.) Anyway, these fixes should remove the need for pg_restore to look at quite so many places in the archive file. There may still be a need for altering the seek-versus-read behavior as you suggest, but I think we need to re-measure that tradeoff after fixing the pg_dump side. regards, tom lane
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Tom Lane <tgl@sss.pgh.pa.us> — 2025-10-11T02:22:16Z
I wrote: > I'm tempted to increase DEFAULT_IO_BUFFER_SIZE so that gzip > also produces blocks in the vicinity of 64K, but we'd have > to decouple the behavior of compress_lz4.c somehow, or it > would want to produce blocks around a megabyte which might > be excessive. (Or if it's not, we'd still want all these > compression methods to choose similar block sizes, I'd think.) After a bit of further experimentation, here is a v2 patchset. 0001 is like my previous patch except that it also fixes Zstd_write and Zstd_close so that the "stream API" code doesn't behave differently from the older API. Also, now with draft commit message. 0002 adjusts things so that lz4 and gzip compression produce block sizes around 128K, which is what compress_zstd.c already does after 0001. While I wouldn't necessarily follow zstd's lead if it were easy to do differently, it isn't. We'd have to ignore ZSTD_CStreamOutSize() in favor of making our own buffer size choice. That seems to carry some risks of tickling bugs that upstream isn't testing for, and the value of 128K is not so far off that I care to take any such risk. This brings us to a place where all three compression modes should yield roughly-comparable data block sizes, which is a good starting point for further discussion of whether pg_restore needs seek-versus-read adjustments. regards, tom lane
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Tom Lane <tgl@sss.pgh.pa.us> — 2025-10-12T01:25:20Z
While playing around with the test cases for pg_dump compression, I was startled to discover that the performance of compress_lz4's "stream API" code is absolutely abysmal. Here is a simple test case to demonstrate, using the regression database as test data: $ pg_dump -Fd --compress=lz4 -f rlz4.dir regression $ time pg_restore -f /dev/null rlz4.dir real 0m0.023s user 0m0.017s sys 0m0.006s So far so good, but now let's compress the toc.dat file: $ lz4 -f -m --rm rlz4.dir/toc.dat $ time pg_restore -f /dev/null rlz4.dir real 0m1.335s user 0m1.326s sys 0m0.008s Considering that lz4 prides itself on fast decompression speed, that is not a sane result. Decompressing the file only requires a couple ms on my machine: $ time lz4cat rlz4.dir/toc.dat.lz4 >/dev/null real 0m0.002s user 0m0.000s sys 0m0.002s So on this example, pg_restore is something more than 600x slower to read the TOC data than it ought to be. On investigation, the blame mostly affixes to LZ4Stream_read_overflow's habit of memmove'ing all the remaining buffered data after each read operation. Since reading a TOC file tends to involve a lot of small (even one-byte) decompression calls, that amounts to an O(N^2) cost. This could have been fixed with a minimal patch, but to my eyes LZ4Stream_read_internal and LZ4Stream_read_overflow are badly-written spaghetti code; in particular the eol_flag logic is inefficient and duplicative. I chose to throw the code away and rewrite from scratch. This version is about sixty lines shorter as well as not having the performance issue. Fortunately, AFAICT the only way to get to this problem is to manually LZ4-compress the toc.dat and/or blobs.toc files within a directory-style archive. Few people do that, which likely explains the lack of field complaints. On top of that, a similar case with gzip doesn't work at all, though it's supposed to: $ pg_dump -Fd --compress=gzip -f rgzip.dir regression $ gzip rgzip.dir/toc.dat $ pg_restore -f /dev/null rgzip.dir pg_restore: error: could not read from input file: Tracking this down, it seems that Gzip_read doesn't cope with a request to read zero bytes. I wonder how long that's been broken. As far as I can see, 002_pg_dump.pl doesn't exercise the case of manually-compressed toc.dat files. I wonder why not. 0001 and 0002 attached are the same as before, then 0003 adds a fix for the LZ4 performance problem, and 0004 fixes the Gzip_read problem. While at it, I got rid of a few other minor inefficiencies such as unnecessary buffer-zeroing. regards, tom lane
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Chao Li <li.evan.chao@gmail.com> — 2025-10-13T03:33:06Z
> On Oct 12, 2025, at 09:25, Tom Lane <tgl@sss.pgh.pa.us> wrote: > > While playing around with the test cases for pg_dump compression, > I was startled to discover that the performance of compress_lz4's > "stream API" code is absolutely abysmal. Here is a simple test > case to demonstrate, using the regression database as test data: > > $ pg_dump -Fd --compress=lz4 -f rlz4.dir regression > $ time pg_restore -f /dev/null rlz4.dir > > real 0m0.023s > user 0m0.017s > sys 0m0.006s > > So far so good, but now let's compress the toc.dat file: > > $ lz4 -f -m --rm rlz4.dir/toc.dat > $ time pg_restore -f /dev/null rlz4.dir > > real 0m1.335s > user 0m1.326s > sys 0m0.008s > > Considering that lz4 prides itself on fast decompression speed, > that is not a sane result. Decompressing the file only requires > a couple ms on my machine: > > $ time lz4cat rlz4.dir/toc.dat.lz4 >/dev/null > > real 0m0.002s > user 0m0.000s > sys 0m0.002s > > So on this example, pg_restore is something more than 600x slower > to read the TOC data than it ought to be. > I tested the patch on my MacBook M4 (Sequoia 15.6.1), compiled with -O2 optimization: - For LZ4 performance improvement: Without the fix (latest master): ``` chaol@ChaodeMacBook-Air tmp % pg_dump -Fd --compress=lz4 -f rlz4.dir regression chaol@ChaodeMacBook-Air tmp % time pg_restore -f /dev/null rlz4.dir pg_restore -f /dev/null rlz4.dir 0.03s user 0.03s system 11% cpu 0.463 total chaol@ChaodeMacBook-Air tmp % lz4 -f -m --rm rlz4.dir/toc.dat # It took 1.59s to restore from compressed data, much slower than from uncompressed data. chaol@ChaodeMacBook-Air tmp % time pg_restore -f /dev/null rlz4.dir pg_restore -f /dev/null rlz4.dir 1.59s user 0.02s system 97% cpu 1.653 total ``` With the fix: ``` chaol@ChaodeMacBook-Air tmp % pg_dump -Fd --compress=lz4 -f rlz4.dir regression chaol@ChaodeMacBook-Air tmp % time pg_restore -f /dev/null rlz4.dir pg_restore -f /dev/null rlz4.dir 0.02s user 0.03s system 16% cpu 0.305 total chaol@ChaodeMacBook-Air tmp % lz4 -f -m --rm rlz4.dir/toc.dat # Much faster !!! chaol@ChaodeMacBook-Air tmp % time pg_restore -f /dev/null rlz4.dir pg_restore -f /dev/null rlz4.dir 0.02s user 0.02s system 93% cpu 0.043 total ``` - For Gzip zero read bug: Without the fix: ``` chaol@ChaodeMacBook-Air tmp % pg_dump -Fd --compress=gzip -f rgzip.dir regression chaol@ChaodeMacBook-Air tmp % gzip rgzip.dir/toc.dat # Failed chaol@ChaodeMacBook-Air tmp % pg_restore -f /dev/null rgzip.dir pg_restore: error: could not read from input file: ``` With the fix: ``` chaol@ChaodeMacBook-Air tmp % pg_dump -Fd --compress=gzip -f rgzip.dir regression chaol@ChaodeMacBook-Air tmp % gzip rgzip.dir/toc.dat # Ran successfully chaol@ChaodeMacBook-Air tmp % pg_restore -f /dev/null rgzip.dir ``` I also reviewed the code change, basically LGTM. Just a couple of trivial comments: 1 - 0001 In WriteDataToArchiveLZ4() ``` + required = LZ4F_compressBound(chunk, &state->prefs); + if (required > state->buflen - state->bufdata) + { + cs->writeF(AH, state->buffer, state->bufdata); + state->bufdata = 0; + } ``` And in EndCompressorLZ4() ``` + required = LZ4F_compressBound(0, &state->prefs); + if (required > state->buflen - state->bufdata) + { + cs->writeF(AH, state->buffer, state->bufdata); + state->bufdata = 0; + } ``` These two code pieces are similar, only difference is the first parameter passed to LZ4F_compressBound(). I think we can create an inline function for it. But these are just 5 lines, I don’t have a strong option on that. Same thing for the other code pieces in LZ4Stream_write() and LZ4Stream_close(). 2 - 0003 ``` /* * LZ4 equivalent to feof() or gzeof(). Return true iff there is no - * decompressed output in the overflow buffer and the end of the backing file ``` This doesn’t belong to the current patch. But “iff” seems a typo of “if”. You may fix it as you are touching this piece of code. Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/ -
Re: [PATCH v1] parallel pg_restore: avoid disk seeks when jumping short distance forward
David G. Johnston <david.g.johnston@gmail.com> — 2025-10-13T03:39:31Z
On Sunday, October 12, 2025, Chao Li <li.evan.chao@gmail.com> wrote: > 2 - 0003 > ``` > /* > * LZ4 equivalent to feof() or gzeof(). Return true iff there is no > - * decompressed output in the overflow buffer and the end of the backing > file > ``` > > This doesn’t belong to the current patch. But “iff” seems a typo of “if”. > You may fix it as you are touching this piece of code. > “iif” is shorthand for “if and only if”. So it isn’t likely to be a typo; it only needs to be changed if it is wrong. I haven’t looked to see. David J.
-
Re: [PATCH v1] parallel pg_restore: avoid disk seeks when jumping short distance forward
David G. Johnston <david.g.johnston@gmail.com> — 2025-10-13T03:40:34Z
On Sunday, October 12, 2025, David G. Johnston <david.g.johnston@gmail.com> wrote: > On Sunday, October 12, 2025, Chao Li <li.evan.chao@gmail.com> wrote: > >> 2 - 0003 >> ``` >> /* >> * LZ4 equivalent to feof() or gzeof(). Return true iff there is no >> - * decompressed output in the overflow buffer and the end of the backing >> file >> ``` >> >> This doesn’t belong to the current patch. But “iff” seems a typo of “if”. >> You may fix it as you are touching this piece of code. >> > > “iif” is shorthand for “if and only if”. So it isn’t likely to be a typo; > it only needs to be changed if it is wrong. I haven’t looked to see. > Never mind…two f’s, not two i’s … David J.
-
Re: [PATCH v1] parallel pg_restore: avoid disk seeks when jumping short distance forward
Chao Li <li.evan.chao@gmail.com> — 2025-10-13T03:41:15Z
> On Oct 13, 2025, at 11:39, David G. Johnston <david.g.johnston@gmail.com> wrote: > > > “iif” is shorthand for “if and only if”. So it isn’t likely to be a typo; it only needs to be changed if it is wrong. I haven’t looked to see. > Thanks for the explanation, I wasn’t aware of that. I learned. Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Tom Lane <tgl@sss.pgh.pa.us> — 2025-10-13T21:07:52Z
Chao Li <li.evan.chao@gmail.com> writes: >> On Oct 12, 2025, at 09:25, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> While playing around with the test cases for pg_dump compression, >> I was startled to discover that the performance of compress_lz4's >> "stream API" code is absolutely abysmal. > I tested the patch on my MacBook M4 (Sequoia 15.6.1), compiled with -O2 optimization: Thanks for looking at it! > I also reviewed the code change, basically LGTM. Just a couple of trivial comments: > 1 - 0001 > In WriteDataToArchiveLZ4() > ``` > + required = LZ4F_compressBound(chunk, &state->prefs); > + if (required > state->buflen - state->bufdata) > + { > + cs->writeF(AH, state->buffer, state->bufdata); > + state->bufdata = 0; > + } > ``` > And in EndCompressorLZ4() > ``` > + required = LZ4F_compressBound(0, &state->prefs); > + if (required > state->buflen - state->bufdata) > + { > + cs->writeF(AH, state->buffer, state->bufdata); > + state->bufdata = 0; > + } > ``` > These two code pieces are similar, only difference is the first parameter passed to LZ4F_compressBound(). > I think we can create an inline function for it. But these are just 5 lines, I don’t have a strong option on that. Yeah, I don't think that would improve code readability noticeably. By the time you got done writing a documentation comment for the new subroutine, the code would probably be longer not shorter. I've pushed the parts of that patch set that I thought were uncontroversial. What's left is the business about increasing DEFAULT_IO_BUFFER_SIZE and then adjusting the tests appropriately. So, v4-0001 attached is the previous v3-0002 to increase DEFAULT_IO_BUFFER_SIZE, plus additions in compress_none.c to make --compress=none also produce predictably large data blocks. I decided that if we're going to rely on that behavior as part of the solution for this thread's original problem, we'd better make it happen for all compression options. 0002 adds a test case in 002_pg_dump.pl to exercise --compress=none, because without that we don't have any coverage of the new code 0001 added in compress_none.c. That makes for a small increase in the runtime of 002_pg_dump.pl, but I'm inclined to think it's worth doing. 0003 modifies the existing test cases that manually compress blobs.toc files so that they also compress toc.dat. I feel like it's mostly an oversight that that wasn't done to begin with; if it had been done, we'd have caught the Gzip_read bug right away. Also, AFAICT, this doesn't cost anything measurable in test runtime. 0004 increases the row width in the existing test case that says it's trying to push more than DEFAULT_IO_BUFFER_SIZE through the compressors. While I agree with the premise, this solution is hugely expensive: it adds about 12% to the already-long runtime of 002_pg_dump.pl. I'd like to find a better way, but ran out of energy for today. (I think the reason this costs so much is that it's effectively iterated hundreds of times because of 002_pg_dump.pl's more or less cross-product approach to testing everything. Maybe we should pull it out of that structure?) regards, tom lane -
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Chao Li <li.evan.chao@gmail.com> — 2025-10-14T00:09:56Z
> On Oct 14, 2025, at 05:07, Tom Lane <tgl@sss.pgh.pa.us> wrote: > > > I've pushed the parts of that patch set that I thought were > uncontroversial. What's left is the business about increasing > DEFAULT_IO_BUFFER_SIZE and then adjusting the tests appropriately. > > So, v4-0001 attached is the previous v3-0002 to increase > DEFAULT_IO_BUFFER_SIZE, plus additions in compress_none.c to make > --compress=none also produce predictably large data blocks. > I decided that if we're going to rely on that behavior as part > of the solution for this thread's original problem, we'd better > make it happen for all compression options. > > 0002 adds a test case in 002_pg_dump.pl to exercise --compress=none, > because without that we don't have any coverage of the new code > 0001 added in compress_none.c. That makes for a small increase > in the runtime of 002_pg_dump.pl, but I'm inclined to think it's > worth doing. > > 0003 modifies the existing test cases that manually compress > blobs.toc files so that they also compress toc.dat. I feel > like it's mostly an oversight that that wasn't done to begin > with; if it had been done, we'd have caught the Gzip_read bug > right away. Also, AFAICT, this doesn't cost anything measurable > in test runtime. > > 0004 increases the row width in the existing test case that says > it's trying to push more than DEFAULT_IO_BUFFER_SIZE through > the compressors. While I agree with the premise, this solution > is hugely expensive: it adds about 12% to the already-long runtime > of 002_pg_dump.pl. I'd like to find a better way, but ran out of > energy for today. (I think the reason this costs so much is that > it's effectively iterated hundreds of times because of > 002_pg_dump.pl's more or less cross-product approach to testing > everything. Maybe we should pull it out of that structure?) > In v4 patch, the code changes are straightforward. 0001 changes compress_none.c to write data to a 128K buffer first, then only flush the buffer when it’s filled up. 0002, 0003 and 0004 add more test cases. I have no comment to the code diff. I tested DEFAULT_IO_BUFFER_SIZE with 4K, 32K, 64K, 128K and 256K. Looks like increasing the buffer size doesn’t improve the performance significantly. Actually, with the buffer size 64K, 128K and 256K, the test results are very close. I tested both with lz4 and none compression. I am not suggesting tuning the buffer size. These data are only for your reference. To do the test, I created a test db and filled in several GB of data. ``` 256K ==== % time pg_dump -Fd --compress=lz4 -f dump_A.dir evantest pg_dump -Fd --compress=lz4 -f dump_A.dir evantest 3.37s user 0.82s system 57% cpu 7.249 total % time pg_restore -f /dev/null dump_A.dir pg_restore -f /dev/null dump_A.dir 0.24s user 0.19s system 43% cpu 0.991 total % time pg_dump -Fd --compress=none -f dump_A.dir evantest pg_dump -Fd --compress=none -f dump_A.dir evantest 2.34s user 1.72s system 68% cpu 5.949 total % time pg_restore -f /dev/null dump_A.dir pg_restore -f /dev/null dump_A.dir 0.02s user 0.19s system 22% cpu 0.921 total 128K === % time pg_dump -Fd --compress=lz4 -f dump_A.dir evantest pg_dump -Fd --compress=lz4 -f dump_A.dir evantest 3.38s user 0.85s system 64% cpu 6.525 total % time pg_restore -f /dev/null dump_A.dir pg_restore -f /dev/null dump_A.dir 0.28s user 0.21s system 47% cpu 1.042 total % time pg_dump -Fd --compress=none -f dump_A.dir evantest pg_dump -Fd --compress=none -f dump_A.dir evantest 2.34s user 1.67s system 68% cpu 5.835 total % time pg_restore -f /dev/null dump_A.dir pg_restore -f /dev/null dump_A.dir 0.03s user 0.22s system 22% cpu 1.118 total 64K === % time pg_dump -Fd --compress=lz4 -f dump_A.dir evantest pg_dump -Fd --compress=lz4 -f dump_A.dir evantest 3.39s user 0.92s system 63% cpu 6.761 total % time pg_restore -f /dev/null dump_A.dir pg_restore -f /dev/null dump_A.dir 0.33s user 0.24s system 40% cpu 1.420 total % time pg_dump -Fd --compress=none -f dump_A.dir evantest pg_dump -Fd --compress=none -f dump_A.dir evantest 2.35s user 1.74s system 69% cpu 5.849 total % time pg_restore -f /dev/null dump_A.dir pg_restore -f /dev/null dump_A.dir 0.04s user 0.22s system 27% cpu 0.939 total 32K === % time pg_dump -Fd --compress=lz4 -f dump_A.dir evantest pg_dump -Fd --compress=lz4 -f dump_A.dir evantest 3.43s user 0.94s system 58% cpu 7.416 total % time pg_restore -f /dev/null dump_A.dir pg_restore -f /dev/null dump_A.dir 0.34s user 0.22s system 56% cpu 0.983 total % time pg_dump -Fd --compress=none -f dump_A.dir evantest pg_dump -Fd --compress=none -f dump_A.dir evantest 2.34s user 1.75s system 67% cpu 6.070 total % time pg_restore -f /dev/null dump_A.dir pg_restore -f /dev/null dump_A.dir 0.05s user 0.23s system 29% cpu 0.926 total 4k==== % time pg_dump -Fd --compress=lz4 -f dump_A.dir evantest pg_dump -Fd --compress=lz4 -f dump_A.dir evantest 3.45s user 0.94s system 60% cpu 7.298 total % time pg_restore -f /dev/null dump_A.dir pg_restore -f /dev/null dump_A.dir 0.37s user 0.29s system 64% cpu 1.016 total % time pg_dump -Fd --compress=none -f dump_A.dir evantest pg_dump -Fd --compress=none -f dump_A.dir evantest 2.33s user 1.78s system 69% cpu 5.947 total % time pg_restore -f /dev/null dump_A.dir pg_restore -f /dev/null dump_A.dir 0.12s user 0.29s system 40% cpu 1.009 total ``` Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Tom Lane <tgl@sss.pgh.pa.us> — 2025-10-14T00:36:07Z
Chao Li <li.evan.chao@gmail.com> writes: > I tested DEFAULT_IO_BUFFER_SIZE with 4K, 32K, 64K, 128K and 256K. Looks like increasing the buffer size doesn’t improve the performance significantly. Actually, with the buffer size 64K, 128K and 256K, the test results are very close. I tested both with lz4 and none compression. I am not suggesting tuning the buffer size. These data are only for your reference. Yeah, I would not expect straight pg_dump/pg_restore performance to vary very much once the buffer size gets above not-too-many KB. The thing we are really interested in here is how fast pg_restore can skip over unwanted table data in a large archive file, and that I believe should be pretty sensitive to block size. You could measure that without getting into the complexities of parallel restore if you make a custom-format dump of a few large tables that does not have offset data in it, and then seeing how fast is selective restore of just the last table. regards, tom lane
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Chao Li <li.evan.chao@gmail.com> — 2025-10-14T01:37:45Z
> On Oct 14, 2025, at 08:36, Tom Lane <tgl@sss.pgh.pa.us> wrote: > > Chao Li <li.evan.chao@gmail.com> writes: >> I tested DEFAULT_IO_BUFFER_SIZE with 4K, 32K, 64K, 128K and 256K. Looks like increasing the buffer size doesn’t improve the performance significantly. Actually, with the buffer size 64K, 128K and 256K, the test results are very close. I tested both with lz4 and none compression. I am not suggesting tuning the buffer size. These data are only for your reference. > > Yeah, I would not expect straight pg_dump/pg_restore performance > to vary very much once the buffer size gets above not-too-many KB. > The thing we are really interested in here is how fast pg_restore > can skip over unwanted table data in a large archive file, and that > I believe should be pretty sensitive to block size. > > You could measure that without getting into the complexities of > parallel restore if you make a custom-format dump of a few large > tables that does not have offset data in it, and then seeing how > fast is selective restore of just the last table. > > Not sure if I did something wrong, but I still don’t see much difference between buffer size 4K and 128K with your suggested test. I created 3 tables, each with 10 millions of rows: ``` evantest=# CREATE TABLE tbl1 AS SELECT generate_series(1,10000000) AS id; SELECT 10000000 evantest=# CREATE TABLE tbl2 AS SELECT generate_series(1,10000000) AS id; SELECT 10000000 evantest=# CREATE TABLE tbl3 AS SELECT generate_series(1,10000000) AS id; SELECT 10000000 ``` And did a custom-format dump: ``` % time pg_dump -Fc -f db.dump evantest pg_dump -Fc -f db.dump evantest 51.72s user 1.13s system 98% cpu 53.602 total ``` Then pg_restore the last tabl, compiled with buffer size 4k and 128k: (I dropped tbl3 before running pg_restore) ``` # 4K === % time pg_restore -d evantest -t tbl3 db.dump pg_restore -d evantest -t tbl3 db.dump 0.06s user 0.04s system 6% cpu 1.528 total # 128K % time pg_restore -d evantest -t tbl3 db.dump pg_restore -d evantest -t tbl3 db.dump 0.05s user 0.04s system 3% cpu 2.146 total ``` The other thing I noticed is that, when I do custom-format dump, if a target file exists, pg_dump will just go ahead overwrite the existing file; however, when I do directory dump, if a target dir exists, pg_dump will fail with an error “directory xxx is not empty”. Why the behaviors are different? Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Tom Lane <tgl@sss.pgh.pa.us> — 2025-10-14T02:44:45Z
Chao Li <li.evan.chao@gmail.com> writes: >> On Oct 14, 2025, at 08:36, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> The thing we are really interested in here is how fast pg_restore >> can skip over unwanted table data in a large archive file, and that >> I believe should be pretty sensitive to block size. > Not sure if I did something wrong, but I still don’t see much difference between buffer size 4K and 128K with your suggested test. > > % time pg_dump -Fc -f db.dump evantest This won't show the effect, because pg_dump will be able to go back and insert data offsets into the dump's TOC, so pg_restore can just seek to where the data is. See upthread discussion about what's needed to provoke Dimitrios' problem. I tried this very tiny (relatively speaking) test case: regression=# create database d1; CREATE DATABASE regression=# \c d1 You are now connected to database "d1" as user "postgres". d1=# create table alpha as select repeat(random()::text, 1000) from generate_series(1,1000000); SELECT 1000000 d1=# create table omega as select 42 as x; SELECT 1 d1=# \q Then $ pg_dump -Fc d1 | cat >d1.dump $ time pg_restore -f /dev/null -t omega d1.dump The point of the pipe-to-cat is to reproduce Dimitrios' problem case with no data offsets in the TOC. Then the restore is doing about the simplest thing I can think of to make it skip over most of the archive file. Also, I'm intentionally using the default choice of gzip because that already responds to DEFAULT_IO_BUFFER_SIZE properly. (This test is with current HEAD, no patches except adjusting DEFAULT_IO_BUFFER_SIZE.) I got these timings: DEFAULT_IO_BUFFER_SIZE = 1K real 0m0.020s user 0m0.002s sys 0m0.017s DEFAULT_IO_BUFFER_SIZE = 4K real 0m0.014s user 0m0.003s sys 0m0.011s DEFAULT_IO_BUFFER_SIZE = 128K real 0m0.002s user 0m0.000s sys 0m0.002s This test case has only about 50MB worth of compressed data, so of course the times are very small; scaling it up to gigabytes would yield more impressive results. But the effect is clearly visible. regards, tom lane
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Chao Li <li.evan.chao@gmail.com> — 2025-10-14T05:36:07Z
> On Oct 14, 2025, at 10:44, Tom Lane <tgl@sss.pgh.pa.us> wrote: > > This won't show the effect, because pg_dump will be able to go back > and insert data offsets into the dump's TOC, so pg_restore can just > seek to where the data is. See upthread discussion about what's > needed to provoke Dimitrios' problem. > > I tried this very tiny (relatively speaking) test case: > > regression=# create database d1; > CREATE DATABASE > regression=# \c d1 > You are now connected to database "d1" as user "postgres". > d1=# create table alpha as select repeat(random()::text, 1000) from generate_series(1,1000000); > SELECT 1000000 > d1=# create table omega as select 42 as x; > SELECT 1 > d1=# \q > > Then > > $ pg_dump -Fc d1 | cat >d1.dump > $ time pg_restore -f /dev/null -t omega d1.dump > > The point of the pipe-to-cat is to reproduce Dimitrios' problem case > with no data offsets in the TOC. Then the restore is doing about the > simplest thing I can think of to make it skip over most of the archive > file. Also, I'm intentionally using the default choice of gzip > because that already responds to DEFAULT_IO_BUFFER_SIZE properly. > (This test is with current HEAD, no patches except adjusting > DEFAULT_IO_BUFFER_SIZE.) > > I got these timings: > > DEFAULT_IO_BUFFER_SIZE = 1K > real 0m0.020s > user 0m0.002s > sys 0m0.017s > > DEFAULT_IO_BUFFER_SIZE = 4K > real 0m0.014s > user 0m0.003s > sys 0m0.011s > > DEFAULT_IO_BUFFER_SIZE = 128K > real 0m0.002s > user 0m0.000s > sys 0m0.002s > > This test case has only about 50MB worth of compressed data, > so of course the times are very small; scaling it up to > gigabytes would yield more impressive results. But the > effect is clearly visible. > With your example, I can now see the difference, however, I had to create 5 more times of rows in the first table: ``` evantest=# CREATE TABLE alpha AS SELECT repeat(random()::text, 1000) FROM generate_series(1, 5000000); SELECT 5000000 evantest=# evantest=# CREATE TABLE omega AS SELECT 42 AS x; SELECT 1 ``` My test is with the patch, I only adjusted DEFAULT_IO_BUFFER_SIZE. DEFAULT_IO_BUFFER_SIZE=4K ``` % /usr/bin/time pg_dump -Fc evantest | cat > d1.dump 294.83 real 220.28 user 45.90 sys % /usr/bin/time pg_restore -f /dev/null -t omega d1.dump 0.16 real 0.02 user 0.09 sys ``` DEFAULT_IO_BUFFER_SIZE=128K ``` % /usr/bin/time pg_dump -Fc evantest | cat > d1.dump 296.89 real 220.85 user 46.64 sys % /usr/bin/time pg_restore -f /dev/null -t omega d1.dump 0.01 real 0.00 user 0.00 sys ``` With bigger blocker size, pg_restore skips less blocks, so it gets faster. Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/ -
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Tom Lane <tgl@sss.pgh.pa.us> — 2025-10-15T19:21:50Z
I wrote: > 0004 increases the row width in the existing test case that says > it's trying to push more than DEFAULT_IO_BUFFER_SIZE through > the compressors. While I agree with the premise, this solution > is hugely expensive: it adds about 12% to the already-long runtime > of 002_pg_dump.pl. I'd like to find a better way, but ran out of > energy for today. (I think the reason this costs so much is that > it's effectively iterated hundreds of times because of > 002_pg_dump.pl's more or less cross-product approach to testing > everything. Maybe we should pull it out of that structure?) The attached patchset accomplishes that by splitting 002_pg_dump.pl into two scripts, one that is just concerned with the compression test cases and one that does everything else. This might not be the prettiest solution, since it duplicates a lot of perl code. I thought about refactoring 002_pg_dump.pl so that it could handle two separate sets of runs-plus-tests, but decided it was overly complicated already. Anyway, 0001 attached is the same as in v4, 0002 performs the test split without intending to change coverage, and then 0003 adds the new test cases I wanted. For me, this ends up with just about the same runtime as before, or maybe a smidge less. I'd hoped for possibly more savings than that, but I'm content with it being a wash. I think this is more or less committable, and then we could get back to the original question of whether it's worth tweaking pg_restore's seek-vs-scan behavior. regards, tom lane
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Tom Lane <tgl@sss.pgh.pa.us> — 2025-10-16T17:01:10Z
I wrote: > I think this is more or less committable, and then we could get > back to the original question of whether it's worth tweaking > pg_restore's seek-vs-scan behavior. And done. Dimitrios, could you re-do your testing against current HEAD, and see if there's still a benefit to tweaking pg_restore's seek-vs-read decisions, and if so what's the best number? regards, tom lane
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-10-16T18:59:05Z
Thank you for your work on this, Tom. I'll try to test it in the weekend. Dimitris On 16 October 2025 19:01:10 CEST, Tom Lane <tgl@sss.pgh.pa.us> wrote: >I wrote: >> I think this is more or less committable, and then we could get >> back to the original question of whether it's worth tweaking >> pg_restore's seek-vs-scan behavior. > >And done. Dimitrios, could you re-do your testing against current >HEAD, and see if there's still a benefit to tweaking pg_restore's >seek-vs-read decisions, and if so what's the best number? > > regards, tom lane
-
[PATCH v4] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-10-20T18:40:27Z
On Thursday 2025-10-16 19:01, Tom Lane wrote: >> I think this is more or less committable, and then we could get >> back to the original question of whether it's worth tweaking >> pg_restore's seek-vs-scan behavior. > > And done. Dimitrios, could you re-do your testing against current > HEAD, and see if there's still a benefit to tweaking pg_restore's > seek-vs-read decisions, and if so what's the best number? Sorry for the delay, I hadn't realized a needed to generate a new database dump using the current HEAD. So I did that, using --compress=none and storing it on compressed btrfs filesystem, since that's my primary use case. I notice that things have improved immensely! Using the test you suggested (see NOTE1): pg_restore -t last_table -f /dev/null huge.pg_dump 1. The strace output is much more reasonable now; basically it's repeating the pattern read(4k) lseek(~128k forward) As a reminder, with old archives it was repeating the pattern: read(4k) lseek(4k forward) lseek(same offset as above) x ~80 times 2. The IO speed is better than before: On my 20TB HDD I get 30-50 MB/s read rate. With old archives I get 10-20 MB/s read rate. 3. Time to complete: ~25 min 4. CPU usage is low. With old archives the pg_restore process shows high *system* CPU (because of the amount of syscalls). I can't really compare the actual runtime between old and new dump, because the two dumps are very different. But I have no doubt the new dump is several times faster to seek through. NOTE1: My original testcase was pg_restore -t last_table -j $NCPU -d testdb This testcase does not show as big improvement, because every single of the parallel workers is concurrently seeking through the dump file. *** All above was measured from master branch HEAD ** 277dec6514728e2d0d87c1279dd5e0afbf897428 Don't rely on zlib's gzgetc() macro. *** Below I have applied attached patch *** Regarding the attached patch (rebased and edited commit message), it basically replaces seek(up to 1MB forward) with read(). The 1MB number comes a bit out of the top of my head. But tweaking it between 128KB and 1MB wouldn't really change anything, given that the block size is now 128KB: The read() will always be chosen against the seek(). Do you know of a real-world case with block sizes >128KB? Anyway I tried it with the new archive from above. 1. strace output is a loop of the following: read(4k) read(~128k) 2. Read rate is between 150-250MB/s basically max that the HDD can give. 3. Time to complete: ~5 min 4. CPU usage: HIGH (63%), most likely because of the sheer amount of data it's parsing. Regards, Dimitris -
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-10-20T20:50:44Z
WriteDataToArchiveNone(ArchiveHandle *AH, CompressorState *cs, const void *data, size_t dLen) { - cs->writeF(AH, data, dLen); + NoneCompressorState *nonecs = (NoneCompressorState *) cs->private_data; + size_t remaining = dLen; + + while (remaining > 0) + { + size_t chunk; + + /* Dump buffer if full */ + if (nonecs->bufdata >= nonecs->buflen) Shouldn't this be equality check instead: if (nonecs->bufdata == nonecs->buflen) And possibly also assert(nonecs->bufdata <= nonecs->buflen) ? + { + cs->writeF(AH, nonecs->buffer, nonecs->bufdata); + nonecs->bufdata = 0; + } + /* And fill it */ + chunk = nonecs->buflen - nonecs->bufdata; + if (chunk > remaining) + chunk = remaining; + memcpy(nonecs->buffer + nonecs->bufdata, data, chunk); + nonecs->bufdata += chunk; + data = ((const char *) data) + chunk; + remaining -= chunk; + } } -
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-10-20T21:12:35Z
On Wednesday 2025-10-15 21:21, Tom Lane wrote: >> 0004 increases the row width in the existing test case that says >> it's trying to push more than DEFAULT_IO_BUFFER_SIZE through >> the compressors. While I agree with the premise, this solution >> is hugely expensive: it adds about 12% to the already-long runtime >> of 002_pg_dump.pl. I'd like to find a better way, but ran out of >> energy for today. (I think the reason this costs so much is that >> it's effectively iterated hundreds of times because of >> 002_pg_dump.pl's more or less cross-product approach to testing >> everything. Maybe we should pull it out of that structure?) > > The attached patchset accomplishes that by splitting 002_pg_dump.pl > into two scripts, one that is just concerned with the compression > test cases and one that does everything else. This might not be > the prettiest solution, since it duplicates a lot of perl code. > I thought about refactoring 002_pg_dump.pl so that it could handle > two separate sets of runs-plus-tests, but decided it was overly > complicated already. > > Anyway, 0001 attached is the same as in v4, 0002 performs the > test split without intending to change coverage, and then 0003 > adds the new test cases I wanted. For me, this ends up with > just about the same runtime as before, or maybe a smidge less. > I'd hoped for possibly more savings than that, but I'm content > with it being a wash. > > I think this is more or less committable, and then we could get > back to the original question of whether it's worth tweaking > pg_restore's seek-vs-scan behavior. Hi Tom, since you are dealing with pg_restore testing, you might want to have a look in the 2nd patch from here: https://www.postgresql.org/message-id/413c1cd8-1d6d-90ba-ac7b-b226a4dad5ed%40gmx.net Direct link to the patch is: https://www.postgresql.org/message-id/attachment/177661/v3-0002-Add-new-test-file-with-pg_restore-test-cases.patch It's a much shorter test, focused on pg_restore. 1. It generates two custom-format dumps (with-TOC and TOC-less). 2. Restores each dump to an empty database using pg_restore with a couple of switches combinations (one combination (--clean --data-only will not work without a patch of mine so we might want to remove that and enrich with others). 3. Tests pg_restore over pre-existing database 4. Tests pg_restore reading file from stdin. Regards, Dimitris -
Re: [PATCH v4] parallel pg_restore: avoid disk seeks when jumping short distance forward
Tom Lane <tgl@sss.pgh.pa.us> — 2025-10-20T21:21:16Z
Dimitrios Apostolou <jimis@gmx.net> writes: > Regarding the attached patch (rebased and edited commit message), it > basically replaces seek(up to 1MB forward) with read(). The 1MB number > comes a bit out of the top of my head. But tweaking it between 128KB and > 1MB wouldn't really change anything, given that the block size is now > 128KB: The read() will always be chosen against the seek(). Do you know > of a real-world case with block sizes >128KB? Yeah, with the recent changes I'd expect table data to pretty much always consist of blocks around 128K, unless the table is smaller than that of course. I experimented with this patch locally and came away not too impressed; it seems the results may be highly platform-dependent. In the interests of having a common benchmark case that's easy to replicate, here's precisely what I did: Use non-assert build of current HEAD (4bea91f21 at the moment). $ createdb bench $ time pgbench -i -s 10000 bench real 14m40.474s user 1m26.717s sys 0m5.045s $ psql bench ... bench=# create table zedtable(f1 int); CREATE TABLE bench=# insert into zedtable values(42); INSERT 0 1 bench=# \q $ time pg_dump -Fc --compress=none bench | cat >bench10000.dump real 7m48.969s user 0m36.334s sys 1m35.209s (At this -s value, the database occupies about 147G and the dump file about 95G. It's important the dump file not fit in RAM.) $ time pg_restore -f /dev/null -t zedtable bench10000.dump real 1m12.646s user 0m0.355s sys 0m5.083s This compares rather favorably to "cat": $ time cat bench10000.dump >/dev/null real 3m6.627s user 0m0.167s sys 0m30.999s I then applied your patch and repeated the restore run: $ time pg_restore -f /dev/null -t zedtable bench10000.dump real 2m39.138s user 0m0.386s sys 0m28.493s So for me, the proposed patch actually makes it 2X slower. Watching it with "iostat 1", I'm seeing about 40MB/s disk read rate with HEAD, and 500MB/s with the patch; "cat" also shows read rate around 500MB/s. So yeah, we can saturate the disk interface by doing all reads and no seeks, but that doesn't net out faster. I did this on a few-years-old Dell Precision 5820 workstation. The specs for it are a bit vague about the disk subsystem: Storage Drive Controllers Integrated Intel AHCI SATA chipset controller (8x 6.0Gb/s), SW RAID 0,1,5,10 Storage Drive 2.5 1.92TB SATA AG Enterprise Solid State Drive and hdparm isn't enormously helpful either: ATA device, with non-removable media Model Number: SSDSC2KB019T8R Serial Number: PHYF1291017A1P9DGN Firmware Revision: XCV1DL69 Media Serial Num: Media Manufacturer: Transport: Serial, ATA8-AST, SATA 1.0a, SATA II Extensions, SATA Rev 2.5, SATA Rev 2.6, SATA Rev 3.0 Standards: Used: unknown (minor revision code 0x006d) Supported: 10 9 8 7 6 5 Likely used: 10 I'm running RHEL 8.10, file system is xfs. So I find this a bit discouraging. It's not clear why you're seeing a win and I'm not, and it's even less clear whether there'd be enough of a win across enough platforms to make it worth trying to engineer a solution that helps many more people than it hurts. regards, tom lane
-
Re: [PING] [PATCH v2] parallel pg_restore: avoid disk seeks when jumping short distance forward
Tom Lane <tgl@sss.pgh.pa.us> — 2025-10-20T21:29:22Z
Dimitrios Apostolou <jimis@gmx.net> writes: > + /* Dump buffer if full */ > + if (nonecs->bufdata >= nonecs->buflen) > Shouldn't this be equality check instead: > if (nonecs->bufdata == nonecs->buflen) Old defensive-programming habit. The invariant we want to establish is that there's some space available, ie nonecs->bufdata < nonecs->buflen and if we just test for equality then we haven't proven that. Agreed that bufdata shouldn't ever be greater than buflen, but if it somehow is, an equality test here would contribute to making things worse (writing ever further past the buffer) not better. > And possibly also assert(nonecs->bufdata <= nonecs->buflen) ? Maybe, but that code is simple enough that I didn't see a big need for an assertion check. regards, tom lane
-
Re: [PATCH v4] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-10-20T22:09:46Z
Thanks for the extensive testing! Did you see the same syscall pattern in strace output, as I did? If yes, then the only reason I can think of that excuses the regression with my patch is that the SATA interface was maxed out when reading sequentially, while the very short latency of SSDs guarantees thousands of seek() operations per second. I was using an HDD, and in older measurements I was using a VM with mounted volume over iSCSI. The first imposes physical limits in the amount of seeks, and the second network round-trip limits. So you are right, it's probably very platform dependent, and the most important fix was to enlarge the underlying block size, that you have done. Dimitris
-
Re: [PATCH v4] parallel pg_restore: avoid disk seeks when jumping short distance forward
Tom Lane <tgl@sss.pgh.pa.us> — 2025-10-20T22:15:13Z
I wrote: > So for me, the proposed patch actually makes it 2X slower. I went and tried this same test case on a 2024 Mac Mini M4 Pro. Cutting to the chase: HEAD: $ time pg_restore -f /dev/null -t zedtable bench10000.dump real 1m26.525s user 0m0.364s sys 0m6.806s Patched: $ time pg_restore -f /dev/null -t zedtable bench10000.dump real 0m15.419s user 0m0.279s sys 0m8.224s So on this hardware it *does* win (although maybe things would be different for a parallel restore). The patched pg_restore takes just about the same amount of time as "cat", and iostat shows both of them reaching a bit more than 6GB/s read speed. My feeling at this point is that we'd probably drop the block size test as irrelevant, and instead simply ignore ctx->hasSeek within this loop if we think we're on a platform where that's the right thing. But how do we figure that out? Not sure where we go from here, but clearly a bunch of research is going to be needed to decide whether this is committable. regards, tom lane
-
Re: [PATCH v4] parallel pg_restore: avoid disk seeks when jumping short distance forward
Tom Lane <tgl@sss.pgh.pa.us> — 2025-10-20T22:23:27Z
Dimitrios Apostolou <jimis@gmx.net> writes: > Thanks for the extensive testing! Did you see the same syscall pattern in strace output, as I did? Yes, I did look at that, and it's the same as you saw: HEAD repeats read(4k) lseek(~128k forward) which is to be expected if we have to read data block headers that are ~128K apart; while patched repeats read(4k) read(~128k) which is a bit odd in itself, why isn't it merging the reads better? > I was using an HDD, Ah. Your original message mentioned NVMe so I was assuming you were also looking at solid-state drives. I can imagine that seeking is more painful on HDDs ... regards, tom lane -
Re: [PATCH v4] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-10-21T13:57:31Z
On Tuesday 2025-10-21 00:23, Tom Lane wrote: > HEAD repeats > > read(4k) > lseek(~128k forward) > > which is to be expected if we have to read data block headers > that are ~128K apart; while patched repeats > > read(4k) > read(~128k) > > which is a bit odd in itself, why isn't it merging the reads better? The read(4k) happens because of the getc() calls that read the next block's length. As noticed in a message above [1], glibc seems to do 4KB buffering by default, for some reason. setvbuf() can mitigate this. [1] https://www.postgresql.org/message-id/1po8os49-r63o-2923-p37n-12698o1qn7p0%40tzk.arg I'm attaching a patch that sets glibc buffering to 1MB just as a proof of concept. It's obviously WIP, it allocates and never frees. :-) Feel free to pick it up and change it as you see fit. With this patch, read() calls are unified in strace. lseeks() remain, even if they are not actually reading anything. It seems to me that glibc could implement an optimisation for fseeko(): store the current position in the file, and do not issue the lseek() system call if the position does not change. >> I was using an HDD, > > Ah. Your original message mentioned NVMe so I was assuming you > were also looking at solid-state drives. I can imagine that > seeking is more painful on HDDs ... Sorry for the confusion, in all this time I've run tests on too many different hardware combinations. Not the best way to draw conclusions, but it's what I had available at each time. Dimitris
-
Re: [PATCH v4] parallel pg_restore: avoid disk seeks when jumping short distance forward
Dimitrios Apostolou <jimis@gmx.net> — 2025-10-21T14:16:26Z
On Tuesday 2025-10-21 00:15, Tom Lane wrote: >> So for me, the proposed patch actually makes it 2X slower. > > I went and tried this same test case on a 2024 Mac Mini M4 Pro. > Cutting to the chase: > > HEAD: > > $ time pg_restore -f /dev/null -t zedtable bench10000.dump > > real 1m26.525s > user 0m0.364s > sys 0m6.806s > > Patched: > > $ time pg_restore -f /dev/null -t zedtable bench10000.dump > > real 0m15.419s > user 0m0.279s > sys 0m8.224s > > So on this hardware it *does* win (although maybe things would > be different for a parallel restore). The patched pg_restore > takes just about the same amount of time as "cat", and iostat > shows both of them reaching a bit more than 6GB/s read speed. > > My feeling at this point is that we'd probably drop the block > size test as irrelevant, and instead simply ignore ctx->hasSeek > within this loop if we think we're on a platform where that's > the right thing. But how do we figure that out? > > Not sure where we go from here, but clearly a bunch of research > is going to be needed to decide whether this is committable. pg_dump files from before your latest fix still exist, and they possibly contain block header every 30 bytes (or however wide is the table rows). A patch in pg_restore would vastly improve this use case. May I suggest the attached patch, which replaces fseeko() with fread() if the distance is 32KB or less? Sounds rather improbable that this would make things worse, but maybe it's possible to generate a dump file with 32KB wide rows, and try restoring on various hardware? If this too is controversial, then we can reduce the number to 4KB. This is the buffering that glibc does internally. By using the same in the given patch, we avoid all the lseek(same-offset) repetitions between the 4K reads. This should be a strict gain, with no downsides. Dimitris
-
Re: [PATCH v4] parallel pg_restore: avoid disk seeks when jumping short distance forward
Tom Lane <tgl@sss.pgh.pa.us> — 2025-10-21T17:36:53Z
Dimitrios Apostolou <jimis@gmx.net> writes: > On Tuesday 2025-10-21 00:15, Tom Lane wrote: >> Not sure where we go from here, but clearly a bunch of research >> is going to be needed to decide whether this is committable. > pg_dump files from before your latest fix still exist, and they possibly > contain block header every 30 bytes (or however wide is the table rows). > A patch in pg_restore would vastly improve this use case. Yes, that's a fair case to worry about. > May I suggest the attached patch, which replaces fseeko() with fread() > if the distance is 32KB or less? Sounds rather improbable that this > would make things worse, but maybe it's possible to generate a dump file > with 32KB wide rows, and try restoring on various hardware? > If this too is controversial, then we can reduce the number to 4KB. This > is the buffering that glibc does internally. By using the same in the > given patch, we avoid all the lseek(same-offset) repetitions between the > 4K reads. This should be a strict gain, with no downsides. I spent some time strace'ing pg_restore on older dump files with relatively small block sizes. You are right that glibc seems quite stupid about this: it appears to issue a kernel lseek() call for every fseeko(), even though it keeps using the same 4K worth of data it had previously read in. I thought maybe that was an artifact of the relatively old glibc in RHEL8, but glibc 2.40 from Fedora 41 is no better. I also checked current macOS, and it's marginally smarter: it issues just one seek call per read call. But that seek is still useless, since the read is still 4K adjacent to the previous 4K. (I wonder if maybe there is some POSIX standards compliance issue involved here? We don't care about the possibility that the disk file is changing under us, but other applications do, and maybe the kernel-visible seek calls are required for some reason.) Putting in a minimum-block-size-to-seek check gets rid of the seek calls entirely, producing a straight stream of reads, on all three platforms. I agree that doing this with a threshold of 4K seems like a no-brainer, since that seems to be the common default stdio buffer size. Using a larger threshold, or setting up a larger buffer, seems to risk platform-dependent results, so it would require more performance testing than I care to do now. Let's do 4K and call it a day. regards, tom lane