Repository navigation
Document concurrent access limits for persistent caches #602
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
terminalchai
wants to merge
2
commits into
drivendataorg:master
Choose a base branch
from
terminalchai:docs/clarify-concurrent-cache-access
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| # Concurrent cache access | ||
|
|
||
| Parallelize by cloud object. Workers that read or write different cloud objects | ||
| can share one persistent cache. Give each thread its own `CloudPath`; one | ||
| `Client` can be shared across threads. Each process should construct its own | ||
| client pointed at the same `local_cache_dir` with `file_cache_mode="persistent"`. | ||
| This also applies when setting the cache directory with the | ||
| `CLOUDPATHLIB_LOCAL_CACHE_DIR` environment variable. | ||
|
|
||
| ## Different objects with a shared persistent cache | ||
|
|
||
| Create a client once for a thread pool and pass different object URIs to the | ||
| workers. Each worker creates its own path using that client: | ||
|
|
||
| ```python | ||
| from concurrent.futures import ThreadPoolExecutor | ||
|
|
||
| from cloudpathlib import S3Client | ||
|
|
||
|
|
||
| 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)) | ||
| ``` | ||
|
|
||
| For a process pool, pass URI strings and construct the client inside the worker. | ||
| The processes can reuse the same cache directory when accessing different objects: | ||
|
|
||
| ```python | ||
| from concurrent.futures import ProcessPoolExecutor | ||
|
|
||
| from cloudpathlib import S3Client | ||
|
|
||
|
|
||
| def read_in_worker(uri): | ||
| client = S3Client(local_cache_dir="cache", file_cache_mode="persistent") | ||
| return client.CloudPath(uri).read_bytes() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| paths = ["s3://bucket/first.csv", "s3://bucket/second.csv"] | ||
| with ProcessPoolExecutor(max_workers=4) as executor: | ||
| for data in executor.map(read_in_worker, paths): | ||
| print(len(data)) | ||
| ``` | ||
|
|
||
| Do not pass a `Client` or `CloudPath` into the process pool. Pickling a `CloudPath` | ||
| removes its `_client`, so the child uses its process's default client, which may | ||
| differ from the parent's configured client. Constructing the client in the subprocess keeps its | ||
| credentials, endpoint, and cache settings explicit. | ||
|
|
||
| Explicitly set `file_cache_mode="persistent"` when sharing a cache directory, | ||
| and coordinate any cache cleanup. Other cache modes can remove cached files | ||
| when a file, path, or client is closed or released. | ||
|
|
||
| Different objects must also map to different local cache paths. The cache key is | ||
| the cloud path with its scheme removed; for HTTP, it is the URL path with the | ||
| host removed. Bucket or container names and keys can therefore collide across | ||
| providers, accounts, or endpoints, and HTTP URLs on different hosts can collide | ||
| when their URL paths match. Use separate cache directories for these namespaces. | ||
|
|
||
| ## Accessing the same file from multiple workers | ||
|
|
||
| Cloudpathlib does not lock cache files while downloading, reading, writing, or | ||
| uploading them. If two workers access the same cloud object through a shared | ||
| cache, one may encounter a partially downloaded file or an | ||
| `OverwriteNewerLocalError` while the other is still downloading. The behavior | ||
| can depend on the provider and operation. Setting | ||
| `force_overwrite_from_cloud=True` does not coordinate the workers. | ||
|
|
||
| If multiple workers need the same object, use a separate cache per worker or | ||
| coordinate access with an external lock. For separate caches, 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. | ||
|
|
||
| ```python | ||
| from concurrent.futures import ThreadPoolExecutor | ||
| from tempfile import TemporaryDirectory | ||
|
|
||
| from cloudpathlib import S3Client | ||
|
|
||
|
|
||
| def read_in_worker(uri): | ||
| with TemporaryDirectory() as cache_dir: | ||
| client = S3Client(local_cache_dir=cache_dir, file_cache_mode="persistent") | ||
| return client.CloudPath(uri).read_bytes() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| paths = ["s3://bucket/first.csv", "s3://bucket/first.csv"] | ||
| with ThreadPoolExecutor(max_workers=4) as executor: | ||
| for data in executor.map(read_in_worker, paths): | ||
| print(len(data)) | ||
| ``` | ||
|
|
||
| The explicit `local_cache_dir` overrides `CLOUDPATHLIB_LOCAL_CACHE_DIR`, so each | ||
| 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. | ||
|
|
||
| If workers must share a cache for the same object, coordinate access outside | ||
| cloudpathlib, such as with a lock shared by all processes that every worker respects. | ||
| Hold the lock for the whole operation, including opening the file, using it, | ||
| and closing it so any upload finishes before another worker accesses the cache. | ||
| A lock that only protects client construction is insufficient. | ||
|
|
||
| See [Caching](caching.ipynb) for cache modes and cleanup behavior, and | ||
| [Streaming I/O](streaming_io.md) for reads that avoid the local cache. Append and | ||
| update operations in streaming mode still fall back to the cache. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
CloudPathremoves_client, and the child then builds the default client.