Skip to content

[Improvement] Make object cache write limit (maxWriteToCacheItems) configurable + log drops #213

Description

@mcop1

Summary

The object cache silently writes at most 50 items per request, hardcoded and not configurable through any config/DI/env option. Make the limit configurable and log when items are dropped.

Problem

protected int $maxWriteToCacheItems = 50; (lib/Cache/Core/CoreCacheHandler.php:122). The only mutator is setMaxWriteToCacheItems() (:230), which has no caller and no config binding anywhere in lib/ or bundles/.

Flow for a normal element load:

  • Cache::save() queues the item (non-forced path) into $saveQueue (:325).
  • Mid-request, when the queue exceeds 3 × 50 = 150, cleanupQueue() trims it back to 50 (:340, :361) — memory protection.
  • On shutdown, writeSaveQueue() calls cleanupQueue() unconditionally before the write loop (:813), so at most 50 items ever reach the backend, regardless of queue size.
public function cleanupQueue(): void
{
    uasort($this->saveQueue, fn (CacheQueueItem $a, CacheQueueItem $b) => $b->getPriority() <=> $a->getPriority());
    array_splice($this->saveQueue, $this->maxWriteToCacheItems);   // silent drop, no log
}

Elements save with default priority 0, so beyond 50 the survivors are arbitrary. Consequence: any request touching >50 elements persists at most 50 — the rest reload cold on every subsequent request, regardless of cache pool capacity. The drop is invisible (no log, no metric).

Measured (demo, DB cache backend; warm one process, measure a fresh cold load; 286 objects → ~800 write candidates)

Scenario cache_items rows Next load: queries Next load: time
Stock (limit 50) 102 5,069 4,841 ms
Limit raised 1,074 576 1,055 ms
Cache disabled (baseline) 4,436 ~4,360 ms

Note the middle row vs the baseline: with the cap at 50, the "cached" run is slower than no cache at all (5,069 > 4,436 queries) — every miss pays a cache-lookup round-trip on top of full rehydration. On the default backend the hardcoded cap makes the object cache a net negative for element-heavy requests.

Proposed solution

  1. Config binding (default stays 50, so BC):

    pimcore:
        cache:
            max_write_items: 50      # -1 = unlimited
            handle_cli: false        # allow cache writes from CLI (also currently a setter-only flag)

    wired via calls: [[setMaxWriteToCacheItems, ['%pimcore.cache.max_write_items%']], [setHandleCli, ['%pimcore.cache.handle_cli%']]] in cache.yaml.

  2. Log the drop in cleanupQueue() (count + first N keys, rate-limited per process) so poor hit rates become diagnosable.

  3. (Optional follow-up) deterministic element priority tiers so that when a limit is hit, eviction is predictable rather than arbitrary.

The 50-item bound was reasonable for the old DB cache backend; on Redis it is far too low. Keeping it configurable (not removing it) preserves the shutdown-latency/memory protection for those who need it.

Effort

S — config node, DI wiring, one log line, tests. No core-logic change (the setter already exists — the benchmark above raised the limit at runtime without patching core).

Related

Part of the performance audit; companion ticket: permission-check memoization (separate issue).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    Affected capability

    None yet

    Platform Version

    None yet

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions