Execution Strategies
A strategy decides how a fleet of hosts moves through the tasks in a play — whether they march in lock-step or each races ahead on its own. The default, linear, puts a synchronization barrier after every task: every host finishes task N before any host begins task N+1. You change this with one line, strategy: at the play level, and the choice stays invisible until you have enough hosts that the barrier itself is the bottleneck.
There are three strategies worth knowing, and they sit on a spectrum from most synchronized to least. linear is the disciplined default. free drops the discipline for speed. host_pinned is a niche variant of free. The thread running through all of them is that a strategy governs ordering across hosts — and ordering, not raw concurrency, is the thing you reach for a strategy to control.
The linear Strategy
The default linear strategy runs the play one task at a time across the whole batch: every targeted host runs task one, and only when the last host has finished task one does any host start task two. That barrier means the play moves at the speed of the slowest host on every step. It sounds like a cost, and on a large fleet it can be, but it is exactly what you want when a later task assumes an earlier one already finished everywhere.
Concretely, on Larkspur's six prod web hosts, a linear play guarantees that "write the nginx config" has completed on all six before "reload nginx" begins on any of them. No host is ever ahead of the others. That predictability is why linear is the default and why most plays should never change it.
Why the Barrier Exists
The per-task barrier is what makes cross-host ordering reliable. "Configure the db tier, then the web tier" only holds if the web hosts cannot start running while a db host is still mid-migration. Without the barrier a fast host could be three tasks ahead of a slow one, and any assumption that step five sees the result of step two — anywhere in the batch — breaks intermittently and only under load. linear removes that whole class of race by construction.
This is the push model showing its hand. Because the control node drives every host through the same task in step, it can enforce an order across the fleet that a pull model — where each node wakes on its own timer and converges alone — simply cannot express. The barrier is not overhead you tolerate; it is the orchestration primitive the rest of this chapter is built on.
The free Strategy
Setting strategy: free removes the barrier entirely. Each host runs the whole task list as fast as it can and finishes independently, so a host that flies through its tasks does not wait at the end of each one for the stragglers. This is the right call for a large set of identical, independent hosts where the real cost is one slow box holding up the other forty — there is no cross-host ordering to protect, so there is nothing for the barrier to buy.
The example below switches a forty-host log-shipper rollout to free. Every host installs and starts the agent on its own clock; a single slow host finishes late without delaying the rest. Note what this does not do: it does not make the slow host faster, and it does not give you batched waves — it only stops fast hosts from idling at the barrier.
- hosts: log_shippers strategy: free # each host races through its own task list become: true tasks: - name: Install the shipping agent ansible.builtin.apt: name: vector state: present - name: Start and enable the agent ansible.builtin.service: name: vector state: started enabled: true
The host_pinned Strategy
The host_pinned strategy behaves like free — no barrier — with one difference in scheduling: a worker stays pinned to its host for the whole run rather than picking up whichever host is next free. That keeps each host's work contiguous on a single worker instead of scattered across many. It is a niche choice for fleets where connection setup is expensive enough that the churn of re-pinning matters; on a normal SSH fleet the difference is marginal and the behavior is surprising, so linear or free covers almost every real case.
linearfreehost_pinnedfree — still no barrier, but a worker stays pinned to its host instead of picking up whichever is next free.Strategy Versus Forks
The single most common confusion in this whole area is strategy versus forks, and they are orthogonal. The strategy decides the ordering discipline across hosts — barrier or no barrier. forks decides how many hosts run at once. You can run linear with twenty forks or free with five; the strategy and the concurrency are set independently and answer different questions.
The trap is reaching for a strategy when you actually wanted more parallelism. A linear play feeling slow on sixty hosts usually needs forks raised, not strategy: free — the barrier is rarely the bottleneck until forks is already high. The next topic takes forks and its safety-side cousin serial head on; keep the two knobs separate in your head, because conflating them is how a deploy gets "sped up" by quietly breaking its ordering.
- Switching to
strategy: freeon a play with cross-host ordering — a web task that assumesdb1is already migrated — so the web hosts run ahead of the migration and the assumption breaks intermittently under load. - Reaching for
freeto "speed up" a rolling deploy, whenserialbatching is what bounds blast radius —freeremoves the very synchronization a controlled rollout depends on. - Assuming
freemakes a single slow host finish faster — it only stops that host from blocking the others; the slow host is exactly as slow as it was. - Setting
strategy: host_pinnedexpecting a meaningful speedup on a normal SSH fleet — the gain is marginal, the scheduling is surprising, andlinearorfreewould have served better. - Changing the strategy when you actually wanted more concurrency — the play was connection-starved, the fix was raising
forks, and switching tofreetraded away ordering for a speedup thatforkswould have delivered safely.
- Keep the default
linearfor anything with ordered tasks or cross-host dependencies, because the per-task barrier is the property the play's correctness rests on. - Reach for
strategy: freeonly on large sets of genuinely independent hosts doing identical work, where one slow host holding up the batch is the real cost you are paying. - Decide strategy and
forksseparately — one controls ordering, the other controls concurrency — instead of changing strategy when the actual problem is too little parallelism. - Leave
host_pinnedalone unless you have measured that connection churn underfreeis the specific bottleneck on your specific fleet. - Treat any switch away from
linearas a correctness decision in code review, not a performance tweak, since dropping the barrier changes what the play is allowed to assume.
Knowledge Check
What does the per-task barrier in the linear strategy guarantee?
- Every targeted host finishes task N before any host begins task N+1, so none ever runs ahead of the rest
- Each host runs through its full task list to completion before the next host is allowed to start
- Only one host is ever connected to at any single instant during the play, no matter how high the
forksvalue is set - Any task that fails on a host is automatically retried at the barrier before the play continues
When is strategy: free the right choice?
- A large set of identical, independent hosts with no cross-host ordering, where one slow host shouldn't hold the rest up
- A rolling deploy where each host must be drained from the load-balancer pool and then re-added in a strict sequence
- Any play you simply want to finish faster, regardless of whatever cross-host dependencies it has
- A play whose later tasks depend on its earlier tasks having already completed everywhere across the whole targeted fleet first
Why are strategy and forks described as orthogonal?
- Strategy sets the ordering discipline across hosts while
forkssets how many run at once — different knobs, set independently - Strategy applies only to a
linearplay andforksapplies only to afreeone, never both - Raising the
forksvalue past a threshold automatically switches the play's strategy tofree - They are just two different names for the very same underlying setting, kept around for compatibility across different Ansible versions
What does host_pinned change relative to free?
- A worker stays pinned to its host for the whole run instead of picking up whichever host is next free, keeping each host's work contiguous
- It re-adds the per-task synchronisation barrier across hosts that the
freestrategy had removed, so the whole fleet finishes task N before any host starts task N+1 again - It serialises the run down to strictly one host at a time, regardless of the
forkslimit - It guarantees a large, measurable speedup on any normal SSH-based fleet you point it at
You got correct