-
Notifications
You must be signed in to change notification settings - Fork 426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Alternative backend to Mnesia - CETS #3629
Changes from all commits
5cceb40
48e5efc
b516753
92d5f4e
02d5d30
c52fb6f
176ee0d
460fd95
f34c209
e5ade2e
7210543
f5010ea
067b0d3
c4353a6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
mongooseim@localhost | ||
ejabberd2@localhost | ||
mongooseim3@localhost |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
-module(ejabberd_sm_cets). | ||
|
||
-behavior(ejabberd_sm_backend). | ||
|
||
-include("mongoose.hrl"). | ||
-include("session.hrl"). | ||
|
||
-export([init/1, | ||
get_sessions/0, | ||
get_sessions/1, | ||
get_sessions/2, | ||
get_sessions/3, | ||
create_session/4, | ||
update_session/4, | ||
delete_session/4, | ||
cleanup/1, | ||
total_count/0, | ||
unique_count/0]). | ||
|
||
-define(TABLE, cets_session). | ||
|
||
-spec init(map()) -> any(). | ||
init(_Opts) -> | ||
cets:start(?TABLE, #{}), | ||
cets_discovery:add_table(mongoose_cets_discovery, ?TABLE). | ||
|
||
-spec get_sessions() -> [ejabberd_sm:session()]. | ||
get_sessions() -> | ||
tuples_to_sessions(ets:tab2list(?TABLE)). | ||
|
||
-spec get_sessions(jid:lserver()) -> [ejabberd_sm:session()]. | ||
get_sessions(Server) -> | ||
%% This is not a full table scan. From the ETS docs: | ||
%% For ordered_set a partially bound key will limit the traversal to only | ||
%% scan a subset of the table based on term order. | ||
%% A partially bound key is either a list or a tuple with | ||
%% a prefix that is fully bound. | ||
R = {{Server, '_', '_', '_'}, '_', '_'}, | ||
Xs = ets:select(?TABLE, [{R, [], ['$_']}]), | ||
tuples_to_sessions(Xs). | ||
|
||
-spec get_sessions(jid:luser(), jid:lserver()) -> [ejabberd_sm:session()]. | ||
get_sessions(User, Server) -> | ||
R = {{Server, User, '_', '_'}, '_', '_'}, | ||
Xs = ets:select(?TABLE, [{R, [], ['$_']}]), | ||
tuples_to_sessions(Xs). | ||
|
||
-spec get_sessions(jid:luser(), jid:lserver(), jid:lresource()) -> | ||
[ejabberd_sm:session()]. | ||
get_sessions(User, Server, Resource) -> | ||
R = {{Server, User, Resource, '_'}, '_', '_'}, | ||
Xs = ets:select(?TABLE, [{R, [], ['$_']}]), | ||
%% TODO these sessions should be deduplicated. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have this issue in Mnesia? Or would it just throw 'running inconsistent database' in this case? I think it would only occur when rejoining after netsplits, because a fresh new node shouldn't have any sessions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very similar in Mnesia: if user reconnects with the same resource name, we send kick to the old session, but that is an async operation, so we could have two records in the table. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably the best solution would be to write a property based test for this module (just test using the module API, without escalus and c2s logic). We could find some interesting edge cases or at least we would figure out a set of properties the session API has. But to be honest, that PR was just to copy what ejabberd_sm_mnesia does with minimal changes, so we can see the usage of cets API comparing to mnesia API. |
||
%% It is possible, that after merging two cets tables we could end up | ||
%% with sessions from two nodes for the same full jid. | ||
%% One of the sessions must be killed. | ||
%% We can detect duplicates on the merging step or on reading (or both). | ||
tuples_to_sessions(Xs). | ||
|
||
-spec create_session(User :: jid:luser(), | ||
Server :: jid:lserver(), | ||
Resource :: jid:lresource(), | ||
Session :: ejabberd_sm:session()) -> ok | {error, term()}. | ||
create_session(User, Server, Resource, Session) -> | ||
case get_sessions(User, Server, Resource) of | ||
[] -> | ||
cets:insert(?TABLE, session_to_tuple(Session)); | ||
Sessions when is_list(Sessions) -> | ||
%% TODO merge_info function would be removed, once MIM-1875 is done | ||
MergedSession = mongoose_session:merge_info | ||
(Session, hd(lists:sort(Sessions))), | ||
cets:insert(?TABLE, session_to_tuple(MergedSession)) | ||
end. | ||
|
||
-spec update_session(User :: jid:luser(), | ||
Server :: jid:lserver(), | ||
Resource :: jid:lresource(), | ||
Session :: ejabberd_sm:session()) -> ok | {error, term()}. | ||
update_session(_User, _Server, _Resource, Session) -> | ||
cets:insert(?TABLE, session_to_tuple(Session)). | ||
|
||
-spec delete_session(ejabberd_sm:sid(), | ||
User :: jid:luser(), | ||
Server :: jid:lserver(), | ||
Resource :: jid:lresource()) -> ok. | ||
delete_session(SID, User, Server, Resource) -> | ||
cets:delete(?TABLE, make_key(User, Server, Resource, SID)). | ||
|
||
%% cleanup is called on each node in the cluster, when Node is down | ||
-spec cleanup(atom()) -> any(). | ||
cleanup(Node) -> | ||
KeyPattern = {'_', '_', '_', {'_', '$1'}}, | ||
Guard = {'==', {node, '$1'}, Node}, | ||
R = {KeyPattern, '_', '_'}, | ||
cets:sync(?TABLE), | ||
%% This is a full table scan, but cleanup is rare. | ||
Tuples = ets:select(?TABLE, [{R, [Guard], ['$_']}]), | ||
lists:foreach(fun({Key, _, _} = Tuple) -> | ||
Session = tuple_to_session(Tuple), | ||
ejabberd_sm:run_session_cleanup_hook(Session) | ||
end, Tuples), | ||
%% We don't need to replicate deletes | ||
%% We remove the local content here | ||
ets:select_delete(?TABLE, [{R, [Guard], [true]}]). | ||
|
||
-spec total_count() -> integer(). | ||
total_count() -> | ||
ets:info(?TABLE, size). | ||
|
||
%% Counts merged by US | ||
-spec unique_count() -> integer(). | ||
unique_count() -> | ||
compute_unique(ets:first(?TABLE), 0). | ||
|
||
compute_unique('$end_of_table', Sum) -> | ||
Sum; | ||
compute_unique({S, U, _, _} = Key, Sum) -> | ||
Key2 = ets:next(?TABLE, Key), | ||
case Key2 of | ||
{S, U, _, _} -> | ||
compute_unique(Key2, Sum); | ||
_ -> | ||
compute_unique(Key2, Sum + 1) | ||
end. | ||
|
||
session_to_tuple(#session{sid = SID, usr = {U, S, R}, priority = Prio, info = Info}) -> | ||
{make_key(U, S, R, SID), Prio, Info}. | ||
|
||
make_key(User, Server, Resource, SID) -> | ||
{Server, User, Resource, SID}. | ||
|
||
tuple_to_session({{S, U, R, SID}, Prio, Info}) -> | ||
#session{sid = SID, usr = {U, S, R}, us = {U, S}, priority = Prio, info = Info}. | ||
|
||
tuples_to_sessions(Xs) -> | ||
[tuple_to_session(X) || X <- Xs]. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -153,8 +153,16 @@ init([]) -> | |
{pg, | ||
{pg, start_link, [mim_scope]}, | ||
permanent, infinity, supervisor, [pg]}, | ||
ConfigDir = filename:dirname(mongoose_config:get_config_path()), | ||
DiscoFile = filename:join(ConfigDir, "cets_disco.txt"), | ||
DiscoOpts = #{name => mongoose_cets_discovery, disco_file => DiscoFile}, | ||
CetsDisco = | ||
{cets_discovery, | ||
{cets_discovery, start_link, [DiscoOpts]}, | ||
permanent, infinity, supervisor, [cets_discovery]}, | ||
{ok, {{one_for_one, 10, 1}, | ||
[PG, | ||
[CetsDisco, | ||
PG, | ||
Comment on lines
+156
to
+165
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's ok to have it temporarily in a feature branch, but I don't think it should be enabled by default - I would make it configurable with a new RDBMS discovery backend, but we could do it later of course. |
||
Hooks, | ||
Cleaner, | ||
SMBackendSupervisor, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A preset like
pgsql_cets
might make more sense, but we could change it separately in the feature branch.