Luyện Phỏng Vấn IT — 2000+ Câu Hỏi Phỏng Vấn IT Có Đáp Án 2026

Redis (Remote Dictionary Server) là in-memory data structure store, thường được dùng làm cache, message broker, và session store.

  • Redis nhanh vì toàn bộ data được lưu trong RAM — tốc độ đọc/ghi RAM nhanh hơn disk hàng nghìn lần.
  • Ngoài ra, Redis dùng I/O multiplexing (epoll/kqueue) với event loop tương tự Node.js, tránh được overhead của context switching và lock contention.
  • Command execution vẫn single-threaded để đảm bảo atomicity.
  • Redis 7.x dùng multi-threaded I/O theo mặc định cho network (đọc/ghi socket), vẫn single-threaded cho command execution để tránh race condition.
  • Benchmark thông thường: Redis đạt 100,000-1,000,000 ops/giây tùy loại operation và hardware.

Redis có 5 kiểu dữ liệu cơ bản (String, Hash, List, Set, Sorted Set), mỗi loại tối ưu cho một use case khác nhau.

Các kiểu dữ liệu Redis và khi nào dùng:

  • String: kiểu cơ bản nhất, dùng cho cache HTML/JSON, counter (INCR pageview), session token. Lệnh: SET key value EX 3600, GET key, INCR counter.
  • Hash: lưu object có nhiều field (như row trong database), hiệu quả hơn lưu từng field dưới dạng String riêng. Dùng cho: user profile, product info. Lệnh: HSET user:1 name 'Alice' age 30, HGETALL user:1.
  • List: linked list có thể push/pop từ cả hai đầu — dùng cho message queue, activity feed, recent items. Lệnh: LPUSH queue task1, RPOP queue.
  • Set: tập hợp unique member — dùng cho tags, unique visitors, friend list. Lệnh: SADD tags:post:1 redis database, SISMEMBER, SUNION.
  • Sorted Set: như Set nhưng mỗi member có score — dùng cho leaderboard, rate limiting, priority queue. Lệnh: ZADD leaderboard 1000 'Alice', ZRANGE leaderboard 0 9 REV WITHSCORES.

Memcached là pure cache với chỉ một kiểu dữ liệu (string), multi-threaded, đơn giản.

  • Redis có nhiều kiểu dữ liệu (String, Hash, List, Set, Sorted Set, Stream, HyperLogLog, Geospatial), hỗ trợ persistence, pub/sub, transactions, Lua scripting, clustering native.
  • Redis phù hợp cho hầu hết use case hiện đại.
  • Nên dùng Memcached khi: chỉ cần simple key-value cache, muốn tận dụng multi-threading tốt hơn cho CPU-bound workload (hiếm gặp trong practice), team đã quen thuộc.
  • Nên dùng Redis khi: cần data structure phong phú, persistence, pub/sub, session store, rate limiting, leaderboard, distributed lock — về cơ bản hầu hết mọi trường hợp.
  • Trong thực tế, Redis đã thay thế Memcached ở phần lớn use case mới; Memcached chỉ còn được dùng trong legacy system.

Redis là multi-purpose in-memory store được dùng rộng rãi cho caching, session, rate limiting, leaderboard và distributed lock trong hầu hết production stack hiện đại.

Các use case phổ biến của Redis trong production:

  • Caching: lưu kết quả database query, API response, rendered HTML để giảm latency và tải cho database — đây là use case phổ biến nhất.
  • Session store: lưu session người dùng với TTL, dễ share giữa nhiều server instance.
  • Rate limiting: dùng INCR + EXPIRE hoặc Sorted Set để giới hạn API request theo IP/user.
  • Leaderboard: dùng Sorted Set để rank user theo điểm, cực kỳ efficient với ZADD, ZRANGE, ZRANK.
  • Queue: dùng List với LPUSH/BRPOP để implement simple task queue (hoặc Redis Streams cho production).
  • Pub/Sub: real-time notification, live dashboard, chat.
  • Distributed lock: implement lock bằng SET key value NX EX timeout để tránh race condition trong distributed system.
  • Geospatial: dùng Geo commands (GEOADD, GEODIST, GEOSEARCH FROMMEMBER/FROMLONLAT BYRADIUS) cho tính năng tìm store gần nhất — GEORADIUS đã deprecated từ Redis 6.2.
  • Full-text search: RedisSearch module (Redis Stack) cho phép search index trực tiếp trên Redis data.

HyperLogLog là probabilistic data structure để đếm unique elements (cardinality estimation) với sai số chuẩn ~0.81% nhưng dùng chỉ 12KB bộ nhớ cố định bất kể có 1 hay 1 tỷ unique element. Set chính xác 100% nhưng tốn memory tỷ lệ với số element. Lệnh: PFADD pageviews user123 user456, PFCOUNT pageviews (trả về ước lượng số unique users), PFMERGE daily_total hour1 hour2 (merge nhiều HLL). Khi dùng HyperLogLog: đếm unique visitors/day, unique queries/hour, unique IPs — khi cần approximate count với memory cực nhỏ và không cần biết chính xác element nào đã add. Khi dùng Set: khi cần biết element cụ thể có trong tập không (SISMEMBER), khi cần list members, khi số element < vài triệu và memory là vừa đủ.

Ví dụ: Google Analytics đếm unique page views dùng HyperLogLog — 1 tỷ visitors chỉ tốn 12KB thay vì hàng GB.

RDB (Redis Database Snapshot): tạo snapshot toàn bộ data ra file .rdb theo định kỳ (ví dụ: save 900 1 = snapshot nếu có ít nhất 1 key thay đổi trong 900 giây).

  • RDB compact, restart nhanh vì chỉ load một file, nhưng có thể mất data của khoảng thời gian kể từ snapshot cuối.
  • AOF (Append-Only File): log mọi write operation vào file .aof; khi restart, replay toàn bộ command để rebuild state.
  • AOF bền hơn (cấu hình appendfsync always không mất data, everysec mất tối đa 1 giây), nhưng file lớn hơn và restart chậm hơn.
  • Có thể dùng cả hai cùng lúc (khuyến nghị cho production): RDB cho backup định kỳ, AOF cho durability.
  • Redis 7.0 giới thiệu RDB-AOF hybrid format — AOF rewrite dùng RDB snapshot + delta AOF, kết hợp ưu điểm của cả hai.
  • Nếu Redis chỉ dùng thuần túy làm cache, có thể disable cả hai để tối đa performance.

Redis Pub/Sub cho phép publisher gửi message đến một channel và tất cả subscriber đang lắng nghe channel đó nhận được message ngay lập tức.

  • Lệnh: SUBSCRIBE news, PUBLISH news 'Hello World'.
  • Pub/Sub của Redis là fire-and-forget — message không được lưu trữ, nếu không có subscriber nào online khi publish thì message bị mất; subscriber mới không nhận được message cũ.
  • Hạn chế so với Kafka: không có message persistence, không replay, không consumer group với offset management, không đảm bảo delivery.
  • Pub/Sub phù hợp cho: real-time notification, chat message, live dashboard update, cache invalidation signal.
  • Nếu cần reliable messaging với durability và replay, dùng Redis Streams (thêm vào Redis 5.0) hoặc Kafka.
  • Redis Keyspace Notifications là biến thể của Pub/Sub để lắng nghe events từ Redis chính nó (key expired, key set, etc.).

Redis Transaction dùng MULTI/EXEC: các command giữa MULTI và EXEC được queue lại và execute atomically — không có command nào khác được xen vào giữa.

  • Tuy nhiên, Redis transaction không có rollback — nếu một command fail (runtime error như sai kiểu data), các command khác vẫn execute.
  • Lỗi cú pháp (syntax error) khi queue command sẽ khiến toàn bộ transaction bị discard; chỉ runtime error mới bị bỏ qua. DISCARD hủy transaction. WATCH key cho phép optimistic locking: nếu key thay đổi trước khi EXEC, transaction bị abort (EXEC trả về nil) — dùng để implement compare-and-swap.
  • Pipeline khác với transaction: pipeline đơn giản là gom nhiều command gửi một lần để giảm network round-trip, không đảm bảo atomicity.

Ví dụ dùng WATCH: đọc balance, kiểm tra đủ tiền, WATCH balance, MULTI, deduct, EXEC — nếu có race condition thì EXEC fail và retry.

Key expiration: dùng EXPIRE key seconds, PEXPIRE key milliseconds, hoặc SET key value EX seconds để tự động xóa key sau khoảng thời gian.

  • Redis kiểm tra expiry theo hai cách: lazy expiration (kiểm tra khi access key) và active expiration (background job mỗi 100ms xóa random sample 20 key trong set expired keys).
  • Eviction policy được áp dụng khi Redis đạt maxmemory: noeviction (reject write khi full), allkeys-lru (xóa key ít dùng nhất gần đây trong toàn bộ keyspace), volatile-lru (chỉ xóa key có TTL ít dùng nhất), allkeys-lfu (xóa key ít dùng nhất theo frequency — Redis 4.0+), volatile-ttl (xóa key có TTL ngắn nhất), allkeys-random.
  • Cache dùng allkeys-lru hoặc allkeys-lfu; session store dùng volatile-lru; data không thể mất dùng noeviction.
  • Nên set maxmemory-policy phù hợp và monitor evicted_keys metric.

Best practice là dùng dấu hai chấm : làm separator để tạo namespace hierarchy, ví dụ: user:1001:profile, user:1001:session, product:sku:ABC123:stock.

  • Các rule quan trọng: dùng tên có ý nghĩa và mô tả rõ object type + identifier + attribute (ví dụ: order:order_id:status); tránh key quá dài (mỗi key đều tốn memory cho metadata overhead); tránh key quá ngắn khó đọc; thống nhất format trong toàn team và document lại.
  • Dùng SCAN thay vì KEYS * khi cần tìm key theo pattern vì KEYS block Redis; SCAN iteration không block.
  • Đối với hot key (một key được access cực nhiều), có thể dùng local cache hoặc key sharding (user:1001:profile:shard:3).
  • Một pattern hay là thêm version vào key namespace để dễ cache invalidation hàng loạt: v2:user:1001:profile — khi cần invalidate tất cả, chỉ cần tăng version prefix.

Redis Bitmap không phải data type riêng — đó là String với các bit operation. Mỗi String key có thể chứa đến 512MB = ~4 tỷ bits. Lệnh: SETBIT key offset 1, GETBIT key offset, BITCOUNT key (đếm số bit=1), BITOP AND/OR/XOR dest key1 key2. Use case điển hình: Daily Active Users (DAU) — dùng user_id làm offset, mỗi ngày một key active:2024-01-15; SETBIT active:2024-01-15 user_id 1 khi user login; BITCOUNT active:2024-01-15 = số user active hôm nay. Memory: 1 triệu users = 1 triệu bits = 125KB — cực kỳ efficient. Feature flags per user: SETBIT feature:dark-mode user_id 1 để enable tính năng cho user cụ thể. Streak tracking: check consecutive days login. BITPOS: tìm vị trí bit đầu tiên = 0 hoặc 1 — dùng để assign ID.

Lưu ý: nếu user_id lớn (> 100 triệu), bitmap vẫn chỉ tốn 12MB cho 100M users — vẫn hiệu quả.

Leaderboard:

bash
ZADD leaderboard 9500 'alice'   # alice: 9500 điểm
ZADD leaderboard 8200 'bob'
ZRANGE leaderboard 0 9 REV WITHSCORES  # top 10 giảm dần
ZRANK leaderboard 'alice'               # rank của alice
ZINCRBY leaderboard 100 'alice'         # tăng 100 điểm

O(log N) cho mỗi update. Sliding Window Rate Limiter dùng Sorted Set:

python
# Key: ratelimit:user123, score = timestamp, member = unique_id
# (pseudocode - wrap in Lua script for atomicity in production)
ZADD ratelimit:user123 now() uuid()
ZREMRANGEBYSCORE ratelimit:user123 0 (now() - window)
count = ZCARD ratelimit:user123
if count > limit: reject
EXPIRE ratelimit:user123 window

Tất cả thao tác này cần wrap trong Lua script để atomic.

  • So với Fixed Window: Sliding Window chính xác hơn, không có boundary spike.
  • Nhược điểm: tốn memory hơn (lưu timestamp từng request) — dùng ZSET với member là timestamp+uuid.

String approach: serialize toàn bộ object thành JSON và lưu vào một key — đơn giản, một GET là lấy toàn bộ.

  • Nhược điểm: muốn update một field phải GET toàn bộ object, deserialize, update, serialize lại, SET — tốn băng thông và CPU; race condition nếu concurrent write. Hash approach: mỗi field là một Hash field — HSET user:1 name 'Alice' age 30 email 'a@b.com'; HGET user:1 name lấy một field; HINCRBY user:1 age 1 tăng atomic không cần GET. Memory trade-off: Redis tự dùng listpack encoding (compact array, đổi tên từ ziplist trong Redis 7.0) khi Hash nhỏ (< hash-max-listpack-entries=128 field và hash-max-listpack-value=64 bytes) — hash nhỏ tốn ít memory hơn nhiều String key riêng lẻ.
  • Khi vượt ngưỡng, Redis chuyển sang hashtable encoding. Quyết định: nhiều field thay đổi độc lập → Hash; object luôn đọc/ghi toàn bộ → String (JSON); object rất lớn (> 1KB serialized) → String với compression.

Cache-Aside (Lazy Loading): application tự quản lý cache.

  • Read: check cache → miss → query DB → set cache → return.
  • Write: update DB, invalidate hoặc update cache.
  • Ưu điểm: chỉ cache data thực sự được request (no unnecessary warming); linh hoạt.
  • Nhược điểm: cache miss gây thêm latency, cold start problem, potential stampede.
  • Phổ biến nhất cho read-heavy workloads. Write-Through: mỗi write cập nhật cả DB lẫn cache đồng thời (application hoặc cache layer).
  • Ưu điểm: cache luôn fresh.
  • Nhược điểm: write latency cao hơn; cache nhiều data ít được đọc (waste memory).
  • Tốt cho write-then-read workloads. Write-Behind (Write-Back): write vào cache trước, batch flush xuống DB async sau.
  • Ưu điểm: write latency cực thấp, reduce DB write load.
  • Nhược điểm: risk data loss nếu cache fail trước khi flush; eventual consistency.
  • Dùng cho: high-frequency write (game score, view counter, shopping cart), khi DB write là bottleneck.
  • Session cần đặc tính: đọc/ghi nhanh (mỗi request authenticated cần verify session), TTL-based expiration (logout tự động sau idle time), và không cần persist lâu dài.
  • Redis phù hợp hoàn hảo: O(1) GET/SET, native TTL support (SETEX session:abc123 3600 '{userId:1,...}'), in-memory nên sub-millisecond latency.
  • So với database: query DB mỗi request tốn 1-10ms thêm; DB không có native TTL; cần background job cleanup expired sessions. Implementation pattern:
bash
# Login: tạo session
SET session:{token} {json_data} EX 86400
# Verify: đọc session
GET session:{token}
# Logout: xóa session ngay
DEL session:{token}
# Extend: renew TTL khi user active
EXPIRE session:{token} 86400

Với Sentinel/Cluster: session available ngay cả khi một node fail. Lưu ý: không lưu sensitive data trong session JSON — chỉ lưu user_id, roles; sensitive data lấy từ DB.

Express.js connect-redis, Next.js iron-session đều support Redis backend.

Redis Sentinel là hệ thống HA cho Redis single-master/replica setup — tự động monitor, notify và failover khi master fail.

  • Sentinel chạy như separate process (thường 3+ Sentinel nodes để quorum). Chức năng: Monitoring — Sentinel liên tục ping master và replicas; Notification — alert qua pub/sub khi có vấn đề; Automatic failover — khi master down, Sentinel bầu (quorum majority) và promote replica thành master mới, cập nhật cấu hình các replica còn lại để follow master mới; Service discovery — client query Sentinel để lấy địa chỉ master hiện tại. Quorum: số Sentinel tối thiểu phải đồng ý master down để bắt đầu failover — tránh split-brain khi network partition.
  • Setup tối thiểu: 3 Sentinel + 1 master + 1 replica. Khác với Cluster: Sentinel không sharding, không scale write horizontally — chỉ HA.
  • Dùng Sentinel khi: cần HA nhưng data fit trong 1 server; dùng Cluster khi: cần scale beyond single server.

Redis có 8 eviction policy; allkeys-lru là lựa chọn tốt nhất cho pure cache, volatile-lru khi mix cache và persistent keys, noeviction khi data không được phép mất.

Khi Redis đạt maxmemory, eviction policy quyết định key nào bị xóa để nhường chỗ. Có 8 policy chính:

  • noeviction (default): trả lỗi khi full — dùng cho DB không chấp nhận mất data
  • allkeys-lru: evict key ít dùng gần đây nhất trong toàn bộ keyspace — tốt nhất cho cache thuần túy
  • allkeys-lfu (Redis 4.0+): evict key ít được access nhất về tần suất — tốt hơn LRU khi access pattern có hot/cold keys rõ ràng
  • allkeys-random: evict random — hiếm dùng
  • volatile-lru: evict key có TTL, ưu tiên LRU — dùng khi mix cache + persistent keys
  • volatile-lfu: evict key có TTL, ưu tiên LFU
  • volatile-random: evict random trong keys có TTL
  • volatile-ttl: evict key có TTL ngắn nhất — dùng khi muốn keys expire sớm bị evict trước

Thực tế: cache-only Redis → allkeys-lru; Redis vừa cache vừa persistent → volatile-lru (chỉ evict keys có TTL, không đụng đến persistent keys). Monitor evicted_keys metric để biết eviction đang xảy ra.