From b32b16c1f0d9b6842a1a4db0ab1776dee895a08f Mon Sep 17 00:00:00 2001 From: Devin Zuczek Date: Sun, 27 Sep 2026 13:54:35 -0400 Subject: [PATCH] [api] improve relationships query --- .../0030_relationship_friend_index.sql | 25 +++++++++++++++++++ packages/domain/src/relationships-db.ts | 7 +++++- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 apps/api/migrations/0030_relationship_friend_index.sql diff --git a/apps/api/migrations/0030_relationship_friend_index.sql b/apps/api/migrations/0030_relationship_friend_index.sql new file mode 100644 index 0000000..6548f2f --- /dev/null +++ b/apps/api/migrations/0030_relationship_friend_index.sql @@ -0,0 +1,25 @@ +-- Cover the friend-list read (`getFriendIds` in @repo/domain, and the inlined copy in the +-- friends-filtered leaderboard). Owned by the `api` worker; generated from +-- packages/domain/src/relationships-db.ts (RELATIONSHIP_SCHEMA_DDL) — keep in sync. +-- +-- The query is `relationship_type = ?2 AND (requester_id = ?1 OR target_id = ?1)`, which +-- SQLite already splits into one probe per side of the OR. But `relationship_type` was in +-- neither index and neither index carried the other side's id, so every row the player +-- appears in — of ANY type — cost an index read plus a table-row read, and the non-friends +-- were then discarded. Those rows accumulate: an unfriend keeps the row as type 0, ignoring +-- or muting a stranger inserts a type 0 row, pending requests are rows. And the query runs +-- on every matchmake and every disconnect (the presence push to friends), so D1 row reads +-- were roughly 2 x (all relationships) x (matchmakes). +-- +-- These two indexes cover each half: seek to (player, Friend), read the other id off the +-- index, never touch the table. Reads become one per actual friend. +-- +-- (target_id, relationship_type, requester_id) subsumes the old (target_id) index, so that +-- one goes. The unique (requester_id, target_id) index stays — it is the pair constraint +-- and what the single-pair lookups use. + +CREATE INDEX IF NOT EXISTS idx_relationship_requester_type + ON relationship (requester_id, relationship_type, target_id); +CREATE INDEX IF NOT EXISTS idx_relationship_target_type + ON relationship (target_id, relationship_type, requester_id); +DROP INDEX IF EXISTS idx_relationship_target; diff --git a/packages/domain/src/relationships-db.ts b/packages/domain/src/relationships-db.ts index ba5158a..148b308 100644 --- a/packages/domain/src/relationships-db.ts +++ b/packages/domain/src/relationships-db.ts @@ -40,7 +40,12 @@ export const RELATIONSHIP_SCHEMA_DDL: string[] = [ target_muted INTEGER NOT NULL DEFAULT 0 )`, `CREATE UNIQUE INDEX IF NOT EXISTS idx_relationship ON relationship (requester_id, target_id)`, - `CREATE INDEX IF NOT EXISTS idx_relationship_target ON relationship (target_id)`, + // Covering indexes for getFriendIds: each half of its OR seeks to (player, type) and reads + // the other id off the index. See apps/api/migrations/0030_relationship_friend_index.sql. + `CREATE INDEX IF NOT EXISTS idx_relationship_requester_type + ON relationship (requester_id, relationship_type, target_id)`, + `CREATE INDEX IF NOT EXISTS idx_relationship_target_type + ON relationship (target_id, relationship_type, requester_id)`, ] /** A stored relationship row (snake_case columns, one row per player pair). */