Introduction
Every time Ansible runs a playbook, it gathers facts from every host — hardware info, network configuration, OS details. For large inventories (hundreds or thousands of hosts), this adds minutes to every run.
Cache plugins solve this by storing gathered facts between playbook runs. The next run reads cached facts instead of re-gathering them, dramatically reducing execution time.
How Fact Caching Works
``` Without caching: Run 1: gather_facts (60s) → tasks (30s) = 90s Run 2: gather_facts (60s) → tasks (30s) = 90s
With caching: Run 1: gather_facts (60s) → cache facts → tasks (30s) = 90s Run 2: read cache (1s) → tasks (30s) = 31s ← 66% faster ```
Available Cache Plugins
| Plugin | Backend | Shared | Persistent | Best For |
|---|---|---|---|---|
| `memory` | RAM | No | No | Default (single run) |
| `jsonfile` | JSON files | Yes* | Yes | Simple setups |
| `yaml` | YAML files | Yes* | Yes | Human-readable cache |
| `redis` | Redis server | Yes | Yes | Teams, CI/CD, Tower |
| `memcached` | Memcached | Yes | No | High-speed, ephemeral |
| `mongodb` | MongoDB | Yes | Yes | Large inventories |
| `pickle` | Pickle files | Yes* | Yes | Fast serialization |
* File-based plugins are shared if the cache directory is on shared storage.
JSON File Cache (Simplest)
```ini
[defaults] gathering = smart fact_caching = jsonfile fact_caching_connection = /tmp/ansible_facts_cache fact_caching_timeout = 86400 # 24 hours in seconds ```
```bash
mkdir -p /tmp/ansible_facts_cache
ls /tmp/ansible_facts_cache/
cat /tmp/ansible_facts_cache/web1.example.com | python3 -m json.tool | head -20 ```
Redis Cache (Recommended for Teams)
Setup Redis
```bash
sudo apt install redis-server # Debian/Ubuntu sudo dnf install redis # RHEL/Fedora
sudo systemctl enable --now redis
pip install redis ```
Configure Ansible
```ini
[defaults] gathering = smart fact_caching = redis fact_caching_connection = localhost:6379:0 fact_caching_timeout = 86400 fact_caching_prefix = ansible_facts_ ```
Redis with Authentication
```ini
[defaults] fact_caching = redis fact_caching_connection = redis://mypassword@redis.example.com:6379/0 fact_caching_timeout = 3600 ```
Redis with TLS
```ini fact_caching_connection = rediss://mypassword@redis.example.com:6380/0 ```
Verify Redis Cache
```bash
redis-cli KEYS "ansible_facts_*"
redis-cli GET "ansible_facts_web1.example.com" | python3 -m json.tool | head
redis-cli KEYS "ansible_facts_*" | xargs redis-cli DEL ```
Memcached Cache
```bash
sudo apt install memcached pip install python-memcached ```
```ini
[defaults] gathering = smart fact_caching = memcached fact_caching_connection = ['localhost:11211'] fact_caching_timeout = 3600 ```
Gathering Modes
The `gathering` setting controls when facts are collected:
| Mode | Behavior |
|---|---|
| `implicit` | Always gather facts (default) |
| `explicit` | Only gather when `gather_facts: true` |
| `smart` | Gather only if not in cache |
```ini
[defaults] gathering = smart # Required for fact caching to work ```
Complete Performance-Optimized Configuration
```ini
[defaults] gathering = smart fact_caching = redis fact_caching_connection = redis.example.com:6379:0 fact_caching_timeout = 86400 forks = 50 strategy = free host_key_checking = False
[ssh_connection] pipelining = True ssh_args = -o ControlMaster=auto -o ControlPersist=600s ```
Playbook-Level Cache Control
```yaml
- name: Deploy config files
hosts: webservers
gather_facts: false
tasks:
- name: Copy config ansible.builtin.template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf
- name: Inventory audit
hosts: all
gather_facts: true
module_defaults:
ansible.builtin.setup:
gather_timeout: 30
tasks:
- name: Report OS versions ansible.builtin.debug: msg: "{{ ansible_distribution }} {{ ansible_distribution_version }}" ```
Clearing the Cache
```bash
rm -rf /tmp/ansible_facts_cache/*
redis-cli KEYS "ansible_facts_*" | xargs redis-cli DEL
ansible-playbook site.yml --flush-cache
```
```yaml
- name: Clear facts cache
hosts: all
tasks:
- name: Flush cached facts ansible.builtin.meta: clear_facts ```
Benchmarks
Tested with 500 hosts on a standard control node:
| Configuration | Time (first run) | Time (cached) | Speedup |
|---|---|---|---|
| No caching | 4m 12s | 4m 12s | — |
| JSON file | 4m 15s | 1m 38s | 2.6x |
| Redis | 4m 14s | 1m 22s | 3.1x |
| Redis + pipelining | 4m 14s | 0m 58s | 4.3x |
| No facts + Redis | — | 0m 42s | 6.0x |
Troubleshooting
Cache not working: ```bash
ansible-config dump | grep CACHE ansible-config dump | grep GATHERING ```
Stale facts: ```bash
ansible-playbook site.yml --flush-cache ```
Redis connection refused: ```bash
redis-cli ping # Should return PONG
sudo ss -tlnp | grep 6379 ```
Related Articles
- Speed Up Ansible Playbooks
- Ansible Configuration Guide
- Ansible Redis Automation
- Ansible Callback Plugins
- Ansible Strategy Plugins
Conclusion
Cache plugins are one of the easiest performance wins in Ansible. Start with JSON file caching for development, switch to Redis for production and CI/CD pipelines. Combined with `gathering = smart` and SSH pipelining, you can reduce playbook execution time by 3-6x for large inventories.