What it actually is
MemoryDB is a Valkey and Redis OSS compatible in-memory database where the memory is not the source of truth. The source of truth is a distributed Multi-AZ transactional log. Every write is committed to that log, across Availability Zones, before the client gets its acknowledgement. Nodes are in-memory views of the log. Lose a node, a shard primary, or an entire AZ, and a replacement rebuilds its memory from the log instead of from a stale replica.
That one design decision is the whole product. Open source Valkey and Redis replicate asynchronously, so a primary can acknowledge a write and then die before any replica sees it. ElastiCache inherits that behavior. MemoryDB moves durability out of the node and into the log, which is why AWS can call it a primary database and not a cache.
The cost of that promise is write latency. A write is a log commit across AZs, not a memory append, so expect single-digit milliseconds on writes where a plain cache gives you microseconds. Reads are served from memory and stay fast.
The model
A MemoryDB cluster is always cluster mode: one or more shards, each with one primary and up to five replicas (the AWS FAQ gives the five-replica ceiling, and the service quotas page lists six nodes per shard). Keys are hashed into slots across shards, so your client must be cluster aware.
The primitives to know are these. The cluster owns the shards. A subnet group tells MemoryDB which subnets, and therefore which AZs, nodes may live in; with Multi-AZ you need subnets in at least two. A parameter group carries engine settings. Access control is an ACL, a named set of users, each with an access string such as on ~* &* +@all. And TLS is not optional in practice: the docs state that clusters using ACLs require --tls-enabled, and clusters without TLS must use the open-access ACL.
The consistency contract matters because it differs per node role. The primary serves consistent reads. Replicas are eventually consistent, and their lag shows up in CloudWatch as ReplicationLag. Only data persisted to the log is visible, which is the property that makes a failover lose nothing that was acknowledged. AWS states unplanned failover typically completes in under 20 seconds and planned failover in under 200 milliseconds.
What AWS operates: the log, node replacement, patching, snapshots, failover. What you operate: capacity planning (the dataset must fit in memory unless you use data tiering), the key design that avoids hot shards, client retry behavior, and your ACLs.
Two features worth knowing exist. Multi-Region (engine 7.3, December 2024) gives active-active replication to up to four additional Regions, asynchronous and typically within one second, with conflicts resolved using CRDTs. And vector search stores vectors as JSON or hash and is included at no extra charge, per the FAQ.
When to use it, when not to
One honest note before the table. The MemoryDB FAQ I read while researching this article says AWS recommends ElastiCache for new workloads, while noting that ElastiCache now supports durability too. Read the current FAQ before you commit, because positioning between these two services has been moving.
Question | MemoryDB | ElastiCache | DynamoDB |
|---|---|---|---|
Is memory the primary store? | Yes, backed by Multi-AZ log | No, usually a cache in front of a database | No, SSD-backed table |
Acknowledged write survives failover? | Yes | Not guaranteed on the classic path | Yes |
Read latency | Microseconds | Microseconds | Single-digit ms (DAX for microseconds) |
Write latency | Single-digit ms | Sub-ms to low ms | Single-digit ms |
Data model | Valkey/Redis structures, vectors, JSON | Same | Items, GSIs |
Dataset must fit in RAM? | Yes (or data tiering on r6gd) | Yes | No |
Pay for | Nodes plus data written | Nodes | Requests and storage |
Use MemoryDB when Redis data structures are your actual data model (leaderboards, sessions you cannot afford to lose, rate-limit state with legal consequences, a feature store), you need microsecond reads, and you refuse to run a second database just to rebuild the first after a failover. Do not use it when the dataset is large and mostly cold (DynamoDB is cheaper per GB), when you only want to absorb read load from another database (ElastiCache is the simpler fit), or when your writes are so hot they approach the replication throughput ceiling covered below.
What it costs
Four dimensions, and one of them surprises people.
Node hours are the bulk of the bill. The pricing page gives these examples: db.r6g.xlarge at $0.432 per hour, db.r7g.xlarge at $0.4319 per hour (US East), and db.r6gd.4xlarge at $2.586 per hour against $1.724 for db.r6g.4xlarge. Reserved nodes exist for one and three years with No, Partial or All Upfront payment. The pricing page did not show full rate tables when I fetched it, so check your Region's numbers there before budgeting.
Data written is $0.20 per GB, and reads are not charged. This is the surprising dimension. A cache is priced on nodes alone. A MemoryDB cluster that rewrites a large hash 50 times a second is writing real gigabytes into the log all month. The pricing page shows a zero line for the first 10 TB per month in its worked examples but is inconsistent about the tiers above that, so treat the write line as something you estimate from your own write volume instead of assuming it is free.
Snapshot storage is $0.021 per GB-month, with storage free up to your cluster size and free entirely at one-day retention.
Data transfer: inbound and same-Region transfer to other AWS services is free; cross-Region transfer under Multi-Region is charged (the pricing example uses $0.02 per GB outbound in US East).
Free tier: the pricing page states no MemoryDB-specific free trial. Following along costs money. A one-shard, one-replica cluster of db.r7g.xlarge is two nodes at about $0.43 each, roughly $0.87 per hour. The drill below takes under an hour including provisioning, so budget about one dollar, plus pennies of data written. Billing starts when the cluster reaches available.
The limits that bite
The service quotas page lists: 300 nodes per Region, 90 nodes per cluster per instance type, 6 nodes per shard, 150 parameter groups and 150 subnet groups per Region, 20 subnets per subnet group. The FAQ describes clusters up to 500 nodes and about 100 TB, which reflects what is reachable after limit increases, so treat the defaults above as your starting point.
Memory is the limit that causes real incidents. MemoryDB reserves part of node memory for non-data use (the data tiering docs say 19 percent of DRAM per node for tiered nodes), and DatabaseMemoryUsagePercentage is the metric to alarm on. A durable database that cannot fit your write is worse than a cache that evicts, because evicting is not an option you want to enable on your primary store.
Write throughput is the second. The metrics documentation notes higher write latency can appear beyond 100 MBps due to write throughput throttling, and ReplicationDelayedWriteCommands counts writes delayed by synchronous replication. MaxReplicationThroughput shows your peak. If one shard takes a hot key's writes, that shard hits the ceiling first, so shard by key design.
Data tiering is the third. Only the r6gd family offers it, it moves values (not keys) to local NVMe SSD under an LRU policy, adds roughly 450 microseconds on SSD reads for 500-byte strings, and the docs say it suits workloads that regularly touch up to 20 percent of the dataset. Keys always stay in memory, so many tiny values and huge keys defeat it.
Engine versions: you can upgrade but never downgrade. The engine versions page lists MemoryDB 7.3 (Multi-Region), Valkey 7.2.6, 7.1 and 7.0 (enhanced), and Redis OSS 6.2. When you create a cluster you specify major.minor, and a full patch version such as 6.2.6 is rejected. That page did not mention Valkey 8.x, so confirm current versions in the console. Node families available: r7g, r6g, r6gd and t4g; enhanced I/O multiplexing applies to 2xlarge and up.
Build it: a cluster, a write loop, and a failover that must not lose an acknowledged write
The deliverable: provision a one-shard, one-replica Valkey cluster with TLS and an ACL, run a writer that records every key it got an acknowledgement for, force a failover mid-stream, and then verify every acknowledged key is present. That is the MemoryDB contract turned into a test.
A note on provenance: the code follows the current AWS CLI and API documentation, but I did not execute it against an account for this article. Run it in a sandbox account first.
Prerequisites: an AWS account, Python 3.10+, pip install boto3 redis, a default VPC or one with subnets in two AZs, and an EC2 instance (or Cloud9, or CloudShell in the same VPC) from which to run the test, because MemoryDB endpoints are VPC-only.
IAM: the managed policy AmazonMemoryDBFullAccess covers the exercise and lets MemoryDB create its service-linked role. In production you would scope to memorydb:* actions on specific ARNs. The final snapshot on delete additionally needs memorydb:CreateSnapshot, otherwise the delete fails with an access denied error.
Step 1, the infrastructure:
import boto3, secrets, string, time
REGION = "us-east-1"
m = boto3.client("memorydb", region_name=REGION)
ec2 = boto3.client("ec2", region_name=REGION)
# Two subnets in two AZs from the default VPC
subnets = ec2.describe_subnets(Filters=[{"Name": "default-for-az", "Values": ["true"]}])["Subnets"]
subnet_ids = [s["SubnetId"] for s in subnets][:2]
vpc_id = subnets[0]["VpcId"]
# Security group: allow 6379 from itself (run the test from an instance in this SG)
sg = ec2.create_security_group(GroupName="memorydb-drill", Description="memorydb drill", VpcId=vpc_id)["GroupId"]
ec2.authorize_security_group_ingress(GroupId=sg, IpPermissions=[{
"IpProtocol": "tcp", "FromPort": 6379, "ToPort": 6379,
"UserIdGroupPairs": [{"GroupId": sg}]}])
m.create_subnet_group(SubnetGroupName="drill-sg", Description="drill", SubnetIds=subnet_ids)
# 16-128 chars, and none of , " / @
alphabet = string.ascii_letters + string.digits
password = "".join(secrets.choice(alphabet) for _ in range(32))
m.create_user(
UserName="drill-user",
AccessString="on ~* &* +@all",
AuthenticationMode={"Type": "password", "Passwords": [password]},
)
m.create_acl(ACLName="drill-acl", UserNames=["drill-user"])
Keep password in a variable or in Secrets Manager, never in source control. Launch your EC2 test instance with the memorydb-drill security group attached.
Step 2, the cluster. The CLI example in the getting-started guide uses create-cluster with --cluster-name, --node-type, --acl-name, --engine valkey and --subnet-group; the boto3 call carries the same fields plus shard and replica counts:
m.create_cluster(
ClusterName="drill",
NodeType="db.r7g.xlarge",
Engine="valkey",
EngineVersion="7.2",
ACLName="drill-acl",
SubnetGroupName="drill-sg",
SecurityGroupIds=[sg],
NumShards=1,
NumReplicasPerShard=1,
TLSEnabled=True,
ParameterGroupName="default.memorydb-valkey7",
)
while True:
c = m.describe_clusters(ClusterName="drill")["Clusters"][0]
print(c["Status"])
if c["Status"] == "available":
break
time.sleep(30)
endpoint = c["ClusterEndpoint"]["Address"]
shard_name = c["Shards"][0]["Name"]
print(endpoint, shard_name)
Provisioning takes several minutes. Wait for available before connecting.
Step 3, the writer and the drill. Run this on the EC2 instance. It increments a counter, writes each value under its own key, records the key only after the server acknowledged it, and does that in a background thread while the main thread triggers the failover:
import threading, time, boto3, redis
from redis.cluster import RedisCluster
ENDPOINT = "<cluster endpoint from step 2>"
PASSWORD = "<the password from step 1>"
r = RedisCluster(host=ENDPOINT, port=6379, ssl=True,
username="drill-user", password=PASSWORD,
socket_timeout=2, socket_connect_timeout=2)
acked, errors = [], []
stop = threading.Event()
def writer():
i = 0
while not stop.is_set():
key = f"drill:{i}"
try:
r.set(key, i)
acked.append(key) # only after an acknowledged SET
except Exception as e:
errors.append((time.time(), repr(e)))
time.sleep(0.05)
i += 1
t = threading.Thread(target=writer); t.start()
time.sleep(10) # steady state
m = boto3.client("memorydb", region_name="us-east-1")
t0 = time.time()
m.failover_shard(ClusterName="drill", ShardName="0001") # use the shard name from step 2
print("failover requested")
time.sleep(60)
stop.set(); t.join()
missing = [k for k in acked if r.get(k) is None]
print(f"acked={len(acked)} errors={len(errors)} missing={len(missing)}")
if errors:
print("first error after", round(errors[0][0] - t0, 1), "s;",
"last error after", round(errors[-1][0] - t0, 1), "s")
Use the shard name your describe_clusters call printed in step 2; the 0001 above is the usual default for a single-shard cluster, but read it from the response.
Verification: the pass condition is missing=0. You will probably see a burst of errors, connection resets or timeouts, for a short window around the failover. The AWS figure for planned failover is typically under 200 ms, so the window should be brief, but your client determines how long the application feels it: redis-py cluster mode has to refresh its slot map and reconnect. The errors are the cost of the failover; missing keys would be a violation of the contract. Every key in acked must exist, because the log committed it before the SET returned.
The interesting experiment is the unplanned one. Reboot the primary node from the console while the writer runs and compare the error window, which AWS says is typically under 20 seconds. Then count missing again. The answer should still be zero, and that is the thing you cannot get from a classic Redis replication setup.
When it breaks
ClusterQuotaForCustomerExceeded or node quota errors: you hit the 300 nodes per Region or 90 per instance type default; request an increase in Service Quotas.
Cannot connect, hangs on TLS handshake: you are outside the VPC, the security group does not allow 6379 from your client, or you connected without TLS to a TLS cluster. Use ssl=True and test from an instance inside the security group.
WRONGPASS or NOAUTH: the user is off (an access string without on leaves the user disabled), the password has a forbidden character (,, ", /, @) or is shorter than 16 characters, or the user is not in the ACL attached to the cluster.
MOVED errors or "cluster mode" failures from a client: you used a non-cluster client. MemoryDB is always cluster mode; use RedisCluster or your language's cluster client.
OOM command not allowed: memory is full. Alarm on DatabaseMemoryUsagePercentage long before this. Fix by adding shards, bigger nodes, or data tiering if your access pattern fits.
Unexpected write slowdowns: look at ReplicationDelayedWriteCommands and MaxReplicationThroughput. A shard near 100 MBps is being throttled by synchronous replication; spread writes over more shards.
Delete fails with access denied: a final snapshot requires memorydb:CreateSnapshot.
Cleanup
Order matters because the cluster depends on the ACL and subnet group:
m.delete_cluster(ClusterName="drill") # optionally FinalSnapshotName=...
while True:
try:
m.describe_clusters(ClusterName="drill")
time.sleep(30)
except m.exceptions.ClusterNotFoundFault:
break
m.delete_acl(ACLName="drill-acl")
m.delete_user(UserName="drill-user")
m.delete_subnet_group(SubnetGroupName="drill-sg")
boto3.client("ec2", region_name="us-east-1").delete_security_group(GroupId=sg)
Terminate the EC2 test instance. Manual snapshots persist and bill at $0.021 per GB-month, automatic ones are removed with the cluster, so delete any final snapshot you took once you have no use for it. Confirm in the MemoryDB console that no clusters remain and check Cost Explorer the next day; the bill for this should be a dollar or so.

