3131 open_snapshot /3 ,
3232 create_snapshot /1 ,
3333 create_snapshot /2 ,
34+ create_witness_snapshot /1 ,
35+ create_witness_snapshot /2 ,
3436 make_empty_snapshot /5 ,
3537 delete_snapshot /2
3638]).
272274-callback storage_make_empty_snapshot (Name :: atom (), Identifier :: # raft_identifier {}, Path :: file :filename (), Position :: wa_raft_log :log_pos (), Config :: wa_raft_server :config (), Data :: dynamic ()) -> ok | error ().
273275-optional_callback ([storage_make_empty_snapshot / 6 ]).
274276
277+ % % Create a new witness snapshot at the provided path, containing only the current
278+ % % position in storage, configuration, and virtual partition range information.
279+ % % The snapshot will be empty (without actual storage data) but will retain all
280+ % % necessary metadata. When loaded, this witness snapshot will reflect the exact
281+ % % position state of the original storage without the storage contents.
282+ -callback storage_create_witness_snapshot (Path :: file :filename (), Handle :: storage_handle ()) -> ok | error ().
283+ -optional_callback ([storage_create_witness_snapshot / 6 ]).
284+
275285% %-----------------------------------------------------------------------------
276286% % RAFT Storage - Types
277287% %-----------------------------------------------------------------------------
@@ -358,6 +368,14 @@ create_snapshot(ServiceRef) ->
358368create_snapshot (ServiceRef , Name ) ->
359369 gen_server :call (ServiceRef , {snapshot_create , Name }, ? RAFT_STORAGE_CALL_TIMEOUT ()).
360370
371+ -spec create_witness_snapshot (ServiceRef :: pid () | atom ()) -> {ok , Pos :: wa_raft_log :log_pos ()} | error ().
372+ create_witness_snapshot (ServiceRef ) ->
373+ gen_server :call (ServiceRef , snapshot_create_witness , ? RAFT_STORAGE_CALL_TIMEOUT ()).
374+
375+ -spec create_witness_snapshot (ServiceRef :: pid () | atom (), Name :: string ()) -> {ok , Pos :: wa_raft_log :log_pos ()} | error ().
376+ create_witness_snapshot (ServiceRef , Name ) ->
377+ gen_server :call (ServiceRef , {snapshot_create_witness , Name }, ? RAFT_STORAGE_CALL_TIMEOUT ()).
378+
361379-spec make_empty_snapshot (ServiceRef :: pid () | atom (), Path :: file :filename (), Position :: wa_raft_log :log_pos (), Config :: wa_raft_server :config (), Data :: term ()) -> ok | error ().
362380make_empty_snapshot (ServiceRef , Path , Position , Config , Data ) ->
363381 gen_server :call (ServiceRef , {make_empty_snapshot , Path , Position , Config , Data }).
@@ -450,8 +468,10 @@ init(#raft_options{table = Table, partition = Partition, identifier = Identifier
450468 open |
451469 {read , Op :: wa_raft_acceptor :command ()} |
452470 snapshot_create |
471+ snapshot_create_witness |
453472 status |
454473 {snapshot_create , Name :: string ()} |
474+ {snapshot_create_witness , Name :: string ()} |
455475 {snapshot_open , Path :: file :filename (), LastAppliedPos :: wa_raft_log :log_pos ()} |
456476 {make_empty_snapshot , Path :: file :filename (), Position :: wa_raft_log :log_pos (), Config :: wa_raft_server :config (), Data :: term ()} |
457477 position |
@@ -475,6 +495,18 @@ handle_call({snapshot_create, Name}, _From, #state{last_applied = #raft_log_pos{
475495 {reply , Error , State }
476496 end ;
477497
498+ handle_call (snapshot_create_witness , From , # state {last_applied = # raft_log_pos {index = LastIndex , term = LastTerm }} = State ) ->
499+ Name = ? WITNESS_SNAPSHOT_NAME (LastIndex , LastTerm ),
500+ handle_call ({snapshot_create , Name }, From , State );
501+
502+ handle_call ({snapshot_create_witness , Name }, _From , # state {last_applied = LastApplied } = State ) ->
503+ case create_witness_snapshot_impl (Name , State ) of
504+ ok ->
505+ {reply , {ok , LastApplied }, State };
506+ {error , _ } = Error ->
507+ {reply , Error , State }
508+ end ;
509+
478510handle_call ({snapshot_open , SnapshotPath , LogPos }, _From , # state {name = Name , module = Module , handle = Handle , last_applied = LastApplied } = State ) ->
479511 ? LOG_NOTICE (" Storage[~0p ] replacing storage at ~0p with snapshot at ~0p ." , [Name , LastApplied , LogPos ], #{domain => [whatsapp , wa_raft ]}),
480512 case Module :storage_open_snapshot (SnapshotPath , LogPos , Handle ) of
@@ -637,6 +669,22 @@ create_snapshot_impl(SnapName, #state{name = Name, root_dir = RootDir, module =
637669 Module :storage_create_snapshot (SnapshotPath , Handle )
638670 end .
639671
672+ -spec create_witness_snapshot_impl (SnapName :: string (), Storage :: # state {}) -> ok | error ().
673+ create_witness_snapshot_impl (SnapName , # state {name = Name , root_dir = RootDir , module = Module , handle = Handle } = State ) ->
674+ SnapshotPath = filename :join (RootDir , SnapName ),
675+ case filelib :is_dir (SnapshotPath ) of
676+ true ->
677+ ? LOG_NOTICE (" Snapshot ~s for ~p already exists. Skipping witness snapshot creation." , [SnapName , Name ], #{domain => [whatsapp , wa_raft ]}),
678+ ok ;
679+ false ->
680+ cleanup_snapshots (State ),
681+ ? LOG_NOTICE (" Create witness snapshot ~s for ~p ." , [SnapName , Name ], #{domain => [whatsapp , wa_raft ]}),
682+ case erlang :function_exported (Module , storage_create_witness_snapshot , 2 ) of
683+ true -> Module :storage_create_witness_snapshot (SnapshotPath , Handle );
684+ false -> {error , not_supported }
685+ end
686+ end .
687+
640688-define (MAX_RETAINED_SNAPSHOT , 1 ).
641689
642690-spec cleanup_snapshots (# state {}) -> ok .
0 commit comments