Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1lMZwQ-0007O1-4R for pgsql-hackers@arkaria.postgresql.org; Wed, 17 Mar 2021 17:26:42 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1lMZvs-0006SL-JV for pgsql-hackers@arkaria.postgresql.org; Wed, 17 Mar 2021 17:26:08 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1lMZvs-0006SE-39 for pgsql-hackers@lists.postgresql.org; Wed, 17 Mar 2021 17:26:08 +0000 Received: from mail-ej1-x630.google.com ([2a00:1450:4864:20::630]) by makus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1lMZvn-0006be-RI for pgsql-hackers@postgresql.org; Wed, 17 Mar 2021 17:26:06 +0000 Received: by mail-ej1-x630.google.com with SMTP id ci14so3885733ejc.7 for ; Wed, 17 Mar 2021 10:26:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=enterprisedb-com.20150623.gappssmtp.com; s=20150623; h=subject:to:cc:references:from:message-id:date:user-agent :mime-version:in-reply-to:content-language:content-transfer-encoding; bh=vzaexiaeyF8usZoBmer6Aikx9/5ZyqPsvk+xzdg6ArE=; b=Acd7Qk9N7TF7JTGoEH1s+RE3GFeQDtvC0g+Zva5dWbCr3eGrUKx7kFkwh6G/6qLMEX nC+eRy+WpbNnXO54iRCdcrlV7K9cAf4yE3RHpsUs5qd999oqnqCeiqP0Ol3Y+hJfntsA u64cFKu2XVFa+G9ornlNjE2gHLm7b4gHlB/D6gWPlSVo173CIgC+KMK2jVr2q32mOsv1 NXkx0CemehXKJEsCPpNwyrsD8ooxdltwVvEm466o9ptFiCIV0ien1UNVw/JKmM4li8tO V24lS0M98LRgyMvdu0OYM3m0lnBstVHjaQEIjj1osJNQD99V3Xxvv11+2zXRx+wmJRuR g9Kg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:subject:to:cc:references:from:message-id:date :user-agent:mime-version:in-reply-to:content-language :content-transfer-encoding; bh=vzaexiaeyF8usZoBmer6Aikx9/5ZyqPsvk+xzdg6ArE=; b=SMuUHoqLpLtus9BqZvxU+MsjwZeMDWJSNXa7xriXRtX2/fKj/UI8OVu+apfyIYGIUE UvTagOS6Rf6flnGwF9fi9oAGhhvSbAa5pvZSUh4EW4PrUCXX5MkqmvcX5g36XUQ8UF8Q yd9xbETG5jkXaxmtXjw3QrxFra/wlRI3Opc9357wponettjNPEgT2tq+iHQuaHkU1L7M FURH54fPsFEjQizAgaJxN9ZFQC/c8UCu4AXFGxWa/Bmq9fs0vDHnN6iEeoPx7JmmXt7G YIOjA5T5g+tbt42hXm5ZMcAM853m+13Rw29uBGRomzqo3Y342ltwPDJUK7CxuQWfHOxF 3ziw== X-Gm-Message-State: AOAM530QrcFLRZYGO54POMY5a6Ey0MxwRzwoERFSBZu2dQnLd/Bu9/XA n8vOTsjxie28+N25cULEWV0z/85J5PQQxw0lUgy8cMpLt5xPMmXzUJRGg6V3tjrGXj8DkdUFVa4 yytM74llBXkc3Q8FxSq5B/iyaCMT8ESV3bJqVveABMx3z3ZAQ5JH8ctBEpkPRdMaVpKmopDIHWI i7FUr3DOE0twqnwSqMWWubtyrkUW0vIvyQ4kd6qJNhRN1GPA1A6KiOXNPkGpaaVHJDeWdt7yzfm oolDb61I+iD/h4IS+i4pW1vOWxqm8HNpaOEAA== X-Google-Smtp-Source: ABdhPJxaX8WPMqzRIsvIbIdc1B+eD+mdNJUoZHO3F1wKWmnEwxD9McljkhVwX4Iv4Z0ZFFJoWeBOtQ== X-Received: by 2002:a17:906:dd6:: with SMTP id p22mr37560909eji.111.1616001960917; Wed, 17 Mar 2021 10:26:00 -0700 (PDT) Received: from [10.137.0.20] (ip-86-49-253-127.net.upcbroadband.cz. [86.49.253.127]) by smtp.gmail.com with ESMTPSA id q26sm12083669eja.45.2021.03.17.10.25.59 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Wed, 17 Mar 2021 10:25:59 -0700 (PDT) Subject: Re: PoC/WIP: Extended statistics on expressions To: Dean Rasheed Cc: Justin Pryzby , PostgreSQL Hackers References: <20210108023537.GA19743@telsasoft.com> <20210116232208.GB8560@telsasoft.com> <2f2a3b3b-93e4-723f-144e-bc21f10d6b61@enterprisedb.com> <20210118045331.GF8560@telsasoft.com> <3f20da4d-89dc-0912-98d2-b6f0ef06c87f@enterprisedb.com> <20210122022958.GC27167@telsasoft.com> <2147e075-fef6-b1d5-e5f5-7f92ffefe827@enterprisedb.com> <1913c1fe-13da-7250-adf4-befeda397536@enterprisedb.com> From: Tomas Vondra Message-ID: Date: Wed, 17 Mar 2021 18:25:58 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.8.0 MIME-Version: 1.0 In-Reply-To: Content-Type: text/plain; charset=utf-8 Content-Language: en-US Content-Transfer-Encoding: 7bit X-CLOUD-SEC-AV-Info: enterprisedb,google_mail,monitor X-CLOUD-SEC-AV-Sent: true X-Gm-Spam: 0 X-Gm-Phishy: 0 List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi, On 3/17/21 4:55 PM, Dean Rasheed wrote: > On Sun, 7 Mar 2021 at 21:10, Tomas Vondra wrote: >> >> 2) ndistinct >> >> There's one thing that's bugging me, in how we handle "partial" matches. >> For each expression we track both the original expression and the Vars >> we extract from it. If we can't find a statistics matching the whole >> expression, we try to match those individual Vars, and we remove the >> matching ones from the list. And in the end we multiply the estimates >> for the remaining Vars. >> >> This works fine with one matching ndistinct statistics. Consider for example >> >> GROUP BY (a+b), (c+d) >> >> with statistics on [(a+b),c] - that is, expression and one column. > > I've just been going over this example, and I think it actually works > slightly differently from how you described, though I haven't worked > out the full general implications of that. > >> We parse the expressions into two GroupExprInfo >> >> {expr: (a+b), vars: [a, b]} >> {expr: (c+d), vars: [c, d]} >> > > Here, I think what you actually get, in the presence of stats on > [(a+b),c] is actually the following two GroupExprInfos: > > {expr: (a+b), vars: []} > {expr: (c+d), vars: [c, d]} > Yeah, right. To be precise, we get {expr: (a+b), vars: [(a+b)]} because in the first case we pass NIL, so add_unique_group_expr treats the whole expression as a var (a bit strange, but OK). > because of the code immediately after this comment in estimate_num_groups(): > > /* > * If examine_variable is able to deduce anything about the GROUP BY > * expression, treat it as a single variable even if it's really more > * complicated. > */ > > As it happens, that makes no difference in this case, where there is > just this one stats object, but it does change things when there are > two stats objects. > >> and the statistics matches the first item exactly (the expression). The >> second expression is not in the statistics, but we match "c". So we end >> up with an estimate for "(a+b), c" and have one remaining GroupExprInfo: >> >> {expr: (c+d), vars: [d]} > > Right. > >> Without any other statistics we estimate that as ndistinct for "d", so >> we end up with >> >> ndistinct((a+b), c) * ndistinct(d) >> >> which mostly makes sense. It assumes ndistinct(c+d) is product of the >> ndistinct estimates, but that's kinda what we've been always doing. > > Yes, that appears to be what happens, and it's probably the best that > can be done with the available stats. > >> But now consider we have another statistics on just (c+d). In the second >> loop we end up matching this expression exactly, so we end up with >> >> ndistinct((a+b), c) * ndistinct((c+d)) > > In this case, with stats on (c+d) as well, the two GroupExprInfos > built at the start change to: > > {expr: (a+b), vars: []} > {expr: (c+d), vars: []} > > so it actually ends up not using any multivariate stats, but instead > uses the two univariate expression stats, giving > > ndistinct((a+b)) * ndistinct((c+d)) > > which actually seems pretty good, and gave very good estimates in the > simple test case I tried. > Yeah, that works pretty well in this case. I wonder if we'd be better off extracting the Vars and doing what I mistakenly described as the current behavior. That's essentially mean extracting the Vars even in the case where we now pass NIL. My concern is that the current behavior (where we prefer expression stats over multi-column stats to some extent) works fine as long as the parts are independent, but once there's dependency it's probably more likely to produce underestimates. I think underestimates for grouping estimates were a risk in the past, so let's not make that worse. >> i.e. we kinda use the "c" twice. Which is a bit unfortunate. I think >> what we should do after the first loop is just discarding the whole >> expression and "expand" into per-variable GroupExprInfo, so in the >> second step we would not match the (c+d) statistics. > > Not using the (c+d) stats would give either > > ndistinct((a+b)) * ndistinct(c) * ndistinct(d) > > or > > ndistinct((a+b), c) * ndistinct(d) > > depending on exactly how the algorithm was changed. In my test, these > both gave worse estimates, but there are probably other datasets for > which they might do better. It all depends on how much correlation > there is between (a+b) and c. > > I suspect that there is no optimal strategy for handling overlapping > stats that works best for all datasets, but the current algorithm > seems to do a pretty decent job. > >> Of course, maybe there's a better way to pick the statistics, but I >> think our conclusion so far was that people should just create >> statistics covering all the columns in the query, to not have to match >> multiple statistics like this. > > Yes, I think that's always likely to work better, especially for > ndistinct stats, where all possible permutations of subsets of the > columns are included, so a single ndistinct stat can work well for a > range of different queries. > Yeah, I agree that's a reasonable mitigation. Ultimately, there's no perfect algorithm how to pick and combine stats when we don't know if there even is a statistical dependency between the subsets of columns. regards -- Tomas Vondra EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company