Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtp (Exim 4.80) (envelope-from ) id 1aYO0Q-0003BK-TF for pgsql-hackers@arkaria.postgresql.org; Wed, 24 Feb 2016 01:12:43 +0000 Received: from localhost ([127.0.0.1] helo=postgresql.org) by malur.postgresql.org with smtp (Exim 4.84) (envelope-from ) id 1aYO0Q-0004xF-Fj for pgsql-hackers@arkaria.postgresql.org; Wed, 24 Feb 2016 01:12:42 +0000 Received: from makus.postgresql.org ([2001:4800:1501:1::229]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.84) (envelope-from ) id 1aYO0P-0004x7-KC for pgsql-hackers@postgresql.org; Wed, 24 Feb 2016 01:12:41 +0000 Received: from mail-wm0-x22f.google.com ([2a00:1450:400c:c09::22f]) by makus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.84) (envelope-from ) id 1aYO0M-0004L8-P5 for pgsql-hackers@postgresql.org; Wed, 24 Feb 2016 01:12:40 +0000 Received: by mail-wm0-x22f.google.com with SMTP id g62so248960621wme.1 for ; Tue, 23 Feb 2016 17:12:38 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=2ndquadrant-com.20150623.gappssmtp.com; s=20150623; h=to:from:subject:message-id:date:user-agent:mime-version :content-type; bh=IFtv5551BnLkjevsPKhlvzFdGaKKK/zZRUUoizRj/l0=; b=EFwK5ibtObtCJnCgEGA2cVNEDDRxAnabTksuDEnpgCDKBhdHKDU6QoacPgJ8y1tK9+ FeHgjBWbJnlQpKrlirg63+BbBOW5NS0kQS96+34bILoXQ24PjC3n7dWFGDWS4RCdHCKR 0EkdcxR/K1Cv5mSJ84GWioShQwIXFVs4BmD0MIpSxY65UCVtDFHfkFn2bXrObLUTniX0 T972/qZ728+vv+PnzvFnPBYBgOc7yUZB70U3rbVCNfSdnKeUuY5jUbVf8xuDDXEYf7BF EBJ2hJqEGBJJ5lVoIMB1YU0R+84PLOhJpn29rgKbJD7JPTJ3vwaxwKfCI8a9UaXxvoSG bQRQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:to:from:subject:message-id:date:user-agent :mime-version:content-type; bh=IFtv5551BnLkjevsPKhlvzFdGaKKK/zZRUUoizRj/l0=; b=YkPP1QKhuvtCOtp5LZ9xlyC4pO+jmNPUBgyHbtKUTJ3nFKjr+1gvmIqSO9vvwHF3Dc csB/ODQXEYywSmdjHMdHyiZjnOhzeGQ6Ij9lBi5Ehy9L5HAK8IJBr7VW4Nu3F+p9R5gW shc595N1S7Z5MxdPFoR1O1YBFhL3tPD+BQaKbN6fqGrzQ0Se+vrQkJTZrb3Ui7BCa0zZ 5vN8qmXjyd+tl8FmQc2k9FJOUxqKWSLMWqt2xIIGzIpDMFaf4jmnk/P2lGXqch4edsey AXeNxpcqyTUZu7gd4DK6/565tvvA+dBJLLztjOkx1NL6+0QfVDp5CVN+Y2imctflT8SZ pSAA== X-Gm-Message-State: AG10YOTEqAxSaynde7GZQbfoDryFlyoYZb12S0xYFn3xQrVhay4iXRPWdWpKgBr3XhFBAMkujVvGtGz9kLkuuR6QMBgXEb/hJjDN/IT2mRU9j8DmjiCr/aufblbzxbhL5IF6O8wu0tbz9IHNKqBB4msPH/lNqsGlRhTKHnzHquJIE01rvKgl5XEC6Hx8EfjvHBpy4L2b+O7a X-Received: by 10.194.88.195 with SMTP id bi3mr35357389wjb.34.1456276357099; Tue, 23 Feb 2016 17:12:37 -0800 (PST) Received: from [10.137.1.13] (ip-78-45-216-26.net.upcbroadband.cz. [78.45.216.26]) by smtp.gmail.com with ESMTPSA id j10sm380683wjb.46.2016.02.23.17.12.35 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 23 Feb 2016 17:12:36 -0800 (PST) To: PostgreSQL-development From: Tomas Vondra Subject: improving GROUP BY estimation Message-ID: <56CD0381.5060502@2ndquadrant.com> Date: Wed, 24 Feb 2016 02:12:33 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Thunderbird/38.1.0 MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="------------040401030103010005010605" X-Pg-Spam-Score: -2.6 (--) List-Archive: List-Help: List-ID: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: X-Mailing-List: pgsql-hackers Precedence: bulk Sender: pgsql-hackers-owner@postgresql.org This is a multi-part message in MIME format. --------------040401030103010005010605 Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 7bit Hi, while working of using correlation statistics when estimating GROUP BY (or generally whatever uses estimate_num_groups), I've noticed that we apply the selectivity in a rather naive way. After estimating number of groups in the whole table - either by reading pg_stats.n_distinct for a single column, or multiplying (and doing some additional magic) for a group of columns, we do this: reldistinct *= rel->rows / rel->tuples; which essentially applies the selectivity to the ndistinct estimate. So when you have a table with 1000 distinct values in a column, and we read 10% of the table, we expect to get 100 distinct values. But consider for example the table has 100M rows, that the column has n_distinct=10000 and we're reading 1% of the table (i.e. 1M rows). The current code will estimate the GROUP BY cardinality to be 100 (as 1% of the n_distinct), but in reality we're more likely to see all 10000 distinct values. Example: CREATE TABLE t (a INT, b FLOAT); INSERT INTO t SELECT (100000 * random())::int, random() FROM generate_series(1,10000000) s(i); ANALYZE t; SELECT n_distinct FROM pg_stats WHERE attname = 'a'; n_distinct ------------ 98882 -- 1% of the table EXPLAIN ANALYZE SELECT a FROM t WHERE b < 0.01 GROUP BY a; QUERY PLAN ------------------------------------------------------------------------- HashAggregate (cost=179514.33..179523.45 rows=912 width=4) (actual time=916.224..955.136 rows=63492 loops=1) Group Key: a -> Seq Scan on t (cost=0.00..179053.25 rows=92216 width=4) (actual time=0.103..830.104 rows=100268 loops=1) Filter: (b < '0.01'::double precision) Rows Removed by Filter: 9899732 Planning time: 1.237 ms Execution time: 982.600 ms (7 rows) -- 5% of the table EXPLAIN ANALYZE SELECT a FROM t WHERE b < 0.05 GROUP BY a; QUERY PLAN ------------------------------------------------------------------------- HashAggregate (cost=180296.06..180345.22 rows=4916 width=4) (actual time=1379.519..1440.005 rows=99348 loops=1) Group Key: a -> Seq Scan on t (cost=0.00..179053.25 rows=497123 width=4) (actual time=0.096..1000.494 rows=500320 loops=1) Filter: (b < '0.05'::double precision) Rows Removed by Filter: 9499680 Planning time: 0.129 ms Execution time: 1480.986 ms (7 rows) This is getting especially bad when reading only a small fraction of a huge table - it's easy to construct cases with arbitrarily large error. And it's worth noting that the error is almost exclusively massive under-estimate, so the "wrong" direction as HashAggregate is still vulnerable to OOM (again, the larger the table the worse). So I think a more appropriate way to estimate this would be to find the expected number of distinct values when sampling with replacements, as explained for example in [1]. Applied to the code, it means changing the line from reldistinct *= rel->rows / rel->tuples; to reldistinct *= (1 - powl((reldistinct - 1) / reldistinct, rel->rows)); With this change, the estimates for the examples look like this: QUERY PLAN ------------------------------------------------------------------------- HashAggregate (cost=179283.79..179883.48 rows=59969 width=4) (actual time=897.071..934.764 rows=63492 loops=1) Group Key: a -> Seq Scan on t (cost=0.00..179053.25 rows=92216 width=4) (actual time=0.104..820.276 rows=100268 loops=1) Filter: (b < '0.01'::double precision) Rows Removed by Filter: 9899732 Planning time: 1.261 ms Execution time: 962.812 ms (7 rows) and QUERY PLAN ------------------------------------------------------------------------- Group (cost=232886.15..235371.76 rows=98234 width=4) (actual time=1519.545..2104.447 rows=99348 loops=1) Group Key: a -> Sort (cost=232886.15..234128.95 rows=497123 width=4) (actual time=1519.540..1838.575 rows=500320 loops=1) Sort Key: a Sort Method: external merge Disk: 6824kB -> Seq Scan on t (cost=0.00..179053.25 rows=497123 width=4) (actual time=0.090..1044.099 rows=500320 loops=1) Filter: (b < '0.05'::double precision) Rows Removed by Filter: 9499680 Planning time: 0.133 ms Execution time: 2147.340 ms (10 rows) So much better. Clearly, there are cases where this will over-estimate the cardinality - for example when the values are somehow correlated. But I'd argue over-estimating is better because of the OOM issues in Hash Aggregate. regards [1] http://math.stackexchange.com/questions/72223/finding-expected-number-of-distinct-values-selected-from-a-set-of-integers -- Tomas Vondra http://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services --------------040401030103010005010605 Content-Type: binary/octet-stream; name="estimate-num-groups-v1.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="estimate-num-groups-v1.patch" diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index 46c95b0..82a7f7f 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -3438,9 +3438,9 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, reldistinct = clamp; /* - * Multiply by restriction selectivity. + * Estimate number of distinct values expected in given number of rows. */ - reldistinct *= rel->rows / rel->tuples; + reldistinct *= (1 - powl((reldistinct - 1) / reldistinct, rel->rows)); /* * Update estimate of total distinct groups. --------------040401030103010005010605 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------040401030103010005010605--