The Day Our Own Queries DoS'ed Us: Inside Zalando Search
Bài tóm tắt Zalando 2025 3. Quy mô lớn Độ khó: Trung bình
Zalando · 2025 · 3. Quy mô lớn · Độ khó: Trung bình — bài gốc
Bối cảnh
- Team Search & Browse vận hành backend catalog và full-text search. Nhiều tầng: Base Search (cụm Elasticsearch, lexical + vector), query builder dùng NER, Catalog API fan-out mỗi lượt tìm thành nhiều query (có A/B test), Search API cùng các dịch vụ xếp hạng và quảng cáo. Mỗi tầng có cache riêng; coordinator node của Elasticsearch đặt trên máy riêng.
- Với mỗi lượt tìm, Catalog API gọi thêm một query facet (brand, size, màu, giá): nặng aggregation theo thiết kế.
- Cụm được chia theo nhóm thị trường, mỗi cụm phục vụ một vài thị trường.
Diễn biến
- Một ngày Chủ nhật, một cụm (phục vụ hai thị trường lớn nhất) CPU tăng vọt, query từ ms thành giây, user thấy trang kết quả rỗng hoặc filter báo 0 sản phẩm. Không có deploy, không đổi cấu hình, traffic đọc và ghi không tăng. Các cụm khác bình thường, tức cô lập theo nhóm thị trường đã hoạt động.
- Playbook đầu tiên (cache lâu hơn, tắt request không thiết yếu, hạ ngưỡng ngắt query dài, scale coordinator và data node) không đỡ chút nào.
- Thí nghiệm: tách hai thị trường ra hai cụm bằng node allocation. Cùng lúc cắt tải: giảm số replica (để ít shard phải dời), dừng hẳn ingestion, ở tầng ứng dụng dùng các tầng presentation làm control plane: tắt call không thiết yếu, giảm số query song song mỗi request, giảm tỉ lệ request đi qua các tích hợp ML và quảng cáo nặng, quay về xếp hạng đơn giản.
- Tách xong thì chứng minh được lỗi nằm ở query của một thị trường. Cụm tự hồi vào buổi tối, nhưng team vẫn đào tiếp: trace trong Lightstep cho thấy một ứng dụng nội bộ gửi gấp 50 lần bình thường, khớp đúng thời gian sự cố.
Nguyên nhân
- Query facet làm aggregation trên trường có cardinality rất cao (SKU, ID duy nhất), vô nghĩa về nghiệp vụ. Một công việc bảo trì cộng một bug khiến workload vốn hiếm (do người dùng nghiệp vụ bấm) bị gọi tự động.
- Chỉ 20–100 request/giây, trong khi cụm xử lý hàng nghìn: quá nhỏ để nổi lên trên dashboard theo client. Query hợp lệ về cú pháp, client nội bộ có thật và không mới, slow query không gắn định danh client.
- Cơ chế: terms aggregation trên trường cardinality cao chạy nặng trong thread pool
searchcủa từng shard, không dùng được collector song song; vài query như vậy đủ giữ cụm "nóng" và bỏ đói traffic thường.
Khắc phục
- Cô lập workload tốt hơn, rate limit theo loại client ("không phải client nào cũng ngang nhau").
- Gắn
X-Opaque-Idđể slow log có định danh client; dashboard slow query theo client và theo kích thước aggregation. - Giới hạn query ở phía ứng dụng với ngưỡng chỉnh được động; runbook đặt
search.max_bucketstoàn cụm. - Playbook phân biệt tải đọc cao, tải ghi cao, và chặn client hành xử sai.
Bài học cho SWE
- Tải đắt không cần nhiều: đo theo số request sẽ bỏ sót query nhỏ mà nặng; cần đo theo chi phí (Load shedding, rate limit và backpressure).
- Khi playbook quen thuộc không tác dụng, chia đôi miền lỗi (tách thị trường) là một cách chẩn đoán (Sự cố và postmortem).
- Gắn định danh caller vào mọi query tới hệ dùng chung.
- "Nghe tiếng vó thì nghĩ tới ngựa", nhưng đôi khi đó là ngựa vằn: client nội bộ hợp lệ cũng có thể DoS.
Liên quan
Được nhắc tới trong
Công ty (1)
Bài tóm tắt (1)