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 1lv1dL-0001Rw-4N for pgsql-performance@arkaria.postgresql.org; Sun, 20 Jun 2021 17:53:23 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1lv1dJ-0000UI-BC for pgsql-performance@arkaria.postgresql.org; Sun, 20 Jun 2021 17:53:21 +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 1lul2O-0005Wu-1A for pgsql-performance@lists.postgresql.org; Sun, 20 Jun 2021 00:10:08 +0000 Received: from mail-qt1-x82f.google.com ([2607:f8b0:4864:20::82f]) by makus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1lul2L-00051v-Qh for pgsql-performance@lists.postgresql.org; Sun, 20 Jun 2021 00:10:07 +0000 Received: by mail-qt1-x82f.google.com with SMTP id p21so10570451qtw.6 for ; Sat, 19 Jun 2021 17:10:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:message-id:mime-version:subject:date:in-reply-to:cc:to :references; bh=4qwrKM2y5fC1Pqlb1Pd/6Ix80NZuMCCvk/Vb/DFOG9o=; b=T8hNoq3WyZJrp5uw7oQ/dfhliCa3iuRZpaH5MEIC7MzwUEZRooHvcjTgeaAU0VMHjS IiSv+FbQ18ECBn8vne2ulw2D8fyrGhiNaOY5MY3/eCXT6l3udm2UPUyCY147A7wfhEd7 Rpz6XP3AynnBsdF3niOV10YIqzT0DXH0uS33oj608LwMOehGR+hjBxTjJIf4zAjO76FV 6MZlTzxGCfBtoQkkumbNhowvXIeQTXjLOrh/loWQClRn5oyK0lTJDKWhcROwU6NYau3+ 2z89fESP5d1Kc89kmgMAYN/WrAoSBtJt3iK200iopW4MEFdXOJsVeIv67WUamBJlKOs/ qYyw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:message-id:mime-version:subject:date :in-reply-to:cc:to:references; bh=4qwrKM2y5fC1Pqlb1Pd/6Ix80NZuMCCvk/Vb/DFOG9o=; b=KM2yEecPd7Y0nedmxpixRWuBTFK9rwZ5UWTcuuMO5r3w5WKvPpqlKZq3BBbQQbEKth 6D8PEC6Y7EPgZEoV0OqgF+T3272hmIraXRVOAEqICda0mU2yREzVxEXadN7acMu3VTWP hwDat4JOVNVOMDB+rxwIqhnnGQrWFb/m+PowKxaMAO6MTvcm2WhTaLnYNu+UGLwlp7AD 0usfIKbHLZbMLgdaXD/sYnGnieNQhpxevlMUyI3ac3CuzGmIUzNVr+MbCLfI5IYxUwOR 4HJnVuXRvYIX7FyNJXSYGLoilUI5piXHL8+49rsfmQKYrLnBcmhpCJzyFwFhFq0/l84H ys4w== X-Gm-Message-State: AOAM532jNE0R4fTKM5qth0inA5fGpEuHz7DDMObKxQwOcoxXOcDRVMeT 6+kJjroXWbLpQsHftR/+NVU= X-Google-Smtp-Source: ABdhPJxYh8aRossi8kaQS54NXN5u0XDRVTUWiP9Bz1J6DtGBp+wvF3k1o7oX7uBQC0/dS4qpr5/icw== X-Received: by 2002:ac8:6949:: with SMTP id n9mr17059156qtr.73.1624147803852; Sat, 19 Jun 2021 17:10:03 -0700 (PDT) Received: from ip-192-168-1-83.ec2.internal (190-21-40-158.baf.movistar.cl. [190.21.40.158]) by smtp.gmail.com with ESMTPSA id e24sm7715638qtp.97.2021.06.19.17.10.01 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Sat, 19 Jun 2021 17:10:03 -0700 (PDT) From: Manuel Weitzman Message-Id: Content-Type: multipart/mixed; boundary="Apple-Mail=_3FBDACC4-0323-4B92-A579-9140BC1AD60D" Mime-Version: 1.0 (Mac OS X Mail 14.0 \(3654.60.0.2.21\)) Subject: Re: Planning performance problem (67626.278ms) Date: Sat, 19 Jun 2021 20:09:58 -0400 In-Reply-To: Cc: David Rowley , Jeremy Schneider , "pgsql-performance@lists.postgresql.org" , Andres Freund To: Tom Lane References: <6d58f68e-ed1a-c48e-1440-691554f6e1e7@9livesdata.com> <20190408144210.GG10080@telsasoft.com> <20190408232644.r6xixkeulsmomwfj@alap3.anarazel.de> <3f5e5673-06aa-bf7c-cdb7-0e053c45ba26@amazon.com> <767433.1619015825@sss.pgh.pa.us> X-Mailer: Apple Mail (2.3654.60.0.2.21) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --Apple-Mail=_3FBDACC4-0323-4B92-A579-9140BC1AD60D Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=us-ascii Hello everyone, > Apparently, the planner isn't reusing the data boundaries across = alternative > plans. It would be nicer if the planner remembered each column = boundaries > for later reuse (within the same planner execution). I've written a very naive (and crappy) patch to show how adding memorization to get_actual_variable_range() could help the planner on scenarios with a big number of joins. For the previous example, > explain (analyze, buffers) > select * from a > join b b1 on (b1.a =3D a.a) > join b b2 on (b2.a =3D a.a) > where b1.a in (1,100,10000,1000000,1000001); each time you add a join clause the planner has to read an extra ~5[K] buffers and gets about 200[ms] slower. 1 join Planning: Buffers: shared hit=3D9 read=3D27329 Planning Time: 101.745 ms Execution Time: 0.082 ms 2 joins Planning: Buffers: shared hit=3D42 read=3D81988 Planning Time: 303.237 ms Execution Time: 0.102 ms 3 joins Planning: Buffers: shared hit=3D94 read=3D136660 Planning Time: 508.947 ms Execution Time: 0.155 ms 4 joins Planning: Buffers: shared hit=3D188 read=3D191322 Planning Time: 710.981 ms Execution Time: 0.168 ms After adding memorization the cost in buffers remains constant and the latency deteriorates only marginally (as expected) with each join. 1 join Planning: Buffers: shared hit=3D10 read=3D27328 Planning Time: 97.889 ms Execution Time: 0.066 ms 2 joins Planning: Buffers: shared hit=3D7 read=3D27331 Planning Time: 100.589 ms Execution Time: 0.111 ms 3 joins Planning: Buffers: shared hit=3D9 read=3D27329 Planning Time: 105.669 ms Execution Time: 0.134 ms 4 joins Planning: Buffers: shared hit=3D132 read=3D27370 Planning Time: 155.716 ms Execution Time: 0.219 ms I'd be happy to improve this patch into something better. Though I'd like suggestions on how to do it: I have this idea of creating a local "memorization" struct instance = within standard_planner(). That would require passing on a pointer down until it reaches get_actual_variable_range(), which I think would be quite ugly, if done just to improve the planner for this scenario. Is there any better mechanism I could reuse from other modules? (utils or cache, for example). Regards, Manuel --Apple-Mail=_3FBDACC4-0323-4B92-A579-9140BC1AD60D Content-Disposition: attachment; filename=actual_variable_range_memorization.diff Content-Type: application/octet-stream; x-unix-mode=0644; name="actual_variable_range_memorization.diff" Content-Transfer-Encoding: 7bit diff --git i/src/backend/optimizer/plan/planner.c w/src/backend/optimizer/plan/planner.c index 5d515842dc..65d74be557 100644 --- i/src/backend/optimizer/plan/planner.c +++ w/src/backend/optimizer/plan/planner.c @@ -316,6 +316,10 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, glob->transientPlan = false; glob->dependsOnRole = false; + + /* clear crappy variable range cache */ + clear_variable_range_cache(); + /* * Assess whether it's feasible to use parallel mode for this query. We * can't do this in a standalone backend, or if the command will try to diff --git i/src/backend/utils/adt/selfuncs.c w/src/backend/utils/adt/selfuncs.c index e96bfce4dc..0e0cc68307 100644 --- i/src/backend/utils/adt/selfuncs.c +++ w/src/backend/utils/adt/selfuncs.c @@ -5567,6 +5567,99 @@ get_stats_slot_range(AttStatsSlot *sslot, Oid opfuncoid, FmgrInfo *opproc, } +/* + * a lot of crappy code to emulate variable range caching + * just intended for testing purposes + */ +typedef struct ActualVariableRangeCache ActualVariableRangeCache; +struct ActualVariableRangeCache { + Oid indexoid; + int64 min_value; + int64 max_value; + bool has_min; + bool has_max; +}; + +static unsigned long range_caches_size = 0; +static ActualVariableRangeCache range_caches[1000] = {{0, 0, 0, false, false}}; + +void +clear_variable_range_cache(void) +{ + for (unsigned long i = 0; i < range_caches_size; i++) { + range_caches[i].indexoid = 0; + range_caches[i].has_min = false; + range_caches[i].has_max = false; + } + range_caches_size = 0; +} + +static bool +get_cached_actual_variable_min(Oid indexoid, Datum *min) +{ + for (unsigned long i = 0; i < range_caches_size; i++) { + if (range_caches[i].indexoid == indexoid) { + if (!range_caches[i].has_min) + return false; + + *min = range_caches[i].min_value; + return true; + } + } + return false; +} + + +static bool +get_cached_actual_variable_max(Oid indexoid, Datum *max) +{ + for (unsigned long i = 0; i < range_caches_size; i++) { + if (range_caches[i].indexoid == indexoid) { + if (!range_caches[i].has_max) + return false; + + *max = range_caches[i].max_value; + return true; + } + } + return false; +} + + +static void +add_actual_variable_min_to_cache(Oid indexoid, Datum *min) +{ + for (unsigned long i = 0; i < range_caches_size; i++) { + if (range_caches[i].indexoid == indexoid) { + range_caches[i].min_value = *min; + range_caches[i].has_min = true; + return; + } + } + range_caches[range_caches_size].indexoid = indexoid; + range_caches[range_caches_size].min_value = *min; + range_caches[range_caches_size].has_min = true; + range_caches_size++; +} + + +static void +add_actual_variable_max_to_cache(Oid indexoid, Datum *max) +{ + for (unsigned long i = 0; i < range_caches_size; i++) { + if (range_caches[i].indexoid == indexoid) { + range_caches[i].max_value = *max; + range_caches[i].has_max = true; + return; + } + } + range_caches[range_caches_size].indexoid = indexoid; + range_caches[range_caches_size].max_value = *max; + range_caches[range_caches_size].has_max = true; + range_caches_size++; +} + + /* * get_actual_variable_range * Attempt to identify the current *actual* minimum and/or maximum @@ -5691,15 +5784,23 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata, /* If min is requested ... */ if (min) { - have_data = get_actual_variable_endpoint(heapRel, - indexRel, - indexscandir, - scankeys, - typLen, - typByVal, - slot, - oldcontext, - min); + /* try to reuse min from previous planner nodes */ + have_data = get_cached_actual_variable_min(index->indexoid, min); + /* or fetch from index if first time here */ + if (!have_data) + have_data = get_actual_variable_endpoint(heapRel, + indexRel, + indexscandir, + scankeys, + typLen, + typByVal, + slot, + oldcontext, + min); + /* update the min memorization */ + if (have_data) + add_actual_variable_min_to_cache(index->indexoid, + min); } else { @@ -5710,16 +5811,25 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata, /* If max is requested, and we didn't find the index is empty */ if (max && have_data) { + /* try to reuse max from previous planner nodes */ + have_data = get_cached_actual_variable_max(index->indexoid, max); + + /* or fetch from index if first time here */ /* scan in the opposite direction; all else is the same */ - have_data = get_actual_variable_endpoint(heapRel, - indexRel, - -indexscandir, - scankeys, - typLen, - typByVal, - slot, - oldcontext, - max); + if (!have_data) + have_data = get_actual_variable_endpoint(heapRel, + indexRel, + -indexscandir, + scankeys, + typLen, + typByVal, + slot, + oldcontext, + max); + /* update the max memorization */ + if (have_data) + add_actual_variable_max_to_cache(index->indexoid, + max); } /* Clean everything up */ diff --git i/src/include/utils/selfuncs.h w/src/include/utils/selfuncs.h index 7ac4a06391..a46c272ac7 100644 --- i/src/include/utils/selfuncs.h +++ w/src/include/utils/selfuncs.h @@ -220,4 +220,7 @@ extern Selectivity scalararraysel_containment(PlannerInfo *root, Oid elemtype, bool isEquality, bool useOr, int varRelid); +extern void +clear_variable_range_cache(void); + #endif /* SELFUNCS_H */ --Apple-Mail=_3FBDACC4-0323-4B92-A579-9140BC1AD60D--