@@ -20,6 +20,7 @@ const METADATA_VERSION: u32 = 0;
20
20
21
21
const DEFAULT_MAX_RETRIES : usize = 5 ;
22
22
const DEFAULT_PUSH_BATCH_SIZE : u32 = 128 ;
23
+ const DEFAULT_PULL_BATCH_SIZE : u32 = 128 ;
23
24
24
25
#[ derive( thiserror:: Error , Debug ) ]
25
26
#[ non_exhaustive]
@@ -66,6 +67,8 @@ pub enum SyncError {
66
67
InvalidLocalGeneration ( u32 , u32 ) ,
67
68
#[ error( "invalid local state: {0}" ) ]
68
69
InvalidLocalState ( String ) ,
70
+ #[ error( "server returned invalid length of frames: {0}" ) ]
71
+ InvalidPullFrameBytes ( usize ) ,
69
72
}
70
73
71
74
impl SyncError {
@@ -98,8 +101,8 @@ pub enum PushStatus {
98
101
}
99
102
100
103
pub enum PullResult {
101
- /// A frame was successfully pulled.
102
- Frame ( Bytes ) ,
104
+ /// Frames were successfully pulled.
105
+ Frames ( Bytes ) ,
103
106
/// We've reached the end of the generation.
104
107
EndOfGeneration { max_generation : u32 } ,
105
108
}
@@ -122,6 +125,7 @@ pub struct SyncContext {
122
125
auth_token : Option < HeaderValue > ,
123
126
max_retries : usize ,
124
127
push_batch_size : u32 ,
128
+ pull_batch_size : u32 ,
125
129
/// The current durable generation.
126
130
durable_generation : u32 ,
127
131
/// Represents the max_frame_no from the server.
@@ -154,6 +158,7 @@ impl SyncContext {
154
158
auth_token,
155
159
max_retries : DEFAULT_MAX_RETRIES ,
156
160
push_batch_size : DEFAULT_PUSH_BATCH_SIZE ,
161
+ pull_batch_size : DEFAULT_PULL_BATCH_SIZE ,
157
162
client,
158
163
durable_generation : 0 ,
159
164
durable_frame_num : 0 ,
@@ -175,7 +180,7 @@ impl SyncContext {
175
180
}
176
181
177
182
#[ tracing:: instrument( skip( self ) ) ]
178
- pub ( crate ) async fn pull_one_frame (
183
+ pub ( crate ) async fn pull_frames (
179
184
& mut self ,
180
185
generation : u32 ,
181
186
frame_no : u32 ,
@@ -185,9 +190,10 @@ impl SyncContext {
185
190
self . sync_url,
186
191
generation,
187
192
frame_no,
188
- frame_no + 1
193
+ // the server expects the range of [start, end) frames, i.e. end is exclusive
194
+ frame_no + self . pull_batch_size
189
195
) ;
190
- tracing:: debug!( "pulling frame" ) ;
196
+ tracing:: debug!( "pulling frame (uri={})" , uri ) ;
191
197
self . pull_with_retry ( uri, self . max_retries ) . await
192
198
}
193
199
@@ -417,20 +423,39 @@ impl SyncContext {
417
423
. map_err ( SyncError :: HttpDispatch ) ?;
418
424
419
425
if res. status ( ) . is_success ( ) {
420
- let frame = hyper:: body:: to_bytes ( res. into_body ( ) )
426
+ let frames = hyper:: body:: to_bytes ( res. into_body ( ) )
421
427
. await
422
428
. map_err ( SyncError :: HttpBody ) ?;
423
- return Ok ( PullResult :: Frame ( frame) ) ;
429
+ // a success result should always return some frames
430
+ if frames. is_empty ( ) {
431
+ tracing:: error!( "server returned empty frames in pull response" ) ;
432
+ return Err ( SyncError :: InvalidPullFrameBytes ( 0 ) . into ( ) ) ;
433
+ }
434
+ // the minimum payload size cannot be less than a single frame
435
+ if frames. len ( ) < FRAME_SIZE {
436
+ tracing:: error!(
437
+ "server returned frames with invalid length: {} < {}" ,
438
+ frames. len( ) ,
439
+ FRAME_SIZE
440
+ ) ;
441
+ return Err ( SyncError :: InvalidPullFrameBytes ( frames. len ( ) ) . into ( ) ) ;
442
+ }
443
+ return Ok ( PullResult :: Frames ( frames) ) ;
424
444
}
425
445
// BUG ALERT: The server returns a 500 error if the remote database is empty.
426
446
// This is a bug and should be fixed.
427
447
if res. status ( ) == StatusCode :: BAD_REQUEST
428
448
|| res. status ( ) == StatusCode :: INTERNAL_SERVER_ERROR
429
449
{
450
+ let status = res. status ( ) ;
430
451
let res_body = hyper:: body:: to_bytes ( res. into_body ( ) )
431
452
. await
432
453
. map_err ( SyncError :: HttpBody ) ?;
433
-
454
+ tracing:: trace!(
455
+ "server returned: {} body: {}" ,
456
+ status,
457
+ String :: from_utf8_lossy( & res_body[ ..] )
458
+ ) ;
434
459
let resp = serde_json:: from_slice :: < serde_json:: Value > ( & res_body[ ..] )
435
460
. map_err ( SyncError :: JsonDecode ) ?;
436
461
@@ -650,22 +675,34 @@ impl SyncContext {
650
675
651
676
let req = req. body ( Body :: empty ( ) ) . expect ( "valid request" ) ;
652
677
653
- let res = self
654
- . client
655
- . request ( req)
656
- . await
657
- . map_err ( SyncError :: HttpDispatch ) ?;
678
+ let ( res, http_duration) =
679
+ crate :: replication:: remote_client:: time ( self . client . request ( req) ) . await ;
680
+ let res = res. map_err ( SyncError :: HttpDispatch ) ?;
658
681
659
682
if !res. status ( ) . is_success ( ) {
660
683
let status = res. status ( ) ;
661
684
let body = hyper:: body:: to_bytes ( res. into_body ( ) )
662
685
. await
663
686
. map_err ( SyncError :: HttpBody ) ?;
687
+ tracing:: error!(
688
+ "failed to pull db file from remote server, status={}, body={}, url={}, duration={:?}" ,
689
+ status,
690
+ String :: from_utf8_lossy( & body) ,
691
+ uri,
692
+ http_duration
693
+ ) ;
664
694
return Err (
665
695
SyncError :: PullFrame ( status, String :: from_utf8_lossy ( & body) . to_string ( ) ) . into ( ) ,
666
696
) ;
667
697
}
668
698
699
+ tracing:: debug!(
700
+ "pulled db file from remote server, status={}, url={}, duration={:?}" ,
701
+ res. status( ) ,
702
+ uri,
703
+ http_duration
704
+ ) ;
705
+
669
706
// todo: do streaming write to the disk
670
707
let bytes = hyper:: body:: to_bytes ( res. into_body ( ) )
671
708
. await
@@ -887,6 +924,11 @@ async fn try_push(
887
924
} )
888
925
}
889
926
927
+ /// PAGE_SIZE used by the sync / diskless server
928
+ const PAGE_SIZE : usize = 4096 ;
929
+ const FRAME_HEADER_SIZE : usize = 24 ;
930
+ const FRAME_SIZE : usize = PAGE_SIZE + FRAME_HEADER_SIZE ;
931
+
890
932
pub async fn try_pull (
891
933
sync_ctx : & mut SyncContext ,
892
934
conn : & Connection ,
@@ -898,10 +940,32 @@ pub async fn try_pull(
898
940
loop {
899
941
let generation = sync_ctx. durable_generation ( ) ;
900
942
let frame_no = sync_ctx. durable_frame_num ( ) + 1 ;
901
- match sync_ctx. pull_one_frame ( generation, frame_no) . await {
902
- Ok ( PullResult :: Frame ( frame) ) => {
903
- insert_handle. insert ( & frame) ?;
904
- sync_ctx. durable_frame_num = frame_no;
943
+ match sync_ctx. pull_frames ( generation, frame_no) . await {
944
+ Ok ( PullResult :: Frames ( frames) ) => {
945
+ tracing:: debug!(
946
+ "pull_frames: generation={}, start_frame={} (end_frame={}, batch_size={}), frames_size={}" ,
947
+ generation, frame_no, frame_no + sync_ctx. pull_batch_size, sync_ctx. pull_batch_size, frames. len( ) ,
948
+ ) ;
949
+ if frames. len ( ) % FRAME_SIZE != 0 {
950
+ tracing:: error!(
951
+ "frame size {} is not a multiple of the expected size {}" ,
952
+ frames. len( ) ,
953
+ FRAME_SIZE ,
954
+ ) ;
955
+ return Err ( SyncError :: InvalidPullFrameBytes ( frames. len ( ) ) . into ( ) ) ;
956
+ }
957
+ for chunk in frames. chunks ( FRAME_SIZE ) {
958
+ let r = insert_handle. insert ( & chunk) ;
959
+ if let Err ( e) = r {
960
+ tracing:: error!(
961
+ "insert error (frame= {}) : {:?}" ,
962
+ sync_ctx. durable_frame_num + 1 ,
963
+ e
964
+ ) ;
965
+ return Err ( e) ;
966
+ }
967
+ sync_ctx. durable_frame_num += 1 ;
968
+ }
905
969
}
906
970
Ok ( PullResult :: EndOfGeneration { max_generation } ) => {
907
971
// If there are no more generations to pull, we're done.
@@ -920,7 +984,7 @@ pub async fn try_pull(
920
984
insert_handle. begin ( ) ?;
921
985
}
922
986
Err ( e) => {
923
- tracing:: debug!( "pull_one_frame error: {:?}" , e) ;
987
+ tracing:: debug!( "pull_frames error: {:?}" , e) ;
924
988
err. replace ( e) ;
925
989
break ;
926
990
}
0 commit comments