Guide · AWS DynamoDB
MCP Server DynamoDB DAX — in-memory caching, item cache vs query cache, consistency
DAX (DynamoDB Accelerator) adds a write-through, in-memory cache in front of DynamoDB — reads that hit the cache return in microseconds instead of single-digit milliseconds — but it is VPC-only and introduces eventual consistency for reads immediately following a write. For MCP servers, three constraints define whether DAX is the right choice: VPC requirement — DAX clusters have no public endpoint, so your MCP server Lambda or ECS task must run in the same VPC; consistency model — DAX serves item-cache reads from its in-memory store with a default TTL of 5 minutes; a write to DynamoDB via the DAX client also updates the item cache (write-through), but a write that bypasses DAX (e.g., from the AWS Console or another service using the raw DynamoDB client) leaves a stale item in the cache until the TTL expires; and no support for strongly consistent reads from the cache — ConsistentRead: true in the DAX client bypasses the cache and reads directly from DynamoDB, negating the caching benefit.
TL;DR
Deploy DAX in the same VPC subnets as your MCP server. Use the DAX client (API-compatible with the DynamoDB Document client) for reads. All writes go through DAX write-through to keep the item cache consistent. Do not use ConsistentRead: true for hot-path reads — it bypasses DAX. Avoid DAX for session data that is updated by multiple services outside your MCP server. Fall back to the raw DynamoDB client for transactional writes (TransactWriteItems) — DAX does not support transactions.
Item cache vs query cache
DAX maintains two separate caches with independent TTL controls:
| Cache type | Default TTL | What it stores | When it's useful for MCP |
|---|---|---|---|
| Item cache | 5 minutes | Individual item results from GetItem and BatchGetItem; keyed by primary key | Session reads: GetItem by session ID for every MCP tool call. High read-to-write ratio for popular sessions. Hot item protection — DAX absorbs read traffic for a session that is being read thousands of times per second. |
| Query cache | 1 minute (default) | Result sets from Query and Scan; keyed by the full query parameters including filter expressions | Status dashboards, analytics queries that run repeatedly with the same parameters. A Query with different parameters (even ExclusiveStartKey for pagination) is treated as a different cache key. Rarely useful for MCP server session management where every query has a unique partition key. |
For MCP session management, the item cache is the valuable one. A hot session (a user running many tool calls in a session) triggers the same GetItem pk=session#abc repeatedly; DAX returns it from memory after the first read.
Setting up the DAX client
The DAX client is API-compatible with the DynamoDB Document client — swap the constructor, keep all command objects the same:
// Without DAX (standard DynamoDB):
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
const ddbClient = new DynamoDBClient({ region: "us-east-1" });
const ddb = DynamoDBDocumentClient.from(ddbClient);
// With DAX (drop-in replacement):
// npm install amazon-dax-client
import AmazonDaxClient from "amazon-dax-client";
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
const daxClient = new AmazonDaxClient({
endpoints: [process.env.DAX_CLUSTER_ENDPOINT!],
// DAX endpoint format: dax://my-cluster.abc123.dax-clusters.us-east-1.amazonaws.com
region: "us-east-1",
requestTimeout: 5000,
// Item cache TTL: 300 seconds (5 minutes) — default
// Query cache TTL: 60 seconds (1 minute) — default
});
const ddb = DynamoDBDocumentClient.from(daxClient as any);
// All GetItem, Query, Put, Update, Delete commands work unchanged
// TransactWriteItems is NOT supported by DAX — use DynamoDB client directly for transactions
DAX clusters are VPC-internal. The DAX endpoint hostname resolves to private IP addresses within the cluster's subnet. Your Lambda or ECS task must be in the same VPC and have a security group rule allowing outbound TCP 8111 (DAX unencrypted) or 9111 (DAX encrypted with TLS).
// CDK: DAX cluster in private subnets
import * as dax from "aws-cdk-lib/aws-dax";
import * as ec2 from "aws-cdk-lib/aws-ec2";
const daxSg = new ec2.SecurityGroup(this, "DaxSg", {
vpc,
description: "DAX cluster — allow access from MCP server only",
});
// MCP server Lambda/ECS must have this security group to reach DAX
daxSg.addIngressRule(
ec2.Peer.securityGroupId(mcpServerSg.securityGroupId),
ec2.Port.tcp(9111), // 9111 for TLS-encrypted DAX
"Allow MCP server to reach DAX"
);
const daxSubnetGroup = new dax.CfnSubnetGroup(this, "DaxSubnetGroup", {
subnetGroupName: "mcp-dax-subnet-group",
subnetIds: vpc.privateSubnets.map((s) => s.subnetId),
});
const daxCluster = new dax.CfnCluster(this, "McpDaxCluster", {
clusterName: "mcp-dax",
nodeType: "dax.r4.large", // smallest production node; t2.small for dev/test
replicationFactor: 2, // 1 primary + 1 replica; use 3 for production HA
iamRoleArn: daxRole.roleArn,
subnetGroupName: daxSubnetGroup.ref,
securityGroupIds: [daxSg.securityGroupId],
sseSpecification: { sseEnabled: true },
clusterEndpointEncryptionType: "TLS",
});
Write-through semantics and cache staleness
DAX uses write-through for PutItem, UpdateItem, and DeleteItem: the write goes to DynamoDB and the item cache is updated (or invalidated) atomically. This keeps the cache consistent for writes coming through the DAX client.
Staleness occurs when writes bypass DAX:
- AWS Console writes — editing a session item in the DynamoDB console goes directly to DynamoDB; the DAX cache still has the old value until its TTL expires (up to 5 minutes)
- AWS CLI or SDK without DAX client — a cleanup script or migration job writing via the raw DynamoDB client bypasses DAX write-through
- Lambda Streams processor — the audit Lambda writing to the session table (see Streams guide) uses the DynamoDB client, not DAX
- TransactWriteItems — DAX does not support DynamoDB transactions; these always go directly to DynamoDB, leaving the item cache stale for affected items
For MCP session state, the safest strategy: route all reads through DAX, route all writes through DAX (for simple writes), and accept that transactional writes will leave the cache stale for up to the item cache TTL. If you need post-transaction reads to be immediately consistent, issue a GetItem with ConsistentRead: true which bypasses DAX and reads fresh from DynamoDB.
When NOT to use DAX for MCP servers
- Write-heavy session patterns — if every MCP tool call writes a new session record, the read-to-write ratio is 1:1 and DAX provides minimal benefit; the item cache is continuously invalidated
- Low request volume — a DAX cluster (minimum 1 node) costs roughly $0.27/hr for
dax.t2.smallor $1.10/hr fordax.r4.large; for low-traffic MCP servers, the DynamoDB read savings won't offset the cluster cost - Strongly consistent reads required — if your MCP server must always see the latest write (e.g., a tool that checks whether another tool call's write completed),
ConsistentRead: truebypasses DAX and the caching benefit is lost - Transactions are the primary write pattern — DAX does not support
TransactWriteItemsorTransactGetItems; a transaction-heavy workload must fall back to the raw DynamoDB client, complicating the client management in the MCP server - Session data updated by multiple services — if your session table is written by the MCP server, a webhook handler, and a background job, all three must route writes through DAX to maintain cache coherency — difficult to enforce across different codebases
Common failure modes
| Symptom | Cause | Fix |
|---|---|---|
| Connection timeout to DAX endpoint from Lambda | Lambda is not in the same VPC as the DAX cluster, or security group does not allow TCP 9111 outbound from Lambda to DAX | Add VPC config to Lambda pointing to the VPC and private subnets containing DAX; add outbound rule on Lambda SG for TCP 9111 to DAX SG |
| Stale session data returned after write from Console or CLI | Write bypassed DAX; item cache still holds old value for up to 5 minutes | For debugging, use ConsistentRead: true to bypass DAX; in production, route all writes through DAX client to maintain write-through coherency |
TransactWriteItems throws UnsupportedOperationException | DAX does not support DynamoDB transactions | Keep a separate raw DynamoDBClient (not DAX) for transactional writes; use DAX only for non-transactional reads and writes |
| DAX cluster is a hot cost center without proportional benefit | Read-to-write ratio is low; the cluster is over-provisioned; or most reads use ConsistentRead: true which bypasses DAX | Check TotalRequestCount vs CacheHits CloudWatch metrics; if hit rate is below 50%, DAX is not cost-effective; return to raw DynamoDB and add application-level caching (Redis/Memcached) for selective session reads |