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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
//! `SAVE`, `BGSAVE`, `BGREWRITEAOF`, `LASTSAVE` and `ROLE`.
//!
//! Four commands about writing the dataset to a file and one about who this
//! server is. They are together because a client asks them together: a backup
//! script says `BGSAVE`, polls `LASTSAVE` until the number moves and then reads
//! the file, and a tool that is about to do any of that asks `ROLE` first so it
//! does not take a backup off a replica by accident.
//!
//! # There is already a file, so why write another one
//!
//! Because the two files are for different readers. The one the keyspace sits on
//! is this server's own, it is written as the commands run and it is what makes
//! a restart cheap. What `SAVE` writes is an RDB, which is the format every
//! other thing in the Redis world can read: `redis-check-rdb`, a real
//! `redis-server` told to start on it, a migration tool, a replica being seeded.
//! So this is an export and not a checkpoint, and the durability of the data was
//! never waiting on it.
//!
//! That is also why nothing here ever runs on its own. Redis writes a snapshot
//! when enough keys have changed because the snapshot is the only copy; here it
//! would be a second copy of something already on disk, so `save` is empty and
//! stays empty, and a file appears when somebody asks for one.
//!
//! # Why the background one is not in the background
//!
//! Redis forks, and the child writes the file out of a copy on write image while
//! the parent carries on. There is no fork here, so `BGSAVE` writes the file
//! before it answers and then says `Background saving started`, which is
//! D-125. The reply is the reply a client is waiting for and the file is on disk
//! by the time it arrives, so a script that polls `LASTSAVE` afterwards sees the
//! number it was waiting for on the first read rather than the third. What a
//! client cannot see is `rdb_bgsave_in_progress` going to one, because there is
//! no window in which it is true.
//!
//! # Why the rewrite writes nothing
//!
//! There is no append only file to rewrite. `BGREWRITEAOF` answers the sentence
//! a real server answers, counts itself and does nothing, which is D-126.
//! `appendonly` reads `no` here and cannot be set to `yes`, so a client that
//! looks before it asks already knows there is nothing to rewrite, and the
//! command is here because tooling calls it blind on the way to something else.
use fs;
use Write as _;
use PathBuf;
use Relaxed;
use ;
use Result;
use Snapshot;
use ;
use REPORTED_VERSION;
use Spec;
use ;
use crateOut;
/// What the file is called, which is also what `CONFIG GET dbfilename` answers.
///
/// Fixed, because `dbfilename` is a protected config on a real server too: it
/// is refused by `CONFIG SET` with a sentence about protection unless the server
/// was started with protected configs turned on, and nothing in this build turns
/// them on.
pub const FILE: &str = "dump.rdb";
/// What a save has done, so the persistence section has something to report.
///
/// Every field is a number a client polls rather than one a command reads, so
/// they are relaxed atomics next to each other rather than one lock around the
/// set of them. Nothing here is read in the same breath as anything else: a
/// client asking `INFO persistence` is asking five separate questions that
/// happen to arrive in one string.
pub
/// One of the five, by name.
///
/// # Errors
///
/// [`yo_common::Code::Invalid`] for a word after `BGSAVE` that is not
/// `SCHEDULE`. A save that could not write its file does not come back through
/// here at all: it writes its own error line, for the reason [`save`] gives.
pub
/// `SAVE`, which writes the file and waits for it.
///
/// The failure reply is a bare `-ERR` with nothing after it. That is not a
/// message this file forgot to write: it is `shared.err`, the reply Redis has
/// for a save that failed, and the reason it says nothing is that whatever went
/// wrong went wrong in the file system and is in the server's log rather than in
/// a sentence a client could act on. It is written here rather than returned as
/// an error because the error writer puts a space after `ERR` and this line has
/// nothing to put after the space.
/// `BGSAVE [SCHEDULE]`, which writes the file and says it started.
///
/// Two sentences and not one. Inside a transaction a real server cannot fork,
/// because the fork would land in the middle of a batch that has been promised
/// to run without anything in between, so it queues the save for the next tick
/// and answers `scheduled` instead of `started`. Nothing here forks and so
/// nothing here has that problem, and the two sentences are still told apart,
/// because a client that reads them apart is a client reading them for a reason.
///
/// A failure is not reported. Redis answers `-ERR` when the fork itself fails
/// and `+Background saving started` when the fork succeeds and the child then
/// fails, and the second is the one this is: the work that could fail happens
/// after the point where a real server has already answered. So a failed save
/// shows up where a failed background save shows up on a real server, which is
/// `rdb_last_bgsave_status`.
/// `BGREWRITEAOF`, which counts itself and does nothing else.
///
/// The same two sentences for the same reason as [`save_in_background`].
/// `ROLE`, which is what a client asks before it trusts anything else it reads.
///
/// Three elements: the word, then a number whose meaning depends on the word,
/// then a list whose shape depends on it too. A master answers its replication
/// offset and the replicas attached to it, and this server is a master with no
/// replicas and nothing written to a stream that does not exist yet, which is
/// the same zero `INFO replication` reports next to it.
/// Write the whole dataset out, and say whether it worked.
///
/// Into a temporary name first and then renamed over the old file, which is what
/// a real server does and is the only way the file is either the old dataset or
/// the new one and never half of each. A reader that opens `dump.rdb` while this
/// is running gets whichever of the two the rename has got to, and both of them
/// are files that load.
///
/// The image is built in memory before any of it is written. That costs the size
/// of the dataset and it is the same trade [`super::backup`] makes, for the same
/// reason: the writer hands back a buffer rather than taking a sink, and turning
/// it into one is the borrowing walk that is a bigger change than this file
/// should make.
pub
/// The bytes on disk, flushed and synced before the rename sees them.
///
/// Synced because the point of the file is that it survives the machine and not
/// only the process, and a rename over an unsynced file is a file that can come
/// back empty after a power cut.
/// How many keys the last file could not carry.
///
/// Read by `DEBUG RELOAD`, which has to know before it throws the keyspace away
/// whether the file it is about to read back holds all of it.
pub
/// The whole dataset as one RDB image, and how many keys could not go in it.
///
/// Every database in turn, and the stripes of each are taken one at a time. So
/// this is not one instant of the whole keyspace: a write to database nine while
/// database two is being walked is in the file and a write to database two after
/// it has been walked is not. A real server forks and gets an instant for free,
/// and buying one here would mean holding every stripe of every database at once
/// while megabytes are written, which would stop the server for as long as the
/// save takes. The same trade [`super::backup`] makes and the same reason.
/// The wall clock second, which is the unit every time in here is in.
/// The `Persistence` section of `INFO`.
///
/// Redis reports thirty odd fields here and this reports fifteen. What is
/// missing is missing because it is about a fork or an append only file, and
/// there is neither: `rdb_bgsave_in_progress` would be a zero that is never
/// anything else and `rdb_last_cow_size` would be a zero about a copy that never
/// happens. A field that is not there is a client falling back, and a field that
/// is there and always zero is a client believing it.
///
/// The two load fields are here because there is a load now. `DEBUG RELOAD` and
/// `yodb serve --restore` both read a whole file into the keyspace, so
/// `rdb_last_load_keys_loaded` is a number about something that happened and
/// `rdb_last_load_keys_expired` says how much of the file was too old to keep.
/// A server that has not loaded reports nought for both, which is Redis's answer
/// on a server started with no file to load as well.
///
/// `rdb_changes_since_last_save` is the one absence that is a gap rather than a
/// decision. It is Redis's dirty counter, which every command adds the number of
/// changes it made to, and yo has no such counter yet. Reporting the number of
/// write commands under that name would be a different number wearing the same
/// label, so it waits for D-113.
pub
/// Write the dataset out because a client asked for it on the way out.
///
/// `SHUTDOWN SAVE` and nothing else. A real server saves on the way down when it
/// has save points configured or when the client said so, and this one has no
/// save points and never will, so the word is the whole of it.
pub