JusDB
Free DB Audit
NoSQL Databases

Valkey vs Memcached in 2026: Threads, Memory Overhead, and When Each Fits

Compare Valkey 9.1 and Memcached 1.6.45 on threading, memory per key, eviction, persistence, replication, and ElastiCache support, then benchmark both with memtier_benchmark.

JusDB Team
Published October 27, 2026
Updated November 10, 2026
20 min read

The usual answer to "Valkey or Memcached?" is an old one: Memcached is multi-threaded, Redis is single-threaded, so pick Memcached for throughput. It is out of date. Since Valkey 8.0, I/O threads read, parse, and write for the main thread, and Valkey 8.0, 8.1, and 9.1 each cut the memory a small key costs. Memcached kept moving too: version 1.6.45 (9 July 2026) ships flash-backed extstore, a restartable cache, and an optional built-in proxy. The old failures still happen. A Valkey node given a maxmemory limit but no eviction policy fails writes with OOM command not allowed when used memory > 'maxmemory', because the default policy is noeviction. A Memcached tier loses a node and every key on it, because Memcached servers never replicate. This guide compares the two on threads, memory per key, eviction, persistence, scaling, and managed support, and shows how to benchmark both.

TL;DR
  • Memcached 1.6.45 runs 4 worker threads by default, and each one parses, executes, and replies. Valkey 9.1 executes every command on one main thread and can hand socket reads, parsing, and writes to I/O threads (io-threads, off by default).
  • Pick Memcached for a plain get/set object cache where losing a node only costs hit rate. Pick Valkey when you need data types, per-field TTLs, replication with failover, or a cache that survives a restart.
  • Valkey's published memory savings are its own figures: about 24 bytes per key in cluster mode in 8.0, about 20 bytes per key in 8.1, and 17 to 44% less string overhead in 9.1. Measure your own keys before you resize a fleet.
  • Configure Valkey as a cache explicitly: set maxmemory, an allkeys-* eviction policy, and save "".
  • Benchmark both with memtier_benchmark, which speaks the Redis protocol and the Memcached text and binary protocols, with the same value sizes, key distribution, and client count.

Scope and review date: November 2026. This post covers self-managed Valkey 9.1 (9.1.3, released 26 October 2026) and Memcached 1.6.45, with notes on Valkey 8.0 and 8.1 and on Amazon ElastiCache. Defaults and option names were checked on 10 November 2026 against valkey.io, the Valkey 9.1.3 valkey.conf, docs.memcached.org, and the Memcached 1.6.45 source. The commands were not run in a lab, so try them on a test host first.


What is the difference between Valkey and Memcached in 2026?

Both keep data in RAM. The difference is scope. Memcached is a key-value cache with one value type: an opaque blob up to 1 MB by default. It has no replication and no snapshots, and the servers in a pool do not talk to each other. Valkey is a data structure server with hashes, lists, sets, sorted sets, streams, and more. It also has replication, automatic failover through Sentinel or Valkey Cluster, and RDB and AOF persistence.

For Valkey versus Redis, see the Redis vs Valkey comparison. Our 2022 ElastiCache for Redis vs Memcached guide covers the pre-Valkey AWS view; this post is the Valkey-era answer.

Valkey 9.1 and Memcached 1.6.45 side by side

Switch engines to compare the same nine properties

Valkey 9.1

PropertyValkey 9.1
Latest release (November 2026)9.1.3, 26 October 2026
ThreadsOne main thread runs commands; optional I/O threads
Data modelStrings, hashes, lists, sets, sorted sets, streams, and more
Size limitsKeys and string values up to 512 MB
Default when memory is fullnoeviction: writes fail
PersistenceRDB snapshots and AOF
Replication and failoverBuilt in, with Sentinel or Valkey Cluster
Scale-outValkey Cluster: 16,384 hash slots
LicenceBSD 3-Clause

Memcached 1.6.45

PropertyMemcached 1.6.45
Latest release (November 2026)1.6.45, 9 July 2026
Threads4 worker threads by default; each runs whole requests
Data modelOpaque values with flags, TTL, and CAS
Size limitsKeys up to 250 bytes; items up to 1 MB by default (-I)
Default when memory is fullEvicts from a segmented LRU
PersistenceNone; warm restart from a RAM-disk file after a clean stop
Replication and failoverNone in the server
Scale-outClient-side hashing, or the optional built-in proxy
LicenceBSD 3-Clause

The real split: Memcached removes features to stay simple, and Valkey adds them. Choose on whether you need replication, data types, or a restart that keeps the data.

How do Memcached and Valkey use CPU threads?

Memcached is multi-threaded in the full sense. The Memcached performance guide says one thread accepts new connections, and each worker thread "runs its own event loop and handles its own clients". The workers share the hash table and slab memory through locks. The default is 4 workers (-t). Background threads also run the LRU maintainer, the LRU crawler, and slab rebalancing.

Valkey still executes commands on one thread. Valkey 8.0 rebuilt I/O threading: the main thread hands jobs to I/O threads, which read and parse requests, write replies, and can run epoll_wait for it. The Valkey 8.0 I/O threading post says command execution stays "single-threaded". The io-threads setting counts the main thread, so io-threads 1 means no extra threads, and that is the default.

One request through each engine

Memcached workers run the whole request; Valkey splits I/O from execution

Request path in Memcached and Valkey Top row, Memcached: a client connection is handed to one of four worker threads, which reads, parses, and executes the command against the shared hash table and slab memory, then writes the reply. Bottom row, Valkey: an I/O thread reads and parses the request, the single main thread executes it, and an I/O thread writes the reply. Memcached 1.6.45 Client get / set Worker thread parse, run, reply Hash + slabs shared, locked 1 of 4 Valkey 9.1 with io-threads Client GET / SET I/O thread read and parse Main thread runs every command reply handed back to an I/O thread to write

Where the ceiling is: in Memcached, more workers add execution capacity. In Valkey, I/O threads free the main thread, but one instance never executes two commands at once.

Enable Valkey's I/O threads in valkey.conf, or at runtime since Valkey 9.0 (PR #2033 in the 9.0.0-rc1 release notes):

bash
# valkey.conf guidance: 4 cores, try 2 or 3; 8 cores, try 6
valkey-cli CONFIG SET io-threads 6
valkey-cli INFO server | grep io_threads_active
valkey-cli INFO cpu | grep used_active_time

Interpretation: io_threads_active:1 means the I/O threads are in use. The 9.1 used_active_time_* fields count time spent on real work per thread. If the main thread is busy and the I/O threads are not, more threads will not help: shard across more primaries.

bash
# Memcached: 8 worker threads, 8 GB of item memory, 4,096 connections
memcached -t 8 -m 8192 -c 4096 -u memcache
printf 'stats settings\r\nquit\r\n' | nc cache-01 11211 \
  | grep -E 'num_threads|maxbytes|item_size_max'

Interpretation: num_threads is the worker count you set. A thread count above the core count rarely helps. Memcached's hardware guide calls it "typically light on CPU usage" and says many cores matter only for heavy workloads.

Valkey's throughput figures are vendor numbers, and neither compares with Memcached. The 8.0 post reports 360K rising to 1.19M SET requests per second over Valkey 7.2 (8 I/O threads, 512-byte values, c7g.16xlarge). The Valkey 9.1 release post reports 2.1M requests per second with 9 I/O threads and a pipeline depth of 10.

How much memory does each key cost in Memcached and Valkey?

For small values, the bookkeeping around each key can be larger than the value.

Memcached. Each item carries a fixed header. By our reading of the _stritem struct in memcached.h (1.6.45), that header is 48 bytes on a 64-bit build, plus 8 bytes for the CAS value unless you start with -C. Then come the key, the value, and a short suffix. The item goes into the smallest slab chunk that fits it. Slab classes grow by a factor of 1.25 by default (-f), so part of each chunk is unused. The hash table and connection buffers sit outside the -m limit.

Valkey. Valkey 8.0, 8.1, and 9.1 each cut per-key overhead. These are the project's own figures:

  • 8.0: one dictionary per hash slot removed 16 bytes per key in cluster mode, and embedding the key removed 8 more. The Valkey 8 memory post measured a 20.63% drop on 16-byte values.
  • 8.1: a new hash table saves "roughly 20 bytes per key-value pair", about 30 bytes with a TTL (new hash table post).
  • 9.1: the embedded string object lost an 8-byte pointer, and the embedding limit rose from 64 to 128 bytes for key, expiry, and value together. The 9.1 memory post reports 17 to 44% less string overhead, about 26% on average, comparing 9.0 with 9.1 on 5 million items.

Those percentages are of overhead, not of total memory. AWS describes the 9.1 change as up to 20% less memory for strings under 128 bytes. Measure both engines with the same data instead:

bash
# Valkey: allocator total and key count
valkey-cli INFO memory | grep -E '^(used_memory|used_memory_dataset|mem_fragmentation_ratio):'
valkey-cli DBSIZE
valkey-cli MEMORY USAGE memtier-4242
valkey-cli OBJECT ENCODING memtier-4242

Interpretation: used_memory divided by DBSIZE gives bytes per key, overhead included. MEMORY USAGE shows one key, and OBJECT ENCODING returns embstr when the value is embedded. MEMORY STATS also reports keys.bytes-per-key directly.

bash
# Memcached: memory in slab pages, item count, and per-class chunk use
printf 'stats\r\nquit\r\n' | nc cache-01 11211 | grep -E 'STAT (curr_items|bytes) '
printf 'stats slabs\r\nquit\r\n' | nc cache-01 11211 \
  | grep -E 'chunk_size|used_chunks|total_malloced'

Interpretation: total_malloced divided by curr_items gives bytes per item in slab memory. If most items sit in one class whose chunk_size is well above their real size, a smaller -f can help.

Disabling CAS saves 8 bytes per item

Starting Memcached with -C drops the CAS field from every item. Do it only if no client uses gets and cas, or the meta protocol's compare-and-swap, because those calls stop working.

When do Valkey's data types matter for a cache?

A Memcached value is a blob. To change one field of a cached user profile, the application reads the blob, decodes it, changes the field, and writes the whole blob back, often with gets and cas to catch races. Valkey can change the field on the server.

bash
# Valkey 9.0+: hash with a per-field TTL
valkey-cli HSET user:42 name "Asha" plan "pro" session_token "t-91c2"
valkey-cli HEXPIRE user:42 900 FIELDS 1 session_token
valkey-cli HTTL user:42 FIELDS 1 session_token

Interpretation: only session_token expires after 900 seconds, and HTTL returns the seconds left. Hash field expiration arrived in Valkey 9.0. In Memcached, a separate TTL needs a separate key.

Counters, leaderboards in sorted sets, queues in lists or streams, and SET key value NX EX 30 locks also run on the server. Memcached has add, incr, and decr, and its meta protocol (mg, ms, md, ma) adds stale-while-revalidate flags and fewer round trips. The meta protocol page also states that the binary protocol is deprecated.

If every cache access is "get this blob, or build it and set it", Valkey's data types add nothing, and Memcached's simpler model costs less to run.

How do eviction and expiry differ?

Memcached is a cache by default. When -m is full it evicts, unless you start it with -M, which makes it return errors instead. Since 1.5.0 it has used a segmented LRU with HOT, WARM, and COLD queues per slab class. A background maintainer thread moves items between queues, and an LRU crawler reclaims expired items. The modern LRU write-up explains the design. Slab rebalancing (slab_reassign and slab_automove) is on by default in 1.6.45, so memory can move between size classes as the item mix changes.

Valkey is not a cache by default. On 64-bit builds maxmemory is 0, meaning no limit, and maxmemory-policy is noeviction. When memory runs out, writes fail with an OOM error. Valkey's LRU and LFU are approximate: it samples maxmemory-samples keys (default 5) and evicts the best candidate, as the key eviction page describes.

ini
# valkey.conf for a pure cache
maxmemory 12gb
maxmemory-policy allkeys-lru   # or allkeys-lfu for a stable hot set
save ""                       # no RDB snapshots
appendonly no

Interpretation: leave headroom below the host's RAM for fragmentation, client buffers, and replication buffers. Use the volatile-* policies only when every evictable key has a TTL; otherwise they behave like noeviction once the keys with a TTL are gone. Lazy freeing of evicted keys has been on by default since Valkey 8.0.

bash
valkey-cli INFO stats | grep -E '^(evicted_keys|expired_keys|keyspace_hits|keyspace_misses):'
printf 'stats\r\nquit\r\n' | nc cache-01 11211 | grep -E 'STAT (evictions|get_hits|get_misses) '

Interpretation: a rising eviction count together with a falling hit ratio means the cache is too small for the working set, in either engine.

Can Memcached persist data or survive a restart?

Partly. The Memcached FAQ calls it "an ephemeral data store", but two features change what that means in practice.

  • Warm restart (1.5.18 and later). Start with -e /tmpfs_mount/memory_file and Memcached keeps item memory in that file. Stop it with SIGUSR1, and the next start reloads the cache. The restart documentation requires a RAM disk or a persistent-memory DAX mount and says the feature "does not make memcached crash safe".
  • Extstore (1.6.0 and later). -o ext_path=/mnt/nvme/extstore:500G moves values to flash while keys and metadata stay in RAM. Per the extstore documentation, items smaller than ext_item_size (512 bytes by default) are not flushed, rotational disks are not supported, and "a restart of memcached effectively empties flash". Extstore adds capacity, not durability.

Valkey has RDB snapshots and an append-only file. A cache usually turns them off, as above, unless a cold restart would overload the database behind it; a node restarted from an RDB file comes back warm.

bash
# Memcached warm restart: start, then stop cleanly
memcached -m 8192 -e /dev/shm/memcached.data -u memcache
kill -USR1 "$(pidof memcached)"   # writes /dev/shm/memcached.data.meta, exits

Interpretation: the next start with the same -e path and compatible settings reuses the items. A crash, a reboot of a tmpfs host, or a changed setting starts the cache empty.

How do you scale out and survive a node failure?

Memcached servers do not know about each other. The client hashes each key to one server, usually with consistent hashing so that adding a server moves only part of the keys. When a server dies, its keys are gone, and requests for them fail or miss until the client routes them elsewhere. The FAQ answers the replication question with "It doesn't". Replication halves the usable cache.

Since 1.6.23 Memcached includes an optional built-in proxy, compiled with ./configure --enable-proxy and configured in Lua. It moves hashing out of the clients and adds route handlers such as route_failover, which retries another pool, and route_allsync, which sends a request to several pools in parallel. It is a routing layer: the servers behind it still do not replicate to each other.

Valkey replicates. A primary streams writes to replicas asynchronously. Sentinel, or the cluster itself, promotes a replica when the primary fails. Valkey Cluster splits the keyspace into 16,384 hash slots across primaries to scale writes and memory. Valkey Cluster vs Sentinel covers that choice.

bash
# Valkey: confirm each primary has a connected replica
valkey-cli -h cache-01 INFO replication | grep -E '^(role|connected_slaves|master_repl_offset):'
valkey-cli -c -h cache-01 CLUSTER SHARDS

Interpretation: connected_slaves:0 on a primary means a node failure loses that shard's keys, like a Memcached node. Replicas double the memory you pay for, so decide whether warm failover is worth it.

Replication does not make a cache a database

Valkey replication is asynchronous, so a failover can lose the last writes. That is fine for cached data that can be rebuilt. It is not fine for data that exists nowhere else.

Which is easier to run on Amazon ElastiCache?

ElastiCache runs Valkey, Memcached, and Redis OSS, both serverless and as node-based clusters. As of November 2026 the managed versions differ a lot from upstream:

  • Memcached: the newest version in the ElastiCache engine versions list is 1.6.22, against 1.6.45 upstream. The 1.6.6 entry says ElastiCache does not support extstore. In AWS's engine comparison table, node-based Memcached has no replication, no automatic failover, and no backup; backup is for serverless caches only.
  • Valkey: ElastiCache offers Valkey up to 9.1, with replication, automatic failover, backups, and online resharding in cluster mode.
  • Serverless: both engines run in cluster mode behind a proxy layer, require TLS clients, and replicate data across Availability Zones. With Memcached, a multiget of 3 items consumes 3 ECPUs.

Note the "Multi-threaded" row in the AWS table: it says Yes for Memcached and No for Valkey. That matches command execution. The same page lists async I/O threading among the ElastiCache Valkey 8.0 features.

How do you benchmark Valkey and Memcached fairly?

Use one load generator for both, so that client overhead is the same. memtier_benchmark speaks the Redis protocol (redis, resp2, resp3) and the Memcached text and binary protocols (memcache_text, memcache_binary). Keep value size, key range, key distribution, set:get ratio, client count, and pipeline depth identical, and run the generator on a separate host.

The same memtier_benchmark run against both engines

Illustrative output, trimmed to fit: the shape of memtier's ALL STATS table, not a measured result

bench@loadgen-01:~
$ memtier_benchmark -s cache-01 -p 11211 -P memcache_text \ -t 4 -c 50 -d 512 --ratio=1:10 --key-pattern=G:G \ --key-maximum=2000000 --test-time=300 --hide-histogramALL STATSType Ops/sec Hits/sec Misses/sec Avg. Latency p99 LatencySets 18200.41 --- --- 0.98100 2.07900Gets 182004.12 171083.87 10920.25 0.97500 2.04700Totals 200204.53 171083.87 10920.25 0.97550 2.05500 Same run against Valkey: change the port and the protocol$ memtier_benchmark -s cache-02 -p 6379 -P resp2 ...

Read Misses/sec first: a miss on a get returns no value, so a run with more misses moves fewer bytes and looks faster. Preload the keys before you compare numbers.

Choosing between Valkey and Memcached, then benchmarking

Seven steps from requirements to a measured decision

  1. Write down what the cache must do

    Data types, per-field TTLs, surviving a node loss. Any "yes" points to Valkey before speed matters.

  2. Capture real key and value sizes

    Per-key overhead depends on size, so sample production.

    valkey-cli --memkeys printf 'stats slabs\r\nquit\r\n' | nc cache-01 11211
  3. Configure both as caches

    Same memory limit and core count. Valkey needs an eviction policy and no snapshots.

    memcached -m 8192 -t 8 -u memcache valkey-server --maxmemory 8gb \ --maxmemory-policy allkeys-lru \ --save "" --io-threads 6
  4. Preload the same key range

    Write every key once so that gets hit. Repeat with -p 6379 -P resp2 for Valkey.

    memtier_benchmark -s cache-01 -p 11211 \ -P memcache_text --ratio=1:0 -d 512 \ --key-pattern=P:P --key-maximum=2000000 \ -n allkeys
  5. Run the mixed test

    Same threads, clients, ratio, and duration. Run each test three times and keep the median.

    memtier_benchmark -s cache-01 -p 11211 \ -P memcache_text -t 4 -c 50 -d 512 \ --ratio=1:10 --key-pattern=G:G \ --test-time=300 --hide-histogram
  6. Compare memory per key

    Valkey: used_memory divided by DBSIZE. Memcached: total_malloced divided by curr_items.

  7. Repeat with your production client

    Measure p99 from the application side as well.

Main risk: a benchmark that saturates the load generator, not the server. Watch CPU on the memtier host, and add threads there before you trust a server ceiling.

One trap is specific to this comparison: one Valkey process executes commands on one core. To compare a 16-core Memcached host fairly, also test several Valkey primaries on that host with memtier's --cluster-mode.

Which should you choose?

If your cache needsChooseWhy
Plain get/set of serialized objects, rebuilt on a missMemcachedMulti-threaded execution, fewer settings, nothing to persist
A large working set on NVMe instead of RAMMemcached with extstoreValues on flash, keys in RAM (self-managed only on AWS)
Hashes, counters, sorted sets, queues, or locksValkeyWork happens on the server, not in read-modify-write loops
Per-field TTLsValkey 9.0 or laterHEXPIRE and related commands
Keep serving warm data after a node failureValkey with replicasReplication and automatic failover
Warm data after a planned restartEitherMemcached warm restart, or Valkey RDB
A managed service with the newest engine featuresValkey on ElastiCacheManaged Memcached is at 1.6.22 without extstore

If you already run Valkey for sessions or queues, running the cache on Valkey too means one engine instead of two. Give the cache its own instances with an eviction policy: a shared noeviction instance fails every write once the cache fills it.

Frequently asked questions

Is Valkey faster than Memcached?

It depends on the workload, and no neutral benchmark of current versions settles it. Memcached runs commands on all its worker threads. Valkey runs them on one main thread with I/O threads for networking, which the Valkey project reports reached 1.19M requests per second in 8.0. Benchmark both with memtier_benchmark on your own data.

Is Valkey multi-threaded like Memcached?

Partly. Since Valkey 8.0, I/O threads read and parse requests and write replies, configured with io-threads, which counts the main thread and defaults to 1. Command execution stays on a single main thread. Memcached runs 4 worker threads by default, and each one parses, executes, and replies, so its execution scales across cores.

Does Memcached support replication or persistence?

The server has no replication. The Memcached FAQ says adding it would halve the usable cache. Since 1.5.18 a warm restart can reload the cache from a file on a RAM disk after a clean SIGUSR1 stop, but it is not crash safe. Extstore puts values on flash for capacity, and a restart empties it.

Which uses less memory per key, Valkey or Memcached?

Measure it with your own keys. Memcached adds a fixed item header, 48 bytes by our reading of the 1.6.45 source, plus 8 bytes for CAS and slab rounding. Valkey cut per-key overhead in 8.0, 8.1, and 9.1, with 9.1 reporting 17 to 44% less string overhead than 9.0. Compare used_memory per key with total_malloced per item.

Can ElastiCache run both Valkey and Memcached?

Yes. ElastiCache offers both as serverless caches and node-based clusters. As of November 2026, the newest managed Memcached is 1.6.22, without extstore, and node-based Memcached has no replication, failover, or backups. Managed Valkey goes up to 9.1, with replication, automatic failover, and backups.

Key takeaways

  • Memcached 1.6.45 executes requests on every worker thread. Valkey 9.1 executes on one main thread and offloads socket I/O and parsing to optional I/O threads.
  • Choose Memcached for a simple blob cache that can lose a node. Choose Valkey when you need data types, per-field TTLs, replication, or failover.
  • Valkey defaults to noeviction with no memory limit. A Valkey cache needs maxmemory, an allkeys-* policy, and usually save "".
  • Memcached's warm restart and extstore are real features, but neither one makes it durable, and ElastiCache does not support extstore.
  • Treat the throughput and memory figures here as vendor numbers or source readings, and decide with a memtier_benchmark run on your own data.

Choosing a cache engine with JusDB

JusDB's Valkey consulting team can review your current Memcached or Valkey tier, size memory per key from samples of your production data, and plan and run a benchmark that compares the two engines on your workload.

Bring your key and value size distribution, hit ratio, peak request rate, and the failure you most want to avoid. Your team approves every production change.

Share this article

Database engineering notes

Articles like this one, in your inbox. No spam, unsubscribe anytime.

JusDB Team

Official JusDB content team

Keep reading

Moving a Multi-Database Valkey Instance into a 9.x Cluster

Move a Valkey instance that uses SELECT 1 to 15 into a 9.x cluster: keep or remap databases, copy each one, verify counts per database, and keep a way back.

Redis18 minNov 10, 2026
Read

Valkey Keyspace Notifications in Production: Expired Events, hexpired and Cluster Mode

Run Valkey keyspace notifications in production: why expired and hexpired events lag the TTL, how to subscribe on every cluster node, and when a stream is safer.

Redis20 minNov 10, 2026
Read

Valkey GLIDE in Production: AZ-Affinity Reads and Migrating from Legacy Clients

Configure Valkey GLIDE AZ-affinity reads on servers and clients, learn what GLIDE 2.5.3 does not support, and migrate from redis-py, Jedis, ioredis, or go-redis one service at a time.

Redis20 minNov 10, 2026
Read