Skip to content

Commit 11be6c1

Browse files
Sarah Hassanfacebook-github-bot
authored andcommitted
add optional storage callback for creating witness snapshots
Reviewed By: hsun324 Differential Revision: D72425041 fbshipit-source-id: e34c5378a659698efb67500d18009246c5547f19
1 parent b8c42ad commit 11be6c1

File tree

3 files changed

+76
-4
lines changed

3 files changed

+76
-4
lines changed

include/wa_raft.hrl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@
5151
-define(SNAPSHOT_PREFIX, "snapshot").
5252
%% Snapshot name
5353
-define(SNAPSHOT_NAME(Index, Term), (?SNAPSHOT_PREFIX "." ++ integer_to_list(Index) ++ "." ++ integer_to_list(Term))).
54+
55+
%% Witness Snapshot name
56+
-define(WITNESS_SNAPSHOT_NAME(Index, Term), (?SNAPSHOT_PREFIX "." ++ integer_to_list(Index) ++ "." ++ integer_to_list(Term) ++ ".witness")).
57+
5458
%% Location of a snapshot
5559
-define(RAFT_SNAPSHOT_PATH(Path, Name), (filename:join(Path, Name))).
5660
-define(RAFT_SNAPSHOT_PATH(Table, Partition, Name), ?RAFT_SNAPSHOT_PATH(?RAFT_PARTITION_PATH(Table, Partition), Name)).

src/wa_raft_storage.erl

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
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
]).
@@ -272,6 +274,14 @@
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) ->
358368
create_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().
362380
make_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+
478510
handle_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.

src/wa_raft_storage_ets.erl

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
storage_apply_config/3,
2323
storage_read/3,
2424
storage_create_snapshot/2,
25+
storage_create_witness_snapshot/2,
2526
storage_open_snapshot/3,
2627
storage_make_empty_snapshot/6
2728
]).
@@ -98,8 +99,17 @@ storage_apply({delete, _Table, Key}, Position, #state{storage = Storage} = State
9899
LogPos :: wa_raft_log:log_pos(),
99100
State :: #state{}
100101
) -> {ok | wa_raft_storage:error(), #state{}}.
101-
storage_apply_config(Config, LogPos, #state{storage = Storage} = State) ->
102-
true = ets:insert(Storage, [{{?METADATA_TAG, config}, {LogPos, Config}}, {?POSITION_TAG, LogPos}]),
102+
storage_apply_config(Config, LogPos, State) ->
103+
storage_apply_config(Config, LogPos, LogPos, State).
104+
105+
-spec storage_apply_config(
106+
Config :: wa_raft_server:config(),
107+
ConfigPos :: wa_raft_log:log_pos(),
108+
LogPos :: wa_raft_log:log_pos(),
109+
State :: #state{}
110+
) -> {ok | wa_raft_storage:error(), #state{}}.
111+
storage_apply_config(Config, ConfigPos, LogPos, #state{storage = Storage} = State) ->
112+
true = ets:insert(Storage, [{{?METADATA_TAG, config}, {ConfigPos, Config}}, {?POSITION_TAG, LogPos}]),
103113
{ok, State}.
104114

105115
-spec storage_read(Command :: wa_raft_acceptor:command(), Position :: wa_raft_log:log_pos(), State :: #state{}) -> ok | {ok, Value :: dynamic()} | not_found.
@@ -118,6 +128,12 @@ storage_create_snapshot(SnapshotPath, #state{storage = Storage}) ->
118128
{error, Reason} -> {error, Reason}
119129
end.
120130

131+
-spec storage_create_witness_snapshot(file:filename(), #state{}) -> ok | wa_raft_storage:error().
132+
storage_create_witness_snapshot(SnapshotPath, #state{name = Name, table = Table, partition = Partition} = State) ->
133+
{ok, ConfigPosition, Config} = storage_config(State),
134+
SnapshotPosition = storage_position(State),
135+
storage_make_empty_snapshot(Name, Table, Partition, SnapshotPath, SnapshotPosition, Config, ConfigPosition, #{}).
136+
121137
-spec storage_open_snapshot(file:filename(), wa_raft_log:log_pos(), #state{}) -> {ok, #state{}} | wa_raft_storage:error().
122138
storage_open_snapshot(SnapshotPath, SnapshotPosition, #state{storage = Storage} = State) ->
123139
SnapshotData = filename:join(SnapshotPath, ?SNAPSHOT_FILENAME),
@@ -136,8 +152,12 @@ storage_open_snapshot(SnapshotPath, SnapshotPosition, #state{storage = Storage}
136152
end.
137153

138154
-spec storage_make_empty_snapshot(atom(), #raft_identifier{}, file:filename(), wa_raft_log:log_pos(), wa_raft_server:config(), dynamic()) -> ok | wa_raft_storage:error().
139-
storage_make_empty_snapshot(Name, #raft_identifier{table = Table, partition = Partition}, SnapshotPath, SnapshotPosition, Config, _Data) ->
155+
storage_make_empty_snapshot(Name, #raft_identifier{table = Table, partition = Partition}, SnapshotPath, SnapshotPosition, Config, Data) ->
156+
storage_make_empty_snapshot(Name, Table, Partition, SnapshotPath, SnapshotPosition, Config, SnapshotPosition, Data).
157+
158+
-spec storage_make_empty_snapshot(atom(), wa_raft:table(), wa_raft:partition(), file:filename(), wa_raft_log:log_pos(), wa_raft_server:config(), wa_raft_log:log_pos(), dynamic()) -> ok | wa_raft_storage:error().
159+
storage_make_empty_snapshot(Name, Table, Partition, SnapshotPath, SnapshotPosition, Config, ConfigPosition, _Data) ->
140160
Storage = ets:new(Name, ?OPTIONS),
141161
State = #state{name = Name, table = Table, partition = Partition, storage = Storage},
142-
storage_apply_config(Config, SnapshotPosition, State),
162+
storage_apply_config(Config, ConfigPosition, SnapshotPosition, State),
143163
storage_create_snapshot(SnapshotPath, State).

0 commit comments

Comments
 (0)