Skip to content

Commit 7d992ca

Browse files
authored
perf(read): reduce Arrow read-path overhead (#242)
1 parent 370582d commit 7d992ca

9 files changed

Lines changed: 86 additions & 2 deletions

File tree

‎src/paimon/common/data/columnar/columnar_row.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ class ColumnarRow : public InternalRow {
7878
row_kind_ = kind;
7979
}
8080

81+
/// Update the row represented by this view without rebuilding its column pointers.
82+
/// @param row_id Zero-based row index in the underlying arrays.
83+
void SetRowId(int64_t row_id) {
84+
row_id_ = row_id;
85+
}
86+
8187
int32_t GetFieldCount() const override {
8288
return array_vec_.size();
8389
}

‎src/paimon/common/data/columnar/columnar_row_test.cpp‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ TEST(ColumnarRowTest, TestSimple) {
7070
ASSERT_EQ(row.GetDouble(6), 5.5);
7171
ASSERT_EQ(row.GetString(7).ToString(), "Hello");
7272
ASSERT_EQ(std::string(row.GetStringView(7)), "Hello");
73+
74+
row.SetRowId(3);
75+
ASSERT_TRUE(row.GetBoolean(0));
76+
ASSERT_EQ(row.GetByte(1), 3);
77+
ASSERT_EQ(row.GetShort(2), 7);
78+
ASSERT_EQ(row.GetInt(3), 13);
79+
ASSERT_EQ(row.GetLong(4), 18);
80+
ASSERT_EQ(row.GetFloat(5), 3.3f);
81+
ASSERT_EQ(row.GetDouble(6), 8.8);
82+
ASSERT_EQ(std::string(row.GetStringView(7)), "WORLD");
7383
}
7484

7585
TEST(ColumnarRowRefTest, TestSimple) {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,9 @@ Status ManifestFile::ReadBucketEntries(const std::string& file_name, int32_t buc
9393
file_name,
9494
[this, bucket, entries](const std::shared_ptr<arrow::StructArray>& batch) -> Status {
9595
const arrow::ArrayVector& fields = batch->fields();
96+
ColumnarRow row(fields, pool_, /*row_id=*/0);
9697
for (int64_t i = 0; i < batch->length(); i++) {
97-
ColumnarRow row(fields, pool_, i);
98+
row.SetRowId(i);
9899
PAIMON_RETURN_NOT_OK(ManifestEntrySerializer::ValidateVersion(row.GetInt(0)));
99100
if (ManifestEntrySerializer::GetBucket(row) != bucket) {
100101
continue;

‎src/paimon/core/utils/objects_file.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,9 @@ Status ObjectsFile<T>::Read(const std::string& file_name,
135135
[this, &filter, result](const std::shared_ptr<arrow::StructArray>& struct_array) -> Status {
136136
result->reserve(result->size() + struct_array->length());
137137
const arrow::ArrayVector& fields = struct_array->fields();
138+
ColumnarRow row(fields, pool_, /*row_id=*/0);
138139
for (int64_t i = 0; i < struct_array->length(); i++) {
139-
ColumnarRow row(fields, pool_, i);
140+
row.SetRowId(i);
140141
PAIMON_ASSIGN_OR_RAISE(T obj, serializer_->FromRow(row));
141142
if (filter) {
142143
PAIMON_ASSIGN_OR_RAISE(bool filter_res, filter(obj));

‎src/paimon/format/avro/avro_direct_decoder.cpp‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,20 @@ Status DecodeFieldToBuilder(const ::avro::NodePtr& avro_node,
5757
::avro::Decoder* decoder, arrow::ArrayBuilder* array_builder,
5858
AvroDirectDecoder::DecodeContext* ctx);
5959

60+
Status ReserveBuilderCapacityImpl(int64_t capacity, arrow::ArrayBuilder* array_builder) {
61+
PAIMON_RETURN_NOT_OK_FROM_ARROW(array_builder->Reserve(capacity));
62+
if (array_builder->type()->id() != arrow::Type::STRUCT) {
63+
return Status::OK();
64+
}
65+
66+
auto* struct_builder = checked_cast<arrow::StructBuilder*>(array_builder);
67+
for (int32_t i = 0; i < struct_builder->num_fields(); ++i) {
68+
PAIMON_RETURN_NOT_OK(
69+
ReserveBuilderCapacityImpl(capacity, struct_builder->field_builder(i)));
70+
}
71+
return Status::OK();
72+
}
73+
6074
/// \brief Skip an Avro value based on its schema without decoding
6175
Status SkipAvroValue(const ::avro::NodePtr& avro_node, ::avro::Decoder* decoder) {
6276
switch (avro_node->type()) {
@@ -193,6 +207,7 @@ Status DecodeListToBuilder(const ::avro::NodePtr& avro_node, ::avro::Decoder* de
193207
// Read array block count
194208
int64_t block_count = decoder->arrayStart();
195209
while (block_count != 0) {
210+
PAIMON_RETURN_NOT_OK(ReserveBuilderCapacityImpl(block_count, value_builder));
196211
for (int64_t i = 0; i < block_count; ++i) {
197212
PAIMON_RETURN_NOT_OK(DecodeFieldToBuilder(element_node, /*projection=*/std::nullopt,
198213
decoder, value_builder, ctx));
@@ -221,6 +236,8 @@ Status DecodeMapToBuilder(const ::avro::NodePtr& avro_node, ::avro::Decoder* dec
221236
// Read map block count
222237
int64_t block_count = decoder->mapStart();
223238
while (block_count != 0) {
239+
PAIMON_RETURN_NOT_OK(ReserveBuilderCapacityImpl(block_count, key_builder));
240+
PAIMON_RETURN_NOT_OK(ReserveBuilderCapacityImpl(block_count, item_builder));
224241
for (int64_t i = 0; i < block_count; ++i) {
225242
PAIMON_RETURN_NOT_OK(DecodeFieldToBuilder(key_node, /*projection=*/std::nullopt,
226243
decoder, key_builder, ctx));
@@ -248,6 +265,8 @@ Status DecodeMapToBuilder(const ::avro::NodePtr& avro_node, ::avro::Decoder* dec
248265
// Read array block count
249266
int64_t block_count = decoder->arrayStart();
250267
while (block_count != 0) {
268+
PAIMON_RETURN_NOT_OK(ReserveBuilderCapacityImpl(block_count, key_builder));
269+
PAIMON_RETURN_NOT_OK(ReserveBuilderCapacityImpl(block_count, item_builder));
251270
for (int64_t i = 0; i < block_count; ++i) {
252271
PAIMON_RETURN_NOT_OK(DecodeFieldToBuilder(key_node, /*projection=*/std::nullopt,
253272
decoder, key_builder, ctx));
@@ -447,4 +466,9 @@ Status AvroDirectDecoder::DecodeAvroToBuilder(const ::avro::NodePtr& avro_node,
447466
return DecodeFieldToBuilder(avro_node, projection, decoder, array_builder, ctx);
448467
}
449468

469+
Status AvroDirectDecoder::ReserveBuilderCapacity(int64_t capacity,
470+
arrow::ArrayBuilder* array_builder) {
471+
return ReserveBuilderCapacityImpl(capacity, array_builder);
472+
}
473+
450474
} // namespace paimon::avro

‎src/paimon/format/avro/avro_direct_decoder.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ class AvroDirectDecoder {
8181
const std::optional<std::set<size_t>>& projection,
8282
::avro::Decoder* decoder, arrow::ArrayBuilder* array_builder,
8383
DecodeContext* ctx);
84+
85+
/// Reserve slots for a builder and any struct children with the same cardinality.
86+
/// @param capacity Number of additional values to append.
87+
/// @param array_builder Builder to reserve.
88+
/// @return Status::OK if all reservations succeed.
89+
static Status ReserveBuilderCapacity(int64_t capacity, arrow::ArrayBuilder* array_builder);
8490
};
8591

8692
} // namespace paimon::avro

‎src/paimon/format/avro/avro_direct_encoder_decoder_test.cpp‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,33 @@ TEST_F(AvroDirectEncoderDecoderTest, TestRecordType) {
361361
CheckResult(schema_json, input_array, &struct_builder);
362362
}
363363

364+
TEST_F(AvroDirectEncoderDecoderTest, TestReserveBuilderCapacity) {
365+
std::shared_ptr<arrow::DataType> nested_type = arrow::struct_({
366+
arrow::field("id", arrow::int32()),
367+
arrow::field("nested", arrow::struct_({arrow::field("value", arrow::int64())})),
368+
arrow::field("items", arrow::list(arrow::int32())),
369+
});
370+
arrow::Result<std::unique_ptr<arrow::ArrayBuilder>> builder_result =
371+
arrow::MakeBuilder(nested_type);
372+
ASSERT_TRUE(builder_result.ok()) << builder_result.status().ToString();
373+
std::unique_ptr<arrow::ArrayBuilder> builder = std::move(builder_result).ValueOrDie();
374+
375+
constexpr int64_t capacity = 1024;
376+
ASSERT_OK(AvroDirectDecoder::ReserveBuilderCapacity(capacity, builder.get()));
377+
378+
auto* root_builder = checked_cast<arrow::StructBuilder*>(builder.get());
379+
ASSERT_GE(root_builder->capacity(), capacity);
380+
ASSERT_GE(root_builder->field_builder(0)->capacity(), capacity);
381+
382+
auto* nested_builder = checked_cast<arrow::StructBuilder*>(root_builder->field_builder(1));
383+
ASSERT_GE(nested_builder->capacity(), capacity);
384+
ASSERT_GE(nested_builder->field_builder(0)->capacity(), capacity);
385+
386+
auto* list_builder = checked_cast<arrow::ListBuilder*>(root_builder->field_builder(2));
387+
ASSERT_GE(list_builder->capacity(), capacity);
388+
ASSERT_EQ(list_builder->value_builder()->capacity(), 0);
389+
}
390+
364391
TEST_F(AvroDirectEncoderDecoderTest, TestDecodeWithProjection) {
365392
arrow::FieldVector fields = {
366393
arrow::field("f0", arrow::boolean()),

‎src/paimon/format/avro/avro_file_batch_reader.cpp‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ Result<BatchReader::ReadBatch> AvroFileBatchReader::NextBatch() {
110110
if (!reader_->hasMore()) {
111111
break;
112112
}
113+
if (array_builder_->length() == 0) {
114+
PAIMON_RETURN_NOT_OK(
115+
AvroDirectDecoder::ReserveBuilderCapacity(batch_size_, array_builder_.get()));
116+
}
113117
reader_->decr();
114118
PAIMON_RETURN_NOT_OK(AvroDirectDecoder::DecodeAvroToBuilder(
115119
reader_->dataSchema().root(), read_fields_projection_, &reader_->decoder(),
@@ -123,7 +127,10 @@ Result<BatchReader::ReadBatch> AvroFileBatchReader::NextBatch() {
123127
}
124128
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Array> array,
125129
array_builder_->Finish());
130+
#ifndef NDEBUG
131+
// Keep structural validation in debug builds without adding its recursive cost to reads.
126132
PAIMON_RETURN_NOT_OK_FROM_ARROW(array->Validate());
133+
#endif
127134
std::unique_ptr<ArrowArray> c_array = std::make_unique<ArrowArray>();
128135
std::unique_ptr<ArrowSchema> c_schema = std::make_unique<ArrowSchema>();
129136
PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportArray(*array, c_array.get(), c_schema.get()));

‎src/paimon/format/orc/orc_adapter.cpp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,8 @@ Result<std::shared_ptr<arrow::Array>> OrcAdapter::AppendBatch(
940940
MakeArrowBuilder(type, batch, pool));
941941
std::shared_ptr<arrow::Array> array;
942942
PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Finish(&array));
943+
// Keep this check in release builds so malformed nested arrays return a Status before they
944+
// reach Arrow constructors that enforce their invariants with a process-terminating check.
943945
PAIMON_RETURN_NOT_OK_FROM_ARROW(array->Validate());
944946
return array;
945947
}

0 commit comments

Comments
 (0)