Skip to content
Talk to our solutions team

Build a fleet in parallel

The same fleet build, finishing in a fraction of the time, by running repositories concurrently with a ceiling you choose — without losing the ability to say which repository failed.

When you finish a build that took forty minutes serially will take as long as its slowest few repositories.

Serial builds waste a machine. A build host with sixteen cores compiling one repository at a time is idle in fifteen of them.

Without thisWith this
Build time is the sum of every repositoryBuild time approaches the slowest one
A sixteen-core builder runs one compileThe machine is actually used
Nobody re-runs the full build because it takes too longIt is cheap enough to run on every change
Unbounded & in a shell script exhausts memory and the OOM killer picks a victimA ceiling you set, and can lower on a small machine

The ceiling is the part people skip. Unbounded parallelism on a fleet build is how you discover your build host’s memory limit — during a release.

You needWhy
A working serial buildSee Build every service from trunk
An idea of your machine’s limitsCores, and memory per compile

Step 1 — extract the per-repository work into its own task

Section titled “Step 1 — extract the per-repository work into its own task”

Fan-out needs something to fan out to. The unit of parallelism is a task, so the work for one repository has to be a task by itself.

tasks:
- name: build-one
shell: |
set -e
cd {{name}}
go build -ldflags "{{ldflags}}" \
-o ../build/{{version}}/{{osarch}}/{{name}} ./...

This is not called directly. It is the body that the fan-out invokes once per row.

- name: build-all
map:
items_path: repos
task_name: build-one
max_concurrency: 4

items_path is the table, task_name is the task to run per item, max_concurrency is the ceiling. Four is a conservative default that suits most builders.

Step 3 — choose the ceiling deliberately

Section titled “Step 3 — choose the ceiling deliberately”

The right number is bounded by memory, not cores. Compilers are memory-hungry, and the failure mode of guessing too high is a killed process with a confusing message.

MachineReasonable ceiling
4 cores, 8 GB2
8 cores, 16 GB4
16 cores, 32 GB6–8
CI container with a hard memory capDivide the cap by the largest single compile

Make it a variable so it can be lowered without editing tasks:

vars:
concurrency: 4
- name: build-all
map:
items_path: repos
task_name: build-one
max_concurrency: "{{concurrency}}"

Step 4 — sequence the groups that must be ordered

Section titled “Step 4 — sequence the groups that must be ordered”

Parallel within a group, ordered between groups. If libraries must exist before services build, express that as two fan-outs chained with next, not as one big pool.

- name: build-libs
map:
items_path: libs
task_name: build-one
max_concurrency: "{{concurrency}}"
next:
go: build-services
- name: build-services
map:
items_path: services
task_name: build-one
max_concurrency: "{{concurrency}}"
name: build-parallel
workingdirectory: ~/workspace
continueonerror: true
vars:
version: "1.2.0"
osarch: linux-amd64
ldflags: "-s -w"
concurrency: 4
tables:
libs:
type: csv
file: ./libs.csv
services:
type: csv
file: ./services.csv
tasks:
- name: prepare-tree
shell: |
mkdir -p build/{{version}}/{{osarch}}
next:
go: build-libs
- name: build-one
shell: |
set -e
cd {{name}}
go build -ldflags "{{ldflags}}" \
-o ../build/{{version}}/{{osarch}}/{{name}} ./...
- name: build-libs
map:
items_path: libs
task_name: build-one
max_concurrency: "{{concurrency}}"
next:
go: build-services
- name: build-services
map:
items_path: services
task_name: build-one
max_concurrency: "{{concurrency}}"
next:
go: manifest
- name: manifest
shell: |
cd build/{{version}}/{{osarch}}
ls -1 > MANIFEST.txt
sha256sum * > SHA256SUMS 2>/dev/null || true
echo "built $(wc -l < MANIFEST.txt) artefacts"

This is the guide where the flow is not merely tidier, it is the feature. map: with max_concurrency and -w gives you a bounded worker pool, per-item output buffering so logs stay readable, and a record of which items completed — none of which the script has. Use the script to develop buildOne; use the flow to run the fleet.

Terminal window
kis flow -f build-parallel.yaml

Throttle on a small machine:

Terminal window
kis flow -f build-parallel.yaml -v concurrency=2

Compare MANIFEST.txt against the serial build’s. Parallelism must not change what is produced — if the artefact list differs, you have a dependency between repositories that the group ordering does not express.

ChangeWhere
More groupsAnother table and another map, chained with next
Fail fast instead of finishingDrop continueonerror
Spread across machines-w raises flow workers; see Run a flow across many hosts (planned)