-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
[ENH] Add quota component and test for static #1720
Conversation
Reviewer ChecklistPlease leverage this checklist to ensure your code review is thorough before approving Testing, Bugs, Errors, Logs, Documentation
System Compatibility
Quality
|
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.
Thanks , some nits and observations but is great overall!
self.resource = resource | ||
|
||
|
||
class QuotaProvider(Component): |
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.
nit - add docstrings about this class
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.
As a general comment, I think that quota or any limit additions to the codebase are unnecessary technical debt better moved to the Proxy/Cache/Coordinator communication pattern. However, that is just my two cents on the long-term impacts.
chromadb/quota/__init__.py
Outdated
self.should_enforce = True | ||
self.system = system | ||
|
||
def payload_static_check(self, metadatas: Optional[Metadatas] = None, documents: Optional[Documents] = None, embeddings: Optional[Embeddings]= None, collection_id: Optional[str]= None): |
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.
We should also check ids
are those are user-passed strings and in the DB we store them as text
. That can get pretty large.
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.
I will add them in another PR
@@ -101,6 +101,7 @@ def __init__(self, system: System): | |||
self._settings = system.settings | |||
self._sysdb = self.require(SysDB) | |||
self._manager = self.require(SegmentManager) | |||
self._quota = self.require(QuotaEnforcer) |
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.
Should we enforce metadata checks on the collection level?
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.
Good question, I will ask with the team.
chromadb/quota/__init__.py
Outdated
def payload_static_check(self, metadatas: Optional[Metadatas] = None, documents: Optional[Documents] = None, embeddings: Optional[Embeddings]= None, collection_id: Optional[str]= None): | ||
if not self.should_enforce: | ||
return | ||
metadata_key_length_quota = self._quota_provider.get_for_subject(resource="METADATA_KEY_LENGTH", subject=collection_id) |
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.
Does this imply that all checks can only be performed against specific collections? How about quotas at user, tenant, and DB levels? Does this approach force Chroma to maintain per-collection quotas? Why are per-collection quotas more interesting than quotas at other levels of object inheritance?
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.
This is just for static one, but yes, later, tenant based quota would also be possible
@@ -291,6 +293,12 @@ def app(self) -> fastapi.FastAPI: | |||
def root(self) -> Dict[str, int]: | |||
return {"nanosecond heartbeat": self._api.heartbeat()} | |||
|
|||
async def quota_exception_handler(request: Request, exc: QuotaError): | |||
return JSONResponse( | |||
status_code=429, |
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.
Why is 429 (Too Many Requests) more appropriate than 413 (Payload Too Large)?
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.
we want something generic, and 429 is also use for resource exhausted. (gcp, aws)
raise QuotaError(resource="DOCUMENT_SIZE", actual=len(document) , quota=document_size_quota) | ||
embedding_dimension_quota = self._quota_provider.get_for_subject(resource="EMBEDDINGS_DIMENSION", subject=collection_id) | ||
if embedding_dimension_quota and embeddings: | ||
for embedding in embeddings: |
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.
using any()
might be a bit more python and codebase idiomatic:
if any(len(embedding) > embedding_dimension_quota for embedding in embeddings):
raise QuotaError("EMBEDDINGS_DIMENSION", actual=max(len(embedding) for embedding in embeddings), quota=embedding_dimension_quota)
Same applies for the other checks
@@ -140,6 +141,7 @@ def __init__(self, settings: Settings): | |||
allow_origins=settings.chroma_server_cors_allow_origins, | |||
allow_methods=["*"], | |||
) | |||
self._app.add_exception_handler(QuotaError, self.quota_exception_handler) |
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.
Why is a dedicated exception handler better than using the existing catch_exceptions_middleware
to handle the error?
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.
I did not see it, can be part of another refactoring. Or maybe we should use add_exception_handler which is the way of doing it in fastapi
@@ -0,0 +1,78 @@ | |||
import random |
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.
Do you think it makes sense that limits-related tests are subject to the rigor of property tests?
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.
as there is no state, I think unit test is ok. wdyt?
## Description of changes *Summarize the changes made by this PR.* - New functionality - Add quota check, it will be use to be able to rate limit, apply static check to payload etc. ## Test plan *How are these changes tested?* - [ ] Tests pass locally with `pytest`, added unit test --------- Co-authored-by: nicolas <[email protected]>
Description of changes
Summarize the changes made by this PR.
Test plan
How are these changes tested?
pytest
, added unit testDocumentation Changes
CIP doc