Reduce the memory usage of the write-ahead log#355
Conversation
f2ffb84 to
64b3682
Compare
| | Tuple3 of 'a * 'a * 'a | ||
| | Tuple4 of 'a * 'a * 'a * 'a | ||
| | Tuple5 of 'a * 'a * 'a * 'a * 'a | ||
| | Tuple6 of 'a * 'a * 'a * 'a * 'a * 'a |
There was a problem hiding this comment.
maybe this is obvious, but why up to 6?
There was a problem hiding this comment.
Not obvious at all; I just got bored after 6 😉 In practice, sizes above about 7 don't really happen with the hash functions we're using, so it's not worth going any further. If we used Obj rather than a type-safe implementation, we could push the threshold much higher & also improve the speed.
As it happens, @pascutto has a WIP patch to use open-addressing in the hashtable, which would avoid the need for small_list.ml entirely. For now, this is probably good enough.
There was a problem hiding this comment.
The first experiments showed that open addressing using linear probing is better for allocations, but worse for both memory and performance, so we should probably go with the short list here :)
icristescu
left a comment
There was a problem hiding this comment.
A lot of nice optimizations, thanks!
If we commit to needing to `read` from disk when finding in the log file (and when resizing the hashtable), we can keep only the offsets of each entry in memory, reducing the memory usage of Index significantly.
Previously, the hash-set bucket used to store binding offsets was determined by the bottom bits of the key's hash. By switching to use the _top_ bits instead, the hashset keeps entries roughly in order (with only the entries within each bucket being relatively out-of-order). This means that we don't need to load all the bindings into an array in order to sort them for merging, reducing the peak memory usage of Index.
Now that the merge function re-uses the in-memory log for sorting values, we only need to know if the index is empty before we start.
Co-authored-by: Clément Pascutto <clement@pascutto.fr>
1b60936 to
3a3bfd8
Compare
|
Thanks all for the reviews. Merging now. |
mirage#355 introduced a small optimisation to re-use a "local" scratch buffer when decoding values from the log file. This is actually unsafe: during the merge, the asynchronous merge thread and the main writer thread can attempt concurrent reads from the log file, causing contention over the scratch buffer. This can be observed by inserting `Thread.yield` calls inside the `Value.decode` implementation and then stress-testing the interface (e.g. by running the replay benchmarks). This commit adds a lock over the scratch buffer, preventing unsafe concurrent access.
mirage#355 introduced a small optimisation to re-use a "local" scratch buffer when decoding values from the log file. This is actually unsafe: during the merge, the asynchronous merge thread and the main writer thread can attempt concurrent reads from the log file, causing contention over the scratch buffer. This can be observed by inserting `Thread.yield` calls inside the `Value.decode` implementation and then stress-testing the interface (e.g. by running the replay benchmarks). This commit adds a lock over the scratch buffer, preventing unsafe concurrent access.
mirage#355 introduced a small optimisation to re-use a "local" scratch buffer when decoding values from the log file. This is actually unsafe: during the merge, the asynchronous merge thread and the main writer thread can attempt concurrent reads from the log file, causing contention over the scratch buffer. This can be observed by inserting `Thread.yield` calls inside the `Value.decode` implementation and then stress-testing the interface (e.g. by running the replay benchmarks). This commit ensures that each call to a `Log_file` function gets its own scratch buffer, ensuring safe concurrent access without introducing potential contention issues.
mirage#355 introduced a small optimisation to re-use a "local" scratch buffer when decoding values from the log file. This is actually unsafe: during the merge, the asynchronous merge thread and the main writer thread can attempt concurrent reads from the log file, causing contention over the scratch buffer. This can be observed by inserting `Thread.yield` calls inside the `Value.decode` implementation and then stress-testing the interface (e.g. by running the replay benchmarks). This commit ensures that each call to a `Log_file` function gets its own scratch buffer, ensuring safe concurrent access without introducing potential contention issues.
CHANGES: ## Fixed - Fix stats recording in `Raw.unsafe_write` (mirage/index#351) ## Changed - Changed the implementation of the write-ahead log to significantly reduce its memory usage (at the cost of some additional disk IO). (mirage/index#355)
This implements "Suggestion 2" as described in #354, without adding an LRU.
Fix #354.