1
+ use std:: borrow:: Cow ;
1
2
use std:: fs:: File ;
2
3
use std:: io:: { BufWriter , Write } ;
3
4
use std:: mem:: size_of;
@@ -8,7 +9,7 @@ use std::sync::{
8
9
Arc ,
9
10
} ;
10
11
11
- use bytemuck:: { bytes_of, pod_read_unaligned, Pod , Zeroable } ;
12
+ use bytemuck:: { bytes_of, pod_read_unaligned, try_from_bytes , Pod , Zeroable } ;
12
13
use bytes:: { Bytes , BytesMut } ;
13
14
use heed:: byteorder:: BigEndian ;
14
15
use heed_types:: { SerdeBincode , U64 } ;
@@ -37,7 +38,7 @@ pub struct CompactionQueue {
37
38
next_id : AtomicU64 ,
38
39
notify : watch:: Sender < Option < u64 > > ,
39
40
db_path : PathBuf ,
40
- snapshot_store : Arc < SnapshotStore > ,
41
+ pub snapshot_store : Arc < SnapshotStore > ,
41
42
}
42
43
43
44
impl CompactionQueue {
@@ -109,9 +110,17 @@ impl CompactionQueue {
109
110
let to_compact_path = to_compact_path. clone ( ) ;
110
111
let db_path = self . db_path . clone ( ) ;
111
112
move || {
112
- let mut builder = SnapshotBuilder :: new ( & db_path, job. database_id , job. log_id ) ?;
113
113
let log = LogFile :: new ( to_compact_path) ?;
114
- for frame in log. rev_deduped ( ) {
114
+ let ( start_fno, end_fno, iter) =
115
+ log. rev_deduped ( ) . expect ( "compaction job with no frames!" ) ;
116
+ let mut builder = SnapshotBuilder :: new (
117
+ & db_path,
118
+ job. database_id ,
119
+ job. log_id ,
120
+ start_fno,
121
+ end_fno,
122
+ ) ?;
123
+ for frame in iter {
115
124
let frame = frame?;
116
125
builder. push_frame ( frame) ?;
117
126
}
@@ -168,8 +177,50 @@ pub struct SnapshotBuilder {
168
177
last_seen_frame_no : u64 ,
169
178
}
170
179
180
+ #[ derive( Debug , Clone , Copy , Pod , Zeroable ) ]
181
+ #[ repr( C ) ]
182
+ pub struct SnapshotFrameHeader {
183
+ pub frame_no : FrameNo ,
184
+ pub page_no : u32 ,
185
+ _pad : u32 ,
186
+ }
187
+
188
+ #[ derive( Clone ) ]
189
+ pub struct SnapshotFrame {
190
+ data : Bytes ,
191
+ }
192
+
193
+ impl SnapshotFrame {
194
+ const SIZE : usize = size_of :: < SnapshotFrameHeader > ( ) + 4096 ;
195
+
196
+ pub fn try_from_bytes ( data : Bytes ) -> crate :: Result < Self > {
197
+ if data. len ( ) != Self :: SIZE {
198
+ color_eyre:: eyre:: bail!( "invalid snapshot frame" )
199
+ }
200
+
201
+ Ok ( Self { data } )
202
+ }
203
+
204
+ pub fn header ( & self ) -> Cow < SnapshotFrameHeader > {
205
+ let data = & self . data [ ..size_of :: < SnapshotFrameHeader > ( ) ] ;
206
+ try_from_bytes ( data)
207
+ . map ( Cow :: Borrowed )
208
+ . unwrap_or_else ( |_| Cow :: Owned ( pod_read_unaligned ( data) ) )
209
+ }
210
+
211
+ pub ( crate ) fn page ( & self ) -> & [ u8 ] {
212
+ & self . data [ size_of :: < SnapshotFrameHeader > ( ) ..]
213
+ }
214
+ }
215
+
171
216
impl SnapshotBuilder {
172
- pub fn new ( db_path : & Path , db_id : DatabaseId , snapshot_id : Uuid ) -> color_eyre:: Result < Self > {
217
+ pub fn new (
218
+ db_path : & Path ,
219
+ db_id : DatabaseId ,
220
+ snapshot_id : Uuid ,
221
+ start_fno : FrameNo ,
222
+ end_fno : FrameNo ,
223
+ ) -> color_eyre:: Result < Self > {
173
224
let temp_dir = db_path. join ( "tmp" ) ;
174
225
let mut target = BufWriter :: new ( NamedTempFile :: new_in ( & temp_dir) ?) ;
175
226
// reserve header space
@@ -178,8 +229,8 @@ impl SnapshotBuilder {
178
229
Ok ( Self {
179
230
header : SnapshotFileHeader {
180
231
db_id,
181
- start_frame_no : u64 :: MAX ,
182
- end_frame_no : u64 :: MIN ,
232
+ start_frame_no : start_fno ,
233
+ end_frame_no : end_fno ,
183
234
frame_count : 0 ,
184
235
size_after : 0 ,
185
236
_pad : 0 ,
@@ -194,16 +245,20 @@ impl SnapshotBuilder {
194
245
pub fn push_frame ( & mut self , frame : Frame ) -> color_eyre:: Result < ( ) > {
195
246
assert ! ( frame. header( ) . frame_no < self . last_seen_frame_no) ;
196
247
self . last_seen_frame_no = frame. header ( ) . frame_no ;
197
- if frame. header ( ) . frame_no < self . header . start_frame_no {
198
- self . header . start_frame_no = frame. header ( ) . frame_no ;
199
- }
200
248
201
- if frame. header ( ) . frame_no > self . header . end_frame_no {
202
- self . header . end_frame_no = frame. header ( ) . frame_no ;
249
+ if frame. header ( ) . frame_no == self . header . end_frame_no {
203
250
self . header . size_after = frame. header ( ) . size_after ;
204
251
}
205
252
206
- self . snapshot_file . write_all ( frame. as_slice ( ) ) ?;
253
+ let header = SnapshotFrameHeader {
254
+ frame_no : frame. header ( ) . frame_no ,
255
+ page_no : frame. header ( ) . page_no ,
256
+ _pad : 0 ,
257
+ } ;
258
+
259
+ self . snapshot_file . write_all ( bytes_of ( & header) ) ?;
260
+ self . snapshot_file . write_all ( frame. page ( ) ) ?;
261
+
207
262
self . header . frame_count += 1 ;
208
263
209
264
Ok ( ( ) )
@@ -241,18 +296,18 @@ impl SnapshotFile {
241
296
}
242
297
243
298
/// Iterator on the frames contained in the snapshot file, in reverse frame_no order.
244
- pub fn frames_iter ( & self ) -> impl Iterator < Item = libsqlx :: Result < Bytes > > + ' _ {
299
+ pub fn frames_iter ( & self ) -> impl Iterator < Item = crate :: Result < SnapshotFrame > > + ' _ {
245
300
let mut current_offset = 0 ;
246
301
std:: iter:: from_fn ( move || {
247
302
if current_offset >= self . header . frame_count {
248
303
return None ;
249
304
}
250
305
let read_offset = size_of :: < SnapshotFileHeader > ( ) as u64
251
- + current_offset * LogFile :: FRAME_SIZE as u64 ;
306
+ + current_offset * SnapshotFrame :: SIZE as u64 ;
252
307
current_offset += 1 ;
253
- let mut buf = BytesMut :: zeroed ( LogFile :: FRAME_SIZE ) ;
308
+ let mut buf = BytesMut :: zeroed ( SnapshotFrame :: SIZE ) ;
254
309
match self . file . read_exact_at ( & mut buf, read_offset as _ ) {
255
- Ok ( _) => Some ( Ok ( buf. freeze ( ) ) ) ,
310
+ Ok ( _) => Some ( Ok ( SnapshotFrame { data : buf. freeze ( ) } ) ) ,
256
311
Err ( e) => Some ( Err ( e. into ( ) ) ) ,
257
312
}
258
313
} )
@@ -262,19 +317,16 @@ impl SnapshotFile {
262
317
pub fn frames_iter_from (
263
318
& self ,
264
319
frame_no : u64 ,
265
- ) -> impl Iterator < Item = libsqlx :: Result < Bytes > > + ' _ {
320
+ ) -> impl Iterator < Item = crate :: Result < SnapshotFrame > > + ' _ {
266
321
let mut iter = self . frames_iter ( ) ;
267
322
std:: iter:: from_fn ( move || match iter. next ( ) {
268
- Some ( Ok ( bytes) ) => match Frame :: try_from_bytes ( bytes. clone ( ) ) {
269
- Ok ( frame) => {
270
- if frame. header ( ) . frame_no < frame_no {
271
- None
272
- } else {
273
- Some ( Ok ( bytes) )
274
- }
323
+ Some ( Ok ( frame) ) => {
324
+ if frame. header ( ) . frame_no < frame_no {
325
+ None
326
+ } else {
327
+ Some ( Ok ( frame) )
275
328
}
276
- Err ( e) => Some ( Err ( e) ) ,
277
- } ,
329
+ }
278
330
other => other,
279
331
} )
280
332
}
@@ -331,20 +383,23 @@ mod test {
331
383
let snapshot_file = SnapshotFile :: open ( & snapshot_path) . unwrap ( ) ;
332
384
assert_eq ! ( snapshot_file. header. start_frame_no, expected_start_frameno) ;
333
385
assert_eq ! ( snapshot_file. header. end_frame_no, expected_end_frameno) ;
334
- assert ! ( snapshot_file. frames_iter( ) . all( |f| expected_page_content
335
- . remove( & Frame :: try_from_bytes( f. unwrap( ) ) . unwrap( ) . header( ) . page_no) ) ) ;
386
+ assert ! ( snapshot_file
387
+ . frames_iter( )
388
+ . all( |f| expected_page_content. remove( & f. unwrap( ) . header( ) . page_no) ) ) ;
336
389
assert ! ( expected_page_content. is_empty( ) ) ;
337
390
338
- assert_eq ! ( snapshot_file
339
- . frames_iter( )
340
- . map( Result :: unwrap)
341
- . map( Frame :: try_from_bytes)
342
- . map( Result :: unwrap)
343
- . map( |f| f. header( ) . frame_no)
344
- . reduce( |prev, new| {
345
- assert!( new < prev) ;
346
- new
347
- } ) . unwrap( ) , 0 ) ;
391
+ assert_eq ! (
392
+ snapshot_file
393
+ . frames_iter( )
394
+ . map( Result :: unwrap)
395
+ . map( |f| f. header( ) . frame_no)
396
+ . reduce( |prev, new| {
397
+ assert!( new < prev) ;
398
+ new
399
+ } )
400
+ . unwrap( ) ,
401
+ 0
402
+ ) ;
348
403
349
404
assert_eq ! ( store. locate( database_id, 0 ) . unwrap( ) . snapshot_id, log_id) ;
350
405
}
0 commit comments