Skip to content

Commit

Permalink
Merge pull request #3163 from esl/cache_users
Browse files Browse the repository at this point in the history
Create a configurable FIFO cache
  • Loading branch information
chrzaszcz authored Jul 2, 2021
2 parents 2bb9c77 + 405b6fc commit f8218b2
Show file tree
Hide file tree
Showing 19 changed files with 379 additions and 147 deletions.
9 changes: 6 additions & 3 deletions big_tests/tests/mam_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -3068,9 +3068,12 @@ check_user_exist(Config) ->
ok = rpc(mim(), ejabberd_auth, try_register, [JID, AdminP]),
%% admin user already registered
{ok, HostType} = rpc(mim(), mongoose_domain_core, get_host_type, [AdminS]),
true = rpc(mim(), mongoose_users, does_user_exist, [HostType, JID]),
false = rpc(mim(), mongoose_users, does_user_exist, [HostType, mongoose_helper:make_jid(<<"fake-user">>, AdminS)]),
false = rpc(mim(), mongoose_users, does_user_exist, [HostType, mongoose_helper:make_jid(AdminU, <<"fake-domain">>)]),
true = rpc(mim(), ejabberd_auth, does_user_exist,
[HostType, JID, stored]),
false = rpc(mim(), ejabberd_auth, does_user_exist,
[HostType, mongoose_helper:make_jid(<<"fake-user">>, AdminS), stored]),
false = rpc(mim(), ejabberd_auth, does_user_exist,
[HostType, mongoose_helper:make_jid(AdminU, <<"fake-domain">>), stored]),
%% cleanup
ok = rpc(mim(), ejabberd_auth, remove_user, [JID]).

Expand Down
1 change: 1 addition & 0 deletions doc/developers-guide/hooks_description.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ This is the perfect place to plug in custom security control.
* disco_sm_features
* disco_sm_identity
* disco_sm_items
* does_user_exist
* ejabberd_ctl_process
* empty
* failed_to_store_message
Expand Down
5 changes: 5 additions & 0 deletions doc/migrations/4.2.0_4.3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,8 @@ or metrics reporters as described in [Logging and monitoring](../operation-and-m

In each endpoint, `host` has been changed to `host_type`.
This is because the metrics are now collected per host type rather than host.


## Users cache

MongooseIM used to feature a cache to check whether a user exists, that was unavoidably enabled, and had no eviction policy, that is, the cache could continue growing forever. Now, MIM features a module called [`mod_cache_users`](../modules/mod_cache_users) that implements a configurable cache policy, that can be enabled, disabled, and parametrised, per `host_type`. This might not be enabled by default in your configuration, so we recommend you verify your configuration and enable it if needed.
33 changes: 33 additions & 0 deletions doc/modules/mod_cache_users.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Module Description

`mod_cache_users` is a module that caches whether a user exists. This is useful for example to decide if a message should be stored in [MAM] or [Inbox] — for example, the receiver might not exist, so no message should be stored in his archive nor his inbox.

This cache has a coarse-grained FIFO strategy, that is, it keeps a set of ETS tables that are periodically rotated, and on rotation, the last table is cleared. Records are simply inserted in the first table and aren't moved afterwards, only the table order is.


## Options

### `modules.mod_cache_users.time_to_live`
* **Syntax:** integer, in minutes, or the string `"infinity"`
* **Default:** `8 * 60` (8h)
* **Example:** `time_to_live = 480`

Time between rotations, that is, the time a single table will live. A record that is inserted in the first table will live as long as this ttl multiplied by the number of tables.

### `modules.mod_cache_users.number_of_segments`
* **Syntax:** integer
* **Default:** `3`
* **Example:** `number_of_segments = 3`

Number of segments the cache has. The more segments there are, the more fine-grained the cache can be, but the slower queries will be: query the cache checks the tables in order until a match is found.

## Example configuration

```toml
[modules.mod_cache_users]
time_to_live = 60
number_of_segments = 1
```

[MAM]: ../mod_mam
[Inbox]: ../mod_inbox
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ nav:
- 'mod_blocking': 'modules/mod_blocking.md'
- 'mod_bosh': 'modules/mod_bosh.md'
- 'mod_caps': 'modules/mod_caps.md'
- 'mod_cache_users': 'modules/mod_cache_users.md'
- 'mod_carboncopy': 'modules/mod_carboncopy.md'
- 'mod_commands': 'modules/mod_commands.md'
- 'mod_csi': 'modules/mod_csi.md'
Expand Down
4 changes: 4 additions & 0 deletions rel/files/mongooseim.toml
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
users_can_see_hidden_services = false

[modules.mod_commands]
{{#mod_cache_users}}

[modules.mod_cache_users]{{{mod_cache_users}}}
{{/mod_cache_users}}

[modules.mod_muc_commands]

Expand Down
4 changes: 4 additions & 0 deletions rel/mim1.vars-toml.config
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
auth.methods = [\"dummy\"]
auth.dummy.base_time = 1
auth.dummy.variance = 5
[host_config.modules.mod_cache_users]
[host_config.modules.mod_carboncopy]
[host_config.modules.mod_stream_management]
[host_config.modules.mod_disco]
Expand Down Expand Up @@ -72,6 +73,9 @@

{mod_amp, "[modules.mod_amp]"}.
{mod_private, "[modules.mod_private]"}.
{mod_cache_users, "
time_to_live = 2
number_of_segments = 5"}.
{zlib, "10_000"}.
{c2s_dhfile, "\"priv/ssl/fake_dh_server.pem\""}.
{s2s_dhfile, "\"priv/ssl/fake_dh_server.pem\""}.
2 changes: 2 additions & 0 deletions rel/mim2.vars-toml.config
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,5 @@
host_type = \"test type\"
modules = { }
auth = { methods = [\"dummy\"] }"}.

{mod_cache_users, false}.
2 changes: 2 additions & 0 deletions rel/mim3.vars-toml.config
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,5 @@

{c2s_dhfile, "\"priv/ssl/fake_dh_server.pem\""}.
{s2s_dhfile, "\"priv/ssl/fake_dh_server.pem\""}.

{mod_cache_users, false}.
40 changes: 35 additions & 5 deletions src/auth/ejabberd_auth.erl
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
get_password_s/1,
get_passterm_with_authmodule/2,
does_user_exist/1,
does_user_exist/3,
does_stored_user_exist/2,
does_method_support/2,
remove_user/1,
Expand All @@ -61,15 +62,18 @@

%% Hook handlers
-export([remove_domain/3]).
-export([does_user_exist/4]).

-include("mongoose.hrl").
-include("jlib.hrl").

-export_type([authmodule/0,
passterm/0]).
passterm/0,
exist_type/0]).

-type authmodule() :: module().
-type passterm() :: binary() | mongoose_scram:scram_tuple() | mongoose_scram:scram_map().
-type exist_type() :: stored | with_anonymous.

%% Types defined below are used in call_auth_modules_*
-type mod_res() :: any().
Expand All @@ -94,16 +98,24 @@ start(HostType) ->
ensure_metrics(HostType),
F = fun(Mod) -> mongoose_gen_auth:start(Mod, HostType) end,
call_auth_modules_for_host_type(HostType, F, #{op => map}),
ejabberd_hooks:add(remove_domain, HostType, ?MODULE, remove_domain, 50),
ejabberd_hooks:add(hooks(HostType)),
ok.

-spec stop(HostType :: mongooseim:host_type()) -> 'ok'.
stop(HostType) ->
ejabberd_hooks:delete(remove_domain, HostType, ?MODULE, remove_domain, 50),
ejabberd_hooks:delete(hooks(HostType)),
F = fun(Mod) -> mongoose_gen_auth:stop(Mod, HostType) end,
call_auth_modules_for_host_type(HostType, F, #{op => map}),
ok.

hooks(HostType) ->
[
%% These hooks must run in between those of mod_cache_users
{does_user_exist, HostType, ?MODULE, does_user_exist, 50},
%% It is important that this handler happens _before_ all other modules
{remove_domain, HostType, ?MODULE, remove_domain, 10}
].

-spec set_opts(HostType :: mongooseim:host_type(),
KVs :: [tuple()]) -> {atomic|aborted, _}.
set_opts(HostType, KVs) ->
Expand Down Expand Up @@ -302,8 +314,8 @@ get_passterm_with_authmodule(HostType, #jid{luser = LUser, lserver = LServer}) -
end,
call_auth_modules_for_host_type(HostType, F, #{default => false}).

%% @doc Returns true if the user exists in the DB or if an anonymous user is
%% logged under the given name
%% @doc Returns true if the user exists in the DB
%% or if an anonymous user is logged under the given name
%% Returns 'false' in case of an error
-spec does_user_exist(JID :: jid:jid() | error) -> boolean().
does_user_exist(#jid{luser = LUser, lserver = LServer}) ->
Expand All @@ -316,6 +328,24 @@ does_user_exist(#jid{luser = LUser, lserver = LServer}) ->
does_user_exist(error) ->
false.

%% Hook interface
-spec does_user_exist(mongooseim:host_type(), jid:jid(), exist_type()) -> boolean().
does_user_exist(HostType, Jid, RequestType) ->
mongoose_hooks:does_user_exist(HostType, Jid, RequestType).

%% @doc does_user_exist hook handler
%% Returns 'false' in case of an error
-spec does_user_exist(boolean(), mongooseim:host_type(), jid:jid(), exist_type()) -> boolean().
does_user_exist(false, HostType, Jid, stored) ->
true =:= does_stored_user_exist(HostType, Jid);
does_user_exist(false, HostType, #jid{luser = LUser, lserver = LServer}, with_anonymous) ->
F = fun(Mod) ->
does_user_exist_in_module(HostType, LUser, LServer, Mod)
end,
call_auth_modules_for_host_type(HostType, F, #{default => false});
does_user_exist(Status, _, _, _) ->
Status.

%% @doc Returns true if the user exists in the DB
%% In case of a backend error, it is propagated to the caller
-spec does_stored_user_exist(mongooseim:host_type(), jid:jid() | error) ->
Expand Down
1 change: 1 addition & 0 deletions src/config/mongoose_config_spec.erl
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,7 @@ configurable_modules() ->
[mod_adhoc,
mod_auth_token,
mod_bosh,
mod_cache_users,
mod_caps,
mod_carboncopy,
mod_csi,
Expand Down
2 changes: 0 additions & 2 deletions src/ejabberd_app.erl
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ start(normal, _Args) ->
%% but some outgoing_pools should be started only with ejabberd_sup already running
ejabberd_sm:start(),
ejabberd_rdbms:start(),
lists:foreach(fun mongoose_users:start/1, ?ALL_HOST_TYPES),
ejabberd_auth:start(),
mongoose_cluster_id:start(),
start_services(),
Expand All @@ -88,7 +87,6 @@ prep_stop(State) ->
stop_modules(),
stop_services(),
broadcast_c2s_shutdown(),
lists:foreach(fun mongoose_users:stop/1, ?ALL_HOST_TYPES),
mongoose_wpool:stop(),
mongoose_metrics:remove_all_metrics(),
State.
Expand Down
2 changes: 1 addition & 1 deletion src/event_pusher/mod_event_pusher_push_plugin_defaults.erl
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ publish_notification(Acc, _, Payload, Services) ->
-spec should_publish(Acc :: mongoose_acc:t(), To :: jid:jid()) -> boolean().
should_publish(Acc, #jid{} = To) ->
HostType = mongoose_acc:host_type(Acc),
try mongoose_users:does_user_exist(HostType, To) of
try ejabberd_auth:does_user_exist(HostType, To, stored) of
false ->
false;
true ->
Expand Down
4 changes: 2 additions & 2 deletions src/inbox/mod_inbox.erl
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,10 @@ maybe_process_message(Acc, From, To, Msg, Dir) ->
Dir :: outgoing | incoming) -> boolean().
inbox_owner_exists(Acc, From, _To, outgoing) ->
HostType = mongoose_acc:host_type(Acc),
mongoose_users:does_user_exist(HostType, From);
ejabberd_auth:does_user_exist(HostType, From, stored);
inbox_owner_exists(Acc, _From, To, incoming) ->
HostType = mongoose_acc:host_type(Acc),
mongoose_users:does_user_exist(HostType, To).
ejabberd_auth:does_user_exist(HostType, To, stored).

maybe_process_acceptable_message(HostType, From, To, Msg, Acc, Dir, one2one) ->
process_message(HostType, From, To, Msg, Acc, Dir, one2one);
Expand Down
2 changes: 1 addition & 1 deletion src/mam/mod_mam.erl
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ filter_packet({From, To, Acc1, Packet}) ->
?LOG_DEBUG(#{what => mam_user_receive_packet, acc => Acc1}),
HostType = mongoose_acc:host_type(Acc1),
{AmpEvent, PacketAfterArchive, Acc3} =
case mongoose_users:does_user_exist(HostType, To) of
case ejabberd_auth:does_user_exist(HostType, To, stored) of
false ->
{mam_failed, Packet, Acc1};
true ->
Expand Down
Loading

0 comments on commit f8218b2

Please sign in to comment.