Re: [PoC] Reducing planning time when tables have many partitions - Mailing list pgsql-hackers
From | Yuya Watari |
---|---|
Subject | Re: [PoC] Reducing planning time when tables have many partitions |
Date | |
Msg-id | CAJ2pMka6KBzBu6--4xH3FrP2ZQFFjUG0phiE_eP34gTLSwGGsQ@mail.gmail.com Whole thread Raw |
In response to | Re: [PoC] Reducing planning time when tables have many partitions (David Rowley <dgrowleyml@gmail.com>) |
Responses |
Re: [PoC] Reducing planning time when tables have many partitions
|
List | pgsql-hackers |
Dear David, Thank you for your comments on my patch. I really apologize for my late response. On Thu, Mar 24, 2022 at 11:03 AM David Rowley <dgrowleyml@gmail.com> wrote: > I think a better way to solve this would be just to have a single hash > table over all EquivalenceClasses that allows fast lookups of > EquivalenceMember->em_expr. I think there's no reason that a given > Expr should appear in more than one non-merged EquivalenceClass. The > EquivalenceClass a given Expr belongs to would need to be updated > during the merge process. Thank you for your idea. However, I think building a hash table whose key is EquivalenceMember->em_expr does not work for this case. What I am trying to optimize in this patch is the following code. ===== EquivalenceClass *ec = /* given */; EquivalenceMember *em; ListCell *lc; foreach(lc, ec->ec_members) { em = (EquivalenceMember *) lfirst(lc); /* predicate is bms_equal or bms_is_subset, etc */ if (!predicate(em)) continue; /* The predicate satisfies */ do something...; } ===== From my observation, the predicates above will be false in most cases and the subsequent processes are not executed. My optimization is based on this notion and utilizes hash tables to eliminate calls of predicates. If the predicate were "em->em_expr == something", the hash table whose key is em_expr would be effective. However, the actual predicates are not of this type but the following. // Find EquivalenceMembers whose relids is equal to the given relids (1) bms_equal(em->em_relids, relids) // Find EquivalenceMembers whose relids is a subset of the given relids (2) bms_is_subset(em->em_relids, relids) Since these predicates perform a match search for not em_expr but em_relids, we need to build a hash table with em_relids as key. If so, we can drastically reduce the planning time for the pattern (1). Besides, by enumerating all subsets of relids, pattern (2) can be optimized. The detailed algorithm is described in the first email. I show an example of the pattern (1). The next code is in src/backend/optimizer/path/equivclass.c. As can be seen from this code, the foreach loop tries to find an EquivalenceMember whose cur_em->em_relids is equal to rel->relids. If found, subsequent processing will be performed. == Before patched == List * generate_implied_equalities_for_column(PlannerInfo *root, RelOptInfo *rel, ec_matches_callback_type callback, void *callback_arg, Relids prohibited_rels) { ... EquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i); EquivalenceMember *cur_em; ListCell *lc2; cur_em = NULL; foreach(lc2, cur_ec->ec_members) { cur_em = (EquivalenceMember *) lfirst(lc2); if (bms_equal(cur_em->em_relids, rel->relids) && callback(root, rel, cur_ec, cur_em, callback_arg)) break; cur_em = NULL; } if (!cur_em) continue; ... } === My patch modifies this code as follows. The em_foreach_relids_equals is a newly defined macro that finds EquivalenceMember satisfying the bms_equal. The macro looks up a hash table using rel->relids as a key. This type of optimization cannot be achieved without using hash tables whose key is em->em_relids. == After patched == List * generate_implied_equalities_for_column(PlannerInfo *root, RelOptInfo *rel, ec_matches_callback_type callback, void *callback_arg, Relids prohibited_rels) { ... EquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i); EquivalenceMember *cur_em; EquivalenceMember *other_em; cur_em = NULL; em_foreach_relids_equals(cur_em, cur_ec, rel->relids) { Assert(bms_equal(cur_em->em_relids, rel->relids)); if (callback(root, rel, cur_ec, cur_em, callback_arg)) break; cur_em = NULL; } if (!cur_em) continue; ... } === > We might not want to build the hash table for all queries. I agree with you. Building a lot of hash tables will consume much memory. My idea for this problem is to let the hash table's key be a pair of EquivalenceClass and Relids. However, this approach may lead to increasing looking up time of the hash table. ========== I noticed that the previous patch does not work with the current HEAD. I attached the modified one to this email. Additionally, I added my patch to the current commit fest [1]. [1] https://commitfest.postgresql.org/38/3701/ -- Best regards, Yuya Watari
Attachment
pgsql-hackers by date: