The CachePersistenceLayer class provides Redis and Valkey-backed storage for idempotency records. It uses cache clients compatible with the CacheClient interface for high-performance scenarios.
Stores idempotency records in Redis or Valkey cache stores with automatic TTL and orphan record handling.
/**
* Valkey and Redis OSS-compatible persistence layer for idempotency records.
*
* Uses a cache client to write and read idempotency records.
* Supports any client that implements the CacheClient interface.
* You must provide your own connected client instance.
*
* @param options - Configuration options for cache persistence
*/
class CachePersistenceLayer extends BasePersistenceLayer {
constructor(options: CachePersistenceOptions);
}
interface CachePersistenceOptions extends BasePersistenceAttributes {
/** Connected cache client instance (required) */
client: CacheClient;
/** Status attribute name (default: 'status') */
statusAttr?: string;
/** Expiry timestamp attribute name (default: 'expiration') */
expiryAttr?: string;
/** In-progress expiry timestamp attribute name (default: 'in_progress_expiration') */
inProgressExpiryAttr?: string;
/** Response data attribute name (default: 'data') */
dataAttr?: string;
/** Payload validation hash attribute name (default: 'validation') */
validationKeyAttr?: string;
}
/**
* Interface for clients compatible with Valkey and Redis-OSS operations.
* Defines the minimum set of operations for cache persistence.
*/
interface CacheClient {
/**
* Retrieves the value associated with the given key
* @param name - The key to get the value for
* @returns The value or null if not found
*/
get(name: string): Promise<CacheValue | null>;
/**
* Sets the value for the specified key with optional parameters
* @param name - The key to set
* @param value - The value to set
* @param options - Optional parameters (e.g., { EX: ttl, NX: true })
* @returns The value set or null if operation failed
*/
set(
name: CacheValue,
value: unknown,
options?: unknown
): Promise<CacheValue | null>;
/**
* Deletes the specified keys from the cache
* @param keys - The keys to delete
* @returns Number of keys deleted
*/
del(keys: string[]): Promise<number>;
}
type CacheValue = string | Uint8Array<ArrayBufferLike>;Usage Examples:
Using Valkey Glide Client
import { GlideClient } from '@valkey/valkey-glide';
import { CachePersistenceLayer } from '@aws-lambda-powertools/idempotency/cache';
import { makeIdempotent } from '@aws-lambda-powertools/idempotency';
// Create and configure Valkey Glide client
const client = await GlideClient.createClient({
addresses: [{
host: String(process.env.CACHE_ENDPOINT),
port: Number(process.env.CACHE_PORT),
}],
useTLS: true,
requestTimeout: 2000,
});
const persistenceStore = new CachePersistenceLayer({
client,
});
const myHandler = async (event: any, context: any) => {
// Your logic
};
export const handler = makeIdempotent(myHandler, { persistenceStore });Using Redis Client
import { createClient } from '@redis/client';
import { CachePersistenceLayer } from '@aws-lambda-powertools/idempotency/cache';
import { makeIdempotent } from '@aws-lambda-powertools/idempotency';
// Create and connect Redis client
const redisClient = await createClient({
url: `rediss://${process.env.CACHE_ENDPOINT}:${process.env.CACHE_PORT}`,
username: 'default',
}).connect();
const persistenceStore = new CachePersistenceLayer({
client: redisClient,
});
const myHandler = async (event: any, context: any) => {
// Your logic
};
export const handler = makeIdempotent(myHandler, { persistenceStore });With Custom Attribute Names
import { createClient } from '@redis/client';
import { CachePersistenceLayer } from '@aws-lambda-powertools/idempotency/cache';
import { makeIdempotent } from '@aws-lambda-powertools/idempotency';
const redisClient = await createClient({
url: process.env.REDIS_URL,
}).connect();
const persistenceStore = new CachePersistenceLayer({
client: redisClient,
statusAttr: 'state',
expiryAttr: 'ttl',
dataAttr: 'response',
validationKeyAttr: 'hash',
});
const myHandler = async (event: any, context: any) => {
// Your logic
};
export const handler = makeIdempotent(myHandler, { persistenceStore });With ElastiCache Redis
import { createClient } from '@redis/client';
import { CachePersistenceLayer } from '@aws-lambda-powertools/idempotency/cache';
import { makeIdempotent, IdempotencyConfig } from '@aws-lambda-powertools/idempotency';
// Connect to AWS ElastiCache Redis
const redisClient = await createClient({
socket: {
host: process.env.ELASTICACHE_ENDPOINT,
port: 6379,
},
}).connect();
const persistenceStore = new CachePersistenceLayer({
client: redisClient,
});
const config = new IdempotencyConfig({
expiresAfterSeconds: 600, // 10 minutes
});
const myHandler = async (event: any, context: any) => {
// Your logic
};
export const handler = makeIdempotent(myHandler, { persistenceStore, config });With MemoryDB for Redis
import { createClient } from '@redis/client';
import { CachePersistenceLayer } from '@aws-lambda-powertools/idempotency/cache';
import { makeIdempotent } from '@aws-lambda-powertools/idempotency';
// Connect to AWS MemoryDB with TLS
const redisClient = await createClient({
socket: {
host: process.env.MEMORYDB_ENDPOINT,
port: 6379,
tls: true,
},
username: 'default',
password: process.env.MEMORYDB_PASSWORD,
}).connect();
const persistenceStore = new CachePersistenceLayer({
client: redisClient,
});
const myHandler = async (event: any, context: any) => {
// Your logic
};
export const handler = makeIdempotent(myHandler, { persistenceStore });Connection Reuse Pattern
import { createClient } from '@redis/client';
import { CachePersistenceLayer } from '@aws-lambda-powertools/idempotency/cache';
import { makeIdempotent } from '@aws-lambda-powertools/idempotency';
// Create client outside handler for connection reuse across invocations
let redisClient: ReturnType<typeof createClient> | null = null;
const getRedisClient = async () => {
if (!redisClient) {
redisClient = createClient({
url: process.env.REDIS_URL,
});
await redisClient.connect();
}
return redisClient;
};
const myHandler = async (event: any, context: any) => {
// Your logic
};
// Initialize at module load time
const client = await getRedisClient();
const persistenceStore = new CachePersistenceLayer({ client });
export const handler = makeIdempotent(myHandler, { persistenceStore });Records are stored as JSON strings with the idempotency key as the cache key:
{
"status": "COMPLETED",
"expiration": 1234567890,
"in_progress_expiration": 1234567890000,
"data": { "statusCode": 200, "body": "..." },
"validation": "e5f6g7h8..."
}Cache Key Format: {keyPrefix}#{hash} where:
keyPrefix defaults to Lambda function namehash is generated from the payload using the configured hash functionThe cache persistence layer automatically sets TTL (Time To Live) on all records:
EX option on SET commandsexpiresAfterSeconds configurationThe persistence layer handles orphaned records (from timed-out Lambda executions):
in_progress_expiration timestampsIdempotencyItemAlreadyExistsError if lock failsThe cache persistence layer uses conditional SET operations:
NX flag to set only if key doesn't existIdempotencyItemAlreadyExistsErrorIdempotencyItemAlreadyExistsError if not expiredYour cache client must implement:
get(key) - Retrieve value by keyset(key, value, options) - Set value with options (must support EX and NX)del(keys) - Delete one or more keysIdempotencyItemNotFoundError - Record not found during retrievalIdempotencyItemAlreadyExistsError - Duplicate request detected or lock acquisition failedIdempotencyPersistenceConsistencyError - JSON parsing error or orphaned record detectedIdempotencyUnknownError - Invalid operation (e.g., trying to insert non-INPROGRESS record)