···3737 @spec scan(%HybridKV{}, non_neg_integer, binary, binary, keyword) :: RangeResult.t
3838 def scan(%HybridKV{} = kv, version, start_key, end_key, opts \\ []) do
3939 limit = Keyword.get(opts, :limit, :infinity)
4040+ reverse = Keyword.get(opts, :reverse, false)
40414142 read_limit = case limit do
4243 :infinity -> :infinity
4344 limit -> limit + 1
4445 end
45464646- pairs = do_scan_merge(kv, version, start_key, end_key, Keyword.put(opts, :limit, read_limit), 0)
4747+ {pairs, count} =
4848+ case reverse do
4949+ false -> do_scan(:forward, kv, version, start_key, end_key, read_limit, 0)
5050+ true -> do_scan(:backward, kv, version, start_key, end_key, read_limit, 0)
5151+ end
47524853 # We over-read by 1 and then use the extra read
4954 # to check if there are more
5050- # TODO: we could compute count more efficiently in do_scan_merge/do_merge
5151- count = length(pairs)
5255 cond do
5356 count < read_limit ->
5454- %RangeResult{pairs: pairs, more: false}
5757+ %RangeResult{pairs: pairs, count: count, more: false}
5558 count == read_limit ->
5656- %RangeResult{pairs: Enum.take(pairs, limit), more: true}
5959+ %RangeResult{pairs: Enum.take(pairs, limit), count: limit, more: true}
5760 end
5861 end
59626063 # This exists purely as a sanity check and should be unreachable except for bugs
6161- defp do_scan_merge(_kv, _version, _start_key, _end_key, _opts, 1000), do: raise "Scan caught in loop!"
6464+ defp do_scan(_direction, _kv, _version, _start_key, _end_key, _limit, 1000), do: raise "Scan caught in loop!"
62656366 # Abandon hope all ye who enter here
6464- defp do_scan_merge(%HybridKV{} = kv, version, start_key, end_key, opts, scan_count) do
6565- limit = Keyword.get(opts, :limit, :infinity)
6666- reverse = Keyword.get(opts, :reverse, false)
6767- storage_result = FlatKV.scan(kv.storage_kv, start_key, end_key, opts)
6767+ defp do_scan(:forward, %HybridKV{} = kv, version, start_key, end_key, limit, scan_count) do
6868+ storage_result = FlatKV.scan(kv.storage_kv, start_key, end_key, limit: limit)
6969+ {_start, storage_end_key} = storage_result.range
7070+7171+ # Read mem only up to the end of the range scanned by storage
7272+ mem_result = MemKV.scan(kv.mem_kv, version, start_key, storage_end_key, limit: limit)
7373+ {_start, mem_end_key} = mem_result.range
7474+7575+ # Both KVs were scanned up to this key
7676+ # Anything past this key was only scanned by storage and must be discarded
7777+ scanned_end_key = min(storage_end_key, mem_end_key)
7878+7979+ merged_pairs =
8080+ merge(storage_result.pairs, mem_result.pairs, false)
8181+ # TODO: more efficient to do this in merge/3
8282+ |> Enum.take_while(fn {k, _v} -> k < scanned_end_key end)
8383+8484+ # TODO: compute count in merge/3
8585+ count = length(merged_pairs)
8686+8787+ cond do
8888+ count > limit ->
8989+ # We got more pairs than we asked for, which can happen if the
9090+ # storage and mem keys are disjoint (which is fairly likely in practice)
9191+ {Enum.take(merged_pairs, limit), limit}
68926969- mem_result =
7070- case storage_result.more do
7171- true ->
7272- # If we hit the limit in the storage result, scan only the (limited) range it spans (including the last key)
7373- {last_storage_key, _value} = List.last(storage_result.pairs)
7474- case reverse do
7575- false -> MemKV.scan(kv.mem_kv, version, start_key, last_storage_key <> "\x00", opts)
7676- true -> MemKV.scan(kv.mem_kv, version, last_storage_key, end_key, opts)
7777- end
7878- false ->
7979- # If we *did not* hit the limit, scan the same full range
8080- MemKV.scan(kv.mem_kv, version, start_key, end_key, opts)
8181- end
9393+ count == limit ->
9494+ # We got exactly what we wanted!
9595+ {merged_pairs, count}
82968383- case storage_result.more or mem_result.more do
8484- false ->
8585- # We fully scanned [start_key, end_key) for both storage and mem
8686- merged_pairs = merge(storage_result.pairs, mem_result.pairs, reverse)
8787- case length(merged_pairs) > limit do
9797+ count < limit ->
9898+ case scanned_end_key == end_key do
8899 true ->
8989- # The merged result contains more than `limit` pairs (storage and mem were disjoint)
9090- Enum.take(merged_pairs, limit)
100100+ # We got <limit pairs but we really did scan the full range
101101+ {merged_pairs, count}
91102 false ->
9292- merged_pairs
103103+ # We got <limit pairs and we did not scan the full range, meaning
104104+ # the KVs hit the limit but then the :deleted tombstones cleared
105105+ # out enough keys to bring us back under the limit, so we must
106106+ # keep scanning
107107+ {next_pairs, next_count} = do_scan(:forward, kv, version, scanned_end_key, end_key, limit - count, scan_count + 1)
108108+ {merged_pairs ++ next_pairs, count + next_count}
93109 end
110110+ end
111111+ end
941129595- true ->
9696- # Find the end of the scanned range, i.e. the last key
9797- # scanned by whichever scan hit the limit, or the
9898- # "smaller" (taking reverse into account) of the two
9999- # last keys if *both* scans hit the limit
100100- scanned_range_end =
101101- cond do
102102- storage_result.more and mem_result.more ->
103103- {sk, _value} = List.last(storage_result.pairs)
104104- {mk, _value} = List.last(mem_result.pairs)
105105- case reverse do
106106- false -> min(sk, mk)
107107- true -> max(sk, mk)
108108- end
113113+ # See :forward for comments, the :backward version is the same except key logic
114114+ # is inverted (deals with start_key instead of end_key)
115115+ defp do_scan(:backward, %HybridKV{} = kv, version, start_key, end_key, limit, scan_count) do
116116+ storage_result = FlatKV.scan(kv.storage_kv, start_key, end_key, limit: limit, reverse: true)
117117+ {storage_start_key, _end_key} = storage_result.range
109118110110- storage_result.more ->
111111- List.last(storage_result.pairs) |> elem(0)
119119+ mem_result = MemKV.scan(kv.mem_kv, version, storage_start_key, end_key, limit: limit, reverse: true)
120120+ {mem_start_key, _end_key} = mem_result.range
112121113113- mem_result.more ->
114114- List.last(mem_result.pairs) |> elem(0)
115115- end
122122+ scanned_start_key = max(storage_start_key, mem_start_key)
116123117117- # TODO: it would be more efficient to integrate the scanned_range_end
118118- # limit into merge/2, but for now this is easier to comprehend
119119- merged_pairs =
120120- merge(storage_result.pairs, mem_result.pairs, reverse)
121121- |> then(fn merged_pairs ->
122122- case reverse do
123123- false -> Enum.take_while(merged_pairs, fn {k, _v} -> k <= scanned_range_end end)
124124- true -> Enum.take_while(merged_pairs, fn {k, _v} -> k >= scanned_range_end end)
125125- end
126126- end)
124124+ merged_pairs =
125125+ merge(storage_result.pairs, mem_result.pairs, true)
126126+ |> Enum.take_while(fn {k, _v} -> k >= scanned_start_key end)
127127128128- merged_length = length(merged_pairs)
129129- cond do
130130- merged_length > limit ->
131131- Enum.take(merged_pairs, limit)
128128+ count = length(merged_pairs)
132129133133- merged_length == limit ->
134134- merged_pairs
130130+ cond do
131131+ count > limit ->
132132+ {Enum.take(merged_pairs, limit), limit}
135133136136- merged_length < limit ->
137137- # MemKV :deleted tombstones removed enough pairs to bring us under the limit,
138138- # so we must continue our scan where it left off
139139- next_opts = Keyword.put(opts, :limit, limit - merged_length)
140140- next_pairs =
141141- case reverse do
142142- false -> do_scan_merge(kv, version, scanned_range_end <> "\x00", end_key, next_opts, scan_count + 1)
143143- true -> do_scan_merge(kv, version, start_key, scanned_range_end, next_opts, scan_count + 1)
144144- end
134134+ count == limit ->
135135+ {merged_pairs, count}
145136146146- merged_pairs ++ next_pairs
137137+ count < limit ->
138138+ case scanned_start_key == start_key do
139139+ true ->
140140+ {merged_pairs, count}
141141+ false ->
142142+ {next_pairs, next_count} = do_scan(:backward, kv, version, start_key, scanned_start_key, limit - count, scan_count + 1)
143143+ {merged_pairs ++ next_pairs, count + next_count}
147144 end
148145 end
149146 end