Skip to content

Commit 48ad287

Browse files
子懿claude
andcommitted
feat(blob): support blob.split-by-file-size to weigh blob files in scan splitting
Align scan split weighting with CoreOptions.BLOB_SPLIT_BY_FILE_SIZE in Java Paimon: - Add the 'blob.split-by-file-size' option (no default value) to control whether blob file size is considered as a factor when performing scan splitting, and parse it into CoreOptions together with 'blob-as-descriptor'. - Expose CoreOptions::BlobSplitByFileSize(), which returns the explicit value or falls back to the negation of 'blob-as-descriptor': content reads count blob file sizes, while descriptor reads, which materialize only small descriptors instead of blob payloads, should not let huge blob files dominate split weights and shatter the scan into tiny splits. - Add a count_blob_size flag to DataEvolutionSplitGenerator: the split weight function counts a blob file as its file_size when enabled, otherwise as the open file cost; non-blob files always count as file_size, and each row-id range still weighs at least the open file cost. - Pass CoreOptions::BlobSplitByFileSize() when constructing DataEvolutionSplitGenerator in the data-evolution scan path. - Tests: CoreOptionsTest covers the default, the value derived from 'blob-as-descriptor', and explicit-value precedence; SplitGeneratorTest verifies that the same data/blob file layout packs into different splits depending on the flag. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent f24a61b commit 48ad287

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)