Recently, I had just resolved two types of W3 Total Cache Page Cache full flush issues in a WordPress multilingual environment:
One was triggered by Polylang during tag, category, and language association operations via edited_term / delete_term;
The other came from Yoast SEO’s daily Cron, which called w3tc_flush_posts() when updating the wpseo option.
The fix at that time was not to completely disable W3 Total Cache’s cache clearing, but to add a taxonomy-aware compatibility layer.
For taxonomies confirmed to accept eventual consistency, we no longer call W3TC’s site-wide:
w3tc_flush_posts();For other unverified taxonomies, the original W3TC full flush behavior was retained.
This design was intentionally conservative.
But on September 10, 2026, after modifying the blog “series” description, I discovered again:
The W3TC Page Cache was almost entirely cleared again.
This time, the root cause was traced to another custom taxonomy:
seriesThis article documents the complete process from discovery and localization to modifying the Git repository, deploying to the production environment, and real-world verification.
1. After modifying the series description, the Page Cache was almost entirely cleared
That day, I performed two types of backend operations in sequence:
- Modified the blog series description;
- Updated a Code Snippet.
After completing these operations, I was concerned that the recently fixed W3TC Page Cache full flush issue had reoccurred, so I immediately ran the existing read-only snapshot tool:
page-cache-snapshot 20260910-after-series-and-code-snippet-updateThe results were obvious.
The Chinese site had only:
ACTIVE_HTML_COUNT=16
AGE_LT_1D=16
AGE_1_TO_2D=0
AGE_2_TO_3D=0The English site had only:
ACTIVE_HTML_COUNT=37
AGE_LT_1D=37
AGE_1_TO_2D=0
AGE_2_TO_3D=0While in the last normal snapshot from the previous day:
www ACTIVE_HTML=10957
en ACTIVE_HTML=9980This equates to:
www:10957 → 16
en : 9980 → 37The Page Cache accumulated over more than two days had essentially disappeared.
This was no longer a standard URL-level cache miss.
Therefore, it could first be confirmed:
Another W3TC full Page Cache flush had occurred.
2. Full Flush Tracer increased from 2 to 4 entries
To troubleshoot such issues, I had previously deployed a temporary MU Plugin:
wp-content/mu-plugins/w3tc-full-flush-tracer.phpIt specifically logs the call chains of:
w3tc_flush_posts
w3tc_flush_allChecking the logs:
wc -l /var/log/w3tc-full-flush-tracer.logPreviously, it consistently showed:
2Both entries were from the previously identified Yoast SEO Cron.
But this time it changed to:
4This means there were two new instances of w3tc_flush_posts().
Both new call chains originated from the backend:
POST /wp-admin/edit-tags.php
↓
wp_update_term()
↓
edited_term
↓
polylang_w3tc_cache_compat_flush_posts_for_term()
↓
w3tc_flush_posts()Therefore, Yoast Cron could be ruled out this time.
However, since I had also just updated a Code Snippet, I couldn’t immediately determine:
Whether it was caused by modifying the series or by updating the Code Snippet?
So I continued checking the Nginx backend access logs.
3. Nginx logs confirm: both requests were taxonomy=series
The backend logs clearly show:
POST /wp-admin/edit-tags.php
taxonomy=series
tag_ID=42451And:
POST /wp-admin/edit-tags.php
taxonomy=series
tag_ID=42453These two terms are respectively:
42451
A Tour of Go 多语言翻译项目And:
42453
A Tour of Go Multilingual Translation ProjectIn other words, both instances of w3tc_flush_posts() corresponded to the Chinese and English series I had just modified.
The Code Snippet could be ruled out.

edit-tags.php requests explicitly point to taxonomy=series.I then checked the taxonomies currently registered in WordPress:
wp eval --allow-root '
foreach ( ["series", "series_group"] as $taxonomy ) {
$obj = get_taxonomy( $taxonomy );
printf(
"%s\tpublic=%s\tshow_ui=%s\thierarchical=%s\tobject_type=%s\trewrite=%s\n",
$taxonomy,
$obj->public ? "yes" : "no",
$obj->show_ui ? "yes" : "no",
$obj->hierarchical ? "yes" : "no",
implode(",", $obj->object_type),
is_array($obj->rewrite) ? ($obj->rewrite["slug"] ?? "") : ""
);
}
'Results:
series
public=yes
show_ui=yes
hierarchical=no
object_type=post
rewrite=series
series_group
public=yes
show_ui=yes
hierarchical=yes
object_type=series_grouping
rewrite=series-categoryThe two terms just modified also clearly belong to:
taxonomy=series
count=65Thus, the root cause could be further narrowed down.
4. Why the existing MU Plugin didn’t prevent this full flush
Previously, to handle the taxonomy cache compatibility issue between Polylang and W3TC, I had deployed:
wp-content/mu-plugins/polylang-w3tc-cache-compat.phpThis plugin removes W3TC’s original taxonomy-blind callback:
remove_action( 'edited_term', 'w3tc_flush_posts', 0 );
remove_action( 'delete_term', 'w3tc_flush_posts', 0 );And replaces it with its own wrapper.
The taxonomies that allowed eventual consistency at the time were:
$taxonomies = array( 'category', 'post_tag' );It also dynamically reads the term translation taxonomy used by Polylang itself.
If the current taxonomy is in this list:
return;It prevents:
w3tc_flush_posts();But for other taxonomies:
if ( function_exists( 'w3tc_flush_posts' ) ) {
w3tc_flush_posts();
}The original W3TC full flush behavior is retained.
So this time, the MU Plugin didn’t fail to work.
Instead, it executed exactly as originally designed:
Edit series
↓
wp_update_term()
↓
edited_term
↓
taxonomy=series
↓
Not in suppression list
↓
fallback
↓
w3tc_flush_posts()
↓
Full Page Cache flushThe only problem was:
There was previously no real-world production evidence to prove that
seriesshould also be added to this list.
Now there is.
5. A series description is not worth clearing the entire cache of two sites
The series actually modified this time each contained:
count=65Meaning there are 65 articles under this series.
But only the series description text was modified.
The result, however, was:
www ACTIVE_HTML:10957 → 16
en ACTIVE_HTML:9980 → 37This means that updating two series terms wiped out the active page cache of nearly 20,000 pages across the Chinese and English sites.
For the current website, this cost is clearly too high.
Especially since the site has enabled:
W3 Total Cache Page Cache
Disk Enhanced
4 天 TTL
PrimePage Cache itself is a crucial layer for origin server load control.
Therefore, the more sensible strategy here is still:
Allow series to use eventual consistency, rather than continuing to trigger site-wide cache flushes.
6. Why only add series, and not series_group while at it
This time, I still did not adopt:
Since they are all series-related taxonomies, just add them all.
In actual operation:
series
object_type=postWhile:
series_group
object_type=series_groupingThe two are not exactly the same thing.
And this real-world production incident only proved that:
seriestriggers an unnecessary full Page Cache flush during normal blog series edits.
There is no actual evidence to prove that:
series_groupshould also be modified at the same time.
Therefore, adhering to the principle of minimal changes:
category suppress full flush
post_tag suppress full flush
series suppress full flush
Polylang term translation taxonomy
suppress full flush
series_group keep original logic
other taxonomies keep original logic7. This time, the production server was not modified directly
There is another detail worth recording during this process.
When I first prepared the fix, I almost modified it directly on the production server:
wp-content/mu-plugins/polylang-w3tc-cache-compat.phpBut doing so would create a very troublesome issue:
Production
≠
Git RepositoryIf the production environment is modified first and the Git sync is forgotten, or if Git deploys back later, version drift can easily occur.
Therefore, I ultimately strictly returned to:
Local Git repository
↓
Modify source code
↓
Update regression tests
↓
Local testing
↓
commit
↓
push
↓
Deploy committed Git version
↓
SHA256 verification
↓
Production runtime verification8. The local modification only adds one taxonomy
Local repository:
shuijingwan/wordpress-polylang-w3tc-cache-compatThe core modification in the source code is just one line.
Originally:
$taxonomies = array( 'category', 'post_tag' );Modified to:
$taxonomies = array( 'category', 'post_tag', 'series' );Also updated:
tests/term-page-cache-flush-suppression-test.phpTest coverage:
edited category
edited post_tag
edited series
edited term_translationsAnd deletion scenarios:
delete category
delete post_tag
delete seriesAt the same time, the original unknown taxonomy fallback must still be retained:
custom_taxonomy
→ w3tc_flush_posts()Local check results:
No syntax errors detected
OK
OK
git diff --check PASS9. Committed and pushed to GitHub
This commit:
60f0873a55424231c476d62bdafefe3f57a2c545Commit message:
fix: 抑制 series 更新触发 W3TC 全量页面缓存清理After pushing:
HEAD
=
origin/master
=
60f0873a55424231c476d62bdafefe3f57a2c545Local source code SHA256:
614a4f9724fd8f5bdde9cf81ca5d0b992c654c1d0532d65432c872e13fcdd7b9Only after this step was completed did the production deployment begin.
10. The first production deployment actually failed
Before deployment, the committed Git version was uploaded to the server:
/tmp/polylang-w3tc-cache-compat.phpUploaded file SHA256:
614a4f9724fd8f5bdde9cf81ca5d0b992c654c1d0532d65432c872e13fcdd7b9Old production file SHA256:
4d9c9119212bcc3ca15fcf999d57b2920ea0f699311f4ec03b03bff9730b90d5Backed up first:
wp-content/mu-plugins/polylang-w3tc-cache-compat.php.bak-20260910-165622First execution:
cp -p "$SOURCE" "$FILE"The server prompted:
cp:是否覆盖...?After confirming, I continued checking.
Found that:
Production SHA256
is still:
4d9c9119212bcc3ca15fcf999d57b2920ea0f699311f4ec03b03bff9730b90d5And at runtime it was still:
series SUPPRESS_FULL_FLUSH=NOThat is to say:
The first deployment did not actually take effect.
This is why production deployment cannot only look at:
命令没有报错It must also check:
File SHA256
+
PHP syntax
+
Runtime behavior11. Deployment was only truly completed after using /bin/cp
Then explicitly bypassed the shell alias:
/bin/cp -pf "$SOURCE" "$FILE"Checked again:
PRODUCTION SHA256
614a4f9724fd8f5bdde9cf81ca5d0b992c654c1d0532d65432c872e13fcdd7b9PHP syntax:
No syntax errors detectedRuntime:
category SUPPRESS_FULL_FLUSH=YES
post_tag SUPPRESS_FULL_FLUSH=YES
series SUPPRESS_FULL_FLUSH=YES
series_group SUPPRESS_FULL_FLUSH=NO
series=YES, while series_group=NO.At the same time:
Full Flush Tracer = 4This indicates that the deployment action itself did not trigger a new full cache flush.
12. Two consecutive real edits to series
After deployment, I didn’t just do CLI simulations.
To get as close to real-world usage as possible, I made two consecutive actual modifications to the series content in the WordPress backend.
Before the fix:
Tracer = 4If the fix had not taken effect, two real modifications should theoretically have continued to:
4 → 5 → 6But the actual check still showed:
Tracer = 4No new additions of:
w3tc_flush_postsCache snapshot comparison:
www
ACTIVE_HTML=86->170
OLD_COUNT=16->16
OLDEST_EPOCH=1789029661->1789029661English site:
en
ACTIVE_HTML=91->153
OLD_COUNT=24->24
OLDEST_EPOCH=1789029648->1789029648These metrics are very clean.
First:
ACTIVE_HTMLContinued to grow normally.
Second:
OLD_COUNTDid not increase due to the two series modifications.
Finally:
OLDEST_EPOCHWas not reset at all.

Therefore, this fix can be officially concluded as:
Production environment verification passed.
13. But the series page still shows the old description
After fixing the full flush, I checked the series page again.
Found that:
The page still displayed an earlier version of the series description.
However, it must be emphasized here:
This is not a new issue that appeared after this
seriesfull flush fix.
Before this fix, I had already modified the series description, but the frontend did not correctly display the latest content at that time either.
That is to say:
Old description issuepredates this series full flush fix itself.
Therefore, it cannot be written as:
Suppress full flush
↓
Causes series page to become outdatedThis causal relationship does not exist.
14. Further confirmation: the current Page Cache is indeed saving a historical old version
Later, I checked the series description in the current WordPress runtime.
The database / WordPress current value already contains:
Brazilian Portuguese
Dutch
Italian
Spanish
Turkishand other new language entries.
But the current W3TC Page Cache HTML still only has the earlier version:
简体中文
日语
德语
法语And the generation time of the current series Page Cache file is:
2026-09-10 17:11:21That is to say:
Although the current Page Cache was regenerated today, the generated content is still the old version of the series description.
This further indicates that:
The old description issue is not simply because the Page Cache wasn’t cleared after disabling the full flush this time.
It is another pre-existing cache consistency issue.
15. This part belongs to the previously confirmed Object Cache issue
For the current website, this type of phenomenon has been investigated before.
The site uses:
W3 Total Cache Object Cache
+
RedisIt has happened before that:
Database has been updated
↓
WordPress certain objects still read old values
↓
Regenerated Page Cache continues to save old contentThat is to say:
Page Cachesometimes just re-saves the old objects already read by the PHP runtime into HTML.
Therefore, since the series description remained an old value this time, I did not dig deeper.
This belongs to the previously confirmed and accepted Object Cache / Redis consistency issue.
The current strategy remains:
It is acceptable to temporarily have the old series description, and not proactively expand the cache clearing scope for this type of low-priority content.
This article will also not expand on further Object Cache troubleshooting.
16. The two issues need to be clearly separated
This time, it is especially easy to mix the two issues together.
The first issue is:
Edit series
↓
edited_term
↓
w3tc_flush_posts()
↓
Site-wide Page Cache is clearedThis issue has been fixed and verified in this article.
The second issue is:
series description has been updated
↓
Object Cache / Redis may still retain old term data
↓
Page Cache continues to write old description when regeneratedThis issue existed previously and has already been confirmed.
Currently temporarily accepted.
So the final state is:
Full Page Cache flush
→ Resolved
series description cache lag
→ Historically known issue
→ Currently accepted
→ Not processing further for nowAfter separating these two issues, the entire causal relationship becomes much clearer.
17. Why it’s still worth writing a separate article this time
On the surface, this was just adding one more to the array:
'series'But the real value isn’t this single line of code.
Rather, the production environment proved once again that:
If W3TC’s taxonomy cache invalidation logic is too broad, it can easily turn a very small backend operation into a site-wide cache cold start.
It had been previously confirmed for:
category
post_tag
Polylang term translation taxonomyThis time, adding:
seriesAnd this time, we also retained the fallback for:
series_group
other unknown taxonomiesThis is less risky than simply and crudely doing:
所有 edited_term 都 returnfor all of them.
18. Current taxonomy cache strategy
So far, this MU Plugin’s handling of term updates can be summarized as:
category
→ Allow eventual consistency
→ No full flush
post_tag
→ Allow eventual consistency
→ No full flush
series
→ Allow eventual consistency
→ No full flush
Polylang term translation taxonomy
→ Allow eventual consistency
→ No full flushOther taxonomies:
→ Keep W3TC original fallback
→ w3tc_flush_posts()The benefit of doing this is:
Only expanding the scope that has passed real-world production verification.
If new taxonomies are discovered later, we will continue to follow:
Discover issue
↓
Confirm taxonomy
↓
Confirm business impact
↓
Only add verified taxonomiesRather than disabling all W3TC clearing triggered by term updates at once.
19. Final result
This new Page Cache full flush issue was ultimately confirmed as follows.
Trigger condition
Modifying:
taxonomy=seriesFor example:
A Tour of Go 多语言翻译项目‘s series description.
Call chain
wp_update_term()
↓
edited_term
↓
polylang_w3tc_cache_compat_flush_posts_for_term()
↓
series not recognized as eventual-consistency taxonomy
↓
w3tc_flush_posts()
↓
www + en full Page Cache flushFix
Add:
seriesInto:
$taxonomies = array(
'category',
'post_tag',
'series'
);series_group remains unchanged.
Git commit
60f0873a55424231c476d62bdafefe3f57a2c545Production file SHA256
614a4f9724fd8f5bdde9cf81ca5d0b992c654c1d0532d65432c872e13fcdd7b9Real-world production verification
Two consecutive series edits:
Tracer:4 → 4Chinese site:
ACTIVE_HTML=86->170
OLD_COUNT=16->16
OLDEST_EPOCH=1789029661->1789029661English site:
ACTIVE_HTML=91->153
OLD_COUNT=24->24
OLDEST_EPOCH=1789029648->1789029648No site-wide Page Cache flush occurred again.
20. Summary
This issue made me confirm once again:
Cache compatibility issues cannot just be looked at in terms of “whether the page is updated”; first, it must be clarified which cache layer exhibited what behavior.
In fact, two different things existed simultaneously this time:
series update triggering site-wide Page Cache flushAnd:
series description having historical Object Cache lagThe former causes a large number of pages to cold start again, so it must be prioritized.
The latter only means that some low-priority taxonomy content temporarily retains an old version, which is currently acceptable.
The final strategy adopted is:
Resolve the full flush that affects site-wide stabilityRather than:
To make one series description refresh immediately
Clearing the entire Page Cache or RedisThis also still aligns with the current site’s cache optimization principles:
Prioritize resolving issues that affect correctness, stability, and overall performance; for acceptable eventual consistency, do not expand the cache clearing scope just to pursue “immediate updates”.
From Polylang tags and categories, to Yoast SEO Cron, and now this series, a clearer handling approach has gradually taken shape:
First confirm the actual trigger chain
↓
Distinguish Page Cache / Object Cache
↓
Keep W3TC default behavior as fallback
↓
Only minimally suppress taxonomies that have been verified in real-world scenarios
↓
Then verify via production snapshots and TracerCompared to “fixing all cache issues” at once, this method of confirming and converging one by one is more suitable for a production WordPress site that already has real traffic.
需要长期技术维护或远程问题排查?
我是拥有 15+ 年经验的 PHP / Go 后端工程师,长期关注已有系统维护、Bug 修复、性能优化、服务器排查、WordPress 网站维护和小功能迭代。
如果你的项目遇到以下情况,可以先从一次小问题排查开始合作:
- ✅ PHP / Laravel / Yii2 老项目无人维护
- ✅ Go / Gin 后端接口需要排查或优化
- ✅ WordPress 网站访问慢、报错或插件冲突
- ✅ Nginx / MySQL / Redis / Linux 服务器异常
- ✅ CDN / Cloudflare / DNS / HTTPS 配置问题
- ✅ 需要长期远程技术支持或兼职维护
更多介绍请查看:关于我 & 合作
微信:13980074657
邮箱:shuijingwanwq@gmail.com
Telegram:@shuijingwan
GitHub:https://github.com/shuijingwan

