本文是跨 vendor migration playbook、cross-link RedisMemcached。跑 migration-playbook-methodology 6 維 audit 後對映 Paradigm = High(multi-paradigm → pure cache)→ Type E paradigm shift;本文是 paradigm reduction(downgrade 方向)的 dogfood。

Memcached 不是 simpler Redis、是 cache paradigm

把 Redis → Memcached 當「移除 Redis 功能」是最常見的誤判:

概念RedisMemcached
核心 paradigmMulti-paradigm(KV + 資料結構 + pub/sub + script)Pure cache(KV + TTL)
Value 類型String / Hash / List / Set / Sorted Set / Stream / Bitmap / HyperLogLogbyte string only
Atomic operations100+(INCR / LPUSH / ZADD / …)INCR / DECR / APPEND / CAS
Server-side scriptingLua scripts (EVAL)
Pub/SubNative
PersistenceRDB / AOF無(restart 全失)
ReplicationAsync / sync replication
ClusterRedis Cluster + Sentinel HAMemcached cluster(client-side sharding)
Eviction policy8 種(LRU / LFU / random / …)LRU only
Expiration accuracyTTL 精確到 msTTL 精確到 second、lazy expiration

核心差異不在「Memcached 少了 Redis 功能」、在「Memcached 是不同的 cache paradigm」。 Redis 的 features(hash / sorted set / pub/sub)多數 不該移除、是 重新分配到對應 specialized service

  • Hash / sorted set → application 端用 JSON + 自管 index
  • Pub/Sub → message queue(NATS / Redis Streams / Kafka)
  • Lua scripts → application code
  • Persistence → 真正需要的 data 該存 DB、不是 cache
  • Replication / cluster → Memcached 自己 cluster strategy

為什麼遷:simplification / cost / ops 三條 driver

  • Operational simplification:Memcached 沒 persistence / replication / cluster mode、ops surface 縮小、團隊不用懂 Redis 25+ command family
  • Cost:對 純 cache use case 而言、Memcached 每 GB 比 Redis 便宜(memory efficiency 略勝 + 無 persistence overhead)
  • Strict cache discipline:Memcached application code 把「真正的 cache」跟「半 persistent state」分開、避免 Redis 變 poor man’s database

反向 driver(Memcached → Redis):

  • Application 寫到 Memcached 後發現需要 atomic counter / leaderboard / queue / lock、應該升 Redis(不是繼續 wrap Memcached)

跑 6 維 audit

維度評估等級
Schema / APIRedis 命令集 → Memcached 命令集、相容度 < 20%High
Operational model兩者都簡單、Memcached 略簡單Low
ParadigmMulti-paradigm → pure cacheHigh
Components同 1 個 cache serviceLow
Application change必改(任何 hash / list / sorted set / pubsub 用法)High
Data topology同 single instance / clusterLow

3 維 High(Schema / Paradigm / Application change)多軸高、主導維度 = Paradigm → Type E paradigm shift;Schema + Application change 抽獨立段補充。

結構:類 Type E + paradigm reduction 分配路線

 11. Memcached 不是 simpler Redis(concept reverse 開頭)
 22. 為什麼遷
 33. 6 維 audit
 44. Paradigm reduction 路線(Redis features 對應的 specialized service)
 55. Schema 差段(Redis vs Memcached command set)
 66. Application 重設計(per-call-site refactor)
 77. Migration 流程(漸進、部分 use case 切)
 88. Production 故障演練
 99. Capacity / cost
1010. 整合 / 下一步

10 章節、220-260 行。比 Type E(Kafka ↔ NATS)多 paradigm reduction 路線 段。

Paradigm reduction 路線

Redis features 對應的 specialized service:

 1Redis Hash           → Application 端 JSON.stringify + Memcached SET
 2                       (or 直接存 DB + Memcached cache layer)
 3
 4Redis List (queue)   → NATS / Kafka / RabbitMQ / SQS
 5
 6Redis List (stack)   → Application 端用 array + 自管 LIFO
 7
 8Redis Set            → Application 端用 array + dedup OR 用 DB unique index
 9
10Redis Sorted Set     → Application 端用 ordered list + comparator
11                       OR PostgreSQL + index
12
13Redis Stream         → Kafka / Redis Streams (保留) / NATS JetStream
14
15Redis Pub/Sub        → NATS Core / Redis Streams / Kafka
16
17Redis Lua script     → Application code(避免 atomic 假設)
18
19Redis distributed lock → Consul / etcd / DB advisory lock / Redis (保留)
20
21Redis Bitmap         → DB bit column / 應用端 bitset
22
23Redis HyperLogLog    → DB approx_count_distinct / 應用端 cardinality estimator

Migration scope 包含 每個 Redis-specific feature use case 對應的 service 評估;不是「移除」、是「重新分配」。

Application 重設計

 1# Before: Redis hash
 2redis.hset('user:123', 'email', 'a@b.com')
 3redis.hset('user:123', 'name', 'Alice')
 4user = redis.hgetall('user:123')
 5
 6# After: Memcached + JSON
 7import json
 8user_data = {'email': 'a@b.com', 'name': 'Alice'}
 9mc.set('user:123', json.dumps(user_data))
10user = json.loads(mc.get('user:123') or '{}')
1# Before: Redis sorted set (leaderboard)
2redis.zadd('leaderboard', {'alice': 100, 'bob': 95})
3top_10 = redis.zrevrange('leaderboard', 0, 9, withscores=True)
4
5# After: PostgreSQL + index + Memcached cache
6# Persistent: write to DB
7# Cache: pre-compute top 10 in DB query, cache in Memcached
8mc.set('leaderboard:top10', json.dumps(db.query('SELECT user, score FROM scores ORDER BY score DESC LIMIT 10')))
1# Before: Redis distributed lock
2with redis.lock('resource:1', timeout=10):
3    process_resource()
4
5# After: PostgreSQL advisory lock OR Consul session
6with db.advisory_lock(resource_id):
7    process_resource()

每個 Redis-specific pattern 都要 per-call-site refactor、不是 SDK 換。

Migration 流程

Kafka ↔ NATSpartial migration

 11. Audit application code、列所有 Redis call site + feature 使用
 22. 按 feature 分類處理 plan:
 3   - Pure KV (GET/SET/DEL/TTL): 切 Memcached 直接
 4   - Hash → JSON + Memcached: per-call-site refactor
 5   - List/Sorted Set: 評估是 queue / leaderboard / 其他用途、對應 service
 6   - Pub/Sub: 移到 message queue
 7   - Lock: 移到 DB 或保留 Redis
 83. 部分 application 先切(純 KV use case)
 94. 複雜 use case 逐步 refactor 到對應 service
105. Memcached 跑 production 後、Redis 可降為 *narrow scope*(只跑剩餘 Redis-specific feature)
11   或完全退役(如果 application 已 refactor 乾淨)
126. 長期混合架構:Memcached cache layer + DB persistent state + 可選的 Redis(locks / specialty)

整體 3-12 個月、依 Redis-specific feature 使用深度。

Production 故障演練

Case 1:Hash → JSON 後 GET/SET round-trip 變 N+1

徵兆:cutover 後 application latency p99 從 5ms 漲到 50ms;profiling 顯示「為了改 user.email、要先 GET user object → modify → SET」、原本 Redis HSET 1 個 round-trip 現在 2 個。

根因:JSON-encoded value 不能 partial update、每次改一欄都要 read-modify-write。

修法

  1. Application 端 cache JSON object in memory:read-modify-write 仍 1 個 SET、但 read 是 memory
  2. Compare-and-swap (CAS):Memcached CAS 防止 concurrent update lost
  3. Field-level cache key:把 hash 拆成 N 個 Memcached key(user:123:email / user:123:name)、避開 JSON

Case 2:Sorted set leaderboard 退化、recomputation cost 爆

徵兆:原本 Redis leaderboard ZADD + ZREVRANGE < 1ms;切 DB-backed leaderboard 後 SELECT ... ORDER BY ... LIMIT 10 在 1M+ row 跑 100-500ms。

根因:Memcached 不支援 sorted set、leaderboard 必須在 DB 算、N 大時 sort 慢。

修法

  1. Cache pre-computed top N:DB scheduled job 每分鐘算 top 100、寫 Memcached、application 讀 cache 不直查 DB
  2. Materialized view + index:DB 端用 materialized view + index、毫秒級 query
  3. 保留 Redis sorted set:leaderboard 是 Redis 強項、不該退到 Memcached、走混合架構

Case 3:Pub/Sub 移除、缺 fan-out 機制

徵兆:原本 Redis Pub/Sub 跑 cache invalidation broadcast、N 個 application instance 都收 invalidation msg;切 Memcached 後失去 broadcast、cache stale。

根因:Memcached 沒 Pub/Sub;application 需要外部 fan-out 機制。

修法

  1. NATS / Redis Streams + consumer group:each application instance 是 consumer、收 invalidation
  2. Database trigger + LISTEN/NOTIFY:PostgreSQL LISTEN/NOTIFY 對中型 fan-out 足夠
  3. Architecture rethink:是否真需要 broadcast invalidation?通常用 TTL-based cache + cache key versioning 就能 cover 多數 invalidation use case

Case 4:Atomic INCR 沒對等、race condition

徵兆:rate limiter / counter pattern 切 Memcached、mc.incr(key) 在 key 不存在時 return None(不 auto-init 為 0);application 端 if None: mc.set(key, 1) race condition、低機率 counter reset。

根因:Memcached INCR 對 missing key 不像 Redis 自動 init;application 端 init logic 容易 race。

修法

1# 用 ADD(atomic put-if-absent)
2mc.add(key, 0)  # only sets if missing
3mc.incr(key)    # always works after add

ADD + INCR 兩個 atomic operation 合起來 race-free。

Case 5:Eviction policy 差異、production cache hit rate 降

徵兆:cutover 後 cache hit rate 從 95% 降到 80%;profiling 發現「重要 key 沒在 cache」、新 key 一直擠走熱 key。

根因:Redis 預設 allkeys-lfu (least frequently used)、長期熱 key 不被擠;Memcached 只有 LRU、單純按 access time、burst access 的 cold key 擠走 long-tail hot key。

修法

  1. Memory headroom:Memcached memory 限制拉高 30-50%、避免 eviction pressure
  2. Application-side cache priority:critical key 用 no-expiration set + 主動 refresh
  3. 保留 Redis for LFU workload:long-tail hot key 場景 Redis LFU 更合適、不該退 Memcached

Capacity / cost

維度RedisMemcached
Memory efficiencybaseline+10-20%(無 metadata overhead)
Throughput~100K ops/s single-thread~500K-1M ops/s multi-threaded
Latency p991-3ms0.5-1ms
Persistence overhead5-15% CPU0
Operational FTE0.3-0.80.1-0.3
Application complexityLow(feature 豐富)Higher(feature 移到 application)
Cost per GB memorybaseline略低(無 persistence I/O / replication overhead)

判讀:純 cache use case 走 Memcached 省 ops + 略省 cost;application 已用 Redis-specific feature 不該切;混合架構是 long-term default。

整合 / 下一步

Redis → DragonflyDB 對比

兩條路:

  • DragonflyDB:保留 Redis paradigm、優化 throughput + memory;application 不用改
  • Memcached:退到 pure cache paradigm、application 必須改、但 ops 簡化

選擇取決於 是否真的需要 Redis multi-paradigm features:用得到就 DragonflyDB / Redis、用不到就 Memcached。

NATS 整合

Redis Pub/Sub 移除後、應用端 fan-out / messaging 需求轉到 NATS / Redis Streams / Kafka;本文 cross-link migration playbook Kafka ↔ NATS 有 paradigm shift 流程參考。

下一步議題

  • Memcached Cluster strategy:client-side consistent hashing vs server-side cluster mode、ops 簡化 vs scalability 取捨
  • Long-term mixed architecture:80% Memcached + 20% Redis 是常見 stable state、不一定要完全消除 Redis

相關連結