Goblin Store versus RocksDB on an 8 GiB object-cache host
Goblin Store populated a 62.1 GiB object corpus 31.3% faster than RocksDB and replayed the 266.1 GiB request stream 72.0% faster through the local C++ APIs. When the recorded chunk-ready times are passed through an idealized 0.937 Gbit/s network—the instance’s sustainable baseline— Goblin delivers the complete schedule at 46.7% higher throughput: 98.28 MiB/s versus 66.99 MiB/s.
This is a direct-I/O, single-request-at-a-time storage-engine comparison on an 8 GiB AWS instance. It is not a network-server benchmark. The network result is a deterministic simulation intended to answer a narrower question: if both engines sat behind an otherwise perfect baseline-rate server, could Goblin’s ordered, incremental delivery hide storage time behind transmission?
Results
Both engines populated the same 373,165 objects and then retrieved the same 6,141,630 requests in the same order. Population covered 66,693,472,653 bytes (62.113 GiB); the read schedule returned 285,757,364,203 bytes (266.132 GiB).
| Engine | Fill time | Fill rate | Last byte to local C++ process | Local completion rate | Last byte over simulated 0.937 Gbit/s link | Simulated network rate |
|---|---|---|---|---|---|---|
| Goblin Store | 675.304 s | 94.19 MiB/s | 946.484 s | 287.93 MiB/s | 2,772.919 s | 98.28 MiB/s |
| RocksDB 9.11.2 | 886.885 s | 71.72 MiB/s | 1,628.405 s | 167.35 MiB/s | 4,068.172 s | 66.99 MiB/s |
| Goblin improvement | 23.9% less time | 31.3% higher | 41.9% less time | 72.0% higher | 31.8% less time | 46.7% higher |
The aggregate completion rate is computed as total returned bytes divided by the sum of each serial request’s measured time. It is not the mean of per-request bandwidths.
Goblin’s resident heads also change when the first useful bytes become visible:
| Engine | Median TTFB | Median complete retrieval | Mean TTFB | Mean complete retrieval |
|---|---|---|---|---|
| Goblin Store | 4.026 µs | 4.530 µs | 7.345 µs | 154.110 µs |
| RocksDB | 117.229 µs | 117.229 µs | 265.142 µs | 265.142 µs |
RocksDB’s DB::Get() materializes the whole value before returning, so its observable time to first
byte equals its completion time. Goblin calls back with the resident head and then with ordered
tail chunks as they become available. For the disk-backed portion of the workload, Goblin and
RocksDB had nearly identical mean complete-value latency—320.908 µs and 320.988 µs
respectively—but Goblin exposed the first bytes in 12.916 µs on average. That is the intended
architectural difference: byte zero can be useful while byte n is still in flight.
Machine and storage layout
The benchmark ran on an AWS m8id.large: 2 vCPUs, 8 GiB of RAM, an Intel Xeon 6 (Granite Rapids)
processor, and one 118 GB local NVMe SSD. AWS lists the device at up to 33,542 read IOPS and 16,771
write IOPS. The source corpus and schedules lived on an attached EBS volume; each disposable cache
database lived on the local instance-store NVMe. The host had deliberately little RAM relative to
the 62.1 GiB corpus, so neither implementation could turn the complete data set into an accidental
page-cache benchmark. See the
AWS general-purpose instance specifications.
Goblin received 1,120 MiB for packed small objects and 4,100 MiB for fixed resident heads. It used 16 KiB heads, 256 KiB tail-read chunks, four reads in flight, four population workers, and enough cached file descriptors to retain every disk-backed object. Persistent heads were enabled in a directory separate from the SSD-tail directory.
RocksDB received the same 5,220 MiB application-memory ceiling through a shared
WriteBufferManager. This is a match between the engines’ explicit cache/write-buffer controls,
not a claim that their total process RSS or metadata overhead is identical. RocksDB used:
rocksdb::BlockBasedTableOptions table_options;
table_options.no_block_cache = true;
rocksdb::Options options;
options.use_direct_reads = true;
options.use_direct_io_for_flush_and_compaction = true;
Compression, mmap reads, the WAL, and BlobDB were disabled. Population used 512 MiB memtables, up
to eight write buffers, two background jobs, normal leveled compaction, one final flush, and a
barrier for background work. Goblin used its ordinary direct-I/O SSD path and persisted the hashed
key plus resident head with normal open/write/close head-file writes. Both engines therefore
finished population in a restartable state before reads began.
The source cache was advised away before population and again after each source read. Reads were strictly serial. The timer stopped before each result row was written, and the CSV stream was flushed between requests, so a crash could not discard completed measurements and result logging was outside the timed interval.
The RocksDB measurements came from an earlier completed pass on the same instance, corpus, schedule, storage layout, and engine configuration. Goblin was rerun after its bounded population pipeline and persistent-head write path were optimized. This was not an interleaved A/B run, so small changes in host or device state remain a caveat; the size of the observed read and simulated delivery differences is much larger than that caveat is likely to explain.
The 0.937 Gbit/s idealized network
The raw CSV records every Goblin callback as
size_in_bytes:ready_time_ns, in callback order. For each request the simulator computes:
chunk_start = max(chunk_ready, previous_chunk_end)
chunk_end = chunk_start + ceil(chunk_size * 8 / 937,000,000)
The final chunk end is the request’s simulated last-byte arrival time. Requests remain serial, just
as they were in the storage benchmark. RocksDB contributes one chunk at DB::Get() completion
because it exposes no earlier bytes.
At 0.937 Gbit/s the payload itself requires 2,439.8 seconds of wire time. Goblin overlaps 613.3 seconds, or 25.1%, of that transmission with storage:
Goblin: 946.484 s storage + 2,439.768 s wire - 613.333 s overlap = 2,772.919 s
RocksDB: 1,628.405 s storage + 2,439.767 s wire - 0.000 s overlap = 4,068.172 s
The
AWS network specification table for m8id.large
lists 0.937 Gbit/s baseline and 12.5 Gbit/s burst bandwidth. This study uses the baseline, not the
additional credit-based burst capacity that may be temporarily available. AWS explains that
instances of this size earn network I/O credits below baseline, spend them while bursting, and
return to baseline after the credits are exhausted. A cache is expected to remain under constant
use, so the sustainable baseline is the appropriate long-running model. See
Amazon EC2 instance network bandwidth.
The model has no TCP/IP headers, packetization, syscall or kernel-stack cost, congestion, retransmission, TLS, or receiver delay. Its value is comparative: it applies the same perfect link to both recorded storage traces and preserves the storage/wire overlap made possible by Goblin’s chunk callback.
The simulator is
bench/simulate_chunk_network.py;
the benchmark driver and analysis tools are in
bench/.
Relation to Netflix’s RAM-to-SSD cache work
Netflix’s 2018 article “Evolution of Application Data Caching: From RAM to SSD” describes the same economic pressure behind this experiment: keeping large caches entirely in RAM is expensive, while NVMe can meet useful cache latency targets at much lower cost. Their EVCache Moneta design stored the complete data set in RocksDB on SSD and active data in Memcached. Netflix reported that this reduced some cluster footprints substantially, but overwrite compaction caused throughput and latency problems for larger online workloads. They ultimately favored memcached extstore, which retained metadata in RAM and put values on flash, avoided duplicate-record capacity pressure, and made many clusters network-bound rather than storage-bound.
This benchmark is related, but it does not reproduce Netflix’s production experiment. We do not model EVCache replication, Memcached’s hot-object tier, their 1 KiB/200K-IOPS test, cluster operations, or a production SLA. Instead, it isolates the local object-store decision on an actual 8 GiB host: ordinary RocksDB with direct I/O and no block cache versus Goblin’s application-owned RAM heads and direct-I/O tails, under matched explicit memory budgets and a trace-derived large-object workload.
The result supports the architectural direction Netflix described. A general LSM tree can be an excellent database, but its compaction and complete-value API are not free when the job is a replaceable object cache. Goblin’s layout is closer in spirit to extstore—small resident routing state plus flash-backed bytes—but retains an initial value prefix, not merely metadata, in RAM. That prefix is what makes ordered storage/network overlap possible. The simulation suggests that the benefit survives wrapping the local engine in a network server: Goblin’s 287.9 MiB/s local completion rate becomes 98.28 MiB/s on the idealized baseline link, while RocksDB’s 167.4 MiB/s becomes 66.99 MiB/s.
This is evidence for Goblin as a blob-cache engine, not a universal RocksDB replacement. A future networked rerun should use an actual peer, hold the instance above its baseline long enough to observe credit behavior, and measure server CPU, TTFB, and last-byte arrival directly.
Corpus provenance
The corpus was derived from the Encrypted Web Traffic Dataset: Event Logs and Packet Traces, published by Science Data Bank under CC BY 4.0. The event log’s hostnames and file sizes were mapped to opaque numeric keys and incompressible random payloads; the benchmark preserves the event-log access order. The generated corpus contains 373,165 unique objects with a mean size of 174.5 KiB, a 26.3 KiB median, a 2.495 MiB p99, and a 150.1 MiB maximum.
