Skip to content

chore(db): drop dead columns, redundant indexes, and unique constraint - #597

Closed
junhoyeo wants to merge 3 commits into
feat/devices-ui-from-pr389from
chore/drop-dead-columns
Closed

chore(db): drop dead columns, redundant indexes, and unique constraint#597
junhoyeo wants to merge 3 commits into
feat/devices-ui-from-pr389from
chore/drop-dead-columns

Conversation

@junhoyeo

@junhoyeo junhoyeo commented May 25, 2026

Copy link
Copy Markdown
Owner

Summary

Drops five pieces of dead schema surface flagged in the post-#593 audit (#593 (comment)):

Drop Why dead
daily_breakdown.provider_breakdown jsonb Declared in schema.ts, zero reads, zero writes anywhere in src/. Pure allocated bytes on every daily row.
daily_breakdown.model_breakdown jsonb Written by submit/route.ts on every submission but the only SELECT (users/[username]/route.ts:110) discards the value. Net result: storage + JSON serialization churn with no consumer.
submissions.status varchar(20) + idx_submissions_status Column is only ever written as 'verified' on insert (submit/route.ts:205). Zero WHERE filters anywhere in the codebase. The index serves no query.
users.is_admin boolean Set in DB, fetched into SessionUser, but no admin gate exists anywhere in the codebase. A user with is_admin=true had zero additional privileges. If admin features ever ship, reintroduce the column WITH an actual gate.

Plus one pre-existing test fixup that was failing on main before this PR: __tests__/api/leaderboard.test.ts was checking 5 positional args to getLeaderboardData but the route now passes 7 (customFrom/customTo from #522 were never added to the expectation).

Migration

  • 0011_drop_dead_columns.sql β€” five DROPs, each gated with IF EXISTS so the migration is safe to replay against any environment regardless of whether the column was previously removed out-of-band.

Depends on #593's 0010_submit_count_safety being present (this PR is branched off feat/devices-ui-from-pr389). Land #593 first, then this rebases cleanly onto main.

Code changes

  • src/lib/db/schema.ts: remove the four columns and the dead index from the schema definitions.
  • src/lib/db/helpers.ts: delete buildModelBreakdown (only caller was the submit flow's model-breakdown writes, now also gone).
  • src/app/api/submit/route.ts: drop the status: "verified" insert value, remove modelBreakdown from both the in-memory toInsert/toUpdate types and the SQL VALUES list / UPDATE SET / AS batch(...) column list.
  • src/app/api/users/[username]/route.ts: stop SELECT-ing dailyBreakdown.modelBreakdown (it was never read from the result).
  • src/lib/auth/session.ts: remove isAdmin from the SessionUser interface and from both getSession() / getSessionFromHeader() returns.
  • src/lib/auth/personalTokens.ts: remove isAdmin from AuthenticatedPersonalToken, the SELECT, and the returned record.

Test changes

All isAdmin: false / isAdmin: "users.isAdmin" mock object entries removed across 11 test files (mechanical β€” they were only there because the production type required the field). Removed the should build modelBreakdown from clients with multiple models test in submit.test.ts since it was inlining the algorithm of the deleted helper rather than testing observable behavior.

Verification

Local stack (postgres:16 on localhost:5432, all migrations 0000β†’0011 applied, seeded with scripts/seed-dev.ts):

  • \d submissions no longer shows status; \d daily_breakdown no longer shows provider_breakdown or model_breakdown; \d users no longer shows is_admin. idx_submissions_status is gone from \di.
  • bunx tsc --noEmit β†’ 0 errors
  • bunx vitest run __tests__ β†’ 251 passed, 0 failed (was 250/1 before the leaderboard test fix)
  • GET /api/users/seed-dev-alice β†’ 200 JSON, totals match seeded data, no isAdmin field anywhere
  • GET /api/users/seed-dev-alice/devices β†’ 200 (devices endpoint from feat(devices): add per-device read+rename APIs and fix submit_count driftΒ #593 unaffected)
  • Inserted a real API token row directly, then POST /api/submit with that token + a fresh daily contribution β†’ HTTP 200, submissionId returned, daily_breakdown row inserted with tokens=1000, cost=0.0500, source_breakdown jsonb populated, no errors despite the dropped model_breakdown column being absent from the INSERT.

Caveats

  • Drops are irreversible. If anyone has been hand-querying provider_breakdown / model_breakdown for ad-hoc analytics on prod, those queries break. From the codebase audit there are no such callers, but worth a heads-up before merge.
  • users.is_admin was already a useless capability (no gates), so dropping it doesn't change observed behavior for anyone. If admin features are planned, the right pattern is a separate admin_users table or a role enum on users, not a boolean column that the auth layer ignores.
  • Migration runs as a single transaction. The DROPs are O(table size) on a Postgres rewrite-free path (column-by-column drops only update catalogs; no row rewrite required), so this should be fast even on a large prod submissions / daily_breakdown. The DROP INDEX is the only meaningful lock.

Test plan


Summary by cubic

Dropped dead DB columns, removed unused submission indexes, added FK coverage indexes, and deleted a redundant unique constraint. Cleaned up submit/auth/profile code and tests to match the new schema; intentionally kept planner-preferred duplicate indexes.

  • Refactors

    • Removed users.is_admin, submissions.status, and daily_breakdown.{provider_breakdown, model_breakdown} references; deleted buildModelBreakdown; stopped writing status: "verified" on submit.
    • Tests: removed isAdmin from mocks, deleted the modelBreakdown unit test, and fixed leaderboard expectations to include the 6th/7th args as undefined.
  • Migration

    • Dropped columns: daily_breakdown.provider_breakdown, daily_breakdown.model_breakdown, submissions.status (+ idx_submissions_status), users.is_admin.
    • Dropped unused indexes: idx_submissions_user_id, idx_submissions_total_tokens, idx_submissions_date_range.
    • Kept explicit non-unique indexes that prod queries prefer: idx_users_username, idx_sessions_token, idx_api_tokens_token, idx_device_codes_device_code, idx_device_codes_user_code.
    • Added FK coverage indexes: idx_device_codes_user_id, idx_group_members_invited_by, idx_group_invites_invited_by.
    • Dropped redundant unique constraint: submissions_user_hash_unique. All DROPs use IF EXISTS; apply after 0010_submit_count_safety.

Written for commit 810f8cf. Summary will update on new commits. Review in cubic

Migration 0011 drops five pieces of confirmed-dead schema surface:

- daily_breakdown.provider_breakdown: declared in schema.ts but never
  read or written anywhere in src/. Pure allocated bytes per row.

- daily_breakdown.model_breakdown: written on every submit, but the
  only SELECT (users/[username]/route.ts:110) discards the value.
  Storage + serialization churn with no consumer.

- submissions.status + idx_submissions_status: column is only ever
  written as 'verified' on insert; zero WHERE filters anywhere.
  The index serves no query.

- users.is_admin: column is set/fetched but no admin gate exists
  anywhere in the codebase. A user with is_admin=true had zero
  additional privileges. If admin features ship later, reintroduce
  WITH an actual gate.

Code changes follow the schema: remove the field from drizzle
table defs, drop the buildModelBreakdown helper (only caller was
the submit write path now also gone), and stop emitting / reading
the dropped fields from session/auth/profile/submit endpoints.

Tests: removed 21 isAdmin mock-object entries and the test that
inlined buildModelBreakdown's algorithm. Also fixed a pre-existing
leaderboard.test.ts failure caused by #522 adding customFrom /
customTo positional args without updating the expectation.

Verification: tsc 0 errors, 251/251 vitest, end-to-end POST
/api/submit against the post-drop schema returns 200 and writes
a daily_breakdown row successfully.

Constraint: do not regress the active submit / profile / devices paths
Constraint: depends on #593's 0010_submit_count_safety being present
Rejected: drop submissions.idx_submissions_user_id + idx_submissions_total_tokens + idx_submissions_date_range as well | needs a fresh pg_stat_user_indexes audit on prod first; the index cleanup is a separate analysis-driven PR
Rejected: drop submissions_user_hash_unique constraint (functionally redundant with submissions_user_id_unique) | low-blast-radius but worth its own PR with a freshness check on the submit-idempotency code path
Confidence: high
Scope-risk: moderate (touches submit/route.ts, the critical write path)
Directive: do NOT reintroduce users.is_admin without also wiring an admin gate; the previous setup misled readers into thinking access control existed
Not-tested: ad-hoc SQL queries that may have been reading the dropped jsonb columns on prod
@vercel

vercel Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tokscale Ready Ready Preview, Comment May 25, 2026 4:54am

Request Review

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 22 files

Re-trigger cubic

Extends 0011_drop_dead_columns with the index-cleanup (group B) and
redundant-unique-constraint (group C) drops that were originally
proposed as follow-up PRs, after a fresh integrity audit on the
local DB confirmed each drop is safe.

Index drops:
- idx_submissions_user_id: redundant left-prefix of
  idx_submissions_leaderboard (user_id, total_tokens, total_cost,
  created_at). Per #389's prod stats audit, 214 scans on this vs
  3.27M on the leaderboard composite.
- idx_submissions_total_tokens: 0 prod scans (#389 audit). All
  reads use SUM() per-user GROUP BY which is served by the
  leaderboard composite.
- idx_submissions_date_range: 0 prod scans (#389 audit). No
  filter ever queries these columns; leaderboard period filters
  use daily_breakdown.date which has its own index.
- idx_users_username, idx_sessions_token, idx_api_tokens_token,
  idx_device_codes_device_code, idx_device_codes_user_code:
  Postgres auto-creates a btree for every UNIQUE constraint;
  these explicit btrees were 1:1 duplicates of *_unique indexes
  and only added INSERT/UPDATE write amplification.

Index adds (FK coverage):
- idx_device_codes_user_id: cascade-delete of a user did a seq
  scan of device_codes.
- idx_group_members_invited_by: cascade-delete of an inviter
  did a seq scan of group_members.
- idx_group_invites_invited_by: same, for group_invites.

Constraint drop:
- submissions_user_hash_unique (user_id, submission_hash) was
  functionally subsumed by submissions_user_id_unique (user_id).
  The stronger constraint already enforces one row per user, so
  any (user_id, submission_hash) collision is rejected at the
  user_id level. The submission_hash column itself stays;
  /api/submit still reads it for idempotency comparison.

Verification on clean local DB:
- All 11 prior migrations (0000-0010) plus this 0011 apply
  successfully from an empty schema.
- pg_constraint audit: all 14 FKs in the schema now have
  covering indexes (was 11 before; +3 from this PR).
- Index audit: all 9 dead/redundant indexes confirmed gone;
  the 3 new FK-coverage indexes confirmed present.
- bunx tsc --noEmit: 0 errors.
- bunx vitest run __tests__: 251/251 pass.
- End-to-end POST /api/submit with a real api_tokens row: HTTP
  200, daily_breakdown row written, submission totals updated.
- Seed script reload: 3 users, 6 devices, 84 daily rows, 1 group
  with 3 members β€” all counts consistent.

Constraint: each index drop must have either a prod stats scan-count justification or be a literal duplicate of an existing unique-constraint index
Constraint: do not regress FK cascade-delete behavior
Rejected: drop idx_submissions_created_at | not redundant β€” leaderboard composite has created_at as the 4th column; a plain created_at filter still benefits from a dedicated index
Rejected: drop idx_users_github_id | github OAuth login does a lookup by github_id on every sign-in; this index is hot
Confidence: high
Scope-risk: moderate (drops are irreversible; index recreation in prod requires CREATE INDEX CONCURRENTLY if scans turn out to be needed)
Directive: do NOT re-add an explicit btree on any column that already has a UNIQUE constraint β€” postgres creates the covering index automatically
Not-tested: prod pg_stat_user_indexes scan counts as of merge day (the audit data is ~6 weeks old per #389)
@junhoyeo junhoyeo changed the title chore(db): drop dead columns flagged by post-batch audit chore(db): drop dead columns, redundant indexes, and unique constraint May 25, 2026
The 2026-05-25 prod audit (pg_stat_user_indexes) changed the picture
for the duplicate-of-unique-constraint indexes I planned to drop:

- idx_sessions_token: 89,560 scans (sessions_token_unique: 0)
- idx_users_username: 30,080 scans (users_username_unique: 0)
- idx_api_tokens_token: 27,126 scans (api_tokens_token_unique: 0)
- idx_device_codes_device_code: 153 scans
- idx_device_codes_user_code: 3 scans

Postgres' planner consistently picks the explicit non-unique index
over the unique-constraint sibling, so these are NOT no-op drops β€”
the unique-constraint indexes show 0 scans in prod because they
aren't being chosen. Removing the explicit ones would invalidate
cached plans and force a re-plan onto the unique-constraint
indexes; safe in theory but a non-zero risk window.

Removed those five DROP INDEX statements from migration 0011 and
restored the corresponding index() declarations to schema.ts with a
comment explaining why the explicit non-unique siblings stay.

Two drops that prod stats initially questioned but the
investigation subagents confirmed safe (no app caller, scans are
write-side enforcement / vacuum):

- idx_submissions_status: 323 scans β€” vacuum cycles + historical
  scan count from before commit acccd44 removed the column from
  the drizzle model. No app WHERE filter exists.
- submissions_user_hash_unique: 388 scans β€” pure constraint-check
  btree probes on every INSERT/UPDATE that writes submission_hash;
  no read query references the column.

Both still get dropped.

Constraint: do not invalidate hot cached query plans on prod without a controlled re-plan window
Rejected: drop all 5 unique-duplicate btrees | the planner uses them, sessions_token alone is 89k scans
Confidence: high
Scope-risk: narrow (only removed planned drops; nothing else changed)
Directive: do NOT remove the 5 explicit btrees on prod without first running ANALYZE on the table and verifying the planner falls back to the unique-constraint index
Not-tested: cold-plan latency after dropping any of these in a future PR
@junhoyeo
junhoyeo deleted the branch feat/devices-ui-from-pr389 May 25, 2026 04:56
@junhoyeo junhoyeo closed this May 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant