shaoyu-li opened a new issue, #12429:
URL: https://github.com/apache/gravitino/issues/12429
### Describe the feature
Introduce a pluggable SPI that lets deployments delegate table location
assignment and release to an external service, instead of requiring the
location to be a statically configured, already-known path.
Today `GenericCatalogOperations#calculateTableLocation` resolves a managed
table's location through a fixed, hard-coded chain:
1. the location table property (for Lance, this is what the
`x-lance-table-location` REST header ends up as);
2. otherwise, the location schema property + table name;
3. otherwise, the location catalog property + schema name + table name;
4. otherwise, throw `IllegalArgumentException`.
The method is private and the chain is not extensible. Symmetrically, when a
table is dropped nothing at all happens to the storage that was assigned to it.
We propose adding an interface — working name `TableLocationProvider` —
discovered via `ServiceLoader` and selected by a catalog property, consulted
when a managed table is created without an explicit location, and notified when
such a table is dropped.
### Motivation
**Assignment.** All three existing sources share the same assumption:
*someone already knows the
concrete path, and supplies it as configuration or as request input.*
In enterprise deployments that assumption often does not hold. Physical
storage layout is owned by
a central data-platform service, and the path for a new table is *allocated*
at creation time
rather than configured ahead of time. Allocation typically depends on inputs
Gravitino has no
opinion about — owning team, organizational domain, region, storage tier,
quota and capacity,
retention policy — and it usually has side effects, such as creating or
reserving the backing
bucket/prefix and registering ownership for lifecycle and cost attribution.
Under the current design an operator has two options, both unsatisfying:
- **Push the problem to the client.** Every writer must call the platform's
allocation service
itself and then pass the resulting path in via `x-lance-table-location`
(or the `location`
property). This spreads a platform concern across every engine and every
user, and nothing stops
a client from passing an arbitrary path that bypasses platform policy
entirely.
- **Fork Gravitino.** Patch `calculateTableLocation` locally, which makes
tracking upstream
painful.
A first-class extension point removes both, and lets the deployment
*enforce* the policy
server-side rather than hoping clients cooperate.
**Release.** The gap on the drop side is arguably worse, because it silently
leaks.
`ManagedTableOperations#purgeTable` states it plainly:
> For Gravitino managed tables, `purgeTable` is equivalent to `dropTable`.
It only removes the
> table metadata from the entity store. Physical data deletion should be
handled by the specific
> catalog implementation if needed.
For a deployment that assigns its own paths this is at most a housekeeping
matter. For a
deployment backed by an external allocator it means storage is stranded
permanently: the allocator
is never told the space can be reclaimed, so quota accounting, cost
attribution and retention
policy all drift out of sync with reality, with no way to tell an
intentionally-dropped table
apart from one that never existed.
Gravitino does have an event mechanism — `TableEventDispatcher` dispatches
`DropTablePreEvent` /
`DropTableEvent` (and the `Purge*` equivalents), and `EventListenerPlugin`
supports a `SYNC` mode —
so this is not a total blind spot. It is, however, not usable for
reclamation as it stands:
`DropTableEvent` carries only the user, the `NameIdentifier` and an
`isExists` flag. It does not
carry the table's `location` or its properties, and by the time the
post-event fires the metadata
is already gone, so a listener knows *which* table disappeared but has no
way to find out *which
storage* it occupied.
A listener could in principle work around this by hooking
`DropTablePreEvent`, loading the table to
capture its location, and stashing it until the post-event arrives — but
that pushes a shadow copy
of Gravitino's own metadata into every deployment, and leaves dangling state
whenever the two
events do not pair up. More fundamentally, the event mechanism is
observational: it cannot *supply*
a location on the create path, which is the other half of what we need.
Assignment and release are
two ends of the same contract, and we would rather they be expressed as one.
We hit both while running the Lance REST catalog against an internal
path-allocation service, but
nothing about the problem is Lance-specific — `calculateTableLocation`,
`dropTable` and
`purgeTable` are all shared by every table format the generic lakehouse
catalog serves.
### Describe the solution
A new SPI in the generic lakehouse catalog, deliberately kept free of any
domain-specific fields:
```java
public interface TableLocationProvider extends Closeable {
/** Name used to select this provider via the catalog property. */
String name();
void initialize(Map<String, String> catalogProperties);
/**
* Returns the location to use for the table being created, or
* {@code Optional.empty()} to fall back to Gravitino's built-in
resolution.
*/
Optional<String> provideTableLocation(TableLocationContext context);
/**
* Notifies the provider that a table whose location it assigned has been
dropped,
* so the underlying storage can be released or scheduled for reclamation.
*
* <p>Invoked only after the table metadata has been successfully removed.
See the
* ordering and failure-handling notes below.
*/
default void releaseTableLocation(TableReleaseContext context) {}
}
```
`TableLocationContext` carries only what Gravitino already knows, so
implementations can pick out
whatever their backend needs (owner, domain, tier, …) from the property maps
without Gravitino
having to model those concepts:
- `NameIdentifier` of the table (metalake / catalog / schema / table)
- table format (`lance`, …)
- the table, schema and catalog property maps
`TableReleaseContext` carries the same identifiers plus the dropped table's
resolved `location` and
its properties, and a `purge` flag distinguishing `dropTable` from
`purgeTable` so the
implementation can choose soft versus hard reclamation.
### Wiring
- Discovered with `ServiceLoader`, mirroring the existing
`LakehouseTableDelegator` /
`LakehouseTableDelegatorFactory` pattern in this same module, and
`CredentialProvider` in
`common`.
- Selected per catalog via a property, e.g. `table-location-provider =
<name>`.
- `provideTableLocation` is called from `calculateTableLocation` **only
after** the explicit
table-level `location` check, so an explicitly requested location still
wins.
- `releaseTableLocation` is called from `dropTable` and `purgeTable`.
### Drop semantics — the part we would most like feedback on
We propose the following, and want to be explicit that it is a deliberate
trade-off rather than a
fully reliable protocol:
- **Ordering: after metadata removal, never before.** If the provider ran
first and succeeded but
metadata removal then failed, the result would be a table that is still
visible in Gravitino
whose storage has been reclaimed — silent data loss. Running afterwards
means the worst case is
storage that outlives its table, which is detectable and recoverable by
reconciliation. We think
leaking is strictly preferable to losing.
- **Failure handling: log and emit a metric; do not fail the drop.** By the
time the provider is
invoked the metadata is already gone and cannot be rolled back, so
propagating the failure would
report an error for an operation that did in fact take effect. The SPI
javadoc should state
clearly that delivery is best-effort and that implementations must be able
to reconcile
independently. Building reliable delivery (outbox, retry queue) would be a
much larger change and
we do not propose it here.
If maintainers would rather have a fail-the-operation contract, or prefer
that the callback be a
separate listener-style extension rather than a method on this interface, we
are happy to go that
way instead — the ordering constraint above is the only part we feel
strongly about.
### Backward compatibility
Fully preserved, by construction:
- no `table-location-provider` configured → the current three-step chain
runs unchanged and no drop
callback fires;
- provider configured but `provideTableLocation` returns `Optional.empty()`
→ falls back to the
current chain;
- explicit table-level `location` present → the provider is never consulted;
- `releaseTableLocation` is a `default` no-op, so implementations that only
care about assignment
need not implement it.
No existing behaviour changes for any deployment that does not opt in.
### Scope for a first iteration
- **In scope:** `createTable`, `dropTable`, `purgeTable`.
- **Out of scope:** `alterTable(RenameTable)`. A rename changes the table's
identity but not its
physical path, and whether an allocator needs to know is very
implementation-dependent; we would
rather not guess. The context objects are shaped so a rename callback can
be added later without
breaking existing implementations.
- **Also out of scope:** filesets in the Hadoop catalog, which have a
structurally similar problem.
Worth a separate issue if this pattern is accepted.
### Additional context
- **Relevant code:**
`catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/generic/GenericCatalogOperations.java`
(`calculateTableLocation`, `dropTable`, `purgeTable`);
`core/src/main/java/org/apache/gravitino/catalog/ManagedTableOperations.java`
(`purgeTable` javadoc quoted above).
- **Existing `ServiceLoader`-based extension points** we would follow for
consistency:
`LakehouseTableDelegatorFactory` (same module) and `CredentialProvider` /
`CredentialProviderFactory` (`common`, `core`).
- We searched existing issues and did not find one covering pluggable
location assignment or
release; apologies if we missed a prior discussion.
- We can help with the design doc and contribute the implementation and tests
--
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]