Skip to content

Spec: Add V4 column updates to the spec - #16425

Open
anuragmantri wants to merge 1 commit into
apache:mainfrom
anuragmantri:spec/column-updates-semantics
Open

anuragmantri wants to merge 1 commit into
apache:mainfrom
anuragmantri:spec/column-updates-semantics

Conversation

@anuragmantri

Copy link
Copy Markdown
Collaborator

These are the spec changes for V4 column updates from this proposal. This will be in conjunction with other V4 spec PRs #16025, #14234 and #15630

@anuragmantri
anuragmantri marked this pull request as draft May 19, 2026 14:42
@github-actions github-actions Bot added the Specification Issues that may introduce spec changes. label May 19, 2026
@anuragmantri
anuragmantri force-pushed the spec/column-updates-semantics branch from d997ce3 to 7f300b3 Compare June 1, 2026 16:58
Comment thread format/spec.md Outdated

* Rows in a column file with a null `_last_updated_sequence_number` inherit the base data file's (bumped) data sequence number. This applies to rows whose values were updated in the commit that produced the column file.
* Rows that carry over unchanged values from a prior column file retain their original `_last_updated_sequence_number`, which the writer materializes as a physical column in the column file.
* The `_row_id` for updated rows is unchanged; it is derived from the base data file's `first_row_id` and the row's position. Column files do not reassign row ids.

@Tishj Tishj Jun 18, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is confusing to me, it assumes that the _row_id value isn't materialized yet (and can therefore be derived from the "base" data_file), but I don't understand how we can make that assumption?

Wait.. I just realized, the _row_id of a data file should not be in a column-data-file.
I finally understand this section, but it's not worded very clearly.
I think it should instead say:
Column data files MUST NOT contain the _row_id field (field-id 2147483540). Regular "Row ID Assignment"(link to section) rules for reading apply.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, agreed. The section now says column files must not contain the reserved _row_id (or _pos) fields, and that adding column files doesn't change row IDs, so the regular Row Lineage rules apply. The old "derived from the base data file" wording is gone.

@RussellSpitzer RussellSpitzer added the Iceberg V4 Iceberg Table Format Version 4 label Jul 16, 2026
@github-actions

Copy link
Copy Markdown

This pull request has been marked as stale due to 30 days of inactivity. It will be closed in 1 week if no further activity occurs. If you think that’s incorrect or this pull request requires a review, please simply write any comment. If closed, you can revive the PR at any time and @mention a reviewer or discuss it on the dev@iceberg.apache.org list. Thank you for your contributions.

@github-actions github-actions Bot added the stale label Aug 16, 2026
@anuragmantri
anuragmantri force-pushed the spec/column-updates-semantics branch from e9e3ea8 to 850bb36 Compare October 6, 2026 03:30
@anuragmantri
anuragmantri force-pushed the spec/column-updates-semantics branch from 850bb36 to 89f73c3 Compare October 19, 2026 20:51

@anuragmantri anuragmantri left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I rebased this on main now that #16025 is merged and rewrote it as the column update rules.

Comment thread format/spec.md Outdated

* Rows in a column file with a null `_last_updated_sequence_number` inherit the base data file's (bumped) data sequence number. This applies to rows whose values were updated in the commit that produced the column file.
* Rows that carry over unchanged values from a prior column file retain their original `_last_updated_sequence_number`, which the writer materializes as a physical column in the column file.
* The `_row_id` for updated rows is unchanged; it is derived from the base data file's `first_row_id` and the row's position. Column files do not reassign row ids.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, agreed. The section now says column files must not contain the reserved _row_id (or _pos) fields, and that adding column files doesn't change row IDs, so the regular Row Lineage rules apply. The old "derived from the base data file" wording is gone.

@anuragmantri
anuragmantri marked this pull request as ready for review October 19, 2026 21:06
Comment thread format/spec.md

- `tracking`:
- `tracking.status` is MODIFIED.
- `tracking.snapshot_id` must not be null.

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.

Suggested change
- `tracking.snapshot_id` must not be null.
- `tracking.snapshot_id` must be the previous entry's (inherited) snapshot ID.

Comment thread format/spec.md
- `column_files`:
- New column files are added to the previous entry's `column_files`.
- Each updated field ID must be removed from the `field_ids` of the previous entry's column files, and a column file with no remaining `field_ids` must be removed.
- One of the new column files must have the `_last_updated_sequence_number` field ID in its `field_ids`. Its value must be null for each updated row, and must be the row's current `_last_updated_sequence_number`, read from the previous entry, for every other row that is not deleted.

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.

Suggested change
- One of the new column files must have the `_last_updated_sequence_number` field ID in its `field_ids`. Its value must be null for each updated row, and must be the row's current `_last_updated_sequence_number`, read from the previous entry, for every other row that is not deleted.
- One of the new column files must have the `_last_updated_sequence_number` field ID in its `field_ids`. Its value must be null for each updated row, and must be the row's current `_last_updated_sequence_number`, the non-null value a reader of the previous entry would see, for every other row that is not deleted.

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.

Shall we do this with the _last_update_time too?

Comment thread format/spec.md
- `sort_order_id`:
- If an updated field is, or contains, the source of a field in the previous entry's sort order, `sort_order_id` must be null.
- `content_stats`:
- Stats for each updated field, including its nested fields, must be computed from the values in the column file that has its ID in `field_ids`, or omitted. Stats for other fields may be kept.

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.

Do we want to talk about the stats of deleted rows?

Comment thread format/spec.md
- `content_stats`:
- Stats for each updated field, including its nested fields, must be computed from the values in the column file that has its ID in `field_ids`, or omitted. Stats for other fields may be kept.
- `deletion_vector`:
- `deletion_vector` must contain all deletes from the previous entry's `deletion_vector` and from delete files and deletion vectors in delete manifests that apply to the previous entry, including equality delete files.

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.

Is it discussed how a change data reader will detect that these rows were deleted previously, and they are not new deletes?

Comment thread format/spec.md

Starting in v4, the values of one or more fields in a data file can be updated without rewriting the data file by adding column files using the [`column_files`](#column-file-fields) field. This section describes column files for data files.

In this section, the **previous entry** is the live entry of the data file before a column update, the **MODIFIED entry** is the entry produced by the column update, an **updated field** is a field whose ID is in the `field_ids` of a column file added by the column update, and an **updated row** is a row modified by the column update.

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.

Suggested change
In this section, the **previous entry** is the live entry of the data file before a column update, the **MODIFIED entry** is the entry produced by the column update, an **updated field** is a field whose ID is in the `field_ids` of a column file added by the column update, and an **updated row** is a row modified by the column update.
In this section, the **previous entry** is the live entry of the data file before a column update, the **MODIFIED entry** is the entry produced by the column update, an **updated field** is a field whose ID is in the `field_ids` of a column file added by the column update, and an **updated row** is a row whose updated-field values were set by the update; other rows keep their current values.

@gaborkaszab gaborkaszab left a comment

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.

Thanks for the PR, @anuragmantri !
Great work! I've made my first pass now to get familiar.

Comment thread format/spec.md
* **Delete manifest** -- A file that lists delete files to be associated with data files at planning time.
* **Data file** -- A file that contains rows of a table.
* **Delete file** -- A file that encodes rows of a table that are deleted by position or data values.
* **Column file** -- (v4+) A file that stores the values of one or more fields for each row of a data file, or for each entry of a v4 leaf manifest.

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.

nit: for each row of and for each entry of seems an implementation detail that we decided on the dense representation. Might not be relevant here when we define column files.

Suggestion:
A file that stores the values of one or more fields of a data file, or a v4 leaf manifest ?

Comment thread format/spec.md

Table state is maintained in metadata files. All changes to table state create a new metadata file and replace the old metadata with an atomic swap. The table metadata file tracks the table schema, partitioning config, custom properties, and snapshots of the table contents. A snapshot represents the state of a table at some time and is used to access the complete set of data files in the table.

Data files in snapshots are tracked by one or more manifest files that contain a row for each data file in the table, the file's partition data, an optional colocated deletion vector (v4), and its metrics. The data in a snapshot is the union of all live files in its manifests; each live file may only appear once (see [Content file uniqueness](#content-file-uniqueness)). Manifest files are reused across snapshots to avoid rewriting metadata that is slow-changing. Manifests can track data files with any subset of a table and are not associated with partitions.

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.

Since we mention V4 and colocated deletion vectors already, shouldn't we also mention column files too?

Comment thread format/spec.md

Version 4 of the Iceberg spec restructures metadata for improved performance and new capabilities:

* Support for an [adaptive metadata tree](#manifests), enabling efficient small commits, column updates, and columnar statistics representation

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.

More general column files instead of column updates?

Comment thread format/spec.md

When `null`, a row's `_row_id` field is assigned to the `first_row_id` from its containing data file plus the row position in that data file (`_pos`). A data file's `first_row_id` field is assigned using inheritance and is documented in [First Row ID Inheritance](#first-row-id-inheritance). A manifest's `first_row_id` is assigned when writing the snapshot root file and is documented in [Snapshot Root File First Row ID Assignment](#snapshot-root-file-first-row-id-assignment). Starting in v4, a data file in the root manifest is assigned a `first_row_id` in the same way. A snapshot's `first-row-id` is set to the table's `next-row-id` and is documented in [Snapshot Row IDs](#snapshot-row-ids).

When an existing row is moved to a different data file for any reason, writers should write `_row_id` and `_last_updated_sequence_number` according to the following rules:

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.

This section explains what is expected when we write an entire row into a new file. Shouldn't we also cover the column update case here, mentioning that _row_id is not expected in the column file, but _lusn is a must?
I see a section below that covers this in the context of writing column files, but I believe that the "Row Lineage" section is more self-contained with covering column updates too.

Comment thread format/spec.md
### Partitioning

Data files are stored in manifests with a tuple of partition values that are used in scans to filter out files that cannot contain records that match the scan’s filter predicate. Partition values for a data file must be the same for all records stored in the data file. Manifests store data files from any partition. Manifests in v3 and earlier store files for a single spec. Starting in v4, manifests may store files from any spec. A manifest is considered partitioned by a spec if every entry in the manifest is partitioned by that spec.
Data files are stored in manifests with a tuple of partition values that are used in scans to filter out files that cannot contain records that match the scan’s filter predicate. Partition values for a data file must be the same for all records stored in the data file. For a data file with column files, this requirement applies only to rows that are not deleted, using values read with its column files. Manifests store data files from any partition. Manifests in v3 and earlier store files for a single spec. Starting in v4, manifests may store files from any spec. A manifest is considered partitioned by a spec if every entry in the manifest is partitioned by that spec.

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.

Maybe it's just me, but I don't get the point of this sentence:
For a data file with column files, this requirement applies only to rows that are not deleted, using values read with its column files.
Do you say that the invariant of all records in a data file should have the same partition values is not true when we have column files on the data file?

Comment thread format/spec.md
- If an updated field is, or contains, the source of a field in the previous entry's partition spec, `partition` must be the partition tuple shared by the rows that are not deleted, or `spec_id` and `partition` must be null.
- If `spec_id` and `partition` are set to null, each source field of an identity partition field in the previous entry's partition spec that is missing from the data file must be stored in a column file of the MODIFIED entry that has the source's top-level field ID in its `field_ids`.
- `sort_order_id`:
- If an updated field is, or contains, the source of a field in the previous entry's sort order, `sort_order_id` must be null.

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.

I find these explanations with "previous entry" and such a bit hard to read. Can't just simply say If we update one or more field that are part of the sort order fields, then sort_order_id must be set to null ?

Comment thread format/spec.md
- `content_stats`:
- Stats for each updated field, including its nested fields, must be computed from the values in the column file that has its ID in `field_ids`, or omitted. Stats for other fields may be kept.
- `deletion_vector`:
- `deletion_vector` must contain all deletes from the previous entry's `deletion_vector` and from delete files and deletion vectors in delete manifests that apply to the previous entry, including equality delete files.

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.

Can't we rephrase this to be simpler, like existing deletes on the data file should be written into deletion_vector including equality deletes ? Or something similar :)

Comment thread format/spec.md
- `deletion_vector`:
- `deletion_vector` must contain all deletes from the previous entry's `deletion_vector` and from delete files and deletion vectors in delete manifests that apply to the previous entry, including equality delete files.
- `column_files`:
- New column files are added to the previous entry's `column_files`.

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.

This sentence seems misleading, gives me the impression that we add the new column files into the previous entry, but we rather add them to the new entry.

Comment thread format/spec.md
- `column_files`:
- New column files are added to the previous entry's `column_files`.
- Each updated field ID must be removed from the `field_ids` of the previous entry's column files, and a column file with no remaining `field_ids` must be removed.
- One of the new column files must have the `_last_updated_sequence_number` field ID in its `field_ids`. Its value must be null for each updated row, and must be the row's current `_last_updated_sequence_number`, read from the previous entry, for every other row that is not deleted.

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.

"for every other row that is not deleted." I wouldn't mention deleted rows here, we don't really have ay assumptions to their _LUSN value. Maybe a simplified version:
Its value must be null for each updated row, and for the non-updated rows it must be the _last_updated_sequence_number read from the previous entry

Comment thread format/spec.md
- Each updated field ID must be removed from the `field_ids` of the previous entry's column files, and a column file with no remaining `field_ids` must be removed.
- One of the new column files must have the `_last_updated_sequence_number` field ID in its `field_ids`. Its value must be null for each updated row, and must be the row's current `_last_updated_sequence_number`, read from the previous entry, for every other row that is not deleted.

Deletion vectors in delete manifests that reference the data file must be removed (see [Deletion Vectors](#deletion-vectors)). After column files are added, delete files in delete manifests do not apply to the data file (see [Scan Planning](#scan-planning)).

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.

Is this true? Don't we guarantee this by bumping the data sequence number?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Iceberg V4 Iceberg Table Format Version 4 not-stale Specification Issues that may introduce spec changes.

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

5 participants