Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors bucket type detection in gcsfs by replacing the BucketType enum with a more granular BucketLayout dataclass that tracks StorageClass, CachingTier, and NamespaceLayout. This change enables support for Rapid Cache Ultra (RCU) buckets alongside Zonal buckets, allowing them to use the high-performance gRPC data path. The default finalization behavior on close is updated to default to False for Zonal buckets and True for RCU buckets. The review feedback highlights potential resource leaks in _put_file, _pipe_file, and simple_upload if a layout lookup fails after a successful write, suggesting that finalize_on_close be resolved inside the try blocks to ensure the writer is always safely closed.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1135 +/- ##
==========================================
+ Coverage 92.24% 92.37% +0.13%
==========================================
Files 20 20
Lines 4047 4094 +47
==========================================
+ Hits 3733 3782 +49
+ Misses 314 312 -2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
4f5e255 to
907b6d1
Compare
7545fb7 to
8a1ebd3
Compare
zhixiangli
left a comment
There was a problem hiding this comment.
Checked against a real RCU bucket (STANDARD + RAPID_CACHE_ULTRA) and a Zonal bucket with google-cloud-storage 3.17.0. RCU writes do land as RAPID and are finalized by default, as described. Details are in the inline comments:
- A failed overwrite on RCU leaves the partial upload as the live object (behavior change, not documented).
- Finalize failures are swallowed, so the write is reported as successful.
ZonalFileleaks the write stream if the closing flush fails (now reachable for RCU writes).- Nit: a failed
"xb"open callsinit_aaowtwice. - Test gaps.
I tested the fixes suggested for 2–4 against the real RCU bucket, and the existing unit tests still pass with them applied.
Two test gaps that can't be anchored inline (outside the diff):
test_zonal_file_commitandtest_zonal_file_lazy_init_creates_empty_file_on_closeonly run on Zonal. Running them onRAPID_WRITE_LAYOUTSwould covercommit()turningfinalize_on_closefromTruetoFalse, and an empty file closing withRAPIDand finalize.- Every test mocks the writer, so the suite also passes on google-cloud-storage 3.12. A cheap guard: assert
storage_classis ininspect.signature(AsyncAppendableObjectWriter.__init__).parameters.
8a1ebd3 to
339728c
Compare
|
Done for the commit and empty-file tests; skipped the SDK signature test since CI runs |
zhixiangli
left a comment
There was a problem hiding this comment.
Thanks for the PR. Routing RCU writes through the appendable writer with storage_class="RAPID" works as described. I checked the write paths against google-cloud-storage 3.17, with the gRPC transport replaced by an in-memory server that reproduces GCS appendable-write behavior. An overwrite replaces the object as soon as the writer opens, and only flushed bytes survive a broken stream.
Because the overwrite happens at writer open, a write that fails now changes the bucket. Before this PR, RCU wrote over HTTP and a failed write left the object unchanged. Two places don't account for that yet: the _put_file ordering and the cache maintenance on failure (inline comments 1 and 2). I'd ask for those to be fixed before merging. The other inline comments are minor.
…yout tests - Add a skip_if_grpc_read fixture that skips when TEST_BUCKET is a Zonal or RCU bucket (both read over gRPC), and use it in the test_core.py tests that assert HTTP read internals: test_cat_file_routing_and_thresholds, test_cat_file_explicit_concurrency and test_user_agent_includes_cache_type_and_source_in_read (replacing its inline zonal-only check). - Cover the HIERARCHICAL_RAPID_CACHE and UNKNOWN layouts in test_get_threshold_for_disk_reads_http_buckets_delegates_to_super. - Drop the unused _is_zonal_bucket patch from the test_fetch_range_split_concurrent_* tests; _fetch_range_split does not check the bucket type.
- zb_hns_utils.close_aaow re-raises a failed finalize, so put_file, pipe_file, simple_upload, upload_chunk and ZonalFile.close() no longer report an unfinalized write as successful. Closes that do not finalize stay best-effort. - ZonalFile._close_impl still closes the writer, without finalizing, when the closing flush fails, and then raises the flush error. - ZonalFile._ensure_aaow marks the file force-flushed when the writer fails to open, so close() does not open it again. - Document that on zonal and RCU buckets an overwrite replaces the existing object as soon as the writer opens, so a failed write leaves the partially written object in its place, unfinalized. - Tests: a failed finalize in close_aaow, a failed closing flush, a failed "xb" writer open, one ignored upload parameter per warning case, the finalize_on_close precedence matrix, and the commit and empty-file tests on the RCU layouts.
On Zonal and RCU buckets the appendable writer replaces the existing object as soon as it opens, so a write that fails after that point has already changed the bucket. - _put_file opens the local file before the writer, so a missing or unreadable lpath raises without touching the object, as on the HTTP path. - Once the writer is open, _put_file and _pipe_file update the dircache in a finally, so a failed append or finalize no longer leaves the old listing cached. - ZonalFile._close_impl invalidates the path and its parent when the closing flush fails, since fsspec's close() then skips that step. Tests: - _put_file with a missing lpath raises FileNotFoundError without opening the writer. - The put/pipe append-failure tests also check the dircache update, and a failed ZonalFile closing flush drops the cached parent listing. - With the real close_aaow, a failed finalize propagates from _put_file, _pipe_file and simple_upload, the writer is closed once, and put/pipe still update the dircache. - zonal_write_mocks gives every open an open writer that carries its bucket and object name, so later opens in a test are closed too. The manual _is_stream_open resets are gone, and test_zonal_file_overwrite checks that both opens finalize their writer.
339728c to
0e47110
Compare
Summary
Writes to Rapid Cache Ultra (RCU) buckets now go through the gRPC appendable writer (
AsyncAppendableObjectWriter) that zonal buckets already use, withstorage_class="RAPID". An RCU bucket defaults new objects toSTANDARD, which appendable writes reject; withRAPIDthe object is written to the zonal cache, and the server copies it to regional storage once it is finalized. For that reasonfinalize_on_closenow defaults toTrueon RCU buckets, while zonal buckets keepFalse. Handling RCU being disabled while a process is running will come in a separate PR.Depends on #1134. Only the last five commits are new here.
Changes
gcsfs/extended_gcsfs.py,gcsfs/core.py):_uses_grpc_data_pathinstead of_is_zonal_bucket:_put_file,_pipe_file, and theinitiate_uploadandsimple_uploadhelpers in both modules. Unknown layouts and buckets with a plain Rapid Cache stay on HTTP._openreturns aZonalFilefor all supported read and write modes on RCU buckets ("+" not in mode).ExtendedGcsFileSystem._init_aaowopens every appendable writer (forZonalFile,put_file,pipe_fileand the upload helpers). It passesstorage_class="RAPID"only for RCU buckets.zb_hns_utils.init_aaow(gcsfs/zb_hns_utils.py) gets astorage_classargument, which it forwards only when it is set._cp_fileand_mergestill check_is_zonal_bucket, so on RCU buckets they keep using the JSON API.finalize_on_close(gcsfs/extended_gcsfs.py,gcsfs/zonal_file.py):ZonalFiledefaults change fromFalsetoNone.open,ZonalFile,put_file,pipe_fileandsimple_upload,Noneresolves toTrueon RCU buckets and toFalseon zonal buckets.put_file,pipe_fileorsimple_uploadfails partway, the writer is closed without finalizing, so a partial object is never finalized.gcsfs/extended_gcsfs.py,gcsfs/zb_hns_utils.py,gcsfs/zonal_file.py; these also apply to zonal buckets):close_aaowre-raises a failed finalize instead of only logging it, soput_file,pipe_file,simple_upload,upload_chunkand closing aZonalFileno longer report an unfinalized write as successful. Closes that do not finalize stay best-effort.ZonalFilefails, the writer is still closed without finalizing, and the flush error is raised.ZonalFilebecomes close-only soclose()does not retry opening the writer.put_fileopens the local file before the writer, so a missing or unreadable local file raises without touching the remote object, as on HTTP.put_fileandpipe_fileupdate the dircache even when the write fails, and aZonalFilewhose closing flush fails drops its cached path and parent listing, because the object has already been replaced.gcsfs/zonal_file.py): the appendable writer ignorescontent_type,metadata,fixed_key_metadataandkms_key_name.ZonalFilewrite modes on zonal and RCU buckets now log a warning when any of them is set.put_file,pipe_fileand the upload helpers already do this.open()), so a write that fails partway leaves the partially written, unfinalized object in place of the old one. Over HTTP, the old object stayed intact until the upload completed."ab"appends through the gRPC writer. It fails on a finalized or non-appendable object instead of overwriting it, so it can only extend objects that were left unfinalized, for example written withfinalize_on_close=False.autocommit=Falseand transactions no longer defer the write, sinceZonalFile.discard()only logs a warning.if_generation_matchvalues above 0 raiseNotImplementedErroruntil the SDK writer accepts that argument.0,mode="create"and"xb"still work.pyproject.toml): requiregoogle-cloud-storage>=3.15.0, the first release whoseAsyncAppendableObjectWriteracceptsstorage_class.gcsfs/tests/conftest.py:zonal_write_mockstakes aBucketLayoutparam (RAPID_WRITE_LAYOUTS: the zonal layout and both RCU layouts) and exposes the expectedstorage_class; it skips the RCU layouts on real GCS. Every open now gets an open writer that carries its bucket and object name.gcsfs/tests/test_zonal.py,gcsfs/tests/test_zonal_file.py: the existing write, commit and empty-file tests also run on the RCU layouts and checkstorage_class. New tests cover every write mode on RCU, the ignored-parameter warning for each parameter, the fullfinalize_on_closeprecedence matrix, failedput_file,pipe_fileandsimple_uploadcalls closing the writer without finalizing, a failed closing flush still closing the writer, a failed writer open not being retried on close, a missing local file not opening the writer, the dircache update after a failed write, and a failed finalize propagating fromput_file,pipe_fileandsimple_uploadthrough the realclose_aaow.gcsfs/tests/test_zonal_unit.py:cp_fileandmergeon RCU buckets use the core implementation, Rapid Cache buckets keep writing over HTTP, andcore.simple_uploadandcore.initiate_uploadhand RCU buckets to the gRPC helpers.gcsfs/tests/test_zb_hns_utils.py:init_aaowforwardsstorage_classonly when it is set, andclose_aaowre-raises a failed finalize.