Thread
Commits
-
Fix bug in archive streamer with LZ4 decompression
- d44efe87ea75 15.14 landed
- 5c639523f721 16.10 landed
- 074003431895 17.6 landed
- d09d1379346e 18.0 landed
- 3369a3b49b0b 19 (unreleased) landed
-
Decompression bug in astreamer_lz4
Mikhail Gribkov <youzhick@gmail.com> — 2025-06-25T20:07:32Z
Hi hackers, While developing my extension, I faced some problems in using pg_basebackup/pg_verifybackup on a cluster with additional custom files saved. The extension saves its own format files, which may have a variety of sizes and usually quite low compression potential. The same situation can be reproduced using randomly filled files. Let’s imagine the extension saves its own statistics in pg_stat: dd if=/dev/urandom of=$PGDATA/pg_stat/junk bs=1M count=16 Now we’ll run the server and make an lz4-packed backup: $PGINSTALL/bin/pg_basebackup --no-sync -cfast --target server:/tmp/server-backup -Xfetch --compress server-lz4 Now we’ll check the backup: $PGINSTALL/bin/pg_verifybackup -n /tmp/server-backup The verification process will fail. The exact error may vary, but the most common version looks like this: pg_verifybackup: error: checksum mismatch for file "pg_stat/junk" in archive "base.tar.lz4" pg_verifybackup: error: "base/4/3431" is present in the manifest but not on disk pg_verifybackup: error: "base/5/2620" is present in the manifest but not on disk pg_verifybackup: error: "base/1/2617" is present in the manifest but not on disk pg_verifybackup: error: "base/4/3576" is present in the manifest but not on disk ...... Using gzip or zstd compressions in the same setup doesn’t lead to any problems. Moreover, the lz4 archive itself is fine and it can be successfully unpacked via cli lz4 unpacker. The problem thus is in reading, and some investigation done by me and my colleagues lead us to astreamer_lz4.c file. Looks like astreamer_lz4_decompressor_content() function is initializing _out-parameters incorrectly: next_out = (uint8 *) mystreamer->base.bbs_buffer.data; avail_out = mystreamer->base.bbs_buffer.maxlen; , while should be: next_out = (uint8 *) mystreamer->base.bbs_buffer.data + mystreamer->bytes_written; avail_out = mystreamer->base.bbs_buffer.maxlen - mystreamer->bytes_written; And this would be consistent with what the gzip version of the function (astreamer_gzip_decompressor_content()) does. In the vast majority of cases the mystreamer->bytes_written value is zero at the moment of next_out/avail_out initialization, thus the problem does not appear. In fact we were totally unable to reproduce non-zero initial value of mystreamer->bytes_written without poor-compressible custom format files.I’m not that deep in the compression algorithms to say exactly why is that, and I realize that very few users will really face the problem, but the problem is definitely there. Attached is a simple patch implementing the above fix. The issue disappears with it. What do you think? -- best regards, Mikhail A. Gribkov -
Re: Decompression bug in astreamer_lz4
Michael Paquier <michael@paquier.xyz> — 2025-06-25T23:29:37Z
On Wed, Jun 25, 2025 at 11:07:32PM +0300, Mikhail Gribkov wrote: > The same situation can be reproduced using randomly filled files. Let’s > imagine the extension saves its own statistics in pg_stat: > dd if=/dev/urandom of=$PGDATA/pg_stat/junk bs=1M count=16 Original, I like that. It's not hard to imagine some extension code that decides to dump at the root of the data folder some file using one of the shutdown callbacks at the root of the data directory. Not seeing the problem would then be a matter of luck. So yes, that's a legit bug IMO. > In the vast majority of cases the mystreamer->bytes_written value is zero > at the moment of next_out/avail_out initialization, thus the problem does > not appear. In fact we were totally unable to reproduce non-zero initial > value of mystreamer->bytes_written without poor-compressible custom format > files.I’m not that deep in the compression algorithms to say exactly why is > that, and I realize that very few users will really face the problem, but > the problem is definitely there. That's not surprising, noted. > Attached is a simple patch implementing the above fix. The issue disappears > with it. It seems like you have spent some time digging into all that, thanks for all that. > What do you think? I think that it would be nice to have some test coverage for such cases in pg_verifybackup for both the client side and the server side of backups compressed, relying on the backend to generate some random data dumped into a file at the root of a data folder. You could just plug in that into one of the scripts, before taking a backup, and check all the compression methods we need to support. See for example brin.sql where with the combination of string_agg() and fipshash(), as one reference. So we could just reuse something like that. I am detecting a threshold close to 512kB to be able to reproduce the problem, so we don't need a file that large to see the failure, making such tests faster with less data required as long as the file is written once in a data folder used by all the backups taken: $ rm $PGDATA/junk && dd if=/dev/urandom of=$PGDATA/junk bs=1k count=512 $ rm -r /tmp/client-backup-lz4/* $ pg_basebackup --format=tar --no-sync -cfast \ -Xfetch --compress client-lz4 -D /tmp/client-backup-lz4 $ pg_verifybackup -n /tmp/client-backup-lz4/ pg_verifybackup: error: checksum mismatch for file "junk" in archive "base.tar.lz4" -- Michael
-
Re: Decompression bug in astreamer_lz4
Mikhail Gribkov <youzhick@gmail.com> — 2025-06-27T12:28:18Z
Hi Michael, > I think that it would be nice to have some test coverage for such > cases in pg_verifybackup for both the client side and the server side > of backups compressed, relying on the backend to generate some random > data dumped into a file at the root of a data folder. Yes, good point. Added checks to both client and server untar tests. I also noticed that the problem showup depends on compression level. For example in 008_untar.pl and 010_client_untar.pl tests it only showed up with compression levels 0 and 1. It looks to me that we do not have any chances to check all possible combinations of vital parameters, but at least we'll check several cases that we know make a difference. > I am detecting a threshold close to 512kB to be able to reproduce the > problem, so we don't need a file that large to see the failure, making > such tests faster with less data required I have raised the file size up to 640kB to make it a bit more reliable as far as we are not sure about all lz4-internals that trigger the problem. It still does not make the test data unreasonably large. Attached is the updated version of the patch with test additions. -- best regards, Mikhail A. Gribkov > -
Re: Decompression bug in astreamer_lz4
Michael Paquier <michael@paquier.xyz> — 2025-07-02T05:05:13Z
On Fri, Jun 27, 2025 at 03:28:18PM +0300, Mikhail Gribkov wrote: > Attached is the updated version of the patch with test additions. Thanks for the updated versions. A bit more variance with the LZ4 levels, even if we cannot track everything, does not sound like a bad thing to me for the server-side and client-side [de]compressions. I have reused what you have suggested, adjusting the tests in the back-branches based on the fact that int->bytea casts don't exist up to v17 and that the command structures were a bit different in test 008. And fixed down to v15. -- Michael