SaaS Feature Flag:灰度开关不是 if else 到处写
SaaS Feature Flag:灰度开关不是 if else 到处写
一、灰度开关会快速失控
SaaS 产品需要灰度发布、客户定制、套餐控制和实验验证。Feature Flag 是常见方案,但如果只是到处写if enabled,代码会很快变成开关迷宫。没人知道哪个开关还在用,哪个客户被打开,哪个开关可以删除。
Feature Flag 不是简单条件判断,而是一套配置、评估、审计和清理机制。
一个真实的教训:某协作 SaaS 两年积累了 140 多个开关,其中 60 多个已经没人记得用途。一次性能优化时,团队发现一个三年前的实验开关仍在每次请求时查询数据库。删掉它后,响应时间下降 12%。更麻烦的是,没人敢删剩下的 60 个——怕某个大客户正在依赖。
二、先区分开关类型
flowchart TD A[Feature Flag] --> B[发布灰度] A --> C[套餐权限] A --> D[客户定制] A --> E[实验开关] A --> F[紧急熔断]发布灰度通常短期存在,套餐权限长期存在,客户定制需要合同依据,实验开关需要统计口径,紧急熔断需要快速生效。不同类型生命周期不同,不能混在一个布尔字段里。
开关命名也要体现意图。new_ui很快会过期,dashboard_v2_rollout更容易知道它是发布灰度。
两类错误的对比:把长期开关当短期管,会导致清理困难;把短期灰度当长期开关做,会导致配置中心堆满历史垃圾。实践中,发布灰度必须设自动过期时间,到期未清理就自动关闭并告警。客户定制则必须有合同追溯,定期和 CSM 确认是否仍需保留。
三、开关评估要集中
type FeatureFlagContext = { tenantId: string userId: string plan: string region: string } function isEnabled(flag: string, ctx: FeatureFlagContext): boolean { return flagService.evaluate(flag, ctx) }集中评估能统一日志和审计。不要在前端、后端、任务系统各写一套判断,否则同一个客户可能看到不一致行为。
feature_flag_policy: require_owner: true require_expiry_for_rollout: true audit_customer_override: true cleanup_expired_flags: true每个开关都要有 owner 和过期时间。没有 owner 的开关,最后没人敢删。
四、开关要进入测试矩阵
开关组合会爆炸。至少要测试默认关闭、目标客户开启、权限不足、紧急关闭四种场景。对于核心流程,开关不能只靠人工点页面验证。
还要监控开关命中。某个实验开关没人命中,说明实验配置错误;某个灰度开关长期只开给一个客户,可能已经变成定制分支。
Feature Flag 还要进入发布回滚流程。某个新功能上线后出现问题,能通过开关关闭是一回事,关闭后数据状态是否兼容是另一回事。功能开关不能替代数据迁移和回滚设计。
type FeatureFlagMeta = { key: string type: "rollout" | "plan" | "experiment" | "kill_switch" owner: string expiresAt?: string rollbackNote?: string }前后端开关还要保持一致。前端隐藏入口但后端接口仍允许调用,会造成权限漏洞;后端关闭但前端仍显示入口,会造成用户困惑。核心开关应以后端评估为准,前端只做体验优化。
清理开关要成为固定工作。每次版本发布后,检查过期灰度、结束实验和废弃定制。开关清理不性感,但它能防止代码变成历史包袱。
实践中的关键洞察
从实际项目经验来看,上述方案的落地效果高度依赖于两个前提条件。第一,团队需要对核心指标达成共识,而不是各说各话。第二,监控和反馈机制必须自动化,手工检查在团队规模扩大后会迅速失效。创业团队最宝贵的资源是创始人的注意力,任何需要人工盯盘的流程本质上都在消耗这个有限资源。
回到根本问题:技术决策最终服务于商业目标。在资源受限的创业阶段,每一次架构选择、每一项工具选型、每一个流程设计,都应该可以追溯到它对用户价值、团队效率或公司生存概率的影响。那些无法回答"这个决定如何帮助我们活得更久或跑得更快"的技术投入,都值得重新审视优先级。
五、总结
SaaS Feature Flag 要按类型管理生命周期,集中评估、审计覆盖、设置 owner 和过期时间。
灰度开关不是到处写 if else。没有治理的开关,最终会变成产品和代码的双重债务。
