rocicorp / replicache

Realtime Sync for Any Backend Stack
https://doc.replicache.dev
1.05k stars 37 forks source link

feat: Simplified Dueling Dags - Implement a dag.LazyStore for memdag #771

Closed grgbkr closed 2 years ago

grgbkr commented 2 years ago

Implements a DAG Store which lazily loads values from a source store and then caches them in an LRU cache. The memory cache for chunks from the source store size is limited to sourceCacheSizeLimit bytes, and values are evicted in an LRU fashion. The purpose of this store is to avoid holding the entire client view (i.e. the source store's content) in each client tab's JavaScript heap.

This store's heads are independent from the heads of source store, and are only stored in memory.

Chunks which are put with a temp hash (see isTempHash) are assumed to not be persisted to the source store and thus are cached separately from the source store chunks. These temp chunks cannot be evicted, and their sizes are not counted towards the source chunk cache size. A temp chunk will be deleted if it is no longer reachable from one of this store's heads.

Writes only manipulate the in memory state of this store and do not alter the source store. Thus values must be written to the source store through a separate process (see persist implemented in 7769f09).

Intended use:

  1. source store is the 'perdag', a slower persistent store (i.e. dag.StoreImpl using a kv.IDBStore)
  2. this store's 'main' head is initialized to the hash of a chunk containing a snapshot commit in the 'perdag'
  3. reads from this store lazily read chunks from the source store and cache them
  4. writes are initially made to this store using temp hashes (i.e. temp chunks)
  5. writes are asynchronously persisted to the perdag through a separate process.
    See persist implemented in 7769f09. This process gathers all temp chunks from this store, computes real hashes for them and then writes them to the perdag. It then replaces in this dag all the temp chunks written to the source with chunks with permanent hashes and updates heads to reference these permanent hashes instead of the temp hashes. This results in the temp chunks being deleted from this store and the chunks with permanent hashes being placed in this store's LRU cache of source chunks.

Performance On our existing performance benchmarks outperforms the existing mem dag store ( dag.StoreImpl on top of kv.MemStore). The current benchmarks really only test performance of the temp hashes cache though, since they don't use persist at all.
I believe this outperforms the existing mem dag store because the temp hashes cache is just a straightforward Map<Hash, Chunk>, and is thus a bit simpler than dag.StoreImpl on top of kv.MemStore which uses 3 keys per chunk. A follow up is to add some benchmarks that exercise persists and lazy loading.

[greg replicache [grgbkr/ssd-lazy-dag-impl]$ npm run perf -- --format replicache

> replicache@8.0.0 perf
> node perf/runner.js "--format" "replicache"

Running 16 benchmarks on Chromium...
[LazyDag] writeSubRead 1MB total, 64 subs total, 5 subs dirty, 16kb read per sub 50/75/90/95%=0.70/0.80/0.90/1.40 ms avg=0.73 ms (19 runs sampled)
[LazyDag] writeSubRead 4MB total, 128 subs total, 5 subs dirty, 16kb read per sub 50/75/90/95%=1.00/1.00/1.90/3.90 ms avg=1.25 ms (17 runs sampled)
[LazyDag] writeSubRead 16MB total, 128 subs total, 5 subs dirty, 16kb read per sub 50/75/90/95%=1.40/2.20/2.50/2.50 ms avg=1.97 ms (7 runs sampled)
[LazyDag] populate 1024x1000 (clean, indexes: 0) 50/75/90/95%=16.40/20.60/28.70/39.00 ms avg=20.30 ms (19 runs sampled)
[LazyDag] populate 1024x1000 (clean, indexes: 1) 50/75/90/95%=38.30/41.50/45.00/58.90 ms avg=43.28 ms (12 runs sampled)
[LazyDag] populate 1024x1000 (clean, indexes: 2) 50/75/90/95%=47.30/48.50/71.30/71.30 ms avg=58.49 ms (9 runs sampled)
[LazyDag] scan 1024x1000 50/75/90/95%=1.20/1.50/2.50/2.70 ms avg=1.49 ms (19 runs sampled)
[LazyDag] create index 1024x5000 50/75/90/95%=105.80/124.90/130.50/130.50 ms avg=139.61 ms (7 runs sampled)
writeSubRead 1MB total, 64 subs total, 5 subs dirty, 16kb read per sub 50/75/90/95%=0.70/0.90/1.00/1.60 ms avg=0.85 ms (19 runs sampled)
writeSubRead 4MB total, 128 subs total, 5 subs dirty, 16kb read per sub 50/75/90/95%=1.40/1.60/2.50/4.70 ms avg=1.79 ms (16 runs sampled)
writeSubRead 16MB total, 128 subs total, 5 subs dirty, 16kb read per sub 50/75/90/95%=2.20/2.30/2.40/2.40 ms avg=2.57 ms (7 runs sampled)
populate 1024x1000 (clean, indexes: 0) 50/75/90/95%=18.60/20.40/22.10/39.30 ms avg=21.08 ms (19 runs sampled)
populate 1024x1000 (clean, indexes: 1) 50/75/90/95%=38.00/45.00/50.20/59.70 ms avg=46.58 ms (11 runs sampled)
populate 1024x1000 (clean, indexes: 2) 50/75/90/95%=50.60/66.30/75.00/75.00 ms avg=63.77 ms (8 runs sampled)
scan 1024x1000 50/75/90/95%=1.20/1.60/2.30/3.10 ms avg=1.53 ms (19 runs sampled)
create index 1024x5000 50/75/90/95%=104.30/115.70/117.30/117.30 ms avg=137.03 ms (7 runs sampled)
Done!

Part of #671

vercel[bot] commented 2 years ago

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: https://vercel.com/rocicorp/replicache/CADUarKvPcYHS9hMUWGmjey3LLTz
✅ Preview: https://replicache-git-grgbkr-ssd-lazy-dag-impl-rocicorp.vercel.app