Skip to content

Commit 1c575f7

Browse files
authored
refactor: refactor ColumnarBatchContext to reduce ptr overhead (alibaba#154)
1 parent b878de6 commit 1c575f7

8 files changed

Lines changed: 141 additions & 114 deletions

File tree

src/paimon/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ set(PAIMON_COMMON_SRCS
118118
common/types/data_type_json_parser.cpp
119119
common/types/row_kind.cpp
120120
common/types/row_type.cpp
121+
common/utils/arrow/arrow_utils.cpp
121122
common/utils/arrow/mem_utils.cpp
122123
common/utils/binary_row_partition_computer.cpp
123124
common/utils/bit_set.cpp

src/paimon/common/data/columnar/columnar_batch_context.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,20 @@ class MemoryPool;
3030

3131
struct ColumnarBatchContext {
3232
ColumnarBatchContext(const std::shared_ptr<arrow::StructArray>& struct_array_in,
33-
const arrow::ArrayVector& array_vec_holder_in,
33+
const arrow::ArrayVector& array_vec_in,
3434
const std::shared_ptr<MemoryPool>& pool_in)
35-
: struct_array(struct_array_in), pool(pool_in), array_vec_holder(array_vec_holder_in) {
36-
array_ptrs.reserve(array_vec_holder.size());
37-
for (const auto& array : array_vec_holder) {
35+
: struct_array(struct_array_in), pool(pool_in) {
36+
array_ptrs.reserve(array_vec_in.size());
37+
for (const auto& array : array_vec_in) {
3838
array_ptrs.push_back(array.get());
3939
}
4040
}
4141

42+
/// @note `struct_array` is the data holder for columnar row, ensure that the data life
43+
/// cycle is consistent with the columnar row, `array_ptrs` maybe a subset of
44+
/// `struct_array`, so `struct_array` cannot be used for `GetXXX()`
4245
std::shared_ptr<arrow::StructArray> struct_array;
4346
std::shared_ptr<MemoryPool> pool;
44-
arrow::ArrayVector array_vec_holder;
4547
std::vector<const arrow::Array*> array_ptrs;
4648
};
4749
} // namespace paimon
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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/common/utils/arrow/arrow_utils.h"
18+
namespace paimon {
19+
Result<std::shared_ptr<arrow::Schema>> ArrowUtils::DataTypeToSchema(
20+
const std::shared_ptr<arrow::DataType>& data_type) {
21+
if (data_type->id() != arrow::Type::STRUCT) {
22+
return Status::Invalid(
23+
fmt::format("Expected struct data type, actual data type: {}", data_type->ToString()));
24+
}
25+
const auto& struct_type = std::static_pointer_cast<arrow::StructType>(data_type);
26+
return std::make_shared<arrow::Schema>(struct_type->fields());
27+
}
28+
29+
Result<std::vector<int32_t>> ArrowUtils::CreateProjection(
30+
const std::shared_ptr<arrow::Schema>& file_schema, const arrow::FieldVector& read_fields) {
31+
std::vector<int32_t> target_to_src_mapping;
32+
target_to_src_mapping.reserve(read_fields.size());
33+
for (const auto& field : read_fields) {
34+
auto src_field_idx = file_schema->GetFieldIndex(field->name());
35+
if (src_field_idx < 0) {
36+
return Status::Invalid(
37+
fmt::format("Field '{}' not found or duplicate in file schema", field->name()));
38+
}
39+
target_to_src_mapping.push_back(src_field_idx);
40+
}
41+
return target_to_src_mapping;
42+
}
43+
44+
Status ArrowUtils::CheckNullabilityMatch(const std::shared_ptr<arrow::Schema>& schema,
45+
const std::shared_ptr<arrow::Array>& data) {
46+
auto struct_array = arrow::internal::checked_pointer_cast<arrow::StructArray>(data);
47+
if (struct_array->num_fields() != schema->num_fields()) {
48+
return Status::Invalid(fmt::format(
49+
"CheckNullabilityMatch failed, data field count {} mismatch schema field count {}",
50+
struct_array->num_fields(), schema->num_fields()));
51+
}
52+
for (int32_t i = 0; i < schema->num_fields(); i++) {
53+
PAIMON_RETURN_NOT_OK(InnerCheckNullabilityMatch(schema->field(i), struct_array->field(i)));
54+
}
55+
return Status::OK();
56+
}
57+
58+
void ArrowUtils::TraverseArray(const std::shared_ptr<arrow::Array>& array) {
59+
arrow::Type::type type = array->type()->id();
60+
switch (type) {
61+
case arrow::Type::type::DICTIONARY: {
62+
auto* dict_array = arrow::internal::checked_cast<arrow::DictionaryArray*>(array.get());
63+
[[maybe_unused]] auto dict = dict_array->dictionary();
64+
return;
65+
}
66+
case arrow::Type::type::STRUCT: {
67+
auto* struct_array = arrow::internal::checked_cast<arrow::StructArray*>(array.get());
68+
for (const auto& field : struct_array->fields()) {
69+
TraverseArray(field);
70+
}
71+
return;
72+
}
73+
case arrow::Type::type::MAP: {
74+
auto* map_array = arrow::internal::checked_cast<arrow::MapArray*>(array.get());
75+
TraverseArray(map_array->keys());
76+
TraverseArray(map_array->items());
77+
return;
78+
}
79+
case arrow::Type::type::LIST: {
80+
auto* list_array = arrow::internal::checked_cast<arrow::ListArray*>(array.get());
81+
TraverseArray(list_array->values());
82+
return;
83+
}
84+
default:
85+
return;
86+
}
87+
}
88+
89+
Status ArrowUtils::InnerCheckNullabilityMatch(const std::shared_ptr<arrow::Field>& field,
90+
const std::shared_ptr<arrow::Array>& data) {
91+
if (PAIMON_UNLIKELY(!field->nullable() && data->null_count() != 0)) {
92+
return Status::Invalid(fmt::format(
93+
"CheckNullabilityMatch failed, field {} not nullable while data have null value",
94+
field->name()));
95+
}
96+
auto type = field->type();
97+
if (type->id() == arrow::Type::STRUCT) {
98+
auto struct_type = arrow::internal::checked_pointer_cast<arrow::StructType>(field->type());
99+
auto struct_array = arrow::internal::checked_pointer_cast<arrow::StructArray>(data);
100+
for (int32_t i = 0; i < struct_type->num_fields(); ++i) {
101+
PAIMON_RETURN_NOT_OK(
102+
InnerCheckNullabilityMatch(struct_type->field(i), struct_array->field(i)));
103+
}
104+
} else if (type->id() == arrow::Type::LIST) {
105+
auto list_type = arrow::internal::checked_pointer_cast<arrow::ListType>(field->type());
106+
auto list_array = arrow::internal::checked_pointer_cast<arrow::ListArray>(data);
107+
PAIMON_RETURN_NOT_OK(
108+
InnerCheckNullabilityMatch(list_type->value_field(), list_array->values()));
109+
} else if (type->id() == arrow::Type::MAP) {
110+
auto map_type = arrow::internal::checked_pointer_cast<arrow::MapType>(field->type());
111+
auto map_array = arrow::internal::checked_pointer_cast<arrow::MapArray>(data);
112+
PAIMON_RETURN_NOT_OK(InnerCheckNullabilityMatch(map_type->key_field(), map_array->keys()));
113+
PAIMON_RETURN_NOT_OK(
114+
InnerCheckNullabilityMatch(map_type->item_field(), map_array->items()));
115+
}
116+
return Status::OK();
117+
}
118+
} // namespace paimon
Lines changed: 11 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024-present Alibaba Inc.
2+
* Copyright 2026-present Alibaba Inc.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,83 +24,28 @@
2424

2525
namespace paimon {
2626

27-
class ArrowUtils {
27+
class PAIMON_EXPORT ArrowUtils {
2828
public:
2929
ArrowUtils() = delete;
3030
~ArrowUtils() = delete;
3131

3232
static Result<std::shared_ptr<arrow::Schema>> DataTypeToSchema(
33-
const std::shared_ptr<arrow::DataType>& data_type) {
34-
if (data_type->id() != arrow::Type::STRUCT) {
35-
return Status::Invalid(fmt::format("Expected struct data type, actual data type: {}",
36-
data_type->ToString()));
37-
}
38-
const auto& struct_type = std::static_pointer_cast<arrow::StructType>(data_type);
39-
return std::make_shared<arrow::Schema>(struct_type->fields());
40-
}
33+
const std::shared_ptr<arrow::DataType>& data_type);
4134

4235
static Result<std::vector<int32_t>> CreateProjection(
43-
const std::shared_ptr<arrow::Schema>& file_schema, const arrow::FieldVector& read_fields) {
44-
std::vector<int32_t> target_to_src_mapping;
45-
target_to_src_mapping.reserve(read_fields.size());
46-
for (const auto& field : read_fields) {
47-
auto src_field_idx = file_schema->GetFieldIndex(field->name());
48-
if (src_field_idx < 0) {
49-
return Status::Invalid(
50-
fmt::format("Field '{}' not found or duplicate in file schema", field->name()));
51-
}
52-
target_to_src_mapping.push_back(src_field_idx);
53-
}
54-
return target_to_src_mapping;
55-
}
36+
const std::shared_ptr<arrow::Schema>& file_schema, const arrow::FieldVector& read_fields);
5637

5738
static Status CheckNullabilityMatch(const std::shared_ptr<arrow::Schema>& schema,
58-
const std::shared_ptr<arrow::Array>& data) {
59-
auto struct_array = arrow::internal::checked_pointer_cast<arrow::StructArray>(data);
60-
if (struct_array->num_fields() != schema->num_fields()) {
61-
return Status::Invalid(fmt::format(
62-
"CheckNullabilityMatch failed, data field count {} mismatch schema field count {}",
63-
struct_array->num_fields(), schema->num_fields()));
64-
}
65-
for (int32_t i = 0; i < schema->num_fields(); i++) {
66-
PAIMON_RETURN_NOT_OK(
67-
InnerCheckNullabilityMatch(schema->field(i), struct_array->field(i)));
68-
}
69-
return Status::OK();
70-
}
39+
const std::shared_ptr<arrow::Array>& data);
40+
41+
// For struct array, arrow is unsafe for fields() and field(); for dict array, arrow is unsafe
42+
// for dictionary(). Therefore, access array in advance before merge sort and projection to
43+
// avoid subsequent multi-threading problems.
44+
static void TraverseArray(const std::shared_ptr<arrow::Array>& array);
7145

7246
private:
7347
static Status InnerCheckNullabilityMatch(const std::shared_ptr<arrow::Field>& field,
74-
const std::shared_ptr<arrow::Array>& data) {
75-
if (PAIMON_UNLIKELY(!field->nullable() && data->null_count() != 0)) {
76-
return Status::Invalid(fmt::format(
77-
"CheckNullabilityMatch failed, field {} not nullable while data have null value",
78-
field->name()));
79-
}
80-
auto type = field->type();
81-
if (type->id() == arrow::Type::STRUCT) {
82-
auto struct_type =
83-
arrow::internal::checked_pointer_cast<arrow::StructType>(field->type());
84-
auto struct_array = arrow::internal::checked_pointer_cast<arrow::StructArray>(data);
85-
for (int32_t i = 0; i < struct_type->num_fields(); ++i) {
86-
PAIMON_RETURN_NOT_OK(
87-
InnerCheckNullabilityMatch(struct_type->field(i), struct_array->field(i)));
88-
}
89-
} else if (type->id() == arrow::Type::LIST) {
90-
auto list_type = arrow::internal::checked_pointer_cast<arrow::ListType>(field->type());
91-
auto list_array = arrow::internal::checked_pointer_cast<arrow::ListArray>(data);
92-
PAIMON_RETURN_NOT_OK(
93-
InnerCheckNullabilityMatch(list_type->value_field(), list_array->values()));
94-
} else if (type->id() == arrow::Type::MAP) {
95-
auto map_type = arrow::internal::checked_pointer_cast<arrow::MapType>(field->type());
96-
auto map_array = arrow::internal::checked_pointer_cast<arrow::MapArray>(data);
97-
PAIMON_RETURN_NOT_OK(
98-
InnerCheckNullabilityMatch(map_type->key_field(), map_array->keys()));
99-
PAIMON_RETURN_NOT_OK(
100-
InnerCheckNullabilityMatch(map_type->item_field(), map_array->items()));
101-
}
102-
return Status::OK();
103-
}
48+
const std::shared_ptr<arrow::Array>& data);
10449
};
10550

10651
} // namespace paimon

src/paimon/core/io/key_value_data_file_record_reader.cpp

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@
3131
#include "paimon/common/data/columnar/columnar_row_ref.h"
3232
#include "paimon/common/table/special_fields.h"
3333
#include "paimon/common/types/row_kind.h"
34+
#include "paimon/common/utils/arrow/arrow_utils.h"
3435
#include "paimon/common/utils/arrow/status_utils.h"
3536
#include "paimon/status.h"
36-
3737
namespace paimon {
3838
class MemoryPool;
3939

@@ -137,7 +137,7 @@ Result<std::unique_ptr<KeyValueRecordReader::Iterator>> KeyValueDataFileRecordRe
137137
value_fields_ = value_struct_array_->fields();
138138
key_ctx_ = std::make_shared<ColumnarBatchContext>(nullptr, key_fields_, pool_);
139139
value_ctx_ = std::make_shared<ColumnarBatchContext>(value_struct_array_, value_fields_, pool_);
140-
TraverseArray(value_struct_array_);
140+
ArrowUtils::TraverseArray(value_struct_array_);
141141
return std::make_unique<KeyValueDataFileRecordReader::Iterator>(this);
142142
}
143143

@@ -151,36 +151,4 @@ void KeyValueDataFileRecordReader::Reset() {
151151
sequence_number_array_.reset();
152152
row_kind_array_.reset();
153153
}
154-
155-
void KeyValueDataFileRecordReader::TraverseArray(const std::shared_ptr<arrow::Array>& array) {
156-
arrow::Type::type type = array->type()->id();
157-
switch (type) {
158-
case arrow::Type::type::DICTIONARY: {
159-
auto* dict_array = arrow::internal::checked_cast<arrow::DictionaryArray*>(array.get());
160-
[[maybe_unused]] auto dict = dict_array->dictionary();
161-
return;
162-
}
163-
case arrow::Type::type::STRUCT: {
164-
auto* struct_array = arrow::internal::checked_cast<arrow::StructArray*>(array.get());
165-
for (const auto& field : struct_array->fields()) {
166-
TraverseArray(field);
167-
}
168-
return;
169-
}
170-
case arrow::Type::type::MAP: {
171-
auto* map_array = arrow::internal::checked_cast<arrow::MapArray*>(array.get());
172-
TraverseArray(map_array->keys());
173-
TraverseArray(map_array->items());
174-
return;
175-
}
176-
case arrow::Type::type::LIST: {
177-
auto* list_array = arrow::internal::checked_cast<arrow::ListArray*>(array.get());
178-
TraverseArray(list_array->values());
179-
return;
180-
}
181-
default:
182-
return;
183-
}
184-
}
185-
186154
} // namespace paimon

src/paimon/core/io/key_value_data_file_record_reader.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,6 @@ class KeyValueDataFileRecordReader : public KeyValueRecordReader {
7676
// virtual for test
7777
virtual void Reset();
7878

79-
private:
80-
// For struct array, arrow is unsafe for fields() and field(); for dict array, arrow is unsafe
81-
// for dictionary(). Therefore, access array in advance before merge sort and projection to
82-
// avoid subsequent multi-threading problems.
83-
static void TraverseArray(const std::shared_ptr<arrow::Array>& array);
84-
8579
private:
8680
int32_t key_arity_;
8781
int32_t level_;

src/paimon/core/io/key_value_in_memory_record_reader.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@
3030
#include "paimon/common/data/columnar/columnar_row.h"
3131
#include "paimon/common/data/internal_row.h"
3232
#include "paimon/common/types/row_kind.h"
33+
#include "paimon/common/utils/arrow/arrow_utils.h"
3334
#include "paimon/common/utils/arrow/status_utils.h"
3435
#include "paimon/core/mergetree/compact/merge_function_wrapper.h"
3536
#include "paimon/core/utils/fields_comparator.h"
3637
#include "paimon/status.h"
37-
3838
namespace paimon {
3939
class MemoryPool;
4040

@@ -85,6 +85,7 @@ KeyValueInMemoryRecordReader::KeyValueInMemoryRecordReader(
8585
key_comparator_(key_comparator),
8686
merge_function_wrapper_(merge_function_wrapper) {
8787
assert(value_struct_array_);
88+
ArrowUtils::TraverseArray(value_struct_array_);
8889
}
8990

9091
Result<std::unique_ptr<KeyValueRecordReader::Iterator>> KeyValueInMemoryRecordReader::NextBatch() {

test/inte/scan_and_read_inte_test.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2584,7 +2584,6 @@ TEST_P(ScanAndReadInteTest, TestCastTimestampType) {
25842584
ASSERT_TRUE(expected->Equals(read_result)) << read_result->ToString();
25852585
}
25862586

2587-
#ifdef PAIMON_ENABLE_AVRO
25882587
TEST_F(ScanAndReadInteTest, TestAvroWithAppendTable) {
25892588
auto read_data = [](int64_t snapshot_id, const std::string& result_json) {
25902589
std::string table_path = GetDataDir() + "/avro/append_multiple.db/append_multiple";
@@ -2721,6 +2720,5 @@ TEST_F(ScanAndReadInteTest, TestAvroWithPkTable) {
27212720
[0, true, 10, 1, 1, 1000, 1.5, 2.5, "Tony", "abcdef", 100, "123.45", [[["key",123]],[1,2,3]]]
27222721
])");
27232722
}
2724-
#endif
27252723

27262724
} // namespace paimon::test

0 commit comments

Comments
 (0)