Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1qOdeV-008Uz0-UX for pgsql-hackers@arkaria.postgresql.org; Wed, 26 Jul 2023 12:30:03 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1qOdeU-004Ffg-8e for pgsql-hackers@arkaria.postgresql.org; Wed, 26 Jul 2023 12:30:02 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1qOdeT-004FfY-Tj for pgsql-hackers@lists.postgresql.org; Wed, 26 Jul 2023 12:30:01 +0000 Received: from mail-pj1-x1033.google.com ([2607:f8b0:4864:20::1033]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1qOdeQ-000TqU-TK for pgsql-hackers@lists.postgresql.org; Wed, 26 Jul 2023 12:30:01 +0000 Received: by mail-pj1-x1033.google.com with SMTP id 98e67ed59e1d1-26809f86bd5so1782256a91.3 for ; Wed, 26 Jul 2023 05:29:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1690374597; x=1690979397; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=t2wjyY7wtjnfGPkYaPFrg9G7In1U9elGje3ejpWJmjo=; b=EeFbQtRrPssRt7KFj4qwXbfS375vmfxF8zWhcj7jGNcan50uskBg6vTFqzyfJk03Ym Hi2Oktnx2ajERpm+cXpB9sxmLeYqwaYgjjfNeBGTDQCy7E0xZv9P0afAFDDKpCtMxwkS G1LnIkJift7GBGxKLJsTO0nGRXBf/XfROnGwDJYYlBE3qgvNpbonxw1MJCIIOnHsB0c6 qRkRc5/JRz80nKid/tnoXYPZsusyERAEiutUla+ZLDvkdTa0GOKmYHokjrxBXMsxU39O yRSVDOghXZpMYiYPMT79sHyJNqaWxCddlxFETTy/ReUSbeeQeQsXH5VdNKou/N6ls26I kcCQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1690374597; x=1690979397; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=t2wjyY7wtjnfGPkYaPFrg9G7In1U9elGje3ejpWJmjo=; b=SEoMp4twk2ORKdnjXNshCszOqQdwu5b+TdPRe0iFGNYg9XidzR5+WXqEn9e1RifuLI nmWDstKAv6Z1xeAfM4397Zl29+MUi6+cWOFaTsQSGlRIK9vuvitbpxsRIJDub9Rq3IxW 5D2DrSdg8Di4IB+8EOfnUP7FrZp/aAvD1wrVTkTe4ePmOjJD16Q0YYTvFVgM4WJ2NUeq 2qIw8uM0o574+8gr+X0apGpyEb6hOz/UNBsPzuAYQDQqFVb70FZ44fla69pB+6KmWi09 Lfjk+c2mTE+cJS6AjA+9qzyBLDa8L4gaYib6QrdbZ6lSjL2OZEv+XGzQNJT1tLrx6sZK Pueg== X-Gm-Message-State: ABy/qLZCJImgSUfDELaHdn+p7jLg9wd65Qfp89Qoe6HMdOF6iKGGdz5x /Gpw0jYTgMLgmBI4s44JTOD9KPwmvW3nIFreWP0= X-Google-Smtp-Source: APBJJlFVp7PVfwO9Vgos+mQGLdySnDjmDmGiZbqW/zUtWRwnmDrYfxO628DartOGERE5sGvXaTspqQz4EoU4tkJ9tfA= X-Received: by 2002:a17:90b:ed3:b0:263:f7ce:335e with SMTP id gz19-20020a17090b0ed300b00263f7ce335emr1388152pjb.43.1690374596659; Wed, 26 Jul 2023 05:29:56 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: From: Matthias van de Meent Date: Wed, 26 Jul 2023 14:29:45 +0200 Message-ID: Subject: Re: Optimizing nbtree ScalarArrayOp execution, allowing multi-column ordered scans, skip scan To: Peter Geoghegan Cc: PostgreSQL Hackers , Tom Lane , Tomas Vondra , Jeff Davis , benoit Content-Type: text/plain; charset="UTF-8" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On Tue, 25 Jul 2023 at 03:34, Peter Geoghegan wrote: > > I've been working on a variety of improvements to nbtree's native > ScalarArrayOpExpr execution. This builds on Tom's work in commit > 9e8da0f7. Cool. > Attached patch is still at the prototype stage. I'm posting it as v1 a > little earlier than I usually would because there has been much back > and forth about it on a couple of other threads involving Tomas Vondra > and Jeff Davis -- seems like it would be easier to discuss with > working code available. > > The patch adds two closely related enhancements to ScalarArrayOp > execution by nbtree: > > 1. Execution of quals with ScalarArrayOpExpr clauses during nbtree > index scans (for equality-strategy SK_SEARCHARRAY scan keys) can now > "advance the scan's array keys locally", which sometimes avoids > significant amounts of unneeded pinning/locking of the same set of > index pages. > > SAOP index scans become capable of eliding primitive index scans for > the next set of array keys in line in cases where it isn't truly > necessary to descend the B-Tree again. Index scans are now capable of > "sticking with the existing leaf page for now" when it is determined > that the end of the current set of array keys is physically close to > the start of the next set of array keys (the next set in line to be > materialized by the _bt_advance_array_keys state machine). This is > often possible. > > Naturally, we still prefer to advance the array keys in the > traditional way ("globally") much of the time. That means we'll > perform another _bt_first/_bt_search descent of the index, starting a > new primitive index scan. Whether we try to skip pages on the leaf > level or stick with the current primitive index scan (by advancing > array keys locally) is likely to vary a great deal. Even during the > same index scan. Everything is decided dynamically, which is the only > approach that really makes sense. > > This optimization can significantly lower the number of buffers pinned > and locked in cases with significant locality, and/or with many array > keys with no matches. The savings (when measured in buffers > pined/locked) can be as high as 10x, 100x, or even more. Benchmarking > has shown that transaction throughput for variants of "pgbench -S" > designed to stress the implementation (hundreds of array constants) > under concurrent load can have up to 5.5x higher transaction > throughput with the patch. Less extreme cases (10 array constants, > spaced apart) see about a 20% improvement in throughput. There are > similar improvements to latency for the patch, in each case. Considering that it caches/reuses the page across SAOP operations, can (or does) this also improve performance for index scans on the outer side of a join if the order of join columns matches the order of the index? That is, I believe this caches (leaf) pages across scan keys, but can (or does) it also reuse these already-cached leaf pages across restarts of the index scan/across multiple index lookups in the same plan node, so that retrieval of nearby index values does not need to do an index traversal? > [...] > Skip Scan > ========= > > MDAM encompasses something that people tend to call "skip scan" -- > terminology with a great deal of baggage. These days I prefer to call > it "filling in missing key predicates", per the paper. That's much > more descriptive, and makes it less likely that people will conflate > the techniques with InnoDB style "loose Index scans" -- the latter is > a much more specialized/targeted optimization. (I now believe that > these are very different things, though I was thrown off by the > superficial similarities for a long time. It's pretty confusing.) I'm not sure I understand. MDAM seems to work on an index level to return full ranges of values, while "skip scan" seems to try to allow systems to signal to the index to skip to some other index condition based on arbitrary cutoffs. This would usually be those of which the information is not stored in the index, such as "SELECT user_id FROM orders GROUP BY user_id HAVING COUNT(*) > 10", where the scan would go though the user_id index and skip to the next user_id value when it gets enough rows of a matching result (where "enough" is determined above the index AM's plan node, or otherwise is impossible to determine with only the scan key info in the index AM). I'm not sure how this could work without specifically adding skip scan-related index AM functionality, and I don't see how it fits in with this MDAM/SAOP system. > [...] > > Thoughts? MDAM seems to require exponential storage for "scan key operations" for conditions on N columns (to be precise, the product of the number of distinct conditions on each column); e.g. an index on mytable (a,b,c,d,e,f,g,h) with conditions "a IN (1, 2) AND b IN (1, 2) AND ... AND h IN (1, 2)" would require 2^8 entries. If 4 conditions were used for each column, that'd be 4^8, etc... With an index column limit of 32, that's quite a lot of memory potentially needed to execute the statement. So, this begs the question: does this patch have the same issue? Does it fail with OOM, does it gracefully fall back to the old behaviour when the clauses are too complex to linearize/compose/fold into the btree ordering clauses, or are scan keys dynamically constructed using just-in-time- or generator patterns? Kind regards, Matthias van de Meent Neon (https://neon.tech/)