{"record":{"id":"15e0221648cb989c","repo":"prestodb/presto","slug":"cannot-commit-stale-table-metadata-for-s","errorCode":null,"errorMessage":"Cannot commit: stale table metadata for %s","messagePattern":"Cannot commit: stale table metadata for (.+?)","errorType":"exception","errorClass":"CommitFailedException","httpStatus":null,"severity":"warning","filePath":"presto-iceberg/src/main/java/com/facebook/presto/iceberg/HiveTableOperations.java","lineNumber":240,"sourceCode":"\n        String metadataLocation = table.getParameters().get(METADATA_LOCATION);\n        if (metadataLocation == null) {\n            throw new PrestoException(ICEBERG_INVALID_METADATA, format(\"Table is missing [%s] property: %s\", METADATA_LOCATION, getSchemaTableName()));\n        }\n\n        refreshFromMetadataLocation(metadataLocation);\n\n        return currentMetadata;\n    }\n\n    @Override\n    public void commit(@Nullable TableMetadata base, TableMetadata metadata)\n    {\n        requireNonNull(metadata, \"metadata is null\");\n\n        // if the metadata is already out of date, reject it\n        if (!Objects.equals(base, current())) {\n            throw new CommitFailedException(\"Cannot commit: stale table metadata for %s\", getSchemaTableName());\n        }\n\n        // if the metadata is not changed, return early\n        if (Objects.equals(base, metadata)) {\n            return;\n        }\n\n        String newMetadataLocation = writeNewMetadata(metadata, version + 1);\n\n        Table table;\n        boolean useHMSLock = Optional.ofNullable(metadata.property(TableProperties.HIVE_LOCK_ENABLED, null))\n                .map(Boolean::parseBoolean)\n                .orElse(config.getLockingEnabled());\n        try (HiveMetastoreLock ignored = HiveMetastoreLock.acquire(metastore, metastoreContext, useHMSLock, database, tableName)) {\n            try {\n                if (base == null) {\n                    String tableComment = metadata.properties().get(TABLE_COMMENT);\n                    Map<String, String> parameters = new HashMap<>();","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/prestodb/presto/blob/55bb57d202de3b926896fa966c2c4a44c779634e/presto-iceberg/src/main/java/com/facebook/presto/iceberg/HiveTableOperations.java#L222-L258","documentation":"HiveTableOperations.commit implements Iceberg's optimistic concurrency: it first checks that the base metadata the writer started from equals the table's current() metadata. If the table advanced meanwhile, it throws CommitFailedException 'Cannot commit: stale table metadata for <schema.table>' so the Iceberg layer can retry the operation against fresh metadata. This is an expected conflict signal, not corruption.","triggerScenarios":"Two concurrent commits to the same table (e.g. two compaction jobs, writer + maintenance), or a long-lived operation whose cached base metadata was invalidated by an external commit (Spark, Flink, another Presto worker) before commit is attempted.","commonSituations":"Parallel streaming writers (Flink/Spark) writing to the same table, manually running maintenance while jobs write, metastore lock issues causing missed updates, long transactions spanning another engine's commits.","solutions":["Retry the operation — Iceberg's commit machinery is designed to retry on CommitFailedException with refreshed metadata.","Reduce concurrency to the table or use separate tables/partitions to avoid overlapping writers.","Enable/verify metastore-based locking for Hive-catalog Iceberg tables so concurrent commits are serialized properly.","Ensure all writers use a compatible Iceberg catalog implementation so base-version checks are consistent."],"exampleFix":"// before: no retry\nops.commit(base, metadata);\n// after: retry loop on CommitFailedException\nfor (int attempt = 0; attempt < 4; attempt++) {\n  try { ops.commit(base, metadata); return; }\n  catch (CommitFailedException e) { base = ops.current(); }\n}","handlingStrategy":"retry","validationCode":"// re-read current metadata immediately before committing to minimize the conflict window\nTableMetadata fresh = ops.current();\nif (!Objects.equals(base, fresh)) { base = ops.refresh(); /* rebase changes */ }","typeGuard":"boolean canCommit(HiveTableOperations ops, TableMetadata base) { return Objects.equals(base, ops.current()); }","tryCatchPattern":"try { ops.commit(base, metadata); } catch (CommitFailedException e) { TableMetadata fresh = ops.current(); /* rebase then retry with backoff, bounded attempts */ }","preventionTips":["Bound concurrent writers per table; queue maintenance jobs separately","Enable proper locking in the metastore-backed catalog configuration","Keep write transactions short to shrink the conflict window","Treat CommitFailedException as retryable in all tooling around the table"],"tags":["iceberg","concurrency","commit","optimistic-locking"],"backgroundTag":"commit-conflict-stale-metadata","analyzedSha":"55bb57d202de3b926896fa966c2c4a44c779634e","analyzedAt":"2026-09-04T12:50:26.162Z","contentChangedAt":"2026-09-04T12:50:26.162Z","schemaVersion":2},"datasetVersion":"2026-09-11T21:17:09.523Z"}