1+ use std:: borrow:: Cow ;
12use std:: fs:: File ;
23use std:: io:: { BufWriter , Write } ;
34use std:: mem:: size_of;
@@ -8,7 +9,7 @@ use std::sync::{
89 Arc ,
910} ;
1011
11- use bytemuck:: { bytes_of, pod_read_unaligned, Pod , Zeroable } ;
12+ use bytemuck:: { bytes_of, pod_read_unaligned, try_from_bytes , Pod , Zeroable } ;
1213use bytes:: { Bytes , BytesMut } ;
1314use heed:: byteorder:: BigEndian ;
1415use heed_types:: { SerdeBincode , U64 } ;
@@ -37,7 +38,7 @@ pub struct CompactionQueue {
3738 next_id : AtomicU64 ,
3839 notify : watch:: Sender < Option < u64 > > ,
3940 db_path : PathBuf ,
40- snapshot_store : Arc < SnapshotStore > ,
41+ pub snapshot_store : Arc < SnapshotStore > ,
4142}
4243
4344impl CompactionQueue {
@@ -109,9 +110,17 @@ impl CompactionQueue {
109110 let to_compact_path = to_compact_path. clone ( ) ;
110111 let db_path = self . db_path . clone ( ) ;
111112 move || {
112- let mut builder = SnapshotBuilder :: new ( & db_path, job. database_id , job. log_id ) ?;
113113 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 {
115124 let frame = frame?;
116125 builder. push_frame ( frame) ?;
117126 }
@@ -168,8 +177,50 @@ pub struct SnapshotBuilder {
168177 last_seen_frame_no : u64 ,
169178}
170179
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+
171216impl 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 > {
173224 let temp_dir = db_path. join ( "tmp" ) ;
174225 let mut target = BufWriter :: new ( NamedTempFile :: new_in ( & temp_dir) ?) ;
175226 // reserve header space
@@ -178,8 +229,8 @@ impl SnapshotBuilder {
178229 Ok ( Self {
179230 header : SnapshotFileHeader {
180231 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 ,
183234 frame_count : 0 ,
184235 size_after : 0 ,
185236 _pad : 0 ,
@@ -194,16 +245,20 @@ impl SnapshotBuilder {
194245 pub fn push_frame ( & mut self , frame : Frame ) -> color_eyre:: Result < ( ) > {
195246 assert ! ( frame. header( ) . frame_no < self . last_seen_frame_no) ;
196247 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- }
200248
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 {
203250 self . header . size_after = frame. header ( ) . size_after ;
204251 }
205252
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+
207262 self . header . frame_count += 1 ;
208263
209264 Ok ( ( ) )
@@ -241,18 +296,18 @@ impl SnapshotFile {
241296 }
242297
243298 /// 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 > > + ' _ {
245300 let mut current_offset = 0 ;
246301 std:: iter:: from_fn ( move || {
247302 if current_offset >= self . header . frame_count {
248303 return None ;
249304 }
250305 let read_offset = size_of :: < SnapshotFileHeader > ( ) as u64
251- + current_offset * LogFile :: FRAME_SIZE as u64 ;
306+ + current_offset * SnapshotFrame :: SIZE as u64 ;
252307 current_offset += 1 ;
253- let mut buf = BytesMut :: zeroed ( LogFile :: FRAME_SIZE ) ;
308+ let mut buf = BytesMut :: zeroed ( SnapshotFrame :: SIZE ) ;
254309 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 ( ) } ) ) ,
256311 Err ( e) => Some ( Err ( e. into ( ) ) ) ,
257312 }
258313 } )
@@ -262,19 +317,16 @@ impl SnapshotFile {
262317 pub fn frames_iter_from (
263318 & self ,
264319 frame_no : u64 ,
265- ) -> impl Iterator < Item = libsqlx :: Result < Bytes > > + ' _ {
320+ ) -> impl Iterator < Item = crate :: Result < SnapshotFrame > > + ' _ {
266321 let mut iter = self . frames_iter ( ) ;
267322 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) )
275328 }
276- Err ( e) => Some ( Err ( e) ) ,
277- } ,
329+ }
278330 other => other,
279331 } )
280332 }
@@ -331,20 +383,23 @@ mod test {
331383 let snapshot_file = SnapshotFile :: open ( & snapshot_path) . unwrap ( ) ;
332384 assert_eq ! ( snapshot_file. header. start_frame_no, expected_start_frameno) ;
333385 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) ) ) ;
336389 assert ! ( expected_page_content. is_empty( ) ) ;
337390
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+ ) ;
348403
349404 assert_eq ! ( store. locate( database_id, 0 ) . unwrap( ) . snapshot_id, log_id) ;
350405 }
0 commit comments