Fix multi-schema replication intermediates dropped on snapshots-endpoint commits#603
Conversation
…int commits Replication commits arriving through PUT /iceberg/v2/snapshots with multi-schema-jump intermediate schemas were silently losing them due to two issues that combine to leave the dest replica with a snapshot whose schema-id points at a schema not present in schemas[]. Trino strictly resolves snapshot schema-id against schemas[] and fails with "Cannot find schema with schema id N"; Spark falls back to current-schema-id and reads succeed. Two fixes: 1. TablesMapper#toTableDto(TableDto, IcebergSnapshotsRequestBody) was missing the @mapping for newIntermediateSchemas. The CreateUpdateTableRequestBody overload had it (added in linkedin#407), but the IcebergSnapshotsRequestBody overload used for the snapshots endpoint did not — so intermediates packed correctly by the client got dropped at the server-side DTO mapping step. 2. OpenHouseTableOperations#doCommit (after linkedin#524 introduced putSnapshotsForReplace) was routing cross-cluster REPLICA_TABLE replication commits through the RTAS replace path because they update both metadata and snapshots in one commit. The server's REPLACE branch treats the commit as a fresh table creation and uses CLIENT_TABLE_SCHEMA (bootstrap-era schema) as the final schema, not the request's actual target. This produces a different but related corruption pattern. Detect REPLICA commits (base REPLICA_TABLE + metadata PRIMARY_TABLE + different cluster IDs) and route them through the regular putSnapshots path so the intermediate-schemas plumbing fires. Both fixes are needed in tandem: without (1), even the regular update branch drops intermediates; without (2), commits hitting the post-linkedin#524 client get routed around (1) entirely. Unit tests: - TablesMapperTest#testToTableDtoWithPutSnapshotsPreservesNewIntermediateSchemas pins the mapper extraction. - OpenHouseTableOperationsTest#testDoCommitRoutesReplicaTableThroughPutSnapshotsNotReplace pins REPLICA dispatch goes to regular putSnapshots (replaceCommit=false). - OpenHouseTableOperationsTest#testDoCommitRoutesPrimaryRtasThroughPutSnapshotsForReplace pins genuine RTAS on PRIMARY tables still uses the replace path.
| boolean isReplicaCommit = | ||
| getTableType(base, metadata) == CreateUpdateTableRequestBody.TableTypeEnum.REPLICA_TABLE; | ||
| if (metadataUpdated && snapshotsUpdated && base != null && !isReplicaCommit) { |
There was a problem hiding this comment.
What makes a replica commit different that we need to special case?
There was a problem hiding this comment.
Does that mean that this excludes RTAS commit for replica table?
| // the replace path treats the commit as a fresh table creation and does not preserve | ||
| // multi-schema delta semantics. Reserve putSnapshotsForReplace for genuine CTAS/RTAS, where | ||
| // the metadata being committed is PRIMARY (not a REPLICA_TABLE update). | ||
| boolean isReplicaCommit = |
There was a problem hiding this comment.
what about if replica, then putsnapshots?
| boolean isReplicaCommit = | ||
| getTableType(base, metadata) == CreateUpdateTableRequestBody.TableTypeEnum.REPLICA_TABLE; | ||
| if (metadataUpdated && snapshotsUpdated && base != null && !isReplicaCommit) { | ||
| // Only CTAS and RTAS can update both metadata and snapshots at the same time. |
There was a problem hiding this comment.
this comment is no longer accurate.
| .get(OPENHOUSE_CLUSTER_ID_KEY) | ||
| .equals(metadata.properties().get(OPENHOUSE_CLUSTER_ID_KEY))) { | ||
| return baseTableType; | ||
| String baseTypeStr = base.properties().get(OPENHOUSE_TABLE_TYPE_KEY); |
There was a problem hiding this comment.
Why might this not exist?
| @VisibleForTesting | ||
| CreateUpdateTableRequestBody.TableTypeEnum getTableType( | ||
| TableMetadata base, TableMetadata metadata) { | ||
| String metaTypeStr = metadata.properties().get(OPENHOUSE_TABLE_TYPE_KEY); |
There was a problem hiding this comment.
nit, I would move this to after the if so on the case where base is null its not evaluated.
| if (baseTableType == CreateUpdateTableRequestBody.TableTypeEnum.REPLICA_TABLE | ||
| && metadataTableType == CreateUpdateTableRequestBody.TableTypeEnum.PRIMARY_TABLE | ||
| && baseCluster != null | ||
| && !baseCluster.equals(metaCluster)) { | ||
| return baseTableType; | ||
| } |
There was a problem hiding this comment.
This logic does not makse sense to me. Why can we not trust the table type?
There was a problem hiding this comment.
Trying to understand the change here. Seems like this change is applicable only for replica table to determine table type?
Summary
Replication commits arriving through PUT /iceberg/v2/snapshots with multi-schema-jump intermediate schemas were silently losing them due to two issues that combine to leave the dest replica with a snapshot whose schema-id points at a schema not present in schemas[]. Trino strictly resolves snapshot schema-id against schemas[] and fails with "Cannot find schema with schema id N"; Spark falls back to current-schema-id and reads succeed.
Two fixes:
TablesMapper#toTableDto(TableDto, IcebergSnapshotsRequestBody) was missing the @mapping for newIntermediateSchemas. The CreateUpdateTableRequestBody overload had it (added in Add multi schema update support for tables during replica table commits #407), but the IcebergSnapshotsRequestBody overload used for the snapshots endpoint did not — so intermediates packed correctly by the client got dropped at the server-side DTO mapping step.
OpenHouseTableOperations#doCommit (after [RTAS] Add client side support (part 3) #524 introduced putSnapshotsForReplace) was routing cross-cluster REPLICA_TABLE replication commits through the RTAS replace path because they update both metadata and snapshots in one commit. The server's REPLACE branch treats the commit as a fresh table creation and uses CLIENT_TABLE_SCHEMA (bootstrap-era schema) as the final schema, not the request's actual target. This produces a different but related corruption pattern. Detect REPLICA commits (base REPLICA_TABLE + metadata PRIMARY_TABLE + different cluster IDs) and route them through the regular putSnapshots path so the intermediate-schemas plumbing fires.
Both fixes are needed in tandem: without (1), even the regular update branch drops intermediates; without (2), commits hitting the post-#524 client get routed around (1) entirely.
Changes
For all the boxes checked, please include additional details of the changes made in this pull request.
Testing Done
For all the boxes checked, include a detailed description of the testing done for the changes made in this pull request.
Additional Information
For all the boxes checked, include additional details of the changes made in this pull request.