Fact Caching¶
What You'll Learn¶
- What fact caching stores, and when Ansible reads from it
- How to configure the
jsonfileandredisbackends - How
gathering = smartdecides whether to contact a host for facts - The staleness trade-off, and how to clear the cache
Why This Exists¶
Fact gathering is a real remote execution step (the implicit setup module call) with real cost, repeated at the start of nearly every play — fact caching lets a run reuse facts gathered recently instead of re-gathering them every single time.
On a few hosts that cost is invisible. On a thousand hosts, gathering can take minutes of every run, and it's also what lets hostvars see facts for hosts that aren't in the current play.
Mental Model¶
Without a cache, facts live in memory and vanish when
ansible-playbookexits. With a cache, every gathered fact is also written to a store, keyed by hostname, with an expiry. Later runs can load facts from the store instead of — or in addition to — contacting the host.
flowchart LR
R["Run starts"] --> Q{"Fresh facts in cache<br>for this host?"}
Q -->|"yes, and gathering = smart"| U["Use cached facts"]
Q -->|"no, or expired"| G["Run setup on the host"] --> W["Write facts to cache"] --> U
Configuring the jsonfile Backend¶
[defaults]
gathering = smart
fact_caching = ansible.builtin.jsonfile
fact_caching_connection = .ansible_fact_cache
fact_caching_timeout = 7200
fact_caching_connectionis a directory; Ansible writes one JSON file per host.fact_caching_timeoutis in seconds.0means never expire.- Add the directory to
.gitignore— facts include IP addresses, hostnames, and other details you don't want in a repository.
Run once, then look:
ansible-playbook site.yml
ls .ansible_fact_cache/
# db01 web01 web02
jq '.ansible_distribution, .ansible_default_ipv4.address' .ansible_fact_cache/web01
Gathering Policies¶
gathering (ansible.cfg) |
Behavior |
|---|---|
implicit (default) |
Gather at the start of every play unless it sets gather_facts: false |
explicit |
Never gather unless a play sets gather_facts: true |
smart |
Gather only for hosts with no facts in the current run or in a fresh cache entry |
smart is what turns a cache into a speed-up. With implicit, Ansible still gathers every time and merely writes the cache.
smart is an ansible.cfg setting
The play keyword gather_facts only accepts true or false. Smart gathering is configured with gathering = smart (or ANSIBLE_GATHERING=smart).
Configuring the Redis Backend¶
jsonfile is local to one machine. When playbooks run from several CI runners or controllers, they each keep separate caches. A shared Redis fixes that:
[defaults]
gathering = smart
fact_caching = community.general.redis
fact_caching_connection = redis.internal.example.com:6379:0
fact_caching_prefix = ansible_facts_
fact_caching_timeout = 7200
The connection string is host:port:db. Secure it like any other shared service: facts describe your whole fleet.
| Backend | Plugin | Good for |
|---|---|---|
jsonfile |
ansible.builtin.jsonfile |
One control node or a persistent runner; simplest |
redis |
community.general.redis |
Many runners or controllers sharing one cache |
memcached |
community.general.memcached |
Same idea, where memcached is already standard |
memory |
ansible.builtin.memory |
The default: in-process only, nothing persists |
Caching Your Own Values¶
- name: Persist the detected cluster role across runs
ansible.builtin.set_fact:
cluster_role: "{{ 'primary' if is_leader else 'replica' }}"
cacheable: true
See set_fact and combine.
Using the Cache for Cross-Host Lookups¶
A common reason to add a cache isn't speed but hostvars. With a warm cache, this works even under --limit web, because db01's facts load from the cache:
Keep the cache warm with a scheduled fact-gathering run:
Staleness: The Trade-Off¶
A cached fact is a snapshot. If a host's IP address, disk layout, or installed kernel changes within the timeout, playbooks see the old value.
| Environment | Reasonable timeout |
|---|---|
| Long-lived VMs and bare metal | Hours to a day |
| Autoscaled or frequently rebuilt instances | Minutes, or no cache |
| Right after hardware or network changes | Flush first |
Force fresh facts when you know something changed:
- name: Re-gather after changing network configuration
ansible.builtin.setup:
gather_subset: [network]
Common Mistakes¶
- Enabling fact caching with a long timeout on hosts whose facts genuinely change often (autoscaled infrastructure), then debugging "wrong" values that are actually just stale.
- Using
jsonfilecaching across multiple CI runners that don't share a filesystem — each runner gets its own cache, defeating the point. - Configuring a cache but leaving
gathering = implicit, so every run still gathers facts and nothing gets faster. - Writing
gather_facts: smartin a play, which isn't a valid value. - Committing the
jsonfilecache directory to Git.
Interview Questions¶
- What problem does fact caching solve, and what's the trade-off against always gathering fresh?
- Why would a team choose
redisfact caching overjsonfile? - What does
gathering = smartdo, and why does a cache not speed anything up without it? - How can a fact cache make
hostvarswork under--limit?
Next¶
Continue to Connection Plugins.