Skip to content

Plain merge tree partition exports - #2290

Open
arthurpassos wants to merge 39 commits into
antalya-26.6from
feature/antalya-26.6/export-partition-plain-merge-tree
Open

Plain merge tree partition exports#2290
arthurpassos wants to merge 39 commits into
antalya-26.6from
feature/antalya-26.6/export-partition-plain-merge-tree

Conversation

@arthurpassos

@arthurpassos arthurpassos commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

List of changes:

  1. Implements partition export for plain merge tree using Json task descriptors stored on disk, the structure is simplified because it is much simpler than the replicated case
  2. Introduces the system.partition_exports table that keeps track of both plain and replicated cases. The old system.replicated_partition_exports is aliased to this new one for backwards compatibility.
  3. Escapes each portion of the partition export key - this is not backwards compatible.
  4. Stop shipping empty parquet files on plain object storage for exports with zero rows
  5. Throws on a few errors zk that were left as todo
  6. Refactor the test suite so that it is parametrized for both engines instead of duplicating the tests

Changelog category (leave one):

  • New Feature

Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):

Export partition on plain merge tree. Should close #1908

User facing & functional changes:

  • Implements partition export for plain merge tree
  • Stop shipping empty parquet files on zero rows exports
  • New system.partition_exports table that covers both the previous replicated_partition_exports and the new one for plain merge tree

Documentation entry for user-facing changes

...

CI/CD Options

Exclude tests:

  • Fast test
  • Integration Tests
  • Stateless tests
  • Stateful tests
  • Unit tests
  • Performance tests
  • Aarch64 tests
  • All with ASAN
  • All with TSAN
  • All with MSAN
  • All with UBSAN
  • All with Coverage
  • All Regression
  • Disable CI Cache

Regression jobs to run:

  • Fast suites (mostly <1h)
  • Aggregate Functions (2h)
  • Alter (1.5h)
  • Benchmark (30m)
  • CAS (content-addressed storage; Antalya only)
  • ClickHouse Keeper (1h)
  • Iceberg (2h)
  • LDAP (1h)
  • OAuth (5m)
  • Parquet (1.5h)
  • RBAC (1.5h)
  • SSL Server (1h)
  • S3 (2h)
  • S3 Export (2h)
  • Swarms (30m)
  • Tiered Storage (2h)

@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown

Workflow [PR], commit [2662683]

@arthurpassos

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-12T14:11:12.740611Z 2662683 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@arthurpassos
arthurpassos marked this pull request as ready for review August 29, 2026 17:20
@arthurpassos

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 240da8173c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Storages/MergeTree/MergeTreePartitionExportScheduler.cpp Outdated
Comment thread src/Storages/MergeTree/MergeTreePartitionExportScheduler.h Outdated
Comment thread src/Storages/MergeTree/MergeTreePartitionExportScheduler.cpp Outdated
Comment on lines +286 to +291
catch (...)
{
replicated_error = std::current_exception();
tryLogCurrentException(getLogger("InterpreterKillQueryQuery"),
"KILL EXPORT PARTITION: could not read system.replicated_partition_exports (the WHERE may "
"reference columns that only exist for plain MergeTree); ignoring ReplicatedMergeTree tables");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Propagate system-table read failures during KILL

If either system-table query fails for an operational reason such as a memory limit, timeout, or internal exception, this broad catch treats it like an engine-specific unknown column and continues with the other table. The command can therefore report successful results while silently leaving matching exports from the failed table running. Only suppress the specific predicate-resolution error that proves the filter belongs exclusively to the other table; propagate all other failures.

AGENTS.md reference: AGENTS.md:L153-L153

Useful? React with 👍 / 👎.

@arthurpassos

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c8d038d350

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Storages/MergeTree/MergeTreePartitionExportTask.h Outdated
Comment thread src/Storages/MergeTree/MergeTreePartitionExportScheduler.cpp
Comment thread src/Storages/System/StorageSystemPartitionExports.cpp Outdated
Comment thread docs/en/antalya/partition_export.md Outdated
Comment thread docs/en/antalya/partition_export.md Outdated
@arthurpassos arthurpassos changed the title Feature/antalya 26.6/export partition plain merge tree Plain merge tree partition exports Sep 4, 2026
@arthurpassos arthurpassos added the port-antalya PRs to be ported to all new Antalya releases label Sep 8, 2026
arthurpassos and others added 7 commits September 8, 2026 13:51
…n-plain-merge-tree

Keep the skip-policy commit-file short-circuit from antalya-26.6 together with
lazy first-sink creation, and port the new skip-policy tests into the unified
object-storage suite.

Co-authored-by: Cursor <cursoragent@cursor.com>

### Pending mutations {#plain-merge-tree-pending-mutations}

The pending-mutation gate is more conservative than on a `Replicated*MergeTree`. A plain `MergeTree` does not scope its mutation snapshot by partition, so a mutation restricted with `IN PARTITION` still marks the parts of every other partition as having pending mutations, and exporting an unaffected partition is refused with `PENDING_MUTATIONS_NOT_ALLOWED`. The gate fails closed - it never exports data that a pending mutation would have changed - so the effect is that you may have to wait for an unrelated mutation to finish, or set `export_merge_tree_part_throw_on_pending_mutations` to `false`.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should check ways around this later

Comment thread docs/en/antalya/partition_export.md Outdated
### Commit info columns

These columns surface paths produced by the destination storage during commit, so it is possible to inspect what was written without consulting the destination directly:
These columns surface paths produced by the destination storage during commit, so it is possible to inspect what was written without consulting the destination directly. They are populated for `Replicated*MergeTree` sources only; a plain `MergeTree` does not persist the commit paths, so they stay empty there even after a successful commit.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shall be implemented for plain merge tree as well

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to be covered in another PR?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I plan to cover it in this pr unless it is too complex (I doubt it is).

For now I am reviewing my own code (AI code :D)

@DimensionWieldr

Copy link
Copy Markdown
Collaborator

Iceberg export partition regression suite is fully passing on no/ice/glue catalogs with plain merge tree tables.

I will wait for dev review and the CI run to do a final CI triage to confirm no PR caused failures.

@mkmkme
mkmkme self-requested a review September 12, 2026 07:36

@mkmkme mkmkme left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good and worth an approval. I left some comments, I'll let you decide what to do about them

The partition export tasks of both engines can be observed through `system.partition_exports`.

The export task can be killed by issuing the kill command: `KILL EXPORT PARTITION <where predicate for system.replicated_partition_exports>`.
`system.replicated_partition_exports` is kept as an alias of `system.partition_exports` for backwards compatibility. It returns exactly the same rows, including exports of plain `MergeTree` tables. Filter on `source_table` (or join against `system.tables`) if you need only one engine.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a bit counter-intuitive IMHO. But I don't have a good and simple solution for that. I can suggest one of two things:

  1. Make replicated_partition_exports only return exports from RMT (but it will likely require quite a bit of work for not exactly a great result)
  2. Mark system.replicated_partition_exports explicitly as deprecated (but I am not sure we have such a mechanism for system tables)

### Exception columns

- `last_exception_per_replica` is an `Array(Tuple(replica String, message String, part String, time DateTime, count UInt64))`. Each tuple is the most recent exception observed by a single replica plus a best-effort within-replica `count`. Replicas that have never reported an exception are omitted.
- `last_exception_per_replica` is an `Array(Tuple(replica String, message String, part String, time DateTime, count UInt64))`. Each tuple is the most recent exception observed by a single replica plus a best-effort within-replica `count`. Replicas that have never reported an exception are omitted. A plain `MergeTree` export runs on a single node, so it contributes at most one tuple and its `replica` is empty.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't found (yet) in the documentation, but wanted to ask: does it make sense for EXPORT PARTITION on a plain MergeTree to be synchronous? And instead of finding the exception in the system table, it would raise the exception as a response?

if (!iceberg_metadata)
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Destination storage {} is a data lake but not an iceberg table", dest_storage->getName());

if (!context->getSettingsRef()[Setting::allow_insert_into_iceberg])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: I would probably move this check before checking for iceberg_metadata being nullptr, but it's not a biggie

auto & entry = it->second;

if (entry.getDescriptor().status != MergeTreePartitionExportTask::Status::PENDING)
return CancellationCode::CancelCannotBeSent;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say this also deserves a LOG_INFO

std::lock_guard lock(mutex);
auto it = findByTransactionId(transaction_id);
if (it == tasks.end())
return;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a LOG_DEBUG here? Why can this happen?

expected_status="COMPLETED",
timeout=60,
poll_interval=0.5,
system_table="partition_exports",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this argument (here and below) is actually used in a way that replicated_partition_exports is passed explicitly?

@mkmkme

mkmkme commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2662683255

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +56 to +57
return ExportPartitionUtils::compositeKey(
manifest.partition_id, manifest.destination_database, manifest.destination_table);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve or migrate the previous ZooKeeper task keys

After upgrading with an existing replicated export, its ZooKeeper child still uses the old <partition>_<database>.<table> key, while this method now derives the escaped dot-separated key. Although poll reads the old child, the inserted entry is indexed by this new derived key and removeStaleEntries immediately removes it because that key is absent from the ZooKeeper child list; the scheduler therefore cannot resume its remaining parts, and a new export also misses the old path during duplicate detection. Migrate existing nodes or retain the actual ZooKeeper child key in the entry.

Useful? React with 👍 / 👎.

entry.part_references = std::move(part_references);
entry.setDescriptor(std::move(descriptor));
persist(composite_key, entry.getDescriptor().toJsonString());
tasks.insert_or_assign(composite_key, std::move(entry));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Block force replacement while the prior commit is running

When a forced re-export arrives while the existing entry has committing == true, this replacement proceeds even though KILL explicitly refuses that state. killExportPart cannot cancel the destination commit, so the old commit can publish successfully and then find its registry entry replaced, while the new transaction subsequently exports and commits the same partition again; for an Iceberg destination this can produce duplicate rows under two transaction IDs. Reject or defer the replacement until the existing commit lease is released.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

antalya antalya-26.6 port-antalya PRs to be ported to all new Antalya releases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support ALTER TABLE ... EXPORT PARTITION for non-replicated MT tables

4 participants