Skip to content

Commit f05db6b

Browse files
committed
feat: add deletion vector compaction planner
Add a metadata-only planner that selects rewrite candidates by file size and file-scoped position-delete pressure, then groups candidates by partition. Expose focused configuration and result models for later rewrite execution.
1 parent dc84d6e commit f05db6b

7 files changed

Lines changed: 387 additions & 0 deletions

File tree

‎src/iceberg/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ set(ICEBERG_SOURCES
2626
catalog/memory/in_memory_catalog.cc
2727
catalog/session_catalog.cc
2828
catalog/session_context.cc
29+
compaction_planner.cc
2930
delete_file_index.cc
3031
deletes/dv_util.cc
3132
deletes/dv_writer.cc

‎src/iceberg/compaction_planner.cc‎

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include "iceberg/compaction_planner.h"
21+
22+
#include <algorithm>
23+
#include <utility>
24+
25+
#include "iceberg/manifest/manifest_entry.h"
26+
#include "iceberg/table_scan.h"
27+
#include "iceberg/util/content_file_util.h"
28+
#include "iceberg/util/macros.h"
29+
30+
namespace iceberg {
31+
namespace {
32+
33+
struct PendingGroup {
34+
CompactionGroup group;
35+
bool rewrite_independently = false;
36+
};
37+
38+
Result<CompactionFile> BuildCompactionFile(
39+
const std::shared_ptr<FileScanTask>& scan_task) {
40+
CompactionFile result{.scan_task = scan_task};
41+
const auto& data_file = scan_task->data_file();
42+
43+
for (const auto& delete_file : scan_task->delete_files()) {
44+
if (delete_file->content != DataFile::Content::kPositionDeletes) {
45+
continue;
46+
}
47+
48+
ICEBERG_ASSIGN_OR_RAISE(auto referenced_file,
49+
ContentFileUtil::ReferencedDataFile(*delete_file));
50+
if (referenced_file != data_file->file_path) {
51+
continue;
52+
}
53+
54+
++result.file_scoped_delete_count;
55+
result.file_scoped_delete_record_count += delete_file->record_count;
56+
}
57+
58+
return result;
59+
}
60+
61+
PendingGroup& FindOrAddGroup(std::vector<PendingGroup>& groups,
62+
const DataFile& data_file) {
63+
auto group = std::ranges::find_if(groups, [&](const PendingGroup& candidate) {
64+
return candidate.group.partition_spec_id == data_file.partition_spec_id &&
65+
candidate.group.partition == data_file.partition;
66+
});
67+
if (group != groups.end()) {
68+
return *group;
69+
}
70+
71+
groups.push_back(PendingGroup{.group = CompactionGroup{
72+
.partition_spec_id = data_file.partition_spec_id,
73+
.partition = data_file.partition,
74+
}});
75+
return groups.back();
76+
}
77+
78+
} // namespace
79+
80+
Result<CompactionPlan> CompactionPlanner::Plan(
81+
std::span<const std::shared_ptr<FileScanTask>> scan_tasks,
82+
const CompactionPlannerConfig& config) {
83+
const long double min_file_size =
84+
config.target_file_size_bytes * config.min_file_size_ratio;
85+
const long double max_file_size =
86+
config.target_file_size_bytes * config.max_file_size_ratio;
87+
std::vector<PendingGroup> pending_groups;
88+
89+
for (const auto& scan_task : scan_tasks) {
90+
ICEBERG_PRECHECK(scan_task != nullptr, "File scan task must not be null");
91+
const auto& data_file = *scan_task->data_file();
92+
ICEBERG_ASSIGN_OR_RAISE(auto file, BuildCompactionFile(scan_task));
93+
94+
const bool is_small = data_file.file_size_in_bytes < min_file_size;
95+
const bool is_oversized = data_file.file_size_in_bytes > max_file_size;
96+
const bool has_many_delete_files =
97+
file.file_scoped_delete_count >= config.delete_file_threshold;
98+
const bool has_high_delete_ratio =
99+
data_file.record_count > 0 &&
100+
static_cast<double>(file.file_scoped_delete_record_count) /
101+
data_file.record_count >=
102+
config.delete_ratio_threshold;
103+
if (!is_small && !is_oversized && !has_many_delete_files && !has_high_delete_ratio) {
104+
continue;
105+
}
106+
107+
auto& group = FindOrAddGroup(pending_groups, data_file);
108+
group.rewrite_independently |=
109+
is_oversized || has_many_delete_files || has_high_delete_ratio;
110+
group.group.data_file_size_bytes += data_file.file_size_in_bytes;
111+
group.group.file_scoped_delete_record_count += file.file_scoped_delete_record_count;
112+
group.group.files.push_back(std::move(file));
113+
}
114+
115+
CompactionPlan plan;
116+
for (auto& pending : pending_groups) {
117+
if (pending.rewrite_independently ||
118+
pending.group.files.size() >= config.min_input_files) {
119+
plan.groups.push_back(std::move(pending.group));
120+
}
121+
}
122+
return plan;
123+
}
124+
125+
} // namespace iceberg

‎src/iceberg/compaction_planner.h‎

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#pragma once
21+
22+
/// \file iceberg/compaction_planner.h
23+
/// Plan data-file compaction without executing rewrites.
24+
25+
#include <cstddef>
26+
#include <cstdint>
27+
#include <memory>
28+
#include <optional>
29+
#include <span>
30+
#include <vector>
31+
32+
#include "iceberg/iceberg_export.h"
33+
#include "iceberg/result.h"
34+
#include "iceberg/row/partition_values.h"
35+
#include "iceberg/type_fwd.h"
36+
37+
namespace iceberg {
38+
39+
/// \brief Thresholds used to select data files for compaction.
40+
struct ICEBERG_EXPORT CompactionPlannerConfig {
41+
int64_t target_file_size_bytes = int64_t{512} * 1024 * 1024;
42+
double min_file_size_ratio = 0.75;
43+
double max_file_size_ratio = 1.8;
44+
size_t min_input_files = 5;
45+
size_t delete_file_threshold = 2;
46+
double delete_ratio_threshold = 0.3;
47+
};
48+
49+
/// \brief A selected data file and its file-scoped delete pressure.
50+
struct ICEBERG_EXPORT CompactionFile {
51+
std::shared_ptr<FileScanTask> scan_task;
52+
size_t file_scoped_delete_count = 0;
53+
int64_t file_scoped_delete_record_count = 0;
54+
};
55+
56+
/// \brief Selected files from one partition that can be rewritten together.
57+
struct ICEBERG_EXPORT CompactionGroup {
58+
std::optional<int32_t> partition_spec_id;
59+
PartitionValues partition;
60+
std::vector<CompactionFile> files;
61+
int64_t data_file_size_bytes = 0;
62+
int64_t file_scoped_delete_record_count = 0;
63+
};
64+
65+
/// \brief Result of compaction planning.
66+
struct ICEBERG_EXPORT CompactionPlan {
67+
std::vector<CompactionGroup> groups;
68+
};
69+
70+
/// \brief Select and group scan tasks for data-file compaction.
71+
class ICEBERG_EXPORT CompactionPlanner {
72+
public:
73+
static Result<CompactionPlan> Plan(
74+
std::span<const std::shared_ptr<FileScanTask>> scan_tasks,
75+
const CompactionPlannerConfig& config = {});
76+
};
77+
78+
} // namespace iceberg

‎src/iceberg/meson.build‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ iceberg_sources = files(
7979
'catalog/memory/in_memory_catalog.cc',
8080
'catalog/session_catalog.cc',
8181
'catalog/session_context.cc',
82+
'compaction_planner.cc',
8283
'delete_file_index.cc',
8384
'deletes/dv_util.cc',
8485
'deletes/dv_writer.cc',
@@ -319,6 +320,7 @@ install_headers(
319320
[
320321
'arrow_c_data.h',
321322
'catalog.h',
323+
'compaction_planner.h',
322324
'constants.h',
323325
'delete_file_index.h',
324326
'exception.h',

‎src/iceberg/test/CMakeLists.txt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ add_iceberg_test(puffin_test
163163
puffin_json_test.cc
164164
puffin_reader_writer_test.cc)
165165

166+
add_iceberg_test(compaction_planner_test SOURCES compaction_planner_test.cc)
167+
166168
if(ICEBERG_BUILD_BUNDLE)
167169
add_iceberg_test(avro_test
168170
USE_BUNDLE

0 commit comments

Comments
 (0)