perf(gateway): speed up presence and harden guild queries (#3072)

This commit is contained in:
Hampus
2026-09-30 21:12:13 +02:00
committed by GitHub
parent 6e2f90b03c
commit ab0b483fbe
44 changed files with 2166 additions and 144 deletions
@@ -118,6 +118,7 @@ init_guilds_role_includes_only_guild_state_children_test() ->
?assert(lists:member(presence_bus, Ids)),
?assert(lists:member(guild_manager, Ids)),
?assert(lists:member(guild_counts_cache, Ids)),
?assert(lists:member(gateway_clock_offset, Ids)),
?assert(lists:member(voice_state_counts_sync, Ids)),
?assert(lists:member(gateway_cluster_handoff, Ids)),
?assertNot(lists:member(session_manager, Ids)),
@@ -137,6 +138,7 @@ init_sessions_role_includes_cluster_handoff_test() ->
?assert(lists:member(session_state_transfer, Ids)),
?assert(lists:member(gateway_cluster_handoff, Ids)),
?assertNot(lists:member(guild_manager, Ids)),
?assertNot(lists:member(gateway_clock_offset, Ids)),
?assertNot(lists:member(presence_manager, Ids)),
persistent_term:erase({fluxer_gateway, runtime_config}).
+118
View File
@@ -5,6 +5,124 @@
-include_lib("eunit/include/eunit.hrl").
read_model_preserves_query_results_test() ->
State = read_model_state(),
try
ok = guild_read_model:put_state(State),
lists:foreach(
fun({Tag, Request, Handler}) ->
{reply, Expected, _} = Handler(Request, State),
?assertEqual({ok, Expected}, guild_read_model:query(100, {Tag, Request}))
end,
[
{get_guild_data, #{user_id => 200}, fun guild_data:get_guild_data/2},
{get_guild_data, #{user_id => 999}, fun guild_data:get_guild_data/2},
{get_guild_data, #{user_id => null}, fun guild_data:get_guild_data/2},
{get_guild_auth_context, #{user_id => 200, channel_id => 500},
fun guild_data:get_auth_context/2},
{get_guild_auth_context, #{user_id => 999, channel_id => null},
fun guild_data:get_auth_context/2},
{get_guild_member, #{user_id => 200}, fun guild_data:get_guild_member/2},
{get_guild_member, #{user_id => 999}, fun guild_data:get_guild_member/2},
{has_member, #{user_id => 200}, fun guild_data:has_member/2}
]
)
after
cleanup_read_model(State)
end.
read_model_uses_live_member_rows_without_copying_members_test() ->
State = read_model_state(),
#{data := #{members_ets := Tab}} = State,
try
ok = guild_read_model:put_state(State),
[{100, _, #{data := SnapshotData}}] = ets:lookup(guild_read_model, 100),
?assertEqual(#{}, maps:get(<<"members">>, SnapshotData)),
[{200, Member}] = ets:lookup(Tab, 200),
Updated = Member#{
<<"nick">> => <<"new nickname">>, <<"communication_disabled_until">> => null
},
ets:insert(Tab, {200, Updated}),
?assertEqual(
{ok, #{success => true, member_data => Updated}},
guild_read_model:query(100, {get_guild_member, #{user_id => 200}})
),
ets:delete(Tab, 200),
?assertEqual(
{ok, #{has_member => false}},
guild_read_model:query(100, {has_member, #{user_id => 200}})
),
?assertMatch(
{ok, #{guild_data := null}},
guild_read_model:query(100, {get_guild_data, #{user_id => 200}})
)
after
cleanup_read_model(State)
end.
read_model_observes_role_and_collection_changes_test() ->
State = read_model_state(),
try
ok = guild_read_model:put_state(State),
Data = maps:get(data, State),
UpdatedData = guild_data_index:normalize_map(Data#{
<<"roles">> => [#{<<"id">> => 100, <<"permissions">> => 0}],
<<"emojis">> => [#{<<"id">> => 700, <<"name">> => <<"new">>}]
}),
Updated = State#{data => UpdatedData},
ok = guild_read_model:update(State, Updated),
{reply, Expected, _} = guild_data:get_guild_data(#{user_id => 200}, Updated),
?assertEqual(
{ok, Expected}, guild_read_model:query(100, {get_guild_data, #{user_id => 200}})
),
#{guild_data := GuildData} = Expected,
?assertEqual([], maps:get(<<"channels">>, GuildData))
after
cleanup_read_model(State)
end.
read_model_survives_blocked_owner_and_rejects_dead_tables_test() ->
State = read_model_state(),
Self = self(),
Owner = spawn(fun() ->
ok = guild_read_model:put_state(State),
Self ! published,
receive
stop -> ok
end
end),
try
receive
published -> ok
after 1000 -> error(publish_timeout)
end,
?assertMatch(
{ok, #{auth_context := #{}}},
guild_read_model:query(
100, {get_guild_auth_context, #{user_id => 200, channel_id => 500}}
)
),
?assertEqual({message_queue_len, 0}, process_info(Owner, message_queue_len)),
ets:delete(maps:get(members_ets, maps:get(data, State))),
?assertEqual(miss, guild_read_model:query(100, {has_member, #{user_id => 200}}))
after
Owner ! stop,
cleanup_read_model(State)
end.
read_model_state() ->
State = test_state(),
Data = guild_data_index:normalize_map(maps:get(data, State)),
Members = guild_data_index:member_map(Data),
Tab = ets:new(read_model_members, [set, public]),
ets:insert(Tab, maps:to_list(Members)),
State#{member_count => map_size(Members), data => Data#{members_ets => Tab}}.
cleanup_read_model(#{data := Data} = State) ->
guild_read_model:delete(100),
catch ets:delete(maps:get(members_ets, Data)),
ets:delete(maps:get(member_presence, State)).
get_guild_data_membership_gate_test() ->
State = test_state(),
{reply, Reply1, _} = guild_data:get_guild_data(#{user_id => 999}, State),
@@ -551,3 +551,53 @@ engine_roles_for_test_member(UserId) when UserId rem 5 =:= 0 ->
[20];
engine_roles_for_test_member(_UserId) ->
[].
version_bumps_on_every_mutation_test() ->
Ref = guild_member_list_engine:new(),
try
V0 = guild_member_list_engine:version(Ref),
ok = guild_member_list_engine:bulk_load(Ref, [{1, <<"a">>, [], true}], []),
V1 = guild_member_list_engine:version(Ref),
ok = guild_member_list_engine:add_member(Ref, 2, <<"b">>, [], false),
V2 = guild_member_list_engine:version(Ref),
ok = guild_member_list_engine:update_member(Ref, 2, <<"c">>, [], false),
V3 = guild_member_list_engine:version(Ref),
ok = guild_member_list_engine:set_online(Ref, 2, true),
V4 = guild_member_list_engine:version(Ref),
changed = guild_member_list_engine:set_hoisted_roles(Ref, [7]),
V5 = guild_member_list_engine:version(Ref),
ok = guild_member_list_engine:remove_member(Ref, 1),
V6 = guild_member_list_engine:version(Ref),
Versions = [V0, V1, V2, V3, V4, V5, V6],
?assertEqual(Versions, lists:usort(Versions)),
?assertEqual(7, length(lists:usort(Versions)))
after
guild_member_list_engine:destroy(Ref)
end.
version_is_stable_for_noop_mutations_test() ->
Ref = guild_member_list_engine:new(),
try
ok = guild_member_list_engine:add_member(Ref, 1, <<"a">>, [], true),
V = guild_member_list_engine:version(Ref),
ok = guild_member_list_engine:set_online(Ref, 1, true),
ok = guild_member_list_engine:set_online(Ref, 99, false),
ok = guild_member_list_engine:remove_member(Ref, 99),
unchanged = guild_member_list_engine:set_hoisted_roles(Ref, []),
_ = guild_member_list_engine:get_items(Ref, 0, 10),
?assertEqual(V, guild_member_list_engine:version(Ref))
after
guild_member_list_engine:destroy(Ref)
end.
version_survives_tables_without_a_version_row_test() ->
Ref = guild_member_list_engine:new(),
try
true = ets:delete(Ref, version),
?assertEqual(undefined, guild_member_list_engine:version(Ref)),
ok = guild_member_list_engine:add_member(Ref, 1, <<"a">>, [], true),
?assertEqual(1, guild_member_list_engine:version(Ref))
after
guild_member_list_engine:destroy(Ref)
end,
?assertEqual(undefined, guild_member_list_engine:version(Ref)).