Skip to content

Commit 72519d0

Browse files
committed
feat(spill): add SpillWriter, SpillReader and SpillChannelManager for spill-to-disk support
1 parent 15ad16c commit 72519d0

14 files changed

Lines changed: 1140 additions & 1 deletion
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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 <random>
22+
#include <string>
23+
24+
#include "paimon/visibility.h"
25+
26+
namespace paimon {
27+
28+
/// A FileIOChannel represents a collection of files that belong logically to the same resource.
29+
/// An example is a collection of files that contain sorted runs of data from the same stream,
30+
/// that will later on be merged together.
31+
class PAIMON_EXPORT FileIOChannel {
32+
public:
33+
/// An ID identifying an underlying file channel.
34+
class PAIMON_EXPORT ID {
35+
public:
36+
ID() = default;
37+
38+
explicit ID(const std::string& path);
39+
40+
ID(const std::string& base_path, std::mt19937* random);
41+
42+
ID(const std::string& base_path, const std::string& prefix, std::mt19937* random);
43+
44+
const std::string& GetPath() const;
45+
46+
bool operator==(const ID& other) const;
47+
48+
bool operator!=(const ID& other) const;
49+
50+
struct Hash {
51+
size_t operator()(const ID& id) const;
52+
};
53+
54+
private:
55+
std::string path_;
56+
};
57+
58+
/// An enumerator for channels that logically belong together.
59+
class PAIMON_EXPORT Enumerator {
60+
public:
61+
Enumerator(const std::string& base_path, std::mt19937* random);
62+
63+
ID Next();
64+
65+
private:
66+
std::string path_;
67+
std::string name_prefix_;
68+
uint64_t local_counter_{0};
69+
};
70+
};
71+
72+
} // namespace paimon

include/paimon/disk/io_manager.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <memory>
2121
#include <string>
2222

23+
#include "paimon/disk/file_io_channel.h"
2324
#include "paimon/result.h"
2425
#include "paimon/visibility.h"
2526

@@ -34,5 +35,11 @@ class PAIMON_EXPORT IOManager {
3435
virtual const std::string& GetTempDir() const = 0;
3536

3637
virtual Result<std::string> GenerateTempFilePath(const std::string& prefix) const = 0;
38+
39+
virtual Result<FileIOChannel::ID> CreateChannel() = 0;
40+
41+
virtual Result<FileIOChannel::ID> CreateChannel(const std::string& prefix) = 0;
42+
43+
virtual Result<std::shared_ptr<FileIOChannel::Enumerator>> CreateChannelEnumerator() = 0;
3744
};
3845
} // namespace paimon

src/paimon/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,10 @@ set(PAIMON_COMMON_SRCS
146146
common/utils/string_utils.cpp)
147147

148148
set(PAIMON_CORE_SRCS
149+
core/disk/file_io_channel.cpp
149150
core/disk/io_manager.cpp
151+
core/mergetree/spill_reader.cpp
152+
core/mergetree/spill_writer.cpp
150153
core/append/append_only_writer.cpp
151154
core/append/bucketed_append_compact_manager.cpp
152155
core/casting/binary_to_string_cast_executor.cpp
@@ -598,6 +601,9 @@ if(PAIMON_BUILD_TESTS)
598601
core/mergetree/merge_tree_writer_test.cpp
599602
core/mergetree/write_buffer_test.cpp
600603
core/mergetree/sorted_run_test.cpp
604+
core/mergetree/spill_channel_manager_test.cpp
605+
core/mergetree/spill_reader_test.cpp
606+
core/mergetree/spill_writer_test.cpp
601607
core/migrate/file_meta_utils_test.cpp
602608
core/operation/metrics/compaction_metrics_test.cpp
603609
core/operation/data_evolution_file_store_scan_test.cpp
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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/disk/file_io_channel.h"
18+
19+
#include <iomanip>
20+
#include <sstream>
21+
#include <utility>
22+
23+
#include "paimon/common/utils/path_util.h"
24+
25+
namespace paimon {
26+
27+
namespace {
28+
29+
constexpr int32_t RANDOM_BYTES_LENGTH = 16;
30+
31+
std::string GenerateRandomHexString(std::mt19937* random) {
32+
std::uniform_int_distribution<int32_t> dist(0, 255);
33+
std::ostringstream hex_stream;
34+
hex_stream << std::hex << std::setfill('0');
35+
for (int32_t i = 0; i < RANDOM_BYTES_LENGTH; ++i) {
36+
hex_stream << std::setw(2) << dist(*random);
37+
}
38+
return hex_stream.str();
39+
}
40+
41+
} // namespace
42+
43+
FileIOChannel::ID::ID(const std::string& path) : path_(path) {}
44+
45+
FileIOChannel::ID::ID(const std::string& base_path, std::mt19937* random)
46+
: path_(PathUtil::JoinPath(base_path, GenerateRandomHexString(random) + ".channel")) {}
47+
48+
FileIOChannel::ID::ID(const std::string& base_path, const std::string& prefix, std::mt19937* random)
49+
: path_(PathUtil::JoinPath(base_path,
50+
prefix + "-" + GenerateRandomHexString(random) + ".channel")) {}
51+
52+
const std::string& FileIOChannel::ID::GetPath() const {
53+
return path_;
54+
}
55+
56+
bool FileIOChannel::ID::operator==(const ID& other) const {
57+
return path_ == other.path_;
58+
}
59+
60+
bool FileIOChannel::ID::operator!=(const ID& other) const {
61+
return !(*this == other);
62+
}
63+
64+
size_t FileIOChannel::ID::Hash::operator()(const ID& id) const {
65+
return std::hash<std::string>{}(id.path_);
66+
}
67+
68+
FileIOChannel::Enumerator::Enumerator(const std::string& base_path, std::mt19937* random)
69+
: path_(base_path), name_prefix_(GenerateRandomHexString(random)) {}
70+
71+
FileIOChannel::ID FileIOChannel::Enumerator::Next() {
72+
std::ostringstream filename;
73+
filename << name_prefix_ << "." << std::setfill('0') << std::setw(6) << (local_counter_++)
74+
<< ".channel";
75+
76+
std::string full_path = PathUtil::JoinPath(path_, filename.str());
77+
return ID(full_path);
78+
}
79+
80+
} // namespace paimon

src/paimon/core/disk/io_manager.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@
1515
*/
1616
#include "paimon/disk/io_manager.h"
1717

18+
#include <mutex>
19+
1820
#include "paimon/common/utils/path_util.h"
1921
#include "paimon/common/utils/uuid.h"
2022

2123
namespace paimon {
2224
class IOManagerImpl : public IOManager {
2325
public:
24-
explicit IOManagerImpl(const std::string& tmp_dir) : tmp_dir_(tmp_dir) {}
26+
explicit IOManagerImpl(const std::string& tmp_dir) : tmp_dir_(tmp_dir) {
27+
std::random_device rd;
28+
random_.seed(rd());
29+
}
2530

2631
const std::string& GetTempDir() const override {
2732
return tmp_dir_;
@@ -35,9 +40,26 @@ class IOManagerImpl : public IOManager {
3540
return PathUtil::JoinPath(tmp_dir_, prefix + "-" + uuid + std::string(kSuffix));
3641
}
3742

43+
Result<FileIOChannel::ID> CreateChannel() override {
44+
std::lock_guard<std::mutex> lock(mutex_);
45+
return FileIOChannel::ID(tmp_dir_, &random_);
46+
}
47+
48+
Result<FileIOChannel::ID> CreateChannel(const std::string& prefix) override {
49+
std::lock_guard<std::mutex> lock(mutex_);
50+
return FileIOChannel::ID(tmp_dir_, prefix, &random_);
51+
}
52+
53+
Result<std::shared_ptr<FileIOChannel::Enumerator>> CreateChannelEnumerator() override {
54+
std::lock_guard<std::mutex> lock(mutex_);
55+
return std::make_shared<FileIOChannel::Enumerator>(tmp_dir_, &random_);
56+
}
57+
3858
private:
3959
static constexpr char kSuffix[] = ".channel";
4060
std::string tmp_dir_;
61+
std::mutex mutex_;
62+
std::mt19937 random_;
4163
};
4264

4365
std::unique_ptr<IOManager> IOManager::Create(const std::string& tmp_dir) {

src/paimon/core/disk/io_manager_test.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,34 @@ TEST(IOManagerTest, GenerateTempFilePathShouldBeDifferentAcrossCalls) {
6363
ASSERT_NE(path1, path2);
6464
}
6565

66+
TEST(IOManagerTest, CreateChannelShouldReturnValidAndUniquePaths) {
67+
auto tmp_dir = UniqueTestDirectory::Create();
68+
std::unique_ptr<IOManager> manager = IOManager::Create(tmp_dir->Str());
69+
const std::string prefix = "spill";
70+
71+
ASSERT_OK_AND_ASSIGN(auto channel1, manager->CreateChannel());
72+
ASSERT_TRUE(StringUtils::StartsWith(channel1.GetPath(), tmp_dir->Str() + "/"));
73+
ASSERT_TRUE(StringUtils::EndsWith(channel1.GetPath(), ".channel"));
74+
ASSERT_EQ(PathUtil::GetName(channel1.GetPath()).size(), 32 + std::string(".channel").size());
75+
76+
ASSERT_OK_AND_ASSIGN(auto channel2, manager->CreateChannel(prefix));
77+
ASSERT_TRUE(StringUtils::StartsWith(PathUtil::GetName(channel2.GetPath()), prefix + "-"));
78+
}
79+
80+
TEST(IOManagerTest, CreateChannelEnumeratorShouldReturnSequentialAndUniquePaths) {
81+
auto tmp_dir = UniqueTestDirectory::Create();
82+
std::unique_ptr<IOManager> manager = IOManager::Create(tmp_dir->Str());
83+
84+
ASSERT_OK_AND_ASSIGN(auto enumerator, manager->CreateChannelEnumerator());
85+
86+
for (int i = 0; i < 10; ++i) {
87+
auto channel_id = enumerator->Next();
88+
ASSERT_TRUE(StringUtils::StartsWith(channel_id.GetPath(), tmp_dir->Str() + "/"));
89+
std::string counter_str = std::to_string(i);
90+
std::string padded_counter = std::string(6 - counter_str.size(), '0') + counter_str;
91+
ASSERT_TRUE(
92+
StringUtils::EndsWith(channel_id.GetPath(), "." + padded_counter + ".channel"));
93+
}
94+
}
95+
6696
} // namespace paimon::test
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 <unordered_set>
20+
21+
#include "paimon/disk/file_io_channel.h"
22+
#include "paimon/fs/file_system.h"
23+
#include "paimon/status.h"
24+
25+
namespace paimon {
26+
27+
class SpillChannelManager {
28+
public:
29+
SpillChannelManager(const std::shared_ptr<FileSystem>& fs, size_t initial_capacity) : fs_(fs) {
30+
channels_.reserve(initial_capacity);
31+
}
32+
33+
void AddChannel(const FileIOChannel::ID& channel_id) {
34+
channels_.emplace(channel_id);
35+
}
36+
37+
Status DeleteChannel(const FileIOChannel::ID& channel_id) {
38+
channels_.erase(channel_id);
39+
PAIMON_RETURN_NOT_OK(fs_->Delete(channel_id.GetPath()));
40+
return Status::OK();
41+
}
42+
43+
void Reset() {
44+
for (const auto& channel : channels_) {
45+
fs_->Delete(channel.GetPath());
46+
}
47+
channels_.clear();
48+
}
49+
50+
const std::unordered_set<FileIOChannel::ID, FileIOChannel::ID::Hash>& GetChannels() const {
51+
return channels_;
52+
}
53+
54+
private:
55+
std::unordered_set<FileIOChannel::ID, FileIOChannel::ID::Hash> channels_;
56+
std::shared_ptr<FileSystem> fs_;
57+
};
58+
59+
} // namespace paimon

0 commit comments

Comments
 (0)