Skip to content

[WIP][core][spark] Measure format table partitions in MSCK REPAIR TABLE - #9297

Open
sundapeng wants to merge 3 commits into
apache:masterfrom
sundapeng:upstream/s5-measure-partitions-in-msck
Open

[WIP][core][spark] Measure format table partitions in MSCK REPAIR TABLE#9297
sundapeng wants to merge 3 commits into
apache:masterfrom
sundapeng:upstream/s5-measure-partitions-in-msck

Conversation

@sundapeng

@sundapeng sundapeng commented Aug 18, 2026

Copy link
Copy Markdown
Member

Stacked on #9295 and #9296. Their commits show up here too until they merge. Review only the
last commit.

Purpose

A commit reports only what it wrote. A format table also holds partitions written outside Paimon,
files deleted out of band, and increments that drifted. MSCK REPAIR TABLE already reconciles the
partition set against the directories, so it is the place to reconcile the numbers too.

Off by default, behind spark.paimon.format-table.repair.collect-statistics: a plain repair lists
partition directories, while measuring lists the files inside every one of them.
spark.paimon.format-table.statistics.parallelism caps how many partitions are measured at once, at
8.

When on, it measures every registered partition that has a directory, not only the ones it just
added, and never registers a partition the repair did not ask for. It reports what a listing sees:
file count, byte size and last file creation time. Row count needs a file footer, which is what
ANALYZE is for. A partition whose directory is gone measures as an exact zero, and a listing failure
aborts the collection, because a truncated listing looks the same as a partition that lost files.

Tests

  • FormatTablePartitionStatsCollectorTest: staging trees, parquet row counts, an unreadable footer,
    a missing directory, alignment with the given specs, a spec missing a partition key, and a listing
    failure aborting both the serial and the parallel path.
  • FormatTablePartitionRepairTest: measuring every partition on disk, a repair without ADD
    registering nothing, and a listing failure leaving the catalog untouched.
  • CatalogManagedPartitionMsckRepairTest: the command end to end, with the option off and on.

API and Format

Two new Spark options. No API or format change.

@sundapeng sundapeng changed the title [core][spark] Measure format table partitions in MSCK REPAIR TABLE [wip][core][spark] Measure format table partitions in MSCK REPAIR TABLE Aug 18, 2026
@sundapeng sundapeng changed the title [wip][core][spark] Measure format table partitions in MSCK REPAIR TABLE [WIP][core][spark] Measure format table partitions in MSCK REPAIR TABLE Aug 18, 2026
@sundapeng
sundapeng force-pushed the upstream/s5-measure-partitions-in-msck branch 6 times, most recently from 36c77c0 to e3ccd68 Compare August 19, 2026 02:14
A catalog-managed format table registers the partitions a commit wrote. It has no
snapshot, so the channel data tables use to report statistics — commitSnapshot —
does not exist for it, and there is nowhere else for the numbers to go. This adds
them to the registration itself.

CreatePartitionsRequest gains an optional partitionStatistics list and a
statisticsMode. Registration and statistics then land in one request and one
server-side transaction, which is the point: "the statistics failed but the
partition registered" is not a state anyone has to handle, and no extra round
trip is paid for the numbers. The alternative, a separate endpoint, buys only the
ability to report statistics without registering — which is exactly the state
worth not having.

Rather than grow a third overload on each layer, the reporting arguments fold into
the ignoreIfExists overloads that apache#8707 added and nothing yet calls. Catalog keeps
the long-standing two-argument createPartitions plus the reporting one, and
RESTApi is back to a single method; CachingCatalog, DelegateCatalog and
RESTCatalog each lose a forwarder, and two Spark tests that registered partitions
through the removed overload now call the two-argument one. No released signature
changes.

While here, supportsPartitionModification says in its javadoc that
createPartitions and alterPartitions do nothing when it is false, which stopped
being true once a REST catalog started registering the partitions of a Format
Table while still reporting false. What the flag actually gates is the
PartitionModification handler a table is given, so committing a table maintains no
partitions; calling the catalog methods directly is unaffected. The javadoc now
says that.

The mode says how a report combines with what the server holds, per field: ADD
sums the three counts and takes the later creation time, SET replaces. Which one a
writer sends follows from what it accounted for: an appending commit (INSERT INTO)
reports ADD, since it knows the files it wrote and not the ones the partition
already held, and one statement whose writers commit separately reports each
writer's share; an overwriting commit (INSERT OVERWRITE, the partition pinned
statically or left to the data) reports SET, since it left the partition holding
what it wrote and nothing else. Reporting a field as unknown leaves the stored
value alone under either mode, so a reporter that can measure only some fields
never erases the others. Statistics never create or remove a partition row,
whatever the numbers say. The mode travels as a string rather than an enum so a
value the server has no name for deserializes instead of failing the request it
arrived in: such a report is dropped and the partitions are still registered.

Compatibility runs both ways and neither direction errors. A new client against
an old server: the field is ignored, the statistics are dropped and the columns
stay unknown. The client is not told, because the response carries no
acknowledgement — an observation that is missing is a thing consumers can handle,
an observation that is wrong is not, and an acknowledgement would be a second
contract to keep in step with the first. An old client against a new server: no
statistics arrive and the columns stay unknown. The existing three-argument
createPartitions keeps working and keeps sending exactly the request it sends
today, so a caller that reports nothing does not change the shape the server sees.

A non-empty ADD report declares itself unsafe to retry, so an automatically
replayed POST cannot count the same increment twice.

Tests: HttpClientRetrySafetyTest covers which requests declare themselves unsafe
to retry — only a non-empty ADD report does — and that the flag itself never
reaches the wire. RESTApiJsonTest covers the request shape, including a mode the
server has no name for surviving deserialization. MockRESTCatalogTest covers
reporting against the mock server end to end: ADD accumulating, SET replacing,
unknown fields leaving the stored value alone, statistics for a partition the
request does not register being dropped, a report that only partly matches not
being applied at all, and an unreadable mode still registering. Its assertions
are the contract the modes are described by. CatalogFormatTablePartitionManagerTest
covers what the manager sends: statistics riding in the request of their own
partitions, a batch that reports nothing sending an empty list rather than null,
and the rejections of malformed reports. CachingCatalogTest and DelegateCatalogTest
cover the new overload forwarding through and invalidating the partition cache.
The writer already counted the rows and the bytes, the commit already knows which
partitions it wrote, and the catalog now has somewhere to put both. This connects
them, behind format-table.commit.report-partition-statistics.

Which mode a commit uses follows from what it did to the partition. An appending
commit saw only its own files, so it reports an increment: a Flink sink commits
once per writer subtask, and N increments over one partition add up to what the
job wrote. An overwriting commit replaced everything the partitions held, so what
it wrote is the total and it reports that.

Static prefix overwrite is why a pure increment cannot express this. Clearing a
prefix empties every partition beneath it, including ones this commit writes
nothing to; their old data is gone and no increment says so. Those partitions
report zero — an exact zero, they really are empty — and stay registered. The
partition set is the catalog's authority and statistics never touch it; only ADD
PARTITION and DROP PARTITION do. The directories emptied that way come out of the
deletion listing, which already had to walk them, so the numbers cost no extra IO:
the row count and byte size come from the writer, the file count from the commit
messages, and the last file creation time from the commit's own clock.

The option is off by default. A non-empty ADD report makes the create-partitions
request unsafe to replay, so a 429 or 503 the client used to retry by itself now
surfaces to the caller instead of risking a double-counted increment. That is a
change to how an existing write path fails, and it should be opted into rather
than inherited. An increment can also drift for reasons no client can see: a
writer that is not Paimon, a file deleted out of band. Convergence is a later full
report over the same partition, which a follow-up will teach MSCK REPAIR and
ANALYZE to produce; a deduplication token in front of every commit would cost more
than the drift does.

Tests: FormatTableCommitStatisticsTest covers append, dynamic overwrite, static
prefix overwrite of a partition this commit does not write, the summation of the
independent increments of concurrent writers of one partition, a listing that
answers under another scheme, a nested directory in the value-only layout, and
reporting turned off.
A commit reports what it wrote. Nothing reports what is already there, and for a
format table plenty is: partitions written by something that is not Paimon, files
deleted out of band, an increment redelivered and counted twice. MSCK REPAIR
TABLE is already the command that reconciles the partition set against the
directories, so it is the natural place to reconcile the numbers too.

Off by default, behind spark.paimon.format-table.repair.collect-statistics,
because measuring changes what a repair costs: the plain diff lists partition
directories, and measuring lists the files inside every one of them. That is a
different order of magnitude on a table with many partitions, and a repair should
not silently become that. When it is on, spark.paimon.format-table.statistics.parallelism
caps how many partitions are measured at once, at 8: listing one is a round trip
the driver spends waiting on, and the cap keeps a table with many partitions from
turning that wait into a burst of requests.

When on it measures every partition that ends up registered with a directory
behind it, not only the ones it just added — the stale numbers of partitions
written outside Paimon are exactly what a repair exists to correct. Without ADD it
stays inside the already-registered set, so measuring never registers a partition
the command was not asked to.

The collector reports what a reader would see. File count, byte size and last file
creation time come from the listing. It stops there: the row count needs a file
footer, which is what ANALYZE is for. A listing failure aborts the whole
collection rather than reporting what it managed to see, because a truncated
listing is indistinguishable from a partition that lost files. A partition whose
directory is gone measures as an exact zero, with no last file to date.

Tests: FormatTablePartitionStatsCollectorTest covers the staging trees a committer
leaves behind, exact parquet row counts, an unreadable footer, a missing
directory, the one-for-one alignment of the result with the given specs, a spec
that omits a partition key, and a listing failure aborting the whole collection
on both the serial and the parallel path. FormatTablePartitionRepairTest covers
measuring every partition on disk rather than only the additions, a repair without
ADD registering nothing, and a listing failure leaving the catalog untouched.
CatalogManagedPartitionMsckRepairTest covers the command end to end with the
option off and on.
@sundapeng
sundapeng force-pushed the upstream/s5-measure-partitions-in-msck branch from e3ccd68 to 42f3394 Compare August 19, 2026 02:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant