shutter::Cache stores reusable binary data in a ShutterDB file and manages its size.
Use it for previews, compiled assets and other data the application can regenerate.
#include <shutter/cache.hpp>
shutter::Cache cache("assets.shdb", {.max_bytes = 1024ULL * 1024 * 1024});
cache.put("asset:42", "generated bytes");
auto bytes = cache.get_string("asset:42");
cache.sync();Link ShutterDB::ShutterDB. The preview example uses content hashes
to reuse generated images across process restarts.
max_bytes defaults to 1 GiB and includes the main file's header, keys, values and
obsolete records. After a successful write, removal or open, the main file is at or
below that budget. The minimum budget is 32 bytes, enough for an empty file.
When an entry needs room, the cache removes the least recently used entries. Successful reads and writes update recency. After restarting, recency starts from the surviving records' write order; reads do not add bookkeeping records to the log.
When the append log exceeds the budget, the cache compacts it. Cleanup aims to leave
one eighth of the usable budget free, so the next few writes do not each trigger a
full rewrite. The value being inserted is protected from that cleanup. An entry too
large for the whole budget returns false before changing or evicting anything.
Appending and compaction need space beyond the budget. Compaction retains the old file while writing the replacement. Its rollback backup shares the old file's bytes on filesystems with hard-link support; other filesystems need an additional full copy. Failed maintenance can leave sidecars or a file over budget; reopening recovers the file and reapplies the budget.
Opening an existing ShutterDB file as a cache can evict its records to meet the chosen
budget. Use DB for records that must remain until the application explicitly deletes them.
| Operation | Result |
|---|---|
put(key, bytes) or put(key, text) |
true when stored; false when too large for this cache |
get(key) |
Owned byte vector, or an empty optional for a miss |
get_string(key) |
Owned string, or an empty optional for a miss |
remove(key) |
Whether the entry existed |
stats() |
Storage statistics, budget, hits, misses and evictions |
sync() |
Synchronize buffered writes |
compact() |
Reclaim obsolete records immediately |
Counters cover the current handle's lifetime. stats().storage contains the ordinary
database statistics. Keys and values retain the DB limits.
Calls on one cache handle are serialized, and that handle exclusively owns its file.
Cache writes are buffered by default. Call sync() at application checkpoints or set
CacheOptions::storage.sync_writes = true for individually synchronized writes.
Closing a handle does not synchronize buffered writes. Eviction and insertion are separate
operations; a failed insertion can leave earlier evictions in place.
The CLI can inspect, verify and compact cache files. DB can open them when the cache
handle is closed.