Skip to content

Commit

Permalink
[#34] – Allows to register shards_sup with custom name (given as pa…
Browse files Browse the repository at this point in the history
…rameter)
  • Loading branch information
cabol committed Mar 19, 2017
1 parent 93aefe5 commit 190424d
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 82 deletions.
96 changes: 51 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Option | Description | Default
`{pick_shard_fun, pick_fun()}` | Function to pick the **shard** on which the `key` will be handled locally – used by `shards_local`. See [shards_state](./src/shards_state.erl). | `shards_local:pick/3`
`{pick_node_fun, pick_fun()}` | Function to pick the **node** on which the `key` will be handled globally/distributed – used by `shards_dist`. See [shards_state](./src/shards_state.erl). | `shards_local:pick/3`
`{nodes, [node()]}` | Allows to set a list of nodes to auto setup a distributed table – the table is created in all given nodes and then all nodes are joined. This option only has effect if the option `{scope, g}` has been set. | `[]`
`{sup_name, atom()}` | Allows to define a custom name for `shards_sup`. | `shards_sup`

> **NOTE:** By default `shards` uses a built-in functions to pick the **shard** (local scope)
and the **node** (distributed scope) on which the key will be handled. BUT you can override
Expand All @@ -98,12 +99,12 @@ You can get the **State** at any time you want:

```erlang
> shards_state:get(mytab1).
{state,shards_local,4,#Fun<shards_local.pick.3>,
{state,shards_local,shards_sup,4,#Fun<shards_local.pick.3>,
#Fun<shards_local.pick.3>}

% this is a wrapper on top of shards_state:get/1
> shards:state(mytab1).
{state,shards_local,4,#Fun<shards_local.pick.3>,
{state,shards_local,shards_sup,4,#Fun<shards_local.pick.3>,
#Fun<shards_local.pick.3>}
```

Expand Down Expand Up @@ -198,6 +199,7 @@ The `shards` state is defined as:
```erlang
-record(state, {
module = shards_local :: module(),
sup_name = shards_sup :: atom(),
n_shards = ?N_SHARDS :: pos_integer(),
pick_shard_fun = fun shards_local:pick/3 :: pick_fun(),
pick_node_fun = fun shards_local:pick/3 :: pick_fun()
Expand All @@ -224,54 +226,58 @@ Most of the `shards_local` functions receives the **State** as parameter, so it
to call it. You can check how `shards` module is implemented [HERE](./src/shards.erl).

If any microsecond matters to you, you can skip the call to the control ETS table by calling
`shards_local` directly. We have two options:
`shards_local` directly – there are two options to do it.

1. The first option is getting the `state`, and passing it as argument. Now the question is:
how to get the **State**? Well, it's extremely easy, you can get the `state` when you call
`shards:new/2` by first time, or you can call `shards:state/1`, `shards_state:get/1` or
`shards_state:new/0,1,2,3,4` at any time you want, and then it might be stored within
the calling process, or wherever you want. E.g.:
### Option 1

```erlang
% take a look at the 2nd element of the returned tuple, that is the state
> shards:new(mytab, [{n_shards, 4}]).
mytab
The first option is getting the `state`, and passing it as argument. Now the question is:
how to get the **State**? Well, it's extremely easy, you can get the `state` when you call
`shards:new/2` by first time, or you can call `shards:state/1`, `shards_state:get/1` or
`shards_state:new/0,1,2,3,4` at any time you want, and then it might be stored within
the calling process, or wherever you want. E.g.:

% remember you can get the state at any time you want
> State = shards:state(mytab).
{state,shards_local,4,#Fun<shards_local.pick.3>,
#Fun<shards_local.pick.3>}

% now you can call shards_local directly
> shards_local:insert(mytab, {1, 1}, State).
true
> shards_local:lookup(mytab, 1, State).
[{1,1}]

% in this case, only the n_shards is different from default, so you
% can do this:
> shards_local:lookup(mytab, 1, shards_state:new(4)).
[{1,1}]
```
```erlang
% take a look at the 2nd element of the returned tuple, that is the state
> shards:new(mytab, [{n_shards, 4}]).
mytab

2. The 2nd option is to call `shards_local` directly without the `state`, but this is only
possible if you have created a table with default `shards` optionssuch as `n_shards`,
`pick_shard_fun` and `pick_node_fun`. If you can take this option it might be significantly
better, since in this case no additional calls are needed, not even to recover the `state`
(like in the previous option), because a new `state` is created with default values.
Therefore, the call is mapped directly to an **ETS** function. E.g.:
% remember you can get the state at any time you want
> State = shards:state(mytab).
{state,shards_local,shards_sup,4,#Fun<shards_local.pick.3>,
#Fun<shards_local.pick.3>}

```erlang
% create a table without set n_shards, pick_shard_fun or pick_node_fun
> shards:new(mytab, []).
mytab
% now you can call shards_local directly
> shards_local:insert(mytab, {1, 1}, State).
true
> shards_local:lookup(mytab, 1, State).
[{1,1}]

% call shards_local without the state
> shards_local:insert(mytab, {1, 1}).
true
> shards_local:lookup(mytab, 1).
[{1,1}]
```
% in this case, only the n_shards is different from default, so you
% can do this:
> shards_local:lookup(mytab, 1, shards_state:new(4)).
[{1,1}]
```

### Option 2

The second option is to call `shards_local` directly without the `state`, but this is only
possible if you have created a table with default `shards` options – such as `n_shards`,
`pick_shard_fun` and `pick_node_fun`. If you can take this option it might be significantly
better, since in this case no additional calls are needed, not even to recover the `state`
(like in the previous option), because a new `state` is created with default values.
Therefore, the call is mapped directly to an **ETS** function. E.g.:

```erlang
% create a table without set n_shards, pick_shard_fun or pick_node_fun
> shards:new(mytab, []).
mytab

% call shards_local without the state
> shards_local:insert(mytab, {1, 1}).
true
> shards_local:lookup(mytab, 1).
[{1,1}]
```

Most of the cases this is not necessary, `shards` wrapper is more than enough, it adds only a
few microseconds of latency. In conclusion, **Shards** gives you the flexibility to do it,
Expand Down Expand Up @@ -406,7 +412,7 @@ And again, let's check it out from any node:

## Examples and/or Projects using Shards

* [ExShards](https://github.com/cabol/ex_shards) is an **Elixir** wrapper for `shards` with extra and nicer functions.
* [ExShards](https://github.com/cabol/ex_shards) **Elixir** wrapper for `shards` with extra and nicer functions.

* [KVX](https://github.com/cabol/kvx) – Simple/basic **Elixir** in-memory Key/Value Store using `shards` (default adapter).

Expand Down
16 changes: 13 additions & 3 deletions src/shards_local.erl
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ all() ->
%% @end
-spec delete(Tab :: atom()) -> true.
delete(Tab) ->
ok = shards_sup:terminate_child(shards_sup, get_pid(Tab)),
SupName = shards_state:sup_name(Tab),
ok = shards_sup:terminate_child(SupName, Tab),
true.

%% @equiv delete(Tab, Key, shards_state:new())
Expand Down Expand Up @@ -850,7 +851,16 @@ member(Tab, Key, State) ->
Name :: atom(),
Options :: [option()].
new(Name, Options) ->
case shards_sup:start_child([Name, Options]) of
case lists:keyfind(sup_name, 1, Options) of
{sup_name, SupName} ->
do_new(SupName, Name, Options);
false ->
do_new(shards_sup, Name, Options)
end.

%% @private
do_new(SupName, Name, Options) ->
case shards_sup:start_child(SupName, Name, Options) of
{ok, Pid} ->
true = register(Name, Pid),
Name;
Expand Down Expand Up @@ -881,7 +891,7 @@ next(Tab, Key1, State) ->
PickShardFun = shards_state:pick_shard_fun(State),
case PickShardFun(Key1, N, r) of
any ->
throw(bad_pick_fun_ret);
error(bad_pick_fun_ret);
Shard ->
ShardName = shard_name(Tab, Shard),
next_(Tab, ets:next(ShardName, Key1), Shard)
Expand Down
8 changes: 5 additions & 3 deletions src/shards_owner_sup.erl
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@
Options :: [term()],
Response :: supervisor:startlink_ret().
start_link(Name, Options) ->
supervisor:start_link(?MODULE, [Name, Options]).
supervisor:start_link(?MODULE, {Name, Options}).

%%%===================================================================
%%% Supervisor callbacks
%%%===================================================================

%% @hidden
init([Name, Options]) ->
init({Name, Options}) ->
% ETS table to store state info.
Name = ets:new(Name, [
set,
Expand Down Expand Up @@ -101,7 +101,7 @@ assert_unique_ids([]) ->
ok;
assert_unique_ids([Id | Rest]) ->
case lists:member(Id, Rest) of
true -> throw({badarg, duplicated_id});
true -> error(badarg);
_ -> assert_unique_ids(Rest)
end.

Expand All @@ -126,6 +126,8 @@ parse_opts([{scope, l} | Opts], Acc) ->
parse_opts(Opts, Acc#{module := shards_local});
parse_opts([{scope, g} | Opts], Acc) ->
parse_opts(Opts, Acc#{module := shards_dist});
parse_opts([{sup_name, SupName} | Opts], Acc) when is_atom(SupName) ->
parse_opts(Opts, Acc#{sup_name := SupName});
parse_opts([{n_shards, N} | Opts], Acc) when is_integer(N), N > 0 ->
parse_opts(Opts, Acc#{n_shards := N});
parse_opts([{pick_shard_fun, Val} | Opts], Acc) when is_function(Val) ->
Expand Down
40 changes: 34 additions & 6 deletions src/shards_state.erl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
new/2,
new/3,
new/4,
new/5,
to_map/1,
from_map/1
]).
Expand All @@ -22,6 +23,8 @@
-export([
module/1,
module/2,
sup_name/1,
sup_name/2,
n_shards/1,
n_shards/2,
pick_shard_fun/1,
Expand Down Expand Up @@ -69,6 +72,7 @@
%% State definition
-record(state, {
module = shards_local :: module(),
sup_name = shards_sup :: atom(),
n_shards = ?N_SHARDS :: pos_integer(),
pick_shard_fun = fun shards_local:pick/3 :: pick_fun(),
pick_node_fun = fun shards_local:pick/3 :: pick_fun()
Expand All @@ -81,6 +85,7 @@

%% @type state_map() = #{
%% module => module(),
%% sup_name => atom(),
%% n_shards => pos_integer(),
%% pick_shard_fun => pick_fun(),
%% pick_node_fun => pick_fun()
Expand All @@ -90,12 +95,14 @@
%% <ul>
%% <li>`module': Module to be used depending on the `scope':
%% `shards_local' or `shards_dist'.</li>
%% <li>`sup_name': Registered name for `shards_sup'.</li>
%% <li>`n_shards': Number of ETS shards/fragments.</li>
%% <li>`pick_shard_fun': Function callback to pick/compute the shard.</li>
%% <li>`pick_node_fun': Function callback to pick/compute the node.</li>
%% </ul>
-type state_map() :: #{
module => module(),
sup_name => atom(),
n_shards => pos_integer(),
pick_shard_fun => pick_fun(),
pick_node_fun => pick_fun()
Expand Down Expand Up @@ -128,15 +135,24 @@ new(Shards) ->
new(Shards, Module) ->
#state{n_shards = Shards, module = Module}.

-spec new(pos_integer(), module(), pick_fun()) -> state().
new(Shards, Module, PickShardFun) ->
#state{n_shards = Shards, module = Module, pick_shard_fun = PickShardFun}.
-spec new(pos_integer(), module(), atom()) -> state().
new(Shards, Module, SupName) ->
#state{n_shards = Shards, module = Module, sup_name = SupName}.

-spec new(pos_integer(), module(), pick_fun(), pick_fun()) -> state().
new(Shards, Module, PickShardFun, PickNodeFun) ->
-spec new(pos_integer(), module(), atom(), pick_fun()) -> state().
new(Shards, Module, SupName, PickShardFun) ->
#state{
n_shards = Shards,
module = Module,
sup_name = SupName,
pick_shard_fun = PickShardFun}.

-spec new(pos_integer(), module(), atom(), pick_fun(), pick_fun()) -> state().
new(Shards, Module, SupName, PickShardFun, PickNodeFun) ->
#state{
n_shards = Shards,
module = Module,
sup_name = SupName,
pick_shard_fun = PickShardFun,
pick_node_fun = PickNodeFun}.

Expand All @@ -146,6 +162,7 @@ new(Shards, Module, PickShardFun, PickNodeFun) ->
-spec to_map(state()) -> state_map().
to_map(State) ->
#{module => State#state.module,
sup_name => State#state.sup_name,
n_shards => State#state.n_shards,
pick_shard_fun => State#state.pick_shard_fun,
pick_node_fun => State#state.pick_node_fun}.
Expand All @@ -157,6 +174,7 @@ to_map(State) ->
from_map(Map) ->
#state{
module = maps:get(module, Map, shards_local),
sup_name = maps:get(sup_name, Map, shards_sup),
n_shards = maps:get(n_shards, Map, ?N_SHARDS),
pick_shard_fun = maps:get(pick_shard_fun, Map, fun shards_local:pick/3),
pick_node_fun = maps:get(pick_node_fun, Map, fun shards_local:pick/3)}.
Expand All @@ -168,7 +186,7 @@ from_map(Map) ->
get(Tab) when is_atom(Tab) ->
case ets:lookup(Tab, state) of
[State] -> State;
_ -> throw({badarg, Tab})
_ -> error(badarg)
end.

%%%===================================================================
Expand All @@ -185,6 +203,16 @@ module(Tab) when is_atom(Tab) ->
module(Module, #state{} = State) when is_atom(Module) ->
State#state{module = Module}.

-spec sup_name(state() | atom()) -> atom().
sup_name(#state{sup_name = SupName}) ->
SupName;
sup_name(Tab) when is_atom(Tab) ->
sup_name(?MODULE:get(Tab)).

-spec sup_name(atom(), state()) -> state().
sup_name(SupName, #state{} = State) when is_atom(SupName) ->
State#state{sup_name = SupName}.

-spec n_shards(state() | atom()) -> pos_integer().
n_shards(#state{n_shards = Shards}) ->
Shards;
Expand Down
Loading

0 comments on commit 190424d

Please sign in to comment.