没有不值得去解决的问题,也没有不值得去学习的技术!

WordPress 英文文章为什么会遗漏新增标签?一次 Redis 缓存问题的排查与修复

【图1:修复后的英文文章编辑页面。右侧标签面板完整显示 8 个英文标签,没有再出现部分标签遗漏的问题。】

作者:

在

最近,我在继续整理 A Tour of Go 多语言翻译项目的开发博客。

为了方便不同语言的读者阅读,我通常会先发布中文文章,再使用 SlyTranslate 将文章翻译成英文。

除了标题和正文,文章的标签也需要同步翻译。

我的操作流程比较固定:

  1. 发布中文文章,并设置相应标签。
  2. 执行批量脚本,为尚未翻译的中文标签创建英文版本。
  3. 检查并完善英文标签的名称与别名。
  4. 使用 SlyTranslate 翻译文章。
  5. 检查英文草稿,确认没有问题后发布。

按照这个流程,只要中文文章有 8 个标签,而且这 8 个标签都已经建立英文对应关系,英文文章就应该关联相应的 8 个英文标签。

但实际情况并不是这样。

这次遇到的问题很有意思:原来就存在的英文标签能够正常继承,刚刚创建的英文标签却会遗漏。

更奇怪的是,数据库中的标签和翻译关系明明都是正确的。

一、第一次检查:英文文章少了两个标签

2026 年 10 月 20 日,我在处理一篇新的开发博客时,再次发现了标签遗漏问题。

当时的中文文章 ID 为 27889,一共设置了 8 个标签。

其中,6 个标签已经有对应的英文版本,另外 2 个是新增加的。

我按照平时的流程执行标签同步脚本。

同步完成后,两个新标签也都成功创建了英文版本:

中文标签英文标签对应关系
本地内容覆盖Local Content Override46228 → 46232
站点架构Site Architecture46230 → 46234

随后,我手动修改了新英文标签的名称和别名,并再次检查中英文标签的对应关系。

检查结果表明,8 个中文标签全部都有正确的英文对应关系。

到这里,我原本认为标签准备工作已经完成,可以开始翻译文章了。

但英文草稿生成后,问题再次出现。

英文草稿 ID 为 27892,实际只有 6 个标签。

恰好缺少刚刚创建的两个英文标签:

  • Local Content Override
  • Site Architecture

其他 6 个原来就存在的标签全部正常。

这一次,我特别注意了检查时机。

在打开英文文章编辑器之前,我就已经通过数据库和接口检查,确认英文草稿只有 6 个标签。

因此,可以排除一种可能:不是因为打开编辑器、保存文章或者页面显示异常,才导致两个标签丢失。

遗漏发生在翻译生成英文草稿的过程中,或者与这个过程直接相关的标签关联环节。

二、数据库明明正确,为什么程序却读不到?

接下来,我开始检查 Polylang 保存的标签翻译关系。

Polylang 是 WordPress 中负责多语言内容关联的插件。

例如,中文标签“站点架构”与英文标签“Site Architecture”,并不是单纯通过名称对应,而是由 Polylang 保存它们之间的翻译关系。

检查结果显示,这两个新标签的翻译关系在数据库中都存在。

而且关系没有写错。

这就出现了一个需要解释的矛盾:

数据库知道中文标签对应哪个英文标签,但 SlyTranslate 在迁移标签时,似乎没有获取到这些对应关系。

之前排查时,我还发现过另一个现象。

英文标签已经改成英文名称,但通过某些 WordPress 接口读取,得到的仍然是修改前的中文名称。

直接查询数据库,结果却是正确的英文名称。

这让我开始怀疑缓存。

不过,怀疑并不意味着已经找到原因。

如果只因为接口数据和数据库不一致,就直接清理整个 Redis 缓存,虽然可能暂时恢复正常,却无法解释问题究竟出在哪里。

而且,清理缓存只是消除当前的旧数据,并不能保证下一次创建新标签时不会再次发生。

所以,我决定继续调查具体的缓存读取过程。

三、真正的突破:命令行和网站读到了不同的缓存

进一步检查 WordPress、Polylang 和 W3 Total Cache 的实际代码后,发现了一个非常关键的细节。

服务器命令行和网站请求使用的缓存键并不完全相同。

我的标签批量同步脚本通过 PHP 命令行执行。

而文章翻译请求是在 WordPress 网站环境中执行的。

网站安装的 W3 Total Cache 会将请求使用的主机名纳入对象缓存键。

这里的主机名,可以简单理解为请求对应的网站域名。

正常访问网站时,请求携带网站域名。

但原来的标签批处理脚本直接加载 WordPress,没有在加载前设置对应的主机名。

于是,两种执行环境可能使用不同的缓存位置。

这意味着:

命令行刚刚创建了英文标签,并更新了它所使用的缓存。

但网站请求使用的另一份缓存,可能仍然保留着英文标签创建之前的旧数据。

问题并不在于数据库没有保存成功,而在于网站读取到了旧缓存。

这次调查没有只停留在推测阶段。

通过只读方式检查 Redis 中实际保存的对象后,找到了对应的证据:

翻译组数据库中的对应关系网站旧缓存中的对应关系
46229中文 46228、英文 46232只有中文 46228
46231中文 46230、英文 46234只有中文 46230

这两条缓存记录恰好对应遗漏的两个新英文标签。

也就是说,数据库中的翻译组已经包含中英文两个成员,但网站旧缓存中的翻译组仍然只有中文成员。

接下来,就需要看看 SlyTranslate 遇到这种情况时会怎么处理。

四、为什么只遗漏新标签,而且没有报错?

检查 SlyTranslate 的代码后,发现标签迁移流程大致是这样的:

首先,读取中文文章关联的全部标签。

然后,逐个调用 Polylang 的接口,查询每个中文标签对应的英文标签。

如果查询成功,就把英文标签加入待关联列表。

如果查询不到,插件会根据配置决定是否自动创建英文标签。

而我的网站并没有启用翻译文章时自动创建标签的功能。

原因也很简单:我已经有专门的标签同步脚本,希望先准备好英文标签,再进行文章翻译。

因此,当 Polylang 因旧缓存返回“找不到英文对应标签”时,SlyTranslate 会直接跳过这个标签。

不会停止整个翻译过程。

也不会提醒我哪些标签被遗漏。

最终,插件只把成功找到的 6 个英文标签关联到草稿,然后返回成功。

这就解释了此前观察到的现象:

  • 已存在较久的英文标签,缓存中的关系也是完整的,所以能够正常继承。
  • 刚刚创建的英文标签,网站旧缓存中还没有对应关系,所以被跳过。
  • 整篇文章仍然翻译成功,但标签数量少了两个。

这条触发链已经通过实际缓存内容和离线测试进行了验证。

不过,这里仍然需要区分证据与推断。

我们没有捕获到发生故障的那一次翻译请求内部的逐标签日志,因此不能说已经百分之百还原了当时每一次函数调用。

更准确的结论是:发现了与故障现象直接匹配的旧缓存,并且可以利用这些真实缓存数据复现“翻译成功,但遗漏两个新标签”的结果。

对我来说,这已经比单纯猜测 Redis 缓存异常有价值多了。

五、最初考虑修复缓存同步,后来改变了方向

找到缓存命名空间不一致的问题后,最初考虑的是修复标签批处理脚本。

方法并不复杂。

在加载 WordPress 之前,为命令行设置与网站请求一致的主机名。

这样,命令行和网站就可以使用相同的缓存命名空间。

同时,再修改 SlyTranslate,让它在发现标签对应关系不完整时明确报错,而不是继续跳过。

这个方案可以解决一部分问题,也比较符合正常的缓存维护思路。

但继续考虑后,我觉得还有一个更简单的方向。

既然问题发生在翻译文章时读取标签对应关系,那么能不能只让这个环节读取最新数据?

我的需求其实很明确:

我不要求整个 WordPress 网站都绕过缓存。

也不需要重新设计 Redis 的缓存管理机制。

更没有必要为此修改 Polylang 或 W3 Total Cache 的内部代码。

我只希望翻译文章时,能够准确找到每个中文标签对应的英文标签。

因此,我倾向于把修复限制在 SlyTranslate 中。

这也符合我最近处理项目性能和架构问题时逐渐形成的原则:

先解决当前真正影响使用的问题。

如果一个问题能够通过局部修改解决,就不要急着把它扩大成整个系统的架构改造。

六、最终只修改了 SlyTranslate 的一个文件

最终选择的方案,只修改:

PolylangAdapter.php

主要做了三件事。

1. 读取源文章标签时绕过查询结果缓存

让插件获取中文文章标签时,不再依赖可能已经过期的查询结果。

不过,单纯关闭这一层缓存还不够。

因为 Polylang 查询标签对应关系时,内部仍然可能读取另一层对象缓存。

这正是本次故障的关键。

2. 查询标签对应关系时,直接读取数据库

新增一个专门用于标签迁移的内部函数:

get_uncached_tag_translation()

它通过参数化的只读数据库查询,获取 Polylang 保存的中英文标签对应关系。

这样,即使 Redis 里仍然保存着旧翻译组,也不会影响这一步获取最新映射。

需要说明的是,这个方案使用了当前 Polylang 版本的内部数据保存格式。

它不是 Polylang 官方公开接口提供的通用功能。

因此,未来升级 Polylang 时,需要重新确认这部分代码是否仍然适用。

我认为这个代价可以接受。

毕竟,依赖集中在一个小范围的函数中,比为了修复一个标签迁移问题而改造全站缓存机制更容易控制。

3. 标签写入之后,再检查一遍

过去的实现有一个明显不足。

调用 WordPress 的标签关联接口后,并没有确认所有标签是否真的关联成功。

现在增加了完整性检查。

例如,预期应该关联 8 个英文标签,那么在执行完标签关联以及相关同步操作后,需要重新读取实际标签列表。

不仅比较数量,还要比较具体的标签 ID。

只有预期集合与实际集合完全一致,才能认为标签迁移成功。

如果缺少标签、出现多余标签,或者数据库查询出错,就明确返回错误。

这样,即使以后遇到其他原因导致部分标签写入失败,也不会再轻易出现“文章翻译成功,但标签悄悄少了几个”的情况。

整个方案没有清理 Redis 缓存,也没有引入自动重新翻译机制。

同时,它采用了更严格的策略:翻译前必须准备好全部英文标签对应关系。如果对应关系不完整,就报告错误,而不是跳过标签继续完成草稿。

七、部署之前,先验证真实故障能否复现

这次没有直接把修改后的 PHP 文件上传服务器。

先使用之前保存的真实缓存数据进行离线测试。

测试重点不是简单确认函数能够正常运行,而是确认原来的故障能否被准确复现,以及修改后的程序能否避免相同结果。

其中最重要的对照是:

测试情况原版 SlyTranslate修改后的版本
使用实际旧缓存数据返回成功,仅关联 6 个标签正确关联全部 8 个标签
标签对应关系不完整可能直接跳过明确报错
标签只写入一部分可能继续返回成功检查失败
后续操作移除了某个标签缺少最终核验能够发现遗漏

最终,23 项离线检查全部通过。

PHP 语法检查也通过。

当然,离线测试仍然不能代替真正的网站环境。

所以我决定先部署修复,再用下一篇正常发布的博客验证。

这样既不用专门制造测试文章,也不需要对已经翻译好的文章重复调用翻译模型。

八、生产部署过程也遇到了两个小问题

代码修复完成后,还需要将文件部署到生产服务器,并提交至 GitHub。

部署时,我要求先核对生产原文件的哈希值,确认与本地分析的版本完全一致。

随后备份原文件,再替换为修复版本。

整个过程只涉及 SlyTranslate 的一个 PHP 文件。

但第一次执行部署命令时,出现了一个错误:

php: 未找到命令

原因并不是服务器没有安装 PHP,而是 PHP 程序没有加入当前命令行的默认搜索路径。

服务器实际使用的 PHP 路径是:

/usr/local/php/bin/php

改用这个路径后,PHP 语法检查通过,生产文件也成功替换。

接着,在提交 GitHub 时又遇到一个小问题。

因为仓库中保存了一份代码补丁文件,Git 的空白检查把补丁中的部分上下文缩进识别为格式问题。

但这些内容属于补丁文件本身,并不是实际 PHP 代码的错误。

因此,我没有为了消除提示而随意修改补丁内容。

最后改为分别检查真实代码的格式,以及补丁能否正确应用。

检查通过后,成功提交并推送到 GitHub。

本次提交:

36a9621:修复 SlyTranslate 翻译时新增英文标签遗漏

至此,代码修复和生产部署全部完成。

但我还没有把这个问题直接判定为彻底解决。

因为还有最后一步:使用真实的新文章验证。

九、下一篇真实博客验证通过

修复部署完成后,我继续按照平时的流程处理下一篇开发博客。

没有重新设计测试流程,也没有专门创建测试文章。

在正常完成中文文章发布、标签准备和英文翻译之后,我检查了新生成的英文文章。

这次英文文章 ID 为 27896。

文章关联了完整的 8 个英文标签:

  • code block
  • Code Comments
  • Completeness Audit
  • Go
  • Go Documentation
  • localization
  • Multilingual Translation
  • Translation Completeness

我先在 WordPress 后台确认了标签列表。

【图1:修复后的英文文章编辑页面。右侧标签面板完整显示 8 个英文标签,没有再出现部分标签遗漏的问题。】
【图1:修复后的英文文章编辑页面。右侧标签面板完整显示 8 个英文标签,没有再出现部分标签遗漏的问题。】

随后,我又打开了英文文章的正式前台页面。

页面底部同样显示完整的 8 个标签。

【图2:英文文章正式前台页面底部的标签列表。全部 8 个标签均已正常显示,确认前台展示结果与后台一致。】
【图2:英文文章正式前台页面底部的标签列表。全部 8 个标签均已正常显示,确认前台展示结果与后台一致。】

至此,修复后的正常翻译流程已经通过了一次真实文章验证。

这比单纯看到 PHP 语法检查通过,或者离线测试全部成功,更能说明修改确实解决了当前使用中遇到的问题。

不过,我也没有因此把整个 WordPress 缓存体系判定为完全没有问题。

本次修改解决的是 SlyTranslate 在迁移文章标签时读取旧翻译映射的问题。

其他地方如果仍然依赖不同缓存命名空间,未来可能还有独立的缓存同步问题需要处理。

但那些问题不属于这次必须完成的修复范围。

十、这次排查带来的几个认识

回顾整个过程,我觉得有三点值得记录。

第一,数据库中的数据正确,不代表程序实际读取到的数据也正确。

最初,我已经确认新增英文标签存在,而且 Polylang 的中英文对应关系也正确。

如果只检查数据库,很容易认为标签同步环节已经完全正常。

但程序真正使用的数据,可能来自缓存。

当缓存与数据库不一致时,就会出现数据库正确、程序行为却错误的情况。

第二,不能因为数量看起来正常,就忽略具体内容。

这次新增标签遗漏还有一个比较明显的规律:

旧标签正常,新标签缺失。

这个规律最终帮助我们把调查范围缩小到新翻译关系的缓存问题,而不是继续怀疑整个标签关联流程。

后续的完整性检查,也不再只比较标签数量,而是比较具体的标签集合。

第三,修复问题不一定需要改造整个相关系统。

最初发现命令行与网站缓存不同步时,很容易把目标扩大成修复整个缓存体系。

但我的实际需求只是保证文章翻译时能获取正确的标签对应关系。

最后虽然单文件方案增加的代码行数更多,却只影响一个明确的业务环节。

对我来说,这样的改动范围更容易理解,也更方便将来维护。

最后

这次问题看起来只是英文文章少了两个标签。

但从最初确认数据库关系,到发现缓存命名空间差异,再到修改插件、完成部署和真实文章验证,中间涉及了 WordPress、Polylang、Redis、W3 Total Cache 以及 SlyTranslate 的多个环节。

最终,我没有选择全站清理缓存,也没有重新翻译已经生成的文章。

而是让 SlyTranslate 在迁移标签时读取最新的翻译关系,并在写入后检查结果是否完整。

现在,下一篇真实博客已经验证通过。

这次修复也已经提交到 GitHub,方便以后查看修改内容及维护。

对我来说,最重要的收获不是多写了几十行 PHP 代码,而是进一步确认了一种排查思路:

先找到程序实际读错了哪一层数据,再决定应该修复哪一个环节。

能够用局部修改解决的问题,就没有必要急着扩大成全站范围的改造。

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系