Skip to content

Commit a971829

Browse files
authored
perf(manifest): selectively decode bucket entries with bounded Avro indexes (#323)
1 parent 3366987 commit a971829

15 files changed

Lines changed: 1682 additions & 158 deletions

‎src/paimon/core/manifest/manifest_file.cpp‎

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626
#include "arrow/c/abi.h"
2727
#include "arrow/c/bridge.h"
2828
#include "paimon/common/data/columnar/columnar_row.h"
29+
#include "paimon/common/predicate/predicate_validator.h"
30+
#include "paimon/common/reader/late_materializing_file_batch_reader.h"
2931
#include "paimon/common/utils/arrow/status_utils.h"
32+
#include "paimon/common/utils/checked_cast.h"
3033
#include "paimon/core/io/rolling_file_writer.h"
3134
#include "paimon/core/manifest/manifest_entry.h"
3235
#include "paimon/core/manifest/manifest_entry_serializer.h"
@@ -39,6 +42,7 @@
3942
#include "paimon/format/file_format.h"
4043
#include "paimon/format/reader_builder.h"
4144
#include "paimon/format/writer_builder.h"
45+
#include "paimon/predicate/predicate_builder.h"
4246
#include "paimon/status.h"
4347

4448
namespace arrow {
@@ -49,6 +53,12 @@ class Schema;
4953
namespace paimon {
5054
class MemoryPool;
5155

56+
namespace {
57+
constexpr int32_t kVersionFieldIndex = 0;
58+
constexpr int32_t kBucketFieldIndex = 3;
59+
constexpr int32_t kTotalBucketsFieldIndex = 4;
60+
} // namespace
61+
5262
ManifestFile::ManifestFile(const std::shared_ptr<FileSystem>& file_system,
5363
const std::shared_ptr<ReaderBuilder>& reader_builder,
5464
const std::shared_ptr<WriterBuilder>& writer_builder,
@@ -91,25 +101,87 @@ Result<std::unique_ptr<ManifestFile>> ManifestFile::Create(
91101
}
92102

93103
Status ManifestFile::ReadBucketEntries(const std::string& file_name, int32_t bucket,
104+
const std::optional<int32_t>& expected_total_buckets,
94105
std::optional<int64_t> file_size,
95106
std::vector<ManifestEntry>* entries) const {
107+
// Readers without precise bitmap selection still filter aligned Arrow columns
108+
// before constructing ManifestEntry and DataFileMeta objects.
96109
return ReadArrowBatches(
97-
file_name,
98-
[this, bucket, entries](const std::shared_ptr<arrow::StructArray>& batch) -> Status {
99-
const arrow::ArrayVector& fields = batch->fields();
100-
ColumnarRow row(fields, pool_, /*row_id=*/0);
101-
for (int64_t i = 0; i < batch->length(); i++) {
110+
file_name, file_size,
111+
[this, bucket, expected_total_buckets,
112+
entries](const std::shared_ptr<arrow::StructArray>& batch) -> Status {
113+
ColumnarRow row(batch->fields(), pool_, /*row_id=*/0);
114+
for (int64_t i = 0; i < batch->length(); ++i) {
102115
row.SetRowId(i);
103-
PAIMON_RETURN_NOT_OK(ManifestEntrySerializer::ValidateVersion(row.GetInt(0)));
104-
if (ManifestEntrySerializer::GetBucket(row) != bucket) {
116+
PAIMON_RETURN_NOT_OK(
117+
ManifestEntrySerializer::ValidateVersion(row.GetInt(kVersionFieldIndex)));
118+
// Different or unknown bucket counts must reach per-entry bucket filtering.
119+
const bool historical_layout =
120+
expected_total_buckets &&
121+
(row.IsNullAt(kBucketFieldIndex) || row.IsNullAt(kTotalBucketsFieldIndex) ||
122+
row.GetInt(kTotalBucketsFieldIndex) != expected_total_buckets.value());
123+
if (!historical_layout && ManifestEntrySerializer::GetBucket(row) != bucket) {
105124
continue;
106125
}
107126
PAIMON_ASSIGN_OR_RAISE(ManifestEntry entry, serializer_->FromRow(row));
108127
entries->push_back(std::move(entry));
109128
}
110129
return Status::OK();
111130
},
112-
file_size);
131+
[this, bucket, expected_total_buckets](std::unique_ptr<FileBatchReader>* reader) {
132+
return PrepareBucketRead(bucket, expected_total_buckets, reader);
133+
});
134+
}
135+
136+
Status ManifestFile::PrepareBucketRead(int32_t bucket,
137+
const std::optional<int32_t>& expected_total_buckets,
138+
std::unique_ptr<FileBatchReader>* reader) const {
139+
if (!(*reader)->SupportPreciseBitmapSelection()) {
140+
return Status::OK();
141+
}
142+
PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<ArrowSchema> c_schema, (*reader)->GetFileSchema());
143+
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Schema> file_schema,
144+
arrow::ImportSchema(c_schema.get()));
145+
const auto& target_type = serializer_->GetDataType();
146+
const std::string& bucket_name = target_type->field(kBucketFieldIndex)->name();
147+
std::shared_ptr<Predicate> selector =
148+
PredicateBuilder::Equal(kBucketFieldIndex, bucket_name, FieldType::INT, Literal(bucket));
149+
if (expected_total_buckets) {
150+
const std::string& total_name = target_type->field(kTotalBucketsFieldIndex)->name();
151+
PAIMON_ASSIGN_OR_RAISE(
152+
selector,
153+
PredicateBuilder::Or(
154+
{selector,
155+
PredicateBuilder::IsNull(kTotalBucketsFieldIndex, total_name, FieldType::INT),
156+
PredicateBuilder::NotEqual(kTotalBucketsFieldIndex, total_name, FieldType::INT,
157+
Literal(expected_total_buckets.value()))}));
158+
}
159+
// Retain unsupported versions regardless of bucket so the consumer validates every version
160+
// before bucket filtering, including when the probe would otherwise select no entries.
161+
const std::string& version_name = target_type->field(kVersionFieldIndex)->name();
162+
PAIMON_ASSIGN_OR_RAISE(
163+
selector,
164+
PredicateBuilder::Or(
165+
{selector, PredicateBuilder::IsNull(kVersionFieldIndex, version_name, FieldType::INT),
166+
PredicateBuilder::NotEqual(
167+
kVersionFieldIndex, version_name, FieldType::INT,
168+
Literal(
169+
checked_cast<ManifestEntrySerializer*>(serializer_.get())->GetVersion()))}));
170+
if (!PredicateValidator::ValidatePredicateWithSchema(*file_schema, selector,
171+
/*validate_field_idx=*/true)
172+
.ok()) {
173+
// An incompatible projection must fall back to ordinary schema evolution.
174+
return Status::OK();
175+
}
176+
PAIMON_ASSIGN_OR_RAISE(
177+
std::unique_ptr<LateMaterializingFileBatchReader> selective_reader,
178+
LateMaterializingFileBatchReader::Create(std::move(*reader), arrow_pool_));
179+
// Keep the on-disk schema; ManifestMetaReader still performs schema evolution afterwards.
180+
ArrowSchema full_c_schema;
181+
PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportSchema(*file_schema, &full_c_schema));
182+
PAIMON_RETURN_NOT_OK(selective_reader->SetReadSchema(&full_c_schema, selector, std::nullopt));
183+
*reader = std::move(selective_reader);
184+
return Status::OK();
113185
}
114186

115187
Result<std::vector<ManifestFileMeta>> ManifestFile::Write(

‎src/paimon/core/manifest/manifest_file.h‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,23 @@ class ManifestFile : public ObjectsFile<ManifestEntry> {
6363
/// @note This method is atomic.
6464
Result<std::vector<ManifestFileMeta>> Write(const std::vector<ManifestEntry>& entries);
6565

66-
/// Read a manifest file and deserialize only entries for the specified bucket.
66+
/// Read entries for a bucket. An inferred total bucket count also retains entries with
67+
/// different or unknown bucket counts for per-entry bucket filtering.
68+
/// Bucket pruning assumes stable bucket-key hashing across schema versions.
69+
/// Serialization versions are validated for all entries before bucket filtering.
6770
///
6871
/// @param file_size Length of the manifest when the caller already has it from the manifest
6972
/// list, which saves the read a metadata request on a remote store. Pass
7073
/// std::nullopt when the length is not known.
7174
Status ReadBucketEntries(const std::string& file_name, int32_t bucket,
75+
const std::optional<int32_t>& expected_total_buckets,
7276
std::optional<int64_t> file_size,
7377
std::vector<ManifestEntry>* entries) const;
7478

7579
private:
80+
Status PrepareBucketRead(int32_t bucket, const std::optional<int32_t>& expected_total_buckets,
81+
std::unique_ptr<FileBatchReader>* reader) const;
82+
7683
ManifestFile(const std::shared_ptr<FileSystem>& file_system,
7784
const std::shared_ptr<ReaderBuilder>& reader_builder,
7885
const std::shared_ptr<WriterBuilder>& writer_builder,

0 commit comments

Comments
 (0)