From 3796b74dcac29d7e7e7f89b69d3fa92b9d105a17 Mon Sep 17 00:00:00 2001 From: David Steele Date: Thu, 5 Mar 2020 14:23:01 -0500 Subject: [PATCH] Use stock PostgreSQL page checksum implementation. We were using a customized version which worked fine but was hard to merge with upstream changes. Now this code is maintained much like the types in static.auto.h that we copy and check with each release. The goal is to eventually build directly against PostgreSQL (either source or libcommon) and this brings us one step closer. --- src/Makefile.in | 8 +- src/postgres/interface.h | 3 + src/postgres/interface/page.c | 20 ++ src/postgres/interface/pageChecksum.auto.c | 232 +++++++++++++++++++++ src/postgres/interface/static.auto.h | 20 ++ src/postgres/pageChecksum.c | 156 -------------- src/postgres/pageChecksum.h | 1 - test/test.pl | 2 +- 8 files changed, 282 insertions(+), 160 deletions(-) create mode 100644 src/postgres/interface/page.c create mode 100644 src/postgres/interface/pageChecksum.auto.c diff --git a/src/Makefile.in b/src/Makefile.in index 2631661a1..3a039d636 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -142,6 +142,7 @@ SRCS = \ info/infoPg.c \ postgres/client.c \ postgres/interface.c \ + postgres/interface/page.c \ postgres/interface/v083.c \ postgres/interface/v084.c \ postgres/interface/v090.c \ @@ -509,6 +510,9 @@ postgres/client.o: postgres/client.c build.auto.h common/assert.h common/debug.h postgres/interface.o: postgres/interface.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/log.h common/logLevel.h common/memContext.h common/regExp.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/list.h common/type/param.h common/type/string.h common/type/stringList.h common/type/stringz.h common/type/variant.h common/type/variantList.h postgres/interface.h postgres/interface/version.h postgres/version.h storage/helper.h storage/info.h storage/read.h storage/storage.h storage/write.h $(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c postgres/interface.c -o postgres/interface.o +postgres/interface/page.o: postgres/interface/page.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/list.h common/type/param.h common/type/string.h common/type/stringList.h common/type/stringz.h common/type/variant.h common/type/variantList.h postgres/interface.h postgres/interface/pageChecksum.auto.c postgres/interface/static.auto.h storage/info.h storage/read.h storage/storage.h storage/write.h + $(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) @COPTIMIZE_PAGE_CHECKSUM@ -c postgres/interface/page.c -o postgres/interface/page.o + postgres/interface/v083.o: postgres/interface/v083.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/list.h common/type/param.h common/type/string.h common/type/stringList.h common/type/stringz.h common/type/variant.h common/type/variantList.h postgres/interface.h postgres/interface/static.auto.h postgres/interface/version.auto.h postgres/interface/version.h postgres/interface/version.intern.h postgres/version.h storage/info.h storage/read.h storage/storage.h storage/write.h $(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c postgres/interface/v083.c -o postgres/interface/v083.o @@ -545,8 +549,8 @@ postgres/interface/v110.o: postgres/interface/v110.c build.auto.h common/assert. postgres/interface/v120.o: postgres/interface/v120.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/list.h common/type/param.h common/type/string.h common/type/stringList.h common/type/stringz.h common/type/variant.h common/type/variantList.h postgres/interface.h postgres/interface/static.auto.h postgres/interface/version.auto.h postgres/interface/version.h postgres/interface/version.intern.h postgres/version.h storage/info.h storage/read.h storage/storage.h storage/write.h $(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c postgres/interface/v120.c -o postgres/interface/v120.o -postgres/pageChecksum.o: postgres/pageChecksum.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/list.h common/type/param.h common/type/string.h common/type/stringList.h common/type/stringz.h common/type/variant.h common/type/variantList.h postgres/interface.h postgres/interface/static.auto.h postgres/pageChecksum.h storage/info.h storage/read.h storage/storage.h storage/write.h - $(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) @COPTIMIZE_PAGE_CHECKSUM@ -c postgres/pageChecksum.c -o postgres/pageChecksum.o +postgres/pageChecksum.o: postgres/pageChecksum.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/list.h common/type/param.h common/type/string.h common/type/stringList.h common/type/stringz.h common/type/variant.h common/type/variantList.h postgres/interface.h postgres/interface/static.auto.h storage/info.h storage/read.h storage/storage.h storage/write.h + $(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c postgres/pageChecksum.c -o postgres/pageChecksum.o protocol/client.o: protocol/client.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/log.h common/logLevel.h common/macro.h common/memContext.h common/object.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/json.h common/type/keyValue.h common/type/string.h common/type/stringz.h common/type/variant.h common/type/variantList.h protocol/client.h protocol/command.h version.h $(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c protocol/client.c -o protocol/client.o diff --git a/src/postgres/interface.h b/src/postgres/interface.h index 6f7d71d6a..7a7c31654 100644 --- a/src/postgres/interface.h +++ b/src/postgres/interface.h @@ -139,6 +139,9 @@ StringList *pgLsnRangeToWalSegmentList( // Get name used for lsn in functions (this was changed in PostgreSQL 10 for consistency since lots of names were changing) const String *pgLsnName(unsigned int pgVersion); +// Calculate the checksum for a page. Page cannot be const because the page header is temporarily modified during processing. +uint16_t pgPageChecksum(unsigned char *page, uint32_t blockNo); + const String *pgWalName(unsigned int pgVersion); // Get wal path (this was changed in PostgreSQL 10 to avoid including "log" in the name) diff --git a/src/postgres/interface/page.c b/src/postgres/interface/page.c new file mode 100644 index 000000000..5137e5b94 --- /dev/null +++ b/src/postgres/interface/page.c @@ -0,0 +1,20 @@ +/*********************************************************************************************************************************** +PostgreSQL Page Interface +***********************************************************************************************************************************/ +#include "build.auto.h" + +#include + +#include "postgres/interface/static.auto.h" + +/*********************************************************************************************************************************** +Include the page checksum code +***********************************************************************************************************************************/ +#include "postgres/interface/pageChecksum.auto.c" + +/**********************************************************************************************************************************/ +uint16_t +pgPageChecksum(unsigned char *page, uint32_t blockNo) +{ + return pg_checksum_page((char *)page, blockNo); +} diff --git a/src/postgres/interface/pageChecksum.auto.c b/src/postgres/interface/pageChecksum.auto.c new file mode 100644 index 000000000..435522fb4 --- /dev/null +++ b/src/postgres/interface/pageChecksum.auto.c @@ -0,0 +1,232 @@ +/*********************************************************************************************************************************** +PostgreSQL Page Checksum Algorithm + +Despite the .auto.h suffix this file is not automatically generated, though it could be. We use this suffix to emphasize that the +code has been copied from PostgreSQL and to exclude this file from project code counts. + +For each supported release of PostgreSQL check the code in this file to see if it has changed. The easiest way to do this is to +copy and paste in place and check git to see if there are any diffs. Tabs should be copied as is to make this process easy even +though the pgBackRest project does not use tabs elsewhere. + +Since the checksum implementation and page format do not (yet) change between versions this code should be copied verbatim from +src/include/storage/checksum_impl.h for each new release. Only the newest released version of the code should be used. + +Modifications need to be made after copying: + +1) Remove `#include "storage/bufpage.h"`. +2) Make pg_checksum_page() static. +***********************************************************************************************************************************/ + +/*------------------------------------------------------------------------- + * + * checksum_impl.h + * Checksum implementation for data pages. + * + * This file exists for the benefit of external programs that may wish to + * check Postgres page checksums. They can #include this to get the code + * referenced by storage/checksum.h. (Note: you may need to redefine + * Assert() as empty to compile this successfully externally.) + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/storage/checksum_impl.h + * + *------------------------------------------------------------------------- + */ + +/* + * The algorithm used to checksum pages is chosen for very fast calculation. + * Workloads where the database working set fits into OS file cache but not + * into shared buffers can read in pages at a very fast pace and the checksum + * algorithm itself can become the largest bottleneck. + * + * The checksum algorithm itself is based on the FNV-1a hash (FNV is shorthand + * for Fowler/Noll/Vo). The primitive of a plain FNV-1a hash folds in data 1 + * byte at a time according to the formula: + * + * hash = (hash ^ value) * FNV_PRIME + * + * FNV-1a algorithm is described at http://www.isthe.com/chongo/tech/comp/fnv/ + * + * PostgreSQL doesn't use FNV-1a hash directly because it has bad mixing of + * high bits - high order bits in input data only affect high order bits in + * output data. To resolve this we xor in the value prior to multiplication + * shifted right by 17 bits. The number 17 was chosen because it doesn't + * have common denominator with set bit positions in FNV_PRIME and empirically + * provides the fastest mixing for high order bits of final iterations quickly + * avalanche into lower positions. For performance reasons we choose to combine + * 4 bytes at a time. The actual hash formula used as the basis is: + * + * hash = (hash ^ value) * FNV_PRIME ^ ((hash ^ value) >> 17) + * + * The main bottleneck in this calculation is the multiplication latency. To + * hide the latency and to make use of SIMD parallelism multiple hash values + * are calculated in parallel. The page is treated as a 32 column two + * dimensional array of 32 bit values. Each column is aggregated separately + * into a partial checksum. Each partial checksum uses a different initial + * value (offset basis in FNV terminology). The initial values actually used + * were chosen randomly, as the values themselves don't matter as much as that + * they are different and don't match anything in real data. After initializing + * partial checksums each value in the column is aggregated according to the + * above formula. Finally two more iterations of the formula are performed with + * value 0 to mix the bits of the last value added. + * + * The partial checksums are then folded together using xor to form a single + * 32-bit checksum. The caller can safely reduce the value to 16 bits + * using modulo 2^16-1. That will cause a very slight bias towards lower + * values but this is not significant for the performance of the + * checksum. + * + * The algorithm choice was based on what instructions are available in SIMD + * instruction sets. This meant that a fast and good algorithm needed to use + * multiplication as the main mixing operator. The simplest multiplication + * based checksum primitive is the one used by FNV. The prime used is chosen + * for good dispersion of values. It has no known simple patterns that result + * in collisions. Test of 5-bit differentials of the primitive over 64bit keys + * reveals no differentials with 3 or more values out of 100000 random keys + * colliding. Avalanche test shows that only high order bits of the last word + * have a bias. Tests of 1-4 uncorrelated bit errors, stray 0 and 0xFF bytes, + * overwriting page from random position to end with 0 bytes, and overwriting + * random segments of page with 0x00, 0xFF and random data all show optimal + * 2e-16 false positive rate within margin of error. + * + * Vectorization of the algorithm requires 32bit x 32bit -> 32bit integer + * multiplication instruction. As of 2013 the corresponding instruction is + * available on x86 SSE4.1 extensions (pmulld) and ARM NEON (vmul.i32). + * Vectorization requires a compiler to do the vectorization for us. For recent + * GCC versions the flags -msse4.1 -funroll-loops -ftree-vectorize are enough + * to achieve vectorization. + * + * The optimal amount of parallelism to use depends on CPU specific instruction + * latency, SIMD instruction width, throughput and the amount of registers + * available to hold intermediate state. Generally, more parallelism is better + * up to the point that state doesn't fit in registers and extra load-store + * instructions are needed to swap values in/out. The number chosen is a fixed + * part of the algorithm because changing the parallelism changes the checksum + * result. + * + * The parallelism number 32 was chosen based on the fact that it is the + * largest state that fits into architecturally visible x86 SSE registers while + * leaving some free registers for intermediate values. For future processors + * with 256bit vector registers this will leave some performance on the table. + * When vectorization is not available it might be beneficial to restructure + * the computation to calculate a subset of the columns at a time and perform + * multiple passes to avoid register spilling. This optimization opportunity + * is not used. Current coding also assumes that the compiler has the ability + * to unroll the inner loop to avoid loop overhead and minimize register + * spilling. For less sophisticated compilers it might be beneficial to + * manually unroll the inner loop. + */ + +/* number of checksums to calculate in parallel */ +#define N_SUMS 32 +/* prime multiplier of FNV-1a hash */ +#define FNV_PRIME 16777619 + +/* Use a union so that this code is valid under strict aliasing */ +typedef union +{ + PageHeaderData phdr; + uint32 data[BLCKSZ / (sizeof(uint32) * N_SUMS)][N_SUMS]; +} PGChecksummablePage; + +/* + * Base offsets to initialize each of the parallel FNV hashes into a + * different initial state. + */ +static const uint32 checksumBaseOffsets[N_SUMS] = { + 0x5B1F36E9, 0xB8525960, 0x02AB50AA, 0x1DE66D2A, + 0x79FF467A, 0x9BB9F8A3, 0x217E7CD2, 0x83E13D2C, + 0xF8D4474F, 0xE39EB970, 0x42C6AE16, 0x993216FA, + 0x7B093B5D, 0x98DAFF3C, 0xF718902A, 0x0B1C9CDB, + 0xE58F764B, 0x187636BC, 0x5D7B3BB1, 0xE73DE7DE, + 0x92BEC979, 0xCCA6C0B2, 0x304A0979, 0x85AA43D4, + 0x783125BB, 0x6CA8EAA2, 0xE407EAC6, 0x4B5CFC3E, + 0x9FBF8C76, 0x15CA20BE, 0xF2CA9FD3, 0x959BD756 +}; + +/* + * Calculate one round of the checksum. + */ +#define CHECKSUM_COMP(checksum, value) \ +do { \ + uint32 __tmp = (checksum) ^ (value); \ + (checksum) = __tmp * FNV_PRIME ^ (__tmp >> 17); \ +} while (0) + +/* + * Block checksum algorithm. The page must be adequately aligned + * (at least on 4-byte boundary). + */ +static uint32 +pg_checksum_block(const PGChecksummablePage *page) +{ + uint32 sums[N_SUMS]; + uint32 result = 0; + uint32 i, + j; + + /* ensure that the size is compatible with the algorithm */ + Assert(sizeof(PGChecksummablePage) == BLCKSZ); + + /* initialize partial checksums to their corresponding offsets */ + memcpy(sums, checksumBaseOffsets, sizeof(checksumBaseOffsets)); + + /* main checksum calculation */ + for (i = 0; i < (uint32) (BLCKSZ / (sizeof(uint32) * N_SUMS)); i++) + for (j = 0; j < N_SUMS; j++) + CHECKSUM_COMP(sums[j], page->data[i][j]); + + /* finally add in two rounds of zeroes for additional mixing */ + for (i = 0; i < 2; i++) + for (j = 0; j < N_SUMS; j++) + CHECKSUM_COMP(sums[j], 0); + + /* xor fold partial checksums together */ + for (i = 0; i < N_SUMS; i++) + result ^= sums[i]; + + return result; +} + +/* + * Compute the checksum for a Postgres page. + * + * The page must be adequately aligned (at least on a 4-byte boundary). + * Beware also that the checksum field of the page is transiently zeroed. + * + * The checksum includes the block number (to detect the case where a page is + * somehow moved to a different location), the page header (excluding the + * checksum itself), and the page data. + */ +static uint16 +pg_checksum_page(char *page, BlockNumber blkno) +{ + PGChecksummablePage *cpage = (PGChecksummablePage *) page; + uint16 save_checksum; + uint32 checksum; + + /* We only calculate the checksum for properly-initialized pages */ + Assert(!PageIsNew(&cpage->phdr)); + + /* + * Save pd_checksum and temporarily set it to zero, so that the checksum + * calculation isn't affected by the old checksum stored on the page. + * Restore it after, because actually updating the checksum is NOT part of + * the API of this function. + */ + save_checksum = cpage->phdr.pd_checksum; + cpage->phdr.pd_checksum = 0; + checksum = pg_checksum_block(cpage); + cpage->phdr.pd_checksum = save_checksum; + + /* Mix in the block number to detect transposed pages */ + checksum ^= blkno; + + /* + * Reduce to a uint16 (to fit in the pd_checksum field) with an offset of + * one. That avoids checksums of zero, which seems like a good idea. + */ + return (uint16) ((checksum % 65535) + 1); +} diff --git a/src/postgres/interface/static.auto.h b/src/postgres/interface/static.auto.h index 656180fe1..e457d46e7 100644 --- a/src/postgres/interface/static.auto.h +++ b/src/postgres/interface/static.auto.h @@ -20,6 +20,18 @@ which could have a large impact on dependencies. Hopefully that won't happen of Note when adding new types it is safer to add them to version.auto.c unless they are needed for code that must be compatible across all versions of PostgreSQL supported by pgBackRest. ***********************************************************************************************************************************/ +#include "common/assert.h" +#include "postgres/interface.h" + +/*********************************************************************************************************************************** +Define Assert() as ASSERT() +***********************************************************************************************************************************/ +#define Assert(condition) ASSERT(condition) + +/*********************************************************************************************************************************** +Define BLCKSZ as PG_PAGE_SIZE_DEFAULT +***********************************************************************************************************************************/ +#define BLCKSZ PG_PAGE_SIZE_DEFAULT /*********************************************************************************************************************************** Types from src/include/c.h @@ -184,3 +196,11 @@ typedef struct PageHeaderData // PageHeader type // --------------------------------------------------------------------------------------------------------------------------------- typedef PageHeaderData *PageHeader; + +// PageIsNew macro +// --------------------------------------------------------------------------------------------------------------------------------- +/* + * PageIsNew + * returns true if page has not been initialized (by PageInit) + */ +#define PageIsNew(page) (((PageHeader) (page))->pd_upper == 0) diff --git a/src/postgres/pageChecksum.c b/src/postgres/pageChecksum.c index 07bea9b2f..71b0a6f1a 100644 --- a/src/postgres/pageChecksum.c +++ b/src/postgres/pageChecksum.c @@ -1,66 +1,4 @@ /*********************************************************************************************************************************** -Checksum Implementation for Data Pages - -Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group -Portions Copyright (c) 1994, Regents of the University of California - -Copied from src/include/storage/checksum_impl.h in the PostgreSQL project. - -The algorithm used to checksum pages is chosen for very fast calculation. Workloads where the database working set fits into OS file -cache but not into shared buffers can read in pages at a very fast pace and the checksum algorithm itself can become the largest -bottleneck. - -The checksum algorithm itself is based on the FNV-1a hash (FNV is shorthand for Fowler/Noll/Vo). The primitive of a plain FNV-1a -hash folds in data 1 byte at a time according to the formula: - - hash = (hash ^ value) * FNV_PRIME - -FNV-1a algorithm is described at http://www.isthe.com/chongo/tech/comp/fnv/ - -PostgreSQL doesn't use FNV-1a hash directly because it has bad mixing of high bits - high order bits in input data only affect high -order bits in output data. To resolve this we xor in the value prior to multiplication shifted right by 17 bits. The number 17 was -chosen because it doesn't have common denominator with set bit positions in FNV_PRIME and empirically provides the fastest mixing -for high order bits of final iterations quickly avalanche into lower positions. For performance reasons we choose to combine 4 bytes -at a time. The actual hash formula used as the basis is: - - hash = (hash ^ value) * FNV_PRIME ^ ((hash ^ value) >> 17) - -The main bottleneck in this calculation is the multiplication latency. To hide the latency and to make use of SIMD parallelism -multiple hash values are calculated in parallel. The page is treated as a 32 column two dimensional array of 32 bit values. Each -column is aggregated separately into a partial checksum. Each partial checksum uses a different initial value (offset basis in FNV -terminology). The initial values actually used were chosen randomly, as the values themselves don't matter as much as that they are -different and don't match anything in real data. After initializing partial checksums each value in the column is aggregated -according to the above formula. Finally two more iterations of the formula are performed with value 0 to mix the bits of the last -value added. - -The partial checksums are then folded together using xor to form a single 32-bit checksum. The caller can safely reduce the value to -16 bits using modulo 2^16-1. That will cause a very slight bias towards lower values but this is not significant for the performance -of the checksum. - -The algorithm choice was based on what instructions are available in SIMD instruction sets. This meant that a fast and good -algorithm needed to use multiplication as the main mixing operator. The simplest multiplication based checksum primitive is the one -used by FNV. The prime used is chosen for good dispersion of values. It has no known simple patterns that result in collisions. Test -of 5-bit differentials of the primitive over 64bit keys reveals no differentials with 3 or more values out of 100000 random keys -colliding. Avalanche test shows that only high order bits of the last word have a bias. Tests of 1-4 uncorrelated bit errors, stray -0 and 0xFF bytes, overwriting page from random position to end with 0 bytes, and overwriting random segments of page with 0x00, 0xFF -and random data all show optimal 2e-16 false positive rate within margin of error. - -Vectorization of the algorithm requires 32bit x 32bit -> 32bit integer multiplication instruction. As of 2013 the corresponding -instruction is available on x86 SSE4.1 extensions (pmulld) and ARM NEON (vmul.i32). Vectorization requires a compiler to do the -vectorization for us. For recent GCC versions the flags -msse4.1 -funroll-loops -ftree-vectorize are enough to achieve -vectorization. - -The optimal amount of parallelism to use depends on CPU specific instruction latency, SIMD instruction width, throughput and the -amount of registers available to hold intermediate state. Generally, more parallelism is better up to the point that state doesn't -fit in registers and extra load-store instructions are needed to swap values in/out. The number chosen is a fixed part of the -algorithm because changing the parallelism changes the checksum result. - -The parallelism number 32 was chosen based on the fact that it is the largest state that fits into architecturally visible x86 SSE -registers while leaving some free registers for intermediate values. For future processors with 256bit vector registers this will -leave some performance on the table. When vectorization is not available it might be beneficial to restructure the computation to -calculate a subset of the columns at a time and perform multiple passes to avoid register spilling. This optimization opportunity -is not used. Current coding also assumes that the compiler has the ability to unroll the inner loop to avoid loop overhead and -minimize register spilling. For less sophisticated compilers it might be beneficial to manually unroll the inner loop. ***********************************************************************************************************************************/ #include "build.auto.h" @@ -71,100 +9,6 @@ minimize register spilling. For less sophisticated compilers it might be benefic #include "common/log.h" #include "postgres/interface.h" #include "postgres/interface/static.auto.h" -#include "postgres/pageChecksum.h" - -/*********************************************************************************************************************************** -pageChecksumBlock - block checksum algorithm - -The data argument must be aligned on a 4-byte boundary. -***********************************************************************************************************************************/ -// number of checksums to calculate in parallel -#define N_SUMS 32 - -// prime multiplier of FNV-1a hash -#define FNV_PRIME 16777619 - -// Base offsets to initialize each of the parallel FNV hashes into a different initial state. -static const uint32_t checksumBaseOffsets[N_SUMS] = -{ - 0x5B1F36E9, 0xB8525960, 0x02AB50AA, 0x1DE66D2A, 0x79FF467A, 0x9BB9F8A3, 0x217E7CD2, 0x83E13D2C, - 0xF8D4474F, 0xE39EB970, 0x42C6AE16, 0x993216FA, 0x7B093B5D, 0x98DAFF3C, 0xF718902A, 0x0B1C9CDB, - 0xE58F764B, 0x187636BC, 0x5D7B3BB1, 0xE73DE7DE, 0x92BEC979, 0xCCA6C0B2, 0x304A0979, 0x85AA43D4, - 0x783125BB, 0x6CA8EAA2, 0xE407EAC6, 0x4B5CFC3E, 0x9FBF8C76, 0x15CA20BE, 0xF2CA9FD3, 0x959BD756 -}; - -// Calculate one round of the checksum. -#define CHECKSUM_COMP(checksum, value) \ -do { \ - uint32_t temp = (checksum) ^ (value); \ - (checksum) = temp * FNV_PRIME ^ (temp >> 17); \ -} while (0) - -static uint32_t -pageChecksumBlock(unsigned char *page) -{ - FUNCTION_TEST_BEGIN(); - FUNCTION_TEST_PARAM_P(UCHARDATA, page); - FUNCTION_TEST_END(); - - ASSERT(page != NULL); - - uint32_t sums[N_SUMS]; - uint32_t (*dataArray)[N_SUMS] = (uint32_t (*)[N_SUMS])page; - uint32_t result = 0; - uint32_t i, j; - - /* initialize partial checksums to their corresponding offsets */ - memcpy(sums, checksumBaseOffsets, sizeof(checksumBaseOffsets)); - - /* main checksum calculation */ - for (i = 0; i < PG_PAGE_SIZE_DEFAULT / sizeof(uint32_t) / N_SUMS; i++) - for (j = 0; j < N_SUMS; j++) - CHECKSUM_COMP(sums[j], dataArray[i][j]); - - /* finally add in two rounds of zeroes for additional mixing */ - for (i = 0; i < 2; i++) - for (j = 0; j < N_SUMS; j++) - CHECKSUM_COMP(sums[j], 0); - - // xor fold partial checksums together - for (i = 0; i < N_SUMS; i++) - result ^= sums[i]; - - FUNCTION_TEST_RETURN(result); -} - -/*********************************************************************************************************************************** -pageChecksum - compute the checksum for a PostgreSQL page - -The checksum includes the block number (to detect the case where a page is somehow moved to a different location), the page header -(excluding the checksum itself), and the page data. -***********************************************************************************************************************************/ -uint16_t -pgPageChecksum(unsigned char *page, unsigned int blockNo) -{ - FUNCTION_TEST_BEGIN(); - FUNCTION_TEST_PARAM_P(UCHARDATA, page); - FUNCTION_TEST_PARAM(UINT, blockNo); - FUNCTION_TEST_END(); - - ASSERT(page != NULL); - - // Save pd_checksum and temporarily set it to zero, so that the checksum calculation isn't affected by the old checksum stored - // on the page. Restore it after, because actually updating the checksum is NOT part of the API of this function. - PageHeader pageHeader = (PageHeader)page; - - uint16_t originalChecksum = pageHeader->pd_checksum; - pageHeader->pd_checksum = 0; - uint32_t checksum = pageChecksumBlock(page); - pageHeader->pd_checksum = originalChecksum; - - // Mix in the block number to detect transposed pages - checksum ^= blockNo; - - // Reduce to a uint16 with an offset of one. That avoids checksums of zero, which seems like a good idea. - FUNCTION_TEST_RETURN((uint16_t)(checksum % 65535 + 1)); -} /*********************************************************************************************************************************** Return the lsn for a page diff --git a/src/postgres/pageChecksum.h b/src/postgres/pageChecksum.h index dbc2d6a52..d3c1b8d88 100644 --- a/src/postgres/pageChecksum.h +++ b/src/postgres/pageChecksum.h @@ -9,7 +9,6 @@ Checksum Implementation for Data Pages /*********************************************************************************************************************************** Functions ***********************************************************************************************************************************/ -uint16_t pgPageChecksum(unsigned char *page, unsigned int blockNo); uint64_t pgPageLsn(const unsigned char *page); bool pgPageChecksumTest( unsigned char *page, unsigned int blockNo, unsigned int pageSize, uint32_t ignoreWalId, uint32_t ignoreWalOffset); diff --git a/test/test.pl b/test/test.pl index a3f501a7c..7189d1198 100755 --- a/test/test.pl +++ b/test/test.pl @@ -582,7 +582,7 @@ eval buildMakefile( $oStorageBackRest, ${$oStorageBackRest->get("src/Makefile.in")}, - {rhOption => {'postgres/pageChecksum.o' => '@COPTIMIZE_PAGE_CHECKSUM@'}}))) + {rhOption => {'postgres/interface/page.o' => '@COPTIMIZE_PAGE_CHECKSUM@'}}))) { push(@stryBuilt, $strBuilt); push(@stryBuiltAll, @stryBuilt);