Thread
Commits
-
Consider explicit incremental sort for Append and MergeAppend
- 55a780e9476a 19 (unreleased) landed
-
Consider explicit incremental sort for Append and MergeAppend
Richard Guo <guofenglinux@gmail.com> — 2025-05-12T09:29:49Z
For ordered Append or MergeAppend, it seems that incremental sort is currently not considered when injecting an explicit sort into subpaths that are not sufficiently ordered. For instance: set enable_seqscan to off; explain (costs off) select hundred as x, two as y from tenk1 union all select thousand as x, tenthous as y from tenk1 order by x, y; QUERY PLAN ------------------------------------------------------------------- Merge Append Sort Key: tenk1.hundred, tenk1.two -> Sort Sort Key: tenk1.hundred, tenk1.two -> Index Scan using tenk1_hundred on tenk1 -> Index Only Scan using tenk1_thous_tenthous on tenk1 tenk1_1 (6 rows) Similar to what we do in 828e94c9d, I think we can also consider using explicit incremental sort for ordered Append or MergeAppend. Here is a patch doing that. With this patch, the plan above changes to: QUERY PLAN ------------------------------------------------------------------- Merge Append Sort Key: tenk1.hundred, tenk1.two -> Incremental Sort Sort Key: tenk1.hundred, tenk1.two Presorted Key: tenk1.hundred -> Index Scan using tenk1_hundred on tenk1 -> Index Only Scan using tenk1_thous_tenthous on tenk1 tenk1_1 (7 rows) This targets v19. Thanks Richard -
Re: Consider explicit incremental sort for Append and MergeAppend
Andrei Lepikhov <lepihov@gmail.com> — 2025-05-15T13:03:36Z
On 12/5/2025 11:29, Richard Guo wrote: > For ordered Append or MergeAppend, it seems that incremental sort is > currently not considered when injecting an explicit sort into subpaths > that are not sufficiently ordered. For instance: Thanks for doing this job. I have reviewed your patch and want to put here some thoughts: 0. The patch looks simple enough to be safe. I passed through the code and found no issues except comments (see thought No.1). I will be okay if you commit it. 1. I'm not very happy with the fact that it strengthens the cost_append connection with create_append_plan. At least, there should be cross-reference comments to let developers know if they change something inside one of these functions. 2. IncrementalSort is not always more effective - it depends on the column's number of groups. In my experience, a non-cost-based decision one day meets the problematic case, and the people who stick with it are much more confused than in the case when planner decision connected to the costings - they trust the cost model or the cost model tuned by GUCs. 3. The functions label_incrementalsort_with_costsize and label_sort_with_costsize are not ideal architectural decisions. Attempting to improve sort / incremental sort cost functions, I am always stuck in the absence of some necessary data from the sorting path and RelOptInfo at this stage. As an alternative, you may check the approach of [1], where we decide how to adjust a subpath to MergeAppend needs inside generate_orderedappend_paths using a cost-based approach. Also, would you have a chance to look into the [1,2]? It seems like a further improvement, bringing a bit closer optimality of appended path choice to single-table scan choice. [1] https://www.postgresql.org/message-id/flat/25d6a2cd161673d51373b7e07e6d9dd6%40postgrespro.ru [2] https://www.postgresql.org/message-id/f0206ef2-6b5a-4d07-8770-cfa7cd30f685@gmail.com -- regards, Andrei Lepikhov
-
Re: Consider explicit incremental sort for Append and MergeAppend
Robert Haas <robertmhaas@gmail.com> — 2025-05-19T13:21:07Z
On Thu, May 15, 2025 at 9:03 AM Andrei Lepikhov <lepihov@gmail.com> wrote: > 2. IncrementalSort is not always more effective - it depends on the > column's number of groups. In my experience, a non-cost-based decision > one day meets the problematic case, and the people who stick with it are > much more confused than in the case when planner decision connected to > the costings - they trust the cost model or the cost model tuned by GUCs. I wonder if this could be fixed in nodeIncrementalSort.c. I think it's a problem to rely on planner estimates because planner estimates of the # of groups are quite unreliable. But at runtime, we can notice whether the groups are small or large and adjust the algorithm accordingly. In fact, it looks like we already have some logic for that: /* * Sorting many small groups with tuplesort is inefficient. In order to * cope with this problem we don't start a new group until the current one * contains at least DEFAULT_MIN_GROUP_SIZE tuples (unfortunately this also * means we can't assume small groups of tuples all have the same prefix keys.) * When we have a bound that's less than DEFAULT_MIN_GROUP_SIZE we start looking * for the new group as soon as we've met our bound to avoid fetching more * tuples than we absolutely have to fetch. */ #define DEFAULT_MIN_GROUP_SIZE 32 But maybe this logic needs to be further refined somehow. I can't shake the feeling that it's going to be really hard to have every place that uses incremental sort decide whether to use an incremental sort or a regular sort -- we should try to get to a place where it's safe to use an incremental sort when it's possible without worrying that it might actually be slower. Or maybe that's not possible for some reason, but it is not clear to me what that reason might be. -- Robert Haas EDB: http://www.enterprisedb.com
-
Re: Consider explicit incremental sort for Append and MergeAppend
Richard Guo <guofenglinux@gmail.com> — 2025-05-22T09:07:22Z
On Mon, May 19, 2025 at 10:21 PM Robert Haas <robertmhaas@gmail.com> wrote: > On Thu, May 15, 2025 at 9:03 AM Andrei Lepikhov <lepihov@gmail.com> wrote: > > 2. IncrementalSort is not always more effective - it depends on the > > column's number of groups. In my experience, a non-cost-based decision > > one day meets the problematic case, and the people who stick with it are > > much more confused than in the case when planner decision connected to > > the costings - they trust the cost model or the cost model tuned by GUCs. > I wonder if this could be fixed in nodeIncrementalSort.c. I think it's > a problem to rely on planner estimates because planner estimates of > the # of groups are quite unreliable. But at runtime, we can notice > whether the groups are small or large and adjust the algorithm > accordingly. In fact, it looks like we already have some logic for > that: > > /* > * Sorting many small groups with tuplesort is inefficient. In order to > * cope with this problem we don't start a new group until the current one > * contains at least DEFAULT_MIN_GROUP_SIZE tuples (unfortunately this also > * means we can't assume small groups of tuples all have the same prefix keys.) > * When we have a bound that's less than DEFAULT_MIN_GROUP_SIZE we start looking > * for the new group as soon as we've met our bound to avoid fetching more > * tuples than we absolutely have to fetch. > */ > #define DEFAULT_MIN_GROUP_SIZE 32 > > But maybe this logic needs to be further refined somehow. I can't > shake the feeling that it's going to be really hard to have every > place that uses incremental sort decide whether to use an incremental > sort or a regular sort -- we should try to get to a place where it's > safe to use an incremental sort when it's possible without worrying > that it might actually be slower. Agreed. In some cases, we currently don't have the infrastructure to consider both incremental sort and full sort and compare their costs — for example, when inserting explicit sorts for mergejoins, or, as in this case, for Append/MergeAppend. Besides, I think the planner currently assumes that incremental sort is always faster than full sort when there are presorted keys, and this premise has been applied in various parts of the code. I checked all the callers of create_incremental_sort_path, and they all follow the logic that if there are presorted keys, only incremental sort is considered. Also, to understand how incremental sort performs in the worst case, I ran the following test. create table ab (a int not null, b int not null); insert into ab select 0,x from generate_Series(1,999000)x union all select x%1000+1,0 from generate_Series(999001,1000000)x; create index on ab (a); vacuum analyze ab; In this table, group 0 has 999000 rows, and the remaining groups 1-1000 have just 1 row each. -- incremental sort explain (analyze, costs off) select * from ab order by a,b; QUERY PLAN ------------------------------------------------------------------------------------------------ Incremental Sort (actual time=585.093..714.589 rows=1000000.00 loops=1) Sort Key: a, b Presorted Key: a Full-sort Groups: 33 Sort Method: quicksort Average Memory: 26kB Peak Memory: 26kB Pre-sorted Groups: 1 Sort Method: external merge Average Disk: 17680kB Peak Disk: 17680kB Buffers: shared hit=5273, temp read=2210 written=2222 -> Index Scan using ab_a_idx on ab (actual time=0.192..330.289 rows=1000000.00 loops=1) Index Searches: 1 Buffers: shared hit=5273 Planning Time: 0.354 ms Execution Time: 759.693 ms (11 rows) -- full sort explain (analyze, costs off) select * from ab order by a,b; QUERY PLAN -------------------------------------------------------------------------------------------- Sort (actual time=570.755..831.825 rows=1000000.00 loops=1) Sort Key: a, b Sort Method: external merge Disk: 17704kB Buffers: shared hit=5273, temp read=2213 written=2225 -> Index Scan using ab_a_idx on ab (actual time=0.187..327.701 rows=1000000.00 loops=1) Index Searches: 1 Buffers: shared hit=5273 Planning Time: 0.304 ms Execution Time: 877.112 ms (9 rows) So with the same subpath, incremental sort still outperforms full sort even if there is a big skew in the number of rows per pre-sorted group (which is a bit surprising to me). So, I think it's generally safe to use incremental sort whenever possible. There might be some corner cases where incremental sort is slower than full sort, and I think it would be best to address those in nodeIncrementalSort.c, as Robert suggested. Thanks Richard -
Re: Consider explicit incremental sort for Append and MergeAppend
Richard Guo <guofenglinux@gmail.com> — 2025-07-07T07:37:54Z
On Thu, May 22, 2025 at 6:07 PM Richard Guo <guofenglinux@gmail.com> wrote: > So, I think it's generally safe to use incremental sort whenever > possible. There might be some corner cases where incremental sort is > slower than full sort, and I think it would be best to address those > in nodeIncrementalSort.c, as Robert suggested. Here's the latest rebase. I'm planning to push it soon, barring any objections. Thanks Richard
-
Re: Consider explicit incremental sort for Append and MergeAppend
Andrei Lepikhov <lepihov@gmail.com> — 2025-07-07T13:38:58Z
On 19/5/2025 15:21, Robert Haas wrote: > On Thu, May 15, 2025 at 9:03 AM Andrei Lepikhov <lepihov@gmail.com> wrote: >> 2. IncrementalSort is not always more effective - it depends on the >> column's number of groups. In my experience, a non-cost-based decision >> one day meets the problematic case, and the people who stick with it are >> much more confused than in the case when planner decision connected to >> the costings - they trust the cost model or the cost model tuned by GUCs. > > I wonder if this could be fixed in nodeIncrementalSort.c. I think it's > a problem to rely on planner estimates because planner estimates of > the # of groups are quite unreliable. But at runtime, we can notice > whether the groups are small or large and adjust the algorithm > accordingly. In fact, it looks like we already have some logic for > that: Yes, the cost estimation issue is the source of my concern. Postgres estimation of the number of groups is still not ideal [1]. The cost sort model doesn't take into account the number of columns [2] - perhaps something else. Therefore, if IncrementalSort performs worse in some cases, this change may result in an increase in user reports. However, comparing Sort and IncrementalSort (see the attachment), I recall that IncrementalSort surpasses plain Sort in both corner cases: tiny groups (beating Sort in terms of memory consumption and smaller sorting sets) and massive groups (abbreviated keys reduce the number of compared columns). And it is not easy to invent the worst case where the Sort node wins. The only aspect I can't stop thinking about is the cost balance: sometimes the planner prefers Sort+SeqScan to IncrementalSort+IndexScan (See explains in the attachment). A non-cost-based choice of IncrementalSort may result in an Append path with a higher cost, which can displace it from the path list and lead to another, less effective choice, such as a less effective IndexScan. However, I can't support this opinion with any real examples. In toto, IMO it makes sense to commit this feature now and see how it behaves. [1] https://www.postgresql.org/message-id/ba0edc53-4b1f-4c67-92d1-29aeddb36a18@gmail.com [2] https://www.postgresql.org/message-id/8742aaa8-9519-4a1f-91bd-364aec65f5cf%40gmail.com -- regards, Andrei Lepikhov