mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
fix(api): batch member user lookups on guild load (#2783)
This commit is contained in:
@@ -365,8 +365,9 @@ FLUXER_DISCOVERY_ENABLED=true
|
||||
# messages, 320 for snowflakes and 64 elsewhere, and they govern every service
|
||||
# Compose does not forward this to. A router holds a slot for the whole round
|
||||
# trip to its shard, so this is a ceiling on requests in flight at once and not a
|
||||
# rate: too low a value does not slow requests down, it rejects them, and the api
|
||||
# turns that rejection into a 503.
|
||||
# rate: too low a value does not slow requests down, it rejects them. The api
|
||||
# turns that rejection into a 503 and logs "shard rejected the request because
|
||||
# it is at its concurrency limit".
|
||||
#FLUXER_SVC_MAX_CONCURRENT_REQUESTS=192
|
||||
|
||||
# The api and the Rust services name their fixed Postgres statement shapes so the
|
||||
|
||||
@@ -795,6 +795,10 @@ export class RpcService {
|
||||
requestCache: RequestCache;
|
||||
}): Promise<Array<GuildMemberResponse>> {
|
||||
const {guildId, members, requestCache} = params;
|
||||
await this.userCacheService.getUserPartialResponses(
|
||||
members.map((member) => member.userId),
|
||||
requestCache,
|
||||
);
|
||||
const mappedMembers = await allSettledWithConcurrency(members, RPC_RESPONSE_MAP_CONCURRENCY, (member) =>
|
||||
mapGuildMemberToResponse(member, this.userCacheService, requestCache),
|
||||
);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {setupTestGuildWithMembers} from '@app/api/guild/tests/GuildTestUtils';
|
||||
import {createUsersServiceClient} from '@app/api/infrastructure/UsersServiceClient';
|
||||
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
|
||||
import {HTTP_STATUS, TEST_IDS} from '@app/api/test/TestConstants';
|
||||
import {createBuilder} from '@app/api/test/TestRequestBuilder';
|
||||
import {RpcRequest} from '@fluxer/schema/src/domains/rpc/RpcSchemas';
|
||||
import {afterEach, beforeEach, describe, expect, test} from 'vitest';
|
||||
import {afterEach, beforeEach, describe, expect, test, vi} from 'vitest';
|
||||
|
||||
interface RpcGuildCollectionMembersResponse {
|
||||
type: 'guild_collection';
|
||||
@@ -69,6 +70,22 @@ describe('RpcService guild member collection pagination', () => {
|
||||
expect(seenIds.slice().sort()).toStrictEqual(expectedIds.slice().sort());
|
||||
});
|
||||
|
||||
test('looks up every member user on a page in one users service request', async () => {
|
||||
const {owner, members, guild} = await setupTestGuildWithMembers(harness, 3);
|
||||
const memberIds = [owner.userId, ...members.map((member) => member.userId)].sort();
|
||||
const lookups = vi.spyOn(createUsersServiceClient(), 'getUserPartialResponses');
|
||||
try {
|
||||
const data = await fetchMemberPage(harness, guild.id, 1000);
|
||||
expect(data.members.map((member) => member.user.id).sort()).toStrictEqual(memberIds);
|
||||
const memberLookups = lookups.mock.calls
|
||||
.map(([userIds]) => userIds.map(String).sort())
|
||||
.filter((userIds) => userIds.some((userId) => memberIds.includes(userId)));
|
||||
expect(memberLookups).toStrictEqual([memberIds]);
|
||||
} finally {
|
||||
lookups.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
test('rejects an unknown guild on the first member page', async () => {
|
||||
await createBuilder(harness, '')
|
||||
.post('/test/rpc-session-init')
|
||||
|
||||
@@ -605,7 +605,7 @@ Default `8090`. The health and metrics port. Not published.
|
||||
|
||||
#### `FLUXER_SVC_MAX_CONCURRENT_REQUESTS`
|
||||
|
||||
Defaults to 192 for messages, 320 for snowflakes, 64 otherwise. In-flight request ceiling. The built-in defaults key off `FLUXER_SVC_NAME`. Compose forwards `FLUXER_SVC_MAX_CONCURRENT_REQUESTS` to `users`, `users-shard`, `messages`, and `messages-shard` at a default of 20, which pairs with their 20-connection Postgres pools, and leaves the other containers on the built-in defaults.
|
||||
Defaults to 192 for messages, 320 for snowflakes, 64 otherwise. In-flight request ceiling. The built-in defaults key off `FLUXER_SVC_NAME`. Compose forwards `FLUXER_SVC_MAX_CONCURRENT_REQUESTS` to `users`, `users-shard`, `messages`, and `messages-shard`, and leaves the other containers on the built-in defaults. Left unset, those four keep their built-in defaults too. Once set, the one value replaces the default on all four, so a value below 192 also lowers the messages ceiling. A router holds a slot for the whole round trip to its shard. A request over the ceiling is rejected at once. The API request behind it fails with a 503, and the API logs `shard rejected the request because it is at its concurrency limit`.
|
||||
|
||||
#### `POD_NAME`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user