feat: support bounded compaction planner#6095
Conversation
PR Review: feat: support bounded compaction plannerP0 – Sequential I/O in
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
| /// The type of compaction planner to use. Defaults to [`CompactionPlannerType::Default`]. | ||
| pub compaction_planner_type: CompactionPlannerType, | ||
| /// The maximum number of bytes to compact in a single compaction operation. | ||
| pub max_compaction_bytes: Option<usize>, | ||
| /// The maximum number of rows to compact in a single compaction operation. | ||
| pub max_compaction_rows: Option<usize>, |
There was a problem hiding this comment.
I'm having some difficulty with how these will be presented as options to the user. Regardless of which way I cut it, it feels like we're leaking implementation details that they should not be concerned about. For example (using spark SQL as an example):
OPTIMIZE <table> WITH (max_compaction_row=100000)
```
Will do nothing, because the user has to explicitly set the planner type:
```
OPTIMIZE <table> WITH (planner="bounded", max_compaction_row=100000)
```
Alternatively, if we remove the `planner` configuration option then this gets really messy as we add additional options that are disjoint. For example, if we had a "fooable" planner that can "foo" with "how_to_foo" configuration option:
```
OPTIMIZE <table> WITH (max_compaction_row=100000, how_to_foo=carefully)
```
means that we can't just create a different planner based on the passed options, because is ^^^ `FooablePlanner` or `BoundedPlanner`
To add maximum compaction bytes / rows configuration is there a reason we need to add a different planner implementation? Or can these be presented as options that are applied in the current planning scheme?
There was a problem hiding this comment.
Hi @hamersaw Thanks a lot for your attention! Looking forward to more discussions with you. After we reach a consensus on the design, I will update the implementation of the current PR.
I understand there are currently two main issues:
- How to better expose the concepts of planners and their corresponding parameters to users.
- Why a separate bounded planner needs to be added here.
How to better expose the concepts of planners and their corresponding parameters to users.
I believe we can refer to the design of how various indexes are exposed to users. Here, we support different types of indexes, and each index has its own tuning parameters.
def create_scalar_index(
self,
column: str,
index_type: Union[
Literal["BTREE"],
Literal["BITMAP"],
Literal["LABEL_LIST"],
Literal["INVERTED"],
Literal["FTS"],
Literal["NGRAM"],
Literal["ZONEMAP"],
Literal["BLOOMFILTER"],
Literal["RTREE"],
IndexConfig,
],
name: Optional[str] = None,
*,
replace: bool = True,
train: bool = True,
fragment_ids: Optional[List[int]] = None,
index_uuid: Optional[str] = None,
**kwargs,
):
On the Compaction side, add new parameters: planner and**kwargs
def compact_files(
self,
*,
target_rows_per_fragment: int = 1024 * 1024,
max_rows_per_group: int = 1024,
max_bytes_per_file: Optional[int] = None,
materialize_deletions: bool = True,
materialize_deletions_threshold: float = 0.1,
num_threads: Optional[int] = None,
batch_size: Optional[int] = None,
compaction_mode: Optional[
Literal["reencode", "try_binary_copy", "force_binary_copy"]
] = None,
binary_copy_read_batch_bytes: Optional[int] = None,
planner: Union[
Literal["DEFAULT"],
Literal["BOUNDED"],
] = "DEFAULT",
**kwargs,
) -> CompactionMetrics:
During parsing, depending on the planner, attempt to capture parameters specific to the current planner from**kwargs (ignoring irrelevant parameters) and pass them to the Rust side.
For invocation, it is similar to:
metrics = dataset.optimize.compact_files(
planner = "bounded",
max_compaction_rows = 1000000,
)
Why a separate bounded planner needs to be added here.
Generally speaking, a compaction plan is roughly divided into three steps: first, obtaining fragments, second, building compaction tasks, and third, constructing the compaction plan.
The main positioning of the bounded planner here is an "incremental" compaction plan.
- Incrementally obtain fragments, obtain version diff fragments (TBD)
- Bounded plan compaction, When building tasks, construct a partial compaction plan through restrictive conditions such as max row and max bytes, rather than a full one.
- In the future, other "incremental" logics can be flexibly added.
In contrast, the Default planner is a full compaction plan.
- Obtain all fragments of the current version
- Build all compaction tasks at once
Of course, we can modify the Default planner to limit the number of tasks it outputs. However, there is a detailed issue here.
The pseudocode logic of the Default planner is as follows:
let metrics = collect all fragment metrics paralle at once
for metrics in metrics {
build task
}
Under the condition of an incremental planner, we may prefer to collect on demand, especially in large tables with hundreds of thousands of fragments. Avoid the memory pressure and unnecessary computational overhead caused by full-scale fragment perception and calculation, that is:
for fragment in fragments {
let metrics = collect current fragment metrics
if condition(metrics) {
break;
}
}
It might be a good choice to distinguish between incremental planners and full planners.
Is that make sense? I am open to all the above. Please let me know in time if you have anything want to discuss! Thanks in advance.
There was a problem hiding this comment.
Great points! In my experience, the cost of planning a compaction (ex. retrieving fragment metadata and aggregating into compaction tasks) is quite small when compared to the actually compaction execution. So I do not think it should be something to build around.
I think bounding compaction executions can come in two flavors (1) limiting the number of input fragments and (2) limiting the number of compaction tasks. With an option, we can still do the former easily. The latter, will need to retrieve all fragments, compile the plan, and then just return / execute a small subset.
Either way, I think that building this using compaction options gives the same functionality and is easier for users to configure.
Closes: #6039