Concurrent LSM store #20
Labels
No Label
Kind/Bug
Kind/Enhancement
Kind/Feature
Project/Lander
Project/Landerctl
idea
invalid
question
wontfix
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Chewing_Bever/lander#20
Loading…
Reference in New Issue
There is no content yet.
Delete Branch "%!s(<nil>)"
Deleting a branch is permanent. Although the deleted branch may exist for a short time before cleaning up, in most cases it CANNOT be undone. Continue?
There's currently no protection against two parallel insertions modifying the trie structure at the same place at the same time, e.g. two threads trying to add a new child node to the same node at the same time. This bug is currently not an issue, as the application is single-threaded, but I'd like to fix this for when I do want to add multithreading.
My only real idea for solving this issue right now would be adding a mutex to every single node in the trie, but this sounds very inefficient.
This issue boils down to a bigger issue I've been avoiding: updating the trie's internal structure while not requiring readers to lock the trie structure itself.
This article has given me some idea on how to accomplish this.
Instead of only keeping track of a single trie, we keep track of two. Readers can use the read map without locks, as it's immutable. Writers use the other map. Writing is serialized using a simple mutex. A writer first updates the writer trie after locking the mutex (note that this only applies to the trie's internal structure, and not the entries, as these are already locked themselves. The tries can simply share the entries). After updating the trie, the tries are swapped atomically. At this point, readers will start using the new trie.
Important here is that writers can only start writing to the write trie after all of its readers have stopped using it, to ensure we don't write to a trie that's still being read. After a write has been completed, the tries can be swapped immediately.
In order to track how many readers are currently reading the old trie, we could introduce some sort of reference-counting pointer, perhaps with a condition variable that is sent out if the value drops to 0.
Steps for writing:
Steps for reading:
The nice thing about this design is that it favors read speeds, which is ideal for Lander.
Possible race condition when inserting new entriesto Concurrent LSM storeA different approach is called "crab latching".
Each node have a rwlock. When reading an element from the trie, we traverse down the trie by read locking both the parent node and the child node. After having acquired the child node, we can release the parent node. We repeat this process until we reach our entry, which we already lock. Once we have the entry pointer, we can release the final child pointer.
For writing, we write-lock each node on the path to the entry, only releasing the locks if we know for certain the nodes on the path will not need to be updated by our operation. For our trie, this would be always, as the trie doesn't mutate its internal structure, meaning we could simply acquire a write lock for a single node at a time, releasing the previous one once the child node has been locked.