Redis/ElastiCache platform caching across serverless apps.
Redis work can sound small if it is described as "added caching." The harder part was the
platform boundary: shared ElastiCache infrastructure, service-scoped credentials, keyspace
rules, Lambda runtime configuration, and a migration path downstream applications could follow.
A shared cache gets risky when every service wires it differently.
Several serverless applications can need Redis for the same broad reason: avoid expensive or
repeated reads on hot paths. The risk is not Redis itself. It is drift, the small wiring
decisions each service makes alone that only become a problem once several services share one
cache.
Cache key collisions when services invent their own prefixes.
Credential blast radius when one broad Redis user is shared.
Drift in secret shapes and connection settings between services.
Unclear ownership of who created which Redis user and why.
App migrations that depend on tribal knowledge instead of a checklist.
02 — Options
The useful platform layer is small, but it needs a clear boundary.
Hand-wire Redis in every app
Fast for the first service, but it spreads endpoint, secret, and client-shape decisions across every downstream stack.
Share one broad credential
Easy to connect, but the blast radius is too large and cache key ownership becomes hard to reason about.
Use a service-scoped platform pattern Chosen
The chosen shape: shared cache foundation, generated service credentials, keyspace-scoped access, and repeatable app wiring.
Build a central cache API
Useful later if cache behavior becomes a product capability, but too much ownership shift for this infrastructure problem.
03 — Architecture
One shared cache foundation, service-level access around it.
The platform stack owns cache infrastructure and the exported endpoint and user-group
identifiers. Each application stack owns its Redis user, generated secret, Lambda permission,
runtime client, and cache behavior. That keeps reuse in the infrastructure layer without
taking application ownership away.
ElastiCache Serverless cache foundation
Redis user group and service user membership
Generated service secret payload
SSM-style exports for endpoint and user-group identifiers
Lambda runtime configuration from REDIS_SECRET_ARN
Scoped secret-read grant for the consuming function
Health-check and cleanup steps for app migration
Platform stack
ElastiCacheserverless Redis →User groupshared membership →Exportsendpoint + group id
↕
A service creates its own scoped user, then connects to the shared cache and consumes the
exported endpoint and group id.
The point is not to hide Redis. It is to put the repeatable infrastructure and credential
shape in one place, then let applications decide what they cache and how they handle misses.
04 — Implementation
The TypeScript surface keeps the moving parts named.
The companion repo is not a full CDK construct library. It is a typed model of the decisions
that mattered: shared cache exports, service-scoped access, idempotent user planning, and
Lambda runtime configuration.
cache-platform.ts
Shared cache foundation
Models the shared ElastiCache Serverless foundation, subnet validation, and app-facing exports.
Converts the secret payload into a TLS Redis connection shape and key prefix. Validates required fields before the client is built.
export function createRedisConnectionConfig(secret: RedisSecret): RedisConnectionConfig {
for (const field of ["host", "port", "username", "password"] as const) {
if (secret[field] === undefined || secret[field] === "") {
throw new Error(`Redis secret is missing ${field}`);
}
}
return {
url: buildRedisUrl(secret),
username: secret.username,
password: secret.password,
keyPrefix: secret.keyspace ? `${secret.keyspace}:` : "",
socket: {
tls: secret.tls !== false,
checkServerIdentity: false
}
};
}
05 — Migration path
The adoption path matters as much as the cache.
A shared platform pattern only helps if application teams can adopt it without guessing which
permissions, secrets, health checks, and cleanup steps are required.
01
Create the service Redis user and generated secret.
02
Inject REDIS_SECRET_ARN into the consuming Lambda environment.
03
Grant the Lambda role read access to only that Redis secret.
04
Build the Redis client from host, port, username, password, and keyspace.
05
Add a Redis ping or cache-read signal to the service health path.
06
Remove old Redis configuration after the deployment is stable.
06 — Tests
The tests cover the provisioning behavior that is easy to drift.
The local tests check the behavior without deploying AWS resources or connecting to Redis.
That makes the public sample deterministic while still showing what would matter in a real
platform implementation.
CheckCommandResult
Type checknpm run checkTypeScript strict mode passes
Behavior testsnpm test11 Node test scenarios pass
Build gatenpm run buildtsc --noEmit passes
test/redis-user-handler.test.ts
Example behavior test
11 Node test scenarios pass. This one asserts the create-user and add-user-to-group actions fire together for a new service user.
test("plans user creation and group membership for new service user", () => {
const actions = planRedisUserChange({
serviceName: "enrollment-api",
keyspace: "enrollment-api",
password: "secret",
existingUsers: [],
currentGroupUserIds: []
});
assert.deepEqual(actions.map((action) => action.type), [
"create-user",
"add-user-to-group"
]);
});
07 — Tradeoffs
What this design chooses, and what it does not claim.
01
Provisioning becomes explicit
Per-service Redis users add a provisioning step. I prefer that cost here because credential rotation, group membership, and access changes become reviewable instead of manual.
02
Keyspace isolation is a boundary, not authorization
Redis access strings and key prefixes reduce accidental collisions. They do not replace application authorization, so the service still owns what it caches and who can read it.
03
A caching boundary, not a datastore of record
This is low-latency shared cache access. It is close to online serving, but it does not promise freshness guarantees or consistency between a cache and a source of truth. The service still owns what is safe to cache and for how long.
04
The public repo is intentionally local
The companion repository does not deploy AWS resources. It keeps the behavior deterministic so the pattern can be evaluated without account IDs, secrets, or employer implementation details.
What this case does not claim
No employer code, private package names, client service names, account IDs, real secrets, or production endpoint values are included.
No Redis benchmark or load-test result is claimed from the public companion repository.
The companion models the access and provisioning shape, not cache invalidation or freshness scenarios. Those would be a natural next extension.
Toward production
Wire the generated secret to real Secrets Manager rotation instead of a static payload.
Attach the scoped secret-read grant to the deployed Lambda role, not a modeled permission.
Replace the SSM-style exports with real parameter wiring the service stack reads at deploy time.
Add the Redis ping to the deployed health path so a cache outage shows up as a service signal.