jerryshao commented on code in PR #12551:
URL: https://github.com/apache/gravitino/pull/12551#discussion_r3853140856


##########
core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java:
##########
@@ -416,6 +417,8 @@ public boolean dropTable(NameIdentifier ident) {
           if (droppedFromCatalog) {
             try {
               store.delete(ident, TABLE);
+            } catch (OptimisticLockException e) {

Review Comment:
   **Correctness: concurrent-alter-vs-drop race can permanently orphan the 
internal table_meta row.**
   
   Sequence: (1) `doWithCatalog(...).dropTable(ident)` succeeds against the 
external catalog (`droppedFromCatalog = true`, external data is now gone); (2) 
`store.delete(ident, TABLE)` loses its new version CAS 
(`softDeleteTableMetasByTableId` now requires `current_version = 
#{currentVersion}`) because another writer concurrently altered the table 
between the initial read and this delete, so it throws 
`OptimisticLockException`; (3) that exception now propagates straight out of 
`dropTable` instead of being retried.
   
   Because the external table is already gone, a client retry of the same 
`dropTable` call will have `doWithCatalog(...).dropTable(ident)` report "not 
found" (see e.g. `HiveCatalogOperations.dropTable`, which returns `false` when 
the table is already absent), so `droppedFromCatalog` becomes `false` on retry 
and the `if (droppedFromCatalog) { store.delete(...) }` block — the only place 
that ever calls `store.delete` — is skipped entirely. The internal Gravitino 
`table_meta` row is then permanently orphaned; no later drop attempt, and no 
existing reconciliation job (`OrphanedSchemaCleanup` only targets schemas, not 
tables), will ever remove it.
   
   Contrast with `alterTable`, which deliberately swallows the same kind of 
conflict via `OperationDispatcher.operateOnEntity` with an explicit 
retry-safety rationale ("failing the request would encourage a retry that could 
apply the external change twice"). Drop/purge propagate instead, but 
retry-safety is actually worse here — it silently blocks all future cleanup of 
the internal entity. The same issue applies to `purgeTable` below (line 476).



##########
core/src/main/java/org/apache/gravitino/storage/relational/service/TableMetaService.java:
##########
@@ -374,4 +381,76 @@ private void 
fillTablePOBuilderParentEntityId(TablePO.Builder builder, Namespace
     builder.withCatalogId(namespacedEntityId.namespaceIds()[1]);
     builder.withSchemaId(namespacedEntityId.entityId());
   }
+
+  private TablePO tablePOWithPersistedIdentityAndVersions(TablePO incomingPO, 
TablePO persistedPO) {
+    // The upsert derives the version inside the database and may preserve an 
existing table ID, so
+    // its dependent rows must carry the identity and versions the database 
ended up with.
+    return TablePO.builder(incomingPO)
+        .withTableId(persistedPO.getTableId())
+        .withCurrentVersion(persistedPO.getCurrentVersion())
+        .withLastVersion(persistedPO.getLastVersion())
+        .build();
+  }
+
+  private void deleteTableDependents(TablePO tablePO) {
+    // The table row has already passed its version check. All cleanup below 
uses the same database
+    // transaction, so either the table and every related row are deleted 
together, or none are.
+    SessionUtils.doWithoutCommit(
+        OwnerMetaMapper.class,
+        mapper ->
+            mapper.softDeleteOwnerRelByMetadataObjectIdAndType(
+                tablePO.getTableId(), MetadataObject.Type.TABLE.name()));
+    
TableColumnMetaService.getInstance().deleteColumnsByTableId(tablePO.getTableId());
+    SessionUtils.doWithoutCommit(
+        SecurableObjectMapper.class,
+        mapper ->
+            mapper.softDeleteObjectRelsByMetadataObject(
+                tablePO.getTableId(), MetadataObject.Type.TABLE.name()));
+    SessionUtils.doWithoutCommit(
+        TagMetadataObjectRelMapper.class,
+        mapper ->
+            mapper.softDeleteTagMetadataObjectRelsByMetadataObject(
+                tablePO.getTableId(), MetadataObject.Type.TABLE.name()));
+    SessionUtils.doWithoutCommit(
+        TagMetadataObjectRelMapper.class,
+        mapper -> 
mapper.softDeleteTagMetadataObjectRelsByTableId(tablePO.getTableId()));
+    SessionUtils.doWithoutCommit(
+        StatisticMetaMapper.class,
+        mapper -> mapper.softDeleteStatisticsByEntityId(tablePO.getTableId()));
+    SessionUtils.doWithoutCommit(
+        PolicyMetadataObjectRelMapper.class,
+        mapper -> 
mapper.softDeletePolicyMetadataObjectRelsByTableId(tablePO.getTableId()));
+    SessionUtils.doWithoutCommit(
+        TableVersionMapper.class,
+        mapper ->
+            mapper.softDeleteTableVersionByTableIdAndVersion(
+                tablePO.getTableId(), tablePO.getCurrentVersion()));
+  }
+
+  private RuntimeException tableWriteFailure(NameIdentifier identifier, 
TablePO observedTablePO) {

Review Comment:
   **Reuse: this classification algorithm is now duplicated a third time.**
   
   `tableWriteFailure` (lock-and-reread via `selectTableMetaByIdForUpdate`, 
compare natural-key fields, return `NoSuchEntityException` or 
`ExceptionUtils.concurrentModification`) is essentially a line-for-line copy of 
`SchemaMetaService.schemaWriteFailure` and 
`CatalogMetaService.catalogWriteFailure` (added by the earlier schema-OCC PR). 
No shared helper was extracted, so a future fix to the classification logic 
(e.g. adding a field to compare, or changing lock semantics) has to be made 
identically in three places — and it already wasn't kept in sync once (the 
schema/catalog versions and this one already differ slightly in structure). 
Consider extracting one generic `writeFailure(NameIdentifier, T observed, 
Function<Long,T> lockingLookup, BiPredicate<T,T> sameParent, EntityType)` 
helper that all three services call.



-- 
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]

Reply via email to