Skip to content

[core][spark] Support ANALYZE TABLE on catalog-managed format tables - #9298

Open
sundapeng wants to merge 1 commit into
apache:masterfrom
sundapeng:upstream/s6-analyze-format-table-partitions
Open

[core][spark] Support ANALYZE TABLE on catalog-managed format tables#9298
sundapeng wants to merge 1 commit into
apache:masterfrom
sundapeng:upstream/s6-analyze-format-table-partitions

Conversation

@sundapeng

@sundapeng sundapeng commented Aug 18, 2026

Copy link
Copy Markdown
Member

Purpose

The catalog statistics of a Format Table are written by whoever touched it: a commit reports what
it wrote, and MSCK REPAIR TABLE measures what it registers. Neither answers for a partition
written by something the catalog never saw, and there was no statement that recomputes one, because
Spark rejects ANALYZE TABLE for every v2 table in the analyzer.

ANALYZE TABLE t [PARTITION (...)] COMPUTE STATISTICS [NOSCAN] now measures the registered
partitions of a Format Table and writes the result back. NOSCAN stops at the directory listing -
file count, byte size, last file creation time - while a full ANALYZE also reads each file footer
for its row count, exact for the formats that carry one and left as it was for the ones that do
not. A PARTITION (...) clause selects the partitions whose leading values it fixes, the shape the
catalog can select on; naming a partition the table does not have is NoSuchPartitionException,
as on any other table.

Analyzing measures partitions and never adds or removes one - MSCK REPAIR TABLE is what does
that. A Format Table has nowhere to keep column statistics, so FOR [ALL] COLUMNS keeps Spark's
own rejection, and so does a table discovering its partitions from the filesystem, which has no
catalog to write to.

Listing a partition is one request, reading a footer is one per file, and both go to the same pool,
so format-table.statistics.parallelism applies to a single large partition as much as to many
small ones.

Tests

FormatTablePartitionStatsCollectorTest covers the measurement: exact row counts, an unreadable
footer poisoning only the partition it is in, an empty partition counting as zero, and counts
staying with their partition when the pool measures several at once.
CatalogManagedPartitionAnalyzeTest covers the statement end to end against the semantics Spark
gives it on a metastore table - partition selection, name resolution under both case sensitivities,
NOSCAN, repeated runs, and the forms that are rejected.

@sundapeng sundapeng changed the title [spark] Support ANALYZE TABLE on catalog-managed format tables [wip][spark] Support ANALYZE TABLE on catalog-managed format tables Aug 18, 2026
@sundapeng sundapeng changed the title [wip][spark] Support ANALYZE TABLE on catalog-managed format tables [WIP][spark] Support ANALYZE TABLE on catalog-managed format tables Aug 18, 2026
@sundapeng
sundapeng force-pushed the upstream/s6-analyze-format-table-partitions branch 10 times, most recently from 140cff9 to 573e681 Compare August 19, 2026 12:48
@sundapeng
sundapeng force-pushed the upstream/s6-analyze-format-table-partitions branch from 573e681 to f24cdad Compare August 19, 2026 17:26
@JingsongLi
JingsongLi marked this pull request as draft August 20, 2026 01:05
@sundapeng
sundapeng force-pushed the upstream/s6-analyze-format-table-partitions branch 9 times, most recently from b8719f9 to 80eb40b Compare August 21, 2026 02:22
The catalog statistics of a Format Table are written by whoever touched
it: a commit reports what it wrote, and MSCK REPAIR TABLE measures what
it registers. Neither answers for a partition written by something the
catalog never saw, and Spark rejects ANALYZE TABLE for every v2 table in
the analyzer, so no statement recomputed one.

ANALYZE TABLE t [PARTITION (...)] COMPUTE STATISTICS [NOSCAN] now
measures the registered partitions and writes the result back. NOSCAN
stops at the directory listing - file count, byte size, last file
creation time - while a full ANALYZE also reads each file footer for its
row count, exact for the formats that carry one and left as it was for
the ones that do not. A PARTITION (...) clause selects the partitions
whose leading values it fixes, the shape the catalog can select on, and
naming a partition the table does not have is a NoSuchPartitionException
as it is on any other table.

Analyzing measures partitions and never adds or removes one. A Format
Table has nowhere to keep column statistics, so FOR [ALL] COLUMNS keeps
Spark's own rejection, and so does a table discovering its partitions
from the filesystem, which has no catalog to write to.

Listing a partition is one request and reading a footer is one per file,
and both go to the same pool, so format-table.statistics.parallelism
applies to a single large partition as much as to many small ones.
@sundapeng
sundapeng force-pushed the upstream/s6-analyze-format-table-partitions branch from 80eb40b to ea9333b Compare August 21, 2026 06:48
@sundapeng sundapeng changed the title [WIP][spark] Support ANALYZE TABLE on catalog-managed format tables [core][spark] Support ANALYZE TABLE on catalog-managed format tables Aug 21, 2026
@sundapeng
sundapeng marked this pull request as ready for review August 21, 2026 06:48
valueByKey.filter(_._2.isDefined).map(_._1).mkString("[", ", ", "]"))
}
// Kept in partition-key order, so a message built from it reads in that order too.
ListMap(prefix.map { case (key, value) => key -> value.get }: _*)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] normalizePartitionSpec only resolves partition column names; it does not cast or canonicalize their values, so this prefix still contains the raw parser strings. For example, an INT partition registered as p=1 is not found by ANALYZE ... PARTITION (p = '01'), because the catalog is queried with p=01 and the command throws NoSuchPartitionException. Null or empty values likewise are not converted to the configured default partition name. Please cast the specified values using v2Table.partitionSchema under Spark SQL semantics and reuse the existing toPaimonPartition/InternalRowPartitionComputer conversion before building the catalog prefix; tests should cover numeric canonicalization and null/default partitions.

List<Future<Long>> counts = new ArrayList<>(partitionFiles.size());
for (FileStatus file : partitionFiles) {
if (rowCounter != null) {
counts.add(executor.submit(() -> rowCount(rowCounter, file)));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Please run full-ANALYZE footer reads on Spark executors. This parallelizes files, but every footer open still runs in a driver-local pool, and the driver eagerly retains one FileStatus plus one Future for every data file before aggregation. A format table with hundreds of thousands or millions of files can therefore make ANALYZE driver-bound or exhaust the driver heap while executor capacity is idle. The Spark layer could batch file descriptors and process them with RDD mapPartitions, creating FileIO and the stats extractor once per task and reducing partial results per catalog partition; format-table.statistics.parallelism can bound the RDD partitions/storage concurrency. The engine-neutral footer parsing and merge logic can remain in paimon-core, and NOSCAN can keep the local path.

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.

2 participants