Guide · AWS MemoryDB · Performance & Cost
MemoryDB Data Tiering for MCP Servers
MemoryDB data tiering on r6gd nodes extends usable cluster capacity by 2–8x by offloading cold data to NVMe SSD while keeping hot data in DRAM. For MCP server probe archives, historical uptime sorted sets, and rate-limit counter histories, data tiering provides operational Redis access to weeks of data at a fraction of DRAM-only cost. This guide covers how the tiering engine works, maxmemory-policy interactions, CloudWatch metrics to watch, the cost model, and the access patterns where tiering hurts rather than helps.
How MemoryDB data tiering works
Data tiering is an internal feature of MemoryDB on r6gd node types. The node has both DRAM and NVMe SSD storage. The tiering engine manages data placement automatically:
- DRAM tier: all recently accessed keys live here. DRAM read latency is ~1µs; write latency matches normal Redis performance.
- SSD tier: keys that have not been accessed for some time are evicted from DRAM to NVMe SSD. SSD read latency is ~40–100µs — 40–100x higher than DRAM, but still orders of magnitude lower than network round trips to an external data store.
- Promotion: when a key in the SSD tier is accessed, it is promoted to DRAM. The promotion is synchronous — the read blocks until the key is in DRAM, which is why SSD tier reads are slower than DRAM reads.
- Write path: all writes land in DRAM first. The tiering engine demotes cold keys from DRAM to SSD asynchronously in the background. Write latency is not affected by the tiering engine.
The tiering decision is LRU-based and transparent to application code. Your Redis client sends the same commands whether a key is in DRAM or SSD — the difference is read latency, not API behavior.
r6gd vs r7g node types
Data tiering is only available on r6gd.* node types. r7g.* nodes are DRAM-only.
| Node type | Architecture | DRAM | NVMe SSD | Tiering | Use case |
|---|---|---|---|---|---|
| db.r7g.large | Graviton3 | 13.1 GB | None | No | Hot data only, lowest latency |
| db.r7g.xlarge | Graviton3 | 26.3 GB | None | No | Medium hot data, lowest latency |
| db.r7g.2xlarge | Graviton3 | 52.6 GB | None | No | Large hot data, lowest latency |
| db.r6gd.large | Graviton2 | 13.1 GB | 118 GB NVMe | Yes | Hot + warm/cold data, cost-optimized |
| db.r6gd.xlarge | Graviton2 | 26.3 GB | 237 GB NVMe | Yes | Large archive, cost-optimized |
| db.r6gd.2xlarge | Graviton2 | 52.6 GB | 474 GB NVMe | Yes | Very large archive, cost-optimized |
| db.r6gd.4xlarge | Graviton2 | 105 GB | 950 GB NVMe | Yes | Enterprise-scale probe archives |
The SSD:DRAM ratio is approximately 9:1 — for every GB of DRAM, there is ~9 GB of SSD. In practice, the effective SSD capacity for customer data is somewhat lower due to internal metadata overhead, but the ratio enables significant cost savings for workloads with a hot/cold access pattern.
Enabling data tiering in CDK
const cluster = new memorydb.CfnCluster(this, 'MemoryDbCluster', {
clusterName: 'mcp-probe-archives',
nodeType: 'db.r6gd.xlarge', // r6gd = data tiering enabled
dataTiering: 'true', // must explicitly enable; 'false' on r7g is default
numShards: 2,
numReplicasPerShard: 1,
// ...other parameters...
});
dataTiering: 'true' is required even on r6gd nodes — MemoryDB will not tier automatically without the explicit flag. If you provision an r6gd node without dataTiering: 'true', only DRAM is used and the SSD capacity is wasted.
maxmemory-policy interaction with data tiering
The maxmemory-policy parameter controls what MemoryDB does when DRAM is full. With data tiering, this interaction has non-obvious consequences.
Policies to avoid with data tiering
- allkeys-lru: evicts the least recently used key from the entire dataset (DRAM + SSD). When DRAM fills up, MemoryDB evicts keys from SSD first (they are, by definition, less recently used). This defeats the purpose of tiering — you lose cold data that you paid SSD to keep, rather than promoting it on demand.
- allkeys-lfu: same problem as allkeys-lru — evicts cold SSD keys before warming them.
- volatile-lru / volatile-lfu with all keys having TTLs: if every key has a TTL, these policies behave like allkeys-lru/lfu and have the same problem.
Recommended policies with data tiering
- noeviction (default): MemoryDB returns OOM errors when DRAM is full rather than evicting. For archives where all data is important, this is correct — the application should handle OOM by retrying or writing to an overflow path. Size your cluster so DRAM is sufficient for your working set.
- volatile-lru with strategic TTL assignment: assign TTLs only to keys you are willing to lose. Keys without TTLs are never evicted; keys with TTLs are evicted LRU-style when DRAM fills. This creates an explicit hot tier (no TTL, DRAM-persistent) and a sacrificeable warm tier (TTL, DRAM when accessed, evicted under memory pressure).
- volatile-ttl: evicts keys with TTLs that expire soonest. Useful when short-lived keys (rate counters, session tokens) coexist with long-lived archive keys — the short-lived keys are evicted first, protecting the archive.
# CDK custom parameter group for data tiering workloads
const paramGroup = new memorydb.CfnParameterGroup(this, 'DataTieringParamGroup', {
family: 'memorydb_redis7',
parameterGroupName: 'mcp-probe-archives-params',
description: 'Tuned for data tiering probe archive workload',
parameters: {
'maxmemory-policy': 'volatile-ttl', // protect archive keys (no TTL) from eviction
'hz': '100', // faster TTL expiry for short-lived probe windows
'lazyfree-lazy-eviction': 'yes', // async eviction to avoid latency spikes
'lazyfree-lazy-expire': 'yes', // async TTL expiry
},
});
MCP server access patterns for data tiering
Data tiering works well when the access pattern is naturally hot/cold over time — recent data is accessed frequently, older data rarely.
Good fit: probe result archives
AliveMCP stores probe results as sorted sets with Unix timestamps as scores:
// Write probe result: ZADD with timestamp score
await redis.zadd(
`{${serverSlug}}:probes:history`,
Date.now() / 1000, // score = Unix timestamp
JSON.stringify({ status: 'up', latencyMs: 42, ts: Date.now() })
);
// Query last 24 hours: ZRANGEBYSCORE (recent = DRAM tier = fast)
const recent = await redis.zrangebyscore(
`{${serverSlug}}:probes:history`,
Date.now() / 1000 - 86400, // 24 hours ago
'+inf'
);
// Query 30-day history: ZRANGEBYSCORE (old data = SSD tier = 40-100µs per key)
const monthly = await redis.zrangebyscore(
`{${serverSlug}}:probes:history`,
Date.now() / 1000 - 2592000, // 30 days ago
'+inf'
);
The last-24-hours query hits DRAM; the 30-day query promotes keys from SSD. For a monitoring dashboard that primarily shows "last hour" and "last day" views, data tiering keeps frequently queried recent data in DRAM while the historical archive stays accessible on SSD.
Poor fit: random access across all keys
If your access pattern is uniformly random across your entire dataset (e.g., random key lookup for any server at any time), the LRU tiering never converges on a stable hot set. Every access has a 50% chance of hitting cold SSD, and the constant promotion/demotion cycle causes CPU overhead and unpredictable latency.
Poor fit: write-heavy sorted set updates to cold ranges
ZADD to a member already in the SSD tier triggers a promotion (SSD read → DRAM write). If your sorted set architecture requires frequent updates to historical members (e.g., incrementing a score for an old entry), each update reads from SSD and then writes back. On a high-throughput MCP server this becomes a significant SSD read amplification bottleneck. Prefer immutable, append-only writes to the archive (new ZADD entries only, no score updates) to avoid this pattern.
Monitoring tier health
MemoryDB emits CloudWatch metrics specific to data tiering. Monitor these to understand whether your cluster is sized correctly:
| CloudWatch metric | What it measures | Alert threshold |
|---|---|---|
| StorageBytesUsed | Total SSD tier usage in bytes | Alert at 85% of SSD capacity |
| DatabaseMemoryUsagePercentage | DRAM usage as % of usable DRAM | Alert at 75% (leaves headroom for promotions) |
| EngineCPUUtilization | Redis engine thread CPU % | Alert at 80% — tiering promotion CPU adds overhead |
| GetTypeCmdsLatency | GET/HGET/ZRANGEBYSCORE p99 latency in µs | Baseline on DRAM ~500µs; spike to 5,000–10,000µs indicates SSD tier thrashing |
| CurrItems | Total key count across DRAM + SSD | Track rate of growth to project when SSD capacity will be exhausted |
// CDK: alert on SSD tier usage approaching capacity
new cloudwatch.Alarm(this, 'MemoryDbSsdCapacityAlarm', {
metric: new cloudwatch.Metric({
namespace: 'AWS/MemoryDB',
metricName: 'StorageBytesUsed',
dimensionsMap: { ClusterName: 'mcp-probe-archives' },
statistic: 'Maximum',
period: cdk.Duration.minutes(15),
}),
// r6gd.xlarge: 237 GB SSD. Alert at 85% = ~202 GB
threshold: 202 * 1024 * 1024 * 1024,
evaluationPeriods: 2,
alarmDescription: 'MemoryDB SSD tier >85% full — scale up or add shards',
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
});
// Alert on elevated GET latency (SSD tier thrashing indicator)
new cloudwatch.Alarm(this, 'MemoryDbGetLatencyAlarm', {
metric: new cloudwatch.Metric({
namespace: 'AWS/MemoryDB',
metricName: 'GetTypeCmdsLatency',
dimensionsMap: { ClusterName: 'mcp-probe-archives' },
statistic: 'p99',
period: cdk.Duration.minutes(5),
}),
threshold: 5000, // 5ms p99 — 10x above healthy DRAM baseline
evaluationPeriods: 3,
alarmDescription: 'MemoryDB GET p99 >5ms — SSD tier thrashing or DRAM pressure',
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
});
Cost model: r6gd vs r7g
Data tiering is not always cheaper. The cost comparison depends on your working set size and total data volume:
| Scenario | r7g configuration | r6gd configuration | Cost comparison |
|---|---|---|---|
| 50 GB working set, 50 GB total | 2x r7g.2xlarge (52.6 GB DRAM) | 2x r6gd.large (13 GB DRAM + 118 GB SSD) | r7g ~20% cheaper — all data fits in DRAM, no tiering benefit |
| 20 GB working set, 200 GB total | 4x r7g.2xlarge (210 GB DRAM) | 2x r6gd.xlarge (26 GB DRAM + 237 GB SSD) | r6gd ~60% cheaper — hot set fits DRAM, cold data in SSD |
| 5 GB working set, 500 GB total | 10x r7g.2xlarge (526 GB DRAM) | 3x r6gd.2xlarge (158 GB DRAM + 1.4 TB SSD) | r6gd ~75% cheaper — extreme ratio of cold to hot |
The break-even point is roughly when total data is 3–4x larger than your working set. Below that ratio, DRAM-only (r7g) is cheaper per GB of usable storage and delivers lower, more predictable latency.
Sizing for the working set
The DRAM tier must be large enough to hold your working set — the keys accessed within a typical request burst window. For AliveMCP's probe archive pattern:
- Working set = all probes from the last 4 hours (dashboard default view)
- Archive = all probes from the last 30 days (SLA report range)
- Working set : archive ratio ≈ 1:180 — a strong case for data tiering
Size DRAM to hold the working set with 25% headroom. Size SSD to hold the full archive plus the same headroom. If both fit in DRAM without the SSD multiple, use r7g for better latency characteristics.
How AliveMCP monitors data-tiering MemoryDB clusters
When your MCP server uses a data-tiering MemoryDB cluster for probe archives or session history, AliveMCP monitors the tiering health as part of the infrastructure stack:
- SSD saturation alert: AliveMCP watches
StorageBytesUsedand alerts at 85% of the r6gd node's NVMe capacity. SSD saturation causes severe performance degradation — MemoryDB starts evicting from SSD (if maxmemory-policy allows) or returning OOM errors. Early warning is critical. - DRAM pressure correlation: when
DatabaseMemoryUsagePercentageexceeds 75%, the tiering engine becomes hyperactive — constantly promoting and demoting keys as it struggles to maintain a working DRAM set. AliveMCP correlates DRAM pressure events with probe latency spikes to distinguish "MCP server is slow" from "MemoryDB is under memory pressure." - Latency baseline drift: AliveMCP establishes a 7-day rolling latency baseline for your MemoryDB cluster. A sustained increase in
GetTypeCmdsLatencyp99 above 2x the baseline triggers a warning — this pattern often precedes SSD tier saturation by several days and provides actionable lead time to resize the cluster.
Data-tiering monitoring is available on the Team tier alongside the standard MCP endpoint health probes.