Giới thiệu
Một hệ thống có thể render và enqueue hàng triệu email chưa chắc đã có thể gửi chúng an toàn. Điểm nghẽn thực sự thường nằm ở tốc độ gửi hợp lệ: giới hạn từ ESP, reputation của domain/IP, và khả năng hấp thụ của downstream systems.
Bài này tập trung vào lớp điều tiết lưu lượng, nơi quyết định chiến dịch 10 triệu email sẽ thành công hay tự đẩy cả domain vào spam folder.
1. Vì sao rate limiting là bắt buộc?
Các giới hạn thực tế
| Nguồn giới hạn | Ví dụ | Hậu quả nếu vượt ngưỡng |
|---|---|---|
| ESP send quota | SES 200 email/giây mặc định | ThrottlingException, queue backlog |
| Domain reputation | Domain mới gửi đột ngột 1M email | Spam placement tăng mạnh |
| IP reputation | Dedicated IP chưa warm-up | Bounce và complaint tăng |
| ISP limits | Gmail, Yahoo throttling theo domain | Deferred delivery, soft bounce |
| Internal systems | Worker quá nhiều, DB update quá dày | CPU cao, lock contention |
Mục tiêu của throttling layer
- Bảo vệ reputation của sender.
- Bảo vệ email providers khỏi traffic burst.
- Ưu tiên transactional email hơn marketing email.
- Duy trì throughput ổn định thay vì spike ngắn rồi sập.
- Giữ queue backlog trong phạm vi kiểm soát.
2. Các thuật toán phổ biến
So sánh nhanh
| Thuật toán | Điểm mạnh | Điểm yếu | Phù hợp cho |
|---|---|---|---|
| Fixed Window | Dễ implement | Burst ở ranh giới window | Counter đơn giản |
| Sliding Window | Chính xác hơn | Tốn state hơn | Per-domain limits |
| Leaky Bucket | Output đều | Hơi cứng với traffic burst hợp lệ | Smoothing traffic |
| Token Bucket | Cho phép burst có kiểm soát | Cần đồng bộ state | ESP/API throttling |
Khuyến nghị cho email system
- Dùng token bucket cho giới hạn theo provider và theo IP pool.
- Dùng sliding window cho complaint rate, bounce rate, open rate theo campaign/domain.
- Dùng priority-aware scheduler để email quan trọng không bị marketing chiếm hết quota.
3. Kiến trúc multi-level throttling
Campaign Queue
│
▼
Priority Scheduler
│
├── Check provider quota (SES / SendGrid / Mailgun)
├── Check domain quota (gmail.com / yahoo.com / outlook.com)
├── Check IP pool quota
├── Check campaign quota
└── Check suppression / reputation guard
│
▼
Dispatch Queue
│
▼
Workers -> ESP
Thứ tự kiểm tra hợp lý
- Kiểm tra suppression list và bounce history.
- Kiểm tra priority class của message.
- Kiểm tra quota theo provider.
- Kiểm tra quota theo recipient domain.
- Kiểm tra quota theo sender IP / dedicated IP pool.
- Nếu fail, reschedule thay vì drop.
Priority policy ví dụ
| Priority | Use case | SLA | Quy tắc |
|---|---|---|---|
| critical | OTP, reset password | < 30s | Luôn có reserved capacity |
| high | Order confirmation | < 2 phút | Không bị block bởi marketing |
| normal | Product updates | < 15 phút | Chia sẻ quota động |
| low | Newsletter, drip campaign | Theo schedule | Bị cắt trước khi reputation xấu |
4. Distributed rate limiter với Redis
Token bucket model
import time
import redis
class RedisTokenBucket:
def __init__(self, client: redis.Redis, key: str, rate: int, burst: int):
self.client = client
self.key = key
self.rate = rate
self.burst = burst
def allow(self, tokens: int = 1) -> bool:
now_ms = int(time.time() * 1000)
script = """
local key = KEYS[1]
local now_ms = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local burst = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
local data = redis.call('HMGET', key, 'tokens', 'updated_at')
local tokens = tonumber(data[1])
local updated_at = tonumber(data[2])
if tokens == nil then
tokens = burst
updated_at = now_ms
end
local elapsed = math.max(0, now_ms - updated_at)
local refill = (elapsed / 1000.0) * rate
tokens = math.min(burst, tokens + refill)
local allowed = 0
if tokens >= requested then
tokens = tokens - requested
allowed = 1
end
redis.call('HMSET', key, 'tokens', tokens, 'updated_at', now_ms)
redis.call('PEXPIRE', key, 60000)
return allowed
"""
result = self.client.eval(script, 1, self.key, now_ms, self.rate, self.burst, tokens)
return result == 1
Các key nên theo dõi
rate:provider:ses
rate:provider:sendgrid
rate:domain:gmail.com
rate:domain:yahoo.com
rate:ip-pool:warm-01
rate:campaign:camp_flash_sale_april
Điểm quan trọng là limiter phải được share giữa tất cả workers, nếu không mỗi worker đều tưởng mình còn quota và hệ thống sẽ vượt ngưỡng ngay lập tức.
5. Adaptive throttling dựa trên tín hiệu deliverability
Tốc độ gửi không nên cố định. Nó phải phản ứng với tín hiệu từ thực tế.
Tín hiệu cần theo dõi
| Signal | Ý nghĩa | Hành động |
|---|---|---|
| Soft bounce tăng | ISP đang defer traffic | Giảm 20-40% send rate |
| Complaint rate tăng | Nội dung/list quality xấu | Giảm mạnh, pause campaign |
| Open rate giảm bất thường | Spam placement tăng | Giảm tốc, đổi IP/domain mix |
| Queue delay quá cao | Hệ thống thiếu worker/quota | Tăng worker hoặc kéo dài ETA |
Policy engine ví dụ
def compute_send_rate(base_rate, metrics):
rate = base_rate
if metrics.soft_bounce_rate > 0.03:
rate *= 0.7
if metrics.complaint_rate > 0.001:
rate *= 0.5
if metrics.provider_throttling_rate > 0.05:
rate *= 0.8
if metrics.critical_queue_depth > 1000:
rate = max(rate, metrics.reserve_for_critical)
return max(int(rate), 10)
Nguyên tắc
- Marketing traffic là nơi phải hy sinh đầu tiên.
- Transactional traffic nên có quota reserve cố định.
- Không giảm quota về 0 ngay lập tức trừ khi complaint spike rất cao hoặc ESP yêu cầu pause.
6. IP warming và domain warm-up
Một sai lầm phổ biến là domain/IP mới nhưng gửi full load ngay ngày đầu.
Lộ trình warm-up mẫu cho dedicated IP mới
| Ngày | Tối đa email/ngày | Đối tượng |
|---|---|---|
| 1 | 5,000 | Users engaged cao |
| 2 | 10,000 | Segment sạch, recent active |
| 3 | 20,000 | Mở rộng nhẹ |
| 4 | 40,000 | Bắt đầu mixed traffic |
| 5 | 80,000 | Giữ theo complaint/bounce |
| 6 | 160,000 | Tiếp tục scale |
| 7+ | Theo reputation | Chỉ tăng nếu metrics ổn |
Guardrails khi warm-up
- Chỉ gửi đến recipients đã opt-in rõ ràng.
- Ưu tiên segment có open/click rate tốt.
- Không reuse list cũ chất lượng thấp.
- Theo dõi Gmail Postmaster Tools và Microsoft SNDS nếu có.
7. Failure modes thường gặp
Khi thiết kế limiter sai
- Local limiter per worker: tổng throughput vượt quota dù từng worker đều "đúng".
- Không reserve capacity: OTP bị chậm vì campaign marketing đang ăn hết token.
- Throttle theo provider nhưng quên domain: ESP vẫn nhận, nhưng Gmail bắt đầu defer.
- Retry vô hạn: throttle bị khuếch đại do retry storm.
Checklist production
- Có quota theo provider, domain, IP, campaign.
- Có reserve cho critical traffic.
- Có cơ chế pause campaign thủ công.
- Có adaptive throttling dựa trên feedback loop.
- Có dashboard hiển thị send rate hiện tại và quota còn lại.
Tổng kết
Rate limiting không chỉ là bài toán kỹ thuật mà là lớp bảo vệ sống còn cho deliverability. Hệ thống tốt phải biết gửi nhanh khi được phép, giảm tốc khi có tín hiệu xấu, và luôn ưu tiên traffic quan trọng nhất.
Bài tiếp theo: Chúng ta sẽ tổ chức batch processing và worker pool để xử lý hàng triệu recipients hiệu quả mà không làm cạn memory hay nghẽn database.