Bear is a distributed state machine manager for Erlang/OTP. It provides a robust way to manage stateful processes across a cluster using consistent hashing for distribution and pes for process registry and discovery.
- Distributed State Management: Automatically distributes
gen_statemprocesses across the cluster. - Consistent Hashing: Ensures even distribution of processes and minimizes reshuffling when nodes leave or join.
- Automatic Handoff: Supports migrating processes between nodes during topology changes.
- Cluster Awareness: Integrated with
pesandsimple_gossipfor cluster membership and failure detection. - Load Balancing: Tools to drain nodes or rebalance handlers explicitly.
- Erlang/OTP 26 or later
- rebar3
To compile the project:
rebar3 compileTo create a release:
rebar3 releaseYou can start a distributed state machine using bear:start_link/3:
-module(my_handler).
-behaviour(bear).
%% API
-export([start/1, init/1, handle_event/4, terminate/3]).
start(Id) ->
bear:start_link(Id, ?MODULE, [Id]).
init([Id]) ->
{ok, handle_event_function, #{id => Id}}.
handle_event({call, From}, get_id, _StateName, Data) ->
{keep_state_and_data, [{reply, From, maps:get(id, Data)}]};
handle_event(_Event, _Content, _StateName, _Data) ->
keep_state_and_data.
terminate(_Reason, _State, _Data) ->
ok.Starts a new state machine on the cluster. The actual node is determined by consistent hashing of Id.
Id: Unique identifier for the state machine.Module: Callback module implementinggen_statembehavior (viabearbehavior).Args: Arguments passed toinit.
Sends a synchronous call to the state machine identified by Id.
Sends an asynchronous cast to the state machine identified by Id.
Stops the state machine identified by Id.
Triggers a manual redistribution of handlers across the cluster. Useful after adding new nodes.
Bear integrates with Riak (via rico) for state persistence. You can control when the state is saved or removed.
To save the current state to the persistent storage, return the atom save as one of the actions in your handle_event callback.
handle_event({call, From}, update_data, _StateName, Data) ->
NewData = update(Data),
{keep_state, NewData, [{reply, From, ok}, save]}.The return value of your terminate/3 callback determines whether the state persists after the process stops.
keep_data: The state remains in the persistent store. Next time the process starts for this ID, it will reload this state.- Any other value: The state is removed from the persistent store.
terminate(_Reason, _State, _Data) ->
keep_data. %% Persist stateRun the test suite using rebar3:
rebar3 testThis acts as an alias running ct (Common Test), proper (Property-based testing), and dialyzer.