Skip to content

Commit 2466671

Browse files
feat: optimize count(*) for pk/append table (alibaba#317)
Co-authored-by: dalingmeng <menglingda.mld@alibaba-inc.com>
1 parent 5f112a1 commit 2466671

31 files changed

Lines changed: 1508 additions & 83 deletions
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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 "paimon/result.h"
20+
#include "paimon/visibility.h"
21+
22+
namespace paimon {
23+
24+
/// Reader abstraction for count queries.
25+
class PAIMON_EXPORT CountReader {
26+
public:
27+
virtual ~CountReader() = default;
28+
29+
/// Count rows for splits bound by the corresponding CreateCountReader call.
30+
virtual Result<int64_t> CountRows() = 0;
31+
};
32+
33+
} // namespace paimon

include/paimon/table/source/table_read.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "paimon/memory/memory_pool.h"
2424
#include "paimon/read_context.h"
2525
#include "paimon/reader/batch_reader.h"
26+
#include "paimon/reader/count_reader.h"
2627
#include "paimon/result.h"
2728
#include "paimon/table/source/split.h"
2829
#include "paimon/visibility.h"
@@ -63,6 +64,12 @@ class PAIMON_EXPORT TableRead {
6364
virtual Result<std::unique_ptr<BatchReader>> CreateReader(
6465
const std::shared_ptr<Split>& split) = 0;
6566

67+
/// Creates a `CountReader` for count queries on the specified splits.
68+
///
69+
/// Implementations may override this to provide a more efficient count path.
70+
virtual Result<std::unique_ptr<CountReader>> CreateCountReader(
71+
const std::vector<std::shared_ptr<Split>>& splits);
72+
6673
protected:
6774
explicit TableRead(const std::shared_ptr<MemoryPool>& memory_pool);
6875

src/paimon/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ set(PAIMON_CORE_SRCS
264264
core/mergetree/lookup_file.cpp
265265
core/mergetree/lookup_levels.cpp
266266
core/mergetree/lookup/remote_lookup_file_manager.cpp
267+
core/mergetree/row_count_accumulator.cpp
267268
core/migrate/file_meta_utils.cpp
268269
core/operation/data_evolution_file_store_scan.cpp
269270
core/operation/data_evolution_split_read.cpp
@@ -305,13 +306,15 @@ set(PAIMON_CORE_SRCS
305306
core/table/sink/commit_message.cpp
306307
core/table/sink/commit_message_impl.cpp
307308
core/table/sink/commit_message_serializer.cpp
309+
core/table/source/append_count_reader.cpp
308310
core/table/source/append_only_table_read.cpp
309311
core/table/source/split.cpp
310312
core/table/source/data_split_impl.cpp
311313
core/table/source/data_table_batch_scan.cpp
312314
core/table/source/data_table_stream_scan.cpp
313315
core/table/source/fallback_table_read.cpp
314316
core/table/source/key_value_table_read.cpp
317+
core/table/source/pk_count_reader.cpp
315318
core/table/source/merge_tree_split_generator.cpp
316319
core/table/source/data_evolution_split_generator.cpp
317320
core/table/source/plan_impl.cpp
@@ -703,6 +706,8 @@ if(PAIMON_BUILD_TESTS)
703706
core/table/sink/commit_message_impl_test.cpp
704707
core/table/source/fallback_data_split_test.cpp
705708
core/table/source/table_read_test.cpp
709+
core/table/source/append_count_reader_test.cpp
710+
core/table/source/pk_count_reader_test.cpp
706711
core/table/source/data_split_test.cpp
707712
core/table/source/deletion_file_test.cpp
708713
core/table/source/split_generator_test.cpp

src/paimon/core/deletionvectors/deletion_vector.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "paimon/core/deletionvectors/deletion_vector.h"
1818

19+
#include <cassert>
1920
#include <cstddef>
2021
#include <string>
2122

@@ -59,6 +60,22 @@ DeletionVector::Factory DeletionVector::CreateFactory(
5960
};
6061
}
6162

63+
std::unordered_map<std::string, DeletionFile> DeletionVector::CreateDeletionFileMap(
64+
const std::vector<std::shared_ptr<DataFileMeta>>& data_files,
65+
const std::vector<std::optional<DeletionFile>>& deletion_files) {
66+
std::unordered_map<std::string, DeletionFile> deletion_file_map;
67+
if (deletion_files.empty()) {
68+
return deletion_file_map;
69+
}
70+
assert(deletion_files.size() == data_files.size());
71+
for (size_t i = 0; i < deletion_files.size(); i++) {
72+
if (deletion_files[i] != std::nullopt) {
73+
deletion_file_map.emplace(data_files[i]->file_name, deletion_files[i].value());
74+
}
75+
}
76+
return deletion_file_map;
77+
}
78+
6279
Result<PAIMON_UNIQUE_PTR<DeletionVector>> DeletionVector::DeserializeFromBytes(const Bytes* bytes,
6380
MemoryPool* pool) {
6481
return BitmapDeletionVector::Deserialize(bytes->data(), bytes->size(), pool);

src/paimon/core/deletionvectors/deletion_vector.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ class DeletionVector {
4949

5050
static Factory CreateFactory(const std::shared_ptr<BucketedDvMaintainer>& dv_maintainer);
5151

52+
/// Builds a map from data file name to its deletion file.
53+
///
54+
/// Entries whose deletion file is absent are skipped. Returns an empty map when
55+
/// `deletion_files` is empty.
56+
static std::unordered_map<std::string, DeletionFile> CreateDeletionFileMap(
57+
const std::vector<std::shared_ptr<DataFileMeta>>& data_files,
58+
const std::vector<std::optional<DeletionFile>>& deletion_files);
59+
5260
virtual ~DeletionVector() = default;
5361

5462
/// Marks the row at the specified position as deleted.

src/paimon/core/deletionvectors/deletion_vector_test.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@
1919
#include <cstdint>
2020
#include <cstdlib>
2121
#include <cstring>
22+
#include <memory>
23+
#include <optional>
2224
#include <set>
2325
#include <vector>
2426

2527
#include "gtest/gtest.h"
2628
#include "paimon/core/deletionvectors/bitmap64_deletion_vector.h"
2729
#include "paimon/core/deletionvectors/bitmap_deletion_vector.h"
30+
#include "paimon/core/io/data_file_meta.h"
31+
#include "paimon/core/table/source/deletion_file.h"
2832
#include "paimon/io/byte_array_input_stream.h"
2933
#include "paimon/io/byte_order.h"
3034
#include "paimon/io/data_input_stream.h"
@@ -41,6 +45,16 @@ void AppendInt32BigEndian(std::vector<uint8_t>* bytes, int32_t value) {
4145
bytes->push_back(static_cast<uint8_t>(value & 0xFF));
4246
}
4347

48+
std::shared_ptr<DataFileMeta> CreateDataFileMeta(const std::string& file_name) {
49+
return std::make_shared<DataFileMeta>(
50+
file_name, /*file_size=*/100, /*row_count=*/10, DataFileMeta::EmptyMinKey(),
51+
DataFileMeta::EmptyMaxKey(), SimpleStats::EmptyStats(), SimpleStats::EmptyStats(),
52+
/*min_sequence_number=*/0, /*max_sequence_number=*/0, /*schema_id=*/0,
53+
DataFileMeta::DUMMY_LEVEL, std::vector<std::optional<std::string>>{}, Timestamp(0, 0),
54+
std::nullopt, nullptr, FileSource::Append(), std::nullopt, std::nullopt, std::nullopt,
55+
std::nullopt);
56+
}
57+
4458
} // namespace
4559

4660
TEST(DeletionVectorTest, TestSimple) {
@@ -161,4 +175,24 @@ TEST(DeletionVectorTest, ReadFromDataInputStreamInvalidMagicNumber) {
161175
"Invalid magic number");
162176
}
163177

178+
TEST(DeletionVectorTest, CreateDeletionFileMap) {
179+
std::vector<std::shared_ptr<DataFileMeta>> data_files = {CreateDataFileMeta("file-0.orc"),
180+
CreateDataFileMeta("file-1.orc"),
181+
CreateDataFileMeta("file-2.orc")};
182+
183+
auto empty_map = DeletionVector::CreateDeletionFileMap(data_files, {});
184+
ASSERT_TRUE(empty_map.empty());
185+
186+
DeletionFile deletion_file_0("dv-0", /*offset=*/10, /*length=*/20, /*cardinality=*/3);
187+
DeletionFile deletion_file_2("dv-2", /*offset=*/30, /*length=*/40, std::nullopt);
188+
std::vector<std::optional<DeletionFile>> deletion_files = {deletion_file_0, std::nullopt,
189+
deletion_file_2};
190+
191+
auto deletion_file_map = DeletionVector::CreateDeletionFileMap(data_files, deletion_files);
192+
ASSERT_EQ(deletion_file_map.size(), 2);
193+
ASSERT_EQ(deletion_file_map.at("file-0.orc"), deletion_file_0);
194+
ASSERT_EQ(deletion_file_map.count("file-1.orc"), 0);
195+
ASSERT_EQ(deletion_file_map.at("file-2.orc"), deletion_file_2);
196+
}
197+
164198
} // namespace paimon::test
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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/row_count_accumulator.h"
18+
19+
#include <utility>
20+
21+
#include "paimon/core/key_value.h"
22+
23+
namespace paimon {
24+
25+
RowCountAccumulator::RowCountAccumulator(std::unique_ptr<SortMergeReader>&& merged_reader)
26+
: merged_reader_(std::move(merged_reader)) {}
27+
28+
Result<int64_t> RowCountAccumulator::CountAll() {
29+
int64_t count = 0;
30+
31+
while (true) {
32+
// Get next batch of merged KV iterators
33+
PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<SortMergeReader::Iterator> iter,
34+
merged_reader_->NextBatch());
35+
if (iter == nullptr) {
36+
// No more data
37+
break;
38+
}
39+
40+
// Iterate through all KV objects in this batch
41+
while (true) {
42+
PAIMON_ASSIGN_OR_RAISE(bool has_next, iter->HasNext());
43+
if (!has_next) {
44+
break;
45+
}
46+
47+
iter->Next();
48+
49+
// At this point:
50+
// - kv has passed through SortMergeReader (deduplicated, merged)
51+
// - kv has passed through DropDeleteReader (kind is guaranteed IsAdd())
52+
// - kv represents a final, valid, non-deleted row
53+
count++;
54+
}
55+
}
56+
57+
return count;
58+
}
59+
60+
void RowCountAccumulator::Close() {
61+
if (merged_reader_) {
62+
merged_reader_->Close();
63+
}
64+
}
65+
66+
} // namespace paimon
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
22+
#include "paimon/core/mergetree/compact/sort_merge_reader.h"
23+
#include "paimon/result.h"
24+
25+
namespace paimon {
26+
27+
/// Counts rows from a merged KeyValue stream after delete rows are dropped.
28+
class RowCountAccumulator {
29+
public:
30+
/// @param merged_reader The merged reader. Must be wrapped with DropDeleteReader
31+
/// so that only valid (non-deleted) KeyValue objects are output.
32+
explicit RowCountAccumulator(std::unique_ptr<SortMergeReader>&& merged_reader);
33+
34+
~RowCountAccumulator() = default;
35+
36+
/// Count all valid rows from the merge reader.
37+
/// Iterates through all merged+deduplicated+non-deleted KeyValue objects.
38+
Result<int64_t> CountAll();
39+
40+
/// Close underlying readers and release resources.
41+
void Close();
42+
43+
private:
44+
std::unique_ptr<SortMergeReader> merged_reader_;
45+
};
46+
47+
} // namespace paimon

src/paimon/core/operation/abstract_split_read.cpp

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -102,29 +102,6 @@ bool AbstractSplitRead::NeedCompleteRowTrackingFields(
102102
}
103103
return false;
104104
}
105-
106-
std::unordered_map<std::string, DeletionFile> AbstractSplitRead::CreateDeletionFileMap(
107-
const DataSplitImpl& data_split) {
108-
return CreateDeletionFileMap(data_split.DataFiles(), data_split.DeletionFiles());
109-
}
110-
111-
std::unordered_map<std::string, DeletionFile> AbstractSplitRead::CreateDeletionFileMap(
112-
const std::vector<std::shared_ptr<DataFileMeta>>& data_files,
113-
const std::vector<std::optional<DeletionFile>>& deletion_files) {
114-
std::unordered_map<std::string, DeletionFile> deletion_file_map;
115-
if (deletion_files.empty()) {
116-
return deletion_file_map;
117-
}
118-
assert(deletion_files.size() == data_files.size());
119-
size_t file_count = deletion_files.size();
120-
for (size_t i = 0; i < file_count; i++) {
121-
if (deletion_files[i] != std::nullopt) {
122-
deletion_file_map.emplace(data_files[i]->file_name, deletion_files[i].value());
123-
}
124-
}
125-
return deletion_file_map;
126-
}
127-
128105
Result<std::unique_ptr<BatchReader>> AbstractSplitRead::ApplyPredicateFilterIfNeeded(
129106
std::unique_ptr<BatchReader>&& reader, const std::shared_ptr<Predicate>& predicate) const {
130107
if (!context_->EnablePredicateFilter() || predicate == nullptr) {

src/paimon/core/operation/abstract_split_read.h

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
#include <memory>
2020
#include <optional>
2121
#include <string>
22-
#include <unordered_map>
2322
#include <vector>
2423

2524
#include "arrow/type_fwd.h"
@@ -30,7 +29,6 @@
3029
#include "paimon/core/operation/split_read.h"
3130
#include "paimon/core/schema/schema_manager.h"
3231
#include "paimon/core/table/source/data_split_impl.h"
33-
#include "paimon/core/table/source/deletion_file.h"
3432
#include "paimon/core/utils/file_store_path_factory.h"
3533
#include "paimon/format/reader_builder.h"
3634
#include "paimon/reader/batch_reader.h"
@@ -73,14 +71,6 @@ class AbstractSplitRead : public SplitRead {
7371
std::unique_ptr<SchemaManager>&& schema_manager,
7472
const std::shared_ptr<MemoryPool>& memory_pool,
7573
const std::shared_ptr<Executor>& executor);
76-
77-
static std::unordered_map<std::string, DeletionFile> CreateDeletionFileMap(
78-
const DataSplitImpl& data_split);
79-
80-
static std::unordered_map<std::string, DeletionFile> CreateDeletionFileMap(
81-
const std::vector<std::shared_ptr<DataFileMeta>>& data_files,
82-
const std::vector<std::optional<DeletionFile>>& deletion_files);
83-
8474
Result<std::unique_ptr<BatchReader>> ApplyPredicateFilterIfNeeded(
8575
std::unique_ptr<BatchReader>&& reader, const std::shared_ptr<Predicate>& predicate) const;
8676

0 commit comments

Comments
 (0)