"Autoloaded options kunnen de performance beïnvloeden" is een van de meer cryptische waarschuwingen die je op een WordPress hosting dashboard tegenkomt. Het klinkt technisch en dat is het ook, maar het idee erachter is simpel: WordPress laadt bij elke request een vaste set instellingen in het geheugen, die set is te groot geworden, en inmiddels betaalt elke request de rekening. In dit artikel leg ik uit wat autoload precies is, waarom het groeit, wat WordPress 6.6 eraan veranderde, en hoe je het veilig opschoont. Er staat ook één vervelend stukje over Redis in dat de meeste andere guides verkeerd hebben.
Wat autoload betekent in wp_options
WordPress gebruikt de wp_options tabel als key-value store voor instellingen. Elke rij heeft een option_name, een option_value en een autoload kolom. Die autoload kolom is het hele verhaal: die bepaalt of de rij bij elke request in het PHP-geheugen terechtkomt, of pas als een plugin er expliciet om vraagt.
Bij elke WordPress bootstrap roept core de functie wp_load_alloptions() aan. Die draait één SQL-query tegen wp_options om alle rijen op te halen waar de autoload kolom "moet autoloaden" aangeeft, serialiseert het hele resultaat tot één blob, en zet dat in de object cache onder de key alloptions in de groep options. Vanaf dat moment komt elke get_option() voor een autoloaded optie uit het geheugen. Het voordeel: een paar veelgebruikte opties (siteurl, blogname, active_plugins, thema-instellingen, enzovoort) zijn direct beschikbaar zonder 30 extra queries per pagina.
De prijs is dat elke byte autoload-data bij elke request in PHP-geheugen terechtkomt, ongeacht of de huidige pagina die data ook nodig heeft. 8 MB autoload betekent dat er bij elke front-end pageview, elk admin-scherm en elke REST- of AJAX-call 8 MB in een PHP-array wordt uitgepakt. Dat is het bloat-patroon: een klein, afgebakend idee dat door jarenlange plugin-wissels onbegrensd is geworden.
Het vijf-waarden autoload model van WP 6.6 (en waarom dat je SQL raakt)
Vóór WordPress 6.6 had autoload twee waarden: yes en no. add_option() stond standaard op yes, wat betekende dat elke plugin-optie die je ooit had geïnstalleerd automatisch werd geautoload, tenzij de plugin-auteur daar bewust van afweek. De meesten deden dat niet.
WordPress 6.6 (uitgebracht in juni 2024) verving die binaire keuze door een systeem met vijf waarden, gedocumenteerd in de Make WordPress Core post "Options API: Disabling autoload for large options". De nieuwe waarden:
| Database waarde | Betekenis | Wordt geautoload? |
|---|---|---|
on |
Expliciet op true gezet door ontwikkelaar |
Ja |
off |
Expliciet op false gezet door ontwikkelaar |
Nee |
auto |
Toegevoegd met null, WordPress beslist later |
Ja, voor nu |
auto-on |
Default, dynamisch bepaald als autoload | Ja |
auto-off |
Default, dynamisch bepaald als niet-autoload | Nee |
De add_option() en update_option() functies staan niet meer standaard op yes. De default is nu null, wat betekent dat WordPress zelf beslist op basis van de grootte van de optie. Alles wat groter is dan zo'n 150 KB en met de default wordt toegevoegd, komt op auto-off te staan. Dit staat ook in de add_option() reference op developer.wordpress.org. De oude yes en no werken nog steeds maar zijn in WordPress 6.7 formeel deprecated ten gunste van booleans en de nieuwe string-waarden.
Dit heeft directe gevolgen voor iedereen die "meet je autoload size"-queries gebruikt die uit oudere guides komen. De klassieke query ziet er zo uit:
SELECT SUM(LENGTH(option_value))
FROM wp_options
WHERE autoload = 'yes';
Op een site die op WordPress 6.6 of nieuwer is geïnstalleerd of waar sindsdien rijen zijn bijgekomen, telt die query te laag, omdat on, auto en auto-on worden genegeerd. De juiste, versie-veilige query is:
SELECT
ROUND(SUM(LENGTH(option_value)) / 1024, 2) AS autoloaded_kb,
COUNT(*) AS autoloaded_count
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto-on', 'auto');
De vier waarden in die IN clause zijn precies wat core zelf controleert. Ze komen uit wp_autoload_values_to_autoload(), de helper die in WP 6.6 is toegevoegd zodat elk stukje code dat "welke rijen beschouwt WordPress als autoloaded" wil vragen, één autoritatief antwoord krijgt. Zie je een oudere hosting-script of StackOverflow-antwoord die alleen autoload = 'yes' gebruikt, ga er dan vanuit dat het verouderd is.
Hoe WordPress de autoload-data bij elke request gebruikt
Een vereenvoudigde bootstrap-volgorde ziet er zo uit:
- WordPress start,
wp-settings.phpdraait, de databaseverbinding gaat open. - Core roept
wp_load_alloptions()aan. wp_load_alloptions()checkt de object cache op de keyalloptionsin groepoptions. Staat die er, dan wordt die array teruggegeven. Staat die er niet, dan draait er één SQL-query tegenwp_optionsmet de vier-waardenINclause, wordt het hele resultaat als één blob in de object cache gezet, en retourneert de functie die blob.- De rest van de request draait. Elke
get_option()voor een autoloaded naam wordt uit het geheugen beantwoord. Alleen niet-autoloaded opties triggeren een verse SQL-query.
Wat je moet onthouden: alloptions is één cache-entry, één geserialiseerde blob, één memory-allocatie. Het is geen per-optie cache. Het is een grote emmer, en WordPress leest die hele emmer of helemaal niets bij elke request. Dat maakt de code simpel, en voor een redelijk grote site is het prima snel. Het is ook precies wat bloat zo duur maakt: er is geen manier om de helft van de emmer te laden.
Je autoload-data correct meten
De juiste vraag is niet "hoe groot is mijn wp_options tabel". Een wp_options tabel met 50.000 rijen maar slechts 200 KB autoload-data is prima gezond, omdat alleen de autoloaded rijen in het hete pad meetellen. De juiste vraag is "hoeveel data wordt er nu geautoload". Open phpMyAdmin via het configuratiescherm van je hosting (bij de meeste hosts staat het onder "Databases"), selecteer je WordPress-database, klik op het tabblad SQL en draai de vier-waarden query hierboven. Biedt je host Adminer aan in plaats van phpMyAdmin, dan werkt dezelfde query daar ook.
Als je SSH-toegang hebt, kan WP-CLI dezelfde query vanaf de commandline draaien:
wp db query "SELECT \
ROUND(SUM(LENGTH(option_value)) / 1024, 2) AS autoloaded_kb, \
COUNT(*) AS autoloaded_count \
FROM wp_options \
WHERE autoload IN ('yes', 'on', 'auto-on', 'auto');"
Ruwe maatstaven om het resultaat te interpreteren:
| Autoload grootte | Wat dit betekent |
|---|---|
| Onder 300 KB | Gezond. Stop hier met optimaliseren en kijk elders. |
| 300 KB tot 800 KB | Acceptabel, wel monitoren. |
| 800 KB tot 1 MB | WordPress Site Health markeert dit als kritiek. |
| 1 MB tot 3 MB | Meetbare impact per request. Grijp in. |
| Meer dan 3 MB | Forse overhead bij elke pageview. Urgent. |
Die 800 KB is niet willekeurig. WordPress 6.6 voegde een Site Health check voor autoload size toe die elke site boven 800 KB als kritiek probleem markeert. Oudere hosting-guides noemen nog steeds 1 MB als drempel. Dat getal is achterhaald sinds medio 2024.
Als je het totaal weet, zoek dan de zwaarste individuele rijen op:
SELECT
option_name,
ROUND(LENGTH(option_value) / 1024, 2) AS size_kb,
autoload
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto-on', 'auto')
ORDER BY LENGTH(option_value) DESC
LIMIT 20;
Dit is de lijst waar je echt mee gaat werken. In de praktijk zijn het meestal een handjevol rijen die samen 80 tot 90 procent van het totaal uitmaken.
De gebruikelijke verdachten
Autoload bloat heeft op bijna elke site die ik voorbij heb zien komen dezelfde vijf of zes oorzaken.
Achtergebleven opties van verwijderde plugins. Een plugin activeert, doet add_option('my_plugin_settings', $bigArray), en ruimt nooit op. Maanden later wordt de plugin verwijderd. De rij blijft voor altijd in wp_options staan, nog steeds autoloaded, nog steeds bytes toevoegend aan elke request. Op een site die in de loop van de jaren tientallen plugins heeft versleten, is dit vrijwel altijd de grootste boosdoener.
Transients die in de autoload lekken. WordPress bewaart transients in wp_options als er geen persistente object cache actief is. Verlopen transients worden pas verwijderd als er iets om vraagt, wat voor wezen-transients van verwijderde plugins nooit gebeurt. De waarde-rij (_transient_<naam>) is vaak autoloaded, de timeout-rij (_transient_timeout_<naam>) niet. Wezen-transients stapelen zich stilletjes op.
Plugins die API-responses en licentie-payloads opslaan. Sommige plugins cachen antwoorden van licentieservers, remote feature flags of analytics-payloads in update_option(). Eén enkele rij kan honderden kilobytes groot zijn, en die wordt bij elke request meegeladen, terwijl het admin-scherm die data eigenlijk maar één keer per dag nodig heeft.
Customizer en thema-data. theme_mods_<thema> rijen kunnen groeien als het thema veel customizer-state daar neerzet. Page builders zijn hier vooral gevoelig voor.
WooCommerce en ecommerce-extensies. Sessiedata, grote product-attribuut taxonomieën, shipping-zone configuraties en allerlei extensie-instellingen. Op een WooCommerce-site met 20 plugins kan autoload creep snel gaan.
max_allowed_packet errors als gerelateerd symptoom. Als een plugin een hele grote waarde probeert op te slaan en je ziet Got a packet bigger than 'max_allowed_packet' bytes in de error log, is dat een andere failure mode maar met dezelfde kern: iets gebruikt wp_options als algemene data store. De oplossing is niet om die payload nog in wp_options te proppen. Het verhogen van de MySQL-limiet is het laatste redmiddel, niet het eerste.
De Redis-misvatting die de meeste sites de mist in stuurt
Ik neem hier even de tijd voor, want dit is het meest voorkomende misverstand dat ik op support-tickets tegenkom. Het misverstand is dat Redis autoload-problemen "oplost". Dat doet Redis niet.
Zo werkt het echt. De alloptions cache entry wordt onder één Redis key opgeslagen. Bij een koude cache draait WordPress de database-query voor alle autoload-opties, serialiseert het hele resultaat, en zet dat als één Redis value neer. Bij elke volgende request trekt WordPress die ene Redis value eruit, serialiseert die uit, en houdt hem in PHP-geheugen vast voor de duur van de request. Het trage stukje van de oorspronkelijke flow (de MySQL-query) is vervangen door een snel stukje (een Redis GET). Dat is wel degelijk winst, en daarom helpt een goed opgezette Redis object cache WordPress performance in het algemeen ook echt.
Maar de PHP-geheugenkost gaat niet weg. Als je autoload-data 8 MB is, haalt elke request nog steeds 8 MB uit Redis, draait er unserialize() overheen, en houdt een array van 8 MB in PHP-geheugen tot de request klaar is. Redis maakte de fetch sneller. Het maakte de data niet kleiner, en de deserialize- en geheugenkost is wat er écht pijn doet op een trage PHP-worker.
Dan is er ook nog de Memcached failure mode, en die is erger. Memcached heeft standaard een limiet van 1 MB per key. De WordPress VIP documentatie over autoloaded options vermeldt dat wanneer alloptions groter wordt dan 1 MB op een Memcached-backed cache, de cache-set stilletjes faalt. WordPress valt dan bij elke request terug op de MySQL-query, en VIP heeft specifiek HTTP 503-responses gedocumenteerd die aan "Error 1024 (alloptions)" gekoppeld zijn als het downstream-symptoom. De site wordt dan dramatisch trager dan ie zou zijn zonder object cache. Zit je op Memcached en kruipt je autoload richting de 1 MB, ruim dan eerst de data op en stapel er geen cache overheen.
Het juiste mentale model: Redis cachet vuile data sneller. Ruim de data eerst op, geniet daarna van de cache.
Autoload-data veilig verkleinen
Het idee dat de meeste mensen verkeerd hebben, is dat aan wp_options zitten gevaarlijk is. De waarheid is genuanceerder. Autoload uitzetten op een rij is bijna altijd veilig, want je verandert de waarde niet, je vertelt WordPress alleen dat hij de rij on-demand moet ophalen in plaats van alvast in het geheugen te stoppen. Als een plugin de optie nodig heeft, vindt get_option() hem gewoon, alleen via een verse SQL-query op het moment van aanroepen. Er gaat niets kapot. Rijen verwijderen is het risicovolle deel, en dat doe je pas als je zeker weet dat de optie verweesd is.
De veilige workflow:
- Maak een database-backup. Elke destructieve stap hieronder gaat ervan uit dat je kunt terugrollen.
- Meet. Draai de vier-waarden SQL-query. Zit je onder 800 KB, dan stop je hier. Je probleem zit ergens anders, en mijn artikel over wat een trage WordPress-database nou écht betekent behandelt de andere vier veel voorkomende oorzaken.
- Identificeer de boosdoeners. Draai de top-20 query hierboven. Schrijf elke grote rij op en deel ze in.
- Zet eerst autoload uit op de makkelijke winsten. Voor alles wat duidelijk veilig is om niet bij elke request alvast te laden (grote license-response blobs, instellingen die zelden gelezen worden, oude plugin-data waar je nog niet zeker van bent), zet je de autoload op
off. Via WP-CLI:wp option set-autoload <option_name> off. Dit commando staat gedocumenteerd op developer.wordpress.org/cli. Via SQL:UPDATE wp_options SET autoload = 'off' WHERE option_name = 'option_name_hier';. - Ruim verlopen transients op. Transients klitten samen in
wp_optionsen verlopen exemplaren zijn veilig te verwijderen. Het makkelijkst is een onderhouden opruim-plugin zoals WP-Optimize of Advanced Database Cleaner, die een knop "verlopen transients verwijderen" in wp-admin bieden. Je kunt ook deze SQL in phpMyAdmin draaien:DELETE FROM wp_options WHERE option_name LIKE '_transient_timeout_%' AND option_value < UNIX_TIMESTAMP();gevolgd doorDELETE FROM wp_options WHERE option_name LIKE '_transient_%' AND option_name NOT LIKE '_transient_timeout_%' AND option_name NOT IN (SELECT REPLACE(option_name, '_transient_timeout_', '_transient_') FROM (SELECT option_name FROM wp_options WHERE option_name LIKE '_transient_timeout_%') AS t);. WP-CLI alternatief:wp transient delete --expired. - Verwijder bevestigde wezen. Voor rijen van plugins die je volledig hebt gedeïnstalleerd mag je de rij verwijderen. Selecteer in phpMyAdmin de betreffende rij in de
wp_optionstabel en klik op Verwijderen, of draaiDELETE FROM wp_options WHERE option_name = 'option_name_hier';in het SQL-tabblad. WP-CLI alternatief:wp option delete <option_name>. Doe dit alleen voor rijen waarvan je zeker weet dat de plugin niet terugkomt. - Meet opnieuw. Draai de vier-waarden query nog een keer. Je wil ruim onder 800 KB komen, idealiter onder 300 KB.
Rijen waar je de autoload-status nooit aan mag komen: siteurl, home, blogname, active_plugins, template, stylesheet, WPLANG, db_version, en alles waarvan de naam met wp_ begint. Dat is core en moet geautoload zijn. Hetzelfde geldt voor alles waar je actief geïnstalleerde plugins op leunen in hun per-request logica. Het hele idee van autoload is dat een kleine core-set opties snel beschikbaar is. Het doel is terugkomen bij die kleine core-set, niet autoload helemaal uitzetten.
Waarom verse WP 6.6-installaties minder snel bloated raken
De veranderingen van 6.6 gaan over de toekomst, niet het verleden. Op een verse installatie op 6.6 of nieuwer krijgt een plugin die add_option('mijn_grote_payload', $bigArray) aanroept zonder expliciet autoload-argument automatisch auto-off toegewezen, omdat de payload groter is dan 150 KB. Precies het gedrag dat je wil.
Maar: bestaande rijen in je database houden gewoon de autoload waarde die ze hadden toen ze werden weggeschreven. Er draait geen automatische migratie. Een site die in 2019 is opgezet en elke release heeft meegepikt, sleept nog steeds de yes-zware footprint van elke plugin-installatie mee die hij ooit heeft gezien. WP 6.6 heeft je gered van de bloat van morgen. De bloat van vandaag ruim je met de hand op.
Autoload-bloat voorkomen in de toekomst
Een paar gewoontes houden autoload onder controle op sites die ik beheer:
- Audit nieuwe plugins voor installatie. Een snelle blik op de
add_option()calls in de plugin-source vertelt je of de plugin explicietfalsemeegeeft of dat hij op de default leunt. Volwassen plugins zijn meestal prima, maar bij kleinere of oudere plugins zitten de verrassingen. - Draai de vier-waarden query elk kwartaal. Maak er een terugkerend klusje van. Vijf minuten om de drie maanden vangt creep voordat het een kritieke Site Health waarschuwing wordt.
- Geef de voorkeur aan plugins met een nette uninstall-hook. De Plugin Handbook sectie over uninstall.php beschrijft hoe een plugin z'n opties hoort op te ruimen als hij verwijderd wordt. Plugins die dit niet volgen zijn de belangrijkste bron van wezen-rijen.
- Beschouw grote autoload-waardes als code smell. Als een plugin (vooral een custom plugin) 100 KB in één
wp_optionsrij wegschrijft, hoort die data waarschijnlijk thuis in een custom tabel, een transient met een lange expiry op een object cache backend, of op het filesystem.wp_optionsis geen cache, het is de instellingen-tabel. - Houd de WordPress Performance-plugin in je achterhoofd. De Performance Lab-plugin die door het WordPress Performance-team wordt onderhouden bevat health checks voor autoload en andere performance-gerelateerde toevoegingen.
Wanneer je hulp moet inschakelen
Schakel hulp in als je één van deze situaties tegenkomt:
- Autoload-data boven de 3 MB en geen duidelijke enkele boosdoener in de top-20 lijst.
- Site Health blijft autoload als kritiek markeren na een opruimronde, omdat iets bij elke request opnieuw autoloaded rijen wegschrijft.
max_allowed_packeterrors in de MySQL error log, wat duidt op een plugin die enorme payloads naarwp_optionswegschrijft.- HTTP 503-fouten of willekeurige cache-misses nadat je Memcached hebt ingeschakeld op een site met veel autoload-data, de gedocumenteerde WordPress VIP failure mode.
- Onbekende option-namen die de top-20 domineren en die je niet aan een geïnstalleerde plugin kunt koppelen.
Als je om hulp vraagt, houd dan dit klaar: de output van de vier-waarden meetquery, de top-20 boosdoeners-query, je WordPress- en PHP-versies, of er een object cache backend draait (Redis of Memcached) en hoeveel geheugen die heeft, de lijst met actieve plugins, en een recente database-backup. Dat is genoeg om iemand zonder extra heen-en-weer te laten triagen.
Is autoload-bloat slechts één symptoom van een breder performance-probleem? Het artikel over hoge TTFB loopt door de andere veelvoorkomende oorzaken van server-side traagheid op WordPress. Gaat het bredere probleem over opgehoopt afval door de hele database (revisies, verweesde postmeta, verlopen transienten, gefragmenteerde tabellen), dan behandelt de database-opschoongids proactief onderhoud voorbij wp_options.