From d3b129c1af44e1f6366c127a5b47ecb2a19e377e Mon Sep 17 00:00:00 2001 From: Adam Toth Date: Fri, 11 Sep 2026 10:47:43 -0700 Subject: [PATCH 1/2] Python(feat): add support to semaphore based on channel count to avoid throttling --- .../_internal/low_level_wrappers/data.py | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/python/lib/sift_client/_internal/low_level_wrappers/data.py b/python/lib/sift_client/_internal/low_level_wrappers/data.py index 9ac5bc071a..ef6bee11db 100644 --- a/python/lib/sift_client/_internal/low_level_wrappers/data.py +++ b/python/lib/sift_client/_internal/low_level_wrappers/data.py @@ -36,6 +36,10 @@ # paging seems to omit all but a single channel. We can increase this batch size once that issue # has been resolved. In the mean time each channel gets its own request. REQUEST_BATCH_SIZE = 1 +# Caps concurrent wire requests to avoid exhausting the backend rate limiter. +# The limiter defaults to 200ms tokens per query; at 500 parallel requests that +# is 100s, safely under the 120s burst limit with headroom for partial-gap fetches. +MAX_PARALLEL_DATA_REQUESTS = 500 TimeRange = Tuple[pd.Timestamp, pd.Timestamp] @@ -923,20 +927,23 @@ def on_page(points: int) -> None: fetched_points += points _render() + _sem = asyncio.Semaphore(MAX_PARALLEL_DATA_REQUESTS) + async def _tick(kwargs: dict[str, Any], label: str) -> Any: - in_flight.append(label) - _render() - try: - return await self._paginate_channel_data( - kwargs=kwargs, - page_size=page_size, - max_points=max_results, - on_page=on_page, - ) - finally: - in_flight.remove(label) + async with _sem: + in_flight.append(label) _render() - bar() + try: + return await self._paginate_channel_data( + kwargs=kwargs, + page_size=page_size, + max_points=max_results, + on_page=on_page, + ) + finally: + in_flight.remove(label) + _render() + bar() # Seed the line up front so a fully-cached call (no fetch # tasks) still shows its cached-point summary in the receipt. From 5284b9785636091823e4c67463866f6291d00308 Mon Sep 17 00:00:00 2001 From: Adam Toth Date: Thu, 1 Oct 2026 08:32:21 -0700 Subject: [PATCH 2/2] Python(chore): scrub rate limiter details from semaphore comment Co-Authored-By: Claude Opus 4.7 (1M context) --- python/lib/sift_client/_internal/low_level_wrappers/data.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/python/lib/sift_client/_internal/low_level_wrappers/data.py b/python/lib/sift_client/_internal/low_level_wrappers/data.py index ef6bee11db..13f93e5653 100644 --- a/python/lib/sift_client/_internal/low_level_wrappers/data.py +++ b/python/lib/sift_client/_internal/low_level_wrappers/data.py @@ -36,9 +36,7 @@ # paging seems to omit all but a single channel. We can increase this batch size once that issue # has been resolved. In the mean time each channel gets its own request. REQUEST_BATCH_SIZE = 1 -# Caps concurrent wire requests to avoid exhausting the backend rate limiter. -# The limiter defaults to 200ms tokens per query; at 500 parallel requests that -# is 100s, safely under the 120s burst limit with headroom for partial-gap fetches. +# Caps concurrent wire requests to stay within backend rate limits. MAX_PARALLEL_DATA_REQUESTS = 500