Object cache: zstd compression #713

Open
opened 2026-10-12 11:17:23 +00:00 by sky_line · 1 comment
Owner

Part of #715 (cache rewrite epic).

Per-region object cache files compress ~3x at zstd-3 (7.4 MB -> 2.4 MB, decompress ~2.7 GB/s). Region-entry read on a laptop HDD ~90 -> ~45 ms, desktop HDD ~55 -> ~25 ms, SATA SSD 13 -> 8 ms. Versioned header so old files are read or discarded cleanly.

Part of #715 (cache rewrite epic). Per-region object cache files compress ~3x at zstd-3 (7.4 MB -> 2.4 MB, decompress ~2.7 GB/s). Region-entry read on a laptop HDD ~90 -> ~45 ms, desktop HDD ~55 -> ~25 ms, SATA SSD 13 -> 8 ms. Versioned header so old files are read or discarded cleanly.
sky_line added this to the Slipstream project 2026-10-12 11:17:23 +00:00
Author
Owner

Interface audit (step 1 of #715): the object cache keeps its files and gets only the codec.

Touches

Code Today
newview/llvocache.cpp:838-839 objects_<x>_<y>.slc and objects_<x>_<y>_extras.slec per region in objectcache/
:1799 serialize_object_cache (main thread) -> :1851 writeToCache -> I/O thread writes .tmp, LLFile::replace (#528) write
:1470 read_object_cache_file, :1572 read_extras_cache_file (I/O thread), :1662 readFromCacheAsync, results via drainCompletions (#568) read
object.cache header: HeaderMetaInfo {version 18, address size} + 128 HeaderEntryInfo; version mismatch removes the whole cache (:1114-1124) versioning

.slc today = region UUID (16 B) + entry count (S32) + entries. There is no magic and no whole-file checksum; validity comes from the UUID match, per-entry decode and size checks.

Slice of the interface

Only LLCache::Codec (the store library's byte codec, #710). LLVOCache does not join the segment store (per #708).

// new file header (both .slc and .slec), little endian
struct VOFileHeader { U32 magic /*"SLC2"*/; U16 header_version; U8 codec; U8 level;
                      U64 raw_bytes; U64 stored_bytes; U64 xxh64_raw; };
Codec::encode(Codec::ZSTD, 3, serialized.data(), n, out);   // in the I/O job, not the main thread
Codec::decode(...)                                         // in read_object_cache_file before parsing
  • Reader: if the first 4 bytes are the magic, decode and then parse as today from an in-memory buffer. Otherwise it is a legacy file and the existing reader runs. No cache version bump, so nothing is wiped.
  • A hash mismatch or decode failure drops that region's file only (as a corrupt read does today: the region is re-requested).
  • Compression runs on the I/O thread (move encode into the postIO job), so the main-thread serialize cost does not grow.
  • Toggle: ObjectCacheCompression (on by default). Off = write IDENTITY with the same header.

Drop-in plan

Depends only on the codec + zstd package (#710 step 0-1), not on the segment store, so it can land any time. Measure region-entry read time on HDD / SATA SSD before and after (ticket numbers: 7.4 -> 2.4 MB, ~90 -> ~45 ms laptop HDD).

**Interface audit (step 1 of #715): the object cache keeps its files and gets only the codec.** ### Touches | Code | Today | |---|---| | newview/llvocache.cpp:838-839 | `objects_<x>_<y>.slc` and `objects_<x>_<y>_extras.slec` per region in `objectcache/` | | :1799 `serialize_object_cache` (main thread) -> :1851 `writeToCache` -> I/O thread writes `.tmp`, `LLFile::replace` (#528) | write | | :1470 `read_object_cache_file`, :1572 `read_extras_cache_file` (I/O thread), :1662 `readFromCacheAsync`, results via `drainCompletions` (#568) | read | | `object.cache` header: `HeaderMetaInfo {version 18, address size}` + 128 `HeaderEntryInfo`; version mismatch removes the whole cache (:1114-1124) | versioning | `.slc` today = region UUID (16 B) + entry count (S32) + entries. There is no magic and no whole-file checksum; validity comes from the UUID match, per-entry decode and size checks. ### Slice of the interface Only `LLCache::Codec` (the store library's byte codec, #710). LLVOCache does **not** join the segment store (per #708). ```cpp // new file header (both .slc and .slec), little endian struct VOFileHeader { U32 magic /*"SLC2"*/; U16 header_version; U8 codec; U8 level; U64 raw_bytes; U64 stored_bytes; U64 xxh64_raw; }; Codec::encode(Codec::ZSTD, 3, serialized.data(), n, out); // in the I/O job, not the main thread Codec::decode(...) // in read_object_cache_file before parsing ``` - Reader: if the first 4 bytes are the magic, decode and then parse as today from an in-memory buffer. Otherwise it is a legacy file and the existing reader runs. No cache version bump, so nothing is wiped. - A hash mismatch or decode failure drops that region's file only (as a corrupt read does today: the region is re-requested). - Compression runs on the I/O thread (move encode into the `postIO` job), so the main-thread serialize cost does not grow. - Toggle: `ObjectCacheCompression` (on by default). Off = write IDENTITY with the same header. ### Drop-in plan Depends only on the codec + zstd package (#710 step 0-1), not on the segment store, so it can land any time. Measure region-entry read time on HDD / SATA SSD before and after (ticket numbers: 7.4 -> 2.4 MB, ~90 -> ~45 ms laptop HDD).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sky_line/Slipstream#713
No description provided.