Ordered and parallel Flow
Latest stable Based on Bluetape4k release 1.11.0
Concurrent execution and emission order are separate contracts. The important difference between flow.async and mapParallel is output ordering, not simply speed.
Decision table
Section titled “Decision table”| Requirement | Choice | Output order | Pressure control |
|---|---|---|---|
| simple sequential transform | standard map | input | one at a time |
| overlap computation but retain response order | Flow.async | input | collect buffer |
| process whichever result finishes first | mapParallel(n) | completion may win | bounded by n |
Ordered concurrency
Section titled “Ordered concurrency”Flow.async converts each input into a LazyDeferred and starts it in the collect scope. Work overlaps, but downstream awaits deferred values in original order.
productIds.asFlow() .async(Dispatchers.IO) { id -> catalog.load(id) } .collect(capacity = 16) { product -> render(product) }A slow earlier item delays emission of later completed items. This head-of-line cost is appropriate when response order is contractual.
Capacity accepts Channel.BUFFERED, Channel.CONFLATED, or a non-negative number. It bounds result buffering; it is not permission for unbounded work.
Throughput-first transforms
Section titled “Throughput-first transforms”mapParallel coerces parallelism to at least one. One uses plain map; values above one use flatMapMerge(concurrency).
val stored = events.asFlow() .mapParallel(parallelism = 8, context = Dispatchers.IO) { event -> repository.persist(event) } .toList()Faster items may emit first, so input order must not be a public contract. This is a good fit for independent persistence, enrichment, and thumbnail work.
Sizing parallelism
Section titled “Sizing parallelism”Do not derive it from CPU count alone.
effective parallelism = min( application budget, connection pool capacity, remote concurrency limit, memory/buffer budget)With retries, worst-case in-flight work approaches parallelism × attempts. Sum competing pipelines that share the same downstream boundary.
Failure and cancellation
Section titled “Failure and cancellation”- transform failure fails collection and structured siblings are cancelled;
- collector cancellation propagates upstream and into active children;
flowOn(context)changes execution context, not lifecycle ownership;- remote work surviving timeout usually indicates a client cancellation-bridge problem.
What to test
Section titled “What to test”- parallelism one, zero, and negative values take the ordered sequential path;
- values above one are allowed to change output order;
- active transforms never exceed the configured bound;
- collector cancellation cleans up child work and remote requests;
- a slow first item demonstrates head-of-line waiting on the ordered path.
Operational signals
Section titled “Operational signals”Track P95/P99 item latency, active transforms, buffer utilization, and downstream waiting together. A permanently full buffer is a capacity mismatch. Locate that boundary before increasing parallelism.
Source and representative tests
Section titled “Source and representative tests”For callback and hot-stream delivery semantics, continue with Subjects and event contracts.