Problem
A WordPress Multisite translation platform (GlotPress 4.0.3 + WordPress 6.8.3) running 67 subsites exhibits severe performance degradation:
- Homepage TTFB: 5.7 seconds
- Traditional Chinese pages (
/zh-tw/,/zh-hk/): TTFB up to 45 seconds - Server load consistently exceeds 30
Server hardware is robust: 80 CPU cores, 62 GB RAM, PHP 8.3, and Redis already deployed (via Object Cache Pro). Yet PHP-FPM workers (22 total) all run at >80% CPU utilization.
Root Cause Analysis
Three compounding factors were identified:
1. wp-chinese-converter’s OpenCC Dictionary Sorting
The primary performance bottleneck. The plugin uses the OpenCC PHP library for simplified–traditional Chinese conversion. Its core inefficiency lies in the get_prepared_dictionaries() method in class-wpcc-opencc-converter.php:
// 53,650 dictionary entries — sorted on *every* request
uksort($flat, function ($a, $b) {
return mb_strlen($b) <=> mb_strlen($a);
});
Although $this->prepared[] caches results within a single request, PHP-FPM spawns isolated processes per request — meaning this expensive sort runs afresh every time. Sorting 53,650 entries with uksort + mb_strlen comparisons consumes several seconds per request.
Subsequently, strtr($output, $dict) performs replacements across >50,000 entries — itself highly inefficient.
2. Missing robots.txt → Crawler Storm on Translation Pages
No robots.txt file existed, allowing aggressive crawlers to flood translation endpoints:
- MJ12bot crawled
/zh-hk/tag/right-sidebar/page/1105(yes — page 1,105) - GPTBot, OAI-SearchBot, Bytespider, SemrushBot, and ClaudeBot concurrently scanned similar paths
Each crawler request triggers full PHP execution, OpenCC dictionary sorting, and large-scale strtr replacement. Among the last 10,000 requests, ~10% were from such bots — yet they exclusively targeted CPU-intensive translation pages.
3. Cavalcade Cron Task Explosion
Cavalcade (a WordPress cron replacement) had 63 zombie action_scheduler_run_queue tasks stuck since November 2025. Five Cavalcade workers each consumed ~95% CPU. Compounding this, WP-Rocket’s activation triggered simultaneous RUCSS generation and preload tasks across all 67 subsites — overwhelming system resources.
4. wp-chinese-converter’s Set-Cookie Headers Break Caching
The plugin sets Set-Cookie: wpcc_variant_xxx on every request, causing nginx fastcgi_cache to skip caching those responses by default — even if cache rules are correctly configured.
Optimization Strategy
Step 1: Block Crawlers (Immediate Impact)
Create robots.txt to disallow low-value crawlers:
User-agent: MJ12bot
Disallow: /
User-agent: Bytespider
Disallow: /
# Repeat for GPTBot, OAI-SearchBot, SemrushBot, DotBot, AhrefsBot, PetalBot, ClaudeBot
User-agent: *
Disallow: /wp-admin/
Disallow: /glotpress/
Disallow: /translate/
Disallow: /zh-tw/tag/
Disallow: /zh-hk/tag/
Also enforce hard blocking at the nginx layer (since robots.txt is merely advisory):
# /www/server/panel/vhost/nginx/extension/wpfanyi.com/bot_block.conf
if ($http_user_agent ~* "(MJ12bot|Bytespider|GPTBot|OAI-SearchBot|SemrushBot|DotBot|AhrefsBot|PetalBot|ClaudeBot)") {
return 403;
}
Step 2: Enable nginx fastcgi_cache (Fundamental Fix)
Define a shared cache zone in the nginx http block:
# 0.wpfanyi_fcgi_cache.conf (in http block)
fastcgi_cache_path /path/to/nginx-cache levels=1:2
keys_zone=wpfanyi_cache:100m max_size=2g inactive=60m use_temp_path=off;
Enable caching in the server block — critically ignoring Set-Cookie headers from wp-chinese-converter:
set $skip_cache 0;
if ($request_method = POST) { set $skip_cache 1; }
if ($arg_s) { set $skip_cache 1; }
if ($http_cookie ~* "wordpress_logged_in_") { set $skip_cache 1; }
if ($request_uri ~* "/wp-admin/|/wp-login.php|/wp-json/") { set $skip_cache 1; }
fastcgi_cache wpfanyi_cache;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 301 60m;
fastcgi_cache_valid 404 1m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache_use_stale error timeout updating http_500 http_503;
fastcgi_cache_lock on;
# Critical: Ignore Set-Cookie to allow caching despite wp-chinese-converter
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
fastcgi_hide_header Set-Cookie;
add_header X-FastCGI-Cache $upstream_cache_status;
Step 3: OPcache-Powered File Caching for OpenCC Dictionaries
Modify get_prepared_dictionaries() to persist sorted dictionaries as PHP files — loaded via include and accelerated by OPcache:
private function get_prepared_dictionaries(string $strategy): array {
if (isset($this->prepared[$strategy])) {
return $this->prepared[$strategy];
}
// Try loading pre-sorted dictionary from cached PHP file (OPcache-accelerated)
$cache_file = WP_CONTENT_DIR . "/cache/wpcc-dictionaries/dict_" . md5($strategy) . ".php";
if (file_exists($cache_file)) {
$prepared = include $cache_file;
if (is_array($prepared) && !empty($prepared)) {
$this->prepared[$strategy] = $prepared;
return $prepared;
}
}
// First-time: sort and write to cache file
$sets = Dictionary::get($strategy);
// ... unchanged uksort logic ...
// Write sorted array as executable PHP file; OPcache auto-caches bytecode
$export = "<?php\nreturn " . var_export($prepared, true) . ";\n";
$tmp = $cache_file . ".tmp." . getmypid();
if (@file_put_contents($tmp, $export, LOCK_EX) !== false) {
@rename($tmp, $cache_file);
if (function_exists("opcache_compile_file")) {
@opcache_compile_file($cache_file);
}
}
return $prepared;
}
Step 4: Restrict Cavalcade CPU Usage
Limit Cavalcade’s resource consumption via systemd:
# /etc/systemd/system/cavalcade.service
[Service]
Nice=19
CPUQuota=200%
CPUWeight=50
Results After Optimization
| Metric | Before | After |
|---|---|---|
| Server Load | 34.87 | 3.14 |
| Homepage TTFB | 5.7 s | 60 ms |
/zh-tw/ TTFB |
45 s | 66 ms |
/zh-hk/ TTFB |
6 s | 65 ms |
| PHP-FPM Workers | 22 @ >80% CPU | 10 @ 19% CPU |
Key Lessons Learned
-
wp-chinese-converter/ OpenCC is a CPU killer — Sorting and replacing across 50,000+ dictionary entries on every traditional-Chinese page request is prohibitively expensive. Persistent caching (e.g., OPcache-backed file cache) is mandatory. -
Set-Cookiesilently breaks HTTP caching — Plugins that unconditionally set cookies preventfastcgi_cachefrom storing responses. Explicitly ignoreSet-Cookieviafastcgi_ignore_headers. -
Translation sites without
robots.txtattract relentless crawlers — Predictable URL patterns like/zh-tw/tag/xxx/page/Ngenerate infinite crawlable pages. Always deploy strictrobots.txtand nginx-level bot blocking. -
Cavalcade + Action Scheduler can spiral out of control — Zombie background tasks + concurrent heavy operations (e.g., WP-Rocket’s RUCSS/preload across 67 sites) cause CPU saturation. Enforce CPU quotas and monitor task queues.
-
fastcgi_cacheis the most effective frontend optimization for multisite WordPress — One well-configured cache layer resolves >99% of visitor-facing performance issues. Simple to implement, immediate impact. -
Object Cache Pro’s metrics snapshots can bloat — The
objectcache_snapshotsentry inwp_sitemetamay grow to hundreds of KB, causing multi-second write latency. Schedule regular cleanup.