1. Tại sao các Chiến dịch Khuyến mãi là Bài Kiểm tra Áp lực Tối hạn
Đối với hầu hết các hệ thống phần mềm, lưu lượng truy cập (traffic) tăng trưởng một cách dần dần — và các đội ngũ kỹ thuật có đủ thời gian để phản ứng. Nhưng đối với PayPay, sự tăng trưởng lưu lượng là tức thời và được lên lịch sẵn: ngay vào khoảnh khắc một chiến dịch cashback (hoàn tiền) trị giá hàng tỷ yên được kích hoạt vào đúng 12 giờ trưa thứ Sáu, hàng triệu người dùng Nhật Bản sẽ đồng thời mở ứng dụng, nhìn thấy banner quảng cáo và nhấn “Thanh toán”.
Đây không phải là một đường dốc thoải. Đây là thăng đàn bạo phát (thundering herd) — một đợt sóng traffic đột biến gần như tức thời mà hạ tầng phải hấp thụ trong vài giây, nếu không người dùng sẽ nhìn thấy lỗi. Và đối với PayPay, một lỗi xảy ra trong chiến dịch không chỉ là trải nghiệm người dùng kém: nó là một sự kiện tin tức trên trang nhất. Chiến dịch tháng 12 năm 2018 tiêu tốn hết 10 tỷ yên chỉ trong 10 ngày đã tạo ra làn sóng dư luận tiêu cực đáng kể về các vấn đề bảo mật và sự mất ổn định hệ thống. Đội ngũ kỹ thuật đã rút ra một bài học xương máu duy nhất từ sự kiện đó: phải vượt qua các chiến dịch bằng thiết kế chủ động, chứ không phải bằng may mắn.
Hồ sơ lưu lượng truy cập của một chiến dịch PayPay tiêu biểu:
Normal TPS (Lưu lượng thông thường): ~400 TPS (baseline)
Campaign launch (Bắt đầu chiến dịch): 4.000+ TPS (tăng 10x trong vòng 30 giây)
Peak sustained (Đỉnh duy trì): 1.250+ TPS (duy trì trong nhiều giờ)
Post-campaign (Sau chiến dịch): Trở lại bình thường (giảm dần sau 2-3 giờ)
Mỗi tầng kiến trúc — từ số lượng pod Kubernetes, mức độ đồng thời của consumer Kafka cho đến số lượng node database TiDB — phải sẵn sàng cho mức tải gấp 10 lần bình thường trước khi nút kích hoạt chiến dịch được nhấn.
2. Bài toán Co giãn trước (Pre-Scaling): Tại sao HPA Phản ứng lại Thất bại
Cơ chế Horizontal Pod Autoscaler (HPA) tiêu chuẩn của Kubernetes theo dõi mức sử dụng CPU hoặc bộ nhớ và tự động thêm pod khi vượt quá các ngưỡng thiết lập. Đối với sự tăng trưởng traffic dần dần, HPA hoạt động rất tốt. Nhưng đối với các đợt đột biến của chiến dịch, nó thất bại theo một cách nghiêm trọng:
- Kích hoạt chiến dịch $\rightarrow$ TPS vọt từ 400 lên 4.000 trong 30 giây.
- Mức sử dụng CPU tăng vọt $\rightarrow$ HPA phát hiện vi phạm ngưỡng.
- HPA gửi yêu cầu tạo pod mới tới Kubernetes scheduler.
- Kubernetes lập lịch chạy pod trên các node còn trống.
- Thực hiện pull container image + khởi động ứng dụng.
- Pod mới chuyển sang trạng thái Ready và tham gia vào load balancer.
Tổng thời gian từ lúc traffic đột biến cho đến khi pod mới thực sự xử lý request: 60–120 giây.
Trong 60–120 giây đó, số lượng pod ban đầu (chỉ được thiết kế cho mức baseline 400 TPS) phải hứng chịu tới 4.000 TPS. Các connection pools cạn kiệt. Hàng đợi (queues) tràn. Người dùng nhận được lỗi. Cho đến khi các pod mới sẵn sàng, thiệt hại đã xảy ra xong.
3. Giải pháp: KEDA Cron Scaler (Cơ chế Pre-Warming)
Cách tiếp cận của PayPay đảo ngược mô hình từ phản ứng (reactive - co giãn sau khi có tải) sang chủ động (proactive - co giãn trước để đón tải).
KEDA (Kubernetes Event-Driven Autoscaling) mở rộng Kubernetes với các bộ kích hoạt co giãn bổ sung, bao gồm bộ kích hoạt cron cho phép scale một deployment lên số lượng replica chỉ định theo một lịch trình định sẵn — trước khi chiến dịch bắt đầu.
Đội ngũ Platform cấu hình một KEDA ScaledObject cho mỗi service đối mặt với chiến dịch, chỉ định thời gian bắt đầu chiến dịch và số lượng replica mục tiêu:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: payment-service-campaign-scaler
spec:
scaleTargetRef:
name: payment-service
triggers:
- type: cron
metadata:
timezone: "Asia/Tokyo"
start: "55 11 * * 5" # 11:55 AM Thứ Sáu — 5 phút trước chiến dịch lúc 12:00
end: "0 18 * * 5" # 6:00 PM Thứ Sáu — kết thúc chiến dịch
desiredReplicas: "100" # Tăng gấp 5 lần số lượng replica thông thường
- type: kafka
metadata:
bootstrapServers: kafka-cluster:9092
consumerGroup: payment-consumer-group
topic: transaction-events
lagThreshold: "50" # Thêm replica khi consumer lag vượt quá 50
Bộ kích hoạt cron scale service lên 100 replicas 5 phút trước khi chiến dịch bắt đầu — cung cấp cho Kubernetes đủ thời gian để lập lịch chạy pod, pull image và làm nóng bộ nhớ đệm (warm caches) của ứng dụng trước khi người dùng đầu tiên chạm vào nút “Thanh toán”. Khi chiến dịch kết thúc, thời gian end của trigger cron sẽ khiến KEDA scale ngược trở lại mức minReplicaCount ban đầu, giúp tiết kiệm chi phí tài nguyên.
Đồng thời, bộ kích hoạt kafka cũng chạy song song: nếu độ trễ tiêu thụ (consumer lag) của topic transaction-events vượt quá 50 message (nghĩa là các consumer không kịp xử lý kịp tốc độ của producer), KEDA sẽ bổ sung thêm các consumer pod một cách động — giúp xử lý lượng tải ngoài dự kiến vượt quá mức pre-scale ban đầu.
4. Loại bỏ Tải (Load Shedding): Bảo vệ Lõi Hệ thống
Ngay cả khi đã pre-scale, lưu lượng chiến dịch thực tế vẫn có thể vượt quá dự tính. PayPay triển khai loại bỏ tải (load shedding) — một chiến lược hạ cấp dịch vụ theo thứ tự ưu tiên nhằm hy sinh các tính năng phụ để bảo vệ lõi thanh toán cốt lõi.
Các dịch vụ được phân chia thành các mức ưu tiên:
| Mức ưu tiên | Các Dịch vụ | Hành vi khi quá tải cực hạn |
|---|---|---|
| P0 — Core | Xử lý thanh toán, ghi sổ cái, kiểm tra số dư | Không bao giờ bị giới hạn; được bảo vệ bằng mọi giá |
| P1 — Important | Xem lịch sử giao dịch, hiển thị số dư ví | Bị giới hạn (throttled) nếu tài nguyên cho P0 bị đe dọa |
| P2 — Non-critical | Phân tích dữ liệu, thông báo push, sự kiện marketing | Chủ động loại bỏ (shed) hoàn toàn khi quá tải kéo dài |
Trong suốt đợt đột biến của chiến dịch, đội ngũ Platform có khả năng áp dụng thay đổi cấu hình circuit breaker để ngay lập tức chặn hoặc giới hạn các service P2 — giải phóng tài nguyên tính toán và connection pool cho luồng thanh toán P0. Người dùng có thể không nhận được thông báo push theo thời gian thực, nhưng giao dịch thanh toán của họ vẫn thành công. Các thông báo push sẽ được xếp hàng đợi trong Kafka và phân phối sau khi đợt cao điểm qua đi.
Cô lập Workload: Các service P0 chạy trên các node pool chuyên dụng trong Kubernetes — được cô lập hoàn toàn khỏi workload P1/P2 bằng cơ chế node selector và taints. Một dịch vụ phân tích dữ liệu P2 tiêu thụ quá nhiều CPU không bao giờ có thể tranh đoạt tài nguyên tính toán của dịch vụ thanh toán P0, bởi vì chúng chạy trên các node vật lý khác nhau.
5. Kafka: Bộ đệm giảm chấn cho Chiến dịch
Hệ thống event bus Kafka là thành phần quan trọng nhất trong suốt thời gian bắt đầu chiến dịch. Vai trò của nó chuyển dịch từ một lớp định tuyến sự kiện thông thường thành một bộ giảm chấn hấp thụ lưu lượng chuyên dụng:
Chiến dịch bắt đầu (Đột biến 4.000 TPS):
- 3.800 events/giây → Đẩy vào topic `campaign-events` của Kafka
- 200 events/giây → Đẩy vào topic `transaction-events` của Kafka
Người dùng nhận phản hồi "202 Accepted" trong vòng 50ms
↓
Kafka lưu trữ bền vững (durably) toàn bộ event
↓
Các consumer xử lý theo tốc độ an toàn của database:
- Consumer `campaign-events`: 800 events/giây (Tích tụ Kafka lag tạm thời, sau đó giải phóng dần)
- Consumer `transaction-events`: 1.200 TPS (Giới hạn chịu tải của TiDB)
Tình trạng consumer lag trong Kafka đối với campaign-events chắc chắn sẽ tăng vọt khi bắt đầu chiến dịch — điều này hoàn toàn nằm trong dự kiến và được chấp nhận. Độ trễ sẽ giảm dần khi các consumer giải quyết hết hàng đợi backlog trong vài phút tiếp theo. Người dùng sẽ thấy trạng thái “Đang xử lý” ngắn ngủi đối với phần tiền hoàn lại (cashback), sau đó số dư sẽ cập nhật khi consumer hoàn tất việc xử lý event của họ.
Idempotency (Tính bất biến) ở quy mô chiến dịch: Với hơn 10 triệu sự kiện được xếp hàng đợi trong Kafka trong suốt chiến dịch, rủi ro xử lý trùng lặp là rất cao. Mỗi sự kiện trao cashback đều mang theo một khóa idempotency (chính là ID giao dịch gốc). Nếu một consumer xử lý một message và sau đó restart (khiến Kafka gửi lại message đó), kho lưu trữ idempotency (Redis) sẽ trả về kết quả đã cache — ngăn chặn việc hoàn tiền hai lần cho khách hàng.
6. Co giãn Đàn hồi TiDB cho các Chiến dịch
Không giống như Kafka và các pod Kubernetes, các node database không thể được thêm vào chỉ trong vài giây. Kiến trúc của TiDB cho phép co giãn đàn hồi có kế hoạch được đội ngũ Platform thực thi từ 30–60 phút trước chiến dịch:
Trước chiến dịch (T-60 phút):
- Khởi tạo thêm 4 node tính toán TiDB (stateless)
- Kết nối vào cluster lưu trữ TiKV hiện tại
- Xác thực kết nối và sức khỏe hệ thống
Trong chiến dịch (T+0 đến T+6 giờ):
- Cluster tính toán TiDB: 12 nodes (thay vì 8 node như bình thường)
- Lớp lưu trữ TiKV: giữ nguyên (không cần di chuyển dữ liệu)
- Băng thông ghi (Write throughput): tăng tỷ lệ thuận theo năng lực tính toán
Sau chiến dịch (T+12 giờ):
- Thu hồi 4 node tính toán TiDB đã thêm
- Lớp lưu trữ TiKV giữ nguyên
- Chi phí hạ tầng quay trở lại mức baseline
Bởi vì TiDB phân tách rõ ràng giữa lớp tính toán (các node TiDB) và lớp lưu trữ (các node TiKV), việc thêm dung lượng tính toán không yêu cầu bất kỳ hoạt động di chuyển hoặc cân bằng lại dữ liệu nào. Các node TiDB mới chỉ cần kết nối tới cluster TiKV hiện tại và ngay lập tức tham gia xử lý các câu truy vấn. Đội ngũ Platform chỉ phải trả chi phí cho tài nguyên tính toán bổ sung này trong thời gian diễn ra chiến dịch.
7. Vòng lặp KAIZEN: Cải tiến sau Mỗi Chiến dịch
PayPay vận hành một chu kỳ đánh giá sau chiến dịch được truyền cảm hứng từ triết lý KAIZEN (cải tiến liên tục) của Toyota. Sau mỗi chiến dịch lớn, đội ngũ SRE và Platform sẽ tiến hành một buổi họp rút kinh nghiệm (retrospective) có cấu trúc:
- Phân tích lưu lượng: TPS thực tế tại đỉnh là bao nhiêu? Nó so với dự báo trước chiến dịch như thế nào? Những điểm nào dự báo chưa chính xác?
- Xác định điểm nghẽn (Bottlenecks): Dịch vụ hoặc thành phần nào xuất hiện dấu hiệu suy giảm hiệu năng đầu tiên? Phản ứng dây chuyền diễn ra như thế nào?
- Hiệu chuẩn Pre-scaling: Số lượng replica mong muốn (
desiredReplicas) của KEDA có chính xác không? Quá dè dặt hay quá dư thừa (lãng phí chi phí)? - Hồ sơ cải tiến: Mọi cải tiến được xác định đều được ghi chép lại, phân cấp ưu tiên và deploy trước khi chiến dịch tiếp theo diễn ra.
Trải qua nhiều chu kỳ chiến dịch, vòng lặp phản hồi này đã tinh lọc các công thức pre-scaling của PayPay, cải thiện độ phủ của các bài test hỗn loạn (chaos testing) và mài sắc các ngưỡng cảnh báo trễ của consumer. Nền tảng năm 2018 bị sập ngay trong chiến dịch đầu tiên và nền tảng năm 2025 xử lý 7,8 tỷ giao dịch mỗi năm thực chất là cùng một hệ thống — được xây dựng lại một cách lặp đi lặp lại thông qua quá trình học hỏi không ngừng từ vận hành thực tế.
Hạ tầng GitOps và triển khai hỗ trợ cơ chế pre-scaling chiến dịch này được trình bày chi tiết trong Phần 1. Để có cái nhìn rộng hơn về các mô hình co giãn hướng sự kiện ở quy mô lớn, bài viết GitOps ở Quy mô lớn sẽ đề cập đến các mẫu triển khai Kubernetes và Argo CD mà PayPay sử dụng làm nền tảng vận hành.