Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IncrementalIDBAdapter - Fix database corruption & Performance improvements #869

Merged
merged 33 commits into from
Dec 10, 2020

Conversation

radex
Copy link
Contributor

@radex radex commented Dec 2, 2020

IncrementalIDBAdapter has had a flaw. Saving only changed chunks (instead of dumping the whole in-memory database into IDB) can easily lead to database corruption if there's more than one writer (multiple tabs of the same web app).

I did consider this possibility when developing #808, but thinking about how this is meant to be used with an app that synchronizes (sync should make the in-memory state of multiple tabs exactly the same, avoiding inconsistency, and therefore corruption), I naively concluded that this won't be a problem. In hindsight, that's obviously wrong, and caused all sorts of issues for Nozbe Teams users. I was quite easily able to find multiple reproducible paths for corrupting the loki database. Most corruptions are subtle (e.g. binary index being slightly out of order) and don't manifest themselves so easily, but sometimes you can get a broken app unable to launch, as there is an order of events where two writers will save a record with the same id in multiple chunks (leading to a UniqueIndex throwing an error). #856 probably made this worse, as collection metadata save can be skipped.

The fix is quite straightforward and I could not come up with a way to break it:

       // To fix that, we save all
       // metadata chunks (loki + collections) with a unique ID on each save and remember it. Before
       // the subsequent save, we read loki from IDB to check if its version ID changed. If not, we're
       // guaranteed that persisted DB is consistent with our diff. Otherwise, we fall back to the slow
       // path and overwrite *all* database chunks with our version. Both reading and writing must
       // happen in the same IDB transaction for this to work.
       // TODO: We can optimize the slow path by fetching collection metadata chunks and comparing their
       // version IDs with those last seen by us. Since any change in collection data requires a metadata
       // chunk save, we're guaranteed that if the IDs match, we don't need to overwrite chukns of this collection

So the worst case scenario gets roughly the same performance as LokiIndexedAdapter, but most cases (one tab/no contention) have the same performance as before, though with slightly higher latency (we must fetch the loki chunk at each save, but it adds almost nothing to the time where the main thread is blocked, just a delay waiting for the concurrent IndexedDB process to give us an answer).

  • I also found and fixed another bug I introduced where collection metadata might not be properly persisted to IDB in an edge case.
  • There's an improvement in peak memory pressure on save - prepared chunks are now put into IDB immediately, without an intermediate array (gets big on large databases)
  • Improved error handling in IncrementalIDBAdapter
  • tests

@radex radex mentioned this pull request Dec 2, 2020
@radex radex marked this pull request as ready for review December 7, 2020 14:50
@radex
Copy link
Contributor Author

radex commented Dec 10, 2020

@techfort This PR should be ready to review if you've got time :)

@techfort techfort merged commit 71fd8fa into techfort:master Dec 10, 2020
@radex radex deleted the performance2 branch December 10, 2020 11:17
@thomaschampagne
Copy link

@radex Thanks for this work.
@techfort Do you plan to perform a npm release with this improvement?

(I use this adapter in my project: https://champagnethomas.medium.com/elevate-desktop-app-2021-update-9155fd74bb85)

Thanks !

@radex
Copy link
Contributor Author

radex commented Apr 19, 2021

@thomaschampagne Nice, cool to see that my LokiJS improvements aren't used only by WatermelonDB :) You can use @nozbe/lokijs NPM prereleases or make your own fork

@thomaschampagne
Copy link

Oh nice !!! I put that in my package.json right now !

@techfort
Copy link
Owner

techfort commented Apr 20, 2021 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants