Have you heard of Nagle's algorithm? If you run memcached for MediaWiki through the PHP extension, one line of configuration is worth checking. In this post we measure how much that setting slows down a single cache command, and compare Femiwiki before and after changing it.
Background
Until September 2026, Femiwiki used a memcached client written in PHP (MemcachedPhpBagOStuff) for MediaWiki's caches, and this client spent about 14% of the CPU time it took to render a page on handling sockets. The Wikimedia Foundation uses MemcachedPeclBagOStuff, which uses the memcached PHP extension, so Femiwiki switched to it too in early October.[1]
After that, opening the user contributions special page (Special:Contributions) took 5–20 seconds depending on the user. Fetching the same list through the API (list=usercontribs) finished in 0.5 seconds, and both the CPU and the database server were idle while the slow requests were being handled. But when we gathered the MediaWiki logs for one such request, we found hundreds of memcached warnings, one for each Structured Discussions (Flow) post put into the cache, logged at intervals of almost exactly 50 ms. Every write to the cache was waiting nearly 50 ms for something.
Below are the times and cache keys of the Lowered set() TTL warnings logged during that request, in order.
Time (UTC) Gap Cache key
00:02:28.623169 global:flow-tree:parent:wlh3xmdzd6vti07q:4.9
00:02:28.673144 +50.0ms global:flow-tree:parent:uifvczbgn9hz94if:4.9
00:02:28.723075 +49.9ms global:flow-tree:parent:wkonm51fol0jag2q:4.9
00:02:28.773073 +50.0ms global:flow-tree:parent:tw5ymwvf2omgq2af:4.9
00:02:28.823128 +50.1ms global:flow-tree:parent:whagekporvo7yjwb:4.9
00:02:28.873120 +50.0ms global:flow-tree:parent:wb4mqxvy0cocks4x:4.9
00:02:28.923113 +50.0ms global:flow-tree:parent:ulntjz6dqri91ilm:4.9
00:02:28.983193 +60.1ms global:flow-tree:parent:uoe2yitxyxwa8ktm:4.9
00:02:29.033084 +49.9ms global:flow-tree:parent:uw9yi830s1yesxcv:4.9
00:02:29.083103 +50.0ms global:flow-tree:parent:ucejtawk4j2jmvx3:4.9
…
Hypothesis
When MediaWiki's cache class WANObjectCache writes a value, it usually adds the WRITE_BACKGROUND flag, which says it does not need to wait to find out whether the write succeeded. When MemcachedPeclBagOStuff gets this flag, it sends the command to memcached with noreply (added to the end of a command, as in set key 0 60 3 noreply, it tells memcached not to send a reply such as STORED[2]).[3]
private function noReplyScope( $flags ): ?ScopedCallback {
if ( $flags !== true && !( $flags & self::WRITE_BACKGROUND ) ) {
return null;
}
$client = $this->client;
$client->setOption( Memcached::OPT_NOREPLY, true );
// ...
}
This is meant to make writes faster, but the same class turns Nagle's algorithm on by default.[3]
'allow_tcp_nagle_delay' => true
// ...
Memcached::OPT_TCP_NODELAY => !$params['allow_tcp_nagle_delay'],
Nagle's algorithm holds back the next small packet while data sent earlier has not yet been acknowledged (ACK).[4] Usually this causes no trouble, because the server sends the ACK along with its reply. But memcached sends nothing back for a noreply command. With no data to carry the ACK, the server's kernel holds it until the delayed ACK timer runs out, and in the meantime Nagle's algorithm keeps the client's next command from going out.
If this hypothesis is right, we should see the following:
- Sending a command that needs a reply right after a
noreplywrite is delayed by a little over 40 ms, Linux's delayed ACK timer. - Sending only
noreplywrites in a row, or writes that get a reply, causes no delay. - With Nagle's algorithm turned off, there is no delay in any case.
Experiment
Outside MediaWiki, we connected to memcached with the same options as MemcachedPeclBagOStuff and ran each of three cases 200 times, with Nagle's algorithm on and off. We installed memcached 1.6.24 and php-memcached 3.2.0 on Ubuntu 24.04 and connected over the loopback address on the same machine.
<?php
function client( bool $nagle ): Memcached {
// Options MemcachedPeclBagOStuff uses
$c = new Memcached();
$c->setOptions( [
Memcached::OPT_NO_BLOCK => false,
Memcached::OPT_BUFFER_WRITES => false,
Memcached::OPT_NOREPLY => false,
Memcached::OPT_BINARY_PROTOCOL => false,
Memcached::OPT_TCP_NODELAY => !$nagle,
Memcached::OPT_LIBKETAMA_COMPATIBLE => true,
] );
$c->addServer( '127.0.0.1', 11211 );
return $c;
}
function bgSet( Memcached $c, string $k, $v ): void {
// Same order as noReplyScope()
$c->setOption( Memcached::OPT_NOREPLY, true );
$c->set( $k, $v, 60 );
$c->setOption( Memcached::OPT_NOREPLY, false );
}
$n = 200;
$value = str_repeat( 'x', 300 );
foreach ( [ true, false ] as $nagle ) {
$c = client( $nagle );
$c->get( 'warmup' );
$t = hrtime( true );
for ( $i = 0; $i < $n; $i++ ) {
bgSet( $c, "a$i", $value );
$c->get( "a$i" );
}
$a = ( hrtime( true ) - $t ) / 1e6 / $n;
$t = hrtime( true );
for ( $i = 0; $i < $n; $i++ ) {
bgSet( $c, "b$i", $value );
}
$c->get( 'b0' );
$b = ( hrtime( true ) - $t ) / 1e6 / $n;
$t = hrtime( true );
for ( $i = 0; $i < $n; $i++ ) {
$c->set( "c$i", $value, 60 );
$c->get( "c$i" );
}
$d = ( hrtime( true ) - $t ) / 1e6 / $n;
printf( "Nagle %-3s %6.2f ms %6.2f ms %6.2f ms\n", $nagle ? 'on' : 'off', $a, $b, $d );
}
Results
Three runs gave the same values to within 0.1 ms, and all three predictions held. When only noreply writes are sent in a row, Nagle's algorithm bundles the small packets together, so they barely slow down. But MediaWiki often writes a value to the cache and reads another one right after, so the first case happens several times per request.
Checking in production
The setting that turns off Nagle's algorithm went live on Femiwiki at 00:38 UTC on 8 November 2026.[5] Taking logged-out visitors' Special:Contributions requests from the web server logs for one day before and one day after, the 90th percentile response time dropped from 8.94 seconds to 0.45 seconds. Structured Discussions writes one cache entry per post, so the more Structured Discussions posts a user had, the more their contributions list sped up.
How to apply it
Add allow_tcp_nagle_delay set to false to the $wgObjectCaches entry that uses MemcachedPeclBagOStuff.
$wgObjectCaches['memcached-pecl'] = [
'class' => \Wikimedia\ObjectCache\MemcachedPeclBagOStuff::class,
'loggroup' => 'memcached',
'allow_tcp_nagle_delay' => false,
];
(This example overrides the memcached-pecl entry in the default value of $wgObjectCaches. If you use an entry with a name of your own, as Femiwiki does, just add the one line to that entry.)
The Wikimedia Foundation turns this off in its production configuration, so it does not run into this problem.[6] And if you use the default name, as in $wgMainCacheType = CACHE_MEMCACHED, you get the pure PHP client, which never uses noreply, so you are not affected either. You only need to check this if you picked memcached-pecl yourself.
History of the default
This option was added to MediaWiki core in July 2019.[7] Until then MediaWiki did not configure Nagle's algorithm at all, so it was left on; setting the default to true seems to have been a way to keep the existing behaviour. The Wikimedia Foundation turned it off in its production configuration in February 2020.[8]
noreply writes came later, in July 2022, so that cache writes would not have to wait.[9] By then the Foundation's servers already had Nagle's algorithm turned off, so the delay caused by the two settings together never showed up there, and the default stayed as it was.
Closing
If requests are slow while the CPU and the database are idle, check whether a single request's log lines are spaced at regular intervals. At Femiwiki, that 50 ms interval is what pointed us towards the network.
References
- ↑ “docker(fastcgi): switch MediaWiki's caches to the pecl memcached client · Pull Request #1198 · femiwiki/infra”. 《GitHub》 (영어). 2026년 11월 9일에 확인함.
- ↑ “memcached/doc/protocol.txt at master · memcached/memcached”. 《GitHub》 (영어). 2026년 11월 9일에 확인함.
- ↑ 3.0 3.1 “includes/libs/ObjectCache/MemcachedPeclBagOStuff.php - mediawiki/core - Git at Google”. 《Wikimedia Gerrit》 (영어). 2026년 11월 9일에 확인함.
- ↑ “RFC 896: Congestion Control in IP/TCP Internetworks”. 《RFC Editor》 (영어). 2026년 11월 9일에 확인함.
- ↑ “perf(femiwiki): turn off Nagle's algorithm for the pecl memcached client · Pull Request #1807 · femiwiki/docker-mediawiki”. 《GitHub》 (영어). 2026년 11월 9일에 확인함.
- ↑ “wmf-config/mc.php - operations/mediawiki-config - Git at Google”. 《Wikimedia Gerrit》 (영어). 2026년 11월 9일에 확인함.
- ↑ “objectcache: use OPT_TCP_NODELAY for MemcachedPeclBagOStuff”. 《Wikimedia Gerrit》 (영어). 2026년 11월 9일에 확인함.
- ↑ “Set "allow_tcp_nagle_delay" to false in mc.php”. 《Wikimedia Gerrit》 (영어). 2026년 11월 9일에 확인함.
- ↑ “objectcache: Accelerate async memcached writes with "noreply"”. 《Wikimedia Gerrit》 (영어). 2026년 11월 9일에 확인함.