1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
//! Walking the whole index while it is being written to.
//!
//! `KEYS` can walk the index in one go and be done with it. `SCAN` cannot: it
//! hands the client a number, the client goes away and does something else, and
//! then it comes back and expects the walk to carry on. Between those two calls
//! the index may have doubled its directory and split any number of segments,
//! and the promise `SCAN` makes has to survive all of it.
//!
//! The promise is one sided and worth stating exactly, because half of what
//! makes it implementable is what it does not say. A key that is there for the
//! whole walk is returned at least once. A key added or removed partway through
//! may or may not appear. A key may appear twice. That is Redis's contract and
//! it is the contract here.
//!
//! # The prefix does not move
//!
//! Redis walks a power of two table and doubles it by adding a bit at the top of
//! the bucket index, so a bucket that was `i` becomes `i` and `i + n`. That is
//! why its cursor counts in reverse binary: it is the only order in which the
//! two halves of a split bucket stay next to each other.
//!
//! This index doubles the other way round. [`Index::dir_index`] takes the top
//! `global_depth` bits below the tag, and doubling the directory copies each
//! entry to two neighbouring slots, so a directory index `d` becomes `2d` and
//! `2d + 1`. A bit is added at the bottom, not the top.
//!
//! That makes the cursor simple, because there is a number that does not move at
//! all. Take the full 48 bits the directory could ever use, left aligned, and
//! call it the prefix. It is a function of the key's hash and nothing else, so
//! it is the same number before a doubling and after one, and the directory
//! index at any depth is just the top `global_depth` bits of it. Walk in
//! increasing prefix order and the boundary between what has been seen and what
//! has not is a number that means the same thing in every version of the index
//! this walk will ever see.
//!
//! # What a split does
//!
//! A segment covers a contiguous run of prefixes. Splitting it cuts that run in
//! half and gives the top half to a new segment, which is a change to where the
//! keys live and not to any key's prefix.
//!
//! If the cursor is partway through a segment when it splits, the walk resumes
//! in the half that still holds the cursor's prefix, finishes it, and then
//! starts the other half from its first bucket. Keys in the top half that had
//! already been returned are returned again. That is the duplicate the contract
//! allows, and it is the price of never having to stop the world.
//!
//! # The shape of the number
//!
//! ```text
//! 63 16 15 6 5 0
//! +----------------------------------+---------+----------+
//! | directory prefix | 0 | bucket |
//! +----------------------------------+---------+----------+
//! ```
//!
//! The bucket within a segment comes off the bottom of the hash and the prefix
//! comes off the top, so the two never overlap and a split cannot move a key
//! from one bucket to another. The ten bits in the middle are spare. They are
//! not padding for its own sake: a segment is 64 buckets today and the day it is
//! not, the field grows into them without the cursors clients are holding
//! meaning something different.
//!
//! Zero is both the start and the end, which is Redis's convention and is not an
//! ambiguity in practice: a walk that has finished says zero, and a client that
//! says zero is starting a new one.
/// How far a scan has got, and the number the client holds between calls.
///
/// It is a position in the keyspace and not a position in memory. Two calls a
/// week apart with the same cursor resume at the same place, even if every
/// segment in the index has split in between.
;
/// Bits the directory can ever use, which is the index's `MAX_DEPTH`.
pub const PREFIX_BITS: u32 = 48;
/// Where the prefix sits in the cursor, leaving room below it for the bucket.
pub const PREFIX_SHIFT: u32 = 16;
/// Bits of bucket index, which is `log2` of [`SEGMENT_BUCKETS`](super::SEGMENT_BUCKETS).
pub const BUCKET_BITS: u32 = 6;