Skip to content

Commit 4d1c320

Browse files
authored
feat: Migrate sort merge reader (#85)
1 parent a22663a commit 4d1c320

12 files changed

Lines changed: 2051 additions & 0 deletions
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#include "paimon/core/mergetree/compact/loser_tree.h"
20+
21+
#include <algorithm>
22+
#include <cassert>
23+
24+
namespace paimon {
25+
LoserTree::LoserTree(std::vector<std::unique_ptr<KeyValueRecordReader>>&& readers,
26+
const CompareFunc& first_comparator, const CompareFunc& second_comparator)
27+
: size_(readers.size()),
28+
initialized_(false),
29+
readers_holder_(std::move(readers)),
30+
tree_(size_),
31+
first_comparator_(first_comparator),
32+
second_comparator_(second_comparator) {
33+
leaves_.reserve(size_);
34+
for (const auto& reader : readers_holder_) {
35+
leaves_.emplace_back(reader.get());
36+
}
37+
}
38+
39+
Status LoserTree::InitializeIfNeeded() {
40+
if (!initialized_) {
41+
std::fill(tree_.begin(), tree_.end(), -1);
42+
for (int32_t i = size_ - 1; i >= 0; i--) {
43+
PAIMON_RETURN_NOT_OK(leaves_[i].AdvanceIfAvailable());
44+
Adjust(i);
45+
}
46+
initialized_ = true;
47+
}
48+
return Status::OK();
49+
}
50+
51+
Status LoserTree::AdjustForNextLoop() {
52+
LeafIterator* winner = &leaves_[tree_[0]];
53+
while (winner->state == State::WINNER_POPPED) {
54+
PAIMON_RETURN_NOT_OK(winner->AdvanceIfAvailable());
55+
Adjust(tree_[0]);
56+
winner = &leaves_[tree_[0]];
57+
}
58+
return Status::OK();
59+
}
60+
61+
std::optional<KeyValue> LoserTree::PopWinner() {
62+
LeafIterator* winner = &leaves_[tree_[0]];
63+
if (winner->state == State::WINNER_POPPED) {
64+
// if the winner has already been popped, it means that all the same key has been
65+
// processed.
66+
return std::nullopt;
67+
}
68+
std::optional<KeyValue> result = std::move(winner->Pop());
69+
Adjust(tree_[0]);
70+
return result;
71+
}
72+
73+
const std::optional<KeyValue>& LoserTree::PeekWinner() const {
74+
static const std::optional<KeyValue> empty_kv = std::nullopt;
75+
return leaves_[tree_[0]].state != State::WINNER_POPPED ? leaves_[tree_[0]].Peek() : empty_kv;
76+
}
77+
78+
void LoserTree::Adjust(int32_t winner) {
79+
for (int32_t parent = (winner + size_) / 2; parent > 0 && winner >= 0; parent /= 2) {
80+
LeafIterator* winner_node = &leaves_[winner];
81+
LeafIterator* parent_node = nullptr;
82+
83+
if (tree_[parent] == -1) {
84+
// initialize the tree.
85+
winner_node->state = State::LOSER_WITH_NEW_KEY;
86+
} else {
87+
parent_node = &leaves_[tree_[parent]];
88+
switch (winner_node->state) {
89+
case State::WINNER_WITH_NEW_KEY: {
90+
AdjustWithNewWinnerKey(parent, parent_node, winner_node);
91+
break;
92+
}
93+
case State::WINNER_WITH_SAME_KEY: {
94+
AdjustWithSameWinnerKey(parent, parent_node, winner_node);
95+
break;
96+
}
97+
case State::WINNER_POPPED: {
98+
if (winner_node->first_same_key_index < 0) {
99+
// fast path, which means that the same key is not yet processed in the
100+
// current tree.
101+
parent = -1;
102+
} else {
103+
// fast path. Directly exchange positions with the same key that has not
104+
// yet been processed, no need to compare level by level.
105+
parent = winner_node->first_same_key_index;
106+
parent_node = &leaves_[tree_[parent]];
107+
winner_node->state = State::LOSER_POPPED;
108+
parent_node->state = State::WINNER_WITH_SAME_KEY;
109+
}
110+
break;
111+
}
112+
default:
113+
assert(false);
114+
}
115+
}
116+
117+
// if the winner loses, exchange nodes.
118+
if (!IsWinner(winner_node->state)) {
119+
std::swap(winner, tree_[parent]);
120+
}
121+
}
122+
tree_[0] = winner;
123+
}
124+
125+
void LoserTree::AdjustWithSameWinnerKey(int32_t index, LeafIterator* parent_node,
126+
LeafIterator* winner_node) {
127+
switch (parent_node->state) {
128+
case State::LOSER_WITH_SAME_KEY: {
129+
// the key of the previous loser is the same as the key of the current winner,
130+
// only the sequence needs to be compared.
131+
const auto& parent_key = parent_node->Peek();
132+
const auto& child_key = winner_node->Peek();
133+
int32_t second_result = second_comparator_(parent_key, child_key);
134+
if (second_result > 0) {
135+
parent_node->state = State::WINNER_WITH_SAME_KEY;
136+
winner_node->state = State::LOSER_WITH_SAME_KEY;
137+
parent_node->SetFirstSameKeyIndex(index);
138+
} else {
139+
winner_node->SetFirstSameKeyIndex(index);
140+
}
141+
return;
142+
}
143+
case State::LOSER_WITH_NEW_KEY:
144+
case State::LOSER_POPPED:
145+
return;
146+
default:
147+
assert(false);
148+
}
149+
}
150+
151+
void LoserTree::AdjustWithNewWinnerKey(int32_t index, LeafIterator* parent_node,
152+
LeafIterator* winner_node) {
153+
switch (parent_node->state) {
154+
case State::LOSER_WITH_NEW_KEY: {
155+
// when the new winner is also a new key, it needs to be compared.
156+
const auto& parent_key = parent_node->Peek();
157+
const auto& child_key = winner_node->Peek();
158+
int32_t first_result = first_comparator_(parent_key, child_key);
159+
if (first_result == 0) {
160+
// if the compared keys are the same, we need to update the state of the node
161+
// and record the index of the same key for the winner.
162+
int32_t second_result = second_comparator_(parent_key, child_key);
163+
if (second_result < 0) {
164+
parent_node->state = State::LOSER_WITH_SAME_KEY;
165+
winner_node->SetFirstSameKeyIndex(index);
166+
} else {
167+
winner_node->state = State::LOSER_WITH_SAME_KEY;
168+
parent_node->state = State::WINNER_WITH_NEW_KEY;
169+
parent_node->SetFirstSameKeyIndex(index);
170+
}
171+
} else if (first_result > 0) {
172+
// the two keys are completely different and just need to update the state.
173+
parent_node->state = State::WINNER_WITH_NEW_KEY;
174+
winner_node->state = State::LOSER_WITH_NEW_KEY;
175+
}
176+
return;
177+
}
178+
case State::LOSER_WITH_SAME_KEY: {
179+
// A node in the WINNER_WITH_NEW_KEY state cannot encounter a node in the
180+
// LOSER_WITH_SAME_KEY state.
181+
assert(false);
182+
break;
183+
}
184+
case State::LOSER_POPPED: {
185+
// this case will only happen during adjustForNextLoop.
186+
parent_node->state = State::WINNER_POPPED;
187+
parent_node->first_same_key_index = -1;
188+
winner_node->state = State::LOSER_WITH_NEW_KEY;
189+
return;
190+
}
191+
default:
192+
assert(false);
193+
}
194+
}
195+
196+
} // namespace paimon
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#pragma once
20+
#include <cstdint>
21+
#include <functional>
22+
#include <memory>
23+
#include <optional>
24+
#include <utility>
25+
#include <vector>
26+
27+
#include "paimon/common/metrics/metrics_impl.h"
28+
#include "paimon/core/io/key_value_record_reader.h"
29+
#include "paimon/core/key_value.h"
30+
#include "paimon/result.h"
31+
#include "paimon/status.h"
32+
33+
namespace paimon {
34+
class Metrics;
35+
36+
class LoserTree {
37+
public:
38+
using CompareFunc =
39+
std::function<int32_t(const std::optional<KeyValue>&, const std::optional<KeyValue>&)>;
40+
LoserTree(std::vector<std::unique_ptr<KeyValueRecordReader>>&& readers,
41+
const CompareFunc& first_comparator, const CompareFunc& second_comparator);
42+
43+
/// Initialize the loser tree in the same way as the regular loser tree.
44+
Status InitializeIfNeeded();
45+
46+
/// Adjust the Key that needs to be returned in the next round.
47+
Status AdjustForNextLoop();
48+
49+
/// Pop the current winner and update its state to `State#WINNER_POPPED`.
50+
std::optional<KeyValue> PopWinner();
51+
52+
/// Peek the current winner, mainly for key comparisons.
53+
const std::optional<KeyValue>& PeekWinner() const;
54+
55+
std::shared_ptr<Metrics> GetReaderMetrics() const {
56+
return MetricsImpl::CollectReadMetrics(readers_holder_);
57+
}
58+
59+
void Close() {
60+
for (const auto& reader : readers_holder_) {
61+
reader->Close();
62+
}
63+
}
64+
65+
private:
66+
struct LeafIterator;
67+
68+
/// Adjust the winner from bottom to top. Using different `State`, we can quickly compare
69+
/// whether all the current same keys have been processed.
70+
void Adjust(int32_t winner);
71+
72+
/// The winner node has the same userKey as the global winner.
73+
void AdjustWithSameWinnerKey(int32_t index, LeafIterator* parent_node,
74+
LeafIterator* winner_node);
75+
76+
/// The userKey of the new local winner node is different from that of the previous global
77+
/// winner.
78+
void AdjustWithNewWinnerKey(int32_t index, LeafIterator* parent_node,
79+
LeafIterator* winner_node);
80+
81+
private:
82+
enum class State {
83+
LOSER_WITH_NEW_KEY = 1,
84+
LOSER_WITH_SAME_KEY = 2,
85+
LOSER_POPPED = 3,
86+
WINNER_WITH_NEW_KEY = 4,
87+
WINNER_WITH_SAME_KEY = 5,
88+
WINNER_POPPED = 6
89+
};
90+
91+
static bool IsWinner(State state) {
92+
if (state == State::LOSER_WITH_NEW_KEY || state == State::LOSER_WITH_SAME_KEY ||
93+
state == State::LOSER_POPPED) {
94+
return false;
95+
}
96+
return true;
97+
}
98+
99+
struct LeafIterator {
100+
explicit LeafIterator(KeyValueRecordReader* reader) : reader(reader) {}
101+
102+
const std::optional<KeyValue>& Peek() const {
103+
return kv;
104+
}
105+
106+
std::optional<KeyValue>&& Pop() {
107+
state = State::WINNER_POPPED;
108+
return std::move(kv);
109+
}
110+
111+
void SetFirstSameKeyIndex(int32_t index) {
112+
if (first_same_key_index == -1) {
113+
first_same_key_index = index;
114+
}
115+
}
116+
117+
/// Reads the next kv if any, otherwise returns null.
118+
Status AdvanceIfAvailable() {
119+
first_same_key_index = -1;
120+
state = State::WINNER_WITH_NEW_KEY;
121+
bool has_next = false;
122+
if (iterator != nullptr) {
123+
PAIMON_ASSIGN_OR_RAISE(has_next, iterator->HasNext());
124+
}
125+
if (iterator == nullptr || !has_next) {
126+
while (!end_of_input) {
127+
PAIMON_ASSIGN_OR_RAISE(iterator, reader->NextBatch());
128+
if (!iterator) {
129+
// read eof
130+
reader->Close();
131+
end_of_input = true;
132+
kv = std::nullopt;
133+
} else {
134+
PAIMON_ASSIGN_OR_RAISE(has_next, iterator->HasNext());
135+
if (!has_next) {
136+
continue;
137+
}
138+
PAIMON_ASSIGN_OR_RAISE(kv, iterator->Next());
139+
break;
140+
}
141+
}
142+
} else {
143+
PAIMON_ASSIGN_OR_RAISE(kv, iterator->Next());
144+
}
145+
return Status::OK();
146+
}
147+
148+
bool end_of_input = false;
149+
int32_t first_same_key_index = -1;
150+
State state = State::WINNER_WITH_NEW_KEY;
151+
KeyValueRecordReader* reader;
152+
std::unique_ptr<KeyValueRecordReader::Iterator> iterator;
153+
std::optional<KeyValue> kv;
154+
};
155+
156+
private:
157+
int32_t size_;
158+
bool initialized_;
159+
// must hold all readers, as data array is allocated by the pool of data file
160+
// reader
161+
std::vector<std::unique_ptr<KeyValueRecordReader>> readers_holder_;
162+
163+
std::vector<int32_t> tree_;
164+
std::vector<LeafIterator> leaves_;
165+
/// if comparator.compare('a', 'b') > 0, then 'a' is the winner. In the following
166+
/// implementation, we always let 'a' represent the parent node.
167+
CompareFunc first_comparator_;
168+
/// same as first_comparator, but mainly used to compare sequenceNumber.
169+
CompareFunc second_comparator_;
170+
};
171+
} // namespace paimon
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#pragma once
20+
#include "paimon/core/key_value.h"
21+
namespace paimon {
22+
/// Merge function to merge multiple `KeyValue`s.
23+
class MergeFunction {
24+
public:
25+
virtual ~MergeFunction() = default;
26+
virtual void Reset() = 0;
27+
virtual Status Add(KeyValue&& kv) = 0;
28+
virtual Result<std::optional<KeyValue>> GetResult() = 0;
29+
};
30+
} // namespace paimon

0 commit comments

Comments
 (0)