Skip to content

Commit 1e14248

Browse files
committed
fix
1 parent b80aecf commit 1e14248

7 files changed

Lines changed: 1131 additions & 27 deletions
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "paimon/core/mergetree/binary_external_sort_buffer.h"
18+
19+
#include <cassert>
20+
#include <utility>
21+
22+
#include "arrow/api.h"
23+
#include "arrow/c/bridge.h"
24+
#include "arrow/compute/api.h"
25+
#include "paimon/common/table/special_fields.h"
26+
#include "paimon/common/utils/arrow/status_utils.h"
27+
#include "paimon/common/utils/fields_comparator.h"
28+
#include "paimon/common/utils/scope_guard.h"
29+
#include "paimon/core/disk/io_manager.h"
30+
#include "paimon/core/io/async_key_value_producer_and_consumer.h"
31+
#include "paimon/core/io/key_value_in_memory_record_reader.h"
32+
#include "paimon/core/io/key_value_meta_projection_consumer.h"
33+
#include "paimon/core/io/key_value_record_reader.h"
34+
#include "paimon/core/io/row_to_arrow_array_converter.h"
35+
#include "paimon/core/mergetree/compact/sort_merge_reader_with_min_heap.h"
36+
#include "paimon/core/mergetree/spill_channel_manager.h"
37+
#include "paimon/core/mergetree/spill_reader.h"
38+
#include "paimon/core/mergetree/spill_writer.h"
39+
40+
namespace paimon {
41+
42+
Result<std::unique_ptr<BinaryExternalSortBuffer>> BinaryExternalSortBuffer::Create(
43+
std::unique_ptr<BinaryInMemorySortBuffer>&& in_memory_buffer,
44+
const std::shared_ptr<arrow::Schema>& value_schema,
45+
const std::vector<std::string>& trimmed_primary_keys,
46+
const std::shared_ptr<FieldsComparator>& key_comparator,
47+
const std::shared_ptr<FieldsComparator>& user_defined_seq_comparator,
48+
const CoreOptions& options, const std::shared_ptr<IOManager>& io_manager,
49+
const std::shared_ptr<MemoryPool>& pool) {
50+
arrow::FieldVector key_fields;
51+
key_fields.reserve(trimmed_primary_keys.size());
52+
for (const auto& primary_key : trimmed_primary_keys) {
53+
auto key_field = value_schema->GetFieldByName(primary_key);
54+
assert(key_field != nullptr);
55+
key_fields.push_back(key_field);
56+
}
57+
auto key_schema = arrow::schema(key_fields);
58+
59+
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<FileIOChannel::Enumerator> spill_channel_enumerator,
60+
io_manager->CreateChannelEnumerator());
61+
return std::unique_ptr<BinaryExternalSortBuffer>(new BinaryExternalSortBuffer(
62+
std::move(in_memory_buffer), key_schema, value_schema, key_comparator,
63+
user_defined_seq_comparator, options, spill_channel_enumerator, pool));
64+
}
65+
66+
BinaryExternalSortBuffer::BinaryExternalSortBuffer(
67+
std::unique_ptr<BinaryInMemorySortBuffer>&& in_memory_buffer,
68+
const std::shared_ptr<arrow::Schema>& key_schema,
69+
const std::shared_ptr<arrow::Schema>& value_schema,
70+
const std::shared_ptr<FieldsComparator>& key_comparator,
71+
const std::shared_ptr<FieldsComparator>& user_defined_seq_comparator,
72+
const CoreOptions& options,
73+
const std::shared_ptr<FileIOChannel::Enumerator>& spill_channel_enumerator,
74+
const std::shared_ptr<MemoryPool>& pool)
75+
: in_memory_buffer_(std::move(in_memory_buffer)),
76+
pool_(pool),
77+
key_schema_(key_schema),
78+
value_schema_(value_schema),
79+
key_comparator_(key_comparator),
80+
user_defined_seq_comparator_(user_defined_seq_comparator),
81+
write_schema_(SpecialFields::CompleteSequenceAndValueKindField(value_schema)),
82+
options_(options),
83+
spill_channel_manager_(std::make_shared<SpillChannelManager>(
84+
options_.GetFileSystem(), options_.GetLocalSortMaxNumFileHandles())),
85+
spill_channel_enumerator_(spill_channel_enumerator) {}
86+
87+
bool BinaryExternalSortBuffer::HasSpilledData() const {
88+
return !spill_channel_manager_->GetChannels().empty();
89+
}
90+
91+
std::vector<FileIOChannel::ID> BinaryExternalSortBuffer::GetSpillChannelIdsSnapshot() const {
92+
const auto& channels = spill_channel_manager_->GetChannels();
93+
std::vector<FileIOChannel::ID> spill_channel_ids;
94+
spill_channel_ids.reserve(channels.size());
95+
for (const auto& spill_channel_id : channels) {
96+
spill_channel_ids.push_back(spill_channel_id);
97+
}
98+
return spill_channel_ids;
99+
}
100+
101+
void BinaryExternalSortBuffer::Clear() {
102+
in_memory_buffer_->Clear();
103+
CleanupSpillFiles();
104+
}
105+
106+
uint64_t BinaryExternalSortBuffer::GetMemorySize() const {
107+
return in_memory_buffer_->GetMemorySize();
108+
}
109+
110+
Result<bool> BinaryExternalSortBuffer::FlushMemory() {
111+
if (!in_memory_buffer_->HasData()) {
112+
return true;
113+
}
114+
115+
int64_t max_spill_disk_size = options_.GetWriteBufferSpillMaxDiskSize();
116+
117+
PAIMON_ASSIGN_OR_RAISE(std::vector<std::unique_ptr<KeyValueRecordReader>> memory_buffer_readers,
118+
in_memory_buffer_->CreateReaders());
119+
PAIMON_RETURN_NOT_OK(SpillMemoryBuffer(std::move(memory_buffer_readers)));
120+
in_memory_buffer_->Clear();
121+
return total_spill_disk_bytes_ < max_spill_disk_size;
122+
}
123+
124+
Result<bool> BinaryExternalSortBuffer::Write(std::unique_ptr<RecordBatch>&& batch) {
125+
PAIMON_ASSIGN_OR_RAISE(bool has_remaining_memory, in_memory_buffer_->Write(std::move(batch)));
126+
if (has_remaining_memory) {
127+
return true;
128+
}
129+
return FlushMemory();
130+
}
131+
132+
Result<std::vector<std::unique_ptr<KeyValueRecordReader>>>
133+
BinaryExternalSortBuffer::CreateReaders() {
134+
PAIMON_ASSIGN_OR_RAISE(std::vector<std::unique_ptr<KeyValueRecordReader>> readers,
135+
CollectSpillReaders());
136+
PAIMON_ASSIGN_OR_RAISE(std::vector<std::unique_ptr<KeyValueRecordReader>> memory_readers,
137+
in_memory_buffer_->CreateReaders());
138+
139+
readers.insert(readers.end(), std::make_move_iterator(memory_readers.begin()),
140+
std::make_move_iterator(memory_readers.end()));
141+
return readers;
142+
}
143+
144+
bool BinaryExternalSortBuffer::HasData() const {
145+
return in_memory_buffer_->HasData() || HasSpilledData();
146+
}
147+
148+
void BinaryExternalSortBuffer::CleanupSpillFiles() {
149+
spill_channel_manager_->Reset();
150+
total_spill_disk_bytes_ = 0;
151+
}
152+
153+
Result<std::vector<std::unique_ptr<KeyValueRecordReader>>>
154+
BinaryExternalSortBuffer::CollectSpillReaders() const {
155+
std::vector<std::unique_ptr<KeyValueRecordReader>> readers;
156+
const auto& channel_ids = spill_channel_manager_->GetChannels();
157+
readers.reserve(channel_ids.size());
158+
for (const auto& channel_id : channel_ids) {
159+
PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<SpillReader> spill_reader,
160+
SpillReader::Create(options_.GetFileSystem(), key_schema_,
161+
value_schema_, pool_, channel_id));
162+
readers.push_back(std::move(spill_reader));
163+
}
164+
return readers;
165+
}
166+
167+
Result<int64_t> BinaryExternalSortBuffer::SpillToDisk(
168+
std::vector<std::unique_ptr<KeyValueRecordReader>>&& readers, int32_t write_batch_size) {
169+
const auto& spill_compress_options = options_.GetSpillCompressOptions();
170+
PAIMON_ASSIGN_OR_RAISE(
171+
auto spill_writer,
172+
SpillWriter::Create(options_.GetFileSystem(), write_schema_, spill_channel_enumerator_,
173+
spill_channel_manager_, spill_compress_options.compress,
174+
spill_compress_options.zstd_level));
175+
auto cleanup_guard = ScopeGuard([&]() {
176+
[[maybe_unused]] auto status =
177+
spill_channel_manager_->DeleteChannel(spill_writer->GetChannelId());
178+
});
179+
180+
auto sorted_reader = std::make_unique<SortMergeReaderWithMinHeap>(
181+
std::move(readers), key_comparator_, user_defined_seq_comparator_,
182+
/*merge_function_wrapper=*/nullptr);
183+
auto create_consumer = [target_schema = write_schema_, pool = pool_]()
184+
-> Result<std::unique_ptr<RowToArrowArrayConverter<KeyValue, KeyValueBatch>>> {
185+
return KeyValueMetaProjectionConsumer::Create(target_schema, pool);
186+
};
187+
auto async_key_value_producer_consumer =
188+
std::make_unique<AsyncKeyValueProducerAndConsumer<KeyValue, KeyValueBatch>>(
189+
std::move(sorted_reader), create_consumer, write_batch_size,
190+
/*projection_thread_num=*/1, pool_);
191+
auto close_guard = ScopeGuard([&]() { async_key_value_producer_consumer->Close(); });
192+
193+
while (true) {
194+
PAIMON_ASSIGN_OR_RAISE(KeyValueBatch key_value_batch,
195+
async_key_value_producer_consumer->NextBatch());
196+
if (key_value_batch.batch == nullptr) {
197+
break;
198+
}
199+
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
200+
std::shared_ptr<arrow::RecordBatch> record_batch,
201+
arrow::ImportRecordBatch(key_value_batch.batch.get(), write_schema_));
202+
PAIMON_RETURN_NOT_OK(spill_writer->WriteBatch(record_batch));
203+
}
204+
205+
PAIMON_RETURN_NOT_OK(spill_writer->Close());
206+
PAIMON_ASSIGN_OR_RAISE(int64_t merged_file_size, spill_writer->GetFileSize());
207+
cleanup_guard.Release();
208+
return merged_file_size;
209+
}
210+
211+
Status BinaryExternalSortBuffer::SpillMemoryBuffer(
212+
std::vector<std::unique_ptr<KeyValueRecordReader>>&& readers) {
213+
PAIMON_ASSIGN_OR_RAISE(int64_t spill_file_size,
214+
SpillToDisk(std::move(readers), options_.GetWriteBatchSize()));
215+
total_spill_disk_bytes_ += spill_file_size;
216+
217+
if (options_.GetLocalSortMaxNumFileHandles() > 0 &&
218+
static_cast<int32_t>(spill_channel_manager_->GetChannels().size()) >=
219+
options_.GetLocalSortMaxNumFileHandles()) {
220+
PAIMON_RETURN_NOT_OK(MergeSpilledFiles());
221+
}
222+
return Status::OK();
223+
}
224+
225+
Status BinaryExternalSortBuffer::MergeSpilledFiles() {
226+
if (spill_channel_manager_->GetChannels().size() < 2) {
227+
return Status::OK();
228+
}
229+
230+
auto spill_channel_ids_before_merge = GetSpillChannelIdsSnapshot();
231+
auto cleanup_guard = ScopeGuard([&]() {
232+
for (const auto& spill_channel_id : spill_channel_ids_before_merge) {
233+
[[maybe_unused]] auto status = spill_channel_manager_->DeleteChannel(spill_channel_id);
234+
}
235+
});
236+
237+
PAIMON_ASSIGN_OR_RAISE(std::vector<std::unique_ptr<KeyValueRecordReader>> readers,
238+
CollectSpillReaders());
239+
PAIMON_ASSIGN_OR_RAISE(int64_t merged_file_size,
240+
SpillToDisk(std::move(readers), options_.GetWriteBatchSize()));
241+
total_spill_disk_bytes_ = merged_file_size;
242+
243+
return Status::OK();
244+
}
245+
246+
} // namespace paimon
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <cstdint>
20+
#include <memory>
21+
#include <string>
22+
#include <vector>
23+
24+
#include "arrow/type_fwd.h"
25+
#include "paimon/core/core_options.h"
26+
#include "paimon/core/disk/file_io_channel.h"
27+
#include "paimon/core/mergetree/binary_in_memory_sort_buffer.h"
28+
#include "paimon/core/mergetree/sort_buffer.h"
29+
#include "paimon/record_batch.h"
30+
#include "paimon/result.h"
31+
#include "paimon/status.h"
32+
33+
namespace arrow {
34+
class Schema;
35+
} // namespace arrow
36+
37+
namespace paimon {
38+
class FieldsComparator;
39+
class IOManager;
40+
class KeyValueRecordReader;
41+
class MemoryPool;
42+
class SpillChannelManager;
43+
44+
/// Spillable SortBuffer. Buffers RecordBatches in an underlying in-memory sort buffer;
45+
/// when the in-memory budget is reached, sorted data is spilled to a new on-disk file.
46+
class BinaryExternalSortBuffer : public SortBuffer {
47+
public:
48+
static Result<std::unique_ptr<BinaryExternalSortBuffer>> Create(
49+
std::unique_ptr<BinaryInMemorySortBuffer>&& in_memory_buffer,
50+
const std::shared_ptr<arrow::Schema>& value_schema,
51+
const std::vector<std::string>& trimmed_primary_keys,
52+
const std::shared_ptr<FieldsComparator>& key_comparator,
53+
const std::shared_ptr<FieldsComparator>& user_defined_seq_comparator,
54+
const CoreOptions& options, const std::shared_ptr<IOManager>& io_manager,
55+
const std::shared_ptr<MemoryPool>& pool);
56+
57+
void Clear() override;
58+
uint64_t GetMemorySize() const override;
59+
Result<bool> FlushMemory() override;
60+
Result<bool> Write(std::unique_ptr<RecordBatch>&& batch) override;
61+
Result<std::vector<std::unique_ptr<KeyValueRecordReader>>> CreateReaders() override;
62+
bool HasData() const override;
63+
64+
private:
65+
bool HasSpilledData() const;
66+
std::vector<FileIOChannel::ID> GetSpillChannelIdsSnapshot() const;
67+
Result<std::vector<std::unique_ptr<KeyValueRecordReader>>> CollectSpillReaders() const;
68+
Result<int64_t> SpillToDisk(std::vector<std::unique_ptr<KeyValueRecordReader>>&& readers,
69+
int32_t write_batch_size);
70+
Status MergeSpilledFiles();
71+
Status SpillMemoryBuffer(std::vector<std::unique_ptr<KeyValueRecordReader>>&& readers);
72+
void CleanupSpillFiles();
73+
74+
BinaryExternalSortBuffer(
75+
std::unique_ptr<BinaryInMemorySortBuffer>&& in_memory_buffer,
76+
const std::shared_ptr<arrow::Schema>& key_schema,
77+
const std::shared_ptr<arrow::Schema>& value_schema,
78+
const std::shared_ptr<FieldsComparator>& key_comparator,
79+
const std::shared_ptr<FieldsComparator>& user_defined_seq_comparator,
80+
const CoreOptions& options,
81+
const std::shared_ptr<FileIOChannel::Enumerator>& spill_channel_enumerator,
82+
const std::shared_ptr<MemoryPool>& pool);
83+
84+
std::unique_ptr<BinaryInMemorySortBuffer> in_memory_buffer_;
85+
86+
const std::shared_ptr<MemoryPool> pool_;
87+
const std::shared_ptr<arrow::Schema> key_schema_;
88+
const std::shared_ptr<arrow::Schema> value_schema_;
89+
const std::shared_ptr<FieldsComparator> key_comparator_;
90+
const std::shared_ptr<FieldsComparator> user_defined_seq_comparator_;
91+
const std::shared_ptr<arrow::Schema> write_schema_;
92+
const CoreOptions options_;
93+
const std::shared_ptr<SpillChannelManager> spill_channel_manager_;
94+
95+
std::shared_ptr<FileIOChannel::Enumerator> spill_channel_enumerator_;
96+
int64_t total_spill_disk_bytes_ = 0;
97+
};
98+
99+
} // namespace paimon

0 commit comments

Comments
 (0)