Redis → Memcached:Memcached 不是 simpler Redis、是 cache paradigm
本文是跨 vendor migration playbook、cross-link Redis 跟 Memcached。跑 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 功能」是最常見的誤判:
| 概念 | Redis | Memcached |
|---|---|---|
| 核心 paradigm | Multi-paradigm(KV + 資料結構 + pub/sub + script) | Pure cache(KV + TTL) |
| Value 類型 | String / Hash / List / Set / Sorted Set / Stream / Bitmap / HyperLogLog | byte string only |
| Atomic operations | 100+(INCR / LPUSH / ZADD / …) | INCR / DECR / APPEND / CAS |
| Server-side scripting | Lua scripts (EVAL) | 無 |
| Pub/Sub | Native | 無 |
| Persistence | RDB / AOF | 無(restart 全失) |
| Replication | Async / sync replication | 無 |
| Cluster | Redis Cluster + Sentinel HA | Memcached cluster(client-side sharding) |
| Eviction policy | 8 種(LRU / LFU / random / …) | LRU only |
| Expiration accuracy | TTL 精確到 ms | TTL 精確到 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 / API | Redis 命令集 → Memcached 命令集、相容度 < 20% | High |
| Operational model | 兩者都簡單、Memcached 略簡單 | Low |
| Paradigm | Multi-paradigm → pure cache | High |
| Components | 同 1 個 cache service | Low |
| Application change | 必改(任何 hash / list / sorted set / pubsub 用法) | High |
| Data topology | 同 single instance / cluster | Low |
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 estimatorMigration 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 ↔ NATS 同 partial 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。
修法:
- Application 端 cache JSON object in memory:read-modify-write 仍 1 個 SET、但 read 是 memory
- Compare-and-swap (CAS):Memcached CAS 防止 concurrent update lost
- 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 慢。
修法:
- Cache pre-computed top N:DB scheduled job 每分鐘算 top 100、寫 Memcached、application 讀 cache 不直查 DB
- Materialized view + index:DB 端用 materialized view + index、毫秒級 query
- 保留 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 機制。
修法:
- NATS / Redis Streams + consumer group:each application instance 是 consumer、收 invalidation
- Database trigger + LISTEN/NOTIFY:PostgreSQL
LISTEN/NOTIFY對中型 fan-out 足夠 - 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 addADD + 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。
修法:
- Memory headroom:Memcached memory 限制拉高 30-50%、避免 eviction pressure
- Application-side cache priority:critical key 用 no-expiration set + 主動 refresh
- 保留 Redis for LFU workload:long-tail hot key 場景 Redis LFU 更合適、不該退 Memcached
Capacity / cost
| 維度 | Redis | Memcached |
|---|---|---|
| Memory efficiency | baseline | +10-20%(無 metadata overhead) |
| Throughput | ~100K ops/s single-thread | ~500K-1M ops/s multi-threaded |
| Latency p99 | 1-3ms | 0.5-1ms |
| Persistence overhead | 5-15% CPU | 0 |
| Operational FTE | 0.3-0.8 | 0.1-0.3 |
| Application complexity | Low(feature 豐富) | Higher(feature 移到 application) |
| Cost per GB memory | baseline | 略低(無 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
相關連結
- Source vendor:Redis
- Target vendor:Memcached
- 平行 migration playbook (Type E):Kafka ↔ NATS / PostgreSQL → CockroachDB
- 平行 Type B 對照:Redis → DragonflyDB(保留 paradigm)
- Methodology:Migration playbook methodology
#backend #cache #redis #memcached #paradigm-shift #migration #type-e