jerryshao commented on code in PR #14120:
URL: https://github.com/apache/gravitino/pull/14120#discussion_r4345762872
##########
catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogOperations.java:
##########
@@ -925,71 +895,32 @@ public boolean dropSchema(NameIdentifier ident, boolean
cascade) throws NonEmpty
Optional.ofNullable(schemaEntity.properties()).orElse(Collections.emptyMap());
Map<String, Path> schemaPaths = getAndCheckSchemaPaths(ident.name(),
properties);
- boolean dropped = super.dropSchema(ident, cascade);
- if (disableFSOps) {
- // No FS cleanup; still remove fileset write-through secrets when
cascade deleted them.
- // Schema secrets are cleaned by SchemaOperationDispatcher.
- if (dropped && cascade) {
- for (FilesetEntity fileset : filesets) {
- secretManager.deleteSecretsFromProperties(fileset.properties());
- }
+ // Catalog force-drop reaches this method without
SchemaOperationDispatcher's fileset
+ // cleanup. Drop each fileset before its schema so a storage failure
preserves the failed
+ // fileset and its parent for retry. Stop at the first failure; earlier
drops stay committed.
+ if (cascade) {
+ for (FilesetEntity fileset : filesets) {
+ dropFilesetAndGet(fileset.nameIdentifier())
+ .ifPresent(
+ deleted ->
secretManager.deleteSecretsFromProperties(deleted.properties()));
+ }
+
+ // Functions can also live in fileset catalogs. Drop their metadata
explicitly before
Review Comment:
Function rows are deleted before `super.dropSchema(ident, false)`. A fileset
created after the fileset snapshot correctly makes that call throw
`NonEmptySchemaException` and keeps the schema, but the functions are already
gone. `testDropSchemaRejectsFilesetCreatedAfterListing` does not register a
function, so this is untested. Please delete functions only after new filesets
can no longer appear, or in the same transaction as the schema delete, and
extend that test to assert the function is still there when the schema drop is
rejected.
##########
catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogOperations.java:
##########
@@ -740,38 +741,7 @@ public Fileset alterFileset(NameIdentifier ident,
FilesetChange... changes)
@Override
public boolean dropFileset(NameIdentifier ident) {
- try {
- // The relational store runs this cleanup after the metadata CAS wins
but before committing
- // its transaction. The callback therefore sees the exact deleted
snapshot, and an I/O
- // failure can still restore the metadata so the caller may fix
permissions and retry.
- //
- // The price is that the recursive storage delete runs inside that
transaction, holding the
- // fileset rows and a pooled connection for as long as the filesystem
takes. Dropping a
- // fileset with a very large tree is therefore a slow write for that
fileset, and enough
- // concurrent drops can hold up the connection pool.
- Optional<FilesetEntity> deletedFileset =
- store.deleteAndGet(
- ident,
- Entity.EntityType.FILESET,
- FilesetEntity.class,
- filesetEntity -> {
- if (!disableFSOps && filesetEntity.filesetType() ==
Fileset.Type.MANAGED) {
- try {
- deleteManagedFilesetStorage(ident, filesetEntity);
- } catch (IOException ioe) {
- throw new UncheckedIOException(ioe);
- }
- }
- });
- return deletedFileset.isPresent();
- } catch (NoSuchEntityException ne) {
- LOG.warn("Fileset {} does not exist", ident);
- return false;
- } catch (UncheckedIOException uioe) {
- throw ExceptionMessages.wrap("Failed to delete fileset " + ident,
uioe.getCause());
- } catch (IOException ioe) {
- throw ExceptionMessages.wrap("Failed to delete fileset " + ident, ioe);
- }
+ return dropFilesetAndGet(ident).isPresent();
Review Comment:
The deleted-snapshot secret cleanup in `dropSchema` runs only for filesets
that this method itself deletes. Public schema cascade drops filesets earlier
in `SchemaOperationDispatcher` / `FilesetOperationDispatcher`, and
`dropFileset` now discards the `dropFilesetAndGet` snapshot and returns a
boolean. The dispatcher still deletes secrets from the `loadFileset()`
properties captured before the compare-and-set. A secret changed between that
load and the committed delete is not cleaned, and `dropSchema` will not see the
fileset on its second list. Catalog and metalake force-drop bypass the
dispatcher, so they are fine. Please clean secrets from the snapshot inside
`dropFilesetAndGet` and remove the pre-drop cleanup in the dispatcher.
##########
catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogOperations.java:
##########
@@ -925,71 +895,32 @@ public boolean dropSchema(NameIdentifier ident, boolean
cascade) throws NonEmpty
Optional.ofNullable(schemaEntity.properties()).orElse(Collections.emptyMap());
Map<String, Path> schemaPaths = getAndCheckSchemaPaths(ident.name(),
properties);
- boolean dropped = super.dropSchema(ident, cascade);
- if (disableFSOps) {
- // No FS cleanup; still remove fileset write-through secrets when
cascade deleted them.
- // Schema secrets are cleaned by SchemaOperationDispatcher.
- if (dropped && cascade) {
- for (FilesetEntity fileset : filesets) {
- secretManager.deleteSecretsFromProperties(fileset.properties());
- }
+ // Catalog force-drop reaches this method without
SchemaOperationDispatcher's fileset
+ // cleanup. Drop each fileset before its schema so a storage failure
preserves the failed
+ // fileset and its parent for retry. Stop at the first failure; earlier
drops stay committed.
+ if (cascade) {
+ for (FilesetEntity fileset : filesets) {
+ dropFilesetAndGet(fileset.nameIdentifier())
+ .ifPresent(
+ deleted ->
secretManager.deleteSecretsFromProperties(deleted.properties()));
+ }
+
+ // Functions can also live in fileset catalogs. Drop their metadata
explicitly before
+ // the non-cascading schema delete so it still protects any
unprocessed filesets.
+ for (FunctionEntity function :
Review Comment:
`super.dropSchema(ident, false)` only succeeds when `checkSchemaIsEmpty`
sees no tables, views, filesets, functions, models, topics, semantic models, or
descendant schemas. This method now deletes only filesets and functions first.
Any other child is left in place, but the filesets and functions are already
committed, and the caller gets `NonEmptySchemaException` on every retry. The
previous `cascade=true` store delete removed those rows in the same transaction
as the schema. Fileset catalogs cannot create most of those types through their
own APIs, but a stray row would wedge the drop with no catalog API to remove
it. Please reject that case before deleting filesets and functions, or delete
those child types explicitly. Please do not switch the final call back to
`cascade=true`; that would again drop a fileset created after the snapshot
without storage cleanup.
##########
catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogOperations.java:
##########
@@ -925,71 +895,32 @@ public boolean dropSchema(NameIdentifier ident, boolean
cascade) throws NonEmpty
Optional.ofNullable(schemaEntity.properties()).orElse(Collections.emptyMap());
Map<String, Path> schemaPaths = getAndCheckSchemaPaths(ident.name(),
properties);
- boolean dropped = super.dropSchema(ident, cascade);
- if (disableFSOps) {
- // No FS cleanup; still remove fileset write-through secrets when
cascade deleted them.
- // Schema secrets are cleaned by SchemaOperationDispatcher.
- if (dropped && cascade) {
- for (FilesetEntity fileset : filesets) {
- secretManager.deleteSecretsFromProperties(fileset.properties());
- }
+ // Catalog force-drop reaches this method without
SchemaOperationDispatcher's fileset
+ // cleanup. Drop each fileset before its schema so a storage failure
preserves the failed
+ // fileset and its parent for retry. Stop at the first failure; earlier
drops stay committed.
+ if (cascade) {
+ for (FilesetEntity fileset : filesets) {
+ dropFilesetAndGet(fileset.nameIdentifier())
+ .ifPresent(
+ deleted ->
secretManager.deleteSecretsFromProperties(deleted.properties()));
+ }
+
+ // Functions can also live in fileset catalogs. Drop their metadata
explicitly before
+ // the non-cascading schema delete so it still protects any
unprocessed filesets.
+ for (FunctionEntity function :
+ store.list(filesetNs, FunctionEntity.class,
Entity.EntityType.FUNCTION)) {
+ store.delete(function.nameIdentifier(), Entity.EntityType.FUNCTION);
}
- return dropped;
}
+ // Reject remaining filesets instead of deleting their metadata without
storage cleanup.
+ boolean dropped = super.dropSchema(ident, false);
Review Comment:
Schema-directory cleanup still runs only after `super.dropSchema(ident,
false)` has committed (lines 942-963). A false `fs.delete` is logged and
ignored, and an `IOException` is thrown only once the schema row is gone.
Catalog force-drop then skips `deleteSecretsFromProperties` for that schema and
aborts, so a retry cannot remove the empty directory or those schema secrets.
Fileset storage failure now preserves metadata; this path does not. Please
either move empty-directory deletion ahead of the schema commit, or keep the
schema row when directory cleanup fails. If this stays best-effort, delete the
schema's write-through secrets before directory cleanup and do not fail the
catalog drop after the row is already gone.
##########
catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogOperations.java:
##########
@@ -925,71 +895,32 @@ public boolean dropSchema(NameIdentifier ident, boolean
cascade) throws NonEmpty
Optional.ofNullable(schemaEntity.properties()).orElse(Collections.emptyMap());
Map<String, Path> schemaPaths = getAndCheckSchemaPaths(ident.name(),
properties);
- boolean dropped = super.dropSchema(ident, cascade);
- if (disableFSOps) {
- // No FS cleanup; still remove fileset write-through secrets when
cascade deleted them.
- // Schema secrets are cleaned by SchemaOperationDispatcher.
- if (dropped && cascade) {
- for (FilesetEntity fileset : filesets) {
- secretManager.deleteSecretsFromProperties(fileset.properties());
- }
+ // Catalog force-drop reaches this method without
SchemaOperationDispatcher's fileset
+ // cleanup. Drop each fileset before its schema so a storage failure
preserves the failed
+ // fileset and its parent for retry. Stop at the first failure; earlier
drops stay committed.
+ if (cascade) {
+ for (FilesetEntity fileset : filesets) {
Review Comment:
Question: Reusing `deleteAndGet` means each managed fileset holds a pooled
connection for its whole recursive delete, and the schema loop does that one
fileset at a time. The old path deleted locations in parallel after the
metadata transaction. This matches single-fileset drop and is what makes a
storage failure roll the fileset row back. Is the loss of parallelism and the
longer connection hold accepted for schema and catalog force-drop of many large
trees?
##########
catalogs/catalog-fileset/src/test/java/org/apache/gravitino/catalog/fileset/TestFilesetCatalogOperations.java:
##########
@@ -867,6 +876,249 @@ public void testDropSchemaWithFSOpsDisabled() throws
IOException {
}
}
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ void testDropSchemaWithFunctions(boolean withFileset) throws IOException {
+ String schemaName = "schema_cascade_functions_" + generateTestId();
+ String catalogPath = TEST_ROOT_PATH + "/" + schemaName;
+ createSchema(schemaName, "comment", catalogPath, null);
+ NameIdentifier schemaIdent = NameIdentifier.of("m1", "c1", schemaName);
+ NameIdentifier functionIdent = NameIdentifier.of("m1", "c1", schemaName,
"function");
+ createFunction(functionIdent);
+ if (withFileset) {
+ createFileset("fileset", schemaName, "comment", Fileset.Type.MANAGED,
catalogPath, null);
+ }
+ NameIdentifier filesetIdent = NameIdentifier.of("m1", "c1", schemaName,
"fileset");
+ Path schemaPath = new Path(catalogPath, schemaName);
+ FileSystem fs = schemaPath.getFileSystem(new Configuration());
+
+ try (FilesetCatalogOperations ops = new FilesetCatalogOperations(store,
secretManager)) {
+ ops.initialize(
+ ImmutableMap.of(LOCATION, catalogPath),
+ randomCatalogInfo("m1", "c1"),
+ FILESET_PROPERTIES_METADATA);
+ Assertions.assertThrows(
+ NonEmptySchemaException.class, () -> ops.dropSchema(schemaIdent,
false));
+ Assertions.assertTrue(store.exists(schemaIdent,
Entity.EntityType.SCHEMA));
+ Assertions.assertTrue(store.exists(functionIdent,
Entity.EntityType.FUNCTION));
+ Assertions.assertEquals(withFileset, store.exists(filesetIdent,
Entity.EntityType.FILESET));
+ Assertions.assertTrue(fs.exists(schemaPath));
+
+ Assertions.assertTrue(ops.dropSchema(schemaIdent, true));
+ Assertions.assertFalse(store.exists(schemaIdent,
Entity.EntityType.SCHEMA));
+ Assertions.assertFalse(store.exists(functionIdent,
Entity.EntityType.FUNCTION));
+ Assertions.assertFalse(store.exists(filesetIdent,
Entity.EntityType.FILESET));
+ Assertions.assertFalse(fs.exists(schemaPath));
+ }
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ void testDropSchemaStopsOnStorageFailureAndCanRetry(boolean throwException)
throws IOException {
Review Comment:
nit: The new cascade tests cover a single location that returns false or
throws, plus retry, secrets, external filesets, and `disableFSOps`. Please add
a managed fileset with two locations where the second `delete` fails: the
fileset row and the remaining location should stay, the first location may
already be gone, and a retry should finish the drop. That is the
partial-location behavior `deleteManagedFilesetStorage` already has for a
direct fileset drop, and this path now depends on it.
##########
catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogOperations.java:
##########
@@ -925,71 +895,32 @@ public boolean dropSchema(NameIdentifier ident, boolean
cascade) throws NonEmpty
Optional.ofNullable(schemaEntity.properties()).orElse(Collections.emptyMap());
Map<String, Path> schemaPaths = getAndCheckSchemaPaths(ident.name(),
properties);
- boolean dropped = super.dropSchema(ident, cascade);
- if (disableFSOps) {
- // No FS cleanup; still remove fileset write-through secrets when
cascade deleted them.
- // Schema secrets are cleaned by SchemaOperationDispatcher.
- if (dropped && cascade) {
- for (FilesetEntity fileset : filesets) {
- secretManager.deleteSecretsFromProperties(fileset.properties());
- }
+ // Catalog force-drop reaches this method without
SchemaOperationDispatcher's fileset
+ // cleanup. Drop each fileset before its schema so a storage failure
preserves the failed
+ // fileset and its parent for retry. Stop at the first failure; earlier
drops stay committed.
+ if (cascade) {
+ for (FilesetEntity fileset : filesets) {
+ dropFilesetAndGet(fileset.nameIdentifier())
+ .ifPresent(
+ deleted ->
secretManager.deleteSecretsFromProperties(deleted.properties()));
+ }
+
+ // Functions can also live in fileset catalogs. Drop their metadata
explicitly before
+ // the non-cascading schema delete so it still protects any
unprocessed filesets.
+ for (FunctionEntity function :
+ store.list(filesetNs, FunctionEntity.class,
Entity.EntityType.FUNCTION)) {
Review Comment:
nit: The new function `store.list` / `store.delete` calls throw
`IOException` into the existing catch (lines 973-974), which always wraps it as
`Failed to delete schema <ident> location`. A function-metadata failure is then
indistinguishable from directory cleanup. Please wrap those store calls at the
function loop. The `NoSuchEntityException` catch has the same shape: after some
filesets are already deleted, a missing parent during the function list returns
false ("schema does not exist") instead of failing the drop.
--
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]