v5-0002-Compress-ReorderBuffer-spill-files-using-LZ4.patch
application/octet-stream
Filename: v5-0002-Compress-ReorderBuffer-spill-files-using-LZ4.patch
Type: application/octet-stream
Part: 1
Patch
Format: format-patch
Series: patch v5-0002
Subject: Compress ReorderBuffer spill files using LZ4
| File | + | − |
|---|---|---|
| src/backend/replication/logical/Makefile | 1 | 0 |
| src/backend/replication/logical/meson.build | 1 | 0 |
| src/backend/replication/logical/reorderbuffer.c | 88 | 1 |
| src/backend/replication/logical/reorderbuffer_lz4.c | 268 | 0 |
| src/include/replication/reorderbuffer_compression.h | 49 | 0 |
From 3fa3afc0f0ae660b5ab7f6c7851f749adea9e421 Mon Sep 17 00:00:00 2001
From: Julien Tachoires <julmon@gmail.com>
Date: Tue, 22 Oct 2024 20:40:42 +0200
Subject: [PATCH 2/6] Compress ReorderBuffer spill files using LZ4
---
src/backend/replication/logical/Makefile | 1 +
src/backend/replication/logical/meson.build | 1 +
.../replication/logical/reorderbuffer.c | 89 +++++-
.../replication/logical/reorderbuffer_lz4.c | 268 ++++++++++++++++++
.../replication/reorderbuffer_compression.h | 49 ++++
5 files changed, 407 insertions(+), 1 deletion(-)
create mode 100644 src/backend/replication/logical/reorderbuffer_lz4.c
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 96c733d009..58dce86258 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -27,6 +27,7 @@ OBJS = \
relation.o \
reorderbuffer.o \
reorderbuffer_pglz.o \
+ reorderbuffer_lz4.o \
slotsync.o \
snapbuild.o \
tablesync.o \
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index 48df907b35..dc2f55d0fa 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -13,6 +13,7 @@ backend_sources += files(
'relation.c',
'reorderbuffer.c',
'reorderbuffer_pglz.c',
+ 'reorderbuffer_lz4.c',
'slotsync.c',
'snapbuild.c',
'tablesync.c',
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 649b196d5f..5d6f4bbfca 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -219,7 +219,7 @@ static const Size max_changes_in_memory = 4096; /* XXX for restore only */
int debug_logical_replication_streaming = DEBUG_LOGICAL_REP_STREAMING_BUFFERED;
/* Compression strategy for spilled data. */
-int logical_decoding_spill_compression = REORDER_BUFFER_PGLZ_COMPRESSION;
+int logical_decoding_spill_compression = REORDER_BUFFER_LZ4_COMPRESSION;
/* ---------------------------------------
* primary reorderbuffer support routines
@@ -5408,6 +5408,9 @@ ReorderBufferNewCompressorState(MemoryContext context, int compression_method)
case REORDER_BUFFER_PGLZ_COMPRESSION:
return pglz_NewCompressorState(context);
break;
+ case REORDER_BUFFER_LZ4_COMPRESSION:
+ return lz4_NewCompressorState(context);
+ break;
case REORDER_BUFFER_NO_COMPRESSION:
default:
return NULL;
@@ -5428,6 +5431,9 @@ ReorderBufferFreeCompressorState(MemoryContext context, int compression_method,
case REORDER_BUFFER_PGLZ_COMPRESSION:
return pglz_FreeCompressorState(context, compressor_state);
break;
+ case REORDER_BUFFER_LZ4_COMPRESSION:
+ return lz4_FreeCompressorState(context, compressor_state);
+ break;
case REORDER_BUFFER_NO_COMPRESSION:
default:
break;
@@ -5494,6 +5500,67 @@ ReorderBufferCompress(ReorderBuffer *rb, ReorderBufferDiskChange **ondisk,
break;
}
+ /* LZ4 Compression */
+ case REORDER_BUFFER_LZ4_COMPRESSION:
+ {
+ Size dst_size = 0;
+ char *src = (char *) rb->outbuf + sizeof(ReorderBufferDiskChange);
+ Size src_size = data_size - sizeof(ReorderBufferDiskChange);
+ StringInfo buf = lz4_GetStringInfoBuffer(compressor_state);
+
+ /*
+ * LZ4 streaming compression implies keeping a copy of the
+ * "raw" data in LZ4 input ring buffer. If the "raw" data does
+ * not fit in this buffer, then we should not try to compress
+ * it. Let's store it uncompressed.
+ *
+ * Even if individual changes larger than 64kB shouldn't
+ * exist, we still want to be sure that this case is covered
+ * anyway.
+ */
+ if (unlikely(src_size > LZ4_RING_BUFFER_SIZE))
+ return ReorderBufferCompress(rb, ondisk,
+ REORDER_BUFFER_NO_COMPRESSION,
+ data_size, compressor_state);
+
+ /*
+ * Make sure the buffer we'll use to store compressed data has
+ * enough space.
+ */
+ dst_size = lz4_CompressBound(src_size);
+ enlargeStringInfo(buf, dst_size);
+
+ /* Use LZ4 streaming compression */
+ lz4_StreamingCompressData(rb->context, src, src_size, buf->data,
+ &dst_size, compressor_state);
+ buf->len = dst_size;
+
+ /*
+ * Make sure the ReorderBuffer has enough space to store
+ * compressed data. Compressed data must be smaller than raw
+ * data, so, the ReorderBuffer should already have room for
+ * compressed data, but we do this to avoid buffer overflow
+ * risks.
+ */
+ ReorderBufferSerializeReserve(rb, (dst_size + sizeof(ReorderBufferDiskChange)));
+
+ hdr = (ReorderBufferDiskChange *) rb->outbuf;
+ hdr->comp_strat = REORDER_BUFFER_STRAT_LZ4_STREAMING;
+ hdr->size = dst_size + sizeof(ReorderBufferDiskChange);
+ hdr->raw_size = src_size;
+
+ /*
+ * Update ondisk: hdr pointer has potentially changed due to
+ * ReorderBufferSerializeReserve()
+ */
+ *ondisk = hdr;
+
+ /* Copy back compressed data into the ReorderBuffer */
+ memcpy((char *) rb->outbuf + sizeof(ReorderBufferDiskChange),
+ buf->data, buf->len);
+
+ break;
+ }
default:
/* Other compression methods not yet supported */
break;
@@ -5549,6 +5616,26 @@ ReorderBufferDecompress(ReorderBuffer *rb, char *data,
errmsg_internal("compressed PGLZ data is corrupted")));
break;
}
+ /* LZ4 streaming decompression */
+ case REORDER_BUFFER_STRAT_LZ4_STREAMING:
+ {
+ char *buf = NULL;
+ Size src_size = ondisk->size - sizeof(ReorderBufferDiskChange);
+ Size buf_size = ondisk->raw_size;
+
+ lz4_StreamingDecompressData(rb->context, data, src_size, &buf,
+ buf_size, compressor_state);
+
+ /* Copy decompressed data into the ReorderBuffer */
+ memcpy((char *) rb->outbuf + sizeof(ReorderBufferDiskChange),
+ buf, buf_size);
+
+ /*
+ * Not necessary to free buf in this case: it points to the
+ * decompressed data stored in LZ4 output ring buffer.
+ */
+ break;
+ }
default:
/* Other compression methods not yet supported */
break;
diff --git a/src/backend/replication/logical/reorderbuffer_lz4.c b/src/backend/replication/logical/reorderbuffer_lz4.c
new file mode 100644
index 0000000000..b9a7e717aa
--- /dev/null
+++ b/src/backend/replication/logical/reorderbuffer_lz4.c
@@ -0,0 +1,268 @@
+/*-------------------------------------------------------------------------
+ *
+ * reorderbuffer_lz4.c
+ * Functions for ReorderBuffer compression using LZ4.
+ *
+ * Copyright (c) 2024-2024, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/common/reorderbuffer_lz4.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#ifdef USE_LZ4
+#include <lz4.h>
+#endif
+
+#include "replication/reorderbuffer_compression.h"
+
+#define NO_LZ4_SUPPORT() \
+ ereport(ERROR, \
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
+ errmsg("compression method lz4 not supported"), \
+ errdetail("This functionality requires the server to be built with lz4 support.")))
+
+/*
+ * Allocate a new LZ4StreamingCompressorState.
+ */
+void *
+lz4_NewCompressorState(MemoryContext context)
+{
+#ifndef USE_LZ4
+ NO_LZ4_SUPPORT();
+ return NULL; /* keep compiler quiet */
+#else
+ LZ4StreamingCompressorState *cstate;
+ MemoryContext oldcontext = MemoryContextSwitchTo(context);
+
+ cstate = (LZ4StreamingCompressorState *)
+ MemoryContextAlloc(context, sizeof(LZ4StreamingCompressorState));
+
+ cstate->buf = makeStringInfo();
+
+ /*
+ * We do not allocate LZ4 ring buffers and streaming handlers at this
+ * point because we have no guarantee that we will need them later. Let's
+ * allocate only when we are about to use them.
+ */
+ cstate->lz4_in_buf = NULL;
+ cstate->lz4_out_buf = NULL;
+ cstate->lz4_in_buf_offset = 0;
+ cstate->lz4_out_buf_offset = 0;
+ cstate->lz4_stream = NULL;
+ cstate->lz4_stream_decode = NULL;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return (void *) cstate;
+#endif
+}
+
+/*
+ * Free LZ4 memory resources and the compressor state.
+ */
+void
+lz4_FreeCompressorState(MemoryContext context, void *compressor_state)
+{
+#ifndef USE_LZ4
+ NO_LZ4_SUPPORT();
+#else
+ LZ4StreamingCompressorState *cstate;
+ MemoryContext oldcontext;
+
+ if (compressor_state == NULL)
+ return;
+
+ oldcontext = MemoryContextSwitchTo(context);
+
+ cstate = (LZ4StreamingCompressorState *) compressor_state;
+
+ destroyStringInfo(cstate->buf);
+
+ if (cstate->lz4_in_buf != NULL)
+ {
+ pfree(cstate->lz4_in_buf);
+ LZ4_freeStream(cstate->lz4_stream);
+ }
+ if (cstate->lz4_out_buf != NULL)
+ {
+ pfree(cstate->lz4_out_buf);
+ LZ4_freeStreamDecode(cstate->lz4_stream_decode);
+ }
+
+ pfree(compressor_state);
+
+ MemoryContextSwitchTo(oldcontext);
+#endif
+}
+
+#ifdef USE_LZ4
+/*
+ * Allocate LZ4 input ring buffer and create the streaming compression handler.
+ */
+static void
+lz4_CreateStreamCompressorState(MemoryContext context, void *compressor_state)
+{
+ LZ4StreamingCompressorState *cstate;
+ MemoryContext oldcontext = MemoryContextSwitchTo(context);
+
+ cstate = (LZ4StreamingCompressorState *) compressor_state;
+ cstate->lz4_in_buf = (char *) palloc0(LZ4_RING_BUFFER_SIZE);
+ cstate->lz4_stream = LZ4_createStream();
+
+ MemoryContextSwitchTo(oldcontext);
+}
+#endif
+
+#ifdef USE_LZ4
+/*
+ * Allocate LZ4 output ring buffer and create the streaming decompression
+ */
+static void
+lz4_CreateStreamDecodeCompressorState(MemoryContext context,
+ void *compressor_state)
+{
+ LZ4StreamingCompressorState *cstate;
+ MemoryContext oldcontext = MemoryContextSwitchTo(context);
+
+ cstate = (LZ4StreamingCompressorState *) compressor_state;
+ cstate->lz4_out_buf = (char *) palloc0(LZ4_RING_BUFFER_SIZE);
+ cstate->lz4_stream_decode = LZ4_createStreamDecode();
+
+ MemoryContextSwitchTo(oldcontext);
+}
+#endif
+
+/*
+ * Data compression using LZ4 streaming API.
+ */
+void
+lz4_StreamingCompressData(MemoryContext context, char *src, Size src_size,
+ char *dst, Size *dst_size, void *compressor_state)
+{
+#ifndef USE_LZ4
+ NO_LZ4_SUPPORT();
+#else
+ LZ4StreamingCompressorState *cstate;
+ int lz4_cmp_size = 0; /* compressed size */
+ char *lz4_in_bufPtr; /* input ring buffer pointer */
+
+ cstate = (LZ4StreamingCompressorState *) compressor_state;
+
+ /* Allocate LZ4 input ring buffer and streaming compression handler */
+ if (cstate->lz4_in_buf == NULL)
+ lz4_CreateStreamCompressorState(context, compressor_state);
+
+ /* Ring buffer offset wraparound */
+ if ((cstate->lz4_in_buf_offset + src_size) > LZ4_RING_BUFFER_SIZE)
+ cstate->lz4_in_buf_offset = 0;
+
+ /* Get the pointer of the next entry in the ring buffer */
+ lz4_in_bufPtr = cstate->lz4_in_buf + cstate->lz4_in_buf_offset;
+
+ /* Copy data that should be compressed into LZ4 input ring buffer */
+ memcpy(lz4_in_bufPtr, src, src_size);
+
+ /* Use LZ4 streaming compression API */
+ lz4_cmp_size = LZ4_compress_fast_continue(cstate->lz4_stream,
+ lz4_in_bufPtr, dst, src_size,
+ *dst_size, 1);
+
+ if (lz4_cmp_size <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg_internal("LZ4 compression failed")));
+
+ /* Move the input ring buffer offset */
+ cstate->lz4_in_buf_offset += src_size;
+
+ *dst_size = lz4_cmp_size;
+#endif
+}
+
+/*
+ * Data decompression using LZ4 streaming API.
+ * LZ4 decompression uses the output ring buffer to store decompressed data,
+ * thus, we don't need to create a new buffer. We return the pointer to data
+ * location.
+ */
+void
+lz4_StreamingDecompressData(MemoryContext context, char *src, Size src_size,
+ char **dst, Size dst_size, void *compressor_state)
+{
+#ifndef USE_LZ4
+ NO_LZ4_SUPPORT();
+#else
+ LZ4StreamingCompressorState *cstate;
+ char *lz4_out_bufPtr; /* output ring buffer pointer */
+ int lz4_dec_size; /* decompressed data size */
+
+ cstate = (LZ4StreamingCompressorState *) compressor_state;
+
+ /* Allocate LZ4 output ring buffer and streaming decompression handler */
+ if (cstate->lz4_out_buf == NULL)
+ lz4_CreateStreamDecodeCompressorState(context, compressor_state);
+
+ /* Ring buffer offset wraparound */
+ if ((cstate->lz4_out_buf_offset + dst_size) > LZ4_RING_BUFFER_SIZE)
+ cstate->lz4_out_buf_offset = 0;
+
+ /* Get current entry pointer in the ring buffer */
+ lz4_out_bufPtr = cstate->lz4_out_buf + cstate->lz4_out_buf_offset;
+
+ lz4_dec_size = LZ4_decompress_safe_continue(cstate->lz4_stream_decode,
+ src,
+ lz4_out_bufPtr,
+ src_size,
+ dst_size);
+
+ Assert(lz4_dec_size == dst_size);
+
+ if (lz4_dec_size < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg_internal("compressed LZ4 data is corrupted")));
+ else if (lz4_dec_size != dst_size)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg_internal("decompressed LZ4 data size differs from original size")));
+
+ /* Move the output ring buffer offset */
+ cstate->lz4_out_buf_offset += lz4_dec_size;
+
+ /* Point to the decompressed data location */
+ *dst = lz4_out_bufPtr;
+#endif
+}
+
+Size
+lz4_CompressBound(Size src_size)
+{
+#ifndef USE_LZ4
+ NO_LZ4_SUPPORT();
+ return -1;
+#else
+ return LZ4_COMPRESSBOUND(src_size);
+#endif
+}
+
+/*
+ * Returns the StringInfo buffer we use to store compressed/decompressed data.
+ */
+StringInfo
+lz4_GetStringInfoBuffer(void *compressor_state)
+{
+#ifndef USE_LZ4
+ NO_LZ4_SUPPORT();
+ return NULL;
+#else
+ LZ4StreamingCompressorState *cstate;
+
+ cstate = (LZ4StreamingCompressorState *) compressor_state;
+
+ return cstate->buf;
+#endif
+}
diff --git a/src/include/replication/reorderbuffer_compression.h b/src/include/replication/reorderbuffer_compression.h
index 9e9565ca7f..3dbf47e18e 100644
--- a/src/include/replication/reorderbuffer_compression.h
+++ b/src/include/replication/reorderbuffer_compression.h
@@ -13,11 +13,16 @@
#ifndef REORDERBUFFER_COMPRESSION_H
#define REORDERBUFFER_COMPRESSION_H
+#ifdef USE_LZ4
+#include <lz4.h>
+#endif
+
/* ReorderBuffer on disk compression algorithms */
typedef enum ReorderBufferCompressionMethod
{
REORDER_BUFFER_NO_COMPRESSION,
REORDER_BUFFER_PGLZ_COMPRESSION,
+ REORDER_BUFFER_LZ4_COMPRESSION,
} ReorderBufferCompressionMethod;
/*
@@ -27,6 +32,7 @@ typedef enum ReorderBufferCompressionStrategy
{
REORDER_BUFFER_STRAT_UNCOMPRESSED,
REORDER_BUFFER_STRAT_PGLZ,
+ REORDER_BUFFER_STRAT_LZ4_STREAMING,
} ReorderBufferCompressionStrategy;
typedef struct PGLZCompressorState
@@ -35,6 +41,49 @@ typedef struct PGLZCompressorState
StringInfo buf;
} PGLZCompressorState;
+#ifdef USE_LZ4
+/*
+ * We use a fairly small LZ4 ring buffer size (64kB). Using a larger buffer
+ * size provide better compression ratio, but as long as we have to allocate
+ * two LZ4 ring buffers per ReorderBufferTXN, we should keep it small.
+
+ * 64kB is also twice the maximum size of a block, which is enough to cover
+ * changes like UPDATE that will contain data of the old and new version of a
+ * tuple.
+ */
+#define LZ4_RING_BUFFER_SIZE (64 * 1024)
+
+/*
+ * LZ4 streaming compression/decompression contextes and buffers.
+ */
+typedef struct LZ4StreamingCompressorState
+{
+ /* Streaming compression handler */
+ LZ4_stream_t *lz4_stream;
+ /* Streaming decompression handler */
+ LZ4_streamDecode_t *lz4_stream_decode;
+ /* LZ4 in/out ring buffers used for streaming compression */
+ char *lz4_in_buf;
+ int lz4_in_buf_offset;
+ char *lz4_out_buf;
+ int lz4_out_buf_offset;
+ /* Buffer used to store compressed data */
+ StringInfo buf;
+} LZ4StreamingCompressorState;
+#endif
+
+extern void *lz4_NewCompressorState(MemoryContext context);
+extern void lz4_FreeCompressorState(MemoryContext context,
+ void *compressor_state);
+extern void lz4_StreamingCompressData(MemoryContext context, char *src,
+ Size src_size, char *dst, Size *dst_size,
+ void *compressor_state);
+extern void lz4_StreamingDecompressData(MemoryContext context, char *src,
+ Size src_size, char **dst,
+ Size dst_size, void *compressor_state);
+extern Size lz4_CompressBound(Size src_size);
+extern StringInfo lz4_GetStringInfoBuffer(void *compressor_state);
+
extern void *pglz_NewCompressorState(MemoryContext context);
extern void pglz_FreeCompressorState(MemoryContext context,
void *compressor_state);
--
2.43.0