Skip to main content

Primary-Key Tables

A primary-key table maintains one logical row per key. Incoming records can insert, update, or remove that row according to the table's merge engine. Multiple physical versions may remain in data files until they are merged during reads or compaction.

Use a primary-key table for CDC replication, upsert pipelines, or combining updates to the same entity. For data that only grows without merging by key, see Append Table.

Quick Start​

The following Flink SQL table keeps the latest row for each order_id:

CREATE TABLE orders (
order_id BIGINT,
customer_id BIGINT,
amount DECIMAL(12, 2),
PRIMARY KEY (order_id) NOT ENFORCED
) WITH (
'bucket' = '4'
);

INSERT INTO orders VALUES (1, 101, 12.00);
INSERT INTO orders VALUES (1, 101, 15.00);

SELECT * FROM orders;
-- 1, 101, 15.00

This example uses four fixed buckets, the default deduplicate merge engine, and the default merge-on-read storage mode. Omitting bucket selects dynamic buckets. See Data Distribution before choosing a bucket mode for your workload, and Sequence Field when updates can arrive out of order.

Design a Primary-Key Table​

These settings answer different questions. Choose them together, checking each feature's compatibility requirements.

DecisionWhat it controlsRead next
Partition and bucket keysWhere a row is stored and how work is distributedData Distribution
Merge engineHow records with the same key form a logical rowMerge Engine
Sequence and row kindWhich update takes precedence and whether it inserts or retractsSequence and Row Kind
Table modeWhether readers merge versions, read compacted files, or apply deletion vectorsTable Mode
Changelog producerWhich changes a streaming consumer receivesChangelog Producer

Then tune Compaction and Query Performance. For specialized workloads, see Primary-Key Indexes, PK Clustering Override, Chain Table, and BLOB Storage.

Bucket​

A table, or each partition of a partitioned table, contains buckets. Each bucket has its own LSM tree of data files. Changelog files and indexes may accompany those data files, depending on the configuration.

A partition contains independent buckets, each with Level-0 files and higher-level sorted runs.

In fixed-bucket mode, Paimon hashes the bucket-key columns to select a bucket. If bucket-key is not set, it uses the primary-key columns excluding partition columns. Dynamic and postpone buckets use different assignment mechanisms; see Data Distribution.

Buckets distribute writes and affect read parallelism, but are not a universal one-reader limit: Table Mode explains when files can be read independently. Use the bucket sizing guidance to balance parallelism against small-file overhead.

LSM Trees​

Paimon stores primary-key data in an LSM tree (log-structured merge-tree). Writers buffer records, sort them, and flush new files. Compaction merges files to reduce the work needed by future reads.

Sorted Runs​

A sorted run contains one or more data files. In the default primary-key layout, records in each file are sorted by key, and file key ranges do not overlap within the same run. Different runs can overlap and contain different versions of the same key.

Files have disjoint key ranges within a sorted run; key 7 occurs in two different runs.

A merge-on-read scan combines the relevant runs and applies the merge engine and record ordering to records with the same key. The result contains one logical row per key, even if several physical versions exist.

Each Level-0 file is a separate sorted run; each higher level forms a run of non-overlapping files. Compaction keeps the number of runs manageable. PK Clustering Override is a specialized layout that changes the physical sort order and resolves row versions using deletion vectors.

Nullable Primary Keys​

Primary key fields are NOT NULL by default. Set primary-key.nullable to true when a source system can produce null key components:

CREATE TABLE nullable_orders (
order_id BIGINT,
payload STRING
) WITH (
'primary-key' = 'order_id',
'primary-key.nullable' = 'true'
);

Null key components use null-safe equality. For example, two records whose key is (1, NULL) are treated as the same key and are merged by the configured merge engine. The option is disabled by default and cannot be changed after the table has snapshots.

In Flink, define a nullable Paimon primary key with the primary-key table option as shown above. The standard SQL PRIMARY KEY constraint implies NOT NULL, so Paimon does not expose a nullable key as a Flink SQL primary-key constraint.

Flink streaming reads that emit updates or deletes require a full changelog producer, for example changelog-producer=input. The default changelog-producer=none produces an upsert changelog, which Flink can normalize only when the table exposes a SQL primary-key constraint. Because a nullable key cannot be exposed as that constraint, Paimon rejects this streaming-read combination instead of producing an invalid Flink plan. Insert-only streaming reads, such as tables using the first-row merge engine, are not affected.