Skip to content

Commit da29897

Browse files
子懿claude
andcommitted
feat(blob): support blob.split-by-file-size to weigh blob files in scan splitting
Introduce the 'blob.split-by-file-size' option (no default) to control whether blob file size counts in scan split weights. When unset it derives from the negation of 'blob-as-descriptor', so descriptor reads weigh a blob file only by the open file cost instead of letting huge blob files shatter the scan into tiny splits. DataEvolutionSplitGenerator gains a count_blob_size flag wired from CoreOptions::BlobSplitByFileSize() in the data-evolution scan path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent f24a61b commit da29897

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. Default is 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
@@ -443,6 +443,8 @@ struct CoreOptions::Impl {
443443
bool row_tracking_partition_group_on_commit = true;
444444
bool data_evolution_enabled = false;
445445
bool blob_view_resolve_enabled = true;
446+
bool blob_as_descriptor = false;
447+
std::optional<bool> blob_split_by_file_size;
446448
bool legacy_partition_name_enabled = true;
447449
bool global_index_enabled = true;
448450
std::optional<int32_t> global_index_thread_num;
@@ -567,6 +569,11 @@ struct CoreOptions::Impl {
567569
// Parse blob-view.resolve.enabled - whether to resolve blob view fields at read time
568570
PAIMON_RETURN_NOT_OK(
569571
parser.Parse<bool>(Options::BLOB_VIEW_RESOLVE_ENABLED, &blob_view_resolve_enabled));
572+
// Parse blob-as-descriptor - read blob field as descriptor rather than blob bytes
573+
PAIMON_RETURN_NOT_OK(parser.Parse<bool>(Options::BLOB_AS_DESCRIPTOR, &blob_as_descriptor));
574+
// Parse blob.split-by-file-size - whether blob file size counts in scan splitting
575+
PAIMON_RETURN_NOT_OK(
576+
parser.Parse(Options::BLOB_SPLIT_BY_FILE_SIZE, &blob_split_by_file_size));
570577
return Status::OK();
571578
}
572579

@@ -944,6 +951,10 @@ int64_t CoreOptions::GetBlobTargetFileSize() const {
944951
return impl_->blob_target_file_size.value();
945952
}
946953

954+
bool CoreOptions::BlobSplitByFileSize() const {
955+
return impl_->blob_split_by_file_size.value_or(!impl_->blob_as_descriptor);
956+
}
957+
947958
int64_t CoreOptions::GetCompactionFileSize(bool has_primary_key) const {
948959
// file size to join the compaction, we don't process on middle file size to avoid
949960
// 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
@@ -68,6 +68,7 @@ class PAIMON_EXPORT CoreOptions {
6868
int64_t GetPageSize() const;
6969
int64_t GetTargetFileSize(bool has_primary_key) const;
7070
int64_t GetBlobTargetFileSize() const;
71+
bool BlobSplitByFileSize() const;
7172
int64_t GetCompactionFileSize(bool has_primary_key) const;
7273
std::string GetPartitionDefaultName() const;
7374

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

@@ -227,6 +228,7 @@ TEST(CoreOptionsTest, TestFromMap) {
227228
{Options::DATA_EVOLUTION_ENABLED, "true"},
228229
{Options::BLOB_FIELD, "blob1,blob2"},
229230
{Options::BLOB_DESCRIPTOR_FIELD, "blob3,blob4"},
231+
{Options::BLOB_AS_DESCRIPTOR, "true"},
230232
{Options::BLOB_VIEW_FIELD, "blob5"},
231233
{Options::BLOB_VIEW_UPSTREAM_WAREHOUSE, "FILE:///tmp/blob_view_upstream_warehouse/"},
232234
{Options::BLOB_VIEW_RESOLVE_ENABLED, "false"},
@@ -365,6 +367,7 @@ TEST(CoreOptionsTest, TestFromMap) {
365367
ASSERT_TRUE(core_options.DataEvolutionEnabled());
366368
ASSERT_EQ(core_options.GetBlobFields(), std::vector<std::string>({"blob1", "blob2"}));
367369
ASSERT_EQ(core_options.GetBlobDescriptorFields(), std::vector<std::string>({"blob3", "blob4"}));
370+
ASSERT_FALSE(core_options.BlobSplitByFileSize());
368371
ASSERT_EQ(core_options.GetBlobViewFields(), std::vector<std::string>({"blob5"}));
369372
ASSERT_EQ(core_options.GetBlobInlineFields(),
370373
std::vector<std::string>({"blob3", "blob4", "blob5"}));
@@ -923,6 +926,12 @@ TEST(CoreOptionsTest, TestFallback) {
923926
ASSERT_EQ(options.GetBlobDescriptorFields(),
924927
std::vector<std::string>({"new_b1", "new_b2"}));
925928
}
929+
{
930+
ASSERT_OK_AND_ASSIGN(CoreOptions options,
931+
CoreOptions::FromMap({{Options::BLOB_AS_DESCRIPTOR, "true"},
932+
{Options::BLOB_SPLIT_BY_FILE_SIZE, "true"}}));
933+
ASSERT_TRUE(options.BlobSplitByFileSize());
934+
}
926935
}
927936

928937
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
@@ -134,8 +134,9 @@ class TableScanImpl {
134134
auto source_split_open_file_cost = core_options.GetSourceSplitOpenFileCost();
135135
if (table_schema->PrimaryKeys().empty()) {
136136
if (core_options.DataEvolutionEnabled()) {
137-
return std::make_unique<DataEvolutionSplitGenerator>(source_split_target_size,
138-
source_split_open_file_cost);
137+
return std::make_unique<DataEvolutionSplitGenerator>(
138+
source_split_target_size, source_split_open_file_cost,
139+
core_options.BlobSplitByFileSize());
139140
}
140141
BucketMode bucket_mode = (core_options.GetBucket() == -1 ? BucketMode::BUCKET_UNAWARE
141142
: BucketMode::HASH_FIXED);

0 commit comments

Comments
 (0)