yuqi1129 opened a new issue, #12443:
URL: https://github.com/apache/gravitino/issues/12443

   ### Version
   
   main branch
   
   ### Describe what's wrong
   
   Three child-entity write paths under `core/.../storage/relational/service` 
mishandle the losing side of a concurrent-write race. They are independent of 
the metalake/catalog/schema OCC work in #12342 / #12350 and predate it; 
splitting them out here so that PR stays scoped.
   
   1. **`TableMetaService.updateTable` commits an unreachable version row.** 
The new `table_version_info` row is written outside the `updateResult > 0` 
guard, so when the meta CAS matches zero rows (the table was deleted, or 
cascade-deleted with its schema, concurrently) the transaction still commits an 
active version row before throwing. `deleteTableVersionByLegacyTimeline` only 
removes rows with `deleted_at > 0`, so the row is never collected — it leaks 
permanently.
   
   2. **`FunctionMetaService.updateFunction` silently drops the update.** It 
inserts the new `function_version_info` row and then calls `ops.updatePO(...)` 
through `doWithoutCommit`, discarding the affected-row count. A concurrent 
delete therefore leaves an active orphan version row *and* the method returns 
the new entity as if the update had succeeded. `ViewMetaService.updateView` 
already handles this correctly by failing inside the transaction.
   
   3. **`ModelVersionMetaService.insertModelVersion` has no schema fence.** 
Unlike `insertModel` / `insertTable` / `insertFileset` / `insertFunction`, it 
never calls `SchemaMetaService.lockSchemaForEntityWrite`, so a concurrent 
schema cascade delete can leave active `model_version_info` rows under a 
deleted model.
   
   ### Error message and/or stacktrace
   
   No stacktrace — these are silent data-consistency defects. Case 1 surfaces 
as `IOException: Failed to update the entity: <ident>` while the orphan row has 
already been committed; case 2 and 3 produce no error at all.
   
   ### How to reproduce
   
   Against main, in `TestTableMetaService` / `TestFunctionMetaService`, drive 
the race from the `updater` callback (it runs before the write transaction 
opens):
   
   ```java
   TableMetaService.getInstance().updateTable(ident, old -> {
     TableMetaService.getInstance().deleteTable(ident);   // competing writer 
wins
     return renamedTable;
   });
   // throws IOException, but table_version_info still holds a row with 
deleted_at = 0
   ```
   
   The same shape with `updateFunction` returns successfully instead of 
throwing, and leaves an active `function_version_info` row.
   
   ### Additional context
   
   Raised by review feedback on #12350 
([thread](https://github.com/apache/gravitino/pull/12350#discussion_r3748836456)).
   
   Intended fixes, all small and local:
   - `updateTable`: move the version-row write under the `updateResult > 0` 
guard.
   - `updateFunction`: capture the update count and throw inside the 
transaction so the version row rolls back, matching `ViewMetaService`.
   - `insertModelVersion`: take `lockSchemaForEntityWrite` like the other child 
inserts.
   
   Not planned: acquiring the shared schema lock at the start of every child 
write transaction. Same-schema updates are already mutually exclusive with a 
cascade delete through the per-row CAS (`current_version` + `deleted_at = 0`); 
the bug is what the losing transaction commits, not missing exclusion.
   


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