@@ -10,7 +10,7 @@ use log::{debug, warn};
1010use crate :: {
1111 commit:: { self , Commit , StoredCommit } ,
1212 error,
13- index:: IndexError ,
13+ index:: { IndexError , IndexFileMut } ,
1414 payload:: Encode ,
1515 repo:: { TxOffset , TxOffsetIndex , TxOffsetIndexMut } ,
1616 Options ,
@@ -332,11 +332,31 @@ impl FileLike for OffsetIndexWriter {
332332
333333 fn ftruncate ( & mut self , tx_offset : u64 , _size : u64 ) -> io:: Result < ( ) > {
334334 self . reset ( ) ;
335- let _ = self . head . truncate ( tx_offset) ;
335+ self . head
336+ . truncate ( tx_offset)
337+ . inspect_err ( |e| {
338+ warn ! ( "failed to truncate offset index at {tx_offset}: {e:?}" ) ;
339+ } )
340+ . ok ( ) ;
336341 Ok ( ( ) )
337342 }
338343}
339344
345+ impl FileLike for IndexFileMut < TxOffset > {
346+ fn fsync ( & mut self ) -> io:: Result < ( ) > {
347+ self . async_flush ( )
348+ }
349+
350+ fn ftruncate ( & mut self , tx_offset : u64 , _size : u64 ) -> io:: Result < ( ) > {
351+ self . truncate ( tx_offset) . map_err ( |e| {
352+ io:: Error :: new (
353+ ErrorKind :: Other ,
354+ format ! ( "failed to truncate offset index at {tx_offset}: {e:?}" ) ,
355+ )
356+ } )
357+ }
358+ }
359+
340360#[ derive( Debug ) ]
341361pub struct Reader < R > {
342362 pub header : Header ,
@@ -393,7 +413,7 @@ impl<R: io::BufRead + io::Seek> Reader<R> {
393413
394414 #[ cfg( test) ]
395415 pub ( crate ) fn metadata ( self ) -> Result < Metadata , error:: SegmentMetadata > {
396- Metadata :: with_header ( self . min_tx_offset , self . header , self . inner )
416+ Metadata :: with_header ( self . min_tx_offset , self . header , self . inner , None )
397417 }
398418}
399419
@@ -513,30 +533,38 @@ pub struct Metadata {
513533}
514534
515535impl Metadata {
516- /// Read and validate metadata from a segment.
536+ /// Reads and validates metadata from a segment.
537+ /// It will look for last commit index offset and then traverse the segment
517538 ///
518- /// This traverses the entire segment, consuming thre `reader.
519- /// Doing so is necessary to determine `max_tx_offset`, `size_in_bytes` and
520- /// `max_epoch`.
521- pub ( crate ) fn extract < R : io:: Read > ( min_tx_offset : u64 , mut reader : R ) -> Result < Self , error:: SegmentMetadata > {
539+ /// Determines `max_tx_offset`, `size_in_bytes`, and `max_epoch` from the segment.
540+ pub ( crate ) fn extract < R : io:: Read + io:: Seek > (
541+ min_tx_offset : TxOffset ,
542+ mut reader : R ,
543+ offset_index : Option < & TxOffsetIndex > ,
544+ ) -> Result < Self , error:: SegmentMetadata > {
522545 let header = Header :: decode ( & mut reader) ?;
523- Self :: with_header ( min_tx_offset, header, reader)
546+ Self :: with_header ( min_tx_offset, header, reader, offset_index )
524547 }
525548
526- fn with_header < R : io:: Read > (
549+ fn with_header < R : io:: Read + io :: Seek > (
527550 min_tx_offset : u64 ,
528551 header : Header ,
529552 mut reader : R ,
553+ offset_index : Option < & TxOffsetIndex > ,
530554 ) -> Result < Self , error:: SegmentMetadata > {
531- let mut sofar = Self {
532- header,
533- tx_range : Range {
534- start : min_tx_offset,
535- end : min_tx_offset,
536- } ,
537- size_in_bytes : Header :: LEN as u64 ,
538- max_epoch : Commit :: DEFAULT_EPOCH ,
539- } ;
555+ let mut sofar = offset_index
556+ . and_then ( |index| Self :: find_valid_indexed_commit ( min_tx_offset, header, & mut reader, index) . ok ( ) )
557+ . unwrap_or_else ( || Self {
558+ header,
559+ tx_range : Range {
560+ start : min_tx_offset,
561+ end : min_tx_offset,
562+ } ,
563+ size_in_bytes : Header :: LEN as u64 ,
564+ max_epoch : u64:: default ( ) ,
565+ } ) ;
566+
567+ reader. seek ( SeekFrom :: Start ( sofar. size_in_bytes ) ) ?;
540568
541569 fn commit_meta < R : io:: Read > (
542570 reader : & mut R ,
@@ -573,6 +601,78 @@ impl Metadata {
573601
574602 Ok ( sofar)
575603 }
604+
605+ /// Finds the last valid commit in the segment using the offset index.
606+ /// It traverses the index in reverse order, starting from the last key.
607+ ///
608+ /// Returns
609+ /// * `Ok((Metadata)` - If a valid commit is found containing the commit, It adds a default
610+ /// header, which should be replaced with the actual header.
611+ /// * `Err` - If no valid commit is found or if the index is empty
612+ fn find_valid_indexed_commit < R : io:: Read + io:: Seek > (
613+ min_tx_offset : u64 ,
614+ header : Header ,
615+ reader : & mut R ,
616+ offset_index : & TxOffsetIndex ,
617+ ) -> io:: Result < Metadata > {
618+ let mut candidate_last_key = TxOffset :: MAX ;
619+
620+ while let Ok ( ( key, byte_offset) ) = offset_index. key_lookup ( candidate_last_key) {
621+ match Self :: validate_commit_at_offset ( reader, key, byte_offset) {
622+ Ok ( commit) => {
623+ return Ok ( Metadata {
624+ header,
625+ tx_range : Range {
626+ start : min_tx_offset,
627+ end : commit. tx_range . end ,
628+ } ,
629+ size_in_bytes : byte_offset + commit. size_in_bytes ,
630+ max_epoch : commit. epoch ,
631+ } ) ;
632+ }
633+
634+ // `TxOffset` at `byte_offset` is not valid, so try with previous entry
635+ Err ( _) => {
636+ candidate_last_key = key. saturating_sub ( 1 ) ;
637+ if candidate_last_key == 0 {
638+ break ;
639+ }
640+ }
641+ }
642+ }
643+
644+ Err ( io:: Error :: new (
645+ ErrorKind :: InvalidData ,
646+ format ! ( "No valid commit found in index up to key: {}" , candidate_last_key) ,
647+ ) )
648+ }
649+
650+ /// Validates and decodes a commit at `byte_offset` in the segment.
651+ ///
652+ /// # Returns
653+ /// * `Ok(commit::Metadata)` - If a valid commit is found with matching transaction offset
654+ /// * `Err` - If commit can't be decoded or has mismatched transaction offset
655+ fn validate_commit_at_offset < R : io:: Read + io:: Seek > (
656+ reader : & mut R ,
657+ tx_offset : TxOffset ,
658+ byte_offset : u64 ,
659+ ) -> io:: Result < commit:: Metadata > {
660+ reader. seek ( SeekFrom :: Start ( byte_offset) ) ?;
661+ let commit = commit:: Metadata :: extract ( reader) ?
662+ . ok_or_else ( || io:: Error :: new ( ErrorKind :: InvalidData , "failed to decode commit" ) ) ?;
663+
664+ if commit. tx_range . start != tx_offset {
665+ return Err ( io:: Error :: new (
666+ ErrorKind :: InvalidData ,
667+ format ! (
668+ "mismatch key in index offset file: expected={} actual={}" ,
669+ tx_offset, commit. tx_range. start
670+ ) ,
671+ ) ) ;
672+ }
673+
674+ Ok ( commit)
675+ }
576676}
577677
578678#[ cfg( test) ]
0 commit comments