Nếu feature flag chỉ là if (flag) { ... } trong code, bạn đang dùng nó như công tắc. Trên production, flag thực chất là chính sách (policy) phân phối hành vi — có hiệu lực theo thời gian, theo người dùng, theo môi trường, và quan trọng nhất: có blast radius khi sai.

Bài này không lặp lại phần “flag vs config runtime” (đã có bài giới thiệu trong blog); thay vào đó, bài đặt flag trong khung taxonomy + lifecycle + governance. Đối tượng: engineer middle+ đã triển khai flag thật và từng thấy “flag hell” hoặc incident do rollout. Mười bài Engineering (020–029) là loạt độc lập: không cần đọc theo thứ tự thời gian đăng.

Phạm vi: không so sánh vendor (LaunchDarkly/Unleash/…); không deep A/B thống kê. Không bàn: thiết kế SDK từng dòng.

Tham chiếu nội bộ: Feature flags vs Config runtime.


1. Taxonomy: ba họ flag và invariant cần giữ

1.1. Release flag

Mục đích: tách triển khai khỏi bật tính năng (deploy ≠ enable).

  • Invariant: flag phải có ngày chết (sunset) trong backlog; merge code “đường cũ” phải được xoá sau khi ổn định.
  • Rủi ro hệ thống: nhánh code song song lâu ngày → conflict, test matrix phình to.

1.2. Experiment flag

Mục đích: đo hành vi (conversion, latency theo cohort), thường gắn analytics.

  • Invariant: cohort phải ổn định có kiểm soát (sticky theo user/session); metric phải gắn version của thử nghiệm.
  • Rủi ro: peeking (nhìn sớm kết quả rồi dừng sai lúc); sai baseline làm kết luận sai.

1.3. Ops / kill-switch

Mục đích: giảm áp lực hệ thống khi downstream sập hoặc bug nghiêm trọng.

  • Invariant: đường dẫn đánh giá flag phải rẻ và ít phụ thuộc (tránh gọi DB mỗi request nếu không cần); có runbook (ai được bật, bật bao lâu, rollback).
  • Rủi ro: bật kill-switch nhưng cache client/server vẫn phục vụ hành vi cũ → “đã tắt mà vẫn chết”.

2. Lifecycle: tạo → rollout → retire (chỗ nợ kỹ thuật tích tụ)

2.1. Tạo

Checklist tối thiểu khi tạo flag mới:

Câu hỏiVì sao quan trọng
Loại flag thuộc họ nào ở mục 1?Quyết định metric, TTL, ownership.
Default off hay on trong prod?Ảnh hưởng incident khi provider flag chậm/timeout.
Ai có quyền thay đổi trong prod?Tránh “một người bật thử” làm thay đổi hành vi hàng triệu user.

2.2. Rollout

  • Phần trăm dần: blast radius nhỏ dần; theo dõi error rate/latency theo cohort (không chỉ “toàn site”).
  • Canary theo tenant/region: hữu ích khi bug chỉ lộ ở subset dữ liệu.

2.3. Retire

Đây là bước team hay bỏ quên. Nợ thật là: số flag tăng → độ phức tạp nhận thức tăng → mỗi lần refactor phải “đoán” hành vi.

Quy tắc thực dụng: mỗi sprint có quota xoá flag (ví dụ tối thiểu 2 flag release đã ổn định), hoặc rule “flag release > 30 ngày không đổi → ticket bắt buộc xoá nhánh”.


3. Ma trận trạng thái: cache, stale, “ai bật gì”

3.1. Nơi flag được đánh giá

  • Server-side: dễ audit, dễ nhất quán; phụ thuộc latency tới provider.
  • Client-side / edge: phản hồi nhanh; khó đảm bảo mọi client cùng phiên bản logic.

3.2. Stale và eventual consistency

Khi flag đổi, các lớp sau có thể vẫn “nhớ” giá trị cũ:

  • CDN / service worker / browser cache.
  • In-process cache trên app server (TTL quá dài).
  • Mobile app bundle cũ không gọi API evaluate mới.

Invariant vận hành: định nghĩa rõ SLO “thời gian tối đa để hành vi đổi sau khi toggle” cho từng loại flag — đặc biệt kill-switch.

3.3. Audit và accountability

Incident hay gặp: “không ai thừa nhận đã bật”. Tối thiểu cần: log structured (flag key, old→new, actor, reason code), và quyền RBAC theo môi trường.


4. Governance theo quy mô team

Quy môTối thiểu nên có
Nhỏ (1 team)Naming convention, default an toàn, checklist retire.
Vừa (2–5 team)Catalog flag, owner per flag, policy “ai merge được default prod”.
Lớn + nhiều servicePlatform evaluate chuẩn, schema versioning, runbook kill-switch, dashboard cardinality theo flag.

Không nhất thiết phải “mua công cụ đắt” ngay; nhưng thiếu quy ước thì công cụ chỉ là UI cho nợ.


5. Kết luận có điều kiện

  • Nên dùng taxonomy rõ để mỗi flag có lifecycle và owner tương ứng.
  • Không nên dùng flag như .env có nút bấm cho mọi tham số vận hành — ranh giới với config runtime đã trình bày ở bài tham chiếu.
  • Khi không cần flag: thay đổi nhỏ, reversible bằng deploy nhanh + canary ở tầng infra, và test đủ tốt — flag thêm chi phối nhận thức.

6. Giải thích thêm: vì sao “flag” lại là rủi ro hệ thống

Flag nhìn kỹ thuật nhưng ảnh hưởng hành vi sản phẩmđường thở vận hành. Khi một flag đổi, bạn thay đổi phân phối code path — tức là thay đổi tập hợp trạng thái hệ thống có thể đạt được. Điều này làm tăng:

  • Không gian kiểm thử tổ hợp: mỗi cờ bật/tắt nhân đôi nhánh (trong thực tế không phải lũy thừa tuyệt đối vì nhiều cờ tương quan, nhưng vẫn đủ để QA “không biết user đang ở cấu hình nào”).
  • Chi phối nhận thức khi debug: log cùng một request_id nhưng hai cờ khác nhau có thể dẫn tới hai stack lỗi khác nhau.
  • Phụ thuộc thời gian: “hôm qua chạy” không còn đúng nếu cờ đổi lúc nửa đêm.

Vì vậy governance không phải “giấy tờ” — nó là cách giữ entropy dưới ngưỡng mà team vẫn ship được.


7. Kịch bản minh họa (hư cấu nhưng đúng cơ chế)

7.1. Release flag quên retire

Team A bật NEW_CHECKOUT cho 100% sau hai tuần canary. Một tháng sau, nhánh if (!flag) vẫn còn vì “sợ rollback”. Khi sửa bug thuế ở checkout, developer phải sửa hai nhánh; một nhánh bị sót → bug chỉ lộ cho user “cũ” (thực tế là subset test không cover). Bài học: retire là một phần của definition of done của feature, không phải việc làm thêm.

7.2. Experiment không version metric

Hai tuần A/B nhưng dashboard không gắn experiment_version. Tuần thứ ba sửa copy nút CTA — cohort vẫn sticky nhưng ý nghĩa thống kê đã đổi. PM kết luận “variant B thắng” trong khi một phần traffic là B-v1 và một phần là B-v2. Bài học: mọi experiment cần dimension “schema/version” trong telemetry.

7.3. Kill-switch bật nhưng edge vẫn cũ

CDN cache HTML chứa bundle reference cũ; service worker precache giữ JS cũ gọi API path cũ. Ops bật DISABLE_HEAVY_REPORT trên server nhưng 5% user vẫn hammer endpoint vì client cũ. Bài học: định nghĩa SLO propagate; có thể cần 429 + Retry-After ở gateway như lớp an toàn cuối.


8. Naming, key space và “flag coupling”

Quy ước đặt tên giảm lỗi con người khi grep và khi audit:

  • Prefix theo domain: billing_, checkout_, ops_.
  • Tránh tên mơ hồ: new_ui → sau 6 tháng không ai biết “new” là gì.
  • Coupling: hai flag AB chỉ hợp lệ khi (A && !B) — nếu không enforce bằng code hoặc policy, bạn đã tạo trạng thái cấm mà runtime vẫn có thể vào được do thứ tự toggle sai.

Có thể dùng object policy thay cho ma trận boolean rải rác (ví dụ một struct “Checkout rollout phase” thay vì 4 cờ độc lập).


9. Đánh giá provider / tự host (không chọn vendor, chỉ tiêu chí)

Khi chọn cơ chế evaluate (SDK, HTTP sidecar, file local), hãy chấm điểm tối thiểu:

Tiêu chíCâu hỏi
AvailabilityProvider chậm/timeout — app fail-open hay fail-closed?
Latency budgetEvaluate mỗi request hay batch + cache?
Multi-regionFlag có nhất quán giữa region không, delay bao lâu chấp nhận được?
DRCó chế độ degrade khi mất kết nối không?

Fail-open (mặc định bật tính năng khi không đọc được flag) vs fail-closed (tắt) là quyết định sản phẩm + rủi ro, không chỉ kỹ thuật.


10. FAQ nhanh cho tech lead

Hỏi: Có nên cho PM tự bật flag production không?

Đáp: Tuỳ loại. Experiment thường cần PM; kill-switch nên giới hạn role + hai người xác nhận (break-glass). Quan trọng là audit log, không phải danh hiệu chức danh.

Hỏi: Bao nhiêu flag là quá nhiều?

Đáp: Không có con số phổ quát; có entropy budget: nếu một engineer mới không thể vẽ sơ đồ “các cờ ảnh hưởng checkout” trong 30 phút, bạn đã vượt ngưỡng vận hành.

Hỏi: Flag có thay thế được kiến trúc module không?

Đáp: Không. Flag điều khiển lộ trình triển khaithử nghiệm; ranh giới module vẫn cần thiết kế riêng.


Bài tập ngắn

  1. Liệt kê 10 flag đang tồn tại trong repo của bạn và gán họ (release/experiment/ops). Có bao nhiêu flag không có owner?
  2. Chọn một kill-switch giả định: vẽ 3 lớp cache có thể làm stale — bạn sẽ đo “thời gian propagate” thế nào?
  3. Viết một invariant cho từng họ flag (một dòng/họ) và treo trong CONTRIBUTING hoặc wiki team.
  4. Tìm một cặp flag có coupling ngầm: đề xuất gộp thành enum phase hoặc state machine rõ ràng.

Tóm tắt cho người vội

  • Phân loại flag → gán lifecycle và owner khác nhau.
  • Retire release flag là nợ kỹ thuật nếu bỏ quên.
  • Kill-switch cần SLO propagate + có thể cần lớp gateway.
  • Governance = entropy budget + audit, không phải mua tool.

Đọc thêm

  • Feature flags vs Config runtime
  • Martin Fowler — Feature Toggles (khái niệm cổ điển, vẫn đáng đọc lại phần phân loại)
  • Google SRE — tư duy blast radius, error budget (bối cảnh chung cho rollout)
  • Accelerate — Forsgren et al. (DORA: lead time, deploy frequency — liên hệ gián tiếp tới discipline rollout)