Repository navigation
Document concurrent access limits for persistent caches - #602
terminalchai wants to merge 1 commit into
Conversation
pjbull
left a comment
There was a problem hiding this comment.
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
Clientis safe to share across threads. Each process should construct its own client pointed at the samelocal_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
CloudPathto a process pool drops_clienton 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.pyand the notebook), which still says a persistent cache is safe across processes. Mention the same limit on thelocal_cache_dirdocstrings for S3, GCS, Azure, and HTTP.
| 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. |
There was a problem hiding this comment.
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 samelocal_cache_dirandfile_cache_mode="persistent". Do not pass aClientorCloudPathinto 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))| 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. |
There was a problem hiding this comment.
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.
| 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. |
There was a problem hiding this comment.
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.
| 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. |
There was a problem hiding this comment.
Point this summary at shared-cache, different-object work first. "Worker-specific caches" should be the same-object fallback.
| - **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). |
There was a problem hiding this comment.
Link the rendered docs page, https://cloudpathlib.drivendata.org/stable/concurrent_cache_access/, the same way the streaming notes link to the docs site.
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:
Contributor checklist:
Prepared with AI assistance; the documentation and executable example were reviewed before submission.