Skip to content

Commit

Permalink
Update README and kvstore examples
Browse files Browse the repository at this point in the history
Summary: as title

Differential Revision: D59690854

fbshipit-source-id: 64289c6b560ea9e79ec2ee93b5511216bbfb96d7
  • Loading branch information
hsun324 authored and facebook-github-bot committed Jul 12, 2024
1 parent 9c91c0b commit 43126fa
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 64 deletions.
106 changes: 72 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,61 @@ WARaft is a Raft library in Erlang by WhatsApp. It provides an Erlang implementa

## Get Started

The following code snippet gives a quick glance about how WARaft works. It creates single node WARaft cluster and write a record.

The [example directory](https://github.com/WhatsApp/waraft/tree/main/examples/kvstore/src) contains a generic key-value store built on top of WARaft.
The following code snippet gives a quick glance about how WARaft works. It creates a single-node WARaft cluster and writes and reads a record.

```
% Cluster config - single node. table name test, partition 1
1> Spec = wa_raft_sup:child_spec([#{table => test, partition => 1, nodes => [node()]}]).
% Setup the WARaft application and the host application
rr(wa_raft_server).
application:ensure_all_started(wa_raft).
application:set_env(test_app, raft_database, ".").
% Create a spec for partition 1 of the RAFT table "test" and start it.
Spec = wa_raft_sup:child_spec(test_app, [#{table => test, partition => 1}]).
% Here we add WARaft to the kernel's supervisor, but you should place WARaft's
% child spec underneath your application's supervisor in a real deployment.
supervisor:start_child(kernel_sup, Spec).
% Check that the RAFT server started successfully
wa_raft_server:status(raft_server_test_1).
% Make a cluster configuration with the current node as the only member
Config = wa_raft_server:make_config([#raft_identity{name = raft_server_test_1, node = node()}]).
% Bootstrap the RAFT server by force-promoting it
wa_raft_server:promote(raft_server_test_1, 1, true, Config).
% Check that now the RAFT server is the leader
wa_raft_server:status(raft_server_test_1).
% Read and write against a key
wa_raft_acceptor:commit(raft_acceptor_test_1, {make_ref(), {write, test, key, 1000}}).
wa_raft_acceptor:read(raft_acceptor_test_1, {read, test, key}).
```

% Start raft processes under kernel_sup as supervisor. It's for demo purpose only. An app supervisor should be used for a real case
2> supervisor:start_child(kernel_sup, Spec).
{ok,<0.140.0>}
A typical output would look like the following:

% Check raft server status
3> wa_raft_server:status(raft_server_test_1).
```
1> % Setup the WARaft application and the host application
rr(wa_raft_server).
[raft_application,raft_identifier,raft_identity,raft_log,
raft_log_pos,raft_options,raft_state]
2> application:ensure_all_started(wa_raft).
{ok,[wa_raft]}
3> application:set_env(test_app, raft_database, ".").
ok
4> % Create a spec for partition 1 of the RAFT table "test" and start it.
Spec = wa_raft_sup:child_spec(test_app, [#{table => test, partition => 1}]).
#{id => wa_raft_sup,restart => permanent,shutdown => infinity,
start =>
{wa_raft_sup,start_link,
[test_app,[#{table => test,partition => 1}],#{}]},
type => supervisor,
modules => [wa_raft_sup]}
5> % Here we add WARaft to the kernel's supervisor, but you should place WARaft's
% child spec underneath your application's supervisor in a real deployment.
supervisor:start_child(kernel_sup, Spec).
{ok,<0.103.0>}
6> % Check that the RAFT server started successfully
wa_raft_server:status(raft_server_test_1).
[{state,stalled},
{id,nonode@nohost},
{table,test},
{partition,1},
{data_dir,"missing/test.1/"},
{data_dir,"./test.1"},
{current_term,0},
{voted_for,undefined},
{commit_index,0},
Expand All @@ -42,17 +79,23 @@ The [example directory](https://github.com/WhatsApp/waraft/tree/main/examples/kv
{votes,#{}},
{inflight_applies,0},
{disable_reason,undefined},
{config,#{version => 1}}]
% Promote current node as leader
4> wa_raft_server:promote(raft_server_test_1, 1, true, #{version => 1, membership => [{raft_server_test_1, node()}]}).
{config,#{version => 1}},
{config_index,0},
{witness,false}]
7> % Make a cluster configuration with the current node as the only member
Config = wa_raft_server:make_config([#raft_identity{name = raft_server_test_1, node = node()}]).
#{version => 1,
membership => [{raft_server_test_1,nonode@nohost}]}
8> % Bootstrap the RAFT server by force-promoting it
wa_raft_server:promote(raft_server_test_1, 1, true, Config).
ok
5> wa_raft_server:status(raft_server_test_1).
[{state,leader}, % leader node
9> % Check that now the RAFT server is the leader
wa_raft_server:status(raft_server_test_1).
[{state,leader},
{id,nonode@nohost},
{table,test},
{partition,1},
{data_dir,"missing/test.1/"},
{data_dir,"./test.1"},
{current_term,1},
{voted_for,undefined},
{commit_index,1},
Expand All @@ -66,24 +109,19 @@ ok
{votes,#{}},
{inflight_applies,0},
{disable_reason,undefined},
{config,#{membership => [{raft_server_test_1,nonode@nohost}],
version => 1}}]
% Write {key, 1000} to raft_server_test_1
6> wa_raft_acceptor:commit(raft_acceptor_test_1, {make_ref(), {write, test, key, 1000}}).
{ok, 2}
% Read key
7> wa_raft_acceptor:commit(raft_acceptor_test_1, {make_ref(), {read, test, key}}).
{ok,{1000,#{},2}}
% Stop raft
8> supervisor:terminate_child(kernel_sup, wa_raft_sup).
ok
9> supervisor:delete_child(kernel_sup, wa_raft_sup).
{config,#{version => 1,
membership => [{raft_server_test_1,nonode@nohost}]}},
{config_index,1},
{witness,false}]
10> % Read and write against a key
wa_raft_acceptor:commit(raft_acceptor_test_1, {make_ref(), {write, test, key, 1000}}).
ok
11> wa_raft_acceptor:read(raft_acceptor_test_1, {read, test, key}).
{ok,1000}
```

The [example directory](https://github.com/WhatsApp/waraft/tree/main/examples/kvstore/src) contains an example generic key-value store built on top of WARaft.

## License

WARaft is [Apache licensed](./LICENSE).
10 changes: 9 additions & 1 deletion examples/kvstore/src/kvstore.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
stdlib,
wa_raft
]},
{env, []},
{env, [
% Specify where you want your data to be stored here
{raft_database, "/mnt/kvstore"},
% Specify your own implementations here
{raft_log_module, wa_raft_log_ets},
{raft_storage_module, wa_raft_storage_ets},
{raft_distribution_module, wa_raft_distribution},
{raft_transport_module, wa_raft_transport}
]},
{mod, {kvstore_app, []}}
]}.
8 changes: 5 additions & 3 deletions examples/kvstore/src/kvstore_app.erl
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@
-module(kvstore_app).
-compile(warn_missing_spec_all).

-behaviour(application).

%% API
-export([
start/2,
stop/1
]).

-spec start(application:start_type(), term()) -> {ok, pid()} | {ok, pid(), State :: term()} | {error, Reason :: term()}.
-spec start(application:start_type(), term()) -> {ok, pid()}.
start(normal, _Args) ->
kvstore_sup:start_link().
{ok, _Pid} = kvstore_sup:start_link().

-spec stop(State) -> ok when State :: term().
-spec stop(term()) -> ok.
stop(_State) ->
ok.
25 changes: 12 additions & 13 deletions examples/kvstore/src/kvstore_client.erl
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,33 @@
delete/1
]).

-include_lib("kernel/include/logger.hrl").
-include_lib("wa_raft/include/wa_raft.hrl").

-define(CALL_TIMEOUT, 5000).
-define(TABLE, kvstore).
-define(NUM_PARTITIONS, 4).
-define(PARTITION(P), list_to_atom(lists:concat(["raft_acceptor_", ?TABLE, "_" , P]))).

%% Read value for a given key. It's a blocking call.
-spec read(term()) -> {ok, {term(), map(), number()}} | {error, term()}.
-spec read(term()) -> {ok, term()} | wa_raft_acceptor:read_error().
read(Key) ->
execute(Key, {read, ?TABLE, Key}).
Acceptor = ?RAFT_ACCEPTOR_NAME(?TABLE, partition(Key)),
wa_raft_acceptor:read(Acceptor, {read, ?TABLE, Key}, ?CALL_TIMEOUT).

%% Write a key/value pair to storage. It's a blocking call.
-spec write(term(), term()) -> {ok, number()} | {error, term()}.
-spec write(term(), term()) -> ok | wa_raft_acceptor:commit_error().
write(Key, Value) ->
execute(Key, {write, ?TABLE, Key, Value}).
commit(Key, {write, ?TABLE, Key, Value}).

%% Delete a key/value pair. It's a blocking call.
-spec delete(term()) -> ok | {error, term()}.
-spec delete(term()) -> ok | wa_raft_acceptor:commit_error().
delete(Key) ->
execute(Key, {delete, ?TABLE, Key}).
commit(Key, {delete, ?TABLE, Key}).

-spec execute(term(), term()) -> term().
execute(Key, Command) ->
Partition = ?PARTITION(partition(Key)),
gen_server:call(Partition, {commit, {make_ref(), Command}}, ?CALL_TIMEOUT).
-spec commit(term(), term()) -> term() | wa_raft_acceptor:commit_error().
commit(Key, Command) ->
Acceptor = ?RAFT_ACCEPTOR_NAME(?TABLE, partition(Key)),
wa_raft_acceptor:commit(Acceptor, {make_ref(), Command}, ?CALL_TIMEOUT).

-spec partition(term()) -> number().
partition(Key) ->
erlang:phash2(Key, ?NUM_PARTITIONS) + 1.

21 changes: 8 additions & 13 deletions examples/kvstore/src/kvstore_sup.erl
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,13 @@ start_link() ->
init([]) ->
Partitions = [1, 2, 3, 4],
Args = [raft_args(P) || P <- Partitions],
ChildSpecs = wa_raft_sup:child_spec(Args),
{ok, {#{}, [ChildSpecs]}}.
ChildSpecs = [
wa_raft_sup:child_spec(Args)
],
{ok, {#{}, ChildSpecs}}.

%% Return raft arguments for the provided partition
-spec raft_args(wa_raft:partition()) -> wa_raft:args().
% Construct a RAFT "args" for a partition.
-spec raft_args(Partition :: wa_raft:partition()) -> wa_raft:args().
raft_args(Partition) ->
#{
%% Table name and partition uniquely identify a RAFT partition
table => kvstore,
partition => Partition,
%% Use in-memory log (Implement your own to meet your needs)
log_module => wa_raft_log_ets,
%% Use in-memory local storage (Implement your own to meet your needs)
storage_module => wa_raft_storage_ets
}.
% RAFT clusters are primarily identified by their table and partition number
#{table => kvstore, partition => Partition}.

0 comments on commit 43126fa

Please sign in to comment.