@@ -3,6 +3,7 @@ package server
33import (
44 "context"
55 "fmt"
6+ "io"
67
78 "net/http"
89 "time"
@@ -188,6 +189,166 @@ func (s *StoreServer) GetMetadata(
188189 }), nil
189190}
190191
192+ // Backup streams a Badger backup of the datastore so it can be persisted externally.
193+ func (s * StoreServer ) Backup (
194+ ctx context.Context ,
195+ req * connect.Request [pb.BackupRequest ],
196+ stream * connect.ServerStream [pb.BackupResponse ],
197+ ) error {
198+ since := req .Msg .GetSinceVersion ()
199+ targetHeight := req .Msg .GetTargetHeight ()
200+
201+ currentHeight , err := s .store .Height (ctx )
202+ if err != nil {
203+ return connect .NewError (connect .CodeInternal , fmt .Errorf ("failed to get current height: %w" , err ))
204+ }
205+
206+ if targetHeight != 0 && targetHeight > currentHeight {
207+ return connect .NewError (
208+ connect .CodeFailedPrecondition ,
209+ fmt .Errorf ("requested target height %d exceeds current height %d" , targetHeight , currentHeight ),
210+ )
211+ }
212+
213+ initialMetadata := & pb.BackupMetadata {
214+ CurrentHeight : currentHeight ,
215+ TargetHeight : targetHeight ,
216+ SinceVersion : since ,
217+ Completed : false ,
218+ LastVersion : 0 ,
219+ }
220+
221+ if err := stream .Send (& pb.BackupResponse {
222+ Response : & pb.BackupResponse_Metadata {
223+ Metadata : initialMetadata ,
224+ },
225+ }); err != nil {
226+ return err
227+ }
228+
229+ writer := newBackupStreamWriter (stream , defaultBackupChunkSize )
230+ version , err := s .store .Backup (ctx , writer , since )
231+ if err != nil {
232+ var connectErr * connect.Error
233+ if errors .As (err , & connectErr ) {
234+ return connectErr
235+ }
236+ if errors .Is (err , context .Canceled ) {
237+ return connect .NewError (connect .CodeCanceled , err )
238+ }
239+ if errors .Is (err , context .DeadlineExceeded ) {
240+ return connect .NewError (connect .CodeDeadlineExceeded , err )
241+ }
242+ return connect .NewError (connect .CodeInternal , fmt .Errorf ("failed to execute backup: %w" , err ))
243+ }
244+
245+ if err := writer .Flush (); err != nil {
246+ var connectErr * connect.Error
247+ if errors .As (err , & connectErr ) {
248+ return connectErr
249+ }
250+ if errors .Is (err , context .Canceled ) {
251+ return connect .NewError (connect .CodeCanceled , err )
252+ }
253+ if errors .Is (err , context .DeadlineExceeded ) {
254+ return connect .NewError (connect .CodeDeadlineExceeded , err )
255+ }
256+ return connect .NewError (connect .CodeInternal , fmt .Errorf ("failed to flush backup stream: %w" , err ))
257+ }
258+
259+ completedMetadata := & pb.BackupMetadata {
260+ CurrentHeight : currentHeight ,
261+ TargetHeight : targetHeight ,
262+ SinceVersion : since ,
263+ LastVersion : version ,
264+ Completed : true ,
265+ }
266+
267+ if err := stream .Send (& pb.BackupResponse {
268+ Response : & pb.BackupResponse_Metadata {
269+ Metadata : completedMetadata ,
270+ },
271+ }); err != nil {
272+ return err
273+ }
274+
275+ return nil
276+ }
277+
278+ const defaultBackupChunkSize = 128 * 1024
279+
280+ var _ io.Writer = (* backupStreamWriter )(nil )
281+
282+ type backupStreamWriter struct {
283+ stream * connect.ServerStream [pb.BackupResponse ]
284+ buf []byte
285+ chunkSize int
286+ }
287+
288+ func newBackupStreamWriter (stream * connect.ServerStream [pb.BackupResponse ], chunkSize int ) * backupStreamWriter {
289+ if chunkSize <= 0 {
290+ chunkSize = defaultBackupChunkSize
291+ }
292+
293+ return & backupStreamWriter {
294+ stream : stream ,
295+ buf : make ([]byte , 0 , chunkSize ),
296+ chunkSize : chunkSize ,
297+ }
298+ }
299+
300+ func (w * backupStreamWriter ) Write (p []byte ) (int , error ) {
301+ written := 0
302+ for len (p ) > 0 {
303+ space := w .chunkSize - len (w .buf )
304+ if space == 0 {
305+ if err := w .flush (); err != nil {
306+ return written , err
307+ }
308+ space = w .chunkSize - len (w .buf )
309+ }
310+
311+ if space > len (p ) {
312+ space = len (p )
313+ }
314+
315+ w .buf = append (w .buf , p [:space ]... )
316+ p = p [space :]
317+ written += space
318+
319+ if len (w .buf ) == w .chunkSize {
320+ if err := w .flush (); err != nil {
321+ return written , err
322+ }
323+ }
324+ }
325+ return written , nil
326+ }
327+
328+ func (w * backupStreamWriter ) Flush () error {
329+ return w .flush ()
330+ }
331+
332+ func (w * backupStreamWriter ) flush () error {
333+ if len (w .buf ) == 0 {
334+ return nil
335+ }
336+
337+ chunk := make ([]byte , len (w .buf ))
338+ copy (chunk , w .buf )
339+
340+ if err := w .stream .Send (& pb.BackupResponse {
341+ Response : & pb.BackupResponse_Chunk {
342+ Chunk : chunk ,
343+ },
344+ }); err != nil {
345+ return err
346+ }
347+
348+ w .buf = w .buf [:0 ]
349+ return nil
350+ }
351+
191352type ConfigServer struct {
192353 config config.Config
193354 signer []byte
0 commit comments