1616
1717#include " paimon/core/mergetree/external_sort_buffer.h"
1818
19+ #include < algorithm>
1920#include < cassert>
2021#include < utility>
2122
@@ -80,9 +81,13 @@ ExternalSortBuffer::ExternalSortBuffer(
8081 user_defined_seq_comparator_(user_defined_seq_comparator),
8182 write_schema_(SpecialFields::CompleteSequenceAndValueKindField(value_schema)),
8283 options_(options),
83- spill_channel_manager_(std::make_shared<SpillChannelManager>(
84- options_.GetFileSystem(), options_.GetLocalSortMaxNumFileHandles())),
85- spill_channel_enumerator_(spill_channel_enumerator) {}
84+ max_fan_in_(options.GetLocalSortMaxNumFileHandles()),
85+ spill_channel_manager_(
86+ std::make_shared<SpillChannelManager>(options_.GetFileSystem(), max_fan_in_)),
87+ leveled_merger_(std::make_unique<LeveledMerger>(max_fan_in_)),
88+ spill_channel_enumerator_(spill_channel_enumerator),
89+ actual_max_fan_in_(max_fan_in_),
90+ spill_batch_size_(options_.GetWriteBatchSize()) {}
8691
8792ExternalSortBuffer::~ExternalSortBuffer () {
8893 DoClear ();
@@ -94,7 +99,10 @@ bool ExternalSortBuffer::HasSpilledData() const {
9499
95100void ExternalSortBuffer::DoClear () {
96101 in_memory_buffer_->Clear ();
97- CleanupSpillFiles ();
102+
103+ spill_channel_manager_->Reset ();
104+ total_spill_disk_bytes_ = 0 ;
105+ leveled_merger_->Clear ();
98106}
99107
100108void ExternalSortBuffer::Clear () {
@@ -105,18 +113,42 @@ uint64_t ExternalSortBuffer::GetMemorySize() const {
105113 return in_memory_buffer_->GetMemorySize ();
106114}
107115
116+ void ExternalSortBuffer::EstimateSpillParameters () {
117+ int64_t estimated_row_size = in_memory_buffer_->GetEstimateMemoryUseForEachRow ();
118+ if (estimated_row_size <= 0 ) {
119+ return ;
120+ }
121+
122+ const int32_t max_batch_size = options_.GetWriteBatchSize ();
123+ const int32_t min_batch_size = std::min (kSpillMinBatchSize , max_batch_size);
124+ const int64_t merge_budget = options_.GetWriteBufferSize ();
125+ const int64_t max_memory_use_per_handle = merge_budget / max_fan_in_;
126+
127+ spill_batch_size_ = max_memory_use_per_handle / estimated_row_size;
128+ spill_batch_size_ = std::clamp (spill_batch_size_, min_batch_size, max_batch_size);
129+
130+ actual_max_fan_in_ = merge_budget / (spill_batch_size_ * estimated_row_size);
131+ actual_max_fan_in_ =
132+ std::clamp (actual_max_fan_in_, CoreOptions::kLocalSortFileHandlesMinimalLimit , max_fan_in_);
133+
134+ // Re-derive spill_batch_size_ from the clamped actual_max_fan_in_ to stay within merge_budget.
135+ spill_batch_size_ = merge_budget / (actual_max_fan_in_ * estimated_row_size);
136+ spill_batch_size_ = std::clamp (spill_batch_size_, 1 , max_batch_size);
137+
138+ leveled_merger_->SetMaxFanIn (actual_max_fan_in_);
139+ }
140+
108141Result<bool > ExternalSortBuffer::FlushMemory () {
109142 if (!in_memory_buffer_->HasData ()) {
110143 return true ;
111144 }
112145
113- int64_t max_spill_disk_size = options_.GetWriteBufferSpillMaxDiskSize ();
114-
146+ EstimateSpillParameters ();
115147 PAIMON_ASSIGN_OR_RAISE (std::vector<std::unique_ptr<KeyValueRecordReader>> memory_buffer_readers,
116148 in_memory_buffer_->CreateReaders ());
117149 PAIMON_RETURN_NOT_OK (SpillMemoryBuffer (std::move (memory_buffer_readers)));
118150 in_memory_buffer_->Clear ();
119- return total_spill_disk_bytes_ < max_spill_disk_size ;
151+ return total_spill_disk_bytes_ < options_. GetWriteBufferSpillMaxDiskSize () ;
120152}
121153
122154Result<bool > ExternalSortBuffer::Write (std::unique_ptr<RecordBatch>&& batch) {
@@ -128,11 +160,17 @@ Result<bool> ExternalSortBuffer::Write(std::unique_ptr<RecordBatch>&& batch) {
128160}
129161
130162Result<std::vector<std::unique_ptr<KeyValueRecordReader>>> ExternalSortBuffer::CreateReaders () {
131- PAIMON_ASSIGN_OR_RAISE (std::vector<std::unique_ptr<KeyValueRecordReader>> readers,
132- CollectSpillReaders ());
133163 PAIMON_ASSIGN_OR_RAISE (std::vector<std::unique_ptr<KeyValueRecordReader>> memory_readers,
134164 in_memory_buffer_->CreateReaders ());
165+ if (!HasSpilledData ()) {
166+ return memory_readers;
167+ }
135168
169+ int32_t max_spill_files = actual_max_fan_in_ - 1 ;
170+ PAIMON_RETURN_NOT_OK (
171+ leveled_merger_->RunFinalCleanupIfNeeded (max_spill_files, CreateMergeFn ()));
172+ PAIMON_ASSIGN_OR_RAISE (std::vector<std::unique_ptr<KeyValueRecordReader>> readers,
173+ CreateSpillReaders (leveled_merger_->GetAllFiles ()));
136174 readers.insert (readers.end (), std::make_move_iterator (memory_readers.begin ()),
137175 std::make_move_iterator (memory_readers.end ()));
138176 return readers;
@@ -142,26 +180,20 @@ bool ExternalSortBuffer::HasData() const {
142180 return in_memory_buffer_->HasData () || HasSpilledData ();
143181}
144182
145- void ExternalSortBuffer::CleanupSpillFiles () {
146- spill_channel_manager_->Reset ();
147- total_spill_disk_bytes_ = 0 ;
148- }
149-
150- Result<std::vector<std::unique_ptr<KeyValueRecordReader>>> ExternalSortBuffer::CollectSpillReaders ()
151- const {
183+ Result<std::vector<std::unique_ptr<KeyValueRecordReader>>> ExternalSortBuffer::CreateSpillReaders (
184+ const std::vector<FileChannelInfo>& files) const {
152185 std::vector<std::unique_ptr<KeyValueRecordReader>> readers;
153- const auto & channel_ids = spill_channel_manager_->GetChannels ();
154- readers.reserve (channel_ids.size ());
155- for (const auto & channel_id : channel_ids) {
156- PAIMON_ASSIGN_OR_RAISE (std::unique_ptr<SpillReader> spill_reader,
186+ readers.reserve (files.size ());
187+ for (const auto & file : files) {
188+ PAIMON_ASSIGN_OR_RAISE (std::unique_ptr<SpillReader> reader,
157189 SpillReader::Create (options_.GetFileSystem (), key_schema_,
158- value_schema_, pool_, channel_id));
159- readers.push_back (std::move (spill_reader ));
190+ value_schema_, pool_, file. channel_id ));
191+ readers.push_back (std::move (reader ));
160192 }
161193 return readers;
162194}
163195
164- Result<int64_t > ExternalSortBuffer::SpillToDisk (
196+ Result<FileChannelInfo > ExternalSortBuffer::SpillToDisk (
165197 std::vector<std::unique_ptr<KeyValueRecordReader>>&& readers, int32_t write_batch_size) {
166198 const auto & spill_compress_options = options_.GetSpillCompressOptions ();
167199 PAIMON_ASSIGN_OR_RAISE (
@@ -202,41 +234,37 @@ Result<int64_t> ExternalSortBuffer::SpillToDisk(
202234 PAIMON_RETURN_NOT_OK (spill_writer->Close ());
203235 PAIMON_ASSIGN_OR_RAISE (int64_t spilled_file_size, spill_writer->GetFileSize ());
204236 cleanup_guard.Release ();
205- return spilled_file_size;
237+ return FileChannelInfo{spill_writer-> GetChannelId (), spilled_file_size} ;
206238}
207239
208240Status ExternalSortBuffer::SpillMemoryBuffer (
209241 std::vector<std::unique_ptr<KeyValueRecordReader>>&& readers) {
210- PAIMON_ASSIGN_OR_RAISE (int64_t spill_file_size,
211- SpillToDisk (std::move (readers), options_.GetWriteBatchSize ()));
212- total_spill_disk_bytes_ += spill_file_size;
213-
214- if (options_.GetLocalSortMaxNumFileHandles () > 0 &&
215- static_cast <int32_t >(spill_channel_manager_->GetChannels ().size ()) >=
216- options_.GetLocalSortMaxNumFileHandles ()) {
217- PAIMON_RETURN_NOT_OK (MergeSpilledFiles ());
218- }
219- return Status::OK ();
242+ PAIMON_ASSIGN_OR_RAISE (FileChannelInfo file_info,
243+ SpillToDisk (std::move (readers), spill_batch_size_));
244+ total_spill_disk_bytes_ += file_info.file_size ;
245+ leveled_merger_->AddFile (file_info);
246+ return leveled_merger_->RunCompactionIfNeeded (CreateMergeFn ());
220247}
221248
222- Status ExternalSortBuffer::MergeSpilledFiles () {
223- if (spill_channel_manager_->GetChannels ().size () < 2 ) {
224- return Status::OK ();
225- }
226- auto spill_channel_ids_before_merge = spill_channel_manager_->GetChannels ();
227- auto cleanup_guard = ScopeGuard ([&]() {
228- for (const auto & spill_channel_id : spill_channel_ids_before_merge) {
229- [[maybe_unused]] auto status = spill_channel_manager_->DeleteChannel (spill_channel_id);
230- }
231- });
249+ LeveledMerger::MergeFn ExternalSortBuffer::CreateMergeFn () {
250+ return [this ](const std::vector<FileChannelInfo>& files) -> Result<FileChannelInfo> {
251+ return MergeAndReplaceFiles (files);
252+ };
253+ }
232254
255+ Result<FileChannelInfo> ExternalSortBuffer::MergeAndReplaceFiles (
256+ const std::vector<FileChannelInfo>& files) {
233257 PAIMON_ASSIGN_OR_RAISE (std::vector<std::unique_ptr<KeyValueRecordReader>> readers,
234- CollectSpillReaders ( ));
235- PAIMON_ASSIGN_OR_RAISE (int64_t merged_file_size ,
236- SpillToDisk (std::move (readers), options_. GetWriteBatchSize () ));
237- total_spill_disk_bytes_ = merged_file_size ;
258+ CreateSpillReaders (files ));
259+ PAIMON_ASSIGN_OR_RAISE (FileChannelInfo output ,
260+ SpillToDisk (std::move (readers), spill_batch_size_ ));
261+ total_spill_disk_bytes_ += output. file_size ;
238262
239- return Status::OK ();
263+ for (const auto & file : files) {
264+ [[maybe_unused]] auto status = spill_channel_manager_->DeleteChannel (file.channel_id );
265+ total_spill_disk_bytes_ -= file.file_size ;
266+ }
267+ return output;
240268}
241269
242270} // namespace paimon
0 commit comments