Skip to content

Document concurrent access limits for persistent caches - #602

Open
terminalchai wants to merge 1 commit into
drivendataorg:masterfrom
terminalchai:docs/clarify-concurrent-cache-access
Open

terminalchai wants to merge 1 commit into
drivendataorg:masterfrom
terminalchai:docs/clarify-concurrent-cache-access

Conversation

@terminalchai

Copy link
Copy Markdown
Contributor

Persistent-cache documentation describes reuse across processes without explaining concurrent access limits. Users sharing CLOUDPATHLIB_LOCAL_CACHE_DIR can encounter incomplete cached downloads or OverwriteNewerLocalError when another worker is downloading the same object.

Add a guide covering cache isolation, cleanup modes, environment-variable precedence, external coordination, and streaming cache fallback. Include an executable thread-pool example that reads the same object in two tasks with separate temporary caches. Link the guide from the README, client settings, and docs navigation; update HISTORY.md.

Addresses the documentation request in #522, which has maintainer support. This PR references the issue without a closing keyword because runtime locking and timestamp behavior remain open topics.

Validation:

  • Reproduced OverwriteNewerLocalError with two LocalS3Client instances sharing a cache and a paused partial download.
  • Executed the exact example with local S3 mocks. Same-object reads succeeded; each task used a distinct cache; explicit settings overrode both deliberately conflicting cache environment variables; caches were removed after each read.
  • Built the docs with MkDocs after regenerating the root-backed pages. Verified rendered guide text, code, navigation and links. Existing unrelated link warnings remain.
  • Black and git diff --check pass.
  • Codex reviewed the documentation and found no remaining factual/API/example defects after its minor suggestions were applied. Review used supplied text because its nested shell runner was unavailable.
  • Documentation-only change; the runtime test suite was not rerun, and no live cloud backends were accessed.

Contributor checklist:

  • Read CONTRIBUTING.md
  • Existing issue Cache timestamps are wrong while downloading #522 and maintainer-supported documentation request
  • Based on latest master
  • Reproduced the reported behavior and verified the documented workaround
  • Guidance covers shared cache behavior across providers
  • Manual live cloud backend testing (docs example verified with local mocks)
  • Relevant local validation passes
  • Updated HISTORY.md under UNRELEASED

Prepared with AI assistance; the documentation and executable example were reviewed before submission.

@pjbull pjbull left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this is not quite right from an advice perspective.

  • We should lead with recommending implementers architect their app with path-level parallelism. A shared persistent cache is safe when each worker uses a different cloud object. One Client is safe to share across threads. Each process should construct its own client pointed at the same local_cache_dir.
  • If that first recommendation is not a good fit, keep a separate cache per worker, and the external lock, for the case where workers read or write the same object.
  • Make sure to add the caveat that submitting a CloudPath to a process pool drops _client on pickle, so the child uses the default client; implements should construct clients in the subprocess or set the default client before forking.
  • Extend the cache-key warning past providers: the same bucket and key on two accounts or endpoints share a local file, and HTTP keys ignore the host.
  • Link this page from the caching guide (docs/docs/script/caching.py and the notebook), which still says a persistent cache is safe across processes. Mention the same limit on the local_cache_dir docstrings for S3, GCS, Azure, and HTTP.

Comment on lines +17 to +22
For independent work, create a client and cache directory inside each task.
Tasks can then read the same cloud object without sharing its local cache.
The example below creates a fresh cache for each task invocation, so repeated
reads download again. This prevents tasks from using or clearing each other's
cache files. Concurrent writes to the same cloud object still need
coordination at the application level.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes a private cache the default for independent work. Independent work on different objects can share one client and one persistent cache.

Suggested lead, before the same-file section:

Parallelize by cloud object. Workers that read or write different objects can share one persistent cache. Give each thread its own CloudPath; the client can be shared. In a process pool, construct the client inside the worker with the same local_cache_dir and file_cache_mode="persistent". Do not pass a Client or CloudPath into the pool.

Move the TemporaryDirectory example under "same file". For the shared case, use different URIs and a client created once:

def read_in_worker(uri):
    return client.CloudPath(uri).read_bytes()

if __name__ == "__main__":
    client = S3Client(local_cache_dir="cache", file_cache_mode="persistent")
    paths = ["s3://bucket/first.csv", "s3://bucket/second.csv"]
    with ThreadPoolExecutor(max_workers=4) as executor:
        for data in executor.map(read_in_worker, paths):
            print(len(data))

Comment on lines +45 to +49
task remains isolated even if that environment variable is set. The explicit
cache mode also overrides `CLOUDPATHLIB_FILE_CACHE_MODE`. The temporary
cache is removed after the read finishes. The same approach can be used with
GCS or Azure clients, or with a process pool: create the client and cache inside
the worker, and keep pool startup under the `__main__` guard.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a process pool, create the client inside the worker and reuse the cache directory. A new directory per worker is only needed when two workers use the same object. Pickling a CloudPath removes _client, and the child then builds the default client.

Comment on lines +57 to +59
Workers operating on different cloud objects do not contend for the same
cached file. Use separate caches for different providers, whose bucket or
container names may otherwise map to the same local paths.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this up so it is the normal case, not a footnote. Add that names also collide across accounts, endpoints, and HTTP hosts: the cache key is the path with the scheme removed, and for HTTP it is the URL path with the host removed.

Comment on lines +5 to +9
A persistent `local_cache_dir` can be reused across sequential sessions, but it
does not synchronize concurrent access to the same cached file. This also
applies to `CLOUDPATHLIB_LOCAL_CACHE_DIR`. See
[Concurrent cache access](concurrent_cache_access.md) for worker-specific caches
and coordination when sharing a cache.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Point this summary at shared-cache, different-object work first. "Worker-specific caches" should be the same-object fallback.

Comment thread README.md
- **Extensible**: The base classes do most of the work generically, so implementing two small classes `MyPath` and `MyClient` is all you need to add support for a new cloud storage service.
- **Read/write support**: Reading just works. Using the `write_text`, `write_bytes` or `.open('w')` methods will all upload your changes to cloud storage without any additional file management as a developer.
- **Seamless caching**: Files are downloaded locally only when necessary. You can also easily pass a persistent cache folder so that across processes and sessions you only re-download what is necessary.
- **Seamless caching**: Files are downloaded locally only when necessary. A persistent cache folder can reuse downloads across sequential processes and sessions. For multiple workers, see [concurrent cache access](https://github.com/drivendataorg/cloudpathlib/blob/master/docs/docs/concurrent_cache_access.md).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link the rendered docs page, https://cloudpathlib.drivendata.org/stable/concurrent_cache_access/, the same way the streaming notes link to the docs site.

This branch is waiting to be deployed

1 waiting deployment
reviewed-live-cloud — 51bbb590 Waiting Oct 11, 2026 by terminalchai via Live Tests (custom_s3) #33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants