Top 20+ câu hỏi phỏng vấn Redis phổ biến

Redis đã trở thành một trong những cơ sở dữ liệu phổ biến nhất hiện nay, nhờ vào hiệu suất cao, dễ sử dụng và linh hoạt. Vì vậy, Redis rất dễ trở thành một trong những chủ đề sẽ xuất hiện trong buổi phỏng vấn tiếp theo của bạn, dù bạn là fresher, Developer hay Software Architect. Sau đây là tổng hợp các câu hỏi phỏng vấn Redis kèm câu trả lời chi tiết giúp bạn chuẩn bị cho buổi phỏng vấn tiếp theo.

Trong bài viết này, bạn sẽ tìm thấy:

  • Những vị trí IT nào thường được hỏi về Redis
  • Bộ câu hỏi phỏng vấn Redis cho từng cấp độ
  • Cách trả lời không chỉ đúng mà còn giúp bạn “ghi điểm”

Kiến thức cơ bản cần nhớ về Redis

Redis (Remote Dictionary Server) là một key-value store trong bộ nhớ (in-memory data store), mã nguồn mở và cực kỳ nhanh.  Redis sử dụng kiến trúc single-threaded cho các thao tác (operations) chính, đảm bảo tính nhất quán dữ liệu và hiệu suất cao.

Redis hỗ trợ nhiều kiểu dữ liệu như strings, hashes, lists, sets, sorted sets, bitmaps, hyperloglogs, geospatial indexes và streams, mang lại khả năng lưu trữ và truy xuất dữ liệu linh hoạt cho nhiều loại ứng dụng khác nhau. 

Đọc chi tiết: Redis là gì: Tổng hợp tính năng hữu ích nhất của Redis

Redis thường được sử dụng:

  • Làm cache để tăng tốc độ truy xuất dữ liệu
  • Làm message broker cho hệ thống giao tiếp
  • Database cho các ứng dụng cần khả năng phản hồi nhanh

Một trong những tính năng nổi bật của Redis là khả năng hỗ trợ persistence (lưu trữ bền vững), cho phép lưu trữ dữ liệu bền vững bằng cách sử dụng các cơ chế như RDB snapshots hoặc AOF logs. 

Redis còn hỗ trợ các tính năng nâng cao như:

  • Gửi nhận thông điệp (pub/sub messaging)
  • Transactions (transactions) và replication
  • Redis Cluster cho phép mở rộng ngang

Redis Sentinel giám sát và failover tự động, giúp xây dựng các hệ thống phân tán dễ dàng và hiệu quả. 

Các vị trí thường gặp câu hỏi phỏng vấn về Redis

  • Backend Developer: Các câu hỏi Redis sẽ chủ yếu tập trung vào việc sử dụng Redis trong backend, đặc biệt là với các ứng dụng cần tối ưu hiệu suất, sử dụng Redis làm cache, message broker hoặc session store.
  • DevOps Engineer: Câu hỏi có thể liên quan đến cách cấu hình và quản lý Redis trong môi trường sản xuất, cách cài đặt Redis cluster và tối ưu hóa hiệu suất Redis trong các hệ thống phân tán.
  • Site Reliability Engineer (SRE): Phỏng vấn SRE có thể tập trung vào việc quản lý Redis trong các hệ thống phân tán, sao lưu và phục hồi dữ liệu, cũng như cách xử lý các vấn đề về tính sẵn sàng cao và khả năng chịu lỗi.
  • Data Engineer: Các câu hỏi Redis có thể bao gồm cách tích hợp Redis vào data pipeline, real-time analytics hoặc streaming data.

Chuẩn bị trước phỏng vấn: Tổng hợp Redis command: Hướng dẫn sử dụng các lệnh Redis phổ biến kèm ví dụ chi tiết

Câu hỏi phỏng vấn Redis – Cấp độ cơ bản

Redis khác gì với RDBMS như MySQL?

Redis và RDBMS như MySQL đều là hệ quản trị cơ sở dữ liệu, nhưng chúng có sự khác biệt rõ rệt về cách hoạt động, mục đích sử dụng và các tính năng hỗ trợ.

Redis phù hợp cho các ứng dụng cần tốc độ nhanh, dữ liệu tạm thời, và xử lý dữ liệu lớn trong thời gian thực, trong khi MySQL là lựa chọn phù hợp cho các hệ thống lưu trữ dữ liệu lâu dài và quản lý dữ liệu quan hệ có tính bền vững và tính toàn vẹn cao.

Cụ thể:

Tiêu chíRedisMySQL (RDBMS)
Kiểu lưu trữIn-memory (lưu trữ trong bộ nhớ RAM)Disk-based (lưu trữ trên đĩa cứng)
Cấu trúc dữ liệuStrings, lists, sets, sorted sets, Hashes,….Dạng bảng với các mối quan hệ giữa các bảng
Hiệu suấtRất nhanh, độ trễ thấp nhờ lưu trữ trong bộ nhớTốc độ chậm hơn vì phải truy xuất dữ liệu từ ổ đĩa
Quản lý dữ liệu bền vữngHỗ trợ persistence qua RDB snapshots và AOF logs, nhưng dữ liệu có thể bị mất khi gặp sự cốDữ liệu bền vững, lưu trữ ổn định trên đĩa cứng
Mục đích sử dụngCaching, quản lý phiên làm việc (session management), xử lý dữ liệu thời gian thực (real-time analytics), hàng đợi công việc (task queues), pub/sub messaging, bảng xếp hạng (leaderboards)…Quản lý dữ liệu quan hệ, tính toàn vẹn dữ liệu cao
Tính mở rộngPhân tán qua Redis Cluster, hỗ trợ mở rộng dễ dàngMở rộng phức tạp hơn, cần thêm các cơ chế khác
Tính linh hoạtHỗ trợ nhiều kiểu dữ liệu linh hoạt, dễ dàng thay đổi cấu trúc dữ liệuQuản lý dữ liệu quan hệ chặt chẽ, yêu cầu cấu trúc ổn định
Tính toàn vẹn giao dịchHỗ trợ tính toàn vẹn qua transactions (multi/exec) với tính nguyên tử (atomic), nhưng không hỗ trợ rollback. Redis cũng hỗ trợ optimistic locking (kỹ thuật cho phép giao dịch tiếp tục mà không khóa tài nguyên ngay lập tức) với lệnh WATCHTuân thủ chuẩn ACID, đảm bảo tính toàn vẹn của giao dịch
Ngôn ngữ truy vấnCung cấp giao diện dựa trên lệnh (command-based interface) với hơn 200 lệnh khác nhauSQL (Structured Query Language)
Memory usageTối ưu cho các thao tác trên bộ nhớ (in-memory operations), vì vậy yêu cầu RAM lớn để xử lý dữ liệu nhanh chóngHiệu quả khi sử dụng disk storage và ít yêu cầu bộ nhớ RAM, giúp tiết kiệm tài nguyên hệ thống

Redis lưu trữ dữ liệu ở đâu?

Redis là một hệ thống cơ sở dữ liệu lưu trữ trong bộ nhớ (in-memory database), có nghĩa là dữ liệu trong Redis chủ yếu được lưu trữ trong bộ nhớ RAM của máy chủ. Điều này giúp Redis có thể truy xuất và xử lý dữ liệu rất nhanh, do các thao tác đọc/ghi xảy ra trực tiếp trong bộ nhớ mà không cần phải truy xuất vào ổ đĩa. 

Tuy nhiên, Redis cũng hỗ trợ các phương pháp persistence (lưu trữ dữ liệu bền vững) để đảm bảo rằng dữ liệu không bị mất khi máy chủ gặp sự cố hoặc khi Redis khởi động lại. Có hai cơ chế persistence phổ biến mà Redis hỗ trợ:

  • RDB (Redis Database snapshots): Redis tạo một snapshot của dữ liệu tại các thời điểm nhất định (theo tần suất cấu hình). Dữ liệu được lưu trữ vào tệp trên đĩa dưới định dạng RDB. Đây là phương pháp lưu trữ nhanh chóng nhưng có thể mất dữ liệu nếu Redis gặp sự cố giữa các lần chụp ảnh (snapshot).
  • AOF (Append Only File): Redis ghi lại tất cả các lệnh ghi dữ liệu vào một tệp AOF (Append Only File), cho phép phục hồi dữ liệu chính xác theo từng thao tác. Phương pháp này an toàn hơn trong việc đảm bảo dữ liệu không bị mất, nhưng tệp AOF có thể lớn và việc phục hồi từ AOF có thể chậm hơn so với RDB.

Khi nào nên dùng Redis, khi nào dùng Memcached?

Khi nào nên dùng Redis?Khi nào nên dùng Memcached?
Khi cần hỗ trợ nhiều kiểu dữ liệu: Redis hỗ trợ nhiều kiểu dữ liệu phong phú như strings, lists, sets, sorted sets, hashes, bitmaps và hyperloglogs. Điều này giúp Redis linh hoạt hơn khi cần lưu trữ và thao tác với các cấu trúc dữ liệu phức tạp.Khi chỉ cần cache đơn giản: Memcached là một công cụ cache đơn giản và dễ sử dụng, phù hợp cho các ứng dụng cần lưu trữ dữ liệu tạm thời với độ phức tạp thấp. Nếu chỉ cần cache đơn giản và không yêu cầu các tính năng phức tạp như Redis, Memcached có thể là lựa chọn tối ưu.
Khi cần tính năng persistence (lưu trữ dữ liệu bền vững): Redis cung cấp hai cơ chế persistence là RDB (Redis Database snapshots) và AOF (Append Only File), giúp bảo vệ dữ liệu khi hệ thống gặp sự cố hoặc khi Redis khởi động lại. Nếu cần đảm bảo tính bền vững cho dữ liệu, Redis là lựa chọn tốt.Khi cần tiết kiệm bộ nhớ: Memcached có cơ chế lưu trữ rất đơn giản và không hỗ trợ các tính năng như Redis, giúp Memcached chiếm ít bộ nhớ hơn, phù hợp khi chỉ cần lưu trữ các key-value pairs.
Khi cần các tính năng nâng cao như Pub/Sub và Transactions: Redis hỗ trợ mô hình Pub/Sub (Publish/Subscribe) cho các ứng dụng thời gian thực và transactions để thực hiện nhiều lệnh Redis một cách nguyên tử (atomic).Khi cần hiệu suất cache cực nhanh: Memcached được thiết kế để có tốc độ truy xuất rất nhanh trong các ứng dụng cache. Nếu mục tiêu của bạn chỉ là cache các đối tượng đơn giản và không yêu cầu các tính năng nâng cao, Memcached có thể cung cấp hiệu suất tốt hơn Redis.
Khi cần khả năng mở rộng và phân tán: Redis hỗ trợ Redis Cluster, giúp phân mảnh dữ liệu và mở rộng hệ thống một cách dễ dàng. Vì vậy redis rất phù hợp với các ứng dụng yêu cầu khả năng mở rộng quy mô lớn.Khi chi phí bộ nhớ là yếu tố quan trọng: Memcached có memory overhead thấp hơn Redis khi lưu trữ các cặp key-value đơn giản, giúp tiết kiệm bộ nhớ hơn trong các trường hợp chỉ yêu cầu lưu trữ dữ liệu đơn giản mà không cần tính năng phức tạp.

Redis có hỗ trợ các kiểu dữ liệu nào?

  1. String (chuỗi): Là kiểu dữ liệu cơ bản và đơn giản nhất trong Redis. String có thể lưu trữ các giá trị như văn bản, số nguyên, số thực hoặc bất kỳ dữ liệu nào dưới dạng byte. Kích thước tối đa của một string là 512MB.
  • Ví dụ để lưu trữ tên người dùng:
r.set('username', 'Alice')  # Lưu trữ tên 'Alice' vào key 'username'
username = r.get('username')  # Lấy giá trị của key 'username'
print(username)  # Output: 'Alice'

# Các operations phổ biến khác
r.incr('counter')            # Tăng giá trị số lên 1
r.append('username', '123')  # Nối thêm chuỗi
r.strlen('username')         # Lấy độ dài chuỗi
  1. List: Lists trong Redis là các dãy các phần tử có thứ tự và cho phép trùng lặp. Ta có thể sử dụng các lệnh như LPUSH để thêm phần tử vào đầu danh sách và RPUSH để thêm phần tử vào cuối danh sách, lệnh LPOP và RPOP được sử dụng để lấy phần tử từ đầu hoặc cuối danh sách.
  • Ví dụ để thêm công việc vào hàng đợi: 
r.lpush('tasks', 'task1')  # Thêm 'task1' vào đầu danh sách 'tasks'
r.rpush('tasks', 'task2')  # Thêm 'task2' vào cuối danh sách 'tasks'
  1. Sets (tập hợp): Sets là tập hợp các phần tử không trùng lặp và không có thứ tự. Redis hỗ trợ các thao tác như SADD để thêm phần tử vào tập hợp và SMEMBERS để lấy tất cả các phần tử trong tập hợp.
  • Ví dụ để lưu trữ các tag không trùng lặp:
r.sadd('tags', 'redis')    # Thêm 'redis' vào tập hợp 'tags'
r.sadd('tags', 'python')   # Thêm 'python' vào tập hợp 'tags'
tags = r.smembers('tags')  # Lấy tất cả các phần tử trong tập hợp 'tags'

# Các operations tập hợp
r.sinter('tags1', 'tags2')    # Giao của hai tập hợp
r.sunion('tags1', 'tags2')    # Hợp của hai tập hợp
r.sdiff('tags1', 'tags2')     # Hiệu của hai tập hợp
  1. Sorted Sets (tập hợp có thứ tự): là tập hợp các phần tử có điểm số (score) đi kèm, giúp Redis sắp xếp chúng theo thứ tự từ thấp đến cao (hoặc ngược lại). Các phần tử trong Sorted Set là duy nhất, nhưng scores có thể trùng lặp.
  • Ví dụ để tạo bảng xếp hạng:
r.zadd('leaderboard', {'player1': 100, 'player2': 150})  # Thêm người chơi vào bảng xếp hạng

# Lấy danh sách người chơi với điểm số từ thấp đến cao
leaderboard = r.zrange('leaderboard', 0, -1, withscores=True)
print(leaderboard)  # Output: [('player1', 100.0), ('player2', 150.0)]
  1. Hashes (Bảng băm): là kiểu dữ liệu giống như một bảng key-value, trong đó mỗi key có thể chứa nhiều cặp trường – giá trị, giúp lưu trữ các đối tượng phức tạp (như thông tin người dùng) hiệu quả.
  • Ví dụ để lưu thông tin người dùng:
r.hset('user:1', 'name', 'Alice')  # Thêm trường 'name' với giá trị 'Alice' vào bảng băm 'user:1'
r.hset('user:1', 'age', 30)        # Thêm trường 'age' với giá trị 30 vào bảng băm 'user:1'

user_info = r.hgetall('user:1')    # Lấy tất cả các trường và giá trị trong bảng băm 'user:1'
print(user_info)  # Output: {'name': 'Alice', 'age': '30'}
  1. Bitmaps (Bản đồ bit): là một cách lưu trữ các giá trị nhị phân (bit) giúp tiết kiệm bộ nhớ và xử lý dữ liệu với lượng lớn phần tử duy nhất. Thực chất, Bitmaps là một string được xử lý như một mảng bit. Bitmaps rất hữu ích trong các ứng dụng như đếm số lần sự kiện xảy ra hoặc theo dõi sự tồn tại của các phần tử. 
  • Ví dụ để đếm số lượng người dùng đã đăng ký:
r.setbit('user:active', 1234, 1)  # Đánh dấu người dùng với ID 1234 là đã hoạt động
is_active = r.getbit('user:active', 1234)  # Kiểm tra xem người dùng với ID 1234 có hoạt động không
print(is_active)  # Output: 1 (người dùng đã hoạt động)
  1. HyperLogLogs (Ước lượng số lượng phần tử duy nhất): là một cấu trúc dữ liệu dùng để ước tính số lượng phần tử duy nhất trong một tập hợp mà không cần lưu trữ tất cả các phần tử, giúp tiết kiệm bộ nhớ. Độ chính xác khoảng 99.19% với memory usage cố định 12KB.
  • Ví dụ để ước lượng số người dùng duy nhất đã truy cập vào trang:
r.pfadd('pageviews', 'user1', 'user2', 'user3')  # Thêm người dùng vào HyperLogLog
unique_users = r.pfcount('pageviews')            # Đếm số người dùng duy nhất
print(unique_users)  # Output: 3
  1. Geospatial Indexes (Chỉ mục địa lý): Redis hỗ trợ lưu trữ và truy vấn dữ liệu địa lý, cho phép làm việc với các vị trí địa lý như vĩ độ và kinh độ. Trên thực tế, tính năng này được triển khai thông qua Sorted Sets, giúp thực hiện các phép toán tìm kiếm và sắp xếp vị trí địa lý một cách hiệu quả.
  • Ví dụ để lưu vị trí của các cửa hàng:
r.geoadd('stores', (13.361389, 38.115556, 'store1'))  # Thêm cửa hàng vào với tọa độ
r.geoadd('stores', (15.087269, 37.502669, 'store2'))  # Thêm cửa hàng 2 vào với tọa độ

stores = r.georadius('stores', 15, 37, 100, unit='km')  # Lấy các cửa hàng trong bán kính 100km từ tọa độ (15, 37)
print(stores)  # Output: ['store2']
  1. Streams (Luồng): là kiểu dữ liệu được giới thiệu từ Redis 5.0, được thiết kế để xử lý các ứng dụng thời gian thực và lưu trữ các dòng dữ liệu không đồng bộ. Streams hỗ trợ nhóm người tiêu dùng (consumer groups) và hàng đợi tin nhắn bền vững (persistent message queues), giúp quản lý và xử lý dữ liệu theo dạng luồng một cách hiệu quả.
  • Ví dụ để gửi và nhận tin nhắn trong hệ thống chat:
r.xadd('chat:room1', {'user': 'Alice', 'message': 'Hello'})  # Thêm tin nhắn vào stream
messages = r.xread({'chat:room1': '0'})  # Đọc tin nhắn từ stream
print(messages)  # Output: [(['chat:room1'], [{'message': 'Hello', 'user': 'Alice'}])]

Redis có đảm bảo tính nhất quán (consistency) không?

Redis là một hệ thống cơ sở dữ liệu phân tán lưu trữ trong bộ nhớ; và như nhiều hệ thống phân tán khác, Redis không đảm bảo tính nhất quán (consistency) theo mô hình ACID truyền thống của các cơ sở dữ liệu quan hệ (RDBMS) như MySQL trong tất cả các tình huống. 

Thay vào đó, Redis cung cấp các cơ chế đảm bảo tính nhất quán phù hợp với từng use case cụ thể.:

  • Tính nhất quán trong các giao dịch (Transactions): Redis hỗ trợ các giao dịch thông qua các lệnh như MULTI, EXEC WATCH. Trong giao dịch, Redis đảm bảo rằng tất cả các lệnh được thực thi một cách nguyên tử (atomic), tức là các lệnh trong giao dịch sẽ được thực hiện đồng thời mà không bị gián đoạn bởi các lệnh khác. Tuy nhiên, Redis không hỗ trợ rollback khi giao dịch thất bại, do đó, nếu có lỗi trong giao dịch, toàn bộ giao dịch sẽ bị bỏ qua và không có cơ chế khôi phục như trong các hệ thống cơ sở dữ liệu quan hệ.
  • Tính nhất quán trong Redis Cluster: Redis Cluster hỗ trợ phân mảnh dữ liệu (sharding) và tự động phân phối dữ liệu giữa nhiều nút (nodes) trong cụm. Tuy nhiên, Redis Cluster không đảm bảo tính nhất quán hoàn toàn theo mô hình CAP theorem (Consistency, Availability, Partition tolerance). Redis Cluster chọn ưu tiên tính sẵn sàng (availability) và khả năng phân tán (partition tolerance) hơn là tính nhất quán tuyệt đối. Redis Cluster có thể trả về dữ liệu không hoàn toàn nhất quán trong một số tình huống như khi một nút không thể kết nối với cụm và việc ghi dữ liệu xảy ra trên một nút không đồng bộ.
  • Làm việc với các chế độ persistence: Redis cung cấp các chế độ RDB snapshots và AOF logs để lưu trữ dữ liệu một cách bền vững (persistence), giúp bảo vệ dữ liệu khỏi sự cố mất điện hoặc khởi động lại. Tuy nhiên, việc sử dụng các phương thức này không đảm bảo tính nhất quán tuyệt đối, vì có thể xảy ra mất mát dữ liệu trong các khoảng thời gian giữa các lần snapshot hoặc ghi lệnh AOF. Với tùy chọn AOF fsync=always, Redis có thể đảm bảo tính bền vững (durability) gần như tuyệt đối, nhưng điều này sẽ đi kèm với sự đánh đổi về hiệu suất. Trong khi đó, RDB snapshots có thể gây mất dữ liệu trong khoảng thời gian giữa các lần snapshot.
  • Tính nhất quán trong chế độ replication: Redis hỗ trợ replication, nghĩa là một bản sao (replica) của Redis master sẽ nhận dữ liệu từ master và đồng bộ hóa các thay đổi. Tuy nhiên, trong một số trường hợp, replica có thể không hoàn toàn đồng bộ với master, dẫn đến một số dữ liệu không nhất quán trong quá trình sao chép. Ngoài ra Redis cung cấp chế độ Asynchronous Replication, có nghĩa là các thay đổi trên master có thể được ghi vào replica một cách không đồng bộ, có thể dẫn đến thời gian trễ hoặc không đồng bộ dữ liệu trong một khoảng thời gian ngắn.

Hãy giải thích một số ưu – nhược điểm của Redis so với MongoDb

  • Ưu điểm của Redis so với MongoDB:
RedisMongoDb
Hiệu suất và tốc độRedis là một cơ sở dữ liệu lưu trữ trong bộ nhớ (in-memory), vì vậy nó có tốc độ truy xuất dữ liệu cực kỳ nhanh so với MongoDB, vốn sử dụng lưu trữ trên đĩa. Redis có thể xử lý hàng triệu thao tác mỗi giây, rất phù hợp cho các ứng dụng cần tốc độ cao, chẳng hạn như caching, quản lý phiên làm việc (session management) và xử lý dữ liệu thời gian thực.MongoDB, mặc dù cũng khá nhanh, nhưng việc truy xuất dữ liệu trên đĩa sẽ luôn chậm hơn so với Redis trong các thao tác chỉ sử dụng bộ nhớ (pure in-memory operations). Tuy nhiên MongoDB lại có khả năng xử lý các truy vấn phức tạp hiệu quả hơn Redis.
Kiểu dữ liệu phong phúRedis hỗ trợ nhiều kiểu dữ liệu phức tạp như strings, lists, sets, sorted sets, hashes, bitmaps, và hyperloglogs. Điều này giúp Redis rất linh hoạt trong việc xử lý các bài toán yêu cầu tốc độ cao và sự đa dạng trong các loại dữ liệu.MongoDB hỗ trợ cấu trúc tài liệu phong phú (rich document structure) với các đối tượng lồng nhau (nested objects), mảng (arrays) và các kiểu dữ liệu BSON. MongoDB cũng hỗ trợ dữ liệu địa lý (geospatial data), chỉ mục văn bản (text indexing) và GridFS để lưu trữ các tệp lớn. Tuy nhiên, MongoDB không có các cấu trúc dữ liệu chuyên biệt như Redis.
Sử dụng làm cachingRedis thường được sử dụng để cache dữ liệu nhờ vào tốc độ truy xuất dữ liệu nhanh và khả năng lưu trữ tạm thời trong bộ nhớ. Nếu bạn cần tăng tốc độ truy xuất dữ liệu từ các hệ thống cơ sở dữ liệu khác (như MongoDB), Redis là một lựa chọn tuyệt vời để làm bộ nhớ đệm.MongoDB không được tối ưu hóa cho caching, mặc dù nó hỗ trợ bộ nhớ đệm nhưng không hiệu quả bằng Redis trong các ứng dụng yêu cầu tốc độ truy xuất cực nhanh.
Quản lý phiên làm việc (session management)Redis là một lựa chọn tuyệt vời cho việc lưu trữ và quản lý session trong các ứng dụng web, do khả năng truy xuất dữ liệu cực nhanh và hỗ trợ tính năng persistence linh hoạt.MongoDB có thể sử dụng để lưu trữ session nhưng không thể cung cấp tốc độ và hiệu suất nhanh như Redis.
Tính phân tán và khả năng mở rộngRedis cung cấp Redis Cluster, cho phép phân mảnh dữ liệu và mở rộng theo chiều ngang (horizontal scaling), giúp dễ dàng mở rộng quy mô khi lượng dữ liệu và yêu cầu xử lý tăng cao.MongoDB cũng hỗ trợ tính năng phân tán, nhưng cấu hình và quản lý MongoDB Cluster có thể phức tạp hơn so với Redis.
  • Nhược điểm của Redis so với MongoDB:
RedisMongoDb
Khả năng lưu trữ lâu dài (Persistence)Redis mặc dù hỗ trợ các cơ chế persistence như RDB snapshots và AOF logs, nhưng dữ liệu chính trong Redis vẫn chủ yếu được lưu trữ trong bộ nhớ RAM. Điều này có thể dẫn đến mất dữ liệu trong trường hợp máy chủ gặp sự cố hoặc hết bộ nhớ.MongoDB là một cơ sở dữ liệu bền vững (persistent database), nơi tất cả dữ liệu được lưu trữ trên đĩa. Điều này đảm bảo rằng dữ liệu sẽ không bị mất ngay cả khi có sự cố với hệ thống.
Khả năng truy vấn phức tạpRedis không hỗ trợ các truy vấn phức tạp như MongoDB, đặc biệt là khi cần thực hiện các truy vấn điều kiện hoặc phân tích dữ liệu phức tạp. Redis chủ yếu phù hợp với các thao tác đơn giản như lưu trữ, lấy hoặc sửa dữ liệu theo key.MongoDB cung cấp khả năng truy vấn mạnh mẽ với các tính năng như aggregation framework, indexes và joins, giúp bạn dễ dàng truy vấn các dữ liệu phức tạp hoặc thực hiện các phép toán phức tạp trên dữ liệu.
Quản lý dữ liệu quan hệRedis không phải là cơ sở dữ liệu quan hệ, vì vậy nếu bạn cần quản lý các mối quan hệ giữa các bảng dữ liệu hoặc thực hiện các phép toán liên quan đến khóa ngoại (foreign keys) hay joins, Redis không phải là lựa chọn phù hợp.MongoDB hỗ trợ lưu trữ dữ liệu không có cấu trúc và có thể linh hoạt với các loại dữ liệu JSON, nhưng nếu cần quản lý dữ liệu quan hệ, RDBMS như MySQL hoặc PostgreSQL có thể là lựa chọn hợp lý hơn.
Tính năng phân tích dữ liệuRedis không được thiết kế để làm công cụ phân tích dữ liệu lớn hoặc lưu trữ các tập dữ liệu phức tạp. Redis thường được sử dụng trong các hệ thống có khối lượng dữ liệu nhỏ đến vừa phải, chủ yếu dùng để cache hoặc xử lý các tác vụ thời gian thực.MongoDB có thể lưu trữ và phân tích dữ liệu ở quy mô lớn với các tính năng như MapReduce, aggregation pipelines và full-text search. Điều này giúp MongoDB trở thành lựa chọn tốt cho các hệ thống cần phân tích dữ liệu hoặc lưu trữ dữ liệu không cấu trúc.

ZSET là gì trong Redis?

ZSET (Sorted Set) là một kiểu dữ liệu trong Redis, tương tự như SET (tập hợp), nhưng mỗi phần tử trong ZSET không chỉ có một giá trị (value) mà còn được gắn thêm một điểm số (score). Các phần tử trong ZSET được sắp xếp theo điểm số, từ thấp đến cao (hoặc ngược lại), và Redis tự động duy trì thứ tự này khi bạn thêm hoặc sửa đổi các phần tử.

  • Đặc điểm quan trọng của ZSET:
    • Members là duy nhất: Mỗi phần tử (member) trong ZSET chỉ có thể xuất hiện một lần, nhưng scores (điểm số) có thể trùng lặp.
    • Tự động sắp xếp: Redis sẽ tự động duy trì thứ tự sắp xếp khi ta thêm hoặc cập nhật phần tử trong ZSET.
    • Hiệu quả về độ phức tạp thời gian: Hầu hết các thao tác trên ZSET có độ phức tạp thời gian O(log N), giúp thực hiện nhanh chóng và hiệu quả.
    • Cách triển khai: Redis sử dụng kết hợp giữa hash table và skip list để đạt hiệu suất cao trong việc lưu trữ và truy xuất dữ liệu.
  • Ứng dụng của ZSET trong Redis:
    • Bảng xếp hạng (Leaderboard): Với điểm số (score) của từng người chơi, bạn có thể dễ dàng tạo và duy trì bảng xếp hạng trong các trò chơi hoặc ứng dụng cạnh tranh.
    • Lọc và xếp hạng dữ liệu: ZSET có thể được sử dụng để lọc và xếp hạng các dữ liệu như thời gian xử lý của một tác vụ, điểm số của người dùng trong ứng dụng, hoặc các sự kiện dựa trên thời gian.
    • Time-based data: Sử dụng timestamp (dấu thời gian) làm score để tạo timeline (dòng thời gian), lịch sự kiện (event scheduling) hoặc giới hạn tần suất yêu cầu (rate limiting windows).
    • Priority queues: Triển khai priority queue (hàng đợi ưu tiên) với score làm mức độ ưu tiên.
    • Range queries: Tìm kiếm dữ liệu trong một phạm vi điểm số (score range) hoặc phạm vi từ vựng (lexicographical range).
    • Social media features: Xây dựng các tính năng như trending posts (bài viết xu hướng), số lượng người theo dõi (follower counts) và activity feeds theo thời gian.
  • Ví dụ:
    • Thêm phần tử vào ZSET: ta dùng lệnh ZADD để thêm một hoặc nhiều phần tử vào ZSET và gán điểm số cho từng phần tử như sau:
r.zadd('leaderboard', {'player1': 1500, 'player2': 2000})
  • Lấy phần tử theo thứ tự điểm số: ta dùng lệnh ZRANGE để lấy các phần tử từ ZSET theo thứ tự điểm số từ thấp đến cao:
r.zrange('leaderboard', 0, -1, withscores=True)
  • Lấy phần tử theo thứ tự giảm dần: ta dùng lệnh ZREVRANGE để lấy các phần tử từ ZSET theo thứ tự điểm số từ cao xuống thấp:
r.zrevrange('leaderboard', 0, -1, withscores=True)
# Kết quả: [('player2', 2000), ('player1', 1500)]

Câu hỏi phỏng vấn Redis trung cấp

Đây là những câu hỏi dành cho ứng viên đã có kha khá kinh nghiệm làm việc thực tế với Redis.

Sự khác nhau giữa RDB và AOF trong Redis persistence và khi nào nên sử dụng mỗi phương pháp?

RDB (Redis Database snapshots)AOF (Append Only File)
Mô tảRDB là cơ chế lưu trữ snapshot của toàn bộ cơ sở dữ liệu Redis tại một thời điểm nhất định. Redis sẽ tạo ra một tệp RDB chứa bản sao dữ liệu của toàn bộ cơ sở dữ liệu dưới dạng nhị phân và được nén, giúp tiết kiệm dung lượng lưu trữ.AOF ghi lại tất cả các lệnh ghi vào Redis vào một tệp, nơi các lệnh được thêm vào cuối tệp theo dạng Redis protocol text format (định dạng văn bản của giao thức Redis). Mỗi khi có một thay đổi trong cơ sở dữ liệu, Redis ghi lệnh thay đổi vào cuối tệp AOF.
Cách hoạt độngRedis sẽ chụp lại dữ liệu tại các khoảng thời gian định kỳ hoặc khi một số điều kiện cụ thể được thoả mãn (ví dụ, sau một số lệnh ghi dữ liệu hoặc sau một khoảng thời gian nhất định). Trong khi đó, parent process vẫn tiếp tục phục vụ các client, trong khi child process sẽ thực hiện việc ghi snapshot vào ổ đĩa.AOF ghi lại tất cả các thao tác ghi dữ liệu trong Redis và sẽ tái tạo lại dữ liệu bằng cách phát lại các lệnh đã lưu trong tệp AOF khi Redis khởi động lại.
Ưu điểmHiệu suất cao: RDB cho phép Redis chạy nhanh hơn vì Redis không cần ghi mọi thay đổi vào đĩa ngay lập tức. Việc chụp dữ liệu định kỳ giúp giảm tải cho hệ thống.Tiết kiệm bộ nhớ: Vì chỉ lưu trữ dữ liệu tại các điểm snapshot, RDB giúp giảm bớt số lượng thao tác ghi đĩa.Phục hồi nhanh: Khi khôi phục từ RDB, Redis có thể tải dữ liệu từ một tệp duy nhất rất nhanh.Đảm bảo tính toàn vẹn dữ liệu: AOF giúp Redis duy trì tính toàn vẹn dữ liệu tốt hơn, vì mọi thay đổi được ghi ngay lập tức vào tệp AOF.Không có mất mát dữ liệu (nếu cấu hình hợp lý): Nếu được cấu hình để ghi lệnh theo mỗi lần thay đổi, AOF giúp giảm thiểu mất mát dữ liệu, vì dữ liệu được ghi liên tục vào tệp AOF.Có thể cấu hình linh hoạt: Redis hỗ trợ cấu hình thời gian ghi dữ liệu vào AOF như mỗi lần thay đổi, mỗi giây, hoặc thậm chí chỉ khi có thay đổi lớn.
Nhược điểmDữ liệu có thể bị mất: Nếu Redis gặp sự cố giữa các lần snapshot, dữ liệu có thể bị mất trong khoảng thời gian giữa các snapshot.Lưu trữ tạm thời: Quá trình tạo snapshot có thể gây gián đoạn hoạt động của Redis trong thời gian ngắn.Tốc độ chậm hơn: Ghi lệnh vào AOF liên tục có thể làm giảm hiệu suất của Redis so với RDB, vì phải ghi dữ liệu vào đĩa ngay lập tức.Tệp AOF có thể rất lớn: Vì AOF ghi lại từng thao tác, tệp AOF có thể trở nên rất lớn theo thời gian. Tuy nhiên, Redis hỗ trợ cơ chế rewrite để giảm kích thước của tệp AOF.Quá trình phục hồi có thể chậm: Việc phục hồi từ AOF có thể lâu hơn so với RDB, vì Redis phải phát lại tất cả các lệnh trong tệp AOF.
Khi nào nên sử dụng?Sử dụng RDB khi cần một cơ chế lưu trữ dữ liệu đơn giản, không yêu cầu độ bền vững tuyệt đối và có thể chấp nhận một lượng mất dữ liệu nhỏ (dữ liệu mất giữa các lần snapshot). Phù hợp cho các ứng dụng không yêu cầu độ chính xác cao về dữ liệu, hoặc các hệ thống có thể dễ dàng phục hồi lại từ bản sao lưu (backup).Khi cần tính bền vững dữ liệu cao và không muốn mất dữ liệu giữa các lần ghi, đặc biệt là khi ứng dụng của bạn không thể chấp nhận mất dữ liệu. Phù hợp cho các ứng dụng yêu cầu tính nhất quán cao, khi mỗi thay đổi dữ liệu cần được ghi nhận ngay lập tức và không thể có sự mất mát dữ liệu.

Giải thích data modeling trong Redis

Data modeling trong Redis không giống như các hệ thống cơ sở dữ liệu quan hệ truyền thống (RDBMS), vì Redis không sử dụng mô hình bảng hoặc mối quan hệ giữa các bảng. Thay vào đó, Redis cung cấp các kiểu dữ liệu key-value và các cấu trúc dữ liệu mạnh mẽ, cho phép xây dựng mô hình dữ liệu hiệu quả cho các ứng dụng yêu cầu tốc độ cao, xử lý dữ liệu thời gian thực và lưu trữ tạm thời.

Việc xây dựng mô hình dữ liệu trong Redis chủ yếu dựa trên việc chọn kiểu dữ liệu phù hợp với nhu cầu và mục đích cụ thể của ứng dụng. 

  1. Strings – mô hình đơn giản và nhanh chóng: là kiểu dữ liệu cơ bản trong Redis, có thể được sử dụng để lưu trữ giá trị đơn giản như chuỗi, số liệu thống kê hoặc token. Đây là kiểu dữ liệu linh hoạt và dễ sử dụng nhất. Redis có thể sử dụng kiểu dữ liệu này cho các trường hợp như cache hoặc thống kê đơn giản.
  2. Lists – lưu trữ dữ liệu theo thứ tự: là các tập hợp có thứ tự, và bạn có thể sử dụng chúng để xây dựng hàng đợi hoặc stack. Redis Lists lý tưởng cho các task queues hoặc message queues, khi các công việc cần được xử lý theo thứ tự (FIFO – First In, First Out).
  3. Sets – loại bỏ phần tử trùng lặp: giúp lưu trữ các phần tử không trùng lặp và Redis tự động đảm bảo không có phần tử nào lặp lại trong tập hợp. Kiểu dữ liệu này rất hữu ích khi bạn cần đảm bảo rằng không có các phần tử trùng lặp trong dữ liệu. Redis Sets rất phù hợp cho các trường hợp như bộ lọc dữ liệu duy nhất hoặc khi bạn cần lưu trữ các thuộc tính của người dùng mà không có sự trùng lặp.
  4. Sorted Sets – xếp hạng và tính toán theo điểm số: là sự kết hợp giữa Set với một điểm số (score) cho mỗi phần tử. Redis tự động sắp xếp các phần tử theo điểm số, giúp dễ dàng xây dựng các ứng dụng yêu cầu xếp hạng hoặc xử lý dữ liệu có thứ tự. Redis Sorted Sets lý tưởng cho các ứng dụng như bảng xếp hạng, đếm điểm, hoặc các hệ thống cần xử lý dữ liệu theo thứ tự điểm số.
  5. Hashes – lưu trữ dữ liệu có cấu trúc: Hashes trong Redis cho phép lưu trữ các cặp trường – giá trị (field – value) dưới một key duy nhất. Điều này làm cho Redis Hashes trở thành một công cụ lý tưởng để lưu trữ các dữ liệu có cấu trúc như thông tin người dùng hoặc cài đặt ứng dụng. Redis Hashes thường được sử dụng để lưu trữ thông tin người dùng, session data hoặc cấu hình ứng dụng.
  6. Streams – Xử lý dữ liệu theo luồng (Real-time): Streams trong Redis được thiết kế cho các ứng dụng xử lý thời gian thực. Streams cho phép lưu trữ và xử lý các dòng dữ liệu không đồng bộ, rất hữu ích trong các ứng dụng như chatbots, log processing hoặc real-time analytics. Redis Streams có thể được sử dụng trong các ứng dụng chat, gửi tin nhắn hoặc log streaming.

Hãy giải thích cách hoạt động của mô hình Pub/Sub trong Redis?

Pub/Sub (Publish/Subscribe) là một mô hình giao tiếp phổ biến trong các hệ thống phân tán, nơi một nhà xuất bản (publisher) gửi tin nhắn đến các kênh, và các người đăng ký (subscribers) nhận tin nhắn từ các kênh đó. Redis Pub/Sub cung cấp khả năng gửi nhận tin nhắn thời gian thực (real-time messaging) với đảm bảo gửi nhận tin nhắn thời gian thực.

  • Cách hoạt động của Redis Pub/Sub:
  1. Publisher (Nhà xuất bản): là ứng dụng hoặc thành phần trong hệ thống gửi các tin nhắn đến một hoặc nhiều kênh mà không cần biết có subscriber nào đang lắng nghe (listen) tin nhắn đó hay không.
  2. Subscriber (Người đăng ký): là ứng dụng hoặc thành phần trong hệ thống đăng ký vào một hoặc nhiều kênh để nhận các tin nhắn mà nhà xuất bản gửi đến trong thời gian thực.
  3. Channels (Kênh): là các “đường dẫn” mà các tin nhắn được gửi qua. Các subscriber phải đăng ký vào các kênh mà họ muốn nhận thông tin.
  • Ví dụ sử dụng Redis Pub/Sub:
    • Publisher (Gửi tin nhắn đến kênh news):
import redis

r = redis.Redis()

# Gửi tin nhắn đến kênh 'news'
r.publish('news', 'New Redis version is released!')
  • Subscriber (Nhận tin nhắn từ kênh news):
import redis

def subscriber():
    r = redis.Redis()

    # Đăng ký nhận tin nhắn từ kênh 'news'
    pubsub = r.pubsub()
    pubsub.subscribe('news')

    # Chờ và nhận tin nhắn
    for message in pubsub.listen():
        print(message)

# Chạy subscriber trong một thread riêng
subscriber()
  • Ứng dụng của Redis Pub/Sub:
    • Thông báo thời gian thực: Redis Pub/Sub có thể được sử dụng để gửi thông báo hoặc cập nhật trong các ứng dụng như chat hoặc thông báo tin tức.
    • Quản lý sự kiện: Các ứng dụng có thể sử dụng Redis Pub/Sub để đồng bộ hóa các sự kiện giữa các thành phần trong hệ thống mà không cần phải lưu trữ dữ liệu.

Giải thích cách Redis thực hiện replication giữa master và slave?

Replication trong Redis là một cơ chế cho phép sao chép dữ liệu từ một Redis master (máy chủ chính) sang một hoặc nhiều Redis slave (máy chủ phụ). Từ Redis 5.0+, thuật ngữ “slave” đã được thay thế bằng “replica” để tránh ngôn từ không phù hợp. Đây là một tính năng quan trọng giúp tăng cường tính sẵn sàng và tính chịu lỗi cho hệ thống Redis, đồng thời hỗ trợ phân tán dữ liệu và quản lý tải cho các ứng dụng yêu cầu hiệu suất cao.

  • Cách thức hoạt động của Redis replication:
  1. Master-Slave Replication:
  • Master: Đây là Redis instance chứa dữ liệu gốc. Nó xử lý tất cả các thao tác ghi (write operations) và đồng bộ hóa dữ liệu với các slave.
  • Slave: Các Redis instance này nhận dữ liệu từ master và lưu trữ một bản sao (replica) của dữ liệu. Các slave chỉ thực hiện các thao tác đọc (read operations). Khi dữ liệu trên master thay đổi, slave sẽ tự động nhận bản cập nhật và duy trì tính đồng bộ.
  1. Quá trình replication: Khi Redis master khởi động hoặc khi một slave được thêm vào, master sẽ gửi bản sao của dữ liệu (snapshot) đến slave. Sau đó, master tiếp tục gửi các lệnh ghi (write commands) mới đến các slave. Slave nhận các lệnh này và thực thi chúng một cách tương tự như master, đảm bảo rằng dữ liệu trên slave luôn đồng bộ với master. Redis sử dụng Asynchronous Replication, nghĩa là việc sao chép dữ liệu từ master đến slave diễn ra không đồng bộ, giúp giảm thiểu độ trễ trong quá trình sao chép. Tuy nhiên, điều này cũng có thể dẫn đến việc có một khoảng trễ nhỏ giữa master và slave.
  2. Quá trình kết nối: Để thực hiện replication, slave cần được kết nối tới master. Điều này có thể được cấu hình trong file cấu hình Redis hoặc thông qua lệnh SLAVEOF. Khi slave kết nối tới master lần đầu, master sẽ gửi toàn bộ dữ liệu hiện có đến slave. Sau đó, mọi thao tác ghi trên master sẽ được gửi đến slave để đảm bảo tính đồng bộ.

* Lệnh SLAVEOF giúp cấu hình replication, trong đó slave kết nối và sao chép dữ liệu từ master. Sau khi lệnh này được thực thi, Redis slave sẽ bắt đầu sao chép dữ liệu từ master và tiếp tục nhận các thay đổi sau đó.

SLAVEOF <master-ip> <master-port>
  1. Failover và Chuyển đổi master: Trong trường hợp master gặp sự cố (failure), Redis không tự động chuyển đổi một slave thành master. Tuy nhiên, Redis Sentinel có thể được sử dụng để giám sát và tự động chuyển đổi master (automatic failover). Redis Sentinel sẽ phát hiện sự cố và tự động cấu hình một slave thành master mới, giúp đảm bảo tính sẵn sàng cao cho hệ thống.
  2. Replication với nhiều slave: Redis cho phép nhiều slave kết nối với một master. Điều này giúp tăng khả năng đọc cho hệ thống vì các slave có thể xử lý các yêu cầu đọc mà không làm tăng tải cho master. Các slave này sẽ tự động nhận các thay đổi từ master và duy trì bản sao đồng bộ.

Làm sao để triển khai một hệ thống giới hạn tần suất (rate limiter) đơn giản bằng Redis?

Một hệ thống giới hạn tần suất đơn giản có thể được triển khai bằng Redis theo các bước sau:

  1. Sử dụng một key trong Redis để đại diện cho mỗi người dùng hoặc địa chỉ IP.
  2. Đặt giá trị của key là số lần yêu cầu đã thực hiện.
  3. Sử dụng lệnh INCR để tăng giá trị của counter mỗi khi có yêu cầu.
  4. Thiết lập thời gian hết hạn (expiration time) cho key bằng cách sử dụng lệnh EXPIRE.
  5. Kiểm tra giá trị counter trước khi cho phép mỗi yêu cầu được thực hiện.

Cách triển khai này cho phép bạn giới số lượng yêu cầu trong một khoảng thời gian cụ thể. Ví dụ, ta có thể giới hạn mỗi người dùng chỉ được phép gửi tối đa 100 yêu cầu mỗi giờ.

Sự khác biệt giữa SETNX, SETEX, GETSET là gì?

  1. SETNX (Set if Not Exists): là viết tắt của SET if Not Exists, có nghĩa là chỉ thiết lập giá trị cho key nếu key đó chưa tồn tại. Nếu key đã tồn tại, lệnh sẽ không thực hiện thay đổi gì và trả về 0. Nếu key chưa tồn tại, lệnh sẽ tạo mới key và gán giá trị, trả về 1. Lệnh này thường được sử dụng trong các tình huống như locking (khóa để tránh ghi đè), ví dụ khi cần đảm bảo rằng một thao tác chỉ được thực hiện nếu key chưa tồn tại. Ví dụ:
r.setnx('username', 'Alice')  # Chỉ thiết lập giá trị 'Alice' cho key 'username' nếu key chưa tồn tại
  1. SETEX (Set with Expiry): là lệnh để thiết lập giá trị cho key và thiết lập thời gian sống (TTL – Time to Live) cho key đó, tức là key sẽ tự động hết hạn sau một khoảng thời gian nhất định (tính bằng giây). Lệnh này yêu cầu chỉ định thời gian sống cho key. SETEX được sử dụng trong các ứng dụng cần phiên làm việc (session) hoặc cache có thời gian sống hạn chế.
r.setex('session:abc123', 3600, 'user:42')  # Lưu giá trị 'user:42' vào key 'session:abc123' với thời gian sống 3600 giây (1 giờ)
  1. GETSET (Get and Set): là lệnh để lấy giá trị của một key và cùng lúc thiết lập giá trị mới cho key đó. Lệnh này trả về giá trị cũ của key trước khi bị thay đổi. GETSET rất hữu ích trong các tình huống cần phải cập nhật giá trị của key đồng thời với việc lấy giá trị hiện tại, chẳng hạn như counter hoặc bộ đếm lượt truy cập.
old_value = r.getset('counter', 100)  # Lấy giá trị cũ của 'counter' và thiết lập giá trị mới là 100
print(old_value)  # In ra giá trị cũ trước khi thay đổi

Tóm lại, mặc dù ba lệnh này đều thực hiện việc thiết lập giá trị cho một key, nhưng chúng phục vụ những mục đích khác nhau:

  • SETNX: Thiết lập giá trị chỉ khi key chưa tồn tại.
  • SETEX: Thiết lập giá trị và cùng lúc thiết lập thời gian sống cho key.
  • GETSET: Lấy giá trị hiện tại của key và đồng thời thay đổi giá trị của key đó.

Cách nào xử lý race condition trong Redis?

Race condition là tình huống mà nhiều client hoặc tiến trình cùng lúc truy cập và sửa đổi dữ liệu mà không có sự đồng bộ, dẫn đến kết quả không chính xác. Trong Redis, WATCH, MULTI EXEC là các công cụ giúp xử lý race condition trong các giao dịch (transactions), đảm bảo rằng các thao tác sẽ được thực hiện nguyên tử và không bị can thiệp bởi các lệnh từ các client khác trong suốt quá trình.

  1. WATCH: được sử dụng để giám sát một hoặc nhiều key. Nếu một key được thay đổi trong quá trình thực thi giao dịch (sau khi gọi WATCH và trước khi gọi EXEC), giao dịch sẽ thất bại. Lệnh này giúp kiểm tra xem một key có bị thay đổi bởi client khác trước khi thực hiện các thao tác quan trọng. Nếu không có sự thay đổi nào, giao dịch sẽ tiếp tục. Ví dụ:
r.watch('counter')  # Giám sát key 'counter'

current_value = r.get('counter')  # Lấy giá trị hiện tại

# Nếu 'counter' không bị thay đổi, thực hiện giao dịch
pipe = r.pipeline()
pipe.incr('counter')
pipe.execute()
  1. MULTI: để bắt đầu một giao dịch trong Redis. Từ khi gọi lệnh MULTI, tất cả các lệnh Redis sẽ được buffered (tạm thời lưu lại) và không thực thi cho đến khi lệnh EXEC được gọi. Điều này giúp đảm bảo rằng các thao tác bên trong giao dịch sẽ được thực hiện nguyên tử, tức là không bị gián đoạn bởi các client khác. Lệnh này thường được sử dụng để nhóm các lệnh lại với nhau và thực thi chúng trong một giao dịch duy nhất. Ví dụ:
pipe = r.pipeline()
pipe.multi()  # Bắt đầu giao dịch

pipe.set('key1', 'value1')  # Lệnh 1 trong giao dịch
pipe.set('key2', 'value2')  # Lệnh 2 trong giao dịch

pipe.execute()  # Thực thi tất cả lệnh trong giao dịch
  1. EXEC: là lệnh để thực thi tất cả các lệnh trong giao dịch đã bắt đầu với MULTI. Nếu có bất kỳ thay đổi nào đối với các key giám sát (WATCH), giao dịch sẽ không được thực thi và sẽ trả về lỗi. Nếu không có thay đổi, tất cả các lệnh trong giao dịch sẽ được thực hiện và Redis sẽ trả về kết quả của các lệnh. Lệnh này thường được dùng để hoàn tất và thực thi giao dịch mà bạn đã bắt đầu với MULTI. Ví dụ:
r.watch('counter')  # Giám sát 'counter'

# Bắt đầu giao dịch
pipe = r.pipeline()
pipe.multi()

pipe.incr('counter')  # Tăng giá trị của 'counter'
pipe.execute()  # Thực thi giao dịch
Giải thích: Lệnh pipe.execute() thực thi tất cả các lệnh trong giao dịch. Nếu trong quá trình đó key 'counter' bị thay đổi, giao dịch sẽ bị thất bại.

Giải thích transactions và atomicity của Redis. 

  • Transactions: 

Trong Redis cho phép nhóm một loạt các lệnh lại với nhau và thực thi chúng một cách nguyên tử (atomic). Quan trọng là Redis transactions đảm bảo tính isolation (cách ly), tức là không có client nào khác có thể thực thi lệnh trong khi giao dịch đang được xử lý. Tuy nhiên, Redis không đảm bảo atomicity theo kiểu “all-or-nothing” như các hệ quản trị cơ sở dữ liệu quan hệ (RDBMS) truyền thống. Redis hỗ trợ transactions thông qua ba lệnh chính: 

  • MULTI: Lệnh này bắt đầu một giao dịch trong Redis. Sau khi gọi lệnh MULTI, tất cả các lệnh Redis được thực thi sẽ được buffered (lưu trữ tạm thời) và không thực thi ngay lập tức.
  • EXEC: Lệnh này thực thi tất cả các lệnh đã được nhóm vào giao dịch bắt đầu với MULTI. Khi gọi EXEC, tất cả các lệnh trong giao dịch sẽ được thực thi nguyên tử, tức là tất cả các lệnh sẽ được thực hiện thành công hoặc sẽ không có lệnh nào được thực thi nếu có lỗi.
  • DISCARD: Lệnh này hủy bỏ giao dịch và tất cả các lệnh đã được nhóm vào giao dịch sẽ không được thực thi.
  • WATCH: Lệnh này cho phép giám sát một hoặc nhiều key. Nếu bất kỳ key nào được WATCH bị thay đổi trước khi EXEC được gọi, toàn bộ transaction sẽ bị hủy bỏ.
  • Atomicity trong Redis Transactions:

Atomicity trong Redis đảm bảo rằng một giao dịch sẽ được thực thi hoàn toàn hoặc không có gì cả. Khi ta gọi MULTI, các lệnh trong giao dịch sẽ không được thực thi ngay lập tức. Thay vào đó, các lệnh sẽ được buffered và Redis sẽ chỉ thực thi chúng khi ta gọi EXEC. Redis đảm bảo rằng khi EXEC được gọi, tất cả các lệnh trong giao dịch sẽ được thực thi mà không có sự can thiệp từ các client khác.

Atomicity có nghĩa là tất cả các lệnh trong giao dịch sẽ được thực thi trong một lượt mà không bị gián đoạn. Nếu có bất kỳ thay đổi nào trên các key mà giao dịch sử dụng, giao dịch đó sẽ bị hủy và không có lệnh nào được thực thi.

  • Ví dụ:
import redis

r = redis.Redis()

# Bắt đầu giao dịch
pipe = r.pipeline()
pipe.multi()  # Gọi MULTI để bắt đầu giao dịch

# Các lệnh trong giao dịch
pipe.set('user:name', 'Alice')
pipe.set('user:age', 30)

# Thực thi giao dịch
pipe.execute()  # Tất cả các lệnh trong giao dịch sẽ được thực thi nguyên tử

Redis có hỗ trợ rollback không?

Redis không hỗ trợ rollback theo cách mà các hệ quản trị cơ sở dữ liệu quan hệ (RDBMS) làm. 

Rollback là quá trình quay lại trạng thái ban đầu nếu một thao tác không thành công trong khi thực hiện một giao dịch. Trong các cơ sở dữ liệu quan hệ như MySQL, rollback là một phần quan trọng của giao dịch, cho phép phục hồi lại dữ liệu trước khi thực hiện thao tác khi có lỗi xảy ra. 

Tại sao Redis không hỗ trợ rollback?

Redis sử dụng cơ chế transactions đơn giản với các lệnh MULTI, EXEC và DISCARD. Khi bắt đầu một giao dịch với MULTI, các lệnh sẽ không được thực thi ngay mà chỉ được buffered cho đến khi ta gọi EXEC để thực thi chúng. Nếu có bất kỳ thay đổi nào trên key trong quá trình giao dịch (bởi các client khác), giao dịch sẽ bị hủy và không có lệnh nào được thực thi. Tuy nhiên, Redis không cung cấp cơ chế để hoàn tác (rollback) các thao tác đã thực hiện trong giao dịch, nếu chúng đã được thực thi một phần.

Các tình huống Redis không hỗ trợ rollback

  • Nếu bạn thực thi một lệnh set trong giao dịch và một lỗi xảy ra trong các lệnh sau, Redis sẽ không quay lại và hoàn tác lệnh set đã thực hiện.
  • Redis không lưu trữ lịch sử các thao tác và không có cơ chế để phục hồi trạng thái trước đó nếu có lỗi, ngoài việc hủy bỏ giao dịch nếu phát hiện có thay đổi không mong muốn trong các key giám sát (WATCH).

Giải pháp thay thế trong Redis:

Mặc dù Redis không hỗ trợ rollback, ta có thể quản lý lỗi và bảo vệ tính nhất quán dữ liệu bằng một số cách sau:

  • WATCH: Sử dụng lệnh WATCH để giám sát các key và phát hiện sự thay đổi của chúng trong quá trình giao dịch. Nếu một key bị thay đổi, giao dịch sẽ không được thực thi và bạn có thể quyết định xử lý lại từ đầu.
  • Application-Level Rollback: Trong trường hợp giao dịch không thành công hoặc cần phải quay lại trạng thái ban đầu, ta có thể thực hiện rollback ở cấp độ ứng dụng. Ví dụ, lưu trữ các trạng thái trước khi thay đổi và khôi phục lại khi có lỗi.

Ví dụ sử dụng WATCH để bảo vệ giao dịch:

r.watch('account:balance')

# Lấy giá trị ban đầu
current_balance = r.get('account:balance')

# Bắt đầu giao dịch
pipe = r.pipeline()
pipe.multi()

# Thực hiện thay đổi
pipe.decr('account:balance', 100)  # Giảm số dư tài khoản

# Thực thi giao dịch nếu không có thay đổi trong quá trình giám sát
pipe.execute()

=> Trong ví dụ trên, nếu giá trị của account:balance bị thay đổi trong quá trình giao dịch, Redis sẽ hủy bỏ giao dịch và không thực thi lệnh decr.

Câu hỏi phỏng vấn Redis nâng cao

Redis Cluster là gì? Cách hoạt động của phân mảnh (sharding) trong Redis Cluster?

Redis Cluster là một tính năng trong Redis cho phép phân tán dữ liệu qua nhiều Redis instance (gọi là nodes) trong một hệ thống phân tán. Redis Cluster giúp mở rộng Redis theo chiều ngang, cải thiện khả năng xử lý và khả năng chịu lỗi (fault tolerance). Redis Cluster cung cấp khả năng phân mảnh dữ liệu (sharding), tự động chia dữ liệu và lưu trữ chúng trên nhiều node khác nhau, đồng thời hỗ trợ replication để đảm bảo tính sẵn sàng và độ tin cậy.

Cấu trúc của Redis Cluster:

  • Nodes (Các node): Redis Cluster bao gồm nhiều Redis nodes (mỗi node là một Redis instance độc lập). Cluster tối thiểu cần 3 master nodes để hoạt động. Mỗi node có thể đóng vai trò master hoặc replica. Mỗi node lưu trữ một phần của dữ liệu và có thể thực hiện các thao tác như đọc và ghi dữ liệu.
  • Master-Slave Replication: Trong Redis Cluster, mỗi master node có thể có một hoặc nhiều slave node để replicate dữ liệu và đảm bảo tính sẵn sàng cao. Nếu một master node bị lỗi, một slave node có thể được nâng cấp thành master mới (failover).
  • Cluster Manager: Redis Cluster tự động quản lý và giám sát các node, phát hiện lỗi và thực hiện failover khi có sự cố. Mỗi node trong cluster giao tiếp với nhau thông qua Cluster Bus (port = redis_port + 10000) để trao đổi thông tin về cluster state, heartbeat, và failure detection.

Phân mảnh (Sharding) trong Redis Cluster

Phân mảnh là một kỹ thuật chia nhỏ dữ liệu và phân phối chúng qua nhiều node. Redis Cluster sử dụng một kỹ thuật phân mảnh đặc biệt gọi là hash slot để chia và phân phối dữ liệu.

  • Hash Slots: Redis Cluster chia dữ liệu thành 16384 slots. Mỗi key trong Redis Cluster được hash (băm) và gán vào một trong 16384 hash slots này. Các node trong Redis Cluster sẽ chịu trách nhiệm quản lý một số lượng cụ thể của các hash slots. Ví dụ nếu Redis Cluster có 3 nodes, các hash slots sẽ được phân phối cho từng node. Một node có thể quản lý một số hash slots, trong khi các node khác quản lý các hash slots còn lại.
  • Hashing và Sharding: Khi lưu trữ một key trong Redis Cluster, Redis sẽ băm key và xác định hash slot của key đó. Sau đó, key sẽ được lưu trữ trong node quản lý hash slot đó. Ví dụ nếu Redis Cluster có 3 nodes, Redis sẽ băm key “user:1000” và xác định node nào sẽ lưu trữ key này dựa trên hash slot của nó. Đối với multi-key operations, Redis sử dụng hash tags để đảm bảo các related keys nằm trong cùng một slot. Ví dụ: “{user:1000}:profile” và “{user:1000}:settings” sẽ cùng nằm trong một slot.
  • Key Slot Mapping: Mỗi node trong Redis Cluster quản lý một hoặc nhiều hash slots. Khi một client gửi yêu cầu đến Redis Cluster, nếu key không thuộc về node hiện tại, Redis sẽ trả về thông báo MOVED redirection với địa chỉ của node chính xác. Client sẽ lưu trữ thông tin về cấu trúc cluster (cluster topology) và gửi yêu cầu trực tiếp đến node đúng, giúp tránh việc phải thực hiện redirection.

Ví dụ: Giả sử ta có một Redis Cluster với 3 nodes và 16384 slots. Redis sẽ chia các slots này cho 3 nodes. Nếu key “user:1000” thuộc về hash slot 5000, key này sẽ được lưu trữ trong node 1. Nếu bạn thực hiện một thao tác với key “user:1000”, Redis Cluster sẽ tự động chuyển yêu cầu đến node 1. Sự phân phối dữ liệu qua các node như sau:

  • Node 1: Quản lý các slots từ 0 đến 5000
  • Node 2: Quản lý các slots từ 5001 đến 10000
  • Node 3: Quản lý các slots từ 10001 đến 16383

Redis Sentinel là gì? Nó giải quyết vấn đề gì?

Redis Sentinel là một hệ thống giám sát và quản lý Redis, được thiết kế để đảm bảo tính sẵn sàng cao và khả năng phục hồi cho các hệ thống Redis. Redis Sentinel cung cấp các tính năng như giám sát, cảnh báo và failover tự động cho Redis, giúp đảm bảo rằng Redis luôn hoạt động ổn định và không bị gián đoạn ngay cả khi có sự cố xảy ra.

  • Các chức năng chính của Redis Sentinel:
  • Giám sát (Monitoring): Redis Sentinel giám sát tất cả các Redis instances (bao gồm cả master và slave nodes) trong hệ thống để đảm bảo rằng chúng hoạt động đúng cách. Sentinel kiểm tra các node Redis theo chu kỳ và phát hiện các sự cố như kết nối bị gián đoạn hoặc Redis node không phản hồi.
  • Failover tự động (Automatic Failover): Khi Redis Sentinel phát hiện rằng master node không hoạt động (do lỗi hoặc không phản hồi), nó sẽ tự động chuyển đổi (failover) một slave node thành master mới. Quá trình failover giúp đảm bảo rằng dịch vụ Redis vẫn có thể hoạt động liên tục mà không gặp gián đoạn.
  • Cảnh báo (Notification): Redis Sentinel có thể gửi thông báo cho người quản trị hệ thống khi có sự cố xảy ra, chẳng hạn như master node không phản hồi, failover được thực hiện hoặc có sự thay đổi trong cấu trúc Redis Cluster.

Redis Sentinel giải quyết vấn đề gì?

  • Tính sẵn sàng cao (High Availability): Redis Sentinel giúp hệ thống Redis có thể chạy liên tục mà không bị gián đoạn. Khi master node gặp sự cố, Sentinel sẽ tự động chuyển đổi một slave node thành master, giúp hệ thống Redis luôn hoạt động ổn định mà không cần sự can thiệp thủ công.
  • Khả năng phục hồi sau lỗi (Fault Tolerance): Với Redis Sentinel, hệ thống có thể phục hồi nhanh chóng sau các sự cố. Việc failover tự động đảm bảo rằng Redis luôn có một master node hoạt động, ngay cả khi một node bị lỗi, giúp giảm thiểu thời gian chết (downtime) của hệ thống.
  • Quản lý phân tán dễ dàng: Redis Sentinel cung cấp cơ chế giám sát phân tán cho các node Redis trong một hệ thống phân tán, giúp người quản trị dễ dàng quản lý và duy trì tính ổn định cho các hệ thống Redis lớn với nhiều node master và slave.
  • Không yêu cầu can thiệp thủ công: Redis Sentinel tự động phát hiện sự cố và thực hiện failover mà không cần sự can thiệp thủ công từ người quản trị, giúp tiết kiệm thời gian và giảm thiểu rủi ro do lỗi của con người.

Bạn sẽ tiếp cận việc di chuyển dữ liệu hoặc thay đổi schema trong một ứng dụng Redis như thế nào?

Việc di chuyển dữ liệu hoặc thay đổi schema trong Redis yêu cầu kế hoạch cẩn thận do Redis không có schema cố định. Một phương pháp tổng quát có thể bao gồm các bước sau:

  1. Thiết kế cấu trúc dữ liệu hoặc schema mới: Xác định cách thức dữ liệu sẽ được lưu trữ trong Redis sau khi thay đổi schema.
  2. Viết script di chuyển dữ liệu: Tạo script để chuyển đổi dữ liệu hiện tại sang cấu trúc mới.
  3. Kiểm tra quy trình di chuyển dữ liệu kỹ lưỡng trong môi trường staging: Đảm bảo rằng quy trình di chuyển sẽ không gặp phải vấn đề khi thực hiện trên môi trường sản xuất.
  4. Chọn chiến lược di chuyển: Có thể là di chuyển offline, di chuyển online với ghi dữ liệu đồng thời (double-writing) hoặc di chuyển dần dần.
  5. Thực thi di chuyển: Có thể sử dụng Lua scripts trong Redis để đảm bảo tính nguyên tử (atomicity) trong quá trình di chuyển.
  6. Cập nhật mã nguồn ứng dụng để làm việc với cấu trúc mới.
  7. Xác minh tính toàn vẹn dữ liệu sau khi di chuyển.

Để có thể gây ấn tượng hơn với nhà tuyển dụng, bạn có thể thảo luận về các yếu tố cần xem xét như:

  • Giảm thiểu thời gian gián đoạn (downtime) trong quá trình di chuyển dữ liệu.
  • Đảm bảo tính nhất quán của dữ liệu trong suốt quá trình di chuyển.
  • Xử lý tình huống rollback khi di chuyển không thành công.
  • Quản lý việc sử dụng bộ nhớ trong quá trình di chuyển.

Redis Streams là gì?

Redis Streams là một kiểu dữ liệu mới được giới thiệu trong Redis 5.0, được thiết kế để xử lý dữ liệu theo luồng (streaming data). Redis Streams là một công cụ mạnh mẽ cho các ứng dụng yêu cầu xử lý dữ liệu theo thời gian thực (real-time data) hoặc dữ liệu luồng (streaming data), như xử lý log (log processing), hàng đợi tin nhắn (message queuing), lưu trữ sự kiện (event sourcing),  phân tích thời gian thực (real-time analytics), lưu trữ sự kiện (event sourcing) với hỗ trợ delivery guarantees (đảm bảo giao hàng tin nhắn) cùng với xử lý phân tán (distributed processing).

Redis Streams cho phép lưu trữ, truy vấn và quản lý các luồng dữ liệu theo một cách rất hiệu quả. Nó cung cấp khả năng ghi và đọc dữ liệu theo thứ tự, với tính năng lưu trữ thông tin về các sự kiện (messages) trong mỗi luồng và cho phép nhiều người tiêu dùng (consumers) xử lý dữ liệu cùng lúc.

Các thành phần cơ bản trong Redis Streams:

  • Xử lý theo ID: Mỗi mục trong Redis Stream (gọi là một message) có một ID duy nhất. ID này thường được định dạng là timestamp-sequence, giúp đảm bảo rằng các message được lưu trữ theo thứ tự thời gian.
  • Stream: Một stream trong Redis là một danh sách các messages (mục tin nhắn), mỗi message có một ID và một số fields (trường dữ liệu). Redis Streams hỗ trợ các thao tác như truy vấn, lọc và xử lý các message trong stream một cách dễ dàng.
  • Consumer Groups: Redis Streams hỗ trợ consumer groups, cho phép nhiều người tiêu dùng (consumer) cùng xử lý các message trong một stream, và mỗi message chỉ được xử lý một lần trong mỗi nhóm người tiêu dùng. Điều này rất hữu ích khi có một số lượng lớn các consumer mà vẫn muốn phân phối công việc giữa các worker một cách hiệu quả.

Nếu Redis crash, dữ liệu có mất không? Làm sao để giảm thiểu rủi ro này?

Redis là một cơ sở dữ liệu lưu trữ trong bộ nhớ (in-memory database), điều này có nghĩa là dữ liệu được lưu trữ trực tiếp trong RAM, mang lại tốc độ truy xuất dữ liệu cực nhanh. Tuy nhiên, điểm yếu của Redis là khi hệ thống crash (sập hệ thống) hoặc mất điện, dữ liệu trong RAM sẽ bị mất trừ khi Redis có cơ chế sao lưu và lưu trữ dữ liệu.

Để giải quyết vấn đề này và giảm thiểu rủi ro mất dữ liệu, Redis cung cấp một số cơ chế lưu trữ bền vững (persistence) mà ta có thể sử dụng tùy vào yêu cầu của ứng dụng.

  • RDB (Redis Database Backup): RDB là cơ chế chụp nhanh (snapshot) dữ liệu của Redis tại một thời điểm cụ thể. Redis sẽ lưu lại toàn bộ trạng thái của bộ nhớ vào một file (mặc định là dump.rdb). 
  • AOF (Append Only File): AOF ghi lại mỗi lệnh ghi (write) mà Redis thực hiện vào một file (mặc định là appendonly.aof). Khi Redis restart, nó sẽ đọc lại lệnh từ file AOF và tái tạo lại toàn bộ trạng thái dữ liệu.
  • Hybrid Persistence (Sự kết hợp giữa RDB và AOF): Redis hỗ trợ kết hợp cả RDB và AOF để đạt được hiệu suất cao và bảo vệ dữ liệu tốt nhất. Khi sử dụng cả hai cơ chế này, Redis sẽ chụp nhanh dữ liệu bằng RDB theo định kỳ và đồng thời ghi các lệnh vào AOF.

Các cách giảm thiểu rủi ro mất dữ liệu khi Redis crash

  1. Sử dụng AOF với chế độ everysec: Chế độ everysec ghi dữ liệu vào AOF mỗi giây, giúp giảm thiểu mất mát dữ liệu nếu Redis crash
  2. Sao lưu dữ liệu theo định kỳ với RDB: Cấu hình Redis để thực hiện sao lưu theo định kỳ sẽ giúp bạn bảo vệ dữ liệu mà không tiêu tốn quá nhiều tài nguyên.
  3. Sử dụng Redis Cluster với replication: Redis Cluster hỗ trợ replication (sao chép dữ liệu), giúp bạn sao lưu dữ liệu từ master node sang các slave nodes. Nếu một master node gặp sự cố, một slave node có thể trở thành master, giúp duy trì khả năng truy cập và bảo vệ dữ liệu.

Bạn đã sử dụng Redis với bất kỳ hệ thống hàng đợi nào chưa? Nếu có, vui lòng giải thích cách sử dụng.

1. Redis làm hàng đợi với Lists

Một trong những cách phổ biến nhất để sử dụng Redis trong hệ thống hàng đợi là sử dụng lists. Redis lists cho phép bạn thực hiện các thao tác kiểu FIFO (First In, First Out) hoặc LIFO (Last In, First Out), tùy vào cách bạn sử dụng các lệnh LPUSH, RPUSH, LPOPRPOP.

  • LPUSHRPUSH: Thêm phần tử vào đầu (LPUSH) hoặc cuối (RPUSH) danh sách.
  • LPOPRPOP: Lấy và loại bỏ phần tử ở đầu (LPOP) hoặc cuối (RPOP) danh sách.

Ví dụ để thêm công việc vào hàng đợi:

r.lpush('task_queue', 'task1')  # Thêm công việc vào đầu hàng đợi
r.rpush('task_queue', 'task2')  # Thêm công việc vào cuối hàng đợi

Ví dụ để lấy và xử lý công việc từ hàng đợi:

task = r.lpop('task_queue')  # Lấy công việc từ đầu hàng đợi
print(f"Processing {task}")

2. Redis với Pub/Sub cho hệ thống hàng đợi

Redis cũng hỗ trợ mô hình Publish/Subscribe (Pub/Sub), có thể được sử dụng để xây dựng các hệ thống hàng đợi và thông báo theo thời gian thực.

  • Publisher: Gửi thông điệp (message) đến một kênh.
  • Subscriber: Nhận các thông điệp từ các kênh đã đăng ký.

Ví dụ:

# Publisher (Gửi tin nhắn)
r.publish('task_channel', 'task1')  # Gửi công việc tới kênh task_channel

# Subscriber (Nhận tin nhắn)
pubsub = r.pubsub()
pubsub.subscribe('task_channel')  # Đăng ký nhận tin nhắn từ kênh task_channel
for message in pubsub.listen():
    print(f"Received {message['data']}")

3. Sử dụng Redis với Job Queues (Hàng đợi công việc)

Trong các hệ thống xử lý công việc nền (background job processing), Redis thường được sử dụng để quản lý các job queues. Hệ thống này cho phép gửi các công việc vào hàng đợi và sau đó các worker sẽ lấy và xử lý chúng một cách hiệu quả. Các framework như RQ (Redis Queue) và Celery sử dụng Redis như một backend để quản lý các công việc.

  • Ví dụ để thêm công việc vào hàng đợi:
from redis import Redis
from rq import Queue

redis_conn = Redis()
q = Queue(connection=redis_conn)  # Tạo hàng đợi
job = q.enqueue(my_task_function)  # Thêm công việc vào hàng đợi
  • Ví dụ để worker xử lý công việc:
from rq import Worker

worker = Worker([q])  # Worker xử lý công việc từ hàng đợi q
worker.work()

Tổng kết

Việc nắm vững các câu hỏi phỏng vấn Redis, từ các tính năng cơ bản đến nâng cao, sẽ giúp bạn tự tin và chuẩn bị tốt hơn cho các buổi phỏng vấn. Hiểu rõ cách Redis hoạt động, cũng như các tính năng sẽ giúp bạn có được lợi thế lớn trong các vị trí phát triển phần mềm và quản lý hệ thống. 

ITviec hy vọng bài viết trên đã cung cấp cho bạn những kiến thức bổ ích giúp bạn chuẩn bị thật tốt cho các cơ hội mới nhé!

TÁC GIẢ
Mỹ Duyên
Mỹ Duyên

Content Writer

Là cử nhân ngành Data Science, Duyên có hơn 1 năm kinh nghiệm nghiên cứu trong ngành Data và tập trung vào AI, phân tích dữ liệu. Thông qua những bài viết từ cơ bản đến nâng cao thuộc lĩnh vực cơ sở dữ liệu, Duyên mang đến cho độc giả những cái nhìn toàn diện và mới mẻ về thế giới công nghệ thông tin và dữ liệu.