···77- [X] BeginBuffer server (batch get_read_version requests)
88- [X] Use slots to spawn servers
99- [ ] Range clears
1010+ - [X] Support write conflict ranges in VersionMap (decreased bench_rw from 28k to 24k TPS)
1011 - [ ] Add clear_range() to Transaction
1112 - [ ] Slice range clear mutations in CommitBuffer
1213 - [ ] Apply range clears on Storage servers
+82-14
lib/version_map.ex
···77 VersionMap is backed by an `:ets` table.
88 """
991010+ import Hobbes.Utils
1111+1012 @opaque t :: :ets.table
11131214 @doc """
···2830 """
2931 @spec add_writes(VersionMap.t, non_neg_integer, [binary]) :: :ok
3032 def add_writes(table, version, keys) when is_integer(version) and is_list(keys) do
3131- Enum.each(keys, fn k when is_binary(k) ->
3232- true = :ets.insert(table, {k, version})
3333+ Enum.each(keys, fn
3434+ {_sk, _ek} -> :noop
3535+ k -> add_range(table, version, k, next_key(k))
3336 end)
3437 :ok
3538 end
36394040+ defp add_range(table, version, start_key, end_key) do
4141+ prev_key =
4242+ case :ets.prev_lookup(table, start_key) do
4343+ {_, [{sk, {ek, ver}}]} ->
4444+ cond do
4545+ ek <= start_key ->
4646+ # The previous range does not intersect with this range
4747+ :noop
4848+ ek <= end_key ->
4949+ # The previous range intersects with this range and must be trimmed to {sk, start_key}
5050+ :ets.insert(table, {sk, {start_key, ver}})
5151+ ek > end_key ->
5252+ # The previous range completely envelops this range and must be split into {sk, start_key} and {end_key, ek}
5353+ :ets.insert(table, {sk, {start_key, ver}})
5454+ :ets.insert(table, {end_key, {ek, ver}})
5555+ end
5656+5757+ sk
5858+5959+ :"$end_of_table" ->
6060+ # There is no previous range
6161+ nil
6262+ end
6363+6464+ case clear_between(table, end_key, prev_key) do
6565+ {sk, {ek, ver}} ->
6666+ case sk < end_key do
6767+ true ->
6868+ # The last range of the clear intersects with the end of this range and must be trimmed to {end_key, ek}
6969+ :ets.delete(table, sk)
7070+ :ets.insert(table, {end_key, {ek, ver}})
7171+7272+ false -> :noop
7373+ end
7474+7575+ nil -> :noop
7676+ end
7777+7878+ :ets.insert(table, {start_key, {end_key, version}})
7979+ end
8080+8181+ defp clear_between(table, end_key, prev_key) do
8282+ case :ets.next_lookup(table, prev_key) do
8383+ {_, [{sk, {ek, ver}}]} ->
8484+ case ek <= end_key do
8585+ true ->
8686+ :ets.delete(table, sk)
8787+ clear_between(table, end_key, sk)
8888+ false ->
8989+ {sk, {ek, ver}}
9090+ end
9191+9292+ :"$end_of_table" ->
9393+ nil
9494+ end
9595+ end
9696+3797 @doc """
3898 Checks if a key or key range has been written to after `version`.
3999···50110 def written_after?(table, version, key_or_range)
5111152112 def written_after?(table, version, key) when is_integer(version) and is_binary(key) do
5353- case :ets.lookup(table, key) do
5454- [{^key, ver}] -> ver > version
5555- [] -> false
113113+ case :ets.prev_lookup(table, next_key(key)) do
114114+ {_, [{_sk, {ek, ver}}]} -> ver > version and key < ek
115115+ :"$end_of_table" -> false
56116 end
57117 end
5811859119 @spec written_after?(VersionMap.t, non_neg_integer, {binary, binary}) :: boolean
60120 def written_after?(table, version, {start_key, end_key})
61121 when is_integer(version) and is_binary(start_key) and is_binary(end_key) do
6262- # TODO: inclusive/exclusive on both sides for reverse range reads
63122 # Check first key, then scan
64123 case written_after?(table, version, start_key) do
65124 true -> true
···6912870129 defp do_written_after_scan(table, version, prev_key, end_key) do
71130 case :ets.next_lookup(table, prev_key) do
7272- {_next_key, [{next_key, ver}]} when next_key < end_key ->
7373- case ver > version do
7474- true -> true
7575- false -> do_written_after_scan(table, version, next_key, end_key)
131131+ {_, [{sk, {_ek, ver}}]} ->
132132+ case sk < end_key do
133133+ true ->
134134+ # Range intersects, check for a conflict
135135+ case ver > version do
136136+ # Conflict found
137137+ true -> true
138138+ # Range does not conflict, keep scanning
139139+ false -> do_written_after_scan(table, version, sk, end_key)
140140+ end
141141+142142+ # Range does not intersect, scan complete
143143+ false -> false
76144 end
771457878- _ ->
7979- false
146146+ # Reached the end of the table without finding a conflict
147147+ :"$end_of_table" -> false
80148 end
81149 end
82150···92160 @spec clear_old(VersionMap.t, non_neg_integer) :: :ok
93161 def clear_old(table, up_to_version) when is_integer(up_to_version) and up_to_version >= 0 do
94162 :ets.select_delete(table, [{
9595- {:"$1", :"$2"},
9696- [{:"=<", :"$2", up_to_version}],
163163+ {:"$1", {:"$2", :"$3"}},
164164+ [{:"=<", :"$3", up_to_version}],
97165 [true],
98166 }])
99167 :ok