Skip to content

implement segmentwise batch sorted merge - #10147

Open
natalya-aksman wants to merge 2 commits into
mainfrom
implement_segmentwise_batch_sorted_merge
Open

implement segmentwise batch sorted merge#10147
natalya-aksman wants to merge 2 commits into
mainfrom
implement_segmentwise_batch_sorted_merge

Conversation

@natalya-aksman

@natalya-aksman natalya-aksman commented Jun 26, 2026

Copy link
Copy Markdown
Member

Implement segmentwise batch sorted merge for queries matching compressed index order over unordered chunks.
For example this query should support Batch sorted merge over compressed unordered chunks for t_segbydev_orderbytime:

 SELECT * FROM t_segbydev_orderbytime ORDER BY dev, time;

Compressed data is always ordered by segmentby values, the batches for Batch sorted merge are consumed segment-by-segment and we want to do heap sort of tuples by orderby keys for each segment.

BSM cost method has been updated for segmentwise BSM.

This PR is a part of ongoing project to improve Batch sorted merge coverage: https://github.com/timescale/eng-database/issues/811.

We may consider further optimizations in the executor for Batch sorted merge in a separate PR, these optimizations have been excluded from this PR:
We can avoid comparing segmentby keys in binary_heap comparator for each tuple as they are the same within each segment.
We will stop requesting new batches after new segment is added to the heap, we will process all batches from the current segment already on the heap and when only the batch from the new segment remains on the heap, we will make new segment the current segment.

@github-actions

Copy link
Copy Markdown

@Poroma-Banerjee, @kpan2034: please review this pull request.

Powered by pull-review

@natalya-aksman
natalya-aksman marked this pull request as draft June 26, 2026 14:07
@natalya-aksman
natalya-aksman force-pushed the implement_segmentwise_batch_sorted_merge branch 4 times, most recently from f2e106b to 8161004 Compare June 29, 2026 17:07
@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@natalya-aksman
natalya-aksman force-pushed the implement_segmentwise_batch_sorted_merge branch from 8161004 to 4e1c221 Compare June 30, 2026 17:42
@natalya-aksman
natalya-aksman force-pushed the implement_segmentwise_batch_sorted_merge branch 6 times, most recently from c59e292 to 4afbad4 Compare July 30, 2026 20:54
@natalya-aksman
natalya-aksman marked this pull request as ready for review July 30, 2026 21:43
@natalya-aksman
natalya-aksman requested review from akuzm, antekresic and svenklemm and removed request for kpan2034 July 30, 2026 21:44
} BatchQueueHeap;

static void
reset_segment(BatchQueueHeap *queue, TupleTableSlot *slot)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need any special handling for segmentby keys? It should just work transparently if you put them as a prefix of the heap keys, no?

@natalya-aksman natalya-aksman Jul 31, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. We don't want segment keys on the heap as they do not need to be merged, they are already sorted in correct order. It will slow heapsort down unnecessarily, also, it will eat up memory and we are trying to keep heap memory down to keep BSM viable.

  2. Segment keys can be passed by reference, i.e. they can be of text type etc. Actually, orderby keys can also be of text type, can they? If yes, then we have a bug in BSM as we always copy heap keys by value. I'll check that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will slow heapsort down unnecessarily, also, it will eat up memory and we are trying to keep heap memory down to keep BSM viable.

Why would it use more memory? We already keep the full uncompressed current tuple in every batch, segmentby is already there. The uptake of source batches also shouldn't be influenced by the way we do comparison. It's the prefix key, so if the next batch compares greater on segmentby, we just stop there.

About the performance characteristics, let's measure if it's actually important. This batch heap queue has a lot of code already, and it's pretty complicated and bug-prone. It's poorly tested too, despite all our effort. We found another serious bug in batch sorted merge just last month. So I'd be happy if we can just make a planner change that works well enough. Maybe it just splits in two PRs trivially, if my idea is right.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reminds me of a kind of adjacent change that I wanted to do a long time ago: decide when to stop intake of the source batches based on comparing the compressed tuple, even before any decompression takes place. IIRC now we have to decompress and materialize the first tuple always, so this wastes some time. Also because of this the batch sorted merge has a special weird interface in the "compressed batch" module, compressed_batch_save_first_tuple.

@natalya-aksman natalya-aksman Jul 31, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The uptake of source batches also shouldn't be influenced by the way we do comparison. It's the prefix key, so if the next batch compares greater on segmentby, we just stop there.

We will be doing unnecessary extra keys comparison for each binary_heap comparison we do as we compare heap entries for the same segment. I.e. instead of comparing on, say, (orderby1, orderby2) columns for each heap comparison we will have to compare on (segby1, segby2, orderby1, orderby2) where (segby1, segby2) are the same for all tuples in the current processed segment.

I'm also looking at this comment which reasons the need for HeapEntry for better performance:

* This is the actual entries of the heap we're going to compare. We're using

If we want to follow this reasoning it won't make sense to add unneeded to fields to HeapEntry.

I can put this (supposedly) optimized method under a GUC and implement HeapEntry in a regular way, just include segmentby keys into it and let binary_heap compare it in a normal way.

We can then compare performance and see if simplified logic doesn't lead to noticeable performance hit.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point is that the current execution logic transparently handles the case when you have to check the segmentby. That's why we can start with a simple planner change, that works "well enough" by itself and then work on optimizing the execution side. Do I get this right? If this doesn't work transparently, and the execution side must have changes as well, then there's probably not much value in making some simplified versions of it.

@akuzm akuzm Jul 31, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Letting the heap consume all batches and then merge them would make BSM unusable.

Right, that's obviously not practical. I was thinking about using the "last batch compressed tuple" in batch_queue_heap_needs_next_batch instead of the last_batch_first_tuple_entry. This means the last batch will not be decompressed until it's needed. This also means that the "comparison for heap property" becomes a separate thing from "comparison to stop intake of new batches". Your change also splits these concepts, that's why I remembered about it.

@natalya-aksman natalya-aksman Jul 31, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point is that the current execution logic transparently handles the case when you have to check the segmentby. That's why we can start with a simple planner change, that works "well enough" by itself and then work on optimizing the execution side. Do I get this right?

That's a valid point. Yes, I can split planner changes only into a separate PR, and execution optimizations can come in the next PR and be under a GUC for good measure. Could be a separate exec method for segmentwise BSM, which will only compare non-segmentby fields on a heap.

LLM Fuzzer found a bug with copying HeapEntry for segmentby keys by value only, that's why I split SegmentEntryColumn from HeapEntryColumn, and that execution change will have to stay I think.
I cannot find that LLM Fuzzer report now, but hopefully it will flag the issue again if I put segment key values into HeapEntryColumn again in a split PR.
And hopefully I can reproduce it for orderby columns and then fix it as a long-standing BSM bug.

I.e., action plan: new PR with only planning changes plus tests, and then we go from there.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it's simpler to use this PR for planner-only changes as it has all the new tests.
The new PR with executor optimizations can be added later.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, that LLM Fuzzer bug was about a different thing. It's OK to copy by value into HeapEntries as they are just pointers into decompressed tuples fields used for comparison.

@natalya-aksman

natalya-aksman commented Jul 31, 2026

Copy link
Copy Markdown
Member Author

I need to update cost estimate for segmentwise BSM, I forgot about it. -- DONE.

!is_segment && destination_attno > 0 &&
tsl_get_decompress_all_function(compression_get_default_algorithm(typoid), typoid) !=
NULL;
bool is_segment = false;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just to avoid unnecessary checks during planning for compressed metadata columns i.e. for columns with destination_attno <= 0.

@natalya-aksman
natalya-aksman force-pushed the implement_segmentwise_batch_sorted_merge branch from 4afbad4 to f270363 Compare July 31, 2026 18:31
@natalya-aksman
natalya-aksman force-pushed the implement_segmentwise_batch_sorted_merge branch from f270363 to a74dff7 Compare July 31, 2026 19:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants