Caching
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, 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:
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
- npm
- pnpm
- yarn
- bun
npm install @credo-ts/redis-cache@alpha ioredis
pnpm add @credo-ts/redis-cache@alpha ioredis
yarn add @credo-ts/redis-cache@alpha ioredis
bun add @credo-ts/redis-cache@alpha ioredis
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:
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':
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))
}
}