Skip to content

Commit b1ffcb0

Browse files
authored
feat(blob): support blob.split-by-file-size to weigh blob files in scan splitting (alibaba#417)
1 parent ee3fbc0 commit b1ffcb0

9 files changed

Lines changed: 93 additions & 6 deletions

File tree

include/paimon/defs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ struct PAIMON_EXPORT Options {
118118
/// "blob.target-file-size" - Target size of a blob file. Default is TARGET_FILE_SIZE.
119119
static const char BLOB_TARGET_FILE_SIZE[];
120120

121+
/// "blob.split-by-file-size" - Whether to consider blob file size as a factor when performing
122+
/// scan splitting. When unset, defaults to the negation of BLOB_AS_DESCRIPTOR.
123+
static const char BLOB_SPLIT_BY_FILE_SIZE[];
124+
121125
/// "partition.default-name" - The default partition name in case the dynamic partition column
122126
/// value is null/empty string. Default is "__DEFAULT_PARTITION__".
123127
static const char PARTITION_DEFAULT_NAME[];

src/paimon/common/defs.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const char Options::FILE_FORMAT[] = "file.format";
3333
const char Options::FILE_SYSTEM[] = "file-system";
3434
const char Options::TARGET_FILE_SIZE[] = "target-file-size";
3535
const char Options::BLOB_TARGET_FILE_SIZE[] = "blob.target-file-size";
36+
const char Options::BLOB_SPLIT_BY_FILE_SIZE[] = "blob.split-by-file-size";
3637
const char Options::PAGE_SIZE[] = "page-size";
3738
const char Options::PARTITION_DEFAULT_NAME[] = "partition.default-name";
3839
const char Options::FILE_COMPRESSION[] = "file.compression";

src/paimon/core/core_options.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,8 @@ struct CoreOptions::Impl {
448448
bool row_tracking_partition_group_on_commit = true;
449449
bool data_evolution_enabled = false;
450450
bool blob_view_resolve_enabled = true;
451+
bool blob_as_descriptor = false;
452+
std::optional<bool> blob_split_by_file_size;
451453
bool legacy_partition_name_enabled = true;
452454
bool global_index_enabled = true;
453455
std::optional<int32_t> global_index_thread_num;
@@ -578,6 +580,11 @@ struct CoreOptions::Impl {
578580
// Parse blob-view.resolve.enabled - whether to resolve blob view fields at read time
579581
PAIMON_RETURN_NOT_OK(
580582
parser.Parse<bool>(Options::BLOB_VIEW_RESOLVE_ENABLED, &blob_view_resolve_enabled));
583+
// Parse blob-as-descriptor - read blob field as descriptor rather than blob bytes
584+
PAIMON_RETURN_NOT_OK(parser.Parse<bool>(Options::BLOB_AS_DESCRIPTOR, &blob_as_descriptor));
585+
// Parse blob.split-by-file-size - whether blob file size counts in scan splitting
586+
PAIMON_RETURN_NOT_OK(
587+
parser.Parse(Options::BLOB_SPLIT_BY_FILE_SIZE, &blob_split_by_file_size));
581588
return Status::OK();
582589
}
583590

@@ -984,6 +991,10 @@ int64_t CoreOptions::GetBlobTargetFileSize() const {
984991
return impl_->blob_target_file_size.value();
985992
}
986993

994+
bool CoreOptions::BlobSplitByFileSize() const {
995+
return impl_->blob_split_by_file_size.value_or(!impl_->blob_as_descriptor);
996+
}
997+
987998
int64_t CoreOptions::GetCompactionFileSize(bool has_primary_key) const {
988999
// file size to join the compaction, we don't process on middle file size to avoid
9891000
// compact a same file twice (the compression is not calculate so accurately. the output

src/paimon/core/core_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class PAIMON_EXPORT CoreOptions {
7777
int64_t GetPageSize() const;
7878
int64_t GetTargetFileSize(bool has_primary_key) const;
7979
int64_t GetBlobTargetFileSize() const;
80+
bool BlobSplitByFileSize() const;
8081
int64_t GetCompactionFileSize(bool has_primary_key) const;
8182
std::string GetPartitionDefaultName() const;
8283

src/paimon/core/core_options_test.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ TEST(CoreOptionsTest, TestDefaultValue) {
4141
ASSERT_EQ(256 * 1024 * 1024L, core_options.GetTargetFileSize(/*has_primary_key=*/false));
4242
ASSERT_EQ(128 * 1024 * 1024L, core_options.GetTargetFileSize(/*has_primary_key=*/true));
4343
ASSERT_EQ(256 * 1024 * 1024L, core_options.GetBlobTargetFileSize());
44+
ASSERT_TRUE(core_options.BlobSplitByFileSize());
4445
ASSERT_EQ(187904815, core_options.GetCompactionFileSize(/*has_primary_key=*/false));
4546
ASSERT_EQ(93952404, core_options.GetCompactionFileSize(/*has_primary_key=*/true));
4647

@@ -240,6 +241,7 @@ TEST(CoreOptionsTest, TestFromMap) {
240241
{Options::DATA_EVOLUTION_ENABLED, "true"},
241242
{Options::BLOB_FIELD, "blob1,blob2"},
242243
{Options::BLOB_DESCRIPTOR_FIELD, "blob3,blob4"},
244+
{Options::BLOB_AS_DESCRIPTOR, "true"},
243245
{Options::BLOB_VIEW_FIELD, "blob5"},
244246
{Options::BLOB_VIEW_UPSTREAM_WAREHOUSE, "FILE:///tmp/blob_view_upstream_warehouse/"},
245247
{Options::BLOB_VIEW_RESOLVE_ENABLED, "false"},
@@ -385,6 +387,7 @@ TEST(CoreOptionsTest, TestFromMap) {
385387
ASSERT_TRUE(core_options.DataEvolutionEnabled());
386388
ASSERT_EQ(core_options.GetBlobFields(), std::vector<std::string>({"blob1", "blob2"}));
387389
ASSERT_EQ(core_options.GetBlobDescriptorFields(), std::vector<std::string>({"blob3", "blob4"}));
390+
ASSERT_FALSE(core_options.BlobSplitByFileSize());
388391
ASSERT_EQ(core_options.GetBlobViewFields(), std::vector<std::string>({"blob5"}));
389392
ASSERT_EQ(core_options.GetBlobInlineFields(),
390393
std::vector<std::string>({"blob3", "blob4", "blob5"}));
@@ -970,6 +973,12 @@ TEST(CoreOptionsTest, TestFallback) {
970973
ASSERT_EQ(options.GetBlobDescriptorFields(),
971974
std::vector<std::string>({"new_b1", "new_b2"}));
972975
}
976+
{
977+
ASSERT_OK_AND_ASSIGN(CoreOptions options,
978+
CoreOptions::FromMap({{Options::BLOB_AS_DESCRIPTOR, "true"},
979+
{Options::BLOB_SPLIT_BY_FILE_SIZE, "true"}}));
980+
ASSERT_TRUE(options.BlobSplitByFileSize());
981+
}
973982
}
974983

975984
TEST(CoreOptionsTest, TestMapStorageLayout) {

src/paimon/core/table/source/data_evolution_split_generator.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <functional>
2222
#include <iterator>
2323

24+
#include "paimon/common/data/blob_utils.h"
2425
#include "paimon/common/utils/bin_packing.h"
2526
#include "paimon/common/utils/range_helper.h"
2627
#include "paimon/core/io/data_file_meta.h"
@@ -40,11 +41,15 @@ Result<std::vector<SplitGenerator::SplitGroup>> DataEvolutionSplitGenerator::Spl
4041
PAIMON_ASSIGN_OR_RAISE(std::vector<std::vector<std::shared_ptr<DataFileMeta>>> ranges,
4142
range_helper.MergeOverlappingRanges(std::move(input)));
4243

43-
auto weight_func = [open_file_cost = open_file_cost_](
44+
auto weight_func = [open_file_cost = open_file_cost_, count_blob_size = count_blob_size_](
4445
const std::vector<std::shared_ptr<DataFileMeta>>& metas) -> int64_t {
4546
int64_t file_size_sum = 0;
4647
for (const auto& meta : metas) {
47-
file_size_sum += meta->file_size;
48+
if (BlobUtils::IsBlobFile(meta->file_name)) {
49+
file_size_sum += count_blob_size ? meta->file_size : open_file_cost;
50+
} else {
51+
file_size_sum += meta->file_size;
52+
}
4853
}
4954
return std::max(file_size_sum, open_file_cost);
5055
};

src/paimon/core/table/source/data_evolution_split_generator.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ struct DataFileMeta;
3030
/// Append data evolution table split generator, which implementation of `SplitGenerator`.
3131
class DataEvolutionSplitGenerator : public SplitGenerator {
3232
public:
33-
DataEvolutionSplitGenerator(int64_t target_split_size, int64_t open_file_cost)
34-
: target_split_size_(target_split_size), open_file_cost_(open_file_cost) {}
33+
DataEvolutionSplitGenerator(int64_t target_split_size, int64_t open_file_cost,
34+
bool count_blob_size)
35+
: target_split_size_(target_split_size),
36+
open_file_cost_(open_file_cost),
37+
count_blob_size_(count_blob_size) {}
3538

3639
Result<std::vector<SplitGroup>> SplitForBatch(
3740
std::vector<std::shared_ptr<DataFileMeta>>&& input) const override;
@@ -44,6 +47,7 @@ class DataEvolutionSplitGenerator : public SplitGenerator {
4447
private:
4548
int64_t target_split_size_;
4649
int64_t open_file_cost_;
50+
bool count_blob_size_;
4751
};
4852

4953
} // namespace paimon

src/paimon/core/table/source/split_generator_test.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "paimon/core/stats/simple_stats.h"
3737
#include "paimon/core/table/bucket_mode.h"
3838
#include "paimon/core/table/source/append_only_split_generator.h"
39+
#include "paimon/core/table/source/data_evolution_split_generator.h"
3940
#include "paimon/core/table/source/merge_tree_split_generator.h"
4041
#include "paimon/data/timestamp.h"
4142
#include "paimon/memory/memory_pool.h"
@@ -116,6 +117,21 @@ class SplitGeneratorTest : public testing::Test {
116117
/*write_cols=*/std::nullopt);
117118
}
118119

120+
std::shared_ptr<DataFileMeta> CreateDataFileMetaWithRowId(const std::string& file_name,
121+
int64_t file_size, int64_t row_count,
122+
int64_t first_row_id) {
123+
return std::make_shared<DataFileMeta>(
124+
file_name, file_size, row_count, /*min_key=*/BinaryRow::EmptyRow(),
125+
/*max_key=*/BinaryRow::EmptyRow(), /*key_stats=*/SimpleStats::EmptyStats(),
126+
/*value_stats=*/SimpleStats::EmptyStats(), /*min_sequence_number=*/0,
127+
/*max_sequence_number=*/0, /*schema_id=*/0,
128+
/*level=*/0, /*extra_files=*/std::vector<std::optional<std::string>>(),
129+
/*creation_time=*/Timestamp(0, 0), /*delete_row_count=*/0,
130+
/*embedded_index=*/nullptr, FileSource::Append(), /*value_stats_cols=*/std::nullopt,
131+
/*external_path=*/std::nullopt, first_row_id,
132+
/*write_cols=*/std::nullopt);
133+
}
134+
119135
static void CheckResult(const std::vector<SplitGenerator::SplitGroup>& result_groups,
120136
const std::vector<std::vector<std::string>>& expected_file_names,
121137
const std::vector<bool>& expected_raw_convertible) {
@@ -206,6 +222,41 @@ TEST_F(SplitGeneratorTest, TestAppend) {
206222
}
207223
}
208224

225+
TEST_F(SplitGeneratorTest, TestDataEvolutionBlobSplitByFileSize) {
226+
// two row id ranges, each with one data file and one much larger blob file
227+
auto create_files = [&]() {
228+
return std::vector<std::shared_ptr<DataFileMeta>>{
229+
CreateDataFileMetaWithRowId("f1", /*file_size=*/100, /*row_count=*/100,
230+
/*first_row_id=*/0),
231+
CreateDataFileMetaWithRowId("blob1.blob", /*file_size=*/1000, /*row_count=*/100,
232+
/*first_row_id=*/0),
233+
CreateDataFileMetaWithRowId("f2", /*file_size=*/100, /*row_count=*/100,
234+
/*first_row_id=*/100),
235+
CreateDataFileMetaWithRowId("blob2.blob", /*file_size=*/1000, /*row_count=*/100,
236+
/*first_row_id=*/100)};
237+
};
238+
{
239+
// blob file size counts in splitting: each range weighs 1100, so the two ranges cannot
240+
// be packed into one split of target size 1200
241+
DataEvolutionSplitGenerator split_generator(/*target_split_size=*/1200,
242+
/*open_file_cost=*/10,
243+
/*count_blob_size=*/true);
244+
ASSERT_OK_AND_ASSIGN(std::vector<SplitGenerator::SplitGroup> split_groups,
245+
split_generator.SplitForBatch(create_files()));
246+
CheckResult(split_groups, {{"f1", "blob1.blob"}, {"f2", "blob2.blob"}}, {false, false});
247+
}
248+
{
249+
// blob file only weighs the open file cost: each range weighs 110, both ranges fit in
250+
// one split of target size 1200
251+
DataEvolutionSplitGenerator split_generator(/*target_split_size=*/1200,
252+
/*open_file_cost=*/10,
253+
/*count_blob_size=*/false);
254+
ASSERT_OK_AND_ASSIGN(std::vector<SplitGenerator::SplitGroup> split_groups,
255+
split_generator.SplitForBatch(create_files()));
256+
CheckResult(split_groups, {{"f1", "blob1.blob", "f2", "blob2.blob"}}, {false});
257+
}
258+
}
259+
209260
TEST_F(SplitGeneratorTest, TestMergeTree) {
210261
std::vector<std::shared_ptr<DataFileMeta>> files = {
211262
CreateDataFileMeta("1", 0, 10), CreateDataFileMeta("2", 0, 12),

src/paimon/core/table/source/table_scan.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,9 @@ class TableScanImpl {
136136
auto source_split_open_file_cost = core_options.GetSourceSplitOpenFileCost();
137137
if (table_schema->PrimaryKeys().empty()) {
138138
if (core_options.DataEvolutionEnabled()) {
139-
return std::make_unique<DataEvolutionSplitGenerator>(source_split_target_size,
140-
source_split_open_file_cost);
139+
return std::make_unique<DataEvolutionSplitGenerator>(
140+
source_split_target_size, source_split_open_file_cost,
141+
core_options.BlobSplitByFileSize());
141142
}
142143
BucketMode bucket_mode = (core_options.GetBucket() == -1 ? BucketMode::BUCKET_UNAWARE
143144
: BucketMode::HASH_FIXED);

0 commit comments

Comments
 (0)