feat: adaptive embedding rate limiting - #66
Conversation
Greptile SummaryThis PR introduces an adaptive AIMD-based embedding rate limiter (
Confidence Score: 5/5The change is safe to merge; the AIMD gate logic is sound and the provider migrations are mechanical. The only notable trade-off is the removal of the platform-stats cache, which could increase DB query frequency under active polling. The core api/bigrag/services/platform_stats.py — caching was removed while the frontend poll interval is 5 seconds Important Files Changed
Reviews (4): Last reviewed commit: "fix: remove collections status poll hook" | Re-trigger Greptile |
| ```python | ||
| from bigrag.services.embedding.base import EmbeddingModel, get_semaphore, logger, truncate_to_tokens | ||
| from bigrag.services.embedding_rate_limit import ( | ||
| is_rate_limit_error, | ||
| rate_limit_cooldown_key, | ||
| rate_limit_delay, | ||
| record_rate_limit_cooldown, | ||
| wait_for_rate_limit_cooldown, | ||
| ) | ||
| ``` |
There was a problem hiding this comment.
_on_success emits no log; spec promises embedding limit recovered
The design spec (under "Observability") lists two log events: embedding limit decreased and embedding limit recovered, each with endpoint, old_limit, new_limit, inflight. The plan's _on_success function returns silently with no logger.* call, so an agent implementing it verbatim will produce one-sided logs where operators can observe the limit dropping but never see it climbing back. The _on_rate_limited log is also missing the old_limit and inflight fields the spec lists. Updating _on_success to emit a logger.info("embedding limit recovered", ...) and adding the missing fields to _on_rate_limited before the plan is executed will keep the implementation consistent with the spec.
| def reset_embedding_limiters() -> None: | ||
| _local_limiters.clear() |
There was a problem hiding this comment.
reset_embedding_limiters only clears _local_limiters but leaves the Redis LIMIT_PREFIX keys untouched. When an admin changes embedding_concurrency in runtime settings, this function fires — but for multi-worker Redis-backed deployments, the authoritative AIMD limit stored in Redis is unaffected. Workers will continue operating at whatever throttled value was last written (potentially MIN_LIMIT = 1.0 after heavy rate limiting), ignoring the new ceiling until it gradually self-heals via _SUCCESS_LUA. In the old code, reset_embedding_semaphores destroyed and recreated semaphores immediately with the new value. The Redis counterpart keys should also be deleted here so the new ceiling takes effect right away.
| def reset_embedding_limiters() -> None: | |
| _local_limiters.clear() | |
| def reset_embedding_limiters() -> None: | |
| _local_limiters.clear() | |
| redis = redis_cache.get_redis() | |
| if redis is None: | |
| return | |
| try: | |
| import asyncio | |
| async def _del_limit_keys() -> None: | |
| keys = await redis.keys(LIMIT_PREFIX + "*") | |
| if keys: | |
| await redis.delete(*keys) | |
| asyncio.get_event_loop().run_until_complete(_del_limit_keys()) | |
| except Exception: | |
| pass |
No description provided.