Repository navigation
Conversation
…/Altinity/ClickHouse into iceberg_rewrite_files_compaction
Audit report: Altinity/ClickHouse#2420 (Iceberg bin-pack
|
Audit report: #2420 at c4c4578AI audit note: This review was generated by AI. Audit update for PR #2420 (Iceberg bin-pack All 10 defects from the previous audit (head Confirmed defectsMedium: the v3 guard uses the format version cached when the table object was created, and full compaction still accepts v3 and writes v2 metadata
if (persistent_components.format_version != 2) // set once in the IcebergMetadata constructor
throw Exception(ErrorCodes::BAD_ARGUMENTS, "OPTIMIZE TABLE is supported only for Iceberg format_version 2, got {}", ...);
// Compaction.cpp / getPlan (latest metadata)
if (initial_metadata_object->getValue<Int32>(Iceberg::f_format_version) < 2) // v3 passes
Low: after full compaction, the regenerated
|
…-pack rewrite The error code does not exist in `ErrorCodes.cpp`, which caused a link failure of `clickhouse` and `unit_tests_dbms`. Use `UNFINISHED` for the case where the outcome of the metadata commit is unknown. Co-authored-by: Cursor <cursoragent@cursor.com>
…-pack rewrite The error code does not exist in `ErrorCodes.cpp`, which caused a link failure of `clickhouse` and `unit_tests_dbms`. Use `UNFINISHED` for the case where the outcome of the metadata commit is unknown.
…/Altinity/ClickHouse into iceberg_rewrite_files_compaction
…/Altinity/ClickHouse into iceberg_rewrite_files_compaction Signed-off-by: Kanthi Subramanian <subkanthi@gmail.com>
…/Altinity/ClickHouse into iceberg_rewrite_files_compaction
CI triage for #2420 (
|
|
To use Codex here, create a Codex account and connect to github. |
…ler functions by steps/phase
|
|
||
| auto hint_path = generator.generateVersionHint(); | ||
|
|
||
| const auto commit_result = tryWriteMetadataFileAndVersionHint( |
There was a problem hiding this comment.
Cleanup deletes the files of a committed snapshot when the version hint update throws
When tryWriteMetadataFileAndVersionHint is called, it writes vN+1.metadata.json first and then moves on to updating the version hint. The loop that updates the hint is not wrapped in a try block, so calls such as object_storage->exists can throw after the new metadata is already live. If that happens, the exception leaves commitMetadata while keep_files_on_error is still false, since it is only set on the Unknown path. The catch block in executeBinPackCompaction then calls cleanup, which removes the merged data files, the new manifests and the manifest list.
As a result, the current snapshot points at objects that no longer exist, and the old small files are already marked as deleted in it, so the table can no longer be read. A single transient S3 error while reading the hint or a malformed version-hint.text is enough to trigger this.
Proposed fix: Set keep_files_on_error = true right before calling tryWriteMetadataFileAndVersionHint. Both Conflict branches already call cleanup explicitly, so they would keep working as they do now.
| if (attempt > 0) | ||
| LOG_INFO(log, "Retrying bin-pack compaction (attempt {}/{})", attempt + 1, MAX_BIN_PACK_RETRIES); | ||
|
|
||
| const auto result = Iceberg::executeBinPackCompaction( |
There was a problem hiding this comment.
OPTIMIZE on a catalog table writes a new snapshot but never commits it to the catalog
When OPTIMIZE runs on a table from a DataLakeCatalog database, the bin-pack path writes merged data files, manifests, a manifest list and vN.metadata.json, and then reports success. However, the catalog is never told about the new metadata, because IcebergMetadata::optimize has no catalog parameter and executeBinPackCompaction only writes the metadata file to storage. The catalog keeps pointing at the previous metadata file, while the new metadata file and its snapshot sit next to it unreferenced.
As a result, catalog readers never see the compaction and every run leaves orphaned files behind. The next OPTIMIZE also picks up the uncommitted metadata file as the latest version, because it finds the latest metadata by listing the directory, so it starts from a state the catalog has never seen. This behavior is new with this PR. Before it, OPTIMIZE on a table without position deletes did nothing. The manifest-only path already handles this case: optimizeManifestFiles takes the catalog and commits through catalog->updateMetadata.
Proposed fix: Pass the catalog and storage ID into optimize the same way as optimizeManifestFiles, and commit through catalog->updateMetadata in commitMetadata when a catalog is present, treating a rejected update as a Conflict.
| StoredObject(storage_manifest_list_path), WriteMode::Rewrite, std::nullopt, | ||
| DBMS_DEFAULT_BUFFER_SIZE, context->getWriteSettings()); | ||
|
|
||
| generateManifestList( |
There was a problem hiding this comment.
Bin-pack manifests are written without a partition summary in the manifest list
In writeManifestList, there is no entry_partition_summaries argument passed to generateManifestList, so write_partition_summary returns early and the partitions field is left empty for every manifest that bin-packing writes. Readers use that summary to skip whole manifests during scan planning. After an OPTIMIZE on a partitioned table, every scan has to open all of the new manifests, including the per-file ones. The manifest-only rewrite in Compaction.cpp builds these summaries for exactly this reason. Since every manifest written here holds a single partition tuple, the bin's partition key or the kept group's key already contains everything the summary needs.
Proposed fix: Build one summary per manifest from the partition key and plan.partition_types, the same way the manifest-only rewrite does in Compaction.cpp, and pass the vector to generateManifestList.
| size_t result = 0; | ||
| FieldVisitorDump dump_visitor; | ||
| for (const auto & value : row) | ||
| result ^= hasher(applyVisitor(dump_visitor, value)); |
There was a problem hiding this comment.
The hash combiner fix here was reverted by a later commit
The commit b5206fe replaced the XOR in here with boost::hash_combine. The next commit, 7d547c2, changed it back to result ^= ... and removed the boost include, which looks like an accident while resolving a conflict. When values are combined with XOR, (a, b) and (b, a) produce the same hash, and any key with two equal values hashes to 0. Since PartitionKeyEqual still compares the keys, results stay correct, but lookups in the partition maps can degrade for tables that are partitioned on more than one column.
Proposed fix: Restore boost::hash_combine(result, hasher(applyVisitor(dump_visitor, value))) and the <boost/container_hash/hash.hpp> include.
| {"iceberg_target_data_file_size_bytes", 536870912, 536870912, "Target file size for Iceberg bin-packing compaction (default 512 MiB)."}, | ||
| {"iceberg_min_data_file_size_bytes", 402653184, 402653184, "Files below this size are candidates for Iceberg bin-packing compaction (default 384 MiB)."}, |
There was a problem hiding this comment.
I think this should be moved to a newer version block since 26.6.2.20001 already released
| return {new_snapshot, manifest_list_path}; | ||
| } | ||
|
|
||
| MetadataGenerator::NextMetadataResult MetadataGenerator::generateReplaceSnapshot( |
There was a problem hiding this comment.
This seems to be mostly the same as generateManifestOnlySnapshot, the duplicated logic could be moved to a helper function and called in both.
| parent_snapshot_id); | ||
|
|
||
| Poco::JSON::Object::Ptr new_snapshot = new Poco::JSON::Object; | ||
| if (format_version > 1) |
There was a problem hiding this comment.
This if statement check isn't necessary because you already throw if format_version != 2 above so it will always be true
Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):
Implement bin-packing
ReWriteFilesoperation that Identifies small data files below a target size threshold(defined in Settings), groups them and merges to a large file, creates a snapshot that marks the old ones as DELETED and the new larger merge file as ADDED.Leave all other files, manifests, and history untouched
CI/CD Options
Exclude tests:
Regression jobs to run: