From c2c94726969532c642ddd4e0cc34720ded632407 Mon Sep 17 00:00:00 2001 From: Hannu Krosing Date: Mon, 7 Sep 2026 14:40:52 +0000 Subject: [PATCH v10 1/9] Refactor detoasting and decompression pipeline to unify full and slice operations Historically, the TOAST subsystem maintained duplicate code paths for full vs slice operations across detoasting, fetching, and decompression: 1. toast_fetch_datum() and toast_fetch_datum_slice() duplicated pointer unpacking, slice bounds calculation, result allocation, and table lifecycle. 2. toast_decompress_datum() and toast_decompress_datum_slice() duplicated algorithm dispatch and recursion. 3. detoast_attr() and detoast_attr_slice() duplicated external/indirect handling and format normalization. 4. pglz_decompress_datum() and pglz_decompress_datum_slice() in toast_compression.c duplicated decompression buffers and pglz_decompress invocations. 5. lz4_decompress_datum() and lz4_decompress_datum_slice() duplicated buffer handling and error checking. Unify these paths by having full retrieval and decompression operations delegate to their slice counterparts with a negative limit/length sentinel (slicelength = -1), defining toast_fetch_datum() and toast_decompress_datum() as static inline wrappers. In detoast_attr_slice(), eliminate branching when fetching compressed slices by initializing max_size = -1, and fast-path standard 4-byte uncompressed datums when sliceoffset == 0 and slicelength < 0 to avoid redundant allocations. --- src/backend/access/common/detoast.c | 234 ++++-------------- src/backend/access/common/toast_compression.c | 124 +++++----- 2 files changed, 106 insertions(+), 252 deletions(-) diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c index 06992de1ede..c7f36d8dfac 100644 --- a/src/backend/access/common/detoast.c +++ b/src/backend/access/common/detoast.c @@ -22,13 +22,18 @@ #include "utils/expandeddatum.h" #include "utils/rel.h" -static varlena *toast_fetch_datum(varlena *attr); static varlena *toast_fetch_datum_slice(varlena *attr, int32 sliceoffset, int32 slicelength); -static varlena *toast_decompress_datum(varlena *attr); +static inline varlena * +toast_fetch_datum(varlena *attr) +{ + return toast_fetch_datum_slice(attr, 0, -1); +} + static varlena *toast_decompress_datum_slice(varlena *attr, int32 slicelength); + /* ---------- * detoast_external_attr - * @@ -115,79 +120,7 @@ detoast_external_attr(varlena *attr) varlena * detoast_attr(varlena *attr) { - if (VARATT_IS_EXTERNAL_ONDISK(attr)) - { - /* - * This is an externally stored datum --- fetch it back from there - */ - attr = toast_fetch_datum(attr); - /* If it's compressed, decompress it */ - if (VARATT_IS_COMPRESSED(attr)) - { - varlena *tmp = attr; - - attr = toast_decompress_datum(tmp); - pfree(tmp); - } - } - else if (VARATT_IS_EXTERNAL_INDIRECT(attr)) - { - /* - * This is an indirect pointer --- dereference it - */ - varatt_indirect redirect; - - VARATT_EXTERNAL_GET_POINTER(redirect, attr); - attr = (varlena *) redirect.pointer; - - /* nested indirect Datums aren't allowed */ - Assert(!VARATT_IS_EXTERNAL_INDIRECT(attr)); - - /* recurse in case value is still extended in some other way */ - attr = detoast_attr(attr); - - /* if it isn't, we'd better copy it */ - if (attr == (varlena *) redirect.pointer) - { - varlena *result; - - result = (varlena *) palloc(VARSIZE_ANY(attr)); - memcpy(result, attr, VARSIZE_ANY(attr)); - attr = result; - } - } - else if (VARATT_IS_EXTERNAL_EXPANDED(attr)) - { - /* - * This is an expanded-object pointer --- get flat format - */ - attr = detoast_external_attr(attr); - /* flatteners are not allowed to produce compressed/short output */ - Assert(!VARATT_IS_EXTENDED(attr)); - } - else if (VARATT_IS_COMPRESSED(attr)) - { - /* - * This is a compressed value inside of the main tuple - */ - attr = toast_decompress_datum(attr); - } - else if (VARATT_IS_SHORT(attr)) - { - /* - * This is a short-header varlena --- convert to 4-byte header format - */ - Size data_size = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT; - Size new_size = data_size + VARHDRSZ; - varlena *new_attr; - - new_attr = (varlena *) palloc(new_size); - SET_VARSIZE(new_attr, new_size); - memcpy(VARDATA(new_attr), VARDATA_SHORT(attr), data_size); - attr = new_attr; - } - - return attr; + return detoast_attr_slice(attr, 0, -1); } @@ -229,6 +162,7 @@ detoast_attr_slice(varlena *attr, int32 extsize; uint32 compress_method; bool is_compressed; + int32 max_size = -1; toast_external_info_get(attr, &toast_ext_data); extsize = VARATT_EXTINFO_GET_EXTSIZE(toast_ext_data.extinfo); @@ -242,11 +176,11 @@ detoast_attr_slice(varlena *attr, /* * For compressed values, we need to fetch enough slices to decompress * at least the requested part (when a prefix is requested). - * Otherwise, just fetch all slices. + * Otherwise, just fetch all slices (max_size = -1). */ if (slicelimit >= 0) { - int32 max_size = extsize; + max_size = extsize; /* * Determine maximum amount of compressed data needed for a prefix @@ -259,32 +193,46 @@ detoast_attr_slice(varlena *attr, */ if (compress_method == TOAST_PGLZ_COMPRESSION_ID) max_size = pglz_maximum_compressed_size(slicelimit, max_size); - - /* - * Fetch enough compressed slices (compressed marker will get set - * automatically). - */ - preslice = toast_fetch_datum_slice(attr, 0, max_size); } - else - preslice = toast_fetch_datum(attr); + + /* + * Fetch enough compressed slices (compressed marker will get set + * automatically). + */ + preslice = toast_fetch_datum_slice(attr, 0, max_size); } else if (VARATT_IS_EXTERNAL_INDIRECT(attr)) { varatt_indirect redirect; + varlena *res; VARATT_EXTERNAL_GET_POINTER(redirect, attr); /* nested indirect Datums aren't allowed */ Assert(!VARATT_IS_EXTERNAL_INDIRECT(redirect.pointer)); - return detoast_attr_slice(redirect.pointer, - sliceoffset, slicelength); + res = detoast_attr_slice(redirect.pointer, sliceoffset, slicelength); + + /* + * If result points directly into redirect target, make a copy in the + * caller's memory context. + */ + if (res == (varlena *) redirect.pointer) + { + varlena *copy = (varlena *) palloc(VARSIZE_ANY(res)); + + memcpy(copy, res, VARSIZE_ANY(res)); + res = copy; + } + + return res; } else if (VARATT_IS_EXTERNAL_EXPANDED(attr)) { /* pass it off to detoast_external_attr to flatten */ preslice = detoast_external_attr(attr); + /* flatteners are not allowed to produce compressed/short output */ + Assert(!VARATT_IS_EXTENDED(preslice)); } else preslice = attr; @@ -295,16 +243,22 @@ detoast_attr_slice(varlena *attr, { varlena *tmp = preslice; - /* Decompress enough to encompass the slice and the offset */ - if (slicelimit >= 0) - preslice = toast_decompress_datum_slice(tmp, slicelimit); - else - preslice = toast_decompress_datum(tmp); + /* Decompress enough to encompass the slice and the offset (or all if slicelimit < 0) */ + preslice = toast_decompress_datum_slice(tmp, slicelimit); if (tmp != attr) pfree(tmp); } + /* + * If the entire datum was requested from offset 0, and the datum is in + * standard 4-byte uncompressed format (not extended/short), return it directly. + * This avoids a redundant copy for inline uncompressed datums, freshly + * decompressed datums, and uncompressed external fetches. + */ + if (sliceoffset == 0 && slicelength < 0 && !VARATT_IS_EXTENDED(preslice)) + return preslice; + if (VARATT_IS_SHORT(preslice)) { attrdata = VARDATA_SHORT(preslice); @@ -337,56 +291,7 @@ detoast_attr_slice(varlena *attr, return result; } -/* ---------- - * toast_fetch_datum - - * - * Reconstruct an in memory Datum from the chunks saved - * in the toast relation - * ---------- - */ -static varlena * -toast_fetch_datum(varlena *attr) -{ - Relation toastrel; - varlena *result; - toast_external_data toast_ext_data; - int32 attrsize; - Oid toastrelid; - Oid8 valueid; - - if (!VARATT_IS_EXTERNAL_ONDISK(attr)) - elog(ERROR, "toast_fetch_datum shouldn't be called for non-ondisk datums"); - - toast_external_info_get(attr, &toast_ext_data); - attrsize = VARATT_EXTINFO_GET_EXTSIZE(toast_ext_data.extinfo); - toastrelid = toast_ext_data.toastrelid; - valueid = toast_ext_data.valueid; - - result = (varlena *) palloc(attrsize + VARHDRSZ); - - if (VARATT_EXTINFO_IS_COMPRESSED(toast_ext_data.extinfo, toast_ext_data.rawsize)) - SET_VARSIZE_COMPRESSED(result, attrsize + VARHDRSZ); - else - SET_VARSIZE(result, attrsize + VARHDRSZ); - - if (attrsize == 0) - return result; /* Probably shouldn't happen, but just in - * case. */ - - /* - * Open the toast relation and its indexes - */ - toastrel = table_open(toastrelid, AccessShareLock); - - /* Fetch all chunks */ - table_relation_fetch_toast_slice(toastrel, valueid, - attrsize, 0, attrsize, result); - - /* Close toast table */ - table_close(toastrel, AccessShareLock); - return result; -} /* ---------- * toast_fetch_datum_slice - @@ -473,42 +378,12 @@ toast_fetch_datum_slice(varlena *attr, int32 sliceoffset, return result; } -/* ---------- - * toast_decompress_datum - - * - * Decompress a compressed version of a varlena datum - */ -static varlena * -toast_decompress_datum(varlena *attr) -{ - ToastCompressionId cmid; - - Assert(VARATT_IS_COMPRESSED(attr)); - - /* - * Fetch the compression method id stored in the compression header and - * decompress the data using the appropriate decompression routine. - */ - cmid = TOAST_COMPRESS_METHOD(attr); - switch (cmid) - { - case TOAST_PGLZ_COMPRESSION_ID: - return pglz_decompress_datum(attr); - case TOAST_LZ4_COMPRESSION_ID: - return lz4_decompress_datum(attr); - default: - elog(ERROR, "invalid compression method id %d", cmid); - return NULL; /* keep compiler quiet */ - } -} - - /* ---------- * toast_decompress_datum_slice - * - * Decompress the front of a compressed version of a varlena datum. + * Decompress a compressed version of a varlena datum (or a slice from the front). * offset handling happens in detoast_attr_slice. - * Here we just decompress a slice from the front. + * If slicelength < 0 or >= decompressed size, decompress the full datum. */ static varlena * toast_decompress_datum_slice(varlena *attr, int32 slicelength) @@ -517,20 +392,9 @@ toast_decompress_datum_slice(varlena *attr, int32 slicelength) Assert(VARATT_IS_COMPRESSED(attr)); - /* - * Some callers may pass a slicelength that's more than the actual - * decompressed size. If so, just decompress normally. This avoids - * possibly allocating a larger-than-necessary result object, and may be - * faster and/or more robust as well. Notably, some versions of liblz4 - * have been seen to give wrong results if passed an output size that is - * more than the data's true decompressed size. - */ - if ((uint32) slicelength >= TOAST_COMPRESS_EXTSIZE(attr)) - return toast_decompress_datum(attr); - /* * Fetch the compression method id stored in the compression header and - * decompress the data slice using the appropriate decompression routine. + * decompress the data (slice or full) using the appropriate decompression routine. */ cmid = TOAST_COMPRESS_METHOD(attr); switch (cmid) diff --git a/src/backend/access/common/toast_compression.c b/src/backend/access/common/toast_compression.c index 09cdf880e4f..e4ba46cc9f4 100644 --- a/src/backend/access/common/toast_compression.c +++ b/src/backend/access/common/toast_compression.c @@ -76,22 +76,37 @@ pglz_compress_datum(const varlena *value) } /* - * Decompress a varlena that was compressed using PGLZ. + * Decompress a varlena that was compressed using PGLZ (or a slice from the front). + * If slicelength < 0 or >= decompressed size, decompress the full datum. */ varlena * -pglz_decompress_datum(const varlena *value) +pglz_decompress_datum_slice(const varlena *value, + int32 slicelength) { varlena *result; int32 rawsize; + int32 destsize; + bool check_complete; + + if (slicelength < 0 || (uint32) slicelength >= VARDATA_COMPRESSED_GET_EXTSIZE(value)) + { + destsize = VARDATA_COMPRESSED_GET_EXTSIZE(value); + check_complete = true; + } + else + { + destsize = slicelength; + check_complete = false; + } /* allocate memory for the uncompressed data */ - result = (varlena *) palloc(VARDATA_COMPRESSED_GET_EXTSIZE(value) + VARHDRSZ); + result = (varlena *) palloc(destsize + VARHDRSZ); /* decompress the data */ rawsize = pglz_decompress((const char *) value + VARHDRSZ_COMPRESSED, VARSIZE(value) - VARHDRSZ_COMPRESSED, VARDATA(result), - VARDATA_COMPRESSED_GET_EXTSIZE(value), true); + destsize, check_complete); if (rawsize < 0) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), @@ -103,31 +118,12 @@ pglz_decompress_datum(const varlena *value) } /* - * Decompress part of a varlena that was compressed using PGLZ. + * Decompress a full varlena that was compressed using PGLZ. */ varlena * -pglz_decompress_datum_slice(const varlena *value, - int32 slicelength) +pglz_decompress_datum(const varlena *value) { - varlena *result; - int32 rawsize; - - /* allocate memory for the uncompressed data */ - result = (varlena *) palloc(slicelength + VARHDRSZ); - - /* decompress the data */ - rawsize = pglz_decompress((const char *) value + VARHDRSZ_COMPRESSED, - VARSIZE(value) - VARHDRSZ_COMPRESSED, - VARDATA(result), - slicelength, false); - if (rawsize < 0) - ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), - errmsg_internal("compressed pglz data is corrupt"))); - - SET_VARSIZE(result, rawsize + VARHDRSZ); - - return result; + return pglz_decompress_datum_slice(value, -1); } /* @@ -176,10 +172,11 @@ lz4_compress_datum(const varlena *value) } /* - * Decompress a varlena that was compressed using LZ4. + * Decompress a varlena that was compressed using LZ4 (or a slice from the front). + * If slicelength < 0 or >= decompressed size, decompress the full datum. */ varlena * -lz4_decompress_datum(const varlena *value) +lz4_decompress_datum_slice(const varlena *value, int32 slicelength) { #ifndef USE_LZ4 NO_COMPRESSION_SUPPORT("lz4"); @@ -187,21 +184,42 @@ lz4_decompress_datum(const varlena *value) #else int32 rawsize; varlena *result; + int32 extsize = VARDATA_COMPRESSED_GET_EXTSIZE(value); + bool is_slice = (slicelength >= 0 && (uint32) slicelength < extsize); - /* allocate memory for the uncompressed data */ - result = (varlena *) palloc(VARDATA_COMPRESSED_GET_EXTSIZE(value) + VARHDRSZ); + /* slice decompression not supported prior to 1.8.3 */ + if (is_slice && LZ4_versionNumber() < 10803) + is_slice = false; + + if (!is_slice) + { + /* allocate memory for full uncompressed data */ + result = (varlena *) palloc(extsize + VARHDRSZ); + + /* decompress the full data */ + rawsize = LZ4_decompress_safe((const char *) value + VARHDRSZ_COMPRESSED, + VARDATA(result), + VARSIZE(value) - VARHDRSZ_COMPRESSED, + extsize); + } + else + { + /* allocate memory for the slice uncompressed data */ + result = (varlena *) palloc(slicelength + VARHDRSZ); + + /* decompress the slice */ + rawsize = LZ4_decompress_safe_partial((const char *) value + VARHDRSZ_COMPRESSED, + VARDATA(result), + VARSIZE(value) - VARHDRSZ_COMPRESSED, + slicelength, + slicelength); + } - /* decompress the data */ - rawsize = LZ4_decompress_safe((const char *) value + VARHDRSZ_COMPRESSED, - VARDATA(result), - VARSIZE(value) - VARHDRSZ_COMPRESSED, - VARDATA_COMPRESSED_GET_EXTSIZE(value)); if (rawsize < 0) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg_internal("compressed lz4 data is corrupt"))); - SET_VARSIZE(result, rawsize + VARHDRSZ); return result; @@ -209,40 +227,12 @@ lz4_decompress_datum(const varlena *value) } /* - * Decompress part of a varlena that was compressed using LZ4. + * Decompress a full varlena that was compressed using LZ4. */ varlena * -lz4_decompress_datum_slice(const varlena *value, int32 slicelength) +lz4_decompress_datum(const varlena *value) { -#ifndef USE_LZ4 - NO_COMPRESSION_SUPPORT("lz4"); - return NULL; /* keep compiler quiet */ -#else - int32 rawsize; - varlena *result; - - /* slice decompression not supported prior to 1.8.3 */ - if (LZ4_versionNumber() < 10803) - return lz4_decompress_datum(value); - - /* allocate memory for the uncompressed data */ - result = (varlena *) palloc(slicelength + VARHDRSZ); - - /* decompress the data */ - rawsize = LZ4_decompress_safe_partial((const char *) value + VARHDRSZ_COMPRESSED, - VARDATA(result), - VARSIZE(value) - VARHDRSZ_COMPRESSED, - slicelength, - slicelength); - if (rawsize < 0) - ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), - errmsg_internal("compressed lz4 data is corrupt"))); - - SET_VARSIZE(result, rawsize + VARHDRSZ); - - return result; -#endif + return lz4_decompress_datum_slice(value, -1); } /* -- 2.56.0.rc1.315.gc6ed9934b7-goog