Mục lục
- Mục Tiêu Bài Học
- Cái Bẫy Golden Hammer
- Khi Cần Primary Database Cho Dữ Liệu Quan Trọng
- Khi Cần Query Phức Tạp, JOIN, Transaction ACID
- Khi Dataset Lớn Hơn RAM Nhiều Lần
- Khi Cần Full-Text Search Phức Tạp
- Khi Cần Message Broker Bền Và Log Lớn
- Khi Cache Quá Đơn Giản: Redis vs Memcached
- Khi Cần Strong Consistency Tuyệt Đối
- Bảng So Sánh Và Decision Guide
- Pitfalls, Anti-patterns Và Tổng Kết
Mục Tiêu Bài Học
Đây là bài cuối của Module 0 — phần xây mental model về Redis. Cả module trước đó tập trung vào việc Redis làm được gì và làm tốt thế nào. Bài này cố ý đi theo hướng ngược lại để bạn không lạm dụng nó. Sau bài này bạn sẽ:
- Hiểu vì sao "Redis nhanh nên việc gì cũng nên dùng Redis" là một tư duy nguy hiểm, và cái giá cụ thể của việc chọn sai công cụ.
- Nhận diện được các trường hợp KHÔNG nên dùng Redis: làm primary database, query phức tạp, dataset lớn hơn RAM, full-text search, message broker bền, cache quá đơn giản, và yêu cầu strong consistency tuyệt đối.
- Biết công cụ thay thế phù hợp cho từng trường hợp: Postgres/MySQL, Elasticsearch (hoặc RediSearch), Kafka, Memcached.
- Dùng được một bảng so sánh Redis vs Postgres vs Memcached vs Kafka vs Elasticsearch theo các tiêu chí durability, query power, search, throughput, ordering, persistence model.
- Áp dụng một decision guide ngắn để chọn công cụ, và tránh các anti-pattern phổ biến như dùng
KEYSđể "query" hay coi Redis Streams là Kafka. - Khắc sâu tư duy đúng: Redis bổ trợ database chính, không thay thế nó — "right tool for the job".
Cái Bẫy Golden Hammer
Có một câu nói kinh điển trong kỹ thuật: "If all you have is a hammer, everything looks like a nail" — nếu bạn chỉ có một cái búa thì cái gì trông cũng giống cái đinh. Đây là golden hammer anti-pattern: quá quen với một công cụ tới mức dùng nó cho mọi bài toán, kể cả những bài toán nó không hợp.
Redis đặc biệt dễ rơi vào bẫy này vì ba lý do: nó nhanh, nó có nhiều data structure, và nó dễ thao tác. Khi đã thấy Redis xử lý cache, session, rate limiting, leaderboard đều gọn gàng, ta dễ kết luận sai rằng "Redis làm gì cũng được". Hậu quả của việc chọn sai chỗ rất cụ thể:
- Tốn tiền: Redis giữ toàn bộ dataset trong RAM. RAM đắt hơn disk hàng chục lần. Nhét một dataset lớn vào Redis nghĩa là phải trả tiền cho lượng RAM khổng lồ, trong khi cùng dữ liệu đó nằm trên disk của Postgres rẻ hơn nhiều.
- Mất data: Redis ưu tiên tốc độ nên persistence của nó (RDB/AOF) vẫn có thể mất một phần dữ liệu gần nhất khi crash, và replication là asynchronous nên failover có thể mất ghi. Coi nó là nơi lưu trữ duy nhất cho dữ liệu quan trọng là rủi ro.
- Phức tạp vô ích: để bắt Redis làm việc nó không sinh ra để làm (ví dụ full-text search, query quan hệ), ta phải viết rất nhiều logic ở tầng ứng dụng — thứ mà một công cụ đúng đã làm sẵn và làm tốt hơn.
Tư duy đúng là: chọn công cụ theo bài toán, không bắt bài toán uốn theo công cụ. Phần còn lại của bài là các tình huống cụ thể mà công cụ khác là lựa chọn tốt hơn Redis.
Khi Cần Primary Database Cho Dữ Liệu Quan Trọng
Đây là cám dỗ lớn nhất: "Redis có persistence (RDB/AOF) mà, vậy dùng nó làm database chính luôn cho nhanh". Đừng làm vậy với dữ liệu quan trọng. Persistence của Redis được thiết kế để khôi phục cache nhanh sau restart, không phải để đảm bảo độ bền (durability) ở mức của một RDBMS.
Vì sao không nên
- Durability không tuyệt đối: RDB chụp snapshot theo chu kỳ nên mất dữ liệu giữa hai lần snapshot. AOF an toàn hơn nhưng với
appendfsync everysec(mặc định phổ biến) vẫn có cửa sổ mất tới ~1 giây ghi khi crash. Đẩy lênalwaysthì an toàn hơn nhưng giết throughput. - Toàn bộ dữ liệu phải vừa RAM: primary DB thường lớn hơn RAM nhiều lần (xem mục 5).
- Thiếu các đảm bảo quan hệ: không có constraint, foreign key, schema validation mạnh, transaction ACID đa-bảng như RDBMS (xem mục 4).
Nên dùng gì thay
Dùng Postgres hoặc MySQL làm nguồn sự thật (source of truth) cho dữ liệu cần durability, quan hệ và truy vấn phức tạp. Redis đứng trước chúng như một lớp cache (cache-aside, học ở Module 1), tăng tốc đọc mà không phải là nơi lưu trữ duy nhất. Nếu mất Redis, dữ liệu vẫn còn nguyên trong Postgres.
Lưu ý: có những trường hợp dữ liệu thực sự volatile theo bản chất (session ngắn hạn, rate-limit counter, cache) thì việc "mất khi restart" là chấp nhận được — lúc đó Redis là nguồn lưu trữ chính cho loại dữ liệu đó hoàn toàn hợp lý. Vấn đề chỉ nảy sinh khi dữ liệu là quan trọng và cần bền vững.
Khi Cần Query Phức Tạp, JOIN, Transaction ACID
Redis là key-value store: cách truy cập tự nhiên của nó là "biết key, lấy value". Nó không có query engine để bạn lọc, sắp xếp, gộp dữ liệu theo điều kiện tuỳ ý như SQL. Khi bạn cần query ad-hoc (truy vấn không dự đoán trước), JOIN nhiều bảng, hay transaction ACID trên nhiều thực thể, Redis không phải công cụ phù hợp.
Một ví dụ "dùng sai" kinh điển
Giả sử ai đó lưu toàn bộ bảng users vào Redis (mỗi user là một key user:{id}) rồi muốn "tìm tất cả user ở Hà Nội đăng ký sau 2024". Cách họ thường làm là quét bằng KEYS:
// ANTI-PATTERN: dùng Redis như một RDBMS
// Lấy hết key user rồi load từng cái về app để lọc thủ công
const keys = await redis.keys("user:*"); // O(N) quét TOÀN BỘ keyspace, block server
const users = [];
for (const key of keys) { // N round-trip (hoặc cần pipeline)
const u = JSON.parse(await redis.get(key));
if (u.city === "Hanoi" && u.createdAt > "2024-01-01") {
users.push(u); // lọc Ở TẦNG APP, không có index
}
}
Đoạn này sai ở nhiều tầng: KEYS quét toàn bộ keyspace với độ phức tạp O(N) và chặn cả server (đã cảnh báo ở các bài trước); việc lọc xảy ra ở tầng app vì Redis không có index theo city hay createdAt; và không có cách nào để JOIN sang bảng orders chẳng hạn. Cùng yêu cầu đó, trong Postgres chỉ là một câu SQL có index lo phần nặng:
SELECT * FROM users
WHERE city = 'Hanoi' AND created_at > '2024-01-01';
-- với index trên (city, created_at), DB tự tối ưu, không quét toàn bảng
Nên dùng gì thay
Dùng RDBMS (Postgres/MySQL) cho mọi nhu cầu query có điều kiện linh hoạt, JOIN, aggregate, và transaction ACID trải trên nhiều bảng. Redis có transaction (MULTI/EXEC) nhưng đó là gom lệnh chạy nguyên tử, không phải ACID đa thực thể với rollback theo constraint như RDBMS. Nếu bạn cần lập index theo nhiều trường trong Redis, đó là dấu hiệu bạn đang cần một database thực sự, không phải Redis thuần.
Khi Dataset Lớn Hơn RAM Nhiều Lần
Redis giữ toàn bộ dataset trong RAM (Redis OSS thuần — các biến thể tiered/flash là sản phẩm thương mại riêng). RAM vừa giới hạn về dung lượng vừa đắt. Một server thường có vài chục đến vài trăm GB RAM, trong khi disk có thể hàng TB với chi phí thấp hơn nhiều bậc.
Nếu dataset của bạn là hàng TB nhưng RAM chỉ vài chục GB, có hai hướng sai lầm: hoặc cố nhồi hết vào Redis (tốn tiền vô lý, hoặc đơn giản là không khả thi), hoặc để Redis bị OOM và bắt đầu evict key một cách khó kiểm soát.
Nên dùng gì thay
- Database on-disk (Postgres/MySQL) làm nơi chứa toàn bộ dataset. Disk rẻ, dữ liệu nằm an toàn trên đó.
- Chỉ cache phần hot trong Redis: theo nguyên lý 80/20, thường chỉ một phần nhỏ dữ liệu được truy cập thường xuyên. Cache đúng phần đó với TTL và eviction policy phù hợp (đã học ở bài về maxmemory và eviction). Như vậy bạn được tốc độ Redis cho phần nóng mà không phải trả tiền RAM cho phần lạnh.
Quy tắc ngón tay cái: nếu working set (phần thường xuyên đọc) lớn hơn RAM khả dụng, bạn cần một database on-disk làm nền và chỉ dùng Redis cho phần hot — không phải dùng Redis làm kho chứa tất cả.
Khi Cần Full-Text Search Phức Tạp
Redis thuần (core) không có full-text search. Bạn không thể làm tìm kiếm theo relevance (xếp hạng độ liên quan), fuzzy matching (chấp nhận gõ sai chính tả), stemming (gốc từ), hay phân tích ngôn ngữ chỉ với String/Hash/Set. Cố mô phỏng bằng cách tự tách từ rồi index vào Set là một dự án phụ tốn công và vẫn thiếu các tính năng cốt lõi của search engine.
Nên dùng gì thay
- Elasticsearch (hoặc OpenSearch): chuyên cho full-text search với inverted index, relevance scoring (BM25), analyzer theo ngôn ngữ, fuzzy, aggregation. Đây là công cụ đúng cho search nghiêm túc.
- RediSearch — một module của Redis Stack: thêm khả năng secondary index và full-text search ngay trên Redis. Đây là lựa chọn hợp lý nếu bạn đã có Redis và nhu cầu search ở mức vừa phải. Nhưng nhấn mạnh: đây là module, không phải Redis thuần — phải cài Redis Stack/module riêng, không có sẵn trong Redis OSS mặc định.
Điểm cần nhớ: khi nghe "Redis làm được full-text search", hãy kiểm tra xem đó là Redis core (không làm được) hay RediSearch module (làm được, nhưng cần cài thêm).
Khi Cần Message Broker Bền Và Log Lớn
Redis có Pub/Sub và Redis Streams, và chúng rất hữu ích cho messaging realtime hoặc job queue ngắn hạn. Nhưng khi yêu cầu là một message broker bền vững với log khổng lồ, lưu trữ lâu dài, replay từ quá khứ xa, và ordering mạnh theo partition ở quy mô lớn — đó là sân của Kafka, không phải Redis.
Vì sao Redis không thay được Kafka ở quy mô log lớn
- Lưu trữ trong RAM: Redis Streams nằm trong RAM, nên giữ một log nhiều TB để replay dài hạn là không khả thi về chi phí. Kafka lưu log trên disk, retention tính bằng ngày/tuần/tháng một cách tự nhiên và rẻ.
- Pub/Sub là fire-and-forget: Pub/Sub của Redis không bền — subscriber offline thì mất message. Streams thì có persistence ở mức Redis, nhưng vẫn ràng buộc bởi RAM.
- Ordering theo partition và throughput log: Kafka được thiết kế quanh partition với ordering đảm bảo trong mỗi partition và throughput ghi log rất cao, cùng hệ sinh thái consumer group, offset, compaction.
Cách phân vai đúng
Redis Streams hợp với job ngắn hạn và luồng realtime: hàng đợi tác vụ, fan-out sự kiện trong vài giây tới vài phút, throughput cao nhưng retention ngắn. Kafka hợp với event log bền, audit trail, data pipeline, sự kiện cần giữ và replay lâu dài. Coi "Streams = Kafka thay thế hoàn toàn" là một hiểu lầm phổ biến.
Khi Cache Quá Đơn Giản: Redis vs Memcached
Một sự thật hơi ngược đời: đôi khi Redis là quá nhiều cho nhu cầu. Nếu bạn chỉ cần một cache key-value thuần — set một blob (chuỗi/JSON) với TTL rồi get lại, không cần Sorted Set, Hash, Stream, pub/sub hay persistence — thì Memcached có thể đủ và đơn giản hơn.
So sánh nhanh Redis vs Memcached
| Tiêu chí | Redis | Memcached |
|---|---|---|
| Data structure | Phong phú: String, Hash, List, Set, Sorted Set, Stream, Bitmap... | Chỉ key-value (blob), không có cấu trúc |
| Persistence | Có (RDB/AOF) | Không — thuần in-memory, mất khi restart |
| Replication / cluster | Có sẵn (replica, Redis Cluster) | Không có sẵn ở core; thường shard ở client |
| Mô hình thread | Xử lý lệnh single-threaded (có I/O threads) | Multi-threaded |
| Tính năng nâng cao | Pub/Sub, Lua, transaction, TTL linh hoạt, eviction policy đa dạng | Tối giản: get/set/delete + TTL |
| Hợp nhất khi | Cần data structure, persistence, hoặc tính năng phức tạp | Chỉ cần cache blob đơn giản, muốn hệ tối giản |
Thực tế hiện nay đa số dự án vẫn chọn Redis vì khi nhu cầu lớn lên (thêm leaderboard, rate limit, queue...) thì Redis mở rộng được mà không phải thay công cụ. Nhưng nếu hệ thống của bạn chắc chắn chỉ cần cache blob đơn giản và bạn ưu tiên sự tối giản về vận hành, Memcached là một lựa chọn hợp lệ. Điểm mấu chốt: đừng mặc định Redis chỉ vì quen — hãy biết rằng có lựa chọn nhẹ hơn.
Khi Cần Strong Consistency Tuyệt Đối
Replication của Redis là asynchronous: master nhận ghi và trả lời client trước, rồi mới đẩy thay đổi sang replica. Điều này tuyệt vời cho hiệu năng, nhưng kéo theo một hệ quả: nếu master crash ngay sau khi xác nhận ghi nhưng trước khi replica kịp nhận, lần ghi đó có thể mất khi failover (một replica được nâng lên làm master mới).
Vì vậy Redis không phù hợp nếu bạn cần strong consistency tuyệt đối xuyên replica cho dữ liệu mà mất một bản ghi là không chấp nhận được — ví dụ số dư tài khoản, giao dịch tài chính, tồn kho phải chính xác từng đơn vị.
Một số cơ chế giảm thiểu (không loại bỏ hoàn toàn)
- Lệnh
WAITbuộc chờ ghi được nhân bản tới một số replica nhất định trước khi trả về, nhưng vẫn không phải là cam kết durability tuyệt đối và đánh đổi latency. - Bật AOF với
appendfsync alwaystăng durability cục bộ trên một node nhưng giảm throughput đáng kể.
Nên dùng gì thay
Với dữ liệu yêu cầu đảm bảo "đã ghi là không mất" và nhất quán chặt, hãy đặt nguồn sự thật ở một RDBMS với transaction ACID và replication có cam kết bền (ví dụ synchronous/quorum replication). Redis có thể đứng trước như cache, nhưng quyết định mang tính tài chính/nghiệp vụ phải dựa trên dữ liệu trong database bền vững.
Bảng So Sánh Và Decision Guide
Gom lại tất cả thành một bảng để thấy mỗi công cụ mạnh ở đâu. Đọc theo hàng để so từng tiêu chí; không có ô nào "thắng tuyệt đối" — điểm là chọn theo bài toán.
| Tiêu chí | Redis | Postgres | Memcached | Kafka | Elasticsearch |
|---|---|---|---|---|---|
| Durability | Hạn chế (RDB/AOF, có thể mất ghi gần nhất) | Cao (ACID, WAL) | Không (thuần in-memory) | Cao (log trên disk, retention dài) | Trung bình (index trên disk, không phải DB giao dịch) |
| Query power | Theo key + lệnh data structure, không query ad-hoc | Rất mạnh (SQL, JOIN, aggregate, transaction) | Chỉ get/set theo key | Đọc tuần tự theo offset, không phải query DB | Truy vấn theo query DSL, lọc/aggregate trên index |
| Full-text search | Không (core); có nếu thêm RediSearch module | Cơ bản (tsvector); không chuyên sâu | Không | Không | Mạnh nhất (relevance, fuzzy, analyzer) |
| Throughput / latency | Rất cao, latency cỡ sub-millisecond | Vừa (chạm disk, query phức tạp tốn hơn) | Rất cao cho cache blob đơn giản | Throughput ghi/đọc log cực cao | Vừa (đánh đổi cho khả năng search) |
| Ordering | Có trong List/Stream, không phải ordering log phân tán mạnh | Theo query (ORDER BY) | Không | Ordering mạnh trong mỗi partition | Sắp xếp theo score/field khi query |
| Persistence model | In-memory + snapshot/append-only tuỳ chọn | On-disk, bền vững là mặc định | In-memory thuần | Append-only log trên disk, retention cấu hình | Index phân tán lưu trên disk |
| Use case chính | Cache, session, rate limit, leaderboard, lock, queue ngắn, realtime | Primary database, dữ liệu quan hệ, giao dịch | Cache blob key-value đơn giản | Event streaming bền, log lớn, data pipeline | Full-text search, log analytics, observability |
Decision guide ngắn
- Cần lưu dữ liệu quan trọng, bền, có quan hệ và query phức tạp? → Postgres/MySQL.
- Cần tăng tốc đọc cho phần dữ liệu nóng, hoặc cần session/rate limit/leaderboard/lock/queue ngắn? → Redis (đứng trước DB chính).
- Chỉ cần cache blob key-value siêu đơn giản, không cần data structure hay persistence? → Memcached (hoặc Redis nếu muốn mở rộng về sau).
- Cần event log bền, replay lâu dài, ordering theo partition ở quy mô lớn? → Kafka.
- Cần full-text search nghiêm túc (relevance, fuzzy, ngôn ngữ)? → Elasticsearch (hoặc RediSearch nếu nhu cầu vừa phải và đã có Redis).
Trong kiến trúc thực tế, các công cụ này thường kết hợp với nhau: Postgres làm nguồn sự thật, Redis cache phần nóng, Kafka làm xương sống event, Elasticsearch lo search. Redis là một mảnh ghép bổ trợ, không phải mảnh duy nhất.
Pitfalls, Anti-patterns Và Tổng Kết
Pitfalls và anti-patterns
- Redis-as-primary-DB cho dữ liệu quan trọng: coi Redis là nơi lưu trữ duy nhất cho dữ liệu cần bền vững. Persistence của Redis có thể mất một phần ghi gần nhất; mất Redis là mất dữ liệu. Luôn có một source of truth bền (RDBMS) phía sau.
- Nhồi dataset khổng lồ vào RAM: nhét cả dataset hàng TB vào Redis để "cho nhanh". RAM đắt và giới hạn; chỉ cache phần hot, để phần lạnh nằm trên disk của DB.
- Mong đợi ACID/consistency như RDBMS: tưởng
MULTI/EXEClà transaction ACID đa-bảng có rollback theo constraint, hoặc tưởng replication là synchronous. Redis không cho đảm bảo đó; dữ liệu nhạy cảm về nhất quán phải nằm ở RDBMS. - Dùng
KEYSđể "query": quétKEYS patternrồi lọc ở app là O(N) và chặn server. Redis không có index ad-hoc — nếu thấy mình cần lọc theo nhiều trường, bạn đang cần một database thực sự (hoặc RediSearch), không phảiKEYS. Nếu buộc phải quét, dùngSCAN(incremental, không chặn) thay choKEYS— nhưng đó vẫn không phải là query. - Coi Streams = Kafka thay thế hoàn toàn: Redis Streams hợp job ngắn và realtime; nó không thay Kafka cho log lớn, retention dài, replay xa và ordering phân tán ở quy mô cao vì Streams bị ràng buộc bởi RAM.
- Dùng Redis core cho full-text search: tự xây inverted index bằng Set là dự án phụ thiếu relevance/fuzzy/analyzer. Dùng Elasticsearch hoặc RediSearch module.
- Mặc định Redis vì quen: đôi khi Memcached đủ và đơn giản hơn cho cache blob thuần. Chọn theo nhu cầu, không theo thói quen.
Tổng kết
- Redis mạnh nhưng không phải golden hammer; chọn sai chỗ thì tốn tiền RAM, có nguy cơ mất data, hoặc làm kiến trúc phức tạp vô ích.
- Không dùng Redis làm primary database cho dữ liệu quan trọng, cho query phức tạp/JOIN/ACID đa-bảng, cho dataset lớn hơn RAM, hay khi cần strong consistency tuyệt đối — những việc đó thuộc về RDBMS như Postgres/MySQL.
- Full-text search nghiêm túc → Elasticsearch (hoặc RediSearch module). Message broker bền, log lớn, replay dài → Kafka. Cache blob siêu đơn giản → Memcached có thể đủ.
- Redis Streams hợp job ngắn và realtime, không thay Kafka ở quy mô log lớn; replication async nên có thể mất ghi khi failover.
- Tư duy đúng khép lại Module 0: Redis bổ trợ database chính, không thay thế — "right tool for the job".
Quiz 5 câu
- Golden hammer anti-pattern là gì, và ba cái giá cụ thể khi dùng Redis sai chỗ là gì?
- Vì sao persistence (RDB/AOF) của Redis không đủ để coi nó là primary database cho dữ liệu quan trọng?
- Tại sao dùng
KEYSđể "query" trong Redis là sai cả về hiệu năng lẫn thiết kế? Nên dùng gì thay nếu thực sự cần truy vấn linh hoạt? - Khi nào Redis Streams phù hợp và khi nào nên chọn Kafka? Lý do cốt lõi nằm ở đâu?
- Replication của Redis là synchronous hay asynchronous, và điều đó ảnh hưởng thế nào tới khả năng đảm bảo strong consistency khi failover?
Đáp án gợi ý
- Golden hammer là quá quen một công cụ nên dùng nó cho mọi bài toán kể cả không hợp. Ba cái giá khi dùng Redis sai chỗ: tốn tiền (RAM đắt, giữ toàn bộ dataset trong RAM), nguy cơ mất data (persistence không tuyệt đối, replication async), và phức tạp vô ích (phải tự viết logic mà công cụ đúng đã làm sẵn).
- RDB chụp snapshot theo chu kỳ nên mất dữ liệu giữa hai lần; AOF với
everysecvẫn có cửa sổ mất tới ~1 giây ghi khi crash; persistence thiết kế để khôi phục cache nhanh chứ không phải durability mức RDBMS. Ngoài ra toàn bộ data phải vừa RAM và thiếu đảm bảo quan hệ/ACID đa-bảng. KEYS patterncó độ phức tạp O(N), quét toàn bộ keyspace và chặn server (single-threaded). Về thiết kế, Redis không có index ad-hoc nên việc lọc phải làm ở tầng app, không JOIN được. Nếu cần truy vấn linh hoạt theo nhiều trường, dùng RDBMS (hoặc RediSearch cho secondary index/search); nếu chỉ cần duyệt key an toàn thì dùngSCANincremental thayKEYS.- Streams hợp job ngắn hạn và luồng realtime (queue, fan-out trong vài giây/phút) vì nằm trong RAM, throughput cao nhưng retention ngắn. Kafka hợp event log bền, retention dài, replay xa và ordering theo partition ở quy mô lớn vì lưu log trên disk rẻ. Lý do cốt lõi: Streams bị ràng buộc bởi RAM còn Kafka lưu log on-disk với retention cấu hình được.
- Asynchronous: master trả lời client trước rồi mới nhân bản sang replica. Nếu master crash trước khi replica nhận, lần ghi đó có thể mất khi một replica được nâng làm master mới (failover). Do đó không đảm bảo strong consistency tuyệt đối;
WAITvà AOFalwaysgiảm thiểu nhưng không loại bỏ hoàn toàn và đánh đổi latency/throughput.
Bài tiếp theo
Module 0 đã hoàn tất phần xây mental model: Redis là gì, nhanh thế nào, hoạt động ra sao bên trong, và quan trọng không kém — khi nào KHÔNG nên dùng. Từ Bài 9 ta bước sang Module 1 — Caching, bắt đầu với pattern caching nền tảng và phổ biến nhất: Cache-Aside. Bạn sẽ thấy luồng đọc cache trước, fallback xuống database khi miss, rồi ghi ngược kết quả vào Redis kèm TTL — đúng cách mà Redis "đứng trước" DB chính như cả Module 0 đã nhấn mạnh.
