From 4356434761e4f8cc1e64bf0aec6a8459db689451 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Tue, 25 Nov 2014 14:24:26 +0900 Subject: [PATCH 2/2] Support compression for full-page writes in WAL full_page_writes has a new setting value called 'compress' allowing a user to reduce the amount of data written to WAL for full-page writes. --- contrib/pg_xlogdump/committsdesc.c | 1 + contrib/pg_xlogdump/pg_xlogdump.c | 28 +++++- doc/src/sgml/config.sgml | 28 ++++++ src/backend/access/transam/xlog.c | 5 + src/backend/access/transam/xloginsert.c | 132 +++++++++++++++++++++++--- src/backend/access/transam/xlogreader.c | 39 +++++++- src/backend/utils/misc/guc.c | 9 ++ src/backend/utils/misc/postgresql.conf.sample | 2 + src/bin/pg_controldata/pg_controldata.c | 2 + src/bin/pg_resetxlog/pg_resetxlog.c | 2 + src/include/access/xlog.h | 1 + src/include/access/xlog_internal.h | 1 + src/include/access/xlogreader.h | 4 + src/include/access/xlogrecord.h | 7 +- src/include/catalog/pg_control.h | 1 + 15 files changed, 236 insertions(+), 26 deletions(-) create mode 120000 contrib/pg_xlogdump/committsdesc.c diff --git a/contrib/pg_xlogdump/committsdesc.c b/contrib/pg_xlogdump/committsdesc.c new file mode 120000 index 0000000..9921611 --- /dev/null +++ b/contrib/pg_xlogdump/committsdesc.c @@ -0,0 +1 @@ +../../src/backend/access/rmgrdesc/committsdesc.c \ No newline at end of file diff --git a/contrib/pg_xlogdump/pg_xlogdump.c b/contrib/pg_xlogdump/pg_xlogdump.c index 26556dc..0227e1b 100644 --- a/contrib/pg_xlogdump/pg_xlogdump.c +++ b/contrib/pg_xlogdump/pg_xlogdump.c @@ -351,14 +351,31 @@ XLogDumpCountRecord(XLogDumpConfig *config, XLogDumpStats *stats, uint8 recid; uint32 rec_len; uint32 fpi_len; + int block_id; stats->count++; - /* Update per-rmgr statistics */ - rmid = XLogRecGetRmid(record); rec_len = XLogRecGetDataLen(record) + SizeOfXLogRecord; - fpi_len = record->decoded_record->xl_tot_len - rec_len; + + /* + * Calculate the amount of FPI data in the record. Each backup block + * takes up BLCKSZ bytes, minus the "hole" length. + * + * XXX: We peek into xlogreader's private decoded backup blocks for the + * hole_length. It doesn't seem worth it to add an accessor macro for + * this. + */ + fpi_len = 0; + for (block_id = 0; block_id <= record->max_block_id; block_id++) + { + if (XLogRecHasCompressedBlockImage(record, block_id)) + fpi_len += BLCKSZ - record->blocks[block_id].compress_len; + else if (XLogRecHasBlockImage(record, block_id)) + fpi_len += BLCKSZ - record->blocks[block_id].hole_length; + } + + /* Update per-rmgr statistics */ stats->rmgr_stats[rmid].count++; stats->rmgr_stats[rmid].rec_len += rec_len; @@ -450,9 +467,10 @@ XLogDumpDisplayRecord(XLogDumpConfig *config, XLogReaderState *record) blk); if (XLogRecHasBlockImage(record, block_id)) { - printf(" (FPW); hole: offset: %u, length: %u\n", + printf(" (FPW); hole: offset: %u, length: %u, compressed: %u\n", record->blocks[block_id].hole_offset, - record->blocks[block_id].hole_length); + record->blocks[block_id].hole_length, + record->blocks[block_id].compress_len); } putchar('\n'); } diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index d607eca..aded6e5 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -2254,6 +2254,34 @@ include_dir 'conf.d' + + wal_compression (boolean) + + wal_compression configuration parameter + + + + + When this parameter is on, the PostgreSQL + server compresses the content of full-page writes when necessary and + inserts in WAL a record with a largely smaller size. + + + + Compression has the advantage of reducing the amount of disk I/O when + doing WAL-logging, at the cost of some extra CPU to perform the + compression of an image. At WAL replay, compressed images still need + some more CPU cycles to perform the decompression of each block image, + but it can reduce as well replay time in I/O bounded environments. + + + + This parameter can only be set at server start. The default value is + off. + + + + wal_buffers (integer) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index da28de9..7b89932 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -88,6 +88,7 @@ char *XLogArchiveCommand = NULL; bool EnableHotStandby = false; bool fullPageWrites = true; bool wal_log_hints = false; +bool wal_compression = false; bool log_checkpoints = false; int sync_method = DEFAULT_SYNC_METHOD; int wal_level = WAL_LEVEL_MINIMAL; @@ -4610,6 +4611,7 @@ BootStrapXLOG(void) ControlFile->max_locks_per_xact = max_locks_per_xact; ControlFile->wal_level = wal_level; ControlFile->wal_log_hints = wal_log_hints; + ControlFile->wal_compression = wal_compression; ControlFile->track_commit_timestamp = track_commit_timestamp; ControlFile->data_checksum_version = bootstrap_data_checksum_version; @@ -8498,6 +8500,7 @@ XLogReportParameters(void) { if (wal_level != ControlFile->wal_level || wal_log_hints != ControlFile->wal_log_hints || + wal_compression != ControlFile->wal_compression || MaxConnections != ControlFile->MaxConnections || max_worker_processes != ControlFile->max_worker_processes || max_prepared_xacts != ControlFile->max_prepared_xacts || @@ -8522,6 +8525,7 @@ XLogReportParameters(void) xlrec.max_locks_per_xact = max_locks_per_xact; xlrec.wal_level = wal_level; xlrec.wal_log_hints = wal_log_hints; + xlrec.wal_compression = wal_compression; xlrec.track_commit_timestamp = track_commit_timestamp; XLogBeginInsert(); @@ -8537,6 +8541,7 @@ XLogReportParameters(void) ControlFile->max_locks_per_xact = max_locks_per_xact; ControlFile->wal_level = wal_level; ControlFile->wal_log_hints = wal_log_hints; + ControlFile->wal_compression = wal_compression; ControlFile->track_commit_timestamp = track_commit_timestamp; UpdateControlFile(); } diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index f3d610f..537aee9 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -27,6 +27,7 @@ #include "miscadmin.h" #include "storage/bufmgr.h" #include "storage/proc.h" +#include "utils/pg_lzcompress.h" #include "utils/memutils.h" #include "pg_trace.h" @@ -50,6 +51,8 @@ typedef struct XLogRecData bkp_rdatas[2]; /* temporary rdatas used to hold references to * backup block data in XLogRecordAssemble() */ + char *compressed_page; /* recipient for compressed page, NULL + * if compression is not activated */ } registered_buffer; static registered_buffer *registered_buffers; @@ -97,6 +100,9 @@ static XLogRecData *XLogRecordAssemble(RmgrId rmid, uint8 info, XLogRecPtr RedoRecPtr, bool doPageWrites, XLogRecPtr *fpw_lsn); +static bool XLogCompressBackupBlock(char *page, uint32 orig_len, + char *dest, uint16 *len); + /* * Begin constructing a WAL record. This must be called before the * XLogRegister* functions and XLogInsert(). @@ -150,6 +156,7 @@ XLogEnsureRecordSpace(int max_block_id, int ndatas) if (nbuffers > max_registered_buffers) { + int i; registered_buffers = (registered_buffer *) repalloc(registered_buffers, sizeof(registered_buffer) * nbuffers); @@ -159,6 +166,14 @@ XLogEnsureRecordSpace(int max_block_id, int ndatas) */ MemSet(®istered_buffers[max_registered_buffers], 0, (nbuffers - max_registered_buffers) * sizeof(registered_buffer)); + for (i = max_registered_buffers; i < nbuffers; i++) + { + if (wal_compression) + registered_buffers[i].compressed_page = (char *) + MemoryContextAllocZero(xloginsert_cxt, PGLZ_MAX_OUTPUT(BLCKSZ)); + else + registered_buffers[i].compressed_page = NULL; + } max_registered_buffers = nbuffers; } @@ -529,6 +544,7 @@ XLogRecordAssemble(RmgrId rmid, uint8 info, if (needs_backup) { Page page = regbuf->page; + int compression_done = false; /* * The page needs to be backed up, so set up *bimg @@ -563,29 +579,77 @@ XLogRecordAssemble(RmgrId rmid, uint8 info, /* Fill in the remaining fields in the XLogRecordBlockData struct */ bkpb.fork_flags |= BKPBLOCK_HAS_IMAGE; - total_len += BLCKSZ - bimg.hole_length; - /* - * Construct XLogRecData entries for the page content. + * Construct XLogRecData entries for the page content. If page + * compression is enabled instead of creating a new entry store + * the data in dedicated buffer to prepare for the compression. + * If page has a hole skip it, allowing to achieve a two-level + * of compression. */ - rdt_datas_last->next = ®buf->bkp_rdatas[0]; - rdt_datas_last = rdt_datas_last->next; - if (bimg.hole_length == 0) + if (wal_compression) { - rdt_datas_last->data = page; - rdt_datas_last->len = BLCKSZ; + int page_len = BLCKSZ - bimg.hole_length; + char uncompressed_page[PGLZ_MAX_OUTPUT(BLCKSZ)]; + uint16 compression_len; + + /* shape block image for compression and skip hole if any */ + if (bimg.hole_length == 0) + memcpy(uncompressed_page, page, BLCKSZ); + else + { + /* Copy page content without hole */ + memcpy(uncompressed_page, page, bimg.hole_offset); + memcpy(uncompressed_page + bimg.hole_offset, + page + bimg.hole_offset + bimg.hole_length, + BLCKSZ - (bimg.hole_offset + bimg.hole_length)); + } + + /* Perform compression of block */ + if (XLogCompressBackupBlock(uncompressed_page, + page_len, + regbuf->compressed_page, + &compression_len)) + { + /* compression is done, add record */ + compression_done = true; + bimg.compress_len = compression_len; + + rdt_datas_last->next = ®buf->bkp_rdatas[0]; + rdt_datas_last = rdt_datas_last->next; + rdt_datas_last->data = regbuf->compressed_page; + rdt_datas_last->len = compression_len; + total_len += compression_len; + } } - else + + /* + * If compression has not been done store normally this + * block image. + */ + if (!compression_done) { - /* must skip the hole */ - rdt_datas_last->data = page; - rdt_datas_last->len = bimg.hole_offset; + total_len += BLCKSZ - bimg.hole_length; - rdt_datas_last->next = ®buf->bkp_rdatas[1]; + rdt_datas_last->next = ®buf->bkp_rdatas[0]; rdt_datas_last = rdt_datas_last->next; + if (bimg.hole_length == 0) + { + rdt_datas_last->data = page; + rdt_datas_last->len = BLCKSZ; + } + else + { + /* must skip the hole */ + rdt_datas_last->data = page; + rdt_datas_last->len = bimg.hole_offset; + + rdt_datas_last->next = ®buf->bkp_rdatas[1]; + rdt_datas_last = rdt_datas_last->next; - rdt_datas_last->data = page + (bimg.hole_offset + bimg.hole_length); - rdt_datas_last->len = BLCKSZ - (bimg.hole_offset + bimg.hole_length); + rdt_datas_last->data = page + (bimg.hole_offset + bimg.hole_length); + rdt_datas_last->len = BLCKSZ - (bimg.hole_offset + bimg.hole_length); + } + bimg.compress_len = 0; } } @@ -681,6 +745,35 @@ XLogRecordAssemble(RmgrId rmid, uint8 info, } /* + * Create a compressed version of a backup block. If successful, return + * true and set 'len' to its length. If block cannot be compressed or if + * compression failed return false. + */ +static bool +XLogCompressBackupBlock(char *page, uint32 orig_len, char *dest, uint16 *len) +{ + /* leave if data can not be compressed */ + if (pglz_compress(page, orig_len, (PGLZ_Header *) dest, + PGLZ_strategy_default) != PGLZ_OK) + return false; + + /* + * We recheck the actual size even if pglz_compress() report success, + * because it might be satisfied with having saved as little as one byte + * in the compressed data --- which could turn into a net loss once you + * consider header and alignment padding. Worst case, the compressed + * format might require three padding bytes (plus header, which is + * included in VARSIZE(buf)), whereas the uncompressed format would take + * only one header byte and no padding if the value is short enough. So + * we insist on a savings of more than 2 bytes to ensure we have a gain. + */ + *len = VARSIZE((struct varlena *) dest); + if (*len >= orig_len - 2) + return false; + return true; +} + +/* * Determine whether the buffer referenced has to be backed up. * * Since we don't yet have the insert lock, fullPageWrites and forcePageWrites @@ -875,9 +968,18 @@ InitXLogInsert(void) if (registered_buffers == NULL) { + int i; registered_buffers = (registered_buffer *) MemoryContextAllocZero(xloginsert_cxt, sizeof(registered_buffer) * (XLR_NORMAL_MAX_BLOCK_ID + 1)); + for (i = 0; i < XLR_NORMAL_MAX_BLOCK_ID + 1; i++) + { + if (wal_compression) + registered_buffers[i].compressed_page = (char *) + MemoryContextAllocZero(xloginsert_cxt, PGLZ_MAX_OUTPUT(BLCKSZ)); + else + registered_buffers[i].compressed_page = NULL; + } max_registered_buffers = XLR_NORMAL_MAX_BLOCK_ID + 1; } if (rdatas == NULL) diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index 67d6223..462266a 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -20,6 +20,7 @@ #include "access/xlog_internal.h" #include "access/xlogreader.h" #include "catalog/pg_control.h" +#include "utils/pg_lzcompress.h" static bool allocate_recordbuf(XLogReaderState *state, uint32 reclength); @@ -1034,7 +1035,13 @@ DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, char **errormsg) { COPY_HEADER_FIELD(&blk->hole_offset, sizeof(uint16)); COPY_HEADER_FIELD(&blk->hole_length, sizeof(uint16)); - datatotal += BLCKSZ - blk->hole_length; + COPY_HEADER_FIELD(&blk->compress_len, sizeof(uint16)); + + /* adapt depending on presence of compressed image */ + if (blk->compress_len != 0) + datatotal += blk->compress_len; + else + datatotal += BLCKSZ - blk->hole_length; } if (!(fork_flags & BKPBLOCK_SAME_REL)) { @@ -1089,7 +1096,12 @@ DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, char **errormsg) if (blk->has_image) { blk->bkp_image = ptr; - ptr += BLCKSZ - blk->hole_length; + + /* adapt depending on presence of compressed image */ + if (blk->compress_len != 0) + ptr += blk->compress_len; + else + ptr += BLCKSZ - blk->hole_length; } if (blk->has_data) { @@ -1195,6 +1207,8 @@ bool RestoreBlockImage(XLogReaderState *record, uint8 block_id, char *page) { DecodedBkpBlock *bkpb; + char *uncompressed_page = NULL; + char *block_image; if (!record->blocks[block_id].in_use) return false; @@ -1202,20 +1216,35 @@ RestoreBlockImage(XLogReaderState *record, uint8 block_id, char *page) return false; bkpb = &record->blocks[block_id]; + block_image = bkpb->bkp_image; + + /* decompress block if needed before processing */ + if (bkpb->compress_len != 0) + { + PGLZ_Header *header = (PGLZ_Header *) block_image; + uncompressed_page = (char *) + palloc(PGLZ_RAW_SIZE(header)); + /* XXX: should check for status code here */ + pglz_decompress(header, uncompressed_page); + block_image = uncompressed_page; + } + /* generate page, taking into account hole if necessary */ if (bkpb->hole_length == 0) { - memcpy(page, bkpb->bkp_image, BLCKSZ); + memcpy(page, block_image, BLCKSZ); } else { - memcpy(page, bkpb->bkp_image, bkpb->hole_offset); + memcpy(page, block_image, bkpb->hole_offset); /* must zero-fill the hole */ MemSet(page + bkpb->hole_offset, 0, bkpb->hole_length); memcpy(page + (bkpb->hole_offset + bkpb->hole_length), - bkpb->bkp_image + bkpb->hole_offset, + block_image + bkpb->hole_offset, BLCKSZ - (bkpb->hole_offset + bkpb->hole_length)); } + if (uncompressed_page) + pfree(uncompressed_page); return true; } diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index b1bff7f..8fd742f 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -927,6 +927,15 @@ static struct config_bool ConfigureNamesBool[] = false, NULL, NULL, NULL }, + { + {"wal_compression", PGC_POSTMASTER, WAL_SETTINGS, + gettext_noop("Compresses full-page writes written in WAL file."), + NULL + }, + &wal_compression, + false, + NULL, NULL, NULL + }, { {"log_checkpoints", PGC_SIGHUP, LOGGING_WHAT, diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index c4b546e..279de4b 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -190,6 +190,8 @@ #wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers # (change requires restart) #wal_writer_delay = 200ms # 1-10000 milliseconds +#wal_compression = off # enable compression of full-page writes + # (change requires restart) #commit_delay = 0 # range 0-100000, in microseconds #commit_siblings = 5 # range 1-1000 diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c index a838bb5..c15f5f4 100644 --- a/src/bin/pg_controldata/pg_controldata.c +++ b/src/bin/pg_controldata/pg_controldata.c @@ -294,6 +294,8 @@ main(int argc, char *argv[]) wal_level_str(ControlFile.wal_level)); printf(_("Current wal_log_hints setting: %s\n"), ControlFile.wal_log_hints ? _("on") : _("off")); + printf(_("Current wal_compression setting: %s\n"), + ControlFile.wal_compression ? _("on") : _("off")); printf(_("Current max_connections setting: %d\n"), ControlFile.MaxConnections); printf(_("Current max_worker_processes setting: %d\n"), diff --git a/src/bin/pg_resetxlog/pg_resetxlog.c b/src/bin/pg_resetxlog/pg_resetxlog.c index f42d515..f4abe3c 100644 --- a/src/bin/pg_resetxlog/pg_resetxlog.c +++ b/src/bin/pg_resetxlog/pg_resetxlog.c @@ -579,6 +579,7 @@ GuessControlValues(void) ControlFile.wal_level = WAL_LEVEL_MINIMAL; ControlFile.wal_log_hints = false; + ControlFile.wal_compression = false; ControlFile.track_commit_timestamp = false; ControlFile.MaxConnections = 100; ControlFile.max_worker_processes = 8; @@ -795,6 +796,7 @@ RewriteControlFile(void) */ ControlFile.wal_level = WAL_LEVEL_MINIMAL; ControlFile.wal_log_hints = false; + ControlFile.wal_compression = false; ControlFile.track_commit_timestamp = false; ControlFile.MaxConnections = 100; ControlFile.max_worker_processes = 8; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index d06fbc0..6bdfa4a 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -98,6 +98,7 @@ extern char *XLogArchiveCommand; extern bool EnableHotStandby; extern bool fullPageWrites; extern bool wal_log_hints; +extern bool wal_compression; extern bool log_checkpoints; /* WAL levels */ diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h index 825cf54..fd058ad 100644 --- a/src/include/access/xlog_internal.h +++ b/src/include/access/xlog_internal.h @@ -186,6 +186,7 @@ typedef struct xl_parameter_change int max_locks_per_xact; int wal_level; bool wal_log_hints; + bool wal_compression; bool track_commit_timestamp; } xl_parameter_change; diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h index eb6cc89..3db312d 100644 --- a/src/include/access/xlogreader.h +++ b/src/include/access/xlogreader.h @@ -55,6 +55,7 @@ typedef struct char *bkp_image; uint16 hole_offset; uint16 hole_length; + uint16 compress_len; /* Buffer holding the rmgr-specific data associated with this block */ bool has_data; @@ -191,6 +192,9 @@ extern bool DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, ((decoder)->blocks[block_id].in_use) #define XLogRecHasBlockImage(decoder, block_id) \ ((decoder)->blocks[block_id].has_image) +#define XLogRecHasCompressedBlockImage(decoder, block_id) \ + (XLogRecHasBlockImage(decoder, block_id) && \ + (decoder)->blocks[block_id].compress_len != 0) extern bool RestoreBlockImage(XLogReaderState *recoder, uint8 block_id, char *dst); extern char *XLogRecGetBlockData(XLogReaderState *record, uint8 block_id, Size *len); diff --git a/src/include/access/xlogrecord.h b/src/include/access/xlogrecord.h index 11ddfac..b3a2808 100644 --- a/src/include/access/xlogrecord.h +++ b/src/include/access/xlogrecord.h @@ -103,11 +103,16 @@ typedef struct XLogRecordBlockHeader * such a "hole" from the stored data (and it's not counted in the * XLOG record's CRC, either). Hence, the amount of block data actually * present is BLCKSZ - hole_length bytes. + * + * compress_len indicates the length of this block when compressed. A length + * of 0 means that this block is not compressed. If the block image has a hole + * the block image is compressed without the hole. */ typedef struct XLogRecordBlockImageHeader { uint16 hole_offset; /* number of bytes before "hole" */ - uint16 hole_length; /* number of bytes in "hole" */ + uint16 hole_length; /* number of bytes before "hole" */ + uint16 compress_len; /* size of compressed block */ } XLogRecordBlockImageHeader; #define SizeOfXLogRecordBlockImageHeader sizeof(XLogRecordBlockImageHeader) diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index 6e9cac9..296e5b0 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -175,6 +175,7 @@ typedef struct ControlFileData */ int wal_level; bool wal_log_hints; + bool wal_compression; int MaxConnections; int max_worker_processes; int max_prepared_xacts; -- 2.2.0