当功能开关(feature flag)让同一URL在不同条件下返回不同内容时,提交记录本身会失真:你提交的是某一时刻的页面状态,而线上可能已经切换到另一套输出。要解决这个问题,不能只记“提交过”,而要把开关状态、渲染快照和提交动作绑定成一条可回溯的版本记录。下面按保留、改写、退出三种取舍展开,并说明各自适用的前提。
功能开关引发的页面变化,通常落在三种形态里,对应的记录方式完全不同。
判断依据不是开关的名字,而是关掉开关后页面是否还能独立成立。如果关掉后页面内容残缺、依赖另一分支才有意义,说明这不是普通开关,而是两套页面共用了一个URL,记录方式要按两套状态分别存。
保留完整版本记录的成本不低,它只在以下条件同时成立时才划算:开关组合数量有限且可枚举;每个状态都会对外真实可见一段时间;该URL承载的流量或转化价值较高。
具体动作是给每次提交建立一条记录,字段至少包含:URL、开关名与取值、渲染时间、渲染后正文的哈希、提交方式、以及本次提交对应的开关组合。哈希用sha256对渲染后可见文本计算即可,作用是快速判断“这次和上次是不是同一状态”,而不是用来证明内容正确。
假设某页面有“登录态”和“访客态”两个开关分支,访客态抓取正常,登录态抓取到的是空壳。如果只保留一条提交记录,事后无法区分异常是开关切换造成还是模板本身出错。分开存档后,你可以直接对比两个状态的渲染文本,定位差异出现在哪一层。这个对比结果会决定下一步是改模板还是改开关默认值。
边界:如果开关由实验平台按用户随机分配,且同一URL对同一抓取器每次返回都不同,那么逐状态存档就失去意义——你无法保证下次抓取落在哪个分支。这种情况下应转向下面的“改写”策略。
当开关组合太多、随机分配或频繁变动时,保留全部状态不现实,正确做法是改写提交目标,让提交只针对一个确定状态。
常用手段有两种。一是固定一套“抓取专用”的开关取值,通过服务端识别抓取器UA或来源特征返回稳定版本。二是把变化部分移出主文档,用异步加载或独立URL承载,主文档保持结构稳定。
选择哪一种,取决于变化内容是否必须被索引。若变化内容本身需要被收录,异步加载可能让它难以进入渲染结果,此时应优先考虑独立URL;若变化内容只是辅助模块,异步即可。
改写后要重新记录:提交的到底是稳定版还是含开关的版本?如果提交的是稳定版,那么开关变化不再触发提交记录更新,但你必须额外记录一条“稳定版与线上默认版的差异说明”,否则下次有人看到线上和提交不一致时会误判为故障。
实际动作:对改写后的稳定版做一次渲染快照,与线上默认开关状态下的快照做文本差异比对。差异只应出现在你预期被排除的模块里;如果主内容也出现差异,说明改写没生效,下一步应回到开关层排查,而不是继续提交。
退出不是放弃,而是承认这个URL不适合用提交来管理状态。常见前提有三类。
退出的具体动作是:停止新增提交记录,但在记录中标注退出原因与退出时间,并保留最后一次有效快照。这一步的意义在于,当后续有人问“这个URL为什么没有近期提交记录”时,能直接看到是主动退出而非遗漏。退出决定本身也会影响下一步——如果退出是因为内容将收敛,那么收敛完成后应重新建立一条基线记录,而不是沿用旧记录。
无论选保留、改写还是退出,一条可复查的版本记录至少要回答三个问题,缺一个都会让后续判断失去依据。
另外要区分“提交后抓取量变化”和“提交本身生效”这两件事。抓取量上升或下降可能来自开关切换、站点整体抓取预算调整、或其他页面的连带影响,不能单独作为提交是否正确的证据。同样,某个状态的抓取量归零,也可能是开关默认值改变或抓取器恰好落在另一分支,需要结合开关记录和渲染快照一起看,而不是直接断定提交失败。
最后,记录格式不必复杂,但必须让下一个接手的人能在不看代码的情况下,仅凭记录判断“当时页面长什么样、为什么提交这个版本”。做不到这一点,版本记录就只是日志,不是决策依据。