mirror of
https://github.com/block/buzz.git
synced 2026-11-15 22:02:04 +00:00
perf(desktop): stop the Projects fan refetching on re-entry and running after leave (#6458)
Entering Projects fires a large fan: an exhaustive paginated relay enumeration (projects/repos/tombstones), five 2,000-event work-item queries plus assignment-operation scans, per-repo activity summaries, and a local-repository filesystem scan. Measured on a large community (101 issues / 258 PRs): | query | measured cost | |---|---| | work-items (5 × 2,000-event REQs + assignment scans) | 3.5–3.9s | | activity summaries | 4.1s | | repository activity | 1.0–2.2s | | local repository scan | 1.7s | Two lifecycle bugs made the fan far more expensive than it needs to be: - **Freshness windows guaranteed a full refetch on nearly every re-entry** (60s enumeration, 30s work-items/activity, 10s local scan) — i.e., the costs above were re-paid on almost every visit. Every local write path already invalidates its keys explicitly (issue/PR mutations, project creation, repo sync), so the short windows only served remote-actor freshness. Raised to 5m/2m/2m with a 30m enumeration cache: re-entries now paint from cache, and the fan re-runs at most every 2–5 minutes. - **Leaving Projects left the whole fan running**, competing with the next surface's channel fetches on the same relay connection. AbortSignal is now threaded through the enumeration and assignment pagination loops (optional params — behavior identical without a signal), and leaving the surface cancels the work-items query. Deliberately NOT cancelled: the enumeration (the always-mounted sidebar projects section observes it and its 30m cache is valuable), repo snapshots and local scans (native work that can't abort — cancelling would discard the finished result and force the same clones again), and activity summaries (a single bounded request). Abort behavior is covered by red-first unit tests on both pagination loops. Remaining follow-up (out of scope): the queries themselves want a relay-side aggregate instead of shipping thousands of events to compute counts client-side. --------- Signed-off-by: Max Lampert <maxwell@squareup.com>
This commit is contained in:
@@ -68,6 +68,7 @@ export async function fetchAssignmentOperationEvents(
|
||||
fetchEvents: (
|
||||
filter: FetchEventsInput,
|
||||
) => Promise<RelayEvent[]> = relayClient.fetchEvents.bind(relayClient),
|
||||
signal?: AbortSignal,
|
||||
): Promise<RelayEvent[]> {
|
||||
if (issueIds.length === 0) return [];
|
||||
const chunks: string[][] = [];
|
||||
@@ -75,7 +76,9 @@ export async function fetchAssignmentOperationEvents(
|
||||
chunks.push(issueIds.slice(i, i + ISSUE_ID_CHUNK_SIZE));
|
||||
}
|
||||
const pages = await Promise.all(
|
||||
chunks.map((chunk) => fetchIssueCommentsExhaustively(chunk, fetchEvents)),
|
||||
chunks.map((chunk) =>
|
||||
fetchIssueCommentsExhaustively(chunk, fetchEvents, signal),
|
||||
),
|
||||
);
|
||||
const seen = new Map<string, RelayEvent>();
|
||||
for (const page of pages) {
|
||||
@@ -91,11 +94,15 @@ export async function fetchAssignmentOperationEvents(
|
||||
async function fetchIssueCommentsExhaustively(
|
||||
issueIds: string[],
|
||||
fetchEvents: (filter: FetchEventsInput) => Promise<RelayEvent[]>,
|
||||
signal?: AbortSignal,
|
||||
): Promise<RelayEvent[]> {
|
||||
const seen = new Map<string, RelayEvent>();
|
||||
let limit = ASSIGNMENT_PAGE_LIMIT;
|
||||
let until: number | undefined;
|
||||
for (;;) {
|
||||
// Leaving the Projects surface cancels its queries; stop queuing pages
|
||||
// behind the next surface's fetches.
|
||||
signal?.throwIfAborted();
|
||||
const page = await fetchEvents({
|
||||
kinds: [KIND_TEXT_NOTE],
|
||||
"#e": issueIds,
|
||||
|
||||
Reference in New Issue
Block a user