Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Fix ref counting and cascading evictions in lazy store (#405)
Problem ====== 1. Ref counting in the lazy store is buggy resulting in incorrect and even negative ref counts. The main problem is that the refs of cached chunks (lazily loaded or put) in a Write that were already "reachable" (already had a positive ref count) are not counted. This is because `computeRefCountUpdates` assumes that the refs of all "reachable" chunks are already counted (i.e. new refs can only come from new heads), it does not have logic for handling the lazy discovery of refs via chunk lazy loading. 2. Current eviction logic in the lazy store kind of mixes the ideas of eviction and deletion. When a chunk is evicted, the ref counts of all of the chunks its ref's are decremented. If any go to zero they are evicted, and so on recursively. This can lead to very large cascading evictions. In particular if we are traversing a DAG that exceeds the cache limit, at the time the limit is exceeded the least recently accessed node is the root. Evicting the root can cascade and cause everything cached so far to be evicted. Solution ====== Cache refs separately from chunks. Clearly distinguish eviction vs deletion of chunks. - Eviction When evicting a chunk do not modify its ref count or delete its cached refs, only remove the chunk from the cache. - Deletion When deleting a chunk (because its ref count has gone to 0), delete its ref count entry, cached refs and the chunk itself. Add logic to `computeRefCountUpdates` for handling lazily discovered refs. Improve documentation and tests. Performance ========== Performance impact looks neutral. Perf comparisons made on my mac laptop Model Name: MacBook Pro Model Identifier: MacBookPro18,4 Chip: Apple M1 Max Total Number of Cores: 10 (8 performance and 2 efficiency) Memory: 64 GB Physical Drive: Device Name: APPLE SSD AP1024R Media Name: AppleAPFSMedia Medium Type: SSD Capacity: 994.66 GB (994,662,584,320 bytes) ***main*** [greg replicache-internal [main]$ npm run perf -- --format replicache > [email protected] perf > npm run build-perf && node perf/runner.js "--format" "replicache" > [email protected] build-perf > node tool/build.js --perf Running 24 benchmarks on Chromium... writeSubRead 1MB total, 64 subs total, 5 subs dirty, 16kb read per sub 50/75/90/95%=1.30/1.50/1.60/2.00 ms avg=1.31 ms (19 runs sampled) writeSubRead 4MB total, 128 subs total, 5 subs dirty, 16kb read per sub 50/75/90/95%=0.90/1.00/1.10/1.40 ms avg=1.02 ms (19 runs sampled) writeSubRead 16MB total, 128 subs total, 5 subs dirty, 16kb read per sub 50/75/90/95%=1.20/1.30/1.40/2.30 ms avg=1.33 ms (16 runs sampled) writeSubRead 64MB total, 128 subs total, 5 subs dirty, 16kb read per sub 50/75/90/95%=1.50/2.40/3.80/3.80 ms avg=2.14 ms (7 runs sampled) populate 1024x1000 (clean, indexes: 0) 50/75/90/95%=7.70/9.00/12.60/16.60 ms avg=9.03 ms (19 runs sampled) populate 1024x1000 (clean, indexes: 1) 50/75/90/95%=16.30/18.90/20.90/23.20 ms avg=18.31 ms (19 runs sampled) populate 1024x1000 (clean, indexes: 2) 50/75/90/95%=22.70/24.50/26.50/29.20 ms avg=23.78 ms (19 runs sampled) populate 1024x10000 (clean, indexes: 0) 50/75/90/95%=42.10/45.10/67.70/67.70 ms avg=53.04 ms (10 runs sampled) populate 1024x10000 (clean, indexes: 1) 50/75/90/95%=94.30/96.70/114.00/114.00 ms avg=122.79 ms (7 runs sampled) populate 1024x10000 (clean, indexes: 2) 50/75/90/95%=141.60/167.60/172.70/172.70 ms avg=188.91 ms (7 runs sampled) scan 1024x1000 50/75/90/95%=0.90/1.20/1.40/1.80 ms avg=0.96 ms (19 runs sampled) scan 1024x10000 50/75/90/95%=6.30/6.30/8.20/9.20 ms avg=7.13 ms (19 runs sampled) create index with definition 1024x5000 50/75/90/95%=109.20/112.80/119.80/119.80 ms avg=138.80 ms (7 runs sampled) create index 1024x5000 50/75/90/95%=23.10/24.20/27.80/30.10 ms avg=26.30 ms (19 runs sampled) startup read 1024x100 from 1024x100000 stored 50/75/90/95%=56.00/71.70/89.00/89.00 ms avg=72.20 ms (7 runs sampled) startup scan 1024x100 from 1024x100000 stored 50/75/90/95%=16.50/27.80/59.70/60.70 ms avg=22.79 ms (19 runs sampled) persist 1024x1000 (indexes: 0) 50/75/90/95%=138.60/163.70/441.20/441.20 ms avg=181.53 ms (7 runs sampled) persist 1024x1000 (indexes: 1) 50/75/90/95%=176.50/177.60/177.60/177.60 ms avg=210.80 ms (7 runs sampled) persist 1024x1000 (indexes: 2) 50/75/90/95%=220.90/223.50/238.30/238.30 ms avg=272.47 ms (7 runs sampled) persist 1024x10000 (indexes: 0) 50/75/90/95%=553.30/561.70/646.00/646.00 ms avg=709.26 ms (7 runs sampled) persist 1024x10000 (indexes: 1) 50/75/90/95%=2765.30/2819.80/2894.20/2894.20 ms avg=3496.86 ms (7 runs sampled) persist 1024x10000 (indexes: 2) 50/75/90/95%=4254.80/4311.80/4349.60/4349.60 ms avg=5438.34 ms (7 runs sampled) populate tmcw 50/75/90/95%=57.30/63.20/82.20/82.20 ms avg=75.66 ms (7 runs sampled) persist tmcw 50/75/90/95%=318.00/318.80/328.30/328.30 ms avg=402.17 ms (7 runs sampled) Done! ***this change*** greg replicache-internal [grgbkr/lazy-store-gc-evicition-fixes]$ npm run perf -- --format replicache > [email protected] perf > npm run build-perf && node perf/runner.js "--format" "replicache" > [email protected] build-perf > node tool/build.js --perf Running 24 benchmarks on Chromium... writeSubRead 1MB total, 64 subs total, 5 subs dirty, 16kb read per sub 50/75/90/95%=1.00/1.10/1.20/1.70 ms avg=1.03 ms (19 runs sampled) writeSubRead 4MB total, 128 subs total, 5 subs dirty, 16kb read per sub 50/75/90/95%=1.00/1.10/1.20/1.30 ms avg=1.09 ms (19 runs sampled) writeSubRead 16MB total, 128 subs total, 5 subs dirty, 16kb read per sub 50/75/90/95%=1.10/1.30/1.60/2.30 ms avg=1.26 ms (16 runs sampled) writeSubRead 64MB total, 128 subs total, 5 subs dirty, 16kb read per sub 50/75/90/95%=1.30/1.60/2.40/2.40 ms avg=1.74 ms (7 runs sampled) populate 1024x1000 (clean, indexes: 0) 50/75/90/95%=7.50/9.10/11.40/15.40 ms avg=9.05 ms (19 runs sampled) populate 1024x1000 (clean, indexes: 1) 50/75/90/95%=14.20/16.80/19.20/21.20 ms avg=16.41 ms (19 runs sampled) populate 1024x1000 (clean, indexes: 2) 50/75/90/95%=18.80/21.10/25.20/29.40 ms avg=21.88 ms (19 runs sampled) populate 1024x10000 (clean, indexes: 0) 50/75/90/95%=43.20/54.60/70.60/70.60 ms avg=58.29 ms (9 runs sampled) populate 1024x10000 (clean, indexes: 1) 50/75/90/95%=95.90/97.70/116.40/116.40 ms avg=124.99 ms (7 runs sampled) populate 1024x10000 (clean, indexes: 2) 50/75/90/95%=141.80/167.90/173.00/173.00 ms avg=191.01 ms (7 runs sampled) scan 1024x1000 50/75/90/95%=0.90/1.00/1.50/1.60 ms avg=0.92 ms (19 runs sampled) scan 1024x10000 50/75/90/95%=6.20/6.30/8.20/8.90 ms avg=7.07 ms (19 runs sampled) create index with definition 1024x5000 50/75/90/95%=106.10/111.20/113.60/113.60 ms avg=135.33 ms (7 runs sampled) create index 1024x5000 50/75/90/95%=24.00/24.80/27.10/31.70 ms avg=26.95 ms (19 runs sampled) startup read 1024x100 from 1024x100000 stored 50/75/90/95%=55.40/70.70/71.70/71.70 ms avg=63.96 ms (8 runs sampled) startup scan 1024x100 from 1024x100000 stored 50/75/90/95%=14.30/29.60/59.40/60.90 ms avg=20.55 ms (19 runs sampled) persist 1024x1000 (indexes: 0) 50/75/90/95%=184.80/282.80/329.70/329.70 ms avg=237.26 ms (7 runs sampled) persist 1024x1000 (indexes: 1) 50/75/90/95%=171.60/172.80/176.00/176.00 ms avg=197.54 ms (7 runs sampled) persist 1024x1000 (indexes: 2) 50/75/90/95%=224.80/227.20/231.60/231.60 ms avg=275.53 ms (7 runs sampled) persist 1024x10000 (indexes: 0) 50/75/90/95%=539.20/545.10/558.50/558.50 ms avg=678.00 ms (7 runs sampled) persist 1024x10000 (indexes: 1) 50/75/90/95%=2312.30/2403.00/2428.90/2428.90 ms avg=2843.89 ms (7 runs sampled) persist 1024x10000 (indexes: 2) 50/75/90/95%=3606.60/3656.50/3665.10/3665.10 ms avg=4601.70 ms (7 runs sampled) populate tmcw 50/75/90/95%=50.60/53.90/80.60/80.60 ms avg=64.89 ms (8 runs sampled) persist tmcw 50/75/90/95%=313.10/314.70/339.90/339.90 ms avg=396.77 ms (7 runs sampled) Done! (cherry picked from commit 4378df2)
- Loading branch information