Skip to main content

Query Performance

Start with the scan plan and the workload: which partitions and columns are filtered, whether rows still need merging, and whether the query is a scan, aggregate, join, or ranked search.

Symptom or query patternCheck firstNext step
MOR scan spends time mergingOverlapping sorted runs and bucket sizesTune compaction or evaluate a different table mode
Selective primary-key lookup scans too much dataPartition, bucket-key, and file key-range pruningPrimary-key filters
Filters on non-key columns scan many filesWhether versions must be merged before filteringFile indexes or clustering
Vector, text, or scalar index searchIndex family and coverage of compacted dataPrimary-Key Indexes
Large join shuffleFixed-bucket layout on both inputsBucketed Join

Table Mode​

Table Mode determines whether files can be read independently. MOR merges files with overlapping key ranges, which constrains split planning and parallelism. Bucket count and skew therefore matter, alongside the number of sorted runs.

MOW, fully compacted COW results, and the read-optimized table can avoid that merge work and use file-based splits. Parallelism still depends on file sizes, split planning, and available engine resources. Resolving row versions also makes filters on non-key columns safe to apply at the file scan; MOR generally has to apply such filters after merging.

Aggregate push down​

Deletion-vector tables can support metadata-based COUNT(*) when the planned splits expose exact merged row counts and the connector can push down the query. For example, with dt as a partition column:

SELECT COUNT(*) FROM orders WHERE dt = '20230101';

Additional row filters can require reading data. Use the engine's EXPLAIN output to verify the actual plan rather than assuming every count is answered from metadata.

Spark does not currently push MIN or MAX into metadata aggregation for primary-key tables. File statistics can describe obsolete physical rows as well as visible rows, so default metadata.stats-mode alone does not make this optimization available.

Data Skipping By Primary Key Filter​

For a fixed-bucket table, equality conditions that determine the bucket key can prune buckets. Partition filters prune partitions, and primary-key range statistics can prune files. The default bucket key excludes partition columns from the primary key.

A predicate on only part of a composite key does not necessarily identify a bucket. Primary-key sorting and key-range pruning are also affected by PK Clustering Override, which sorts files by other columns.

Data Skipping By File Index​

File indexes can narrow reads of fully compacted files or tables using deletion vectors, where row versions do not need to be merged across the filtered files. Paimon still applies the relevant row filters and deletion vectors for correctness.

IndexTable optionUseful predicate pattern
Bloom filterfile-index.bloom-filter.columnsEquality and point lookups
Bitmapfile-index.bitmap.columnsExact matches on indexed values
Range bitmapfile-index.range-bitmap.columnsRange predicates

A data file can have indexes for several columns. Small index data can be embedded in metadata; larger index data is stored alongside the data file. Size and selectivity determine whether an index saves enough scan work to justify its storage and maintenance cost.

To add file indexes to an existing table without rewriting its data files, configure the file-index.<type>.columns options and run rewrite_file_index; see Flink Procedures.

File indexes are separate from Primary-Key Indexes, whose index groups follow compacted data levels and support scalar predicates and ranked searches. Check that page's coverage rules, especially for Vector and Full Text search.

Bucketed Join​

Spark can use compatible fixed-bucket layouts to avoid a join shuffle. For example:

SET spark.sql.sources.v2.bucketing.enabled = true;

CREATE TABLE fact_table (order_id INT, f1 STRING) USING paimon
TBLPROPERTIES ('bucket' = '10', 'primary-key' = 'order_id');

CREATE TABLE dim_table (order_id INT, f2 STRING) USING paimon
TBLPROPERTIES ('bucket' = '10', 'primary-key' = 'order_id');

SELECT * FROM fact_table AS fact
JOIN dim_table AS dim ON fact.order_id = dim.order_id;

The setting enables Spark to use partitioning reported by V2 data sources. The join keys, bucket keys, and bucket counts must be compatible; the same bucket count alone is not sufficient. Check EXPLAIN to confirm that Spark avoids the shuffle for the actual query.