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.

Medium Python 3.9+ Aug 9, 2026 Concurrency & performance 14 views 0 copies

Python code

43 lines
Python 3.9+
import 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

stdout
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

  1. Use `asyncio.Queue` instead of a `deque` for built-in async queue semantics.
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Concurrency & performance

Related tutorials and quizzes for this topic.