> For the complete documentation index, see [llms.txt](https://docs.k9.io/key9-identity/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.k9.io/key9-identity/maxmind-api-proxy/caching/overview.md).

# How Caching Works

`maxmind-api-proxy` uses Redis as a look-aside cache to avoid querying MaxMind for IPs that have been seen recently. This is the primary mechanism for reducing API costs.

***

## Cache flow

```
Incoming request for IP X
         │
         ▼
  Redis GET geoip:X
         │
    ┌────┴────┐
   HIT       MISS
    │           │
    │      Query MaxMind for X
    │           │
    │      Redis SET geoip:X  (TTL = redis_cache_time hours)
    │           │
    └────┬──────┘
         │
    Return JSON to client
```

On a **cache hit** the response is returned immediately from Redis without any network call to MaxMind.

On a **cache miss** the proxy queries MaxMind, stores the response in Redis under the key `geoip:<ip_address>`, and returns the result.

***

## Cache keys

Each IP address is stored under the key `geoip:<ip_address>`:

```
geoip:8.8.8.8
geoip:2001:4860:4860::8888
```

IPv4 and IPv6 addresses are treated as distinct keys.

***

## Cache TTL

The `redis_cache_time` config value sets how long, **in hours**, a cached entry lives before Redis automatically expires it:

```json
{
  "redis_cache_time": 24
}
```

After the TTL expires, the next query for that IP becomes a cache miss and hits MaxMind again. There is no proactive cache invalidation — entries only expire via the Redis TTL mechanism.

Choose a TTL that balances cost savings against data freshness. MaxMind updates its databases periodically; a 24-hour TTL is a reasonable default for most use cases. For rapidly changing datasets (e.g. hosting/VPN detection) a shorter TTL may be appropriate.

***

## Caching error responses

The `cache_errors` config field controls whether MaxMind error responses are cached:

```json
{
  "cache_errors": false
}
```

| Value                 | Behaviour                                                                                                                                          |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `false` (recommended) | Error responses from MaxMind (those containing an `"error"` field) are **not** stored in Redis. The next query for that IP will hit MaxMind again. |
| `true`                | Error responses are cached like any other response. Subsequent queries for the same IP return the cached error until the TTL expires.              |

`cache_errors: false` is safer because it prevents a transient MaxMind error (e.g. a temporary outage or rate limit) from being replayed to clients for hours. Use `cache_errors: true` only if you explicitly want to suppress repeated calls for IPs that are legitimately not in MaxMind's database.

***

## Inspecting the cache

You can inspect cached entries directly with `redis-cli`:

```bash
# Connect to Redis
redis-cli -h 127.0.0.1 -p 6379 -a your_redis_password

# Check if an IP is cached
EXISTS geoip:8.8.8.8

# View the cached data
GET geoip:8.8.8.8

# Check the remaining TTL (in seconds)
TTL geoip:8.8.8.8

# Manually evict a specific IP
DEL geoip:8.8.8.8

# Count all cached entries
KEYS geoip:* | wc -l
```

***

## Cache statistics

When `http_mode` is not `production`, the proxy logs cache statistics after every request:

```
[ Total Queries: 42 | Cached: 35 [83.3%] | Not Cached: 7 [16.7%] - 8.8.8.8 pull from cache.
```

| Field         | Description                                                      |
| ------------- | ---------------------------------------------------------------- |
| Total Queries | Total number of lookup requests handled since the proxy started. |
| Cached        | Requests served from Redis.                                      |
| Not Cached    | Requests that required a MaxMind query.                          |
| Percentage    | Running hit/miss rate since startup.                             |
