From 177c2dfe1ea463cc43a4856a466454bbea4ece2d Mon Sep 17 00:00:00 2001 From: Andrey Borodin Date: Tue, 6 Oct 2026 11:27:54 +0500 Subject: [PATCH v10 1/2] Reuse zstd decompression contexts when restoring WAL images Restoring each zstd-compressed full-page image currently creates and destroys a decompression context. Avoid that overhead by keeping one context per WAL reader, allocated on first use. Free the context in XLogReaderFree(). In backend builds, also register a reset callback with the reader's memory context so that errors do not leak the allocation made by zstd. Author: Andrey Borodin Reviewed-by: Michael Paquier Discussion: https://www.postgresql.org/message-id/asRsOl73nf8GeBFd@paquier.xyz --- src/backend/access/transam/xlogreader.c | 51 +++++++++++++++++++++++-- src/include/access/xlogreader.h | 13 +++++++ 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index 7db7c273b0c..4497eb52ec5 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -36,6 +36,7 @@ #ifndef FRONTEND #include "pgstat.h" #include "storage/bufmgr.h" +#include "utils/memutils.h" #include "utils/wait_event.h" #else #include "common/logging.h" @@ -55,6 +56,9 @@ static bool ValidXLogRecord(XLogReaderState *state, XLogRecord *record, static void ResetDecoder(XLogReaderState *state); static void WALOpenSegmentInit(WALOpenSegment *seg, WALSegmentContext *segcxt, int segsize, const char *waldir); +#if defined(USE_ZSTD) && !defined(FRONTEND) +static void XLogReaderFreeZstdContext(void *arg); +#endif /* size of the buffer allocated for error message. */ #define MAX_ERRORMSG_LEN 1000 @@ -171,10 +175,29 @@ XLogReaderFree(XLogReaderState *state) pfree(state->errormsg_buf); if (state->readRecordBuf) pfree(state->readRecordBuf); +#ifdef USE_ZSTD + if (state->zstd_dctx != NULL) + { +#ifndef FRONTEND + MemoryContextUnregisterResetCallback(GetMemoryChunkContext(state), + &state->zstd_dctx_cb); +#endif + ZSTD_freeDCtx(state->zstd_dctx); + } +#endif pfree(state->readBuf); pfree(state); } +#if defined(USE_ZSTD) && !defined(FRONTEND) +/* Release the zstd context even if an error bypasses XLogReaderFree(). */ +static void +XLogReaderFreeZstdContext(void *arg) +{ + ZSTD_freeDCtx(arg); +} +#endif + /* * Allocate readRecordBuf to fit a record of at least the given length. * @@ -2177,9 +2200,31 @@ RestoreBlockImage(XLogReaderState *record, uint8 block_id, char *page) else if ((bkpb->bimg_info & BKPIMAGE_COMPRESS_ZSTD) != 0) { #ifdef USE_ZSTD - size_t decomp_result = ZSTD_decompress(tmp.data, - BLCKSZ - bkpb->hole_length, - ptr, bkpb->bimg_len); + size_t decomp_result; + + if (record->zstd_dctx == NULL) + { + record->zstd_dctx = ZSTD_createDCtx(); + if (record->zstd_dctx == NULL) + { + report_invalid_record(record, "out of memory while restoring image at %X/%08X, block %d", + LSN_FORMAT_ARGS(record->ReadRecPtr), + block_id); + return false; + } + +#ifndef FRONTEND + /* The reader may outlive the current memory context. */ + record->zstd_dctx_cb.func = XLogReaderFreeZstdContext; + record->zstd_dctx_cb.arg = record->zstd_dctx; + MemoryContextRegisterResetCallback(GetMemoryChunkContext(record), + &record->zstd_dctx_cb); +#endif + } + + decomp_result = ZSTD_decompressDCtx(record->zstd_dctx, tmp.data, + BLCKSZ - bkpb->hole_length, + ptr, bkpb->bimg_len); if (ZSTD_isError(decomp_result)) decomp_success = false; diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h index 4a9a687e879..f139a61d046 100644 --- a/src/include/access/xlogreader.h +++ b/src/include/access/xlogreader.h @@ -34,8 +34,13 @@ #ifndef XLOGREADER_H #define XLOGREADER_H +#ifdef USE_ZSTD +#include +#endif + #ifndef FRONTEND #include "access/transam.h" +#include "utils/palloc.h" #endif #include "access/xlogrecord.h" @@ -239,6 +244,14 @@ struct XLogReaderState * ---------------------------------------- */ +#ifdef USE_ZSTD + /* Reused to decompress zstd-compressed full-page images. */ + ZSTD_DCtx *zstd_dctx; +#ifndef FRONTEND + MemoryContextCallback zstd_dctx_cb; +#endif +#endif + /* * Buffer for decoded records. This is a circular buffer, though * individual records can't be split in the middle, so some space is often -- That's all, folks. May the source be with you.