# Caching

> Choose and configure the cache a Credo agent uses for resolved DID documents, ledger objects and certificate revocation lists.

Credo caches data that is slow to fetch: resolved DID documents, AnonCreds objects from ledgers and revocation lists of X.509 certificates. A cache is always registered, and you only replace it in two cases: when you use [multi-tenancy](https://credo.js.org/guides/agent-setup/multi-tenancy.md), and when several instances of the agent run side by side.

## Which should I use?

Keep the default for a single agent without tenants, such as a mobile wallet: nothing to configure, and entries survive a restart. Use `InMemoryLruCache` when the agent has tenants, because the default throws as soon as a tenant uses it. Use `RedisCache` when several instances of the agent run behind a load balancer, so they share one cache. With the default, instances overwrite each other's cache record in the shared storage, and with `InMemoryLruCache` each instance fetches and caches everything on its own.

| Cache | Package | Tenants | Several processes share it | Survives a restart |
| --- | --- | --- | --- | --- |
| `SingleContextStorageLruCache` (default) | `@credo-ts/core` | No | No | Yes |
| `InMemoryLruCache` | `@credo-ts/core` | Yes | No | No |
| `RedisCache` | `@credo-ts/redis-cache` | Yes | Yes | Yes |

The default keeps up to 500 entries in memory and saves them as a record in the agent's storage. When a tenant uses it, it throws `SingleContextStorageLruCache can not be used with multiple agent context instances`.

## Configure the cache

Register a `CacheModule` under the key `cache` to replace the default:

```ts
import { Agent, CacheModule, InMemoryLruCache } from '@credo-ts/core'
import { agentDependencies } from '@credo-ts/node'

const agent = new Agent({
  dependencies: agentDependencies,
  modules: {
    ...storage, // Storage and key management, see Agent setup
    cache: new CacheModule({
      cache: new InMemoryLruCache({ limit: 500 }),
    }),
  },
})
```

`InMemoryLruCache` removes the least recently used entries when it reaches `limit`. The limit applies to all agent contexts together.

| `CacheModule` option | Description |
| --- | --- |
| `cache` | The cache to use. Required. |
| `defaultExpiryInSeconds` | Expiry for entries set without one. `RedisCache` uses it. Default `60`. |
| `useCachedStorageService` | Also caches DIDComm connection, mediation and mediator routing records, to save storage reads on busy DIDComm agents such as mediators. Default `false`. With several instances, only turn it on with a cache they share, such as Redis. |

## Redis

```bash
npm install @credo-ts/redis-cache@^0.7.2 ioredis
```

```ts
import { Agent, CacheModule } from '@credo-ts/core'
import { agentDependencies } from '@credo-ts/node'
import { RedisCache } from '@credo-ts/redis-cache'
import { Redis } from 'ioredis'

const redis = new Redis('redis://localhost:6379')

const agent = new Agent({
  dependencies: agentDependencies,
  modules: {
    ...storage, // Storage and key management, see Agent setup
    cache: new CacheModule({
      // Also accepts ioredis options, e.g. new RedisCache({ host: 'localhost', port: 6379 })
      cache: new RedisCache(redis),
      defaultExpiryInSeconds: 300,
    }),
  },
})
```

`RedisCache` takes an `ioredis` client or `ioredis` options. Keys are prefixed with the agent context, or with `global:` for shared entries, so tenants and instances can share one Redis database. When you create the client yourself, close it after the agent shuts down:

```ts
await agent.shutdown()
// The agent doesn't close the Redis connection for you
await redis.quit()
```

`cache.destroy(agentContext)` removes all entries of one agent context, for example after you delete a tenant.

## What is cached

| Data | Cached for | Scope |
| --- | --- | --- |
| Resolved DID documents, for methods whose resolver allows caching (`did:web`, `did:webvh`, `did:cheqd`, `did:indy`, `did:sov`, `did:hedera`). `did:key`, `did:jwk` and `did:peer` aren't cached. | 5 minutes, change it with `cacheDurationInSeconds` when resolving | Global for methods in `DidsModule({ publicDidMethods })`, otherwise per agent context |
| AnonCreds schemas, credential definitions, revocation registry definitions and status lists | 5 minutes | Global |
| Summaries of X.509 certificate revocation lists | Until the list's next update | Global |
| Indy VDR: which ledger a DID is on | Until evicted | Global |
| DIDComm connection, mediation and mediator routing records, with `useCachedStorageService: true` | Until evicted | Per agent context |

Global entries are public data that all tenants share. `publicDidMethods` defaults to `web`, `indy`, `sov`, `cheqd`, `hedera` and `webvh`.

## Custom cache

Implement the `Cache` interface: `get`, `set` and `remove`. Each method receives the agent context and an optional `scope`. Keep entries of different agent contexts apart, and only share entries when `scope` is `'global'`:

```ts
import type { AgentContext, Cache, CacheOptions } from '@credo-ts/core'

export class MapCache implements Cache {
  private entries = new Map<string, { value: unknown; expiresAt?: number }>()

  // Isolate entries per agent context unless the caller asks for the global scope
  private key(agentContext: AgentContext, key: string, options?: CacheOptions) {
    return options?.scope === 'global' ? `global:${key}` : `${agentContext.contextCorrelationId}:${key}`
  }

  public async get<CacheValue>(agentContext: AgentContext, key: string, options?: CacheOptions) {
    const namespacedKey = this.key(agentContext, key, options)
    const entry = this.entries.get(namespacedKey)
    if (!entry) return null

    if (entry.expiresAt && entry.expiresAt < Date.now()) {
      this.entries.delete(namespacedKey)
      return null
    }

    return entry.value as CacheValue
  }

  public async set<CacheValue>(
    agentContext: AgentContext,
    key: string,
    value: CacheValue,
    expiresInSeconds?: number,
    options?: CacheOptions
  ) {
    this.entries.set(this.key(agentContext, key, options), {
      value,
      expiresAt: expiresInSeconds ? Date.now() + expiresInSeconds * 1000 : undefined,
    })
  }

  public async remove(agentContext: AgentContext, key: string, options?: CacheOptions) {
    this.entries.delete(this.key(agentContext, key, options))
  }
}
```
