Automating Feature-Flag Cleanup at Scale with a Multi-Agent LLM System

Bài tóm tắt DoorDash 2026 3. Quy mô lớn Độ khó: Trung bình

DoorDash · 2026 · 3. Quy mô lớn · Độ khó: Trung bình — bài gốc

Bối cảnh

  • Nền tảng experimentation của DoorDash quản lý hơn 60.000 feature flag (gọi là dynamic value, DV) trên khoảng 623 repo; hơn một nghìn flag đã "chết" (tính năng đã rollout hết nhưng nhánh điều kiện còn trong code), mỗi tháng thêm khoảng 2.300 flag mới (2026). Dọn tay một flag mất 1–2 giờ, nên backlog chỉ tăng.
  • Theo DoorDash, flag chết không vô hại: thêm nhánh và trường hợp biên phải đọc khi xử lý sự cố, và giữ một đường code ngủ có thể bị bật nhầm (dẫn vụ một công ty giao dịch dùng lại flag cũ, mất 460 triệu USD trong 45 phút).
  • Định nghĩa "chết": 90 ngày không sửa, còn được tham chiếu trong code, chưa ở trạng thái lưu trữ, không trong danh sách loại trừ. Cron hằng ngày mở ticket Jira gán cho người tạo flag.

Thay đổi chính

  • Vì sao không dùng công cụ AST (Piranha): flag ở DoorDash đi qua ba lớp (hằng tên flag, wrapper inject qua DI gọi client, logic nghiệp vụ gọi wrapper), mỗi lớp một file, liên kết bằng ngữ nghĩa chứ không bằng cú pháp; một flag Boolean đơn giản đã chạm 5–20 file kể cả test. Và giá trị đúng để thay vào code nằm ở trạng thái rollout đang chạy trên nền tảng experimentation (có flag int, string, JSON; có thí nghiệm dừng ở 60% rồi bỏ), không nằm trong repo.
  • Pipeline hai pha, một chốt người (Google ADK):
    • Pha 1 (Claude Sonnet làm orchestrator): đọc ticket, hỏi nền tảng experimentation qua MCP lấy UUID, tỉ lệ rollout, giá trị đích, tìm mọi tham chiếu, ra báo cáo đề xuất giá trị đích và file bị ảnh hưởng.
    • Chốt người: engineer xác nhận giá trị đích cho từng flag; trường hợp mập mờ (rollout dở, thí nghiệm bỏ) thì người quyết hoàn tất rollout, rollback hay để sau.
    • Pha 2 (Claude Opus cho từng flag): tối đa 4 agent song song mỗi repo, mỗi agent một git worktree riêng; thay lời gọi wrapper bằng giá trị đích, rút gọn điều kiện, xoá wrapper và hằng, sửa test; chỉ được mở PR khi build, test, coverage ≥95% trên dòng đã đổi (JaCoCo) và lint (Detekt) đều qua; lỗi thì tự chẩn đoán và thử lại; timeout cứng 1 giờ. Gradle chạy --no-daemon để các worktree không nhiễm trạng thái của nhau.

Vì sao (kết quả trên 50 flag gần nhất)

  • 45/50 ra PR dùng được, trung bình 4,79 USD và 13,8 phút mỗi flag; 31 merge ngay, 14 cần sửa nhỏ một lần (thiếu coverage, sót biến chết), 5 cần engineer can thiệp.
  • Theo độ phức tạp: đơn giản 100%, trung bình 94%, phức tạp 85% (phức tạp trung bình 17,7 phút, 6,20 USD; ngoại lệ khoảng 40 phút, 19 USD).
  • Không PR nào gây bug hay hồi quy; cả 5 lần can thiệp đều do chuỗi gọi dài xuyên interface: agent bỏ sót chứ không làm sai. Theo DoorDash, giới hạn tin cậy của hệ là độ đầy đủ, không phải độ đúng.
  • Bài học của họ: chia model theo vai (model rẻ cho điều phối, model mạnh cho sửa code) giảm chi phí; không có worktree riêng thì các agent sửa chồng file, ra diff hỏng; đọc trạng thái rollout thật là quyết định đáng giá nhất. Tiếp theo: điểm tin cậy để tự duyệt flag rủi ro thấp (Boolean, 100% rollout, mặc định trong code đã khớp production).

Bài học cho SWE

  • Vòng đời feature flag cần cả bước xoá; xoá đúng cần dữ liệu vận hành (trạng thái rollout), không chỉ code.
  • Agent sửa code ở quy mô lớn cần: nguồn sự thật bên ngoài code, môi trường cô lập để thất bại thì vứt đi được, và cổng kiểm tất định trước khi mở PR.
  • Thiết kế sao cho khi thất bại thì thất bại an toàn (bỏ sót, không làm hỏng).

Liên quan