Skip to content

Reduce the memory usage of the write-ahead log#355

Merged
craigfe merged 14 commits intomirage:masterfrom
craigfe:log-only-offsets-in-memory
Oct 7, 2021
Merged

Reduce the memory usage of the write-ahead log#355
craigfe merged 14 commits intomirage:masterfrom
craigfe:log-only-offsets-in-memory

Conversation

@craigfe
Copy link
Member

@craigfe craigfe commented Oct 4, 2021

This implements "Suggestion 2" as described in #354, without adding an LRU.

Fix #354.

@craigfe craigfe requested a review from pascutto October 4, 2021 10:13
@craigfe craigfe force-pushed the log-only-offsets-in-memory branch from f2ffb84 to 64b3682 Compare October 4, 2021 12:23
| 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this is obvious, but why up to 6?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 :)

Copy link
Contributor

@icristescu icristescu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of nice optimizations, thanks!

Copy link

@Ngoguey42 Ngoguey42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

craigfe and others added 14 commits October 6, 2021 21:47
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>
@craigfe craigfe force-pushed the log-only-offsets-in-memory branch from 1b60936 to 3a3bfd8 Compare October 6, 2021 20:47
@craigfe
Copy link
Member Author

craigfe commented Oct 7, 2021

Thanks all for the reviews. Merging now.

@craigfe craigfe merged commit fe5e962 into mirage:master Oct 7, 2021
craigfe added a commit to craigfe/index that referenced this pull request Oct 10, 2021
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.
craigfe added a commit to craigfe/index that referenced this pull request Oct 10, 2021
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.
craigfe added a commit to craigfe/index that referenced this pull request Oct 10, 2021
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.
craigfe added a commit to craigfe/index that referenced this pull request Oct 10, 2021
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.
icristescu added a commit to icristescu/opam-repository that referenced this pull request Oct 15, 2021
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)
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.

Reduce the memory usage of the write-ahead log

4 participants