Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 39 — Feature Flag Strategy

Viết PRD & Product Spec cho PM Bài 39/60

Mở đầu — vì sao bài này quan trọng

Hãy hình dung bạn vừa ship một tính năng thanh toán mới. Code đã merge, deploy đã chạy, và đúng 9 giờ tối — giờ cao điểm mua sắm — bạn nhận ra luồng thanh toán mới làm rớt đơn của 30% người dùng. Bạn có hai lựa chọn: (1) viết hotfix, chờ review, build lại, deploy lại — mất ít nhất 40 phút và mất hàng nghìn đơn; hoặc (2) vào dashboard, gạt một công tắc, tính năng mới biến mất khỏi production trong 5 giây mà không cần deploy lại gì cả.

Cái công tắc đó chính là feature flag (cờ tính năng), và chiến lược quản lý nó là chủ đề của bài này.

Là PM, bạn có thể nghĩ feature flag là "chuyện của engineer". Nhưng thực tế, flag là một quyết định sản phẩm. Bạn quyết định ai được thấy tính năng, bật ở đâu, tắt khi nào, và ai có quyền gạt công tắc khi sự cố xảy ra. Một PRD/Spec thiếu phần "feature flag strategy" sẽ khiến đội kỹ thuật tự quyết — và rồi bạn rơi vào tình huống không biết tính năng đang bật cho bao nhiêu phần trăm người dùng, không biết flag nào còn sống flag nào đã chết, hoặc tệ hơn: không có nút tắt khẩn cấp khi cần.

Bài này dạy bạn cách viết phần feature flag strategy trong Spec một cách rõ ràng: phân loại flag, đặt tên, định nghĩa vòng đời, và quy định ai sở hữu nút tắt. Đây là phần kỹ thuật-mà-PM-phải-hiểu, vì nó quyết định bạn ship an toàn hay ship liều.

Khái niệm cốt lõi

Feature flag là gì

Feature flag (còn gọi là feature toggle) là một đoạn điều kiện trong code cho phép bật/tắt một hành vi mà không cần deploy lại. Thay vì "code có tính năng" hoặc "code không có tính năng", flag tách hai khái niệm này ra: tính năng tồn tại trong code nhưng chỉ chạy khi flag bật.

Điều này thay đổi căn bản cách ship sản phẩm. Trước đây, "merge code" đồng nghĩa với "ra mắt tính năng". Với flag, bạn merge code bất kỳ lúc nào (flag mặc định tắt), rồi quyết định thời điểm và đối tượng ra mắt như một động tác riêng biệt — một quyết định sản phẩm, không phải quyết định kỹ thuật.

Bốn loại flag — và đây là phần quan trọng nhất

Lỗi lớn nhất khi viết flag strategy là gộp tất cả flag vào một rổ. Thực tế có bốn loại flag khác nhau về bản chất, vòng đời và chủ sở hữu. Phân loại đúng giúp bạn biết flag nào cần dọn, flag nào giữ vĩnh viễn.

1. Release flag (cờ phát hành) — ngắn hạn, vài tuần. Mục đích: tách "merge code" khỏi "ra mắt cho người dùng". Bạn merge tính năng mới với flag tắt, test trên production an toàn, rồi bật dần. Khi tính năng đã ổn định và bật 100% cho mọi người, flag này phải bị xóa — vòng đời điển hình là 2–6 tuần. Đây là loại flag "nợ kỹ thuật" nếu để lâu: code đầy if (flag) không ai dọn.

2. Operational flag / Kill switch (cờ vận hành) — vĩnh viễn. Mục đích: nút tắt khẩn cấp cho một tính năng có rủi ro vận hành. Ví dụ: tắt tính năng gợi ý sản phẩm bằng AI khi server quá tải, tắt luồng thanh toán qua một cổng đang lỗi. Flag này sống mãi vì bạn luôn muốn có nút tắt. Nó không phải nợ kỹ thuật — nó là cơ chế an toàn.

3. Experiment flag (cờ thí nghiệm) — trung hạn, theo vòng đời thí nghiệm. Mục đích: chia traffic cho A/B test. Flag quyết định người dùng vào nhánh A hay B. Vòng đời gắn với thí nghiệm (thường 2–8 tuần), và sau khi có kết luận thì nhánh thua bị xóa, nhánh thắng được "đóng băng" thành mặc định. (Chi tiết về thiết kế experiment thuộc Bài 37 — ở đây ta chỉ bàn flag như công cụ.)

4. Permission flag / Entitlement flag (cờ quyền) — vĩnh viễn. Mục đích: bật tính năng theo phân khúc người dùng — gói trả phí, khách hàng enterprise, người dùng beta. Ví dụ: tính năng "xuất báo cáo PDF" chỉ bật cho gói Pro. Flag này sống cùng vòng đời sản phẩm vì nó định nghĩa ai được dùng gì.

LoạiVòng đờiChủ sở hữuDọn dẹp?
Release2–6 tuầnPM + EngBắt buộc xóa
Operational / Kill switchVĩnh viễnEng / On-callGiữ
ExperimentTheo thí nghiệmPM / DataXóa nhánh thua
Permission / EntitlementVĩnh viễnPM / GrowthGiữ

Targeting — bật cho ai

Một flag không chỉ "bật/tắt" toàn cục. Phần mạnh nhất là targeting: quy định flag bật cho đối tượng nào theo điều kiện. Các chiều targeting phổ biến:

  • Theo phần trăm: bật cho 1% → 5% → 25% → 100% người dùng (đây là rollout dần — chi tiết ở Bài 38).
  • Theo thuộc tính người dùng: chỉ user nội bộ, chỉ user ở Việt Nam, chỉ user gói Pro.
  • Theo allowlist: một danh sách user ID cụ thể (thường là đội QA, beta tester, khách hàng VIP).
Trong Spec, bạn phải ghi rõ flag dùng chiều targeting nào, vì nó quyết định trải nghiệm thực tế.

Tình huống thực tế

Ví dụ 1 — Tiki và nút tắt cứu giờ flash sale

Một sàn TMĐT lớn (lấy bối cảnh giống Tiki) chuẩn bị cho đợt flash sale 12.12. Đội Search vừa ship một thuật toán xếp hạng kết quả tìm kiếm mới, kỳ vọng tăng tỉ lệ click vào sản phẩm. Họ bọc tính năng này trong một operational flag tên search_ranking_v2_enabled, mặc định bật, nhưng có thể tắt tức thì.

19h ngày flash sale, traffic tăng gấp 8 lần. Thuật toán mới gọi một service tính điểm real-time, và service đó bắt đầu nghẽn — thời gian trả kết quả tìm kiếm nhảy từ 200ms lên 4 giây. Người dùng tìm "son môi" phải chờ 4 giây mới thấy kết quả, tỉ lệ thoát tăng vọt.

On-call engineer mở dashboard flag, gạt search_ranking_v2_enabled về off. Toàn hệ thống lập tức quay về thuật toán cũ (nhanh, ổn định) trong vòng 10 giây — không deploy, không downtime. Flash sale tiếp tục bình thường. Hôm sau đội Search điều tra, tối ưu lại service, rồi bật lại flag vào ngày thường với traffic thấp.

Bài học: tính năng nào chạm vào đường đi quan trọng (search, payment, checkout) và có rủi ro tải, bắt buộc phải có operational flag — và phải ghi rõ trong Spec ai là người được phép gạt nó lúc 19h ngày sale. Nếu không có flag này, lựa chọn duy nhất là hotfix khẩn cấp giữa giờ cao điểm — một thảm họa.

Ví dụ 2 — Startup fintech và bãi rác flag không ai dọn

Một startup fintech Đông Nam Á (khoảng 25 engineer) ship rất nhanh, dùng release flag cho mọi tính năng — điều này tốt. Nhưng họ không có quy trình dọn flag. Sau 18 tháng, codebase có 214 feature flag, trong đó khoảng 160 cái đã bật 100% từ lâu nhưng chưa ai xóa.

Hậu quả xuất hiện theo ba cách. Thứ nhất, một engineer mới sửa logic trong nhánh if (flag_x) mà không biết flag đó đã chết, làm hỏng một luồng đang chạy. Thứ hai, không ai dám đụng vào flag new_kyc_flow_enabled vì không rõ nó còn ảnh hưởng ai — nó đã ở trạng thái "bật 70%" suốt 9 tháng do người PM cũ nghỉ việc mà không bàn giao. Thứ ba, file cấu hình flag nặng đến mức làm chậm thời gian khởi động ứng dụng.

Họ phải lập hẳn một "flag cleanup sprint": rà từng flag, xác định loại, xóa toàn bộ release flag đã hoàn tất, và gắn ngày hết hạn cho mọi flag mới. Quy tắc mới: mỗi release flag khi tạo phải có expiry dateowner, và CI sẽ cảnh báo khi một release flag quá 30 ngày tuổi.

Bài học: flag không có vòng đời rõ ràng sẽ biến thành nợ kỹ thuật nguy hiểm. Trong Spec, mỗi flag phải đi kèm loại, owner, và điều kiện gỡ bỏ. "Flag debt" là một dạng nợ thật, không phải lý thuyết.

Ví dụ 3 — Tính năng AI chat và rollout theo phần trăm

Một nền tảng học trực tuyến (bối cảnh giả định gần với chính nền tảng này) muốn ra mắt trợ lý AI trả lời câu hỏi học viên. Đây là tính năng tốn chi phí (mỗi câu hỏi gọi LLM mất tiền) và có rủi ro chất lượng (AI có thể trả lời sai). Họ dùng một release flag kết hợp targeting theo phần trăm: ai_tutor_enabled.

Kế hoạch trong Spec ghi rõ: ngày 1 bật cho đội nội bộ (allowlist 12 user); ngày 3 bật 5% học viên gói Pro; ngày 7 lên 25% nếu chỉ số phản hồi tích cực ≥ 80% và chi phí mỗi user/ngày ≤ 3.000đ; ngày 14 lên 100% cho gói Pro. Song song, họ giữ một operational flag riêng ai_tutor_kill_switch để tắt ngay nếu chi phí LLM tăng đột biến.

Ở mốc 25%, dữ liệu cho thấy chi phí vọt lên vì một số học viên hỏi liên tục hàng trăm câu. Họ không cần dừng cả rollout — chỉ cần thêm targeting "giới hạn 50 câu/ngày" và giữ nguyên 25%. Sau khi vá xong, tiếp tục lên 100%. Khi ổn định, release flag ai_tutor_enabled bị xóa, nhưng operational flag ai_tutor_kill_switch được giữ lại vĩnh viễn.

Bài học: với tính năng tốn tiền hoặc rủi ro, hãy tách release flag (để bật dần rồi xóa) và operational flag (kill switch giữ mãi) thành hai cái riêng. Đừng dùng chung một flag cho hai mục đích có vòng đời khác nhau.

Hướng dẫn từng bước

Khi viết phần "Feature Flag Strategy" trong PRD/Spec, đi theo 6 bước sau:

Bước 1 — Liệt kê các flag cần thiết. Với mỗi tính năng trong Spec, hỏi: tính năng này có cần bật/tắt độc lập không? Có cần kill switch không? Có cần A/B test không? Có giới hạn theo gói/phân khúc không? Mỗi câu "có" tương ứng một flag. Đừng tạo flag thừa — flag nào cũng là chi phí bảo trì.

Bước 2 — Phân loại mỗi flag. Gán mỗi flag vào một trong bốn loại: release, operational, experiment, permission. Đây là bước quyết định vòng đời. Nếu một flag vừa muốn "bật dần" vừa muốn "kill switch vĩnh viễn", tách thành hai flag.

Bước 3 — Đặt tên theo convention rõ ràng. Tên flag phải đọc là biết ngay flag bật thì điều gì xảy ra. Dùng dạng feature_action_state, ví dụ checkout_express_enabled, search_ranking_v2_enabled. Tránh tên mơ hồ như flag_new, test123, temp_fix. (Nguyên tắc đặt tên chi tiết ở Bài 55.) Quy ước nên là: flag bật = true luôn nghĩa là "tính năng mới/hành vi mới đang chạy".

Bước 4 — Định nghĩa targeting và trạng thái mặc định. Ghi rõ: mặc định flag tắt hay bật? Bật cho ai (toàn bộ / phần trăm / allowlist / theo thuộc tính)? Với release flag, mô tả lộ trình rollout (1% → 100%). Lưu ý: rollout chi tiết là chủ đề Bài 38, ở đây chỉ cần nêu flag hỗ trợ targeting gì.

Bước 5 — Gán owner và vòng đời. Mỗi flag ghi rõ: ai sở hữu (được quyền gạt), và điều kiện gỡ bỏ. Với release flag: "xóa sau khi bật 100% và ổn định 1 tuần". Với operational flag: "giữ vĩnh viễn". Đây là phần PM hay bỏ quên nhất, dẫn đến flag rác.

Bước 6 — Định nghĩa quy trình khẩn cấp. Với mọi kill switch, ghi rõ: ai được phép tắt, tắt bằng cách nào (dashboard nào), và tắt thì người dùng đang dùng dở thấy gì (rơi về hành vi cũ hay thấy thông báo lỗi?). Một kill switch mà không ai biết cách dùng lúc 2 giờ sáng thì vô dụng.

Một block mẫu để dán vào Spec:

Feature Flag: ai_tutor_enabled
  • Loại: Release flag
  • Mặc định: OFF
  • Targeting: internal allowlist → 5% Pro → 25% Pro → 100% Pro
  • Owner: PM (Khang)
  • Điều kiện xóa: sau khi bật 100% ổn định 7 ngày
  • Kill switch riêng: ai_tutor_kill_switch (operational, giữ vĩnh viễn)

Lỗi thường gặp & mẹo

Lỗi 1 — Gộp release flag và kill switch làm một. Hai loại này có vòng đời ngược nhau: một cái cần xóa, một cái cần giữ. Gộp lại khiến bạn không bao giờ dám xóa, và codebase ngày càng nặng. Mẹo: luôn tách thành hai flag riêng nếu tính năng vừa cần rollout dần vừa cần nút tắt lâu dài.

Lỗi 2 — Không gắn owner và expiry. Flag không chủ là flag bất tử. Mẹo: quy định trong Spec rằng mọi release flag phải có owner + ngày hết hạn, và đề nghị team bật cảnh báo CI khi flag quá hạn.

Lỗi 3 — Tên flag đảo nghĩa. Flag tên disable_new_checkout mà bật thì... tắt tính năng mới — gây nhầm lẫn chết người lúc khẩn cấp. Mẹo: luôn để "bật = hành vi mới đang chạy", tên flag mô tả cái được bật.

Lỗi 4 — Quên định nghĩa hành vi khi tắt. Khi gạt kill switch, người dùng đang ở giữa luồng thấy gì? Mẹo: trong Spec luôn ghi "fallback behavior" — thường là rơi về hành vi cũ một cách êm ái, không phải màn hình lỗi.

Lỗi 5 — Dùng flag thay cho quản lý cấu hình dài hạn. Flag để bật/tắt tính năng, không phải để lưu mọi cấu hình kinh doanh. Nếu một "flag" tồn tại mãi và mang giá trị cấu hình (ví dụ mức phí), nó thực ra là entitlement/config — hãy gọi đúng tên và quản đúng chỗ.

Mẹo vàng: giữ một "flag registry" — một bảng (Notion/sheet) liệt kê mọi flag đang sống, loại, owner, ngày tạo, ngày dự kiến xóa. Mỗi sprint review lướt qua bảng này 5 phút. Đây là cách rẻ nhất để flag debt không tích tụ.

Bài tập thực hành

Lấy một tính năng bạn đang hoặc sắp viết Spec (nếu chưa có, dùng đề bài: "thêm tính năng đặt lịch học 1:1 với mentor có thanh toán").

  • Liệt kê tất cả các flag tính năng cần có. Với mỗi flag, viết một dòng mô tả "flag bật thì điều gì xảy ra".
  • Phân loại từng flag vào 4 loại (release / operational / experiment / permission). Giải thích vì sao.
  • Đặt tên cho mỗi flag theo convention feature_action_state.
  • Với mỗi release flag, viết lộ trình targeting (mặc định OFF → bật cho ai → 100%) và điều kiện xóa.
  • Xác định ít nhất một kill switch: ai sở hữu, tắt bằng cách nào, và người dùng đang dùng dở sẽ thấy gì.
  • Viết block "Feature Flag Strategy" hoàn chỉnh theo mẫu ở phần Hướng dẫn, sẵn sàng dán vào Spec.
Tự chấm: nếu bạn có ít nhất một flag mỗi loại được dùng (release + operational), mỗi flag có owner và điều kiện gỡ bỏ rõ ràng, và kill switch có quy trình khẩn cấp — bạn đã viết một flag strategy chuyên nghiệp.

Tóm tắt

  • Feature flag tách "merge code" khỏi "ra mắt tính năng", biến việc ra mắt thành một quyết định sản phẩm có thể đảo ngược trong vài giây.
  • bốn loại flag với vòng đời khác nhau: release (ngắn hạn, phải xóa), operational/kill switch (vĩnh viễn), experiment (theo thí nghiệm), permission (vĩnh viễn). Phân loại sai dẫn đến flag rác hoặc thiếu nút tắt.
  • Mỗi flag trong Spec phải có: loại, tên rõ nghĩa, targeting, trạng thái mặc định, owner, và điều kiện gỡ bỏ.
  • Tính năng chạm đường đi quan trọng hoặc tốn chi phí bắt buộc có kill switch riêng, với quy trình khẩn cấp ghi rõ ai gạt và người dùng thấy gì khi tắt.
  • Flag debt là nợ thật: không có owner và expiry, flag sẽ tồn tại mãi và gây lỗi. Duy trì một flag registry và rà soát định kỳ.
Viết flag strategy không phải để làm hài lòng engineer — nó là cách bạn, với tư cách PM, giành lại quyền kiểm soát khi nào, cho ai, và làm sao tắt tính năng của mình.