Pipeline trong Redis: Tối ưu hiệu năng Batch Operations
Bạn có biết?
Khi bạn cần import 10.000 sản phẩm vào cache Redis, mỗi lệnh SET mất khoảng 0.1ms qua mạng LAN. Với cách thông thường, tổng thời gian là 1 giây — nhưng với Pipeline, con số này giảm xuống chỉ còn 50ms. Đó là sức mạnh của Pipeline — kỹ thuật gom nhiều lệnh thành một batch, gửi và nhận kết quả trong một lần duy nhất.
Pipeline là gì?
Pipeline là kỹ thuật gom nhiều lệnh Redis lại, gửi tất cả cùng lúc qua mạng, rồi nhận tất cả kết quả cùng lúc. Thay vì mỗi lệnh phải chờ round-trip (RTT), Pipeline chỉ cần 1 round-trip cho toàn bộ batch.
Ví dụ, gửi 5 lệnh không có Pipeline:
Client → SET key1 → Server → OK (RTT 1)
Client → SET key2 → Server → OK (RTT 2)
Client → SET key3 → Server → OK (RTT 3)
Client → GET key4 → Server → value4 (RTT 4)
Client → GET key5 → Server → value5 (RTT 5)
# Tổng: 5 round-trips
Với Pipeline:
Client → SET key1 + SET key2 + SET key3 + GET key4 + GET key5 → Server
Client ← OK + OK + OK + value4 + value5 ← Server
# Tổng: 1 round-trip 🚀
Pipeline ≠ Transaction
Nhiều người nhầm lẫn Pipeline với Transaction. Điểm khác biệt chính:
| Tiêu chí | Pipeline | Transaction (MULTI/EXEC) |
|---|---|---|
| Atomicity | Không | Có |
| Isolation | Không | Có |
| Use case | Tối ưu hiệu năng | Đảm bảo tính nhất quán |
| Performance | Rất nhanh | Chậm hơn một chút |
| Error handling | Tiếp tục khi lỗi | Hoàn toàn rollback |
Pipeline chỉ tối ưu network round-trips, không đảm bảo atomicity.
Transaction đảm bảo tất cả lệnh thực hiện together hoặc không lệnh nào.
Các lệnh cơ bản
Sử dụng Pipeline trong Redis CLI
Redis CLI hỗ trợ Pipeline qua tùy chọn --pipe:
# Tạo file chứa các lệnh
echo -e "SET key1 value1\nSET key2 value2\nSET key3 value3" > commands.txt
# Gửi qua Pipeline
cat commands.txt | redis-cli --pipe
# Kết quả:
# All data transferred. Waiting for the last reply...
# Last reply received from server.
# errors: 0, replies: 3
Benchmark với redis-benchmark
# Benchmark không Pipeline (1 command/lần)
redis-benchmark -t set -n 100000 -c 1
# SET: 85,470 requests/sec
# Benchmark với Pipeline (100 commands/lần)
redis-benchmark -t set -n 100000 -c 1 -P 100
# SET: 1,250,000 requests/sec
# ~15x nhanh hơn!
Sử dụng Pipeline trong Python
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
# Tạo pipeline
pipe = r.pipeline()
# Thêm lệnh vào pipeline
pipe.set("user:1:name", "Alice")
pipe.set("user:2:name", "Bob")
pipe.hset("user:1", mapping={"age": "25", "city": "Hanoi"})
pipe.lpush("queue", "task1", "task2")
pipe.incr("counter")
# Thực thi tất cả cùng lúc
results = pipe.execute()
print(results) # [True, True, 0, 2, 1]
Sử dụng Pipeline trong Node.js
const Redis = require('ioredis');
const redis = new Redis();
const pipeline = redis.pipeline();
pipeline.set('user:1:name', 'Alice');
pipeline.set('user:2:name', 'Bob');
pipeline.hset('user:1', 'age', '25');
pipeline.lpush('queue', 'task1', 'task2');
const results = await pipeline.exec();
// results = [[null,'OK'], [null,'OK'], [null,0], [null,2]]
Pipeline + Transaction
Kết hợp Pipeline và Transaction để vừa tối ưu hiệu năng vừa đảm bảo atomicity:
# Python: Pipeline + Transaction
pipe = r.pipeline(transaction=True) # Tự động MULTI/EXEC
pipe.set("balance:alice", 1000)
pipe.set("balance:bob", 500)
pipe.decrby("balance:alice", 200)
pipe.incrby("balance:bob", 200)
results = pipe.execute()
# Nếu bất kỳ lệnh nào lỗi, tất cả rollback
print(results) # [True, True, 800, 700]
So sánh Pipeline vs Transaction vs Lua
| Feature | Pipeline | Transaction | Lua Script |
|---|---|---|---|
| Network round-trips | 1 | 1 | 1 |
| Atomicity | Không | Có | Có |
| Conditional logic | Không | Có (WATCH) | Có |
| Performance | Rất cao | Cao | Trung bình |
| Complexity | Thấp | Trung bình | Cao |
Khi nào dùng gì?
- Cần tối ưu hiệu năng batch? → Pipeline
- Cần atomicity? → Transaction (MULTI/EXEC)
- Cần conditional logic phức tạp? → Lua Script
- Cần cả atomicity + hiệu năng? → Pipeline + Transaction
Use Cases thực tế
1. Bulk Import dữ liệu
Import hàng nghìn records từ database vào Redis cache:
# Import 10,000 users vào Redis
pipe = r.pipeline()
for user in users:
pipe.hset(f"user:{user['id']}", mapping={
"name": user['name'],
"email": user['email'],
"city": user['city']
})
pipe.sadd("users:all", user['id'])
pipe.execute()
# Không Pipeline: ~500 users/sec
# Có Pipeline: ~12,000 users/sec (24x nhanh hơn)
2. Batch Cache Update
Update cache cho nhiều products sau khi database thay đổi:
pipe = r.pipeline()
for product in products:
# Set cache với TTL 1 giờ
pipe.setex(f"product:{product['id']}", 3600, json.dumps(product))
# Update price index
pipe.zadd("products:by_price", {str(product['id']): product['price']})
# Update stock
pipe.hset("products:stock", str(product['id']), product['stock'])
pipe.execute()
3. Batch Counter Updates
Cập nhật counters cho nhiều events cùng lúc:
pipe = r.pipeline()
for event in events:
pipe.hincrby(f"stats:{event['date']}", event['type'], 1)
pipe.incr(f"total:{event['type']}")
pipe.execute()
4. Multi-key Read
Đọc thông tin nhiều users cùng lúc:
pipe = r.pipeline()
for user_id in user_ids:
pipe.hgetall(f"user:{user_id}")
pipe.zscore("users:activity", str(user_id))
results = pipe.execute()
# results = [user1_data, score1, user2_data, score2, ...]
Best Practices
- Giới hạn batch size — Chia nhỏ batches 500-2000 lệnh, tránh memory spike
- Dùng Pipeline cho batch operations — Bất kỳ lúc nào gửi > 5 lệnh liên tiếp
- Kết hợp Transaction khi cần atomicity —
pipeline(transaction=True) - Không dùng Pipeline cho blocking commands — BLPOP, BRPOP không phù hợp
- Monitor memory — Pipeline giữ tất cả lệnh trong memory trước khi gửi
- Pipeline càng lợi hơn trên mạng xa — High latency = lợi ích càng lớn
Lưu ý quan trọng
- Pipeline không đảm bảo atomicity — Nếu cần atomic, dùng Pipeline + Transaction
- Batch
size quá lớn → memory spike — Nên chia nhỏ 500-2000 lệnh/batch
- Mỗi lệnh vẫn kiểm tra riêng — Lỗi ở lệnh này không ảnh hưởng lệnh khác

Bước tiếp theo
Bạn đã nắm vững Pipeline trong Redis! Tiếp theo, chúng ta sẽ tìm hiểu về Persistence — cách Redis lưu dữ liệu xuống disk với RDB và AOF.