Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1iDckh-0006rM-KF for pgsql-hackers@arkaria.postgresql.org; Thu, 26 Sep 2019 23:00:47 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1iDckg-0008GI-7B for pgsql-hackers@arkaria.postgresql.org; Thu, 26 Sep 2019 23:00:46 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1iDckf-0008GA-RZ for pgsql-hackers@lists.postgresql.org; Thu, 26 Sep 2019 23:00:45 +0000 Received: from mail-wr1-x444.google.com ([2a00:1450:4864:20::444]) by makus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1iDckb-0006oZ-C1 for pgsql-hackers@postgresql.org; Thu, 26 Sep 2019 23:00:44 +0000 Received: by mail-wr1-x444.google.com with SMTP id q17so543983wrx.10 for ; Thu, 26 Sep 2019 16:00:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=2ndquadrant-com.20150623.gappssmtp.com; s=20150623; h=date:from:to:cc:subject:message-id:references:mime-version :content-disposition:in-reply-to:user-agent; bh=MXBuNod/+phvRBhKZLFpocE3EebqIdUh6P+wmcShn1c=; b=m3m2xB4RDhGzHl7ul1FO1BjRi9f0qcYnkLMWk8Pp0w6DWVSHS4rkwOTEKM2SXStU15 /NK++X52Z3T0hpJvPrf28Ct5DL0f1iluRAnW99MyYONbZQ38vZOSfNRoAOOurRJC5k3e bIOd6LkkqkkdMilcJqprywOT//Gkp6xejl5w1VLpRKzL3jBgVoV2BfaBBDv4x0RtAZUA iEcXythzns/Qxd0nql7nyD0x0q2Id9bQ7E3Y8IaJ+JDm2GJt7xREo5yUz7AvGsdWMMEO GRK43AkXKZ/Yt9mjFqN39/G6dal+sHqoRHGnXw5Otrg7ufBdXSY2BB9TFQ885vNdICoR ehVw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to:user-agent; bh=MXBuNod/+phvRBhKZLFpocE3EebqIdUh6P+wmcShn1c=; b=ogqmo62BDN0JQkZn/46Py9DNx4BK1tJ3Beu4b2t3uGO0bdB1To9dF6vYXUUdWtOGXZ wBIKgdZElXSFDh/jHXg5yPppHbctFiPBwNlVe39dMe/H1yWNQ++F4MTfYNkFnJk2/Ecn +qtJxfoZlZJR/0e7f+Yd5hPRebWxgkaw9PbAnFLX+KZgI3Qto6BTKJED5g8opPK79gF1 UK65a4O1gEKWcr8I/djVu9124L7KbNTMpyg5gSkFXsQxQSHwpz2YH1hKdW4JmywuJoob paB35Ptu7zoRSQE3bUko0er1DxBm8R1tniltg8wjhzZjR/0TBXsdna3UhyVK0qagkLAg 9weA== X-Gm-Message-State: APjAAAWBozOcYLxy2VbiHXGkHQpSANfsjfmkv1y4bfsQHaBPkpAMFwY4 D15E/hwswOSUUrJkUZBY4FHnhQ== X-Google-Smtp-Source: APXvYqynop2UcGp/5T1Tij2Znxyb+GwqbSiISDycQ3w/aduElD9aEC/4cMevMvU9KhToO9eq6noieA== X-Received: by 2002:adf:afed:: with SMTP id y45mr534651wrd.347.1569538839781; Thu, 26 Sep 2019 16:00:39 -0700 (PDT) Received: from localhost (ip-86-49-253-183.net.upcbroadband.cz. [86.49.253.183]) by smtp.gmail.com with ESMTPSA id h17sm10825541wme.6.2019.09.26.16.00.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 26 Sep 2019 16:00:38 -0700 (PDT) Date: Fri, 27 Sep 2019 01:00:36 +0200 From: Tomas Vondra To: Alvaro Herrera Cc: Binguo Bao , Andrey Borodin , Paul Ramsey , Pgsql Hackers Subject: Re: Optimize partial TOAST decompression Message-ID: <20190926230036.ubxzgkqsphbzmbdl@development> References: <20190710144725.kaswinyw2bdgb6tz@development> <20190925203834.GA25060@alvherre.pgsql> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="wuuf3hzl5uj5avyt" Content-Disposition: inline In-Reply-To: <20190925203834.GA25060@alvherre.pgsql> User-Agent: NeoMutt/20180716 List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk --wuuf3hzl5uj5avyt Content-Type: text/plain; charset=us-ascii; format=flowed Content-Disposition: inline On Wed, Sep 25, 2019 at 05:38:34PM -0300, Alvaro Herrera wrote: >Hello, can you please update this patch? > I'm not the patch author, but I've been looking at the patch recently and I have a rebased version at hand - so attached. FWIW I believe the patch is solid and in good shape, and it got stuck after I reported some benchmarks showing somewhat flaky performance. I know Binguo Bao was trying to reproduce the benchmark, and I assume the silence means he was not successful :-( On the larger data set the patch however performed very nicely, so maybe I just did something stupid while running the smaller one, or maybe it's just noise (the queries were just a couple of ms in that test). I do plan to rerun the benchmarks and investigate a bit - if I find the patch is fine, I'd like to commit it shortly. regards -- Tomas Vondra http://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services --wuuf3hzl5uj5avyt Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="0001-Optimize-partial-TOAST-decompression-7.patch" From 7167c0e5f78d74a0c02a76748e7b8caaabe65ccd Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Fri, 27 Sep 2019 00:51:52 +0200 Subject: [PATCH] Optimize partial TOAST decompression --- src/backend/access/common/detoast.c | 53 +++++++++++++++++++++++----- src/common/pg_lzcompress.c | 27 ++++++++++++++ src/include/access/toast_internals.h | 1 + src/include/common/pg_lzcompress.h | 1 + 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c index c8b49d6a12..4727c892a8 100644 --- a/src/backend/access/common/detoast.c +++ b/src/backend/access/common/detoast.c @@ -196,6 +196,8 @@ heap_tuple_untoast_attr(struct varlena *attr) * * Public entry point to get back part of a toasted value * from compression or external storage. + * + * Note: when slicelength is negative, return suffix of the value. * ---------- */ struct varlena * @@ -217,8 +219,29 @@ heap_tuple_untoast_attr_slice(struct varlena *attr, if (!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer)) return toast_fetch_datum_slice(attr, sliceoffset, slicelength); - /* fetch it back (compressed marker will get set automatically) */ - preslice = toast_fetch_datum(attr); + /* + * When a prefix of the value is requested, fetch enough slices to + * decompress. Otherwise, fetch all slices. + */ + if (slicelength > 0 && sliceoffset >= 0) + { + int32 max_size; + + /* + * Determine maximum amount of compressed data needed for a prefix + * of a given length (after decompression). + */ + max_size = pglz_maximum_compressed_size(sliceoffset + slicelength, + TOAST_COMPRESS_SIZE(attr)); + + /* + * 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); } else if (VARATT_IS_EXTERNAL_INDIRECT(attr)) { @@ -476,7 +499,9 @@ toast_fetch_datum(struct varlena *attr) * Reconstruct a segment of a Datum from the chunks saved * in the toast relation * - * Note that this function only supports non-compressed external datums. + * Note that this function supports non-compressed external datums + * and compressed external datums (in which case the requrested slice + * has to be a prefix, i.e. sliceoffset has to be 0). * ---------- */ static struct varlena * @@ -517,10 +542,11 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length) VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); /* - * It's nonsense to fetch slices of a compressed datum -- this isn't lo_* - * we can't return a compressed datum which is meaningful to toast later + * It's meaningful to fetch slices at the start of a compressed datum, + * since we can decompress the prefix raw data without decompressing the + * whole value. */ - Assert(!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer)); + Assert(!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) || 0 == sliceoffset); attrsize = toast_pointer.va_extsize; totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1; @@ -531,12 +557,23 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset, int32 length) length = 0; } + /* + * When fetching a prefix of a compressed external datum, account for + * the rawsize tracking amount of raw data (it's stored at the beginning + * as an int32 value). + */ + if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) && length > 0) + length = length + sizeof(int32); + if (((sliceoffset + length) > attrsize) || length < 0) length = attrsize - sliceoffset; result = (struct varlena *) palloc(length + VARHDRSZ); - SET_VARSIZE(result, length + VARHDRSZ); + if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer)) + SET_VARSIZE_COMPRESSED(result, length + VARHDRSZ); + else + SET_VARSIZE(result, length + VARHDRSZ); if (length == 0) return result; /* Can save a lot of work at this point! */ @@ -720,7 +757,7 @@ toast_decompress_datum(struct varlena *attr) SET_VARSIZE(result, TOAST_COMPRESS_RAWSIZE(attr) + VARHDRSZ); if (pglz_decompress(TOAST_COMPRESS_RAWDATA(attr), - VARSIZE(attr) - TOAST_COMPRESS_HDRSZ, + TOAST_COMPRESS_SIZE(attr), VARDATA(result), TOAST_COMPRESS_RAWSIZE(attr), true) < 0) elog(ERROR, "compressed data is corrupted"); diff --git a/src/common/pg_lzcompress.c b/src/common/pg_lzcompress.c index 988b3987d0..18766ade02 100644 --- a/src/common/pg_lzcompress.c +++ b/src/common/pg_lzcompress.c @@ -771,3 +771,30 @@ pglz_decompress(const char *source, int32 slen, char *dest, */ return (char *) dp - dest; } + + +/* ---------- + * pglz_max_compressed_size - + * + * Calculate the maximum compressed size for a given amount of raw data. + * Return the maximum size, or total compressed size if maximum size is + * larger than total compressed size. + * ---------- + */ +int32 +pglz_maximum_compressed_size(int32 rawsize, int32 total_compressed_size) +{ + int32 compressed_size; + + /* + * Use int64 to prevent overflow during calculation. + */ + compressed_size = (int32) ((int64) rawsize * 9 + 8) / 8; + + /* + * Maximum compressed size can't be larger than total compressed size. + */ + compressed_size = Min(compressed_size, total_compressed_size); + + return compressed_size; +} diff --git a/src/include/access/toast_internals.h b/src/include/access/toast_internals.h index 494b07a4b1..b8703d8c94 100644 --- a/src/include/access/toast_internals.h +++ b/src/include/access/toast_internals.h @@ -31,6 +31,7 @@ typedef struct toast_compress_header */ #define TOAST_COMPRESS_HDRSZ ((int32) sizeof(toast_compress_header)) #define TOAST_COMPRESS_RAWSIZE(ptr) (((toast_compress_header *) (ptr))->rawsize) +#define TOAST_COMPRESS_SIZE(ptr) ((int32) VARSIZE(ptr) - TOAST_COMPRESS_HDRSZ) #define TOAST_COMPRESS_RAWDATA(ptr) \ (((char *) (ptr)) + TOAST_COMPRESS_HDRSZ) #define TOAST_COMPRESS_SET_RAWSIZE(ptr, len) \ diff --git a/src/include/common/pg_lzcompress.h b/src/include/common/pg_lzcompress.h index 555576436c..e59fe2eaea 100644 --- a/src/include/common/pg_lzcompress.h +++ b/src/include/common/pg_lzcompress.h @@ -87,5 +87,6 @@ extern int32 pglz_compress(const char *source, int32 slen, char *dest, const PGLZ_Strategy *strategy); extern int32 pglz_decompress(const char *source, int32 slen, char *dest, int32 rawsize, bool check_complete); +extern int32 pglz_maximum_compressed_size(int32 rawsize, int32 total_compressed_size); #endif /* _PG_LZCOMPRESS_H_ */ -- 2.21.0 --wuuf3hzl5uj5avyt--