tomtongue opened a new pull request, #16859:
URL: https://github.com/apache/iceberg/pull/16859

   ## Overview
   
   This PR adds a read-only `table_properties_log` metadata table that exposes 
the history of a table's effective properties from Iceberg metadata files.
   
   For each retained metadata version, the table returns the metadata file 
timestamp, the metadata file location, the snapshot that was current in that 
metadata version, and the full `properties` map recorded in that 
`metadata.json`.
   
   This makes table-property history queryable from the table's own metadata. 
It allows users to answer questions such as which properties were in effect 
when a snapshot became current, when a property change appeared in table 
metadata, and whether a later incident or behavior change lines up with a 
table-property update.
   
   The change is read-only and engine-level. It does not change the table spec, 
does not require a format-version bump, and does not change the write path. The 
new table is registered through the existing metadata-table mechanism, so it is 
available to Spark and Flink using their existing metadata-table syntax.
   
   ## Motivation
   
   Iceberg versions table state through immutable metadata files. Schemas, 
partition specs, sort orders, snapshots, `snapshot-log` entries, and 
`metadata-log` entries are retained as part of table metadata. However, 
`TableMetadata.properties` is represented as the current effective property map 
in each metadata file. When a property is changed, the current metadata points 
at the new map, and there is no direct query surface for comparing that map 
across previous metadata versions.
   
   * The historical values are still present as long as the corresponding 
metadata files are retained. Each historical `metadata.json` contains the full 
property map for that metadata version, and `TableMetadata.previousFiles()` 
records the retained previous metadata files. Using that information requires 
locating and parsing those files manually today.
   
   * That gap matters for operational debugging and audit-style questions. For 
example, an operator may need to determine whether `gc.enabled` was enabled 
before an expiration run, whether a performance regression started after 
`write.target-file-size-bytes` or `write.distribution-mode` changed, or whether 
a read behavior change lines up with `write.delete.mode`, `write.update.mode`, 
or `write.merge.mode` and more.
   
   `table_properties_log` exposes the history Iceberg already retains, without 
adding new persisted metadata and without requiring out-of-band catalog logs.
   
   ## Use-cases
   
   The primary use-cases are point-in-time inspection of table configuration 
from Iceberg metadata.
   
   * **Audit and forensic checks.** Query whether a property had a specific 
value in a retained metadata version, including values that were later changed 
back.
   * **Incident RCA.** Correlate a behavior change with the metadata version 
where a property first changed.
   * **Performance tuning.** Verify that tuning properties took effect and 
compare the values used before and after a change.
   * **Engine-agnostic access.** Use the same core metadata table from Spark 
and Flink through the existing metadata-table resolution path.
   
   ## Changes
   
   This PR makes the following changes:
   
   * Adds `TablePropertiesLogTable` in core.
     * The table is modeled on `MetadataLogEntriesTable` and the existing 
static metadata-table pattern.
     * It reads `TableMetadata.previousFiles()` and appends the current 
metadata file as the latest row.
     * Each row is built by reading the corresponding `metadata.json` with 
`TableMetadataParser.read`.
   
   * Adds `MetadataTableType.TABLE_PROPERTIES_LOG`.
     * The metadata table can be resolved by name through 
`MetadataTableType.from`.
   
   * Registers `TABLE_PROPERTIES_LOG` in `MetadataTableUtils`.
     * Spark can query it as `<table>.table_properties_log`.
     * Flink can query it as `<table>$table_properties_log`.
     * No Spark-specific or Flink-specific implementation is needed.
   
   * Exposes the following schema:
   
     | Column | Type | Description |
     | --- | --- | --- |
     | `timestamp` | `timestamp with zone` | Timestamp of the metadata log 
entry |
     | `file` | `string` | Location of the `metadata.json` file |
     | `latest_snapshot_id` | `long` | Snapshot that was current in that 
metadata version; `null` before the first snapshot |
     | `properties` | `map<string, string>` | Effective table properties stored 
in that metadata version |
   
   * Add the tests verifying the metadata table query results to Spark 4.1 and 
Flink 2.1
   
   Note that:
   
   * The visible history is bounded by retained metadata files. In practice 
this is controlled by metadata retention settings such as 
`write.metadata.previous-versions-max` and whether old metadata files are 
deleted after commit.
   * The table reads one retained `metadata.json` per emitted metadata version. 
This is bounded by the metadata log exposed by the current metadata, not by 
every metadata file ever written for the table.
   * Predicate filtering is handled by the metadata-table scan path and 
engines; this PR does not add predicate pushdown into historical metadata-file 
reads.
   * `latest_snapshot_id` is the snapshot current in that metadata version. A 
metadata-only property update can therefore produce a row with the same 
`latest_snapshot_id` as the previous row.
   
   ### Example: Query on TablePropertiesLogTable with Spark
   
   ```sql
   CREATE TABLE props_log (id int, name string) USING iceberg TBLPROPERTIES 
('key1'='value1')";
   
   SELECT * FROM props_log.table_properties_log;
   /*
   
+-----------------------+---------------------------------------------------------------------------------------------+------------------+-------------------------------------------------------------------------+
   |timestamp              |file                                                
                                         |latest_snapshot_id|properties         
                                                      |
   
+-----------------------+---------------------------------------------------------------------------------------------+------------------+-------------------------------------------------------------------------+
   |2026-06-17 
15:24:33.798|s3://warehouse/db/props_log/metadata/00000-059abc2d-e741-4465-ae11-121b8023d380.metadata.json|NULL
              |{key1 -> value1, owner -> spark, write.parquet.compression-codec 
-> zstd}|
   
+-----------------------+---------------------------------------------------------------------------------------------+------------------+-------------------------------------------------------------------------+
   */
   
   ALTER TABLE db.props_log SET TBLPROPERTIES('key2'='value2');
   
   INSERT INTO db.props_log VALUES (1, 'a');
   
   /*
   
+-----------------------+---------------------------------------------------------------------------------------------+-------------------+-----------------------------------------------------------------------------------------+
   |timestamp              |file                                                
                                         |latest_snapshot_id |properties        
                                                                       |
   
+-----------------------+---------------------------------------------------------------------------------------------+-------------------+-----------------------------------------------------------------------------------------+
   |2026-06-17 
15:24:33.798|s3://warehouse/db/props_log/metadata/00000-059abc2d-e741-4465-ae11-121b8023d380.metadata.json|NULL
               |{key1 -> value1, owner -> spark, 
write.parquet.compression-codec -> zstd}                |
   |2026-06-17 
15:25:17.637|s3://warehouse/db/props_log/metadata/00001-28f3023b-423f-4b4c-bc18-ef93596baffe.metadata.json|NULL
               |{key1 -> value1, owner -> spark, key2 -> value2, 
write.parquet.compression-codec -> zstd}|
   |2026-06-17 
15:26:09.574|s3://warehouse/db/props_log/metadata/00002-6b48f0eb-a243-4146-9890-0400deba828b.metadata.json|1312307084484665830|{key1
 -> value1, owner -> spark, key2 -> value2, write.parquet.compression-codec -> 
zstd}|
   
+-----------------------+---------------------------------------------------------------------------------------------+-------------------+-----------------------------------------------------------------------------------------+
   
   */
   ```
   
   ## Alternatives Considered
   
   * **Record table properties into each snapshot summary at commit time.**: 
This was the original design direction, and the full design is captured in 
[Preserving Table Property History in Snapshot 
Summary](https://docs.google.com/document/d/1w3dQ_-m6rqSpsaLGuXTSUYVBNQn3UiYBCWEgt9ztTJo/edit?tab=t.0).
 That approach would make property history available from `<table>.snapshots` 
and tie values directly to snapshot retention. However, it requires a 
write-path change and a spec addition for reserved snapshot-summary keys. It 
also needs pointer compression or another size-control mechanism to avoid 
repeating the full property map on every snapshot. The current PR avoids those 
concerns by exposing property history already present in retained metadata 
files.
   
   * **Add a new top-level property-history field to `TableMetadata`.**: 
Another option is to add a dedicated history structure to table metadata, such 
as a list of property-map versions or property deltas. That would make the 
history independent of metadata-log retention and could support direct point 
lookups without reading historical metadata files. However, it would introduce 
new persisted table metadata that every reader must preserve correctly, and it 
would require defining retention, compatibility, and upgrade behavior for that 
new structure. It is also heavier than necessary for this PR because Iceberg 
already stores the complete property map in each retained `metadata.json`. A 
read-only metadata table exposes that existing information without changing the 
table metadata format.
   
   ## Testing
   
   Run the following specific tests:
   
   - `./gradlew :iceberg-spark:iceberg-spark-extensions-4.1_2.13:test --tests 
"org.apache.iceberg.spark.extensions.TestMetadataTables.testTablePropertiesLog"`
   - `./gradlew :iceberg-flink:iceberg-flink-2.1:test --tests 
"org.apache.iceberg.flink.source.TestFlinkMetaDataTable.testTablePropertiesLog"`
   
   ## AI Assistance
   
   I used Claude Code and Codex while preparing this PR. For the code changes, 
I used them to explore the existing codebase and to review my implementation, 
including checking whether it followed existing coding patterns, whether the 
implementation and wording were consistent with nearby code, and whether there 
were simpler alternatives. For the PR description, I used them as reviewers to 
refine the text and point out missing context. I reviewed the final code and PR 
text myself and am responsible for the submitted changes.
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to