Files
fluxer/fluxer_gateway/test/push_ets_cache_stress_tests.erl
T

125 lines
3.8 KiB
Erlang

%% SPDX-License-Identifier: AGPL-3.0-or-later
-module(push_ets_cache_stress_tests).
-typing([eqwalizer]).
-include_lib("eunit/include/eunit.hrl").
-define(USER_COUNT, 10000).
-define(CONCURRENT_WRITERS, 8).
-define(WRITES_PER_WORKER, 2000).
get_subscriptions_many_large_mixed_set_test_() ->
{timeout, 15, fun get_subscriptions_many_large_mixed_set/0}.
concurrent_subscription_reads_and_writes_keep_cache_consistent_test_() ->
{timeout, 15, fun concurrent_subscription_reads_and_writes_keep_cache_consistent/0}.
get_subscriptions_many_large_mixed_set() ->
cleanup_tables(),
ok = push_ets_cache:init(),
try
lists:foreach(fun put_subscription_for_even_user/1, lists:seq(1, ?USER_COUNT)),
{Cached, Missing} = push_ets_cache:get_subscriptions_many(lists:seq(1, ?USER_COUNT)),
?assertEqual(?USER_COUNT div 2, map_size(Cached)),
?assertEqual(?USER_COUNT div 2, length(Missing)),
?assertEqual([#{endpoint => <<"endpoint-2">>}], maps:get(2, Cached)),
?assertEqual(true, lists:member(1, Missing)),
?assertEqual(false, lists:member(2, Missing))
after
cleanup_tables()
end.
concurrent_subscription_reads_and_writes_keep_cache_consistent() ->
cleanup_tables(),
ok = push_ets_cache:init(),
Ref = make_ref(),
Parent = self(),
try
_Writers = spawn_writers(Ref, Parent),
_Readers = spawn_readers(Ref, Parent),
collect_done(Ref, writer_done, ?CONCURRENT_WRITERS),
collect_done(Ref, reader_done, ?CONCURRENT_WRITERS),
ExpectedSize = ?CONCURRENT_WRITERS * ?WRITES_PER_WORKER,
?assertEqual(ExpectedSize, push_ets_cache:table_size(push_subscriptions)),
{Cached, Missing} = push_ets_cache:get_subscriptions_many(lists:seq(1, ExpectedSize)),
?assertEqual(ExpectedSize, map_size(Cached)),
?assertEqual([], Missing)
after
cleanup_tables()
end.
put_subscription_for_even_user(UserId) when UserId rem 2 =:= 0 ->
ok = push_ets_cache:put_subscriptions(UserId, [#{endpoint => endpoint(UserId)}]);
put_subscription_for_even_user(_UserId) ->
ok.
spawn_writers(Ref, Parent) ->
[
spawn(fun() ->
writer_loop(WriterIndex, ?WRITES_PER_WORKER),
Parent ! {Ref, writer_done}
end)
|| WriterIndex <- lists:seq(0, ?CONCURRENT_WRITERS - 1)
].
writer_loop(WriterIndex, Count) ->
Start = WriterIndex * Count + 1,
End = Start + Count - 1,
lists:foreach(
fun(UserId) ->
ok = push_ets_cache:put_subscriptions(UserId, [#{endpoint => endpoint(UserId)}])
end,
lists:seq(Start, End)
).
spawn_readers(Ref, Parent) ->
[
spawn(fun() ->
reader_loop(ReaderIndex),
Parent ! {Ref, reader_done}
end)
|| ReaderIndex <- lists:seq(0, ?CONCURRENT_WRITERS - 1)
].
reader_loop(ReaderIndex) ->
Offset = ReaderIndex * 100,
lists:foreach(
fun(Iteration) ->
Start = 1 + ((Offset + Iteration * 97) rem ?USER_COUNT),
_ = push_ets_cache:get_subscriptions_many(lists:seq(Start, Start + 99)),
ok
end,
lists:seq(1, 100)
).
collect_done(_Ref, _Message, 0) ->
ok;
collect_done(Ref, Message, Remaining) ->
receive
{Ref, Message} ->
collect_done(Ref, Message, Remaining - 1)
after 5000 ->
?assert(false)
end.
endpoint(UserId) ->
UserIdBin = integer_to_binary(UserId),
<<"endpoint-", UserIdBin/binary>>.
cleanup_tables() ->
delete_table(push_user_guild_settings),
delete_table(push_subscriptions),
delete_table(push_blocked_ids),
delete_table(push_badge_counts),
ok.
delete_table(Table) ->
try ets:delete(Table) of
_ -> ok
catch
throw:_ -> ok;
error:_ -> ok;
exit:_ -> ok
end.