Skip to content

Commit e5ec8a0

Browse files
liujiayi771codexclaude
committed
feat(commit): support dropping delete file stats
Honor manifest.delete-file-drop-stats during commit scans and manifest entry creation while preserving statistics for ADD entries and writer restore. Co-Authored-By: Codex <noreply@openai.com> AI-Model: gpt-5 Co-Authored-By: Claude Code <noreply@anthropic.com> AI-Contributed/Feature: 120/124 AI-Contributed/UT: 267/321
1 parent 3663237 commit e5ec8a0

25 files changed

Lines changed: 409 additions & 36 deletions

include/paimon/defs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ struct PAIMON_EXPORT Options {
173173
/// compaction of manifest, default value is 16MB.
174174
static const char MANIFEST_FULL_COMPACTION_FILE_SIZE[];
175175

176+
/// "manifest.delete-file-drop-stats" - Whether final DELETE manifest entries should omit
177+
/// value statistics. Default is false.
178+
static const char MANIFEST_DELETE_FILE_DROP_STATS[];
179+
176180
/// "source.split.target-size" - Target size of a source split when scanning a bucket. Default
177181
/// value is 128MB.
178182
static const char SOURCE_SPLIT_TARGET_SIZE[];

src/paimon/common/defs.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const char Options::MANIFEST_COMPRESSION[] = "manifest.compression";
5252
const char Options::MANIFEST_MERGE_MIN_COUNT[] = "manifest.merge-min-count";
5353
const char Options::MANIFEST_FULL_COMPACTION_FILE_SIZE[] =
5454
"manifest.full-compaction-threshold-size";
55+
const char Options::MANIFEST_DELETE_FILE_DROP_STATS[] = "manifest.delete-file-drop-stats";
5556
const char Options::SOURCE_SPLIT_TARGET_SIZE[] = "source.split.target-size";
5657
const char Options::SOURCE_SPLIT_OPEN_FILE_COST[] = "source.split.open-file-cost";
5758
const char Options::SCAN_SNAPSHOT_ID[] = "scan.snapshot-id";

src/paimon/core/append/append_compact_coordinator.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ Result<LinkedHashMap<BinaryRow, std::vector<std::shared_ptr<DataFileMeta>>>> Sca
244244
CreateFileStoreScan(snapshot_manager, schema_manager, table_schema,
245245
arrow_schema, partition_schema, core_options,
246246
path_factory, scan_filter, executor, pool));
247+
if (core_options.ManifestDeleteFileDropStats()) {
248+
scan->EnableDropStats();
249+
}
247250

248251
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<FileStoreScan::RawPlan> plan, scan->CreatePlan());
249252
std::vector<ManifestEntry> add_entries = plan->Files(FileKind::Add());

src/paimon/core/append/append_compact_coordinator_test.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ class AppendCompactCoordinatorTest : public ::testing::Test {
141141
}
142142

143143
void CheckCommitMessage(const std::shared_ptr<CommitMessage>& msg, size_t expected_before_files,
144-
int64_t expected_total_rows) {
144+
int64_t expected_total_rows, bool expect_dropped_stats = false) {
145145
auto impl = dynamic_cast<CommitMessageImpl*>(msg.get());
146146
ASSERT_TRUE(impl);
147147
ASSERT_EQ(impl->Bucket(), 0);
@@ -154,9 +154,17 @@ class AppendCompactCoordinatorTest : public ::testing::Test {
154154
int64_t total_before_rows = 0;
155155
for (const auto& file : compact_before) {
156156
total_before_rows += file->row_count;
157+
if (expect_dropped_stats) {
158+
ASSERT_EQ(SimpleStats::EmptyStats(), file->value_stats);
159+
ASSERT_TRUE(file->value_stats_cols.has_value());
160+
ASSERT_TRUE(file->value_stats_cols->empty());
161+
}
157162
}
158163
ASSERT_EQ(total_before_rows, expected_total_rows);
159164
ASSERT_EQ(compact_after[0]->row_count, expected_total_rows);
165+
if (expect_dropped_stats) {
166+
ASSERT_FALSE(compact_after[0]->value_stats == SimpleStats::EmptyStats());
167+
}
160168
}
161169

162170
private:
@@ -179,6 +187,7 @@ TEST_F(AppendCompactCoordinatorTest, TestRunCompactsAllPartitions) {
179187
{Options::BUCKET, "-1"},
180188
{Options::FILE_SYSTEM, "local"},
181189
{Options::COMPACTION_MIN_FILE_NUM, "2"},
190+
{Options::MANIFEST_DELETE_FILE_DROP_STATS, "true"},
182191
};
183192

184193
arrow::FieldVector fields = {
@@ -246,11 +255,13 @@ TEST_F(AppendCompactCoordinatorTest, TestRunCompactsAllPartitions) {
246255
// f1=10: 2 files compacted into 1, total 7 rows
247256
CheckCommitMessage(compact_messages[0],
248257
/*expected_before_files=*/2,
249-
/*expected_total_rows=*/7);
258+
/*expected_total_rows=*/7,
259+
/*expect_dropped_stats=*/true);
250260
// f1=20: 2 files compacted into 1, total 3 rows
251261
CheckCommitMessage(compact_messages[1],
252262
/*expected_before_files=*/2,
253-
/*expected_total_rows=*/3);
263+
/*expected_total_rows=*/3,
264+
/*expect_dropped_stats=*/true);
254265

255266
// Commit compact results
256267
ASSERT_OK(Commit(table_path, compact_messages));

src/paimon/core/core_options.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ struct CoreOptions::Impl {
448448
int64_t write_buffer_spill_max_disk_size = std::numeric_limits<int64_t>::max();
449449

450450
bool ignore_delete = false;
451+
bool manifest_delete_file_drop_stats = false;
451452
bool write_buffer_spillable = true;
452453
bool write_only = false;
453454
bool bucket_append_ordered = false;
@@ -657,6 +658,9 @@ struct CoreOptions::Impl {
657658
// Parse manifest.full-compaction-threshold-size - size threshold for full compaction
658659
PAIMON_RETURN_NOT_OK(parser.ParseMemorySize(Options::MANIFEST_FULL_COMPACTION_FILE_SIZE,
659660
&manifest_full_compaction_file_size));
661+
// Parse manifest.delete-file-drop-stats - drop stats from DELETE entries, default false
662+
PAIMON_RETURN_NOT_OK(parser.Parse(Options::MANIFEST_DELETE_FILE_DROP_STATS,
663+
&manifest_delete_file_drop_stats));
660664
return Status::OK();
661665
}
662666

@@ -1172,6 +1176,10 @@ int64_t CoreOptions::GetManifestFullCompactionThresholdSize() const {
11721176
return impl_->manifest_full_compaction_file_size;
11731177
}
11741178

1179+
bool CoreOptions::ManifestDeleteFileDropStats() const {
1180+
return impl_->manifest_delete_file_drop_stats;
1181+
}
1182+
11751183
const std::string& CoreOptions::GetManifestCompression() const {
11761184
return impl_->manifest_compression;
11771185
}

src/paimon/core/core_options.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ class PAIMON_EXPORT CoreOptions {
9696
const std::string& GetManifestCompression() const;
9797
int32_t GetManifestMergeMinCount() const;
9898
int64_t GetManifestFullCompactionThresholdSize() const;
99+
100+
/// Return whether final DELETE manifest entries should omit value statistics.
101+
///
102+
/// @return True when DELETE entries should omit value statistics.
103+
bool ManifestDeleteFileDropStats() const;
104+
99105
int64_t GetSourceSplitTargetSize() const;
100106
int64_t GetSourceSplitOpenFileCost() const;
101107
std::optional<int64_t> GetScanSnapshotId() const;

src/paimon/core/core_options_test.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ TEST(CoreOptionsTest, TestDefaultValue) {
6262
ASSERT_EQ(8 * 1024 * 1024L, core_options.GetManifestTargetFileSize());
6363
ASSERT_EQ(16 * 1024 * 1024L, core_options.GetManifestFullCompactionThresholdSize());
6464
ASSERT_EQ(30, core_options.GetManifestMergeMinCount());
65+
ASSERT_FALSE(core_options.ManifestDeleteFileDropStats());
6566
ASSERT_EQ(0, core_options.GetScanManifestEntryCacheMaxSnapshots());
6667
ASSERT_EQ(nullptr, core_options.GetCache());
6768
ASSERT_EQ(128 * 1024 * 1024L, core_options.GetSourceSplitTargetSize());
@@ -194,6 +195,7 @@ TEST(CoreOptionsTest, TestFromMap) {
194195
{Options::MANIFEST_TARGET_FILE_SIZE, "16MB"},
195196
{Options::MANIFEST_FULL_COMPACTION_FILE_SIZE, "32MB"},
196197
{Options::MANIFEST_MERGE_MIN_COUNT, "2"},
198+
{Options::MANIFEST_DELETE_FILE_DROP_STATS, "true"},
197199
{Options::SOURCE_SPLIT_TARGET_SIZE, "24MB"},
198200
{Options::SOURCE_SPLIT_OPEN_FILE_COST, "32MB"},
199201
{Options::READ_BATCH_SIZE, "2048"},
@@ -328,6 +330,7 @@ TEST(CoreOptionsTest, TestFromMap) {
328330
ASSERT_EQ(16 * 1024 * 1024L, core_options.GetManifestTargetFileSize());
329331
ASSERT_EQ(32 * 1024 * 1024L, core_options.GetManifestFullCompactionThresholdSize());
330332
ASSERT_EQ(2, core_options.GetManifestMergeMinCount());
333+
ASSERT_TRUE(core_options.ManifestDeleteFileDropStats());
331334
ASSERT_EQ(nullptr, core_options.GetCache());
332335
ASSERT_EQ(24 * 1024 * 1024L, core_options.GetSourceSplitTargetSize());
333336
ASSERT_EQ(32 * 1024 * 1024L, core_options.GetSourceSplitOpenFileCost());

src/paimon/core/io/data_file_meta.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@ std::shared_ptr<DataFileMeta> DataFileMeta::CopyWithExtraFiles(
9696
first_row_id, write_cols);
9797
}
9898

99+
std::shared_ptr<DataFileMeta> DataFileMeta::CopyWithoutStats() const {
100+
return std::make_shared<DataFileMeta>(
101+
file_name, file_size, row_count, min_key, max_key, key_stats, SimpleStats::EmptyStats(),
102+
min_sequence_number, max_sequence_number, schema_id, level, extra_files, creation_time,
103+
delete_row_count, embedded_index, file_source, std::vector<std::string>(), external_path,
104+
first_row_id, write_cols);
105+
}
106+
99107
DataFileMeta::DataFileMeta(
100108
const std::string& _file_name, int64_t _file_size, int64_t _row_count,
101109
const BinaryRow& _min_key, const BinaryRow& _max_key, const SimpleStats& _key_stats,

src/paimon/core/io/data_file_meta.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ struct DataFileMeta {
8383
std::shared_ptr<DataFileMeta> CopyWithExtraFiles(
8484
const std::vector<std::optional<std::string>>& new_extra_files) const;
8585

86+
/// Create a copy without value statistics. All other metadata is preserved.
87+
///
88+
/// @return A new metadata object with empty value statistics and value-stat columns.
89+
std::shared_ptr<DataFileMeta> CopyWithoutStats() const;
90+
8691
std::optional<int64_t> AddRowCount() const {
8792
return delete_row_count == std::nullopt ? std::optional<int64_t>()
8893
: row_count - delete_row_count.value();

src/paimon/core/io/data_file_meta_test.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,44 @@
2020

2121
#include "gtest/gtest.h"
2222
#include "paimon/status.h"
23+
#include "paimon/testing/utils/binary_row_generator.h"
2324
#include "paimon/testing/utils/testharness.h"
2425

2526
namespace paimon::test {
27+
TEST(DataFileMetaTest, TestCopyWithoutStats) {
28+
std::shared_ptr<MemoryPool> pool = GetDefaultPool();
29+
SimpleStats value_stats = BinaryRowGenerator::GenerateStats(
30+
{1, std::string("a")}, {5, std::string("z")}, {0, 1}, pool.get());
31+
auto file_meta = std::make_shared<DataFileMeta>(
32+
"data-0.orc", /*file_size=*/645, /*row_count=*/5, BinaryRow::EmptyRow(),
33+
BinaryRow::EmptyRow(), SimpleStats::EmptyStats(), value_stats,
34+
/*min_sequence_number=*/0, /*max_sequence_number=*/4, /*schema_id=*/0,
35+
/*level=*/0, /*extra_files=*/std::vector<std::optional<std::string>>(),
36+
/*creation_time=*/Timestamp(1737111915429ll, 0),
37+
/*delete_row_count=*/2, /*embedded_index=*/nullptr, FileSource::Append(),
38+
/*value_stats_cols=*/std::vector<std::string>({"f0", "f1"}),
39+
/*external_path=*/"file:/tmp/bucket-0/data-0.orc", /*first_row_id=*/100,
40+
/*write_cols=*/std::vector<std::string>({"f0"}));
41+
42+
std::shared_ptr<DataFileMeta> result = file_meta->CopyWithoutStats();
43+
44+
ASSERT_NE(file_meta.get(), result.get());
45+
DataFileMeta expected = *file_meta;
46+
expected.value_stats = SimpleStats::EmptyStats();
47+
expected.value_stats_cols = std::vector<std::string>();
48+
ASSERT_EQ(expected, *result);
49+
ASSERT_EQ(value_stats, file_meta->value_stats);
50+
ASSERT_EQ(std::vector<std::string>({"f0", "f1"}), file_meta->value_stats_cols.value());
51+
52+
// Upgrade cannot restore stats once they have been dropped. Writer restore must therefore
53+
// retain full stats for metadata-only ADD entries; see the Paimon Java bug at
54+
// https://github.com/apache/paimon/issues/7026.
55+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<DataFileMeta> upgraded, result->Upgrade(/*new_level=*/1));
56+
ASSERT_EQ(SimpleStats::EmptyStats(), upgraded->value_stats);
57+
ASSERT_TRUE(upgraded->value_stats_cols.has_value());
58+
ASSERT_TRUE(upgraded->value_stats_cols->empty());
59+
}
60+
2661
TEST(DataFileMetaTest, TestAddRowCount) {
2762
DataFileMeta file_meta("data-80110e15-97b5-4bcf-ac09-6ca2659a4950-0.orc", /*file_size=*/645,
2863
/*row_count=*/5, BinaryRow::EmptyRow(), BinaryRow::EmptyRow(),

0 commit comments

Comments
 (0)