Skip to content

WordPress and WooCommerce sites

option-churn

Find options that are rewritten across WordPress requests, who writes them, which keys change, and how often update calls do nothing.

Kind
Open-source tool
Language
PHP
License
MIT
Status
First release
  • database
  • developer-tools
  • profiling
  • wordpress
  • wordpress-performance
  • wordpress-plugin
  • wp-cli
  • wp-options

Repeated writes add up

Some WordPress performance problems come from options that are written far more often than their data requires. A plugin may save a timestamp, increment a counter, or rebuild a cached array on every page view. That turns otherwise read-heavy traffic into repeated updates to the options table.

Autoloaded options make this pattern more expensive. Each write also replaces WordPress's cached copy of all autoloaded options. With a persistent object cache, that can mean replacing a large cached entry on every request.

A single request does not show the full pattern. Query Monitor can show queries from that request, but a recurring write may only become obvious after many page views. option-churn records totals across requests so you can see how often each option was touched, how many writes changed the stored value, and how many update_option() calls WordPress skipped because the value was unchanged.

The repository's example is synthetic. Its must-use fixture writes acme_heartbeat, calls update_option() with the same value for acme_static, and creates a transient. The resulting report demonstrates how repeated writes, unchanged calls, and transient activity appear after multiple front-end requests.

What it records

The plugin watches option writes made through update_option(), add_option(), delete_option(), transients stored in the options table, and the network equivalents on multisite.

For each option, it records how many requests of that type wrote it, the number of writes that changed the row, and the number of skipped update_option() calls. It also records the average serialized size of written values and the option's current autoload setting.

Request types are front, admin, ajax, rest, cron, cli and xmlrpc. Reports can combine them or focus on one type.

Caller attribution comes from the first call site outside WordPress core, found with debug_backtrace(). The report can identify a plugin, must-use plugin, theme or core function. If core makes the write itself, the core function is shown.

For arrays and objects, changed_keys records which top-level keys changed and the share of writes in which each key changed. A transient's timeout row is counted with the transient.

Decisions that matter

option-churn separates calls from writes. That distinction matters when code calls update_option() on every request but keeps passing the same value. WordPress skips the row update in that case, and the plugin records the call as noop_calls rather than as a write.

The plugin also records activity across many requests instead of treating one page load as representative. That makes patterns such as an option written on most front-end requests visible in one report.

Its own storage sits outside the options table. Running totals live in {prefix}option_churn_requests, _writes and _keys. At shutdown, each recorded request adds its counts to the running totals for each option and caller it touched, and a row is created only the first time that pair is seen. A request that writes no options still adds one statement for its request count.

Settings live as constants in wp-config.php, so changing recording behavior does not create another option write. OPTION_CHURN_RECORD controls whether recording is active. OPTION_CHURN_SAMPLE can record only a share of requests. OPTION_CHURN_SKIP excludes request types such as cli or cron.

The plugin's own WP-CLI commands are excluded from recording.

Data stays local

The plugin keeps running totals only. Those totals include option names, top-level key names, callers, counts and sizes. It never stores option values.

It makes no network requests and uses no API key. There is no model involved. The recorded data remains in the site's own option-churn tables until you reset or remove it.

The report is available through WP-CLI and under Tools, Option churn for users who can manage_options. JSON output is available for scripts through wp option-churn report --format=json.

Reading the report

An option written on most requests can point to data whose update frequency does not match how often it needs to change. If only one top-level key changes, the report can help narrow the cause to a timestamp, counter or similar field.

The README suggests several responses once you have confirmed the pattern. Frequently changed values can move to wp_cache_set() in a persistent object cache, be written less often, or leave the options table. An option that is written often but read rarely may be a candidate for autoload off.

High noop_calls counts show code repeatedly calling update_option() with unchanged data. WordPress avoids the write, but it still compares the value. An option that is not autoloaded may also need to be read from the database first.

A transient rebuilt on every request is another pattern the report can expose. In that case, the README points to its expiry and the condition around set_transient() as places to inspect.

Where it stops

The plugin only sees writes that pass through WordPress's option functions. Direct $wpdb updates to the options table are outside its view.

Transients kept in a persistent object cache never reach the options table, so they are not counted. changed_keys compares only top-level keys rather than nested differences.

Caller attribution follows the first call site outside core. If one plugin writes through another plugin's API, the plugin whose code made that call receives the attribution.

On multisite, totals are kept per site in shared tables. Network options are counted under the site that made the request.

A normal plugin starts recording only after WordPress loads it. That means writes from must-use plugins and plugins loaded before option-churn are missed. The README provides a must-use loader for cases where those earlier writes also need to be counted.

Recording adds database work of its own, so the README recommends development and staging use, or a short production window with OPTION_CHURN_SAMPLE, followed by deactivation once the needed numbers have been collected.

Install and run

The plugin requires WordPress 6.6 or later and PHP 7.4 or later. Activation creates its tables. After collecting requests, use the report to compare options across all request types or a selected type.

git clone https://github.com/hamzaahmadaslam/option-churn.git wp-content/plugins/option-churn
wp plugin activate option-churn
wp option-churn report
wp option-churn report --type=front
wp option-churn status

If it is loaded through wp-content/mu-plugins/option-churn-loader.php, activation does not run, so wp option-churn install must be run once to create the tables.

Related