Reduce memory used by partitionwise joins
authorAmit Langote <amitlan@postgresql.org>
Mon, 25 Mar 2024 03:02:40 +0000 (12:02 +0900)
committerAmit Langote <amitlan@postgresql.org>
Mon, 25 Mar 2024 09:06:46 +0000 (18:06 +0900)
Specifically, this commit reduces the memory consumed by the
SpecialJoinInfos that are allocated for child joins in
try_partitionwise_join() by freeing them at the end of creating paths
for each child join.

A SpecialJoinInfo allocated for a given child join is a copy of the
parent join's SpecialJoinInfo, which contains the translated copies
of the various Relids bitmapsets and semi_rhs_exprs, which is a List
of Nodes.  The newly added freeing step frees the struct itself and
the various bitmapsets, but not semi_rhs_exprs, because there's no
handy function to free the memory of Node trees.

Author: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Reviewed-by: Richard Guo <guofenglinux@gmail.com>
Reviewed-by: Amit Langote <amitlangote09@gmail.com>
Reviewed-by: Andrey Lepikhov <a.lepikhov@postgrespro.ru>
Reviewed-by: Tomas Vondra <tomas.vondra@enterprisedb.com>
Discussion: https://postgr.es/m/CAExHW5tHqEf3ASVqvFFcghYGPfpy7o3xnvhHwBGbJFMRH8KjNw@mail.gmail.com

src/backend/optimizer/path/joinrels.c
src/backend/optimizer/util/pathnode.c
src/include/nodes/pathnodes.h

index 4750579b0a74a230c47ea9524742bcec00a6f507..c59aff28226aa860b4c0287b7d17847b7eeccd1e 100644 (file)
@@ -45,6 +45,7 @@ static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1,
 static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root,
                                                SpecialJoinInfo *parent_sjinfo,
                                                Relids left_relids, Relids right_relids);
+static void free_child_join_sjinfo(SpecialJoinInfo *child_sjinfo);
 static void compute_partition_bounds(PlannerInfo *root, RelOptInfo *rel1,
                                     RelOptInfo *rel2, RelOptInfo *joinrel,
                                     SpecialJoinInfo *parent_sjinfo,
@@ -1659,6 +1660,7 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
                                    child_restrictlist);
 
        pfree(appinfos);
+       free_child_join_sjinfo(child_sjinfo);
    }
 }
 
@@ -1666,6 +1668,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
  * Construct the SpecialJoinInfo for a child-join by translating
  * SpecialJoinInfo for the join between parents. left_relids and right_relids
  * are the relids of left and right side of the join respectively.
+ *
+ * If translations are added to or removed from this function, consider
+ * updating free_child_join_sjinfo() accordingly.
  */
 static SpecialJoinInfo *
 build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo,
@@ -1705,6 +1710,37 @@ build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo,
    return sjinfo;
 }
 
+/*
+ * free_child_join_sjinfo
+ *     Free memory consumed by a SpecialJoinInfo created by
+ *     build_child_join_sjinfo()
+ *
+ * Only members that are translated copies of their counterpart in the parent
+ * SpecialJoinInfo are freed here.
+ */
+static void
+free_child_join_sjinfo(SpecialJoinInfo *sjinfo)
+{
+   /*
+    * Dummy SpecialJoinInfos of inner joins do not have any translated fields
+    * and hence no fields that to be freed.
+    */
+   if (sjinfo->jointype != JOIN_INNER)
+   {
+       bms_free(sjinfo->min_lefthand);
+       bms_free(sjinfo->min_righthand);
+       bms_free(sjinfo->syn_lefthand);
+       bms_free(sjinfo->syn_righthand);
+
+       /*
+        * semi_rhs_exprs may in principle be freed, but a simple pfree() does
+        * not suffice, so we leave it alone.
+        */
+   }
+
+   pfree(sjinfo);
+}
+
 /*
  * compute_partition_bounds
  *     Compute the partition bounds for a join rel from those for inputs
index 0a7e5c2678f5fb6fc6fe6785d7bc9471bdee44f6..c29ca5a0da2f9187d918bb46232e1f93986ddbe1 100644 (file)
@@ -1707,8 +1707,9 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
    pathnode->subpath = subpath;
 
    /*
-    * Under GEQO, the sjinfo might be short-lived, so we'd better make copies
-    * of data structures we extract from it.
+    * Under GEQO and when planning child joins, the sjinfo might be
+    * short-lived, so we'd better make copies of data structures we extract
+    * from it.
     */
    pathnode->in_operators = copyObject(sjinfo->semi_operators);
    pathnode->uniq_exprs = copyObject(sjinfo->semi_rhs_exprs);
index b3069516b2449623ee32f6cf73fd2394c84479c3..08dbee002fe26c512045f19ab72431ba6c85ff81 100644 (file)
@@ -2856,6 +2856,9 @@ typedef struct PlaceHolderVar
  * cost estimation purposes it is sometimes useful to know the join size under
  * plain innerjoin semantics.  Note that lhs_strict and the semi_xxx fields
  * are not set meaningfully within such structs.
+ *
+ * We also create transient SpecialJoinInfos for child joins during
+ * partiotionwise join planning, which are also not present in join_info_list.
  */
 #ifndef HAVE_SPECIALJOININFO_TYPEDEF
 typedef struct SpecialJoinInfo SpecialJoinInfo;