Repository navigation
Conversation
…mpty locations and add delete-data-on-drop Creating a MANAGED fileset on a location that already exists and is not empty is rejected unless allow-existing-location-as-managed=true is set; empty existing directories are still adopted. A new mutable fileset property delete-data-on-drop (default true) lets a managed fileset keep its storage when it or its schema is dropped. Both properties are documented. Signed-off-by: Yasutaka Hisano <yasutennis713@gmail.com>
mchades
left a comment
There was a problem hiding this comment.
I reproduced two issues on b8bb3ac.
| ClassLoader cl = Thread.currentThread().getContextClassLoader(); | ||
| filesets.parallelStream() | ||
| .filter(f -> f.filesetType() == Fileset.Type.MANAGED) | ||
| .filter(this::deletesStorageOnDrop) |
There was a problem hiding this comment.
[P1] Honor committed retention changes during cascade
filesets is read before super.dropSchema(...), so this filter uses a stale snapshot. In a multi-server deployment, another server can successfully set delete-data-on-drop=false after that read but before the metadata deletion; the cascade still sees the old value and deletes the data. I reproduced this interleaving, and forced catalog drops also use this path. Please evaluate retention against the version actually being deleted, as dropFileset does, or abort if it changed.
| } else { | ||
| // A managed fileset takes its data with it on drop, so silently adopting a location | ||
| // that already holds files would put data Gravitino never created at risk. | ||
| if (type == Fileset.Type.MANAGED |
There was a problem hiding this comment.
[P2] Validate every location before creating directories
This check runs in the same loop as mkdirs. With fresh locations a=/new/root/child and b=/new/root, processing a first creates b as a non-empty parent, so the request is rejected even though neither location existed before the call. I reproduced this on the PR head; the pre-change implementation accepts it. When a later location genuinely contains existing data, earlier newly created directories are also left behind after rejection. Please preflight all locations before creating any of them.
|
@Ystk-hsn Please address the two review comments and update the PR. |
| * The property name that controls whether dropping a managed fileset also deletes its storage | ||
| * locations. Defaults to true; set it to false to keep the data while removing the metadata. | ||
| */ | ||
| String PROPERTY_DELETE_DATA_ON_DROP = "delete-data-on-drop"; |
There was a problem hiding this comment.
@Ystk-hsn With type conversion being added in #14124, I suggest narrowing this PR to the creation guard:
- Keep the rejection of Managed filesets over existing non-empty locations, with
allow-existing-location-as-managed=truefor explicit adoption. - Remove
delete-data-on-dropand its associated deletion logic, tests, and documentation. ConvertingMANAGEDtoEXTERNALbefore dropping will provide the explicit way to retain data, keeping deletion behavior tied to the fileset type.
Please also address the multi-location preflight comment: validate all locations before creating any directories. Removing the retention property would also remove the new behavior discussed in the cascade race comment, so that issue would no longer need to be addressed within this PR.
…ntion Validate all location filesystem states before creating directories. Preserve nested new locations and avoid leaving new directories when a later location is rejected. Remove delete-data-on-drop in favor of the separate type-conversion work in apache#14124. Retain the creation guard and explicit adoption option, with regression coverage and updated documentation.
jerryshao
left a comment
There was a problem hiding this comment.
Thanks @Ystk-hsn for working on this. The create-time guard looks right for the common case: non-empty managed locations are rejected with IllegalArgumentException, empty directories are still adopted, external filesets are unchanged, and disable-filesystem-ops skips both the check and later deletes. The earlier feedback (preflight before any mkdirs, dropping delete-data-on-drop) is addressed in 56642e32e.
A few things to address before merging (details inline):
- The adopt loop can still take a directory that became non-empty after the preflight.
listStatus().length > 0lists every child of exactly the large directories this guard is meant to reject; please uselistStatusIteratorand confirm object-store semantics.- The docs (
filesets.mdquick start,manage-fileset-metadata-using-gravitino.md:23) and the PR description still describe the old contract / the removeddelete-data-on-dropproperty. - Missing tests for derived (schema/catalog/placeholder) locations and for the immutability of the new property.
Question on scope vs #14124: this PR only guards create (createMultipleLocationFileset). #14124 can convert an EXTERNAL fileset to MANAGED via alter with no non-empty check, and alter is allowed for WRITE_FILESET while drop is owner-only, so a later owner drop or schema cascade would delete that data. Already-created managed filesets on existing data are also still deleted on drop, so item 2 of #12550 now depends entirely on #14124. If that conversion is the supported opt-in, could we document it in both PRs and have #14124 run this same check (or an equally explicit confirmation) on EXTERNAL -> MANAGED? #14120 does not touch this create path, so there should be no conflict there.
| } | ||
| } | ||
|
|
||
| for (Map.Entry<String, Path> entry : filesetPathsBuilder.build().entrySet()) { |
There was a problem hiding this comment.
The preflight above is not what gets stored. In this second loop, for an existing path we go to the else branch (line 614, "manages the existing location") without listing it again, and mkdirs also succeeds when the directory already exists. A path that was absent or empty during the first loop can hold data by the time we get here (another writer, or mkdirs of a nested location in this same request), and it will then be stored as MANAGED and recursively deleted on drop. Please treat "absent" / "empty" as a snapshot: on the adopt path, reject the request if the directory now has children that are not other locations from this call, and clean up directories this call already created. Note that a blanket re-check here would break testCreateManagedFilesetWithNestedNewLocations when the child is created first.
There was a problem hiding this comment.
This guard is a preflight check. The base exists/mkdirs/metadata-write sequence already permits concurrent filesystem changes, and another check would still leave a race before metadata insertion. The two-pass validation preserves nested locations created by the same request. Concurrency control and cleanup of partially created directories are separate lifecycle changes, so they are outside this PR.
| // that already holds files would put data Gravitino never created at risk. | ||
| if (type == Fileset.Type.MANAGED | ||
| && !allowExistingLocationAsManaged | ||
| && fs.listStatus(formalizePath).length > 0) { |
There was a problem hiding this comment.
listStatus().length > 0 materializes every child just to decide the directory is non-empty, and the directories this guard is meant to reject are exactly the large ones that can make this time out or OOM the server. Also, a listing failure (including AccessControlException) is wrapped as a generic "Failed to create fileset". Please use listStatusIterator and stop at the first real child, as probeLocation already does. Could you also confirm the behavior on S3/GCS/OSS/ABFS: exists() == false must not hide objects under that prefix, and a 0-byte directory marker alone should not count as content (otherwise pre-created empty prefixes would be rejected).
There was a problem hiding this comment.
Switched to listStatusIterator(...).hasNext() and covered empty/non-empty results. The IOException wrapper is unchanged from the base.
I checked the pinned Hadoop 3.3.6/GCS 1.9.4 sources: S3A/GCS/OSS filter their own directory marker; ABFS uses the service's path listing. S3A supports incremental listing, ABFS does so when enabled, while GCS/OSS retain the base array-backed iterator. Prefix visibility remains connector/configuration-dependent, including GCS implicit-directory inference. Changing those existing semantics or upgrading connectors is outside this PR. These are source checks, not live cloud tests.
| @@ -55,13 +55,15 @@ See [How to use GVFS](./how-to-use-gvfs.md). | |||
| ### Managed and External | |||
|
|
|||
| A managed fileset belongs to Gravitino. Creating it creates the directory, and deleting it deletes | |||
There was a problem hiding this comment.
The quick start earlier on this page (line 48) still says that pointing at an existing path makes the fileset external. That is no longer true: the type stays whatever the caller sent, and creating MANAGED on a non-empty location now fails unless allow-existing-location-as-managed=true. The same sentence is in docs/manage-fileset-metadata-using-gravitino.md:23. Please update both, and also rewrite the PR description, since delete-data-on-drop / deletesStorageOnDrop are no longer part of this patch.
There was a problem hiding this comment.
Updated the quick start and creation guide to describe the explicit type and adoption override. Rewrote the PR title/description around the creation guard and removed the obsolete retention claims.
| } | ||
| } | ||
|
|
||
| /** Checks that managed filesets reject existing data without registering metadata. */ |
There was a problem hiding this comment.
Please add tests for a managed fileset with no explicit storage location whose path is derived from the schema location (and the catalog-location equivalent), and for a {{schema}}/{{fileset}} placeholder path that resolves onto existing files. Also assert that setProperty / removeProperty of allow-existing-location-as-managed is rejected, and that only a boolean true (any case) opts in.
There was a problem hiding this comment.
Added coverage for catalog/schema-derived locations and schema/fileset placeholders over existing data, including explicit adoption. Added rejection tests for setting/removing the property through the alter validator, plus true-case variants and false/invalid values.
| * external fileset instead. | ||
| */ | ||
| String PROPERTY_ALLOW_EXISTING_LOCATION_AS_MANAGED = "allow-existing-location-as-managed"; | ||
|
|
There was a problem hiding this comment.
nit: please add the matching PROPERTY_ALLOW_EXISTING_LOCATION_AS_MANAGED constant next to the other fileset property constants in the Python client (clients/client-python/gravitino/api/file/fileset.py). Optional: booleanPropertyEntry uses Boolean.valueOf, so yes / 1 are silently treated as false (fail-closed, consistent with other boolean properties); consider rejecting values other than true/false for this property.
There was a problem hiding this comment.
Added the Python constant. Keeping the existing boolean decoding semantics: only case-insensitive true opts in; false, yes, 1, and other values fail closed. The new tests cover these cases. Strict parsing would be a separate behavior change.
|
Regarding the scope question: Type conversion and its authorization belong to #14124. This PR is limited to create-time validation and keeps existing managed-fileset deletion and cascade behavior unchanged. Retaining existing data by converting to EXTERNAL is separate work; the updated PR description identifies this as the creation-time part of #12550. |
What changes were proposed in this pull request?
Reject creation of a
MANAGEDfileset on an existing non-empty location unlessallow-existing-location-as-managed=trueis supplied. The property defaults tofalse, is immutable, and has Java and Python constants.Preflight all resolved locations before creating directories, preserving nested locations within one request. Check directory contents through
listStatusIterator().hasNext()and document the explicit adoption behavior.Why are the changes needed?
Silently adopting existing data as managed makes that data subject to deletion when the fileset is dropped. This adds an explicit choice at creation time.
Addresses the creation-time protection in #12550. Type conversion is separate work in #14124.
Does this PR introduce any user-facing change?
Yes. Existing non-empty locations require
EXTERNALor an explicit managed-adoption override. Existing empty directories, external filesets, and catalogs with filesystem operations disabled retain their existing behavior.Drop and cascade behavior are unchanged. Validation is a creation-time preflight; it does not reserve paths against concurrent filesystem writers.
How was this patch tested?
./gradlew :api:build :catalogs:catalog-fileset:build -PskipITs --max-workers=2 --console=plainObject-store connector behavior was inspected in the pinned dependency sources; live cloud-storage tests were not run.