fix(store): relocate LocalStore temporary files to configurable tmp_dir - #4173
fix(store): relocate LocalStore temporary files to configurable tmp_dir#4173omar-merhebi wants to merge 2 commits into
Conversation
`LocalStore` now writes temporary files to a dedicated `tmp_dir` rather than directly inside the store path. Previously, `_atomic_write` wrote temp files ending in `.partial` inside the store which triggered `ZarrUserWarning` during concurrent reading and listing of the store. The location defaults to the system temporary directory (via `tempfile.gettempdir()`), and is overridable via the `tmp_dir` argument to `LocalStore` or globally via the `store.local.tmp_dir` config option (env var `ZARR_STORE__LOCAL__TMP_DIR`). The temporary directory must be on the same filesystem as the store, otherwise `_atomic_write` will raise `OSError` with `EXDEV`. The pre-existing try/except now also has a specific catch for `EXDEV` and re-raises, informing the user to change the `tmp_dir` value to a location on the same filesystem rather than emitting a raw `OSError`. Documented this new option in config.md and storage.md. Fixes zarr-developers#4161.
4ae0f4e to
a8f6db7
Compare
|
Flagging this before anyone digs into the failing CIs Both failures have the same root cause: temps default to the system temp dir, which is on a different filesystem than the store, so the RTD (Linux): temp dir Windows (windows-latest, py3.14): the temp dir is on ubuntu-latest: This passes because the Ubuntu runners keep temp on the checkout's disk so it's the same filesystem on those runners and it works. Ubuntu moved to using Therefore, any setup where a store isn't on the temp dir's filesystem (e.g., a |
|
I didn't realize that containerized environments generally put temporary storage on a separate volume! that argues heavily against my original suggestion to use the default In light of that, I think it's probably better to write these temporary files in the same directory as the final key, as a default. Curious to hear what @mkitti thinks |
|
Having a temporary directory as an option is nice, but I think the default should be a directory that is likely to be the on same volume as the original data. Changing the default to a temporary directory that is not on the same volume and then erroring when attempting to do an atomic write would be a serious regression. Better management of the warnings seems more appropriate if that is the primary complaint. The presence of .partial files while reading should produce some warning though as it seems likely that the reading client may receive a partially updated array. Perhaps we could just provide some explicit keyword to suppress that warning forcing the user explicitly acknowledge that caution is needed when attempting to interact with the data in this way. |
|
@d-v-b @mkitti agreed on both counts. I would propose leaving the config option as nice to have but revert the default back to what That puts #4161 largely back to where we started though, so I would like to deliberate a bit on the warning before proceeding. I'm realizing Case 1: Rewriting an existing node's
Case 2: Creating a node. Here This changes what the fix would actually have to do. Filtering So I'd like to put the reserved in-root staging directory back on the table. I withdrew it before but I'm reopening it because I didn't notice case 2 and nothing else we have discussed fixes it. It would be excluded from If the staging directory works for you, I'd rework this PR onto it and that closes case 1 and #4161. If you'd rather not, I can reduce this PR to the config option with the default reverted and I can keep #4161 open. Case 2 should be its own issue either way in my opinion because that's a bigger change and is its own fix. |
|
Reading and writing from the same array is not a very well supported use case for raw Zarr, and we have few guardrails there. The warnings should be there to indicate this. Suppressing the warnings should only be for those who really have thought about the I/O access patterns and concurrency issues. Warning suppression should draw scrutiny. zarr.json modifications are particularly fraught because that could lead to corruption of the entire array. These use pattern increasingly sounds like something that might require icechunk or something similar to maintain data integrity. |
|
@mkitti fair enough, I'll take a look at icechunk. One clarification is that I'm not reading and writing the same array. Each worker works on a disjoint subtree, and no node is written by two workers nor is So, with the warning staying as-is and the default reverting to what |
Summary
LocalStorenow writes temporary files to a dedicatedtmp_dirrather than directly inside the store path. Previously,_atomic_writewrote temp files ending in.partialinside the store which triggeredZarrUserWarningduring concurrent reading and listing of the store.The location defaults to the system temporary directory (via
tempfile.gettempdir()), and is overridable via thetmp_dirargument toLocalStoreor globally via thestore.local.tmp_dirconfig option (env varZARR_STORE__LOCAL__TMP_DIR). The temporary directory must be on the same filesystem as the store, otherwise_atomic_writewill raiseOSErrorwithEXDEV. The pre-existing try/except now also has a specific catch forEXDEVand re-raises, informing the user to change thetmp_dirvalue to a location on the same filesystem rather than emitting a rawOSError. Documented this new option in config.md and storage.md. Fixes #4161.For reviewers
I added a specific catch in the try/except block of
_atomic_write()that specifically catchesOSErrorcaused byEXDEVand instructs the user to pick a temporary directory on the same filesystem if the atomic write fails. Happy to modify the message, change logic, or remove it entirely if preferred. I also verified thaterrno.EXDEVis theerrnothat is raised on both Unix and Windows. Used the convention of naming the config arg and vars astmp_dirto copy the oldtmp_pathvariable in_atomic_write(). Also happy to change that naming scheme if preferable. I specifically avoidedshutil.move()here to keepLocalStoreatomic (shutil.move()attemptsos.rename()and falls back to copying if it fails). This follows the design discussed in #4161.Author attestation
TODO
docs/user-guide/*.mdchanges/