How to Implement a Batch Requests Flush Interval in Python
A simple async batcher that accumulates items and flushes them either when a max batch size is reached or after a time-based flush interval.
Python code
43 linesimport asyncio
from collections import deque
class Batcher:
def __init__(self, flush_interval=0.5, max_batch=5):
self.flush_interval = flush_interval
self.max_batch = max_batch
self.queue = deque()
self.lock = asyncio.Lock()
async def add(self, item):
async with self.lock:
self.queue.append(item)
if len(self.queue) >= self.max_batch:
await self._flush()
async def _flush(self):
if not self.queue:
return
batch = list(self.queue)
self.queue.clear()
await self._process_batch(batch)
async def _process_batch(self, batch):
print(f"Flushing {len(batch)} items: {batch}")
await asyncio.sleep(0.1) # simulate processing
async def start(self):
while True:
await asyncio.sleep(self.flush_interval)
async with self.lock:
await self._flush()
async def main():
batcher = Batcher()
task = asyncio.create_task(batcher.start())
for i in range(12):
await batcher.add(i)
await asyncio.sleep(0.05)
task.cancel()
if __name__ == "__main__":
asyncio.run(main())
Output
Flushing 5 items: [0, 1, 2, 3, 4]
Flushing 5 items: [5, 6, 7, 8, 9]
Flushing 2 items: [10, 11]
How it works
The Batcher uses an asyncio.Lock to protect the queue from concurrent modification. add appends an item and flushes immediately if the batch reaches the max size. The background start task sleeps for the flush interval and then flushes any remaining buffered items, ensuring that no item stays in the queue longer than the interval. The _process_batch method simulates the actual work; in production you would replace it with an API call or database write. This pattern is common for rate-limited APIs where grouping requests reduces overhead.
Common mistakes
- Forgetting to cancel the background task, leaving the event loop busy.
- Not using a lock, causing race conditions when multiple coroutines add items concurrently.
- Flushing on both max size and interval can send small batches; adjust thresholds to balance latency and throughput.
Variations
- Use `asyncio.Queue` instead of a `deque` for built-in async queue semantics.
- Implement a decorator to wrap individual API calls into batches automatically.
Real-world use cases
- Batching logs or metrics before sending to an observability backend to reduce network calls.
- Grouping database insert operations to improve throughput and reduce transaction overhead.
- Aggregating user events for analytics pipelines that process data in bulk.
Sponsored
More from Concurrency & performance
- Benchmark list.append vs deque.append in Python medium
- Build a Python Performance Profiler That Generates Readable Reports medium
- Graceful Shutdown Executor Context Manager in Python medium
- How to Build a Producer-Consumer Pattern with asyncio.Queue in Python medium
- How to Cancel an asyncio Task with Graceful Cleanup in Python medium
- How to Convert Data in Parallel with ThreadPoolExecutor in Python easy
Keep learning
Related tutorials and quizzes for this topic.