Skip to main content

Sequence and Row Kind

Record ordering and row kind answer different questions: which update takes precedence, and whether that update inserts or retracts a row. Configure them alongside the merge engine.

OptionUse
sequence.fieldOrder updates by one or more source fields
rowkind.fieldDecode an operation such as +I or -D from a column
sequence.snapshot-orderingOrder updates across writers by commit snapshot ID

Partial updates from independent streams can use sequence groups instead of one shared sequence field.

Sequence Field

By default, Paimon assigns internal sequence numbers to order records during merging. This order does not necessarily match the order in the source system, especially when updates arrive late or come from multiple writers. Set sequence.field to a source version or timestamp when the source defines which update should take precedence.

For example, this Flink SQL table uses the source version to keep the latest order status:

CREATE TABLE order_status (
order_id BIGINT PRIMARY KEY NOT ENFORCED,
status STRING,
source_version BIGINT
) WITH (
'sequence.field' = 'source_version'
);

INSERT INTO order_status VALUES (1, 'shipped', 12), (1, 'paid', 11);

-- After the INSERT job finishes, query in batch mode.
SELECT * FROM order_status;
-- 1, shipped, 12

The default sequence.field.sort-order = ascending merges the largest value last. With the default deduplicate engine, version 12 wins even if version 11 arrives later. Setting sequence.field.sort-order = descending reverses that comparison, so the smallest value wins for deduplicate.

For multiple fields, such as 'sequence.field' = 'update_time,source_offset', Paimon compares the first field, then uses the next field to break ties. If all configured values are equal, Paimon falls back to its internal record order. Use a source tie-breaker when the result must be independent of arrival order.

The merge engine still determines how ordered records are combined. Sequence fields do not turn an aggregation engine into deduplication, or guarantee a complete historical ordering for order-dependent field aggregates such as first_value.

Restrictions:

  • sequence.field cannot be combined with merge-engine = first-row, cross-partition updates, or sequence.snapshot-ordering.
  • Do not assign an aggregate-function to a sequence field itself.
  • GEOMETRY, GEOGRAPHY, and managed BLOB fields cannot be sequence fields.

Row Kind Field

By default, Paimon uses the input row's row kind. Set rowkind.field when the source instead encodes each operation in a data column. The configured column must have a character-string type and contain a non-null operation value on every input row.

ValueRow kindMeaning
+IINSERTAdd a row or contribution
-UUPDATE_BEFORERetract the old row or contribution
+UUPDATE_AFTERAdd the updated row or contribution
-DDELETERetract a row or contribution

The merge engine determines how each kind affects stored state; see its delete and retraction requirements before choosing an input encoding.

For example, the following Flink SQL table accepts upserts and deletes encoded in op. Wait for each bounded INSERT job to finish before running the next statement, and run the queries in batch mode.

CREATE TABLE order_changes (
order_id BIGINT PRIMARY KEY NOT ENFORCED,
status STRING,
source_version BIGINT,
op STRING NOT NULL
) WITH (
'sequence.field' = 'source_version',
'rowkind.field' = 'op'
);

INSERT INTO order_changes VALUES (1, 'paid', 11, '+I');
INSERT INTO order_changes VALUES (1, 'shipped', 12, '+U');

SELECT order_id, status FROM order_changes;
-- 1, shipped

INSERT INTO order_changes VALUES (1, 'shipped', 13, '-D');

SELECT order_id, status FROM order_changes;
-- No rows

The operation column remains a regular stored column; Paimon does not remove it from the schema. Use the short values in the table above, without surrounding whitespace. Long names such as INSERT and DELETE are not accepted.

Snapshot Ordering

Use sequence.snapshot-ordering = true when updates from multiple writers should be ordered by their commit snapshot ID. Records in later snapshots are considered newer, regardless of the writers' per-record sequence numbers.

For example, with deduplicate, a row committed in snapshot 41 takes precedence over a row with the same key in snapshot 40, even if the writer of snapshot 40 assigned a larger internal sequence number. This follows commit order, which may differ from source event time.

Configure the option when creating the table. This Flink SQL example uses fixed buckets for multiple writers:

CREATE TABLE committed_orders (
order_id BIGINT PRIMARY KEY NOT ENFORCED,
status STRING
) WITH (
'bucket' = '4',
'sequence.snapshot-ordering' = 'true',
'write-only' = 'true'
);
Requirement or boundaryMeaning
Primary-key table; immutable optionChoose this ordering strategy at table creation
write-only = true for writersRun a separate dedicated compaction job to preserve snapshot ordering during compaction
No sequence.fieldSource-field ordering and snapshot ordering are mutually exclusive
No ordering within one snapshotDo not rely on this option to choose among multiple versions of the same key in the same snapshot

Writer buffers can merge versions before flushing, but they do not establish a guaranteed order across files within a snapshot. Size and spill settings do not change this ordering contract.