Skip to main content

When bumping PHP memory isn't enough: tracing serialized cache bloat in a page builder

·750 words·4 mins
Tech WordPress Performance PHP Caching

A colleague flagged a 500 critical error on a WordPress site and traced it quickly to PHP memory. The fix seemed obvious: bump the limit from 128M to 512M and move on. That worked, but it left me uneasy. A 4x increase to stop a crash is a blunt instrument, and I wanted to understand what was actually consuming that memory before we closed the ticket.

What I found was a page builder caching large serialized CSS blobs in the object cache — and PHP unserializing those blobs on every relevant request. The memory increase bought stability, but it didn’t address the underlying pressure.

What the page builder was actually doing
#

Illustration for What the page builder was actually doing This particular page builder stores each page’s layout as JSON in a post meta key (something like _builder_data). On save — or on first view — it compiles that JSON into CSS and writes a minified file under wp-content/uploads/builder/css/. So far, so reasonable.

The problem is what happens with object caching enabled. The builder also caches its compiled output — CSS blobs, merged widget styles, metadata pointers — as serialized strings in the object cache, and some of those strings get large. When PHP pulls one of those values from cache and unserializes it, memory spikes. Under 128M, that spike was occasionally fatal. Under 512M, it mostly isn’t — but the spike still happens.

The specific setting that made this worse was CSS Print Method: Embedding. In that mode, the builder injects its compiled CSS inline into the HTML response rather than linking to the external file it already wrote to disk, which means the CSS blob travels through PHP’s memory on every page load, not just at compile time. I didn’t know this setting existed until I started looking at what the builder actually writes to the object cache, and the connection between a UI toggle and a memory spike is not something you’d guess from the setting’s label. I only found it by looking at what the builder writes to post meta and how the object cache interacts with it — not from any log or error message that pointed there directly.

What I changed
#

Illustration for What I changed Two things, on top of the memory increase my colleague had already applied:

1. Flushed the page builder’s CSS and JS cache via the builder’s own cache-clearing tool. This forced a clean recompile and wrote fresh files to disk, clearing out any stale or oversized blobs sitting in the object cache.

2. Changed CSS Print Method from Embedding to External File in the builder’s performance settings. With this set, the builder links to the CSS file on disk rather than injecting compiled styles inline. The file still exists and is still served — PHP just doesn’t have to hold the blob in memory on every request.

These two changes should reduce the size of what gets serialized and cached, and more importantly, reduce how often PHP has to unserialize large values during normal page rendering. I didn’t capture a before/after memory profile on this one, which I regret — a rough reading of the cached blob size before flushing would have made the diagnosis a lot more concrete than it ended up being.

What this actually taught me
#

Illustration for What this actually taught me The memory limit increase was the right emergency move, and I’m not second-guessing my colleague’s call. A 500 is a production outage and a higher limit stopped it. But stopping there would have left us with a system stable mostly by coincidence — stable because 512M happened to be enough headroom for the current content volume, until it wasn’t. Spending an hour on the why before closing the ticket is what turned this from a band-aid into an actual fix.

The serialized blob pattern is easy to miss if you’re only watching memory totals and not cache contents. Large values can look perfectly fine in the database, then become expensive the moment PHP has to reconstruct them in memory at request time. If you’re running a visual page builder with object caching enabled, check whether compiled CSS is being stored as a serialized value and how large those values are getting. The External File print method is almost always the right default: it keeps CSS on disk where it belongs and out of PHP’s memory on every request. Embedding may have made more sense when avoiding an extra HTTP request mattered more than it does now; on a setup with object caching active, External File is almost always the better choice.

Related

That 4 MB `options:notoptions` key is why your WordPress site throws a 500 every ten days
·788 words·4 mins
Tech WordPress Redis Performance Caching
An intermittent WordPress 500 that cleared on refresh turned out to be a single 4 MB Redis key growing without a TTL. Here is what the big-keys scan showed, why the mechanism is easy to miss, and the three config changes that stopped it.
The silent memory leak: debugging intermittent 500s in WordPress Redis caching
·1026 words·5 mins
Tech WordPress Redis Performance Debugging
A WordPress site was throwing intermittent 500 errors every ten days. The cache flushed clean each time, which masked the real problem: a single Redis key growing to 4MB and occasionally tipping PHP memory over the edge.
WooCommerce slows down under concurrency, not under load
·1092 words·6 mins
Tech WooCommerce WordPress Performance Scaling
The WooCommerce performance failures that actually hurt at scale don’t show up in standard audits. They live in plugins doing unbounded per-request work that looks harmless at five requests per second and falls apart at twenty.