This document is the implementation plan and design for LevelDB Scopes. It serves to both document the current state and the future plans for implementing LevelDB Scopes.
See LevelDB Scopes general design doc here.
The only thing implemented so far is the lock manager interface & discrete implementation.
Scope
A scope encompasses a group of changes that can be reverted. It is basically synonymous with a transaction, and would be used for readwrite and versionchange transactions in LevelDB. The scope has a defined list of key ranges where the changes can occur. It operates by keeping an undo log, which is either discarded on commit, or replayed on revert.
Undo Log
Each scope saves an undo log, which has all the information needed to undo all changes performed in the scope.
Scope Number (scope#)
Each scope has an identifier unique to the backing store that is auto-incrementing. During recovery, it is set to the minimum unused scope (see more in the recovery section).
Sequence Number (seq#)
Every undo log entry has a unique sequence number within the scope. These should start at (using Fixed64) and decrement. The sequence number ‘0’ is reserved for the commit point
Commit Point
This signifies that a scope has been committed. It also means that a specific sequence number was written for a scope.
Key Range
Represents a range of LevelDB keys. Every operation has a key or a key range associated with it. Sometimes key ranges are expected to be re-used or modified again by subsequent operations, and sometimes key ranges are known to be never used again.
Lock
To prevent reading uncommitted data, IndexedDB ‘locks’ objects stores when there are (readwrite) transactions operating on them.
Purpose | Key | Final format | Value (protobufs) |
---|---|---|---|
Metadata | undo-metadata | prefix-0 | {version: 1} |
Scope Metadata | undo-scopes-<scope#> | prefix-1-<scope#> | key ranges if scope is active, or <empty> if committed. |
Undo log operations | undo-log-<scope#>-<seq#> | prefix-2-<scope#>-<seq#> | undo operation |
Commit point | undo-log-<scope#>-0 | prefix-2-<scope#>-0 | <empty> OR <ranges to delete> |
prefix
).All values will be protobufs
IndexedDB Sequence
IndexedDB Sequence
Cleanup & Revert Sequence
IndexedDB Sequence
Cleanup & Revert Sequence
The scopes feature needs a fairly generic lock manager. This lock manager needs two levels, because versionchange transactions need to lock a whole database, where other transactions will lock smaller ranges within the level one range.
Accepts a lock type (shared or exclusive), a level number, a key range, and a callback which is given a moveable-only lock object, which releases its lock on destruction. The levels are totally independent from the perspective of the lock manager. IF an application wants to use multiple levels, they should employ an acquisition order (like, always acquire locks in increasing level order) so they don't deadlock.
The lock manager basically holds ranges, and needs to know if a new range intersects any old ranges. A good data structure for this is an Interval Tree.
Put(key, value)
Delete(key)
DeleteRange(key_range)
Note - all cases where “old_value” is used requires reading the current value from the database.
Put(key, value)
Delete(key)
if an old value doesn't exist,Put(key, old_value)
if an old value does exist, orAdd(key, value)
Delete(key)
Put(key, old_value)
if the old_value exists, orDeleteRange(key_range)
Put(key, old_value)
for every entry in that key range. This requires iterating the database using the key_range to find all entries.If the values being created are in a key range that is initially empty (we trust the API caller here - there is no verification), and if the key range will never be reused if it is reverted, then the undo log can consist of a single:
DeleteRange(key_range)
Examples of this are creating new databases or indexes in a versionchange transaction. The scopes system can check to make sure it's range is empty before doing operations. This can either be done as a hint (per-range, per-scope), or always.
This is done by having commit ranges in the value of the commit point. A new scope is created, and committed, with the key ranges never-to-be-used-again will be the value of the commit point record.