Store caching¶
The Store can optionally use a second backend as a local cache for selected
namespaces, which is especially useful when the primary backend is remote,
slower or otherwise more “expensive” than the cache.
Configuration¶
cache_urlorcache_backend: where cached data is storedconfig: mapping of namespace to its configuration dict, containing nesting levels and cache policy settings.
Each namespace configuration dictionary can have:
levels: a required list of integers specifying nesting levels.cache: optional cache mode, acceptingCacheModevalues or string aliases:CacheMode.C_OFFor"off": bypass cache completely (default).CacheMode.C_MIRRORor"mirror": always read from primary backend, but update the cache after successful primary backend reads and writes.CacheMode.C_WRITETHROUGHor"writethrough": read-through + write-through. For now, only content-hash addressed namespaces should use this mode.
max_age: optional maximum age expressed in seconds since last access. The default isNone(no age limit).size: optional maximum size in bytes. It sets a per-namespace cache size budget enforced by evicting least-recently-used items until the namespace total size is within the configured budget.
Example:
from borgstore.store import Store, CacheMode
store = Store(
url="sftp://user@host/repo",
config={
"data": {
"levels": [2],
"cache": "writethrough",
"max_age": 3600,
"size": 4 * 1024**3,
},
"meta": {
"levels": [1],
"cache": CacheMode.C_MIRROR,
},
},
cache_url="file:///home/user/.cache/borgstore/repo",
)
Behavior¶
Cache keys are identical to primary backend keys (same nesting).
Soft-deleted items are cached under the same
.delname as primary.Soft delete/undelete renames cache entries as well.
On
Store.open()andStore.close(), cache-enabled namespaces are scanned to clean up the cache. Cleanup order per namespace is:remove expired cache objects when
max_ageis configured,if
sizeis configured, evict the least-recently-used remaining items until the namespace total size is<= size.
Expired entries are always removed first, even if total size is already below the
sizelimit.Cache failures are non-fatal and logged as warnings.
Manual Cache Invalidation¶
If you need to programmatically clear or invalidate parts of the cache (for
example, to resolve stale objects after primary backend deletes by other
clients, or if cache corruption is suspected), you can use the
cache_invalidate method:
To invalidate a single item:
store.cache_invalidate("data/00000000")
To invalidate all cached items in a specific namespace (e.g.
"data/"):store.cache_invalidate("data/")
To invalidate all cached items across all configured namespaces, pass
ROOTNS:from borgstore.constants import ROOTNS store.cache_invalidate(ROOTNS)
Limitations¶
Eviction by
max_ageorsizeis open-time and close-time only (Store.open()/Store.close()), not continuous duringstore()/load()operations.No proactive cache validation/revalidation.
If an object is deleted in the primary backend by another client, the local cache will still have a stale object.
max_ageand LRU-by-sizedepend on backendItemInfo.atimesupport. Ifatimeis 0 (not implemented):using
max_agewould empty the cache onStore.open()orStore.close()using
sizewould not work in LRU order, because order can’t be determined
If a partial range
loadcall for an object in a cached namespace causes a cache miss, the full object will be read from the primary backend and the cache will be populated with the full object.
Statistics¶
Store.stats includes cache counters:
backend_load_volumebackend_store_volumebackend_load_callsbackend_store_callsbackend_delete_callscache_disabledcache_hitscache_missescache_hit_ratiocache_errorscache_load_volumecache_store_volumecache_load_callscache_store_callscache_delete_calls