Feat/concurrent members - #2519

Merged
d-v-b merged 37 commits into
zarr-developers:mainfrom
d-v-b:feat/concurrent-members
Jan 7, 2025
Merged

Feat/concurrent members#2519
d-v-b merged 37 commits into
zarr-developers:mainfrom
d-v-b:feat/concurrent-members

Conversation

@d-v-b

Copy link
Copy Markdown
Contributor

Makes AsyncGroup.members() fetch keys and fetch metadata concurrently, which provides a big performance win for high-latency storage backends. The number of concurrent operations is limited by the zarr-wide configuration setting.

in main, Group.members() requires ~O(num_members) time to complete, because it does not perform IO concurrently. In this PR, Group.members runs in constant time (until the number of concurrent requests exceeds the concurrency limit).

@d-v-b

Copy link
Copy Markdown
ContributorAuthor

note: this PR depends on #2474

@d-v-b
d-v-b requested review from TomAugspurger and jhamman and removed request for jhammanNovember 26, 2024 14:42
@jhammanjhamman added the V3 label Nov 29, 2024
Comment threadsrc/zarr/core/group.py Outdated
Comment threadsrc/zarr/core/group.py
Comment threadsrc/zarr/core/group.py
Comment threadtests/test_group.py
d-v-band others added 2 commits December 12, 2024 11:04
Co-authored-by: Deepak Cherian <dcherian@users.noreply.github.com>
@dstansbydstansby removed the V3 label Dec 12, 2024
# as opposed to a prefix, in the store under the prefix associated with this group
# in which case `key` cannot be the name of a sub-array or sub-group.
warnings.warn(
f"Object at {e.args[0]} is not recognized as a component of a Zarr hierarchy.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's technically possible for KeyError.args to be empty, so this [0] would raise an IndexError:

IndexErrorTraceback (mostrecentcalllast)
CellIn[10], line1---->1KeyError().args[0]

If we're comfortable assuming / requiring that things raising a KeyError here will populate that with the key, then I think this is fine to ignore. Otherwise, we might want to catch that IndexError.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I'm pretty sure that args will always be populated, because we are handling exceptions coming downstream of a function that necessarily takes a concrete key to query from storage. For that reason, even if I did add code to handle the case when e.args is not populated, I have no idea how we would test this case with our current Group api, since there's no way that I know of to reach this line without some concrete keys.

manager provided by that semaphore. If the semaphore parameter is None, then getitem is invoked
without a context manager.
"""
if semaphore is not None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Style nitpick: I like contextlib.nullcontext for cases where you may or may not have a context manager: https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext

3.10 added support for asynchronous context managers, so this should be usable here:

semaphore=semaphoreorcontextlib.nullcontext()
asyncwithsemaphore:
....

And I wonder whether getitem_semaphore should be the one to look up async.concurrency from the zarr config? Maybe we want that to be the default so that we don't miss it anywhere (and have some other way to indicate unbounded concurrency?)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I tried contextlib.nullcontext but I didn't feel like the LOC saved was worth the added indirection in this case. I assume that contextlib.nullcontext doesn't add notable performance overhead, by contrast, I find the current if... else construction easier to reason about, and it requires 1 fewer import.

And I wonder whether getitem_semaphore should be the one to look up async.concurrency from the zarr config? Maybe we want that to be the default so that we don't miss it anywhere (and have some other way to indicate unbounded concurrency?)

As I understand it, we only query the concurrency limit when the semaphore is created, which is necessarily before getitem_semaphore gets called. All invocations of getitem_semaphore have to use the same Semaphore instance, because otherwise there would be no coordination mechanism for rate-limiting. So I think that means getitem_semaphore shouldn't know anything about the config, or even concurrency limits.

@d-v-b

d-v-b commented Jan 7, 2025

Copy link
Copy Markdown
ContributorAuthor

@dstansby how can I re-rerun the docs build? and do you have any idea why it failed?

@dstansby

Copy link
Copy Markdown
Contributor

Do you have a readtheodcs account? If you send me the email or username for that, I can add you as a maintainer to the project on readthedocs.org. Then you'll get a "Rebuild this build" button:

Screenshot 2025-01-07 at 14 22 48

I'm not sure why this one failed... I will restart!

@d-v-b

d-v-b commented Jan 7, 2025

Copy link
Copy Markdown
ContributorAuthor

thanks for fixing it @dstansby!

@d-v-b
d-v-b merged commit bc5877b into zarr-developers:mainJan 7, 2025
@d-v-b
d-v-b deleted the feat/concurrent-members branch January 7, 2025 14:33
Sign up for freeto 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.

5 participants

@d-v-b@dstansby@TomAugspurger@dcherian@jhamman
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Feat/concurrent members - #2519

Merged
d-v-b merged 37 commits into
zarr-developers:mainfrom
d-v-b:feat/concurrent-members
Jan 7, 2025
Merged

Feat/concurrent members#2519
d-v-b merged 37 commits into
zarr-developers:mainfrom
d-v-b:feat/concurrent-members

Conversation

@d-v-b

Copy link
Copy Markdown
Contributor

Makes AsyncGroup.members() fetch keys and fetch metadata concurrently, which provides a big performance win for high-latency storage backends. The number of concurrent operations is limited by the zarr-wide configuration setting.

in main, Group.members() requires ~O(num_members) time to complete, because it does not perform IO concurrently. In this PR, Group.members runs in constant time (until the number of concurrent requests exceeds the concurrency limit).

@d-v-b

Copy link
Copy Markdown
ContributorAuthor

note: this PR depends on #2474

@d-v-b
d-v-b requested review from TomAugspurger and jhamman and removed request for jhammanNovember 26, 2024 14:42
@jhammanjhamman added the V3 label Nov 29, 2024
Comment threadsrc/zarr/core/group.py Outdated
Comment threadsrc/zarr/core/group.py
Comment threadsrc/zarr/core/group.py
Comment threadtests/test_group.py
d-v-band others added 2 commits December 12, 2024 11:04
Co-authored-by: Deepak Cherian <dcherian@users.noreply.github.com>
@dstansbydstansby removed the V3 label Dec 12, 2024
# as opposed to a prefix, in the store under the prefix associated with this group
# in which case `key` cannot be the name of a sub-array or sub-group.
warnings.warn(
f"Object at {e.args[0]} is not recognized as a component of a Zarr hierarchy.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's technically possible for KeyError.args to be empty, so this [0] would raise an IndexError:

IndexErrorTraceback (mostrecentcalllast)
CellIn[10], line1---->1KeyError().args[0]

If we're comfortable assuming / requiring that things raising a KeyError here will populate that with the key, then I think this is fine to ignore. Otherwise, we might want to catch that IndexError.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I'm pretty sure that args will always be populated, because we are handling exceptions coming downstream of a function that necessarily takes a concrete key to query from storage. For that reason, even if I did add code to handle the case when e.args is not populated, I have no idea how we would test this case with our current Group api, since there's no way that I know of to reach this line without some concrete keys.

manager provided by that semaphore. If the semaphore parameter is None, then getitem is invoked
without a context manager.
"""
if semaphore is not None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Style nitpick: I like contextlib.nullcontext for cases where you may or may not have a context manager: https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext

3.10 added support for asynchronous context managers, so this should be usable here:

semaphore=semaphoreorcontextlib.nullcontext()
asyncwithsemaphore:
....

And I wonder whether getitem_semaphore should be the one to look up async.concurrency from the zarr config? Maybe we want that to be the default so that we don't miss it anywhere (and have some other way to indicate unbounded concurrency?)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I tried contextlib.nullcontext but I didn't feel like the LOC saved was worth the added indirection in this case. I assume that contextlib.nullcontext doesn't add notable performance overhead, by contrast, I find the current if... else construction easier to reason about, and it requires 1 fewer import.

And I wonder whether getitem_semaphore should be the one to look up async.concurrency from the zarr config? Maybe we want that to be the default so that we don't miss it anywhere (and have some other way to indicate unbounded concurrency?)

As I understand it, we only query the concurrency limit when the semaphore is created, which is necessarily before getitem_semaphore gets called. All invocations of getitem_semaphore have to use the same Semaphore instance, because otherwise there would be no coordination mechanism for rate-limiting. So I think that means getitem_semaphore shouldn't know anything about the config, or even concurrency limits.

@d-v-b

d-v-b commented Jan 7, 2025

Copy link
Copy Markdown
ContributorAuthor

@dstansby how can I re-rerun the docs build? and do you have any idea why it failed?

@dstansby

Copy link
Copy Markdown
Contributor

Do you have a readtheodcs account? If you send me the email or username for that, I can add you as a maintainer to the project on readthedocs.org. Then you'll get a "Rebuild this build" button:

Screenshot 2025-01-07 at 14 22 48

I'm not sure why this one failed... I will restart!

@d-v-b

d-v-b commented Jan 7, 2025

Copy link
Copy Markdown
ContributorAuthor

thanks for fixing it @dstansby!

@d-v-b
d-v-b merged commit bc5877b into zarr-developers:mainJan 7, 2025
@d-v-b
d-v-b deleted the feat/concurrent-members branch January 7, 2025 14:33
Sign up for freeto 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.

5 participants

@d-v-b@dstansby@TomAugspurger@dcherian@jhamman
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Feat/concurrent members - #2519

Merged
d-v-b merged 37 commits into
zarr-developers:mainfrom
d-v-b:feat/concurrent-members
Jan 7, 2025
Merged

Feat/concurrent members#2519
d-v-b merged 37 commits into
zarr-developers:mainfrom
d-v-b:feat/concurrent-members

Conversation

@d-v-b

Copy link
Copy Markdown
Contributor

Makes AsyncGroup.members() fetch keys and fetch metadata concurrently, which provides a big performance win for high-latency storage backends. The number of concurrent operations is limited by the zarr-wide configuration setting.

in main, Group.members() requires ~O(num_members) time to complete, because it does not perform IO concurrently. In this PR, Group.members runs in constant time (until the number of concurrent requests exceeds the concurrency limit).

@d-v-b

Copy link
Copy Markdown
ContributorAuthor

note: this PR depends on #2474

@d-v-b
d-v-b requested review from TomAugspurger and jhamman and removed request for jhammanNovember 26, 2024 14:42
@jhammanjhamman added the V3 label Nov 29, 2024
Comment threadsrc/zarr/core/group.py Outdated
Comment threadsrc/zarr/core/group.py
Comment threadsrc/zarr/core/group.py
Comment threadtests/test_group.py
d-v-band others added 2 commits December 12, 2024 11:04
Co-authored-by: Deepak Cherian <dcherian@users.noreply.github.com>
@dstansbydstansby removed the V3 label Dec 12, 2024
# as opposed to a prefix, in the store under the prefix associated with this group
# in which case `key` cannot be the name of a sub-array or sub-group.
warnings.warn(
f"Object at {e.args[0]} is not recognized as a component of a Zarr hierarchy.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's technically possible for KeyError.args to be empty, so this [0] would raise an IndexError:

IndexErrorTraceback (mostrecentcalllast)
CellIn[10], line1---->1KeyError().args[0]

If we're comfortable assuming / requiring that things raising a KeyError here will populate that with the key, then I think this is fine to ignore. Otherwise, we might want to catch that IndexError.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I'm pretty sure that args will always be populated, because we are handling exceptions coming downstream of a function that necessarily takes a concrete key to query from storage. For that reason, even if I did add code to handle the case when e.args is not populated, I have no idea how we would test this case with our current Group api, since there's no way that I know of to reach this line without some concrete keys.

manager provided by that semaphore. If the semaphore parameter is None, then getitem is invoked
without a context manager.
"""
if semaphore is not None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Style nitpick: I like contextlib.nullcontext for cases where you may or may not have a context manager: https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext

3.10 added support for asynchronous context managers, so this should be usable here:

semaphore=semaphoreorcontextlib.nullcontext()
asyncwithsemaphore:
....

And I wonder whether getitem_semaphore should be the one to look up async.concurrency from the zarr config? Maybe we want that to be the default so that we don't miss it anywhere (and have some other way to indicate unbounded concurrency?)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I tried contextlib.nullcontext but I didn't feel like the LOC saved was worth the added indirection in this case. I assume that contextlib.nullcontext doesn't add notable performance overhead, by contrast, I find the current if... else construction easier to reason about, and it requires 1 fewer import.

And I wonder whether getitem_semaphore should be the one to look up async.concurrency from the zarr config? Maybe we want that to be the default so that we don't miss it anywhere (and have some other way to indicate unbounded concurrency?)

As I understand it, we only query the concurrency limit when the semaphore is created, which is necessarily before getitem_semaphore gets called. All invocations of getitem_semaphore have to use the same Semaphore instance, because otherwise there would be no coordination mechanism for rate-limiting. So I think that means getitem_semaphore shouldn't know anything about the config, or even concurrency limits.

@d-v-b

d-v-b commented Jan 7, 2025

Copy link
Copy Markdown
ContributorAuthor

@dstansby how can I re-rerun the docs build? and do you have any idea why it failed?

@dstansby

Copy link
Copy Markdown
Contributor

Do you have a readtheodcs account? If you send me the email or username for that, I can add you as a maintainer to the project on readthedocs.org. Then you'll get a "Rebuild this build" button:

Screenshot 2025-01-07 at 14 22 48

I'm not sure why this one failed... I will restart!

@d-v-b

d-v-b commented Jan 7, 2025

Copy link
Copy Markdown
ContributorAuthor

thanks for fixing it @dstansby!

@d-v-b
d-v-b merged commit bc5877b into zarr-developers:mainJan 7, 2025
@d-v-b
d-v-b deleted the feat/concurrent-members branch January 7, 2025 14:33
Sign up for freeto 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.

5 participants

@d-v-b@dstansby@TomAugspurger@dcherian@jhamman
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Feat/concurrent members - #2519

Merged
d-v-b merged 37 commits into
zarr-developers:mainfrom
d-v-b:feat/concurrent-members
Jan 7, 2025
Merged

Feat/concurrent members#2519
d-v-b merged 37 commits into
zarr-developers:mainfrom
d-v-b:feat/concurrent-members

Conversation

@d-v-b

Copy link
Copy Markdown
Contributor

Makes AsyncGroup.members() fetch keys and fetch metadata concurrently, which provides a big performance win for high-latency storage backends. The number of concurrent operations is limited by the zarr-wide configuration setting.

in main, Group.members() requires ~O(num_members) time to complete, because it does not perform IO concurrently. In this PR, Group.members runs in constant time (until the number of concurrent requests exceeds the concurrency limit).

@d-v-b

Copy link
Copy Markdown
ContributorAuthor

note: this PR depends on #2474

@d-v-b
d-v-b requested review from TomAugspurger and jhamman and removed request for jhammanNovember 26, 2024 14:42
@jhammanjhamman added the V3 label Nov 29, 2024
Comment threadsrc/zarr/core/group.py Outdated
Comment threadsrc/zarr/core/group.py
Comment threadsrc/zarr/core/group.py
Comment threadtests/test_group.py
d-v-band others added 2 commits December 12, 2024 11:04
Co-authored-by: Deepak Cherian <dcherian@users.noreply.github.com>
@dstansbydstansby removed the V3 label Dec 12, 2024
# as opposed to a prefix, in the store under the prefix associated with this group
# in which case `key` cannot be the name of a sub-array or sub-group.
warnings.warn(
f"Object at {e.args[0]} is not recognized as a component of a Zarr hierarchy.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's technically possible for KeyError.args to be empty, so this [0] would raise an IndexError:

IndexErrorTraceback (mostrecentcalllast)
CellIn[10], line1---->1KeyError().args[0]

If we're comfortable assuming / requiring that things raising a KeyError here will populate that with the key, then I think this is fine to ignore. Otherwise, we might want to catch that IndexError.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I'm pretty sure that args will always be populated, because we are handling exceptions coming downstream of a function that necessarily takes a concrete key to query from storage. For that reason, even if I did add code to handle the case when e.args is not populated, I have no idea how we would test this case with our current Group api, since there's no way that I know of to reach this line without some concrete keys.

manager provided by that semaphore. If the semaphore parameter is None, then getitem is invoked
without a context manager.
"""
if semaphore is not None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Style nitpick: I like contextlib.nullcontext for cases where you may or may not have a context manager: https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext

3.10 added support for asynchronous context managers, so this should be usable here:

semaphore=semaphoreorcontextlib.nullcontext()
asyncwithsemaphore:
....

And I wonder whether getitem_semaphore should be the one to look up async.concurrency from the zarr config? Maybe we want that to be the default so that we don't miss it anywhere (and have some other way to indicate unbounded concurrency?)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I tried contextlib.nullcontext but I didn't feel like the LOC saved was worth the added indirection in this case. I assume that contextlib.nullcontext doesn't add notable performance overhead, by contrast, I find the current if... else construction easier to reason about, and it requires 1 fewer import.

And I wonder whether getitem_semaphore should be the one to look up async.concurrency from the zarr config? Maybe we want that to be the default so that we don't miss it anywhere (and have some other way to indicate unbounded concurrency?)

As I understand it, we only query the concurrency limit when the semaphore is created, which is necessarily before getitem_semaphore gets called. All invocations of getitem_semaphore have to use the same Semaphore instance, because otherwise there would be no coordination mechanism for rate-limiting. So I think that means getitem_semaphore shouldn't know anything about the config, or even concurrency limits.

@d-v-b

d-v-b commented Jan 7, 2025

Copy link
Copy Markdown
ContributorAuthor

@dstansby how can I re-rerun the docs build? and do you have any idea why it failed?

@dstansby

Copy link
Copy Markdown
Contributor

Do you have a readtheodcs account? If you send me the email or username for that, I can add you as a maintainer to the project on readthedocs.org. Then you'll get a "Rebuild this build" button:

Screenshot 2025-01-07 at 14 22 48

I'm not sure why this one failed... I will restart!

@d-v-b

d-v-b commented Jan 7, 2025

Copy link
Copy Markdown
ContributorAuthor

thanks for fixing it @dstansby!

@d-v-b
d-v-b merged commit bc5877b into zarr-developers:mainJan 7, 2025
@d-v-b
d-v-b deleted the feat/concurrent-members branch January 7, 2025 14:33
Sign up for freeto 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.

5 participants

@d-v-b@dstansby@TomAugspurger@dcherian@jhamman
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Feat/concurrent members - #2519

Merged
d-v-b merged 37 commits into
zarr-developers:mainfrom
d-v-b:feat/concurrent-members
Jan 7, 2025
Merged

Feat/concurrent members#2519
d-v-b merged 37 commits into
zarr-developers:mainfrom
d-v-b:feat/concurrent-members

Conversation

@d-v-b

Copy link
Copy Markdown
Contributor

Makes AsyncGroup.members() fetch keys and fetch metadata concurrently, which provides a big performance win for high-latency storage backends. The number of concurrent operations is limited by the zarr-wide configuration setting.

in main, Group.members() requires ~O(num_members) time to complete, because it does not perform IO concurrently. In this PR, Group.members runs in constant time (until the number of concurrent requests exceeds the concurrency limit).

@d-v-b

Copy link
Copy Markdown
ContributorAuthor

note: this PR depends on #2474

@d-v-b
d-v-b requested review from TomAugspurger and jhamman and removed request for jhammanNovember 26, 2024 14:42
@jhammanjhamman added the V3 label Nov 29, 2024
Comment threadsrc/zarr/core/group.py Outdated
Comment threadsrc/zarr/core/group.py
Comment threadsrc/zarr/core/group.py
Comment threadtests/test_group.py
d-v-band others added 2 commits December 12, 2024 11:04
Co-authored-by: Deepak Cherian <dcherian@users.noreply.github.com>
@dstansbydstansby removed the V3 label Dec 12, 2024
# as opposed to a prefix, in the store under the prefix associated with this group
# in which case `key` cannot be the name of a sub-array or sub-group.
warnings.warn(
f"Object at {e.args[0]} is not recognized as a component of a Zarr hierarchy.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's technically possible for KeyError.args to be empty, so this [0] would raise an IndexError:

IndexErrorTraceback (mostrecentcalllast)
CellIn[10], line1---->1KeyError().args[0]

If we're comfortable assuming / requiring that things raising a KeyError here will populate that with the key, then I think this is fine to ignore. Otherwise, we might want to catch that IndexError.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I'm pretty sure that args will always be populated, because we are handling exceptions coming downstream of a function that necessarily takes a concrete key to query from storage. For that reason, even if I did add code to handle the case when e.args is not populated, I have no idea how we would test this case with our current Group api, since there's no way that I know of to reach this line without some concrete keys.

manager provided by that semaphore. If the semaphore parameter is None, then getitem is invoked
without a context manager.
"""
if semaphore is not None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Style nitpick: I like contextlib.nullcontext for cases where you may or may not have a context manager: https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext

3.10 added support for asynchronous context managers, so this should be usable here:

semaphore=semaphoreorcontextlib.nullcontext()
asyncwithsemaphore:
....

And I wonder whether getitem_semaphore should be the one to look up async.concurrency from the zarr config? Maybe we want that to be the default so that we don't miss it anywhere (and have some other way to indicate unbounded concurrency?)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I tried contextlib.nullcontext but I didn't feel like the LOC saved was worth the added indirection in this case. I assume that contextlib.nullcontext doesn't add notable performance overhead, by contrast, I find the current if... else construction easier to reason about, and it requires 1 fewer import.

And I wonder whether getitem_semaphore should be the one to look up async.concurrency from the zarr config? Maybe we want that to be the default so that we don't miss it anywhere (and have some other way to indicate unbounded concurrency?)

As I understand it, we only query the concurrency limit when the semaphore is created, which is necessarily before getitem_semaphore gets called. All invocations of getitem_semaphore have to use the same Semaphore instance, because otherwise there would be no coordination mechanism for rate-limiting. So I think that means getitem_semaphore shouldn't know anything about the config, or even concurrency limits.

@d-v-b

d-v-b commented Jan 7, 2025

Copy link
Copy Markdown
ContributorAuthor

@dstansby how can I re-rerun the docs build? and do you have any idea why it failed?

@dstansby

Copy link
Copy Markdown
Contributor

Do you have a readtheodcs account? If you send me the email or username for that, I can add you as a maintainer to the project on readthedocs.org. Then you'll get a "Rebuild this build" button:

Screenshot 2025-01-07 at 14 22 48

I'm not sure why this one failed... I will restart!

@d-v-b

d-v-b commented Jan 7, 2025

Copy link
Copy Markdown
ContributorAuthor

thanks for fixing it @dstansby!

@d-v-b
d-v-b merged commit bc5877b into zarr-developers:mainJan 7, 2025
@d-v-b
d-v-b deleted the feat/concurrent-members branch January 7, 2025 14:33
Sign up for freeto 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.

5 participants

@d-v-b@dstansby@TomAugspurger@dcherian@jhamman
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Feat/concurrent members - #2519

Merged
d-v-b merged 37 commits into
zarr-developers:mainfrom
d-v-b:feat/concurrent-members
Jan 7, 2025
Merged

Feat/concurrent members#2519
d-v-b merged 37 commits into
zarr-developers:mainfrom
d-v-b:feat/concurrent-members

Conversation

@d-v-b

Copy link
Copy Markdown
Contributor

Makes AsyncGroup.members() fetch keys and fetch metadata concurrently, which provides a big performance win for high-latency storage backends. The number of concurrent operations is limited by the zarr-wide configuration setting.

in main, Group.members() requires ~O(num_members) time to complete, because it does not perform IO concurrently. In this PR, Group.members runs in constant time (until the number of concurrent requests exceeds the concurrency limit).

@d-v-b

Copy link
Copy Markdown
ContributorAuthor

note: this PR depends on #2474

@d-v-b
d-v-b requested review from TomAugspurger and jhamman and removed request for jhammanNovember 26, 2024 14:42
@jhammanjhamman added the V3 label Nov 29, 2024
Comment threadsrc/zarr/core/group.py Outdated
Comment threadsrc/zarr/core/group.py
Comment threadsrc/zarr/core/group.py
Comment threadtests/test_group.py
d-v-band others added 2 commits December 12, 2024 11:04
Co-authored-by: Deepak Cherian <dcherian@users.noreply.github.com>
@dstansbydstansby removed the V3 label Dec 12, 2024
# as opposed to a prefix, in the store under the prefix associated with this group
# in which case `key` cannot be the name of a sub-array or sub-group.
warnings.warn(
f"Object at {e.args[0]} is not recognized as a component of a Zarr hierarchy.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's technically possible for KeyError.args to be empty, so this [0] would raise an IndexError:

IndexErrorTraceback (mostrecentcalllast)
CellIn[10], line1---->1KeyError().args[0]

If we're comfortable assuming / requiring that things raising a KeyError here will populate that with the key, then I think this is fine to ignore. Otherwise, we might want to catch that IndexError.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I'm pretty sure that args will always be populated, because we are handling exceptions coming downstream of a function that necessarily takes a concrete key to query from storage. For that reason, even if I did add code to handle the case when e.args is not populated, I have no idea how we would test this case with our current Group api, since there's no way that I know of to reach this line without some concrete keys.

manager provided by that semaphore. If the semaphore parameter is None, then getitem is invoked
without a context manager.
"""
if semaphore is not None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Style nitpick: I like contextlib.nullcontext for cases where you may or may not have a context manager: https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext

3.10 added support for asynchronous context managers, so this should be usable here:

semaphore=semaphoreorcontextlib.nullcontext()
asyncwithsemaphore:
....

And I wonder whether getitem_semaphore should be the one to look up async.concurrency from the zarr config? Maybe we want that to be the default so that we don't miss it anywhere (and have some other way to indicate unbounded concurrency?)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I tried contextlib.nullcontext but I didn't feel like the LOC saved was worth the added indirection in this case. I assume that contextlib.nullcontext doesn't add notable performance overhead, by contrast, I find the current if... else construction easier to reason about, and it requires 1 fewer import.

And I wonder whether getitem_semaphore should be the one to look up async.concurrency from the zarr config? Maybe we want that to be the default so that we don't miss it anywhere (and have some other way to indicate unbounded concurrency?)

As I understand it, we only query the concurrency limit when the semaphore is created, which is necessarily before getitem_semaphore gets called. All invocations of getitem_semaphore have to use the same Semaphore instance, because otherwise there would be no coordination mechanism for rate-limiting. So I think that means getitem_semaphore shouldn't know anything about the config, or even concurrency limits.

@d-v-b

d-v-b commented Jan 7, 2025

Copy link
Copy Markdown
ContributorAuthor

@dstansby how can I re-rerun the docs build? and do you have any idea why it failed?

@dstansby

Copy link
Copy Markdown
Contributor

Do you have a readtheodcs account? If you send me the email or username for that, I can add you as a maintainer to the project on readthedocs.org. Then you'll get a "Rebuild this build" button:

Screenshot 2025-01-07 at 14 22 48

I'm not sure why this one failed... I will restart!

@d-v-b

d-v-b commented Jan 7, 2025

Copy link
Copy Markdown
ContributorAuthor

thanks for fixing it @dstansby!

@d-v-b
d-v-b merged commit bc5877b into zarr-developers:mainJan 7, 2025
@d-v-b
d-v-b deleted the feat/concurrent-members branch January 7, 2025 14:33
Sign up for freeto 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.

5 participants

@d-v-b@dstansby@TomAugspurger@dcherian@jhamman
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Feat/concurrent members - #2519

Merged
d-v-b merged 37 commits into
zarr-developers:mainfrom
d-v-b:feat/concurrent-members
Jan 7, 2025
Merged

Feat/concurrent members#2519
d-v-b merged 37 commits into
zarr-developers:mainfrom
d-v-b:feat/concurrent-members

Conversation

@d-v-b

Copy link
Copy Markdown
Contributor

Makes AsyncGroup.members() fetch keys and fetch metadata concurrently, which provides a big performance win for high-latency storage backends. The number of concurrent operations is limited by the zarr-wide configuration setting.

in main, Group.members() requires ~O(num_members) time to complete, because it does not perform IO concurrently. In this PR, Group.members runs in constant time (until the number of concurrent requests exceeds the concurrency limit).

@d-v-b

Copy link
Copy Markdown
ContributorAuthor

note: this PR depends on #2474

@d-v-b
d-v-b requested review from TomAugspurger and jhamman and removed request for jhammanNovember 26, 2024 14:42
@jhammanjhamman added the V3 label Nov 29, 2024
Comment threadsrc/zarr/core/group.py Outdated
Comment threadsrc/zarr/core/group.py
Comment threadsrc/zarr/core/group.py
Comment threadtests/test_group.py
d-v-band others added 2 commits December 12, 2024 11:04
Co-authored-by: Deepak Cherian <dcherian@users.noreply.github.com>
@dstansbydstansby removed the V3 label Dec 12, 2024
# as opposed to a prefix, in the store under the prefix associated with this group
# in which case `key` cannot be the name of a sub-array or sub-group.
warnings.warn(
f"Object at {e.args[0]} is not recognized as a component of a Zarr hierarchy.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's technically possible for KeyError.args to be empty, so this [0] would raise an IndexError:

IndexErrorTraceback (mostrecentcalllast)
CellIn[10], line1---->1KeyError().args[0]

If we're comfortable assuming / requiring that things raising a KeyError here will populate that with the key, then I think this is fine to ignore. Otherwise, we might want to catch that IndexError.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I'm pretty sure that args will always be populated, because we are handling exceptions coming downstream of a function that necessarily takes a concrete key to query from storage. For that reason, even if I did add code to handle the case when e.args is not populated, I have no idea how we would test this case with our current Group api, since there's no way that I know of to reach this line without some concrete keys.

manager provided by that semaphore. If the semaphore parameter is None, then getitem is invoked
without a context manager.
"""
if semaphore is not None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Style nitpick: I like contextlib.nullcontext for cases where you may or may not have a context manager: https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext

3.10 added support for asynchronous context managers, so this should be usable here:

semaphore=semaphoreorcontextlib.nullcontext()
asyncwithsemaphore:
....

And I wonder whether getitem_semaphore should be the one to look up async.concurrency from the zarr config? Maybe we want that to be the default so that we don't miss it anywhere (and have some other way to indicate unbounded concurrency?)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I tried contextlib.nullcontext but I didn't feel like the LOC saved was worth the added indirection in this case. I assume that contextlib.nullcontext doesn't add notable performance overhead, by contrast, I find the current if... else construction easier to reason about, and it requires 1 fewer import.

And I wonder whether getitem_semaphore should be the one to look up async.concurrency from the zarr config? Maybe we want that to be the default so that we don't miss it anywhere (and have some other way to indicate unbounded concurrency?)

As I understand it, we only query the concurrency limit when the semaphore is created, which is necessarily before getitem_semaphore gets called. All invocations of getitem_semaphore have to use the same Semaphore instance, because otherwise there would be no coordination mechanism for rate-limiting. So I think that means getitem_semaphore shouldn't know anything about the config, or even concurrency limits.

@d-v-b

d-v-b commented Jan 7, 2025

Copy link
Copy Markdown
ContributorAuthor

@dstansby how can I re-rerun the docs build? and do you have any idea why it failed?

@dstansby

Copy link
Copy Markdown
Contributor

Do you have a readtheodcs account? If you send me the email or username for that, I can add you as a maintainer to the project on readthedocs.org. Then you'll get a "Rebuild this build" button:

Screenshot 2025-01-07 at 14 22 48

I'm not sure why this one failed... I will restart!

@d-v-b

d-v-b commented Jan 7, 2025

Copy link
Copy Markdown
ContributorAuthor

thanks for fixing it @dstansby!

@d-v-b
d-v-b merged commit bc5877b into zarr-developers:mainJan 7, 2025
@d-v-b
d-v-b deleted the feat/concurrent-members branch January 7, 2025 14:33
Sign up for freeto 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.

5 participants

@d-v-b@dstansby@TomAugspurger@dcherian@jhamman
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Feat/concurrent members - #2519

Merged
d-v-b merged 37 commits into
zarr-developers:mainfrom
d-v-b:feat/concurrent-members
Jan 7, 2025
Merged

Feat/concurrent members#2519
d-v-b merged 37 commits into
zarr-developers:mainfrom
d-v-b:feat/concurrent-members

Conversation

@d-v-b

Copy link
Copy Markdown
Contributor

Makes AsyncGroup.members() fetch keys and fetch metadata concurrently, which provides a big performance win for high-latency storage backends. The number of concurrent operations is limited by the zarr-wide configuration setting.

in main, Group.members() requires ~O(num_members) time to complete, because it does not perform IO concurrently. In this PR, Group.members runs in constant time (until the number of concurrent requests exceeds the concurrency limit).

@d-v-b

Copy link
Copy Markdown
ContributorAuthor

note: this PR depends on #2474

@d-v-b
d-v-b requested review from TomAugspurger and jhamman and removed request for jhammanNovember 26, 2024 14:42
@jhammanjhamman added the V3 label Nov 29, 2024
Comment threadsrc/zarr/core/group.py Outdated
Comment threadsrc/zarr/core/group.py
Comment threadsrc/zarr/core/group.py
Comment threadtests/test_group.py
d-v-band others added 2 commits December 12, 2024 11:04
Co-authored-by: Deepak Cherian <dcherian@users.noreply.github.com>
@dstansbydstansby removed the V3 label Dec 12, 2024
# as opposed to a prefix, in the store under the prefix associated with this group
# in which case `key` cannot be the name of a sub-array or sub-group.
warnings.warn(
f"Object at {e.args[0]} is not recognized as a component of a Zarr hierarchy.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's technically possible for KeyError.args to be empty, so this [0] would raise an IndexError:

IndexErrorTraceback (mostrecentcalllast)
CellIn[10], line1---->1KeyError().args[0]

If we're comfortable assuming / requiring that things raising a KeyError here will populate that with the key, then I think this is fine to ignore. Otherwise, we might want to catch that IndexError.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I'm pretty sure that args will always be populated, because we are handling exceptions coming downstream of a function that necessarily takes a concrete key to query from storage. For that reason, even if I did add code to handle the case when e.args is not populated, I have no idea how we would test this case with our current Group api, since there's no way that I know of to reach this line without some concrete keys.

manager provided by that semaphore. If the semaphore parameter is None, then getitem is invoked
without a context manager.
"""
if semaphore is not None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Style nitpick: I like contextlib.nullcontext for cases where you may or may not have a context manager: https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext

3.10 added support for asynchronous context managers, so this should be usable here:

semaphore=semaphoreorcontextlib.nullcontext()
asyncwithsemaphore:
....

And I wonder whether getitem_semaphore should be the one to look up async.concurrency from the zarr config? Maybe we want that to be the default so that we don't miss it anywhere (and have some other way to indicate unbounded concurrency?)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I tried contextlib.nullcontext but I didn't feel like the LOC saved was worth the added indirection in this case. I assume that contextlib.nullcontext doesn't add notable performance overhead, by contrast, I find the current if... else construction easier to reason about, and it requires 1 fewer import.

And I wonder whether getitem_semaphore should be the one to look up async.concurrency from the zarr config? Maybe we want that to be the default so that we don't miss it anywhere (and have some other way to indicate unbounded concurrency?)

As I understand it, we only query the concurrency limit when the semaphore is created, which is necessarily before getitem_semaphore gets called. All invocations of getitem_semaphore have to use the same Semaphore instance, because otherwise there would be no coordination mechanism for rate-limiting. So I think that means getitem_semaphore shouldn't know anything about the config, or even concurrency limits.

@d-v-b

d-v-b commented Jan 7, 2025

Copy link
Copy Markdown
ContributorAuthor

@dstansby how can I re-rerun the docs build? and do you have any idea why it failed?

@dstansby

Copy link
Copy Markdown
Contributor

Do you have a readtheodcs account? If you send me the email or username for that, I can add you as a maintainer to the project on readthedocs.org. Then you'll get a "Rebuild this build" button:

Screenshot 2025-01-07 at 14 22 48

I'm not sure why this one failed... I will restart!

@d-v-b

d-v-b commented Jan 7, 2025

Copy link
Copy Markdown
ContributorAuthor

thanks for fixing it @dstansby!

@d-v-b
d-v-b merged commit bc5877b into zarr-developers:mainJan 7, 2025
@d-v-b
d-v-b deleted the feat/concurrent-members branch January 7, 2025 14:33
Sign up for freeto 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.

5 participants

@d-v-b@dstansby@TomAugspurger@dcherian@jhamman