In PSR-6, what do CacheItemPoolInterface::saveDeferred() and commit() do, and what may a pool do with deferred items before commit()?
answer
- queue now, persist later
- commit() persists everything outstanding
- pool may persist early
- getItem() sees the deferred value
- both return bool, not exceptions
basics
~20 ssaveDeferred() queues an item for later persistence and commit() persists every queued item, so a pool can batch writes. A pool may persist deferred items earlier, must not lose them, and must return them from getItem() before commit.
solid answer
~40 sIn PSR-6, `save($item)` persists an item immediately, while `saveDeferred($item)` only queues it, letting the pool use bulk-set operations its storage supports. `commit()` persists every outstanding deferred item and returns `true` if all were saved or there were none. The specification leaves timing to the pool: it may persist deferred items before `commit()`, for example on `save()`, on a size limit or in a destructor, but it must eventually persist them and not lose data, and a `getItem()` for a deferred key must return the deferred value. `saveDeferred()` returns `false` if the item could not be queued, or if a commit was attempted and failed. Failures are reported through these booleans, since implementations should trap storage errors rather than let them bubble, so a caller that needs certainty must check `commit()`'s return value.
code
php · 18 lines<?php
declare(strict_types=1);
use Psr\Cache\CacheItemPoolInterface;
/** @param array<string, float> $rates */
function warmRates(CacheItemPoolInterface $pool, array $rates): bool
{
foreach ($rates as $pair => $rate) {
$item = $pool->getItem('rate.' . $pair)->set($rate)->expiresAfter(600);
$pool->saveDeferred($item);
}
// Same pool, before commit: the deferred value must already be visible.
// $pool->getItem('rate.EURUSD')->get() returns the queued rate.
return $pool->commit(); // false if any deferred item failed to persist
}go deeper
Recall that save() writes now, saveDeferred() queues, and commit() writes everything queued.
Explain the five deferred-item rules, especially early persistence and read-your-own-deferred-writes, and why failures come back as booleans.
Use deferred saves for bulk warm-ups, always finish with a checked commit(), and never build correctness on deferral being atomic.
Judge when batching cache writes is worth the added failure modes, compared with PSR-16 setMultiple() or no batching at all.
## Immediate versus deferred saves In **PSR-6**, a `CacheItemPoolInterface` offers two ways to persist an item you obtained with `getItem()` and changed with `set()`: | Method | Effect | Returns | |---|---|---| | `save($item)` | persists the item immediately | `true` on success, `false` on error | | `saveDeferred($item)` | queues the item to be persisted later | `false` if it could not be queued, or if a commit was attempted and failed; `true` otherwise | | `commit()` | persists every outstanding deferred item | `true` if all were saved or there were none; `false` otherwise | The point of deferral is **batching**. Many storage engines can write many keys in one round trip; a pool that collects deferred items can send them together instead of one network call per item. ## What the specification promises about deferred items The **Deferred** definition in PSR-6 sets these rules: 1. A pool **may** delay persisting a deferred item to take advantage of bulk operations. 2. It **must** ensure deferred items are eventually persisted and data is not lost. 3. It **may** persist them before the caller asks, using any logic it likes: an object destructor, persisting everything on the next `save()`, a timeout, or a maximum-items check. 4. When the caller invokes `commit()`, all outstanding deferred items **must** be persisted. 5. A request for a deferred item **must** return the deferred, not-yet-persisted item. Rule 5 means code in the same process reads its own writes: after `saveDeferred()`, `getItem()` for that key sees the new value even if storage has not been touched yet. ## What callers should and should not assume - Do **not** assume nothing is written until `commit()`: rule 3 allows early persistence, so deferral is not a transaction and there is no rollback. - Do **not** rely on a destructor to flush: some pools do, but the specification only lists it as one permitted strategy. Call `commit()` explicitly when the batch is done. - **Do** check the return value of `commit()` when correctness depends on the writes; errors surface as `false`, not exceptions. - Other processes cannot see deferred items until they are persisted; in practice only the pool object holding the queue can answer with them. ## Why failures are booleans PSR-6's error-handling section says caching should never be a critical part of application functionality. Implementations **must not** throw exceptions other than those the interfaces define, and **should** trap errors from the underlying store rather than let them bubble, logging or reporting them instead. So a storage outage typically appears as `save()`, `saveDeferred()` or `commit()` returning `false`, and a later `getItem()` returning a miss. The exceptions that can reach callers implement `Psr\Cache\CacheException`, and an illegal key raises `Psr\Cache\InvalidArgumentException`. ## Example: warming many rates at once An exchange-rate library can warm its cache for every currency pair after one bulk API call: ```php foreach ($rates as $pair => $rate) { $item = $pool->getItem('rate.' . $pair); $pool->saveDeferred($item->set($rate)->expiresAfter(600)); } if (!$pool->commit()) { $this->logger->warning('Rate cache warm-up was not fully persisted'); } ``` With a backend that supports multi-key writes, this becomes one round trip instead of one per pair. ## Comparison with PSR-16 PSR-16 has no deferred mode. Its batch equivalent is `setMultiple($values, $ttl = null)`, which writes a set of key/value pairs in one call and returns a boolean. That covers the "many writes, one round trip" case, but not incremental queuing across different parts of the code followed by a single flush. ## Checklist - Use `saveDeferred()` for many writes that belong together; `save()` for single writes. - Always finish a batch with `commit()` and check its result. - Treat deferral as batching, not as a transaction.
- Is PSR-6's saveDeferred() plus commit() a transaction you can roll back?No. The pool may persist deferred items before `commit()`, on its own schedule, and PSR-6 defines no rollback method. Deferral exists so the pool can batch writes, not to make a set of writes atomic. Treat `commit()` as a flush whose result you check.
- How does a PSR-6 caller find out that a cache write failed?Through return values: `save()`, `saveDeferred()` and `commit()` return `false` on failure. PSR-6 tells implementations not to throw anything beyond the defined exception interfaces and to trap storage errors, reporting them instead, so an outage usually looks like `false` on write and a miss on read.
saying these in an interview costs you the question
- Believes nothing reaches storage until commit() is called.
- Thinks getItem() on a deferred key returns a miss until commit().
- Treats saveDeferred() and commit() as an atomic transaction with rollback.
- Expects commit() to throw an exception when a write fails.
- Relies on the pool's destructor to flush deferred items.