8 Min Lesezeit veröffentlicht 23. August 2026
Wenn eine WordPress-Installation langsam antwortet, steht der Autoload-Bestand schnell unter Verdacht. Der Rat, der dann folgt, ist immer derselbe: Die Optionstabelle sei aufgebläht, WordPress lade bei jedem Aufruf hunderte Kilobyte aus der Datenbank, und deshalb dauere alles länger. Als Abhilfe wird ein persistenter Object Cache empfohlen, weil der die Abfrage einspart.
Der erste Teil dieser Erzählung stimmt. Der zweite auch. Dazwischen fehlt aber der Schritt, der erklärt, warum ein Object Cache das Problem oft nicht so weit löst, wie erwartet. Dieser Artikel misst beide Hälften nach: einmal auf turbopress.de selbst, einmal auf einer eigens dafür aufgebauten Installation mit einem deutlich größeren Bestand.
Kurz gefasst
- Das Dekodieren der autoloaded Optionen ist nicht der Kostenpunkt: 580 KB brauchten 0,85 ms, und an einem echten Seitenaufruf wurden davon nur 1.849 Byte in 39 Zugriffen überhaupt dekodiert.
- Was jeder Request zahlt, ist das Array aus Rohwerten im Arbeitsspeicher. Gemessen belegte es das 1,09-Fache der Größe auf der Platte auf einer Wegwerfinstallation und das 1,60-Fache auf turbopress.de — viele kleine Optionen kosten pro Stück Aufschlag.
- Ein persistenter Object Cache entfernte alle 25 Datenbankabfragen eines Seitenaufrufs, aber nicht die Datenmenge: Die Speicherspitze stieg dabei um 276.880 Byte.
- Gemessen am 22. August 2026 auf WordPress 7.1 mit PHP 8.5 und MariaDB 10.11.
Was Autoload überhaupt ist
WordPress liest beim Start einer Anfrage alle Optionen, deren Autoload-Spalte auf einen Wert gesetzt ist, den der Kern als „mitladen“ versteht, in einem einzigen SELECT aus der Tabelle wp_options. Das Ergebnis landet unter dem Schlüssel alloptions im Objekt-Cache der Anfrage. Jeder spätere Aufruf von get_option() greift dann auf dieses Array zu, statt erneut in die Datenbank zu gehen.
Die Kosten dieses Vorgangs zerfallen in drei Teile, und sie werden regelmäßig durcheinandergebracht: die Abfrage selbst, das Dekodieren serialisierter Werte, und der Speicher, den das Ergebnis für die Dauer der Anfrage belegt. Nur der erste Teil verschwindet durch einen Object Cache.
Die Messung
Gemessen wurde am 22. August 2026 auf zwei Installationen: auf turbopress.de, WordPress 7.1 auf PHP 8.5 mit fünfzehn aktiven Plugins, und auf einer Wegwerfinstallation mit demselben WordPress und demselben PHP, deren Optionstabelle künstlich auf einen Bestand gebracht wurde, wie er auf gewachsenen Installationen vorkommt.
Das Dekodieren wurde als beste von drei Runden gemessen, nicht als Mittelwert: Ein langsamer Durchgang misst mit, was die Maschine sonst noch tat, ein schneller kommt der eigentlichen Arbeit näher. Der Speicher wurde als Differenz von memory_get_usage() vor und nach dem Dekodieren erhoben.
| Messwert | turbopress.de | Wegwerfinstallation |
|---|---|---|
| Autoload-Optionen | 540 | 170 |
| Werte auf der Platte | 140,5 KB | 580,4 KB |
| davon serialisiert | 125,4 KB in 79 Optionen | 579,9 KB in 77 Optionen |
| Dekodieren, beste von drei | 0,22 ms | 0,85 ms |
| Speicher des dekodierten Zustands | 412,2 KB | 2.023,7 KB |
| Faktor dekodiert zu serialisiert | 3,29 | 3,49 |
Die erste Zahl, auf die es ankommt, ist die Dekodierzeit. 0,85 Millisekunden für 580 Kilobyte. Auf einer Seite, deren Antwortzeit in dreistelligen Millisekunden gemessen wird, ist das nicht der Grund für irgendetwas. Wer den Autoload-Bestand halbiert, gewinnt hier eine halbe Millisekunde.
Die zweite Zahl ist interessanter. Der dekodierte Zustand belegt das Drei- bis Dreieinhalbfache dessen, was auf der Platte steht. Ein serialisiertes Array wird beim Dekodieren zu einem PHP-Array aus einzelnen Zeichenketten, Hash-Buckets und Zeigern, und das ist regelmäßig ein Vielfaches der Bytes, aus denen es entstand.
Wo die Kosten wirklich liegen
An dieser Stelle wird häufig ein Schluss gezogen, der nicht trägt: dass dieser dekodierte Zustand die ganze Anfrage über im Speicher liege. Das tut er nicht, und der Kern sagt auch klar, warum.
In wp-includes/option.php baut wp_load_alloptions() das Array auf, und es legt dort die rohen Werte ab, so wie sie in der Spalte stehen. Dekodiert wird erst in get_option(), und zwar in der Rückgabezeile — bei jedem einzelnen Aufruf neu. Das Ergebnis wird nirgends zurückgeschrieben.
// wp-includes/option.php, WordPress 7.1
// in wp_load_alloptions(): der rohe Wert wandert ins Array
$alloptions[ $o->option_name ] = $o->option_value;
// am Ende von get_option(): dekodiert wird bei jedem Aufruf
return apply_filters( "option_{$option}", maybe_unserialize( $value ), $option );Daraus folgt eine andere Kostenrechnung. Dauerhaft im Speicher liegt das Array aus rohen Zeichenketten, nicht der dekodierte Zustand. Gemessen wurde es getrennt, nachdem das Ergebnisobjekt der Datenbankschicht verworfen war — sonst zählt man es doppelt:
| Messwert | turbopress.de | Wegwerfinstallation |
|---|---|---|
| Rohbytes, Name und Wert | 154,1 KB | 623,2 KB |
| Speicher des Arrays | 246,4 KB | 677,3 KB |
| Faktor | 1,60 | 1,09 |
Der Unterschied zwischen den beiden Faktoren ist kein Messfehler, sondern die eigentliche Erkenntnis. turbopress.de hat 540 Optionen mit im Schnitt 260 Byte, die Wegwerfinstallation 170 Optionen mit im Schnitt 3,6 Kilobyte. PHP zahlt pro Zeichenkette und pro Hash-Bucket einen festen Aufschlag von einigen Dutzend Byte. Bei vielen kleinen Optionen wiegt dieser Aufschlag schwer, bei wenigen großen fällt er kaum ins Gewicht.
Zweihundert winzige Optionen kosten im Speicher mehr als ihre Größe auf der Platte vermuten lässt, eine einzelne große Option kostet ungefähr das, was sie wiegt. Wer Autoload-Bestände nach Kilobyte sortiert, sieht deshalb nur die halbe Wahrheit.
Wie oft wird tatsächlich dekodiert?
Wenn get_option() bei jedem Aufruf neu dekodiert, dann hängt die tatsächliche Dekodierarbeit nicht am Gesamtbestand, sondern daran, was gelesen wird. Auf der Wegwerfinstallation wurde für jede serialisierte Autoload-Option ein Filter registriert und mitgezählt. Ergebnis eines Aufrufs der Startseite:
URI=/ Aufrufe=39 dekodierte_Bytes=1849
sticky_posts 15x 6 B je Aufruf
theme_mods_twentytwentyfive 4x 37 B je Aufruf
active_plugins 1x 85 B je Aufruf
sidebars_widgets 1x 198 B je Aufruf39 Dekodiervorgänge über insgesamt 1.849 Byte — bei einem Autoload-Bestand von 580 Kilobyte. Die großen Optionen wurden in dieser Anfrage kein einziges Mal angefasst. Sie kosteten trotzdem: Übertragung aus der Datenbank und Platz im Speicher, aber keine Rechenzeit.
Nebenbei fällt sticky_posts auf, fünfzehnmal gelesen in einer einzigen Anfrage. Bei sechs Byte ist das folgenlos. Bei einer Option von zweihundert Kilobyte wäre dieselbe Zugriffsart teuer, und zwar bei jedem Aufruf erneut, weil nichts zwischengespeichert wird.
Was ein Object Cache daran ändert
Auf der Wegwerfinstallation wurde derselbe Seitenaufruf zweimal gemessen: einmal ohne persistenten Object Cache, einmal mit Redis als Drop-in, nach einem Warmlauf. Gemessen wurden die Datenbankabfragen und die Speicherspitze echter HTTP-Anfragen, nicht auf der Kommandozeile.
| ohne Object Cache | mit Object Cache | |
|---|---|---|
| Datenbankabfragen | 25 | 0 |
| Speicherspitze | 6.960.232 Byte | 7.237.112 Byte |
Die Abfragen sind vollständig verschwunden, alle fünfundzwanzig. Das ist der Gewinn, und er ist real. Die Speicherspitze ist dabei nicht gesunken, sondern um 276.880 Byte gestiegen — der Redis-Client bringt eigene Puffer mit, und der Autoload-Bestand muss weiterhin als PHP-Array vorliegen.
Der Eintrag in Redis lässt sich direkt nachsehen. Er ist 655.496 Byte groß, also praktisch derselbe Bestand, der vorher aus der Datenbank kam. Er reist jetzt über einen Socket statt über eine Datenbankverbindung, aber er reist bei jeder Anfrage.
Ein Object Cache spart die Abfrage. Er spart nicht die Übertragung, nicht das Array im Speicher und nicht das Dekodieren beim Lesen. Wer einen aufgeblähten Autoload-Bestand hinter einem Object Cache versteckt, hat einen von drei Kostenpunkten beseitigt.
Ab wann das praktisch wehtut
Für die Einordnung braucht es die Größenordnung einer echten Anfrage. Auf turbopress.de wurden dafür Aufrufe am Seiten-Cache vorbei gemessen, mit allen fünfzehn Plugins:
| Aufruf | Speicherspitze |
|---|---|
| Startseite | 26.609.272 Byte |
| Startseite, zweiter Aufruf | 23.576.160 Byte |
| Kategorieseite | 22.253.744 Byte |
| Einzelner Beitrag | 22.456.840 Byte |
Rund 21 bis 25 Megabyte pro Anfrage. Der Autoload-Bestand steuert davon 246 Kilobyte bei, also gut ein Prozent. Bei dieser Größenordnung ist die Diskussion müßig.
Interessant wird sie, wenn man die gemessenen Faktoren hochrechnet. Jedes Megabyte Autoload auf der Platte wird im Speicher zu 1,1 bis 1,6 Megabyte, je nachdem, ob der Bestand aus wenigen großen oder vielen kleinen Optionen besteht. Eine Installation mit acht Megabyte Autoload — und die gibt es, meist durch ein Plugin, das Protokolle oder Zwischenergebnisse in Optionen ablegt — trägt damit rund neun bis dreizehn Megabyte allein für diesen einen Posten.
Gegen ein memory_limit von 512 Megabyte ist das nichts. Gegen die 128 Megabyte, mit denen viele geteilte Hostings arbeiten, ist es der Unterschied zwischen einer Anfrage, die durchläuft, und einer, die im Bildeditor oder beim Plugin-Update abbricht. Und dieser Posten liegt an, bevor die eigentliche Arbeit beginnt, bei jeder Anfrage, auch bei denen, die keine einzige dieser Optionen brauchen.
Der Reihe nach heißt das: Erst prüfen, ob der Bestand überhaupt groß ist. Wenn ja, prüfen, wer ihn verursacht. Und erst danach überlegen, ob ein Object Cache das richtige Mittel ist — er behebt an dieser Stelle die Abfrage und sonst nichts.
Selbst nachsehen
Die Größe des Bestands und die größten Einzelposten liefert eine Abfrage. Der Tabellenpräfix ist anzupassen, die Autoload-Werte gelten für WordPress ab 6.6:
SELECT COUNT(*) AS optionen,
ROUND(SUM(LENGTH(option_value))/1024, 1) AS kb
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto', 'auto-on');
SELECT option_name,
ROUND(LENGTH(option_value)/1024, 1) AS kb
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto', 'auto-on')
ORDER BY LENGTH(option_value) DESC
LIMIT 25;Was der Bestand im Speicher belegt und wie lange sein Dekodieren dauert, beantwortet die Datenbank nicht. Dafür braucht es PHP. Das folgende Stück misst beides und ist so gebaut, dass es nichts ausführt: allowed_classes auf false verhindert, dass beim Dekodieren eine __wakeup-Methode anspringt.
<?php
global $wpdb;
$werte = wp_autoload_values_to_autoload();
$liste = implode( ',', $werte );
$roh = $wpdb->get_col( $wpdb->prepare(
"SELECT option_value FROM {$wpdb->options}
WHERE FIND_IN_SET( autoload, %s )
AND LEFT( option_value, 2 ) IN ( 'a:', 'O:' )",
$liste
) );
$start = microtime( true );
foreach ( $roh as $wert ) {
unserialize( $wert, array( 'allowed_classes' => false ) );
}
$ms = ( microtime( true ) - $start ) * 1000;
$vorher = memory_get_usage();
$dekodiert = array();
foreach ( $roh as $wert ) {
$dekodiert[] = unserialize( $wert, array( 'allowed_classes' => false ) );
}
$speicher = memory_get_usage() - $vorher;
printf( "Dekodieren: %.2f ms, Speicher: %.1f KB\n", $ms, $speicher / 1024 );Auf mehreren Installationen nacheinander ist das mühsam. Turbopress Database Diagnostics misst beide Hälften — Dekodierzeit und Speicher — auf der Installation, auf der es läuft, und ordnet die Optionen dem Plugin zu, das sie angelegt hat.
Für den Reihenbetrieb über mehrere Installationen gibt es dieselbe Messung als WP-CLI-Befehl, wp turbopress-diagnostics scan, dessen Ausgabe sich weiterverarbeiten lässt.
Wenn du beim Nachsehen feststellst, dass der Bestand groß ist, geht es weiter mit der Frage, was davon weg kann: WordPress-Datenbank aufräumen. Wenn du an der Antwortzeit insgesamt arbeitest, ist der Autoload-Bestand nur ein Posten unter mehreren: TTFB verbessern. Und wenn du nach dieser Messung trotzdem einen Object Cache einrichten willst — was aus anderen Gründen sinnvoll sein kann —, steht das hier: Object Cache mit Valkey oder Redis.

