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]