From 19d6c5380dc40acdbc277f9148c3019a7d47dacc Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Fri, 15 May 2026 11:58:35 -0700 Subject: [PATCH] perf(contests): add partial index for shadowban CTE on aggregate_user.score MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cold-cache /v1/events/remix-contests?status=all takes ~22s end-to-end (warm: ~100ms). The dominant cost is the sequential scan of aggregate_user introduced by the shadowban filter: SELECT user_id FROM aggregate_user WHERE score < 0 aggregate_user has one row per user (millions), and only a small number have score < 0, so a partial index covers the filter cheaply. The same CTE is used in v1_event_comments, v1_fan_club_feed, v1_track_comments, and v1_track_comment_count — all benefit. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...0198_aggregate_user_score_negative_idx.sql | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 ddl/migrations/0198_aggregate_user_score_negative_idx.sql diff --git a/ddl/migrations/0198_aggregate_user_score_negative_idx.sql b/ddl/migrations/0198_aggregate_user_score_negative_idx.sql new file mode 100644 index 00000000..406bd6e9 --- /dev/null +++ b/ddl/migrations/0198_aggregate_user_score_negative_idx.sql @@ -0,0 +1,29 @@ +-- Partial index covering only shadowbanned users (aggregate_user.score < 0). +-- +-- Several handlers — v1_event_comments, v1_events_remix_contests, +-- v1_fan_club_feed, v1_track_comments, v1_track_comment_count — materialize +-- a `low_abuse_score` CTE via: +-- +-- SELECT user_id FROM aggregate_user WHERE score < 0 +-- +-- Without a covering index this is a sequential scan over the entire +-- aggregate_user table (one row per user, in the millions). On cold cache, +-- /v1/events/remix-contests?status=all measured ~22s end-to-end, dominated +-- by that seq scan; on warm cache the same call returns in ~100ms. +-- +-- aggregate_user.score < 0 is a very small fraction of users (shadowbanned +-- accounts only), so a partial index is dramatically cheaper than a full +-- btree on `score`. +-- +-- Size budget: thousands of matching rows × ~12 bytes ≈ tens of KB. +-- +-- NOTE: intentionally NOT wrapped in BEGIN/COMMIT so CREATE INDEX +-- CONCURRENTLY can run without holding ACCESS EXCLUSIVE on aggregate_user. +-- IF NOT EXISTS keeps the migration idempotent. + +create index concurrently if not exists idx_aggregate_user_score_negative + on aggregate_user (user_id) + where score < 0; + +comment on index idx_aggregate_user_score_negative is + 'Partial index for the shadowban-author CTEs (score < 0); avoids a full table scan of aggregate_user.';