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 1mYCfF-000321-B3 for pgsql-hackers@arkaria.postgresql.org; Wed, 06 Oct 2021 19:33:17 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1mYCfE-0007Os-85 for pgsql-hackers@arkaria.postgresql.org; Wed, 06 Oct 2021 19:33:16 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1mYCfD-0007KQ-GU for pgsql-hackers@lists.postgresql.org; Wed, 06 Oct 2021 19:33:15 +0000 Received: from mail-ed1-x52b.google.com ([2a00:1450:4864:20::52b]) by magus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1mYCf5-0001M4-A7 for pgsql-hackers@postgresql.org; Wed, 06 Oct 2021 19:33:14 +0000 Received: by mail-ed1-x52b.google.com with SMTP id l7so14151924edq.3 for ; Wed, 06 Oct 2021 12:33:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=enterprisedb.com; s=google; h=subject:from:to:references:message-id:date:user-agent:mime-version :in-reply-to:content-language; bh=Cs+KPOP9FZW3fSAIRlJv6RBpofj7rQtsQYF2KF11tWI=; b=AMzESqnsMh/A+8fgpsaKhEt0PjHAJGFf9b3UMr8h+JuMlytE9RmSu+lS6l3V7/jeC1 WlLosOE+ASVgpqqSRy/w7IIithACcade8T/OUSq1ujdy5gWUCTw3AC2OxuLdMxUmdZXX FCio/zaEbO/VXb7MGcRJju9SGfsUzsm9ugrjtx+Q8jS8ris21cpImDnOz4Ks+GT/6Noe F4mQtT9W8Gz0HhPY1KoYXnmLpG8DOJU8LTwaLlo2pZHLM71QpzGbyMdy8CffSsUpk5WI 8Xx4GvNytbSi3fVED33EzYpcc0uLMlrcpwdFd4H1mmLIq1F+ILPXLOcH6JdtX5zPpzPm opsQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:subject:from:to:references:message-id:date :user-agent:mime-version:in-reply-to:content-language; bh=Cs+KPOP9FZW3fSAIRlJv6RBpofj7rQtsQYF2KF11tWI=; b=ISmiuCh8KUvoqqwqQfdoi7Y4kzscpwhL48xznbPt0IX+9bW2pch7j8fAMiJ1jequGu 1LeYOEOlk7tXhG8CGpZccNgEBF8ui2Zt7YpQaUgBq32rmTSrAngIPQYJMBNl6QTm3myJ m3F+O1lW4qZjHy7eWDJZg7DDGI7BgjI+OM6dSWXyh2GcfLBSYXYFjB5y8gp3NscQmnuk tZRX0F2dVtVxQR3mH7owQfgYgk3pzI/0OkqWwUGmZ2cnOYGhB4/kzl6mVdP8F3yaYFqT Wt5PuLvL9bqTgfgoaaN/L+FIjGD5oxyBymJEo/FAI+nMsp3US7d/g/Dc3eqVAWcqXESL 2PgQ== X-Gm-Message-State: AOAM532q08bzX14eySUFEGxuwpWwT5P3oxt9yGQnQ+6HWt7MrmmG5ECv Yt7ak3/CQUQkl7R82BXsEsSYjc5gB8khIl70TG6Tg0Kd4V2lDxKOToxRVWsMWP0S3TIdSJjJVRt SFksCmwg0fJX3OCn+gVWjxblOt7nt88EhD2H5sJ+FWmyrty6cfjtaAzbe8UroeIdKh1i7i5MDDl gYdmIUROokGF0AxkKRHxZy1LTlbTOb3eyzxsTpLZQuPhAq4bdg2NpHpyefT3uv6GQ38Nf5wxiyj UvvpFGjRSzEiq0bs7oWKl1AViWgMxdmHD+EkA== X-Google-Smtp-Source: ABdhPJz53FGX2ldeL7uC+eLtN5QYzDh6bLl/TdOP9KFePuKk7Z5ruJoaFSZbJRAt8oKUxELIW2RD9g== X-Received: by 2002:a50:cf87:: with SMTP id h7mr138014edk.152.1633548785219; Wed, 06 Oct 2021 12:33:05 -0700 (PDT) Received: from [10.137.0.19] (ip-86-49-251-104.net.upcbroadband.cz. [86.49.251.104]) by smtp.gmail.com with ESMTPSA id bj10sm9250043ejb.17.2021.10.06.12.33.04 for (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Wed, 06 Oct 2021 12:33:04 -0700 (PDT) Subject: Re: using extended statistics to improve join estimates From: Tomas Vondra To: pgsql-hackers References: <2ed75657-e084-9539-c6de-597e5675014c@enterprisedb.com> Message-ID: Date: Wed, 6 Oct 2021 21:33:03 +0200 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.11.0 MIME-Version: 1.0 In-Reply-To: <2ed75657-e084-9539-c6de-597e5675014c@enterprisedb.com> Content-Type: multipart/mixed; boundary="------------FBD3E703B48D398697A870FE" Content-Language: en-US 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 This is a multi-part message in MIME format. --------------FBD3E703B48D398697A870FE Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 7bit Hi, attached is an improved version of this patch, addressing some of the points mentioned in my last message: 1) Adds a couple regression tests, testing various join cases with expressions, additional conditions, etc. 2) Adds support for expressions, so the join clauses don't need to reference just simple columns. So e.g. this can benefit from extended statistics, when defined on the expressions: -- CREATE STATISTICS s1 ON (a+1), b FROM t1; -- CREATE STATISTICS s2 ON (a+1), b FROM t2; SELECT * FROM t1 JOIN t2 ON ((t1.a + 1) = (t2.a + 1) AND t1.b = t2.b); 3) Can combine extended statistics and regular (per-column) statistics. The previous version required extended statistics MCV on both sides of the join, but adding extended statistics on both sides may impractical (e.g. if one side does not have correlated columns it's silly to have to add it just to make this patch happy). For example you may have extended stats on the dimension table but not the fact table, and the patch still can combine those two. Of course, if there's no MCV on either side, we can't do much. So this patch works when both sides have extended statistics MCV, or when one side has extended MCV and the other side regular MCV. It might seem silly, but the extended MCV allows considering additional baserel conditions (if there are any). examples ======== The table / data is very simple, but hopefully good enough for some simple examples. create table t1 (a int, b int, c int); create table t2 (a int, b int, c int); insert into t1 select mod(i,50), mod(i,50), mod(i,50) from generate_series(1,1000) s(i); insert into t2 select mod(i,50), mod(i,50), mod(i,50) from generate_series(1,1000) s(i); analyze t1, t2; First, without extended stats (just the first line of explain analyze, to keep the message short): explain analyze select * from t1 join t2 on (t1.a = t2.a and t1.b = t2.b); QUERY PLAN ------------------------------------------------------------------------ Hash Join (cost=31.00..106.00 rows=400 width=24) (actual time=5.426..22.678 rows=20000 loops=1) explain analyze select * from t1 join t2 on (t1.a = t2.a) where t1.c < 25; QUERY PLAN ------------------------------------------------------------------------ Hash Join (cost=28.50..160.75 rows=10000 width=24) (actual time=5.325..19.760 rows=10000 loops=1) explain analyze select * from t1 join t2 on (t1.a = t2.a) where t1.c < 25 and t2.c > 25; QUERY PLAN ------------------------------------------------------------------------ Hash Join (cost=24.50..104.75 rows=4800 width=24) (actual time=5.618..5.639 rows=0 loops=1) Now, let's create statistics: create statistics s1 on a, b, c from t1 ; create statistics s2 on a, b, c from t2 ; analyze t1, t2; and now the same queries again: explain analyze select * from t1 join t2 on (t1.a = t2.a and t1.b = t2.b); QUERY PLAN ------------------------------------------------------------------------ Hash Join (cost=31.00..106.00 rows=20000 width=24) (actual time=5.448..22.713 rows=20000 loops=1) explain analyze select * from t1 join t2 on (t1.a = t2.a) where t1.c < 25; QUERY PLAN ------------------------------------------------------------------------ Hash Join (cost=28.50..160.75 rows=10000 width=24) (actual time=5.317..16.680 rows=10000 loops=1) explain analyze select * from t1 join t2 on (t1.a = t2.a) where t1.c < 25 and t2.c > 25; QUERY PLAN ------------------------------------------------------------------------ Hash Join (cost=24.50..104.75 rows=1 width=24) (actual time=5.647..5.667 rows=0 loops=1) Those examples are a bit simplistic, but the improvements are fairly clear I think. limitations & open issues ========================= Let's talk about the main general restrictions and open issues in the current patch that I can think of at the moment. 1) statistics covering all join clauses The patch requires the statistics to cover all the join clauses, mostly because it simplifies the implementation. This means that to use the per-column statistics, there has to be just a single join clause. AFAICS this could be relaxed and we could use multiple statistics to estimate the clauses. But it'd make selection of statistics much more complicated, because we have to pick "matching" statistics on both sides of the join. So it seems like an overkill, and most joins have very few conditions anyway. 2) only equality join clauses The other restriction is that at the moment this only supports simple equality clauses, combined with AND. So for example this is supported t1 JOIN t2 ON ((t1.a = t2.a) AND (t1.b + 2 = t2.b + 1)) while these are not: t1 JOIN t2 ON ((t1.a = t2.a) OR (t1.b + 2 = t2.b + 1)) t1 JOIN t2 ON ((t1.a - t2.a = 0) AND (t1.b + 2 = t2.b + 1)) t1 JOIN t2 ON ((t1.a = t2.a) AND ((t1.b = t2.b) OR (t1.c = t2.c))) I'm not entirely sure these restrictions can be relaxed. It's not that difficult to evaluate these cases when matching items between the MCV lists, similarly to how we evaluate bitmaps for baserel estimates. But I'm not sure what to do about the part not covered by the MCV lists. The eqjoinsel() approach uses ndistinct estimates for that, but that only works for AND clauses, I think. How would that work for OR? Similarly, I'm not sure we can do much for non-equality conditions, but those are currently estimated as default selectivity in selfuncs.c. 3) estimation by join pairs At the moment, the estimates are calculated for pairs of relations, so for example given a query explain analyze select * from t1 join t2 on (t1.a = t2.a and t1.b = t2.b) join t3 on (t1.b = t3.b and t2.c = t3.c); we'll estimate the first join (t1,t2) just fine, but then the second join actually combines (t1,t2,t3). What the patch currently does is it splits it into (t1,t2) and (t2,t3) and estimates those. I wonder if this should actually combine all three MCVs at once - we're pretty much combining the MCVs into one large MCV representing the join result. But I haven't done that yet, as it requires the MCVs to be combined using the join clauses (overlap in a way), but I'm not sure how likely that is in practice. In the example it could help, but that's a bit artificial example. 4) still just inner equi-joins I haven't done any work on extending this to outer joins etc. Adding outer and semi joins should not be complicated, mostly copying and tweaking what eqjoinsel() does. regards -- Tomas Vondra EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company --------------FBD3E703B48D398697A870FE Content-Type: text/x-patch; charset=UTF-8; name="0001-Estimate-joins-using-extended-statistics-20211006.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0001-Estimate-joins-using-extended-statistics-20211006.patch"