Problem
Each network call, database statement or message send has fixed overhead for round trips, parsing and acknowledgement. Doing thousands of tiny operations individually wastes throughput and overwhelms downstream systems.
Also known as: request-batching, micro-batching, bulk-operations
Group many small operations into fewer larger ones to amortize per-call overhead.
Each network call, database statement or message send has fixed overhead for round trips, parsing and acknowledgement. Doing thousands of tiny operations individually wastes throughput and overwhelms downstream systems.
Batching amortizes fixed costs. A database insert may spend 1 millisecond on the network and 0.05 milliseconds on the row, so inserting 1000 rows one by one takes about 1 second, while one multi-row statement takes roughly 60 milliseconds.
buffer = []
on item: buffer.add(item)
if size(buffer) >= 500: flush()
every 20 ms: if buffer not empty: flush()
flush(): send(buffer); handle per-item results; clear buffer
Size-or-time triggers keep latency bounded under both heavy and light traffic. Kafka producers expose this as batch.size and linger.ms. Read-side batching appears in the DataLoader pattern, which gathers all keys requested in one tick of the event loop and issues a single multi-get, solving N plus 1 in GraphQL resolvers.
Treat batches as units of retry. Use idempotency keys or upserts so a replay after a timeout does not duplicate data. When the backend returns partial success, retry only the failed items and send repeated offenders to a dead-letter queue.
Bound the buffer. When the producer outpaces the flusher, apply backpressure or drop low-priority items instead of growing memory without limit.
Lower per-operation cost
Low
Medium: failure and ordering semantics
Process crash loses items waiting in the buffer
One poison item causes the entire batch to be retried forever
Batch size grows beyond downstream limits and is rejected