Sets trong Redis: Tagging, Dedup và Set Operations

3 phút đọc

Bạn có biết?

Bạn cần kiểm tra nhanh "username này đã follow mình chưa"? Hay lọc ra những tag chung giữa 2 bài viết? Với List hay Hash, bạn phải duyệt toàn bộ dữ liệu — chậm và tốn tài nguyên. Redis Sets giải quyết những bài toán này trong chớp mắt, chỉ với một lệnh duy nhất.

Giống như một hộp kẹo không bao giờ có hai viên trùng màu, Set trong Redis là một tập hợp các phần tử duy nhất, không có thứ tự. Nếu bạn cố thêm một phần tử đã tồn tại, Redis sẽ im lặng bỏ qua — không báo lỗi, không tạo bản sao.

Sets trong Redis là gì?

Set là một cấu trúc dữ liệu lưu trữ tập hợp các string duy nhất (không trùng lặp), không sắp xếp theo thứ tự. Mỗi phần tử chỉ xuất hiện đúng một lần, và thao tác kiểm tra thành viên (membership check) có độ phức tạp O(1) — tức là nhanh không đổi dù Set có 10 phần tử hay 10 triệu phần tử.

Điểm mạnh lớn nhất của Set là hỗ trợ các phép toán tập hợp ngay phía server: giao (intersection), hợp (union), hiệu (difference). Thay vì kéo toàn bộ dữ liệu về ứng dụng để xử lý, bạn để Redis tính toán và chỉ nhận kết quả cuối cùng.

  • Duy nhất (unique) — mỗi phần tử chỉ tồn tại một lần, tự động chống trùng lặp
  • Không có thứ tự (unordered) — thứ tự thêm vào không được đảm bảo khi đọc ra
  • O(1) membership check — kiểm tra tồn tại cực nhanh với SISMEMBER
  • Tối đa 2^32 - 1 phần tử (hơn 4 tỷ) — thoải mái cho hầu hết use case
  • Phép toán tập hợp mạnh mẽ — SINTER, SUNION, SDIFF chạy ngay trên server

Làm quen với các lệnh Sets cơ bản

Hãy tưởng tượng bạn đang xây dựng tính năng bookmark bài viết cho người dùng. Mỗi user có một Set riêng chứa ID các bài đã lưu:

# Thêm bài viết vào bookmark của user:1001
SADD user:1001:bookmarks 101 102 103 101
# (integer) 3  → chỉ thêm được 3, vì 101 đã tồn tại

# Xem tất cả bookmark
SMEMBERS user:1001:bookmarks
# 1) "101"  2) "102"  3) "103"

# Kiểm tra user đã bookmark bài 101 chưa?
SISMEMBER user:1001:bookmarks 101
# (integer) 1  → có

# Đếm số bookmark
SCARD user:1001:bookmarks
# (integer) 3

# Xóa một bookmark
SREM user:1001:bookmarks 102
# (integer) 1

# Lấy ngẫu nhiên một bookmark (không xóa) / xóa luôn khỏi Set
SRANDMEMBER user:1001:bookmarks
SPOP user:1001:bookmarks

Mẹo: với Set lớn (hàng trăm nghìn phần tử), đừng dùng SMEMBERS — nó trả về toàn bộ Set và có thể treo ứng dụng. Dùng SSCAN để duyệt từng phần một, giống SCAN với Keys.

Set Operations: SINTER, SUNION, SDIFF

Đây là nơi Set thể hiện sức mạnh thật sự. Redis tính toán trực tiếp trên server, bạn chỉ nhận kết quả:

# User A thích: toán, lý, hóa
SADD user:A:likes toan ly hoa
# User B thích: lý, hóa, sinh
SADD user:B:likes ly hoa sinh

# Sở thích CHUNG của A và B (giao) → "ly" "hoa"
SINTER user:A:likes user:B:likes

# Tổng hợp tất cả sở thích (hợp) → toan ly hoa sinh
SUNION user:A:likes user:B:likes

# Thứ A thích mà B KHÔNG thích (hiệu) → "toan"
SDIFF user:A:likes user:B:likes

# Lưu kết quả vào Set mới để dùng lại
SINTERSTORE common:likes user:A:likes user:B:likes

Use case 1: Tagging cho bài viết

Đây là use case kinh điển nhất của Set. Mỗi tag là một Set chứa ID các bài viết, hoặc mỗi bài là một Set chứa các tag:

# Bài 101 gắn tag: redis, database, backend
SADD post:101:tags redis database backend

# Tag "redis" đang có những bài nào?
SMEMBERS tag:redis:posts

# Bài nào vừa có tag "redis" vừa có tag "database"?
SINTER tag:redis:posts tag:database:posts

# Xóa một tag khỏi bài
SREM post:101:tags database

Mô hình hai chiều (post → tags, tag → posts) cho phép truy vấn theo cả hai hướng trong O(1). Khi bài viết được gắn tag, bạn chỉ cần SADD vào cả hai Set — không lo trùng lặp, không cần kiểm tra tồn tại trước.

Use case 2: Dedup — loại bỏ trùng lặp

Set tự động chống trùng lặp, nên nó là công cụ hoàn hảo để đếm unique:

# Đếm visitor duy nhất trong ngày (theo user_id)
SADD stats:2026-07-29:unique_users 1001 1002 1001 1003
SCARD stats:2026-07-29:unique_users
# (integer) 3  → dù có 4 lần truy cập, chỉ 3 user duy nhất

# Email đăng ký nhận tin — chống đăng ký trùng
SADD newsletter:subscribers [email protected]

# Hàng đợi xử lý công việc — tránh xử lý cùng một job 2 lần
SADD jobs:processed job_123
SISMEMBER jobs:processed job_123

Lưu ý: nếu bạn cần đếm unique với số lượng cực lớn (hàng trăm triệu) và chấp nhận sai số nhỏ, HyperLogLog (PFADD/PFCOUNT) tốn ít bộ nhớ hơn nhiều — chỉ ~12KB cho 2^64 phần tử. Set dùng khi bạn cần danh sách thật, HyperLogLog dùng khi chỉ cần con số.

Use case 3: Like, Follow và trạng thái Online

Set sinh ra cho những bài toán membership kiểu "ai đó có nằm trong nhóm này không":

# Ai đã like bài 555?
SADD post:555:likes 1001 1002 1003

# User 1001 đã like chưa? → SISMEMBER O(1), không cần COUNT(*)
SISMEMBER post:555:likes 1001

# Danh sách follow của user A và B — đề xuất "có thể bạn quen"
SADD user:A:following 2001 2002
SADD user:B:following 2002 2003
# Người A follow mà B chưa follow → gợi ý kết bạn
SDIFF user:A:following user:B:following

# User online trong 5 phút gần đây (user_id thêm vào Set có TTL)
SADD online_users 1001 1002
EXPIRE online_users 300

Sets trong Node.js với ioredis

Trong ứng dụng Node.js, dùng package ioredis, các lệnh Set được gọi trực tiếp với tên viết hoa:

import Redis from "ioredis";
const redis = new Redis();

// Gắn tag cho bài viết
await redis.sadd("post:101:tags", ["redis", "database", "backend"]);

// Kiểm tra bài có tag "redis" không
const hasRedis = await redis.sismember("post:101:tags", "redis");

// Lấy tất cả tag
const tags = await redis.smembers("post:101:tags");

// Bài viết chung giữa 2 tag
const common = await redis.sinter("tag:redis:posts", "tag:database:posts");

// Đếm unique user hôm nay
const uniqueUsers = await redis.scard("stats:2026-07-29:unique_users");

// Thêm user vào danh sách online, tự hết hạn sau 5 phút
await redis.sadd("online_users", userId);
await redis.expire("online_users", 300);

Toàn bộ lệnh trên là atomic — mỗi lệnh chạy một mạch trên Redis, không lo race condition giữa các request đồng thời. Với các thao tác phức tạp cần chạy liên hoàn như một khối, hãy dùng Pipeline hoặc Lua Script (xem bài TransactionsLua Scripts).

Sets vs Lists: chọn cái nào?

Quy tắc nhớ nhanh: cần chống trùng + kiểm tra tồn tại → Set. Cần thứ tự + hàng đợi → List. Cần thứ tự + điểm số (leaderboard) → Sorted Set.

Best Practices

  • Dùng SISMEMBER thay vì SMEMBERS để kiểm tra — tránh kéo toàn bộ Set về client khi chỉ cần một câu hỏi có/không
  • Set lớn thì dùng SSCAN thay cho SMEMBERS — duyệt theo batch, không nghẽn Redis
  • Đặt TTL cho Set tạm thời (online_users, session) — tránh rò rỉ bộ nhớ
  • Dùng *STORE khi tái sử dụng kết quả — SINTERSTORE/SUNIONSTORE tính một lần, dùng nhiều nơi
  • Đặt tên key có tiền tố rõ ràngpost:101:tags, tag:redis:posts giúp debug và truy vấn dễ dàng
  • Nhớ giới hạn bộ nhớ — Set 1 triệu phần tử ~ hàng chục MB; theo dõi bằng MEMORY USAGE key

Tổng kết

Sets là một trong những cấu trúc dữ liệu được dùng nhiều nhất trong Redis, không chỉ vì khả năng chống trùng lặp mà còn nhờ các phép toán tập hợp chạy ngay trên server. Từ tagging, dedup, likes/follows đến unique visitors — chỉ cần một lệnh, không cần vòng lặp, không lo race condition.

Bước tiếp theo: nắm vững Sets rồi, hãy tìm hiểu cách giám sát và cảnh báo Redis production để đảm bảo hệ thống của bạn luôn khỏe mạnh. Hoặc xem Sorted Sets nếu bạn cần leaderboard và xếp hạng theo điểm số.

Xem thêm: Tất cả bài viết về Redis · Hashes trong Redis

← Bài trướcCache Strategies với Redis: Từ Lý Thuyết Đến Thực Hành