160
round trips for one 80-item cart
Two calls per line at 1.5 ms each is 240 ms of waiting. The data itself, about 16 KB, crosses a 10 Gbit/s link in about 13 µs.
Don’t keep-alive and HTTP/2 fix it?
Bad: “Connections are reused”
A loop that awaits each call still pays one full round trip per item.
Good: Fewer round trips
Only batching, or overlapping the calls, cuts the waits.
It grows out of code that reads well
An await inside a loop, ORM lazy loading, get-by-id services, one GET per cache key. With 3 items on a laptop, nobody notices.
Detect it: count calls, not milliseconds
Calls per request rise with item count. pg_stat_statements shows huge call counts at one row each, and traces show a staircase of tiny spans.
Move the loop to the other side of the wire
- One query with
WHERE sku = ANY($1) - Batch endpoints, one call carrying every id
- Redis MGET or a pipeline
- Buffer writes; COPY instead of row-by-row inserts
24 tiles, four services: 96 calls, or 4
A listing BFF calls each service’s batch endpoint once, in parallel. The call count stops depending on how many tiles the page shows.
No batch API? Fire all 80 calls in parallel. Problem solved?
The catch
It hides latency, not load: with 80 calls, 55% of pages hit a p99-slow call. Batch first, then parallelise with a limit.
Remember
Every call pays a round trip, so N calls cost N × RTT. Move the loop to the server, with batch queries, batch endpoints, MGET or buffered writes. Parallelise only what you can’t batch.