Skip to content

Commit

Permalink
Implement explicit snapshots
Browse files Browse the repository at this point in the history
See Level/community#118. TLDR:

```js
await db.put('example', 'before')
const snapshot = db.snapshot()
await db.put('example', 'after')
await db.get('example', { snapshot })) // Returns 'before'
await snapshot.close()
```

This was relatively easy to implement because it's merely exposing an
existing LevelDB feature. Which we were already using too (for example,
creating an iterator internally creates a snapshot; I now refer to that
as an "implicit" snapshot). The only challenge was garbage collection
and state management. For that I knew I could copy iterator logic but I
wanted to avoid its technical debt, so I first cleaned that up:

- `binding.iterator_close()` is now synchronous, because that's 2x
  faster than `napi_create_async_work`, let alone executing that async
  work. Measured with `performance.timerify(binding.iterator_close)`.
  Simplifies state.
- I changed the database reference (`Database#ref_`) from a weak to a
  strong reference, preventing GC until `db.close()`. I thought this
  would remove the need for strong references to iterators & snapshots
  seeing as the `AbstractLevel` class already has references in the
  `db[kResources]` set, but a new unit test in `iterator-gc-test.js`
  proved me wrong. So I kept that as-is (would like to revisit).
- Having a strong reference to the database removed the need for the
  `IncrementPriorityWork` function to increment the refcount of said
  reference. It now just increments a `std::atomic<int>` (for our own
  state outside of V8).
- Iterators no longer call `IncrementPriorityWork` which had 2
  purposes:
  1. Increase the db reference count. See above.
  2. Delay database close until iterators were closed. A leftover from
     before `abstract-level` which closes iterators & snapshots before
     calling `db._close()`.

I then moved common logic for references and teardown to a new struct
called `Resource`, inherited by `Iterator` and `ExplicitSnapshot`. The
latter is a new struct that wraps a LevelDB snapshot.

Keeping snapshots open during read operations like `db.get()` is
handled in `abstract-level` through `AbstractSnapshot#ref()`.

I think further cleanup is possible (and to move more state management
to JS) but I'll save that for a future PR.

This PR depends on Level/abstract-level#93 but
is otherwise complete.
  • Loading branch information
vweevers committed Dec 27, 2024
1 parent d142686 commit c2a6c3d
Show file tree
Hide file tree
Showing 5 changed files with 342 additions and 183 deletions.
Loading

0 comments on commit c2a6c3d

Please sign in to comment.