Skip to content

Commit 57cce08

Browse files
authored
feat: support LookupMergeTreeCompactRewriter (alibaba#186)
1 parent 3b834c5 commit 57cce08

54 files changed

Lines changed: 2812 additions & 150 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

include/paimon/defs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,10 @@ struct PAIMON_EXPORT Options {
354354
static const char SPILL_COMPRESSION_ZSTD_LEVEL[];
355355
/// "cache-page-size" - Memory page size for caching. Default value is 64 kb.
356356
static const char CACHE_PAGE_SIZE[];
357+
/// "file.format.per.level" - Define different file format for different level, you can add the
358+
/// conf like this: 'file.format.per.level' = '0:avro,3:parquet', if the file format for level
359+
/// is not provided, the default format which set by FILE_FORMAT will be used.
360+
static const char FILE_FORMAT_PER_LEVEL[];
357361
};
358362

359363
static constexpr int64_t BATCH_WRITE_COMMIT_IDENTIFIER = std::numeric_limits<int64_t>::max();

src/paimon/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,8 @@ set(PAIMON_CORE_SRCS
233233
core/mergetree/compact/partial_update_merge_function.cpp
234234
core/mergetree/compact/sort_merge_reader_with_loser_tree.cpp
235235
core/mergetree/compact/sort_merge_reader_with_min_heap.cpp
236+
core/mergetree/compact/lookup_merge_tree_compact_rewriter.cpp
237+
core/mergetree/compact/changelog_merge_tree_rewriter.cpp
236238
core/mergetree/merge_tree_writer.cpp
237239
core/mergetree/levels.cpp
238240
core/mergetree/lookup_levels.cpp
@@ -560,7 +562,10 @@ if(PAIMON_BUILD_TESTS)
560562
core/mergetree/compact/aggregate/field_sum_agg_test.cpp
561563
core/mergetree/compact/deduplicate_merge_function_test.cpp
562564
core/mergetree/compact/first_row_merge_function_test.cpp
565+
core/mergetree/compact/first_row_merge_function_wrapper_test.cpp
563566
core/mergetree/compact/interval_partition_test.cpp
567+
core/mergetree/compact/lookup_changelog_merge_function_wrapper_test.cpp
568+
core/mergetree/compact/lookup_merge_tree_compact_rewriter_test.cpp
564569
core/mergetree/compact/lookup_merge_function_test.cpp
565570
core/mergetree/compact/partial_update_merge_function_test.cpp
566571
core/mergetree/compact/reducer_merge_function_wrapper_test.cpp
@@ -621,6 +626,7 @@ if(PAIMON_BUILD_TESTS)
621626
core/table/source/table_scan_test.cpp
622627
core/tag/tag_test.cpp
623628
core/utils/branch_manager_test.cpp
629+
core/utils/file_store_path_factory_cache_test.cpp
624630
core/utils/field_mapping_test.cpp
625631
core/utils/fields_comparator_test.cpp
626632
core/utils/file_store_path_factory_test.cpp

src/paimon/common/data/serializer/row_compacted_serializer_test.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,38 @@ TEST(RowCompactedSerializerTest, TestNestedNullWithTimestampAndDecimal2) {
506506
}
507507
}
508508

509+
TEST(RowCompactedSerializerTest, TestListType) {
510+
auto pool = GetDefaultPool();
511+
// prepare data
512+
auto inner_child1 = arrow::field("inner1", arrow::list(arrow::int32()));
513+
auto arrow_type = arrow::struct_({inner_child1});
514+
// each inner child per row
515+
std::shared_ptr<arrow::Array> array = arrow::ipc::internal::json::ArrayFromJSON(arrow_type,
516+
R"([
517+
[[5, 6, 7]],
518+
[[1, 2, 3]],
519+
[[4]]
520+
])")
521+
.ValueOrDie();
522+
auto struct_array = std::dynamic_pointer_cast<arrow::StructArray>(array);
523+
ASSERT_TRUE(struct_array);
524+
auto columnar_row =
525+
std::make_shared<ColumnarRow>(/*struct_array=*/nullptr, struct_array->fields(), pool,
526+
/*row_id=*/0);
527+
528+
// serialize and deserialize
529+
ASSERT_OK_AND_ASSIGN(auto serializer,
530+
RowCompactedSerializer::Create(arrow::schema(arrow_type->fields()), pool));
531+
ASSERT_OK_AND_ASSIGN(auto bytes, serializer->SerializeToBytes(*columnar_row));
532+
ASSERT_OK_AND_ASSIGN(auto row, serializer->Deserialize(bytes));
533+
534+
// check result
535+
ASSERT_EQ(row->GetFieldCount(), 1);
536+
537+
// for inner_child1
538+
ASSERT_EQ(row->GetArray(0)->ToIntArray().value(), std::vector<int32_t>({5, 6, 7}));
539+
}
540+
509541
TEST(RowCompactedSerializerTest, TestSliceComparator) {
510542
auto pool = GetDefaultPool();
511543
arrow::FieldVector fields = {

src/paimon/common/defs.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,5 @@ const char Options::LOOKUP_CACHE_BLOOM_FILTER_FPP[] = "lookup.cache.bloom.filter
101101
const char Options::LOOKUP_CACHE_SPILL_COMPRESSION[] = "lookup.cache-spill-compression";
102102
const char Options::SPILL_COMPRESSION_ZSTD_LEVEL[] = "spill-compression.zstd-level";
103103
const char Options::CACHE_PAGE_SIZE[] = "cache-page-size";
104+
const char Options::FILE_FORMAT_PER_LEVEL[] = "file.format.per.level";
104105
} // namespace paimon

src/paimon/core/append/append_only_writer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ AppendOnlyWriter::SingleFileWriterCreator AppendOnlyWriter::GetDataFileWriterCre
173173
::ArrowSchema arrow_schema;
174174
ScopeGuard guard([&arrow_schema]() { ArrowSchemaRelease(&arrow_schema); });
175175
PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportSchema(*schema, &arrow_schema));
176-
auto format = options_.GetWriteFileFormat();
176+
auto format = options_.GetFileFormat();
177177
PAIMON_ASSIGN_OR_RAISE(
178178
std::shared_ptr<WriterBuilder> writer_builder,
179179
format->CreateWriterBuilder(&arrow_schema, options_.GetWriteBatchSize()));

src/paimon/core/core_options.cpp

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ class ConfigParser {
123123
std::string normalized_value = StringUtils::ToLowerCase(iter->second);
124124
PAIMON_ASSIGN_OR_RAISE(*value, Factory::Get(normalized_value, config_map_));
125125
} else {
126-
PAIMON_ASSIGN_OR_RAISE(*value, Factory::Get(default_identifier, config_map_));
126+
PAIMON_ASSIGN_OR_RAISE(
127+
*value, Factory::Get(StringUtils::ToLowerCase(default_identifier), config_map_));
127128
}
128129
return Status::OK();
129130
}
@@ -247,6 +248,37 @@ class ConfigParser {
247248
return Status::OK();
248249
}
249250

251+
// parse file.format.per.level
252+
Status ParseFileFormatPerLevel(
253+
std::map<int32_t, std::shared_ptr<FileFormat>>* file_format_per_level_ptr) const {
254+
auto& file_format_per_level = *file_format_per_level_ptr;
255+
std::string file_format_per_level_str;
256+
PAIMON_RETURN_NOT_OK(
257+
ParseString(Options::FILE_FORMAT_PER_LEVEL, &file_format_per_level_str));
258+
if (!file_format_per_level_str.empty()) {
259+
auto level2format =
260+
StringUtils::Split(file_format_per_level_str, std::string(","), std::string(":"));
261+
for (const auto& single_level : level2format) {
262+
if (single_level.size() != 2) {
263+
return Status::Invalid(fmt::format(
264+
"fail to parse key {}, value {} (usage example: 0:avro,3:parquet)",
265+
Options::FILE_FORMAT_PER_LEVEL, file_format_per_level_str));
266+
}
267+
auto level = StringUtils::StringToValue<int32_t>(single_level[0]);
268+
if (!level || level.value() < 0) {
269+
return Status::Invalid(
270+
fmt::format("fail to parse level {} from string to int in {}",
271+
single_level[0], Options::FILE_FORMAT_PER_LEVEL));
272+
}
273+
std::shared_ptr<FileFormat> file_format;
274+
PAIMON_RETURN_NOT_OK(ParseObject<FileFormatFactory>(
275+
"_no_use", /*default_identifier=*/single_level[1], &file_format));
276+
file_format_per_level[level.value()] = file_format;
277+
}
278+
}
279+
return Status::OK();
280+
}
281+
250282
bool ContainsKey(const std::string& key) const {
251283
return config_map_.find(key) != config_map_.end();
252284
}
@@ -336,6 +368,7 @@ struct CoreOptions::Impl {
336368
double lookup_cache_bloom_filter_fpp = 0.05;
337369
CompressOptions lookup_compress_options{"zstd", 1};
338370
int64_t cache_page_size = 64 * 1024; // 64KB
371+
std::map<int32_t, std::shared_ptr<FileFormat>> file_format_per_level;
339372
};
340373

341374
// Parse configurations from a map and return a populated CoreOptions object
@@ -596,6 +629,8 @@ Result<CoreOptions> CoreOptions::FromMap(
596629
// Parse cache-page-size
597630
PAIMON_RETURN_NOT_OK(parser.ParseMemorySize(Options::CACHE_PAGE_SIZE, &impl->cache_page_size));
598631

632+
// parse file.format.per.level
633+
PAIMON_RETURN_NOT_OK(parser.ParseFileFormatPerLevel(&impl->file_format_per_level));
599634
return options;
600635
}
601636

@@ -617,7 +652,15 @@ int32_t CoreOptions::GetBucket() const {
617652
return impl_->bucket;
618653
}
619654

620-
std::shared_ptr<FileFormat> CoreOptions::GetWriteFileFormat() const {
655+
std::shared_ptr<FileFormat> CoreOptions::GetWriteFileFormat(int32_t level) const {
656+
auto iter = impl_->file_format_per_level.find(level);
657+
if (iter != impl_->file_format_per_level.end()) {
658+
return iter->second;
659+
}
660+
return impl_->file_format;
661+
}
662+
663+
std::shared_ptr<FileFormat> CoreOptions::GetFileFormat() const {
621664
return impl_->file_format;
622665
}
623666

@@ -807,9 +850,13 @@ const std::map<std::string, std::string>& CoreOptions::ToMap() const {
807850
}
808851

809852
bool CoreOptions::NeedLookup() const {
810-
return GetMergeEngine() == MergeEngine::FIRST_ROW ||
811-
GetChangelogProducer() == ChangelogProducer::LOOKUP || DeletionVectorsEnabled() ||
812-
impl_->force_lookup;
853+
return GetLookupStrategy().need_lookup;
854+
}
855+
856+
LookupStrategy CoreOptions::GetLookupStrategy() const {
857+
return {GetMergeEngine() == MergeEngine::FIRST_ROW,
858+
GetChangelogProducer() == ChangelogProducer::LOOKUP, DeletionVectorsEnabled(),
859+
impl_->force_lookup};
813860
}
814861

815862
bool CoreOptions::CompactionForceRewriteAllFiles() const {
@@ -966,4 +1013,5 @@ const CompressOptions& CoreOptions::GetLookupCompressOptions() const {
9661013
int32_t CoreOptions::GetCachePageSize() const {
9671014
return static_cast<int32_t>(impl_->cache_page_size);
9681015
}
1016+
9691017
} // namespace paimon

src/paimon/core/core_options.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "paimon/core/options/changelog_producer.h"
2828
#include "paimon/core/options/compress_options.h"
2929
#include "paimon/core/options/external_path_strategy.h"
30+
#include "paimon/core/options/lookup_strategy.h"
3031
#include "paimon/core/options/merge_engine.h"
3132
#include "paimon/core/options/sort_engine.h"
3233
#include "paimon/format/file_format.h"
@@ -53,7 +54,8 @@ class PAIMON_EXPORT CoreOptions {
5354
~CoreOptions();
5455

5556
int32_t GetBucket() const;
56-
std::shared_ptr<FileFormat> GetWriteFileFormat() const;
57+
std::shared_ptr<FileFormat> GetFileFormat() const;
58+
std::shared_ptr<FileFormat> GetWriteFileFormat(int32_t level) const;
5759
std::shared_ptr<FileSystem> GetFileSystem() const;
5860
const std::string& GetFileCompression() const;
5961
int32_t GetFileCompressionZstdLevel() const;
@@ -101,6 +103,7 @@ class PAIMON_EXPORT CoreOptions {
101103
int64_t DeletionVectorTargetFileSize() const;
102104
ChangelogProducer GetChangelogProducer() const;
103105
bool NeedLookup() const;
106+
LookupStrategy GetLookupStrategy() const;
104107
bool FileIndexReadEnabled() const;
105108

106109
std::map<std::string, std::string> GetFieldsSequenceGroups() const;
@@ -150,7 +153,6 @@ class PAIMON_EXPORT CoreOptions {
150153

151154
private:
152155
struct Impl;
153-
154156
std::unique_ptr<Impl> impl_;
155157
};
156158

src/paimon/core/core_options_test.cpp

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ namespace paimon::test {
3333
TEST(CoreOptionsTest, TestDefaultValue) {
3434
ASSERT_OK_AND_ASSIGN(CoreOptions core_options, CoreOptions::FromMap({}));
3535
ASSERT_EQ(core_options.GetManifestFormat()->Identifier(), "avro");
36-
ASSERT_EQ(core_options.GetWriteFileFormat()->Identifier(), "parquet");
36+
ASSERT_EQ(core_options.GetFileFormat()->Identifier(), "parquet");
37+
ASSERT_EQ(core_options.GetWriteFileFormat(0)->Identifier(), "parquet");
38+
ASSERT_EQ(core_options.GetWriteFileFormat(3)->Identifier(), "parquet");
3739
ASSERT_TRUE(core_options.GetFileSystem());
3840
ASSERT_EQ(-1, core_options.GetBucket());
3941
ASSERT_EQ(64 * 1024L, core_options.GetPageSize());
@@ -82,6 +84,10 @@ TEST(CoreOptionsTest, TestDefaultValue) {
8284
ASSERT_EQ(2 * 1024 * 1024, core_options.DeletionVectorTargetFileSize());
8385
ASSERT_EQ(ChangelogProducer::NONE, core_options.GetChangelogProducer());
8486
ASSERT_FALSE(core_options.NeedLookup());
87+
LookupStrategy expected_lookup_strategy = {/*is_first_row=*/false,
88+
/*produce_changelog=*/false,
89+
/*deletion_vector=*/false, /*force_lookup=*/false};
90+
ASSERT_EQ(expected_lookup_strategy, core_options.GetLookupStrategy());
8591
ASSERT_TRUE(core_options.GetFieldsSequenceGroups().empty());
8692
ASSERT_FALSE(core_options.PartialUpdateRemoveRecordOnDelete());
8793
ASSERT_TRUE(core_options.GetPartialUpdateRemoveRecordOnSequenceGroup().empty());
@@ -185,14 +191,17 @@ TEST(CoreOptionsTest, TestFromMap) {
185191
{Options::LOOKUP_CACHE_BLOOM_FILTER_FPP, "0.5"},
186192
{Options::LOOKUP_CACHE_SPILL_COMPRESSION, "lz4"},
187193
{Options::SPILL_COMPRESSION_ZSTD_LEVEL, "2"},
188-
{Options::CACHE_PAGE_SIZE, "6MB"}};
194+
{Options::CACHE_PAGE_SIZE, "6MB"},
195+
{Options::FILE_FORMAT_PER_LEVEL, "0:AVRO,3:parquet"}};
189196

190197
ASSERT_OK_AND_ASSIGN(CoreOptions core_options, CoreOptions::FromMap(options));
191198
auto fs = core_options.GetFileSystem();
192199
ASSERT_TRUE(fs);
193200

194-
auto format = core_options.GetWriteFileFormat();
195-
ASSERT_EQ(format->Identifier(), "orc");
201+
ASSERT_EQ(core_options.GetFileFormat()->Identifier(), "orc");
202+
ASSERT_EQ(core_options.GetWriteFileFormat(0)->Identifier(), "avro");
203+
ASSERT_EQ(core_options.GetWriteFileFormat(1)->Identifier(), "orc");
204+
ASSERT_EQ(core_options.GetWriteFileFormat(3)->Identifier(), "parquet");
196205

197206
auto manifest_format = core_options.GetManifestFormat();
198207
ASSERT_EQ(manifest_format->Identifier(), "avro");
@@ -235,6 +244,11 @@ TEST(CoreOptionsTest, TestFromMap) {
235244
ASSERT_EQ(4 * 1024 * 1024, core_options.DeletionVectorTargetFileSize());
236245
ASSERT_EQ(ChangelogProducer::FULL_COMPACTION, core_options.GetChangelogProducer());
237246
ASSERT_TRUE(core_options.NeedLookup());
247+
LookupStrategy expected_lookup_strategy = {/*is_first_row=*/false,
248+
/*produce_changelog=*/false,
249+
/*deletion_vector=*/true, /*force_lookup=*/true};
250+
ASSERT_EQ(expected_lookup_strategy, core_options.GetLookupStrategy());
251+
238252
std::map<std::string, std::string> seq_grp;
239253
seq_grp["g_1,g_3"] = "c,d";
240254
ASSERT_EQ(core_options.GetFieldsSequenceGroups(), seq_grp);
@@ -294,6 +308,13 @@ TEST(CoreOptionsTest, TestInvalidCase) {
294308
"invalid changelog producer: invalid");
295309
}
296310

311+
TEST(CoreOptionsTest, TestInvalidFileFormatPerLevel) {
312+
ASSERT_NOK_WITH_MSG(CoreOptions::FromMap({{Options::FILE_FORMAT_PER_LEVEL, "0:AVRO:parquet"}}),
313+
"fail to parse key file.format.per.level, value 0:AVRO:parquet");
314+
ASSERT_NOK_WITH_MSG(CoreOptions::FromMap({{Options::FILE_FORMAT_PER_LEVEL, "aaa:avro"}}),
315+
"fail to parse level aaa from string to int in file.format.per.level");
316+
}
317+
297318
TEST(CoreOptionsTest, TestCreateExternalPath) {
298319
std::map<std::string, std::string> options = {
299320
{Options::DATA_FILE_EXTERNAL_PATHS,

src/paimon/core/global_index/global_index_scan_impl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ Status GlobalIndexScanImpl::Scan() {
110110
path_factory_,
111111
FileStorePathFactory::Create(
112112
root_path_, arrow_schema, table_schema_->PartitionKeys(),
113-
options_.GetPartitionDefaultName(), options_.GetWriteFileFormat()->Identifier(),
113+
options_.GetPartitionDefaultName(), options_.GetFileFormat()->Identifier(),
114114
options_.DataFilePrefix(), options_.LegacyPartitionNameEnabled(), external_paths,
115115
global_index_external_path, options_.IndexFileInDataFileDir(), pool_));
116116

src/paimon/core/global_index/global_index_write_task.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Result<std::shared_ptr<GlobalIndexFileManager>> CreateGlobalIndexFileManager(
4545
std::shared_ptr<FileStorePathFactory> path_factory,
4646
FileStorePathFactory::Create(
4747
table_path, all_arrow_schema, table_schema->PartitionKeys(),
48-
core_options.GetPartitionDefaultName(), core_options.GetWriteFileFormat()->Identifier(),
48+
core_options.GetPartitionDefaultName(), core_options.GetFileFormat()->Identifier(),
4949
core_options.DataFilePrefix(), core_options.LegacyPartitionNameEnabled(),
5050
external_paths, global_index_external_path, core_options.IndexFileInDataFileDir(),
5151
pool));

0 commit comments

Comments
 (0)