perf: add indexes to improve query performance in users, user_traffic, hosts, and internal_squad_inbounds tables#153
perf: add indexes to improve query performance in users, user_traffic, hosts, and internal_squad_inbounds tables#153ZeN220 wants to merge 1 commit intoremnawave:devfrom
users, user_traffic, hosts, and internal_squad_inbounds tables#153Conversation
…fic`, `hosts`, and `internal_squad_inbounds` tables
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
PR Summary
By introducing these changes, we ensure that our database operations become more efficient by allowing quicker access to data across various tables. |
Greptile SummaryThis PR adds 9 B-tree indexes across the Key considerations:
Confidence Score: 4/5
Important Files Changed
Entity Relationship Diagram%%{init: {'theme': 'neutral'}}%%
erDiagram
users {
BigInt t_id PK
String uuid UK
String status
String traffic_limit_strategy
DateTime expire_at
BigInt telegram_id
String external_squad_uuid FK
DateTime created_at
}
user_traffic {
BigInt t_id PK
DateTime online_at
BigInt used_traffic_bytes
}
hosts {
String uuid PK
String config_profile_inbound_uuid FK
Boolean is_disabled
}
internal_squad_inbounds {
String internal_squad_uuid PK
String inbound_uuid PK
}
config_profile_inbounds {
String uuid PK
String profile_uuid FK
}
external_squads {
String uuid PK
}
users ||--o| user_traffic : "has traffic"
users }o--o| external_squads : "belongs to"
hosts }o--o| config_profile_inbounds : "linked to inbound"
internal_squad_inbounds }o--|| config_profile_inbounds : "references"
Reviews (1): Last reviewed commit: "feat: add indexes to improve query perfo..." | Re-trigger Greptile |
| -- CreateIndex | ||
| CREATE INDEX "users_status_expire_at_idx" ON "users"("status", "expire_at"); | ||
|
|
||
| -- CreateIndex | ||
| CREATE INDEX "users_traffic_limit_strategy_status_idx" ON "users"("traffic_limit_strategy", "status"); | ||
|
|
||
| -- CreateIndex | ||
| CREATE INDEX "users_expire_at_idx" ON "users"("expire_at"); | ||
|
|
||
| -- CreateIndex | ||
| CREATE INDEX "users_external_squad_uuid_idx" ON "users"("external_squad_uuid"); | ||
|
|
||
| -- CreateIndex | ||
| CREATE INDEX "users_telegram_id_idx" ON "users"("telegram_id"); | ||
|
|
||
| -- CreateIndex | ||
| CREATE INDEX "users_created_at_idx" ON "users"("created_at"); | ||
|
|
||
| -- CreateIndex | ||
| CREATE INDEX "user_traffic_online_at_idx" ON "user_traffic"("online_at"); | ||
|
|
||
| -- CreateIndex | ||
| CREATE INDEX "hosts_config_profile_inbound_uuid_idx" ON "hosts"("config_profile_inbound_uuid"); | ||
|
|
||
| -- CreateIndex | ||
| CREATE INDEX "internal_squad_inbounds_inbound_uuid_idx" ON "internal_squad_inbounds"("inbound_uuid"); |
There was a problem hiding this comment.
Non-concurrent index builds may block writes on production
All nine indexes are created with plain CREATE INDEX, which acquires a ShareLock on each table for the full duration of the build. On a live system with many rows in users or user_traffic, every write to those tables (INSERT/UPDATE/DELETE) will be blocked until the corresponding index is finished. Six indexes are being built on users in a single migration, which is especially risky.
The safe alternative is CREATE INDEX CONCURRENTLY, which builds the index without holding a write lock. The catch is that it cannot run inside a transaction, so Prisma's default transaction wrapping must be bypassed by adding the pragma comment at the top of the file:
-- This migration uses CREATE INDEX CONCURRENTLY which requires running outside a transaction.
-- Prisma wraps migrations in transactions by default; the pragma below disables that.
-- migrate:once pragma: disable-transaction
CREATE INDEX CONCURRENTLY "users_status_expire_at_idx" ON "users"("status", "expire_at");
CREATE INDEX CONCURRENTLY "users_traffic_limit_strategy_status_idx" ON "users"("traffic_limit_strategy", "status");
CREATE INDEX CONCURRENTLY "users_expire_at_idx" ON "users"("expire_at");
CREATE INDEX CONCURRENTLY "users_external_squad_uuid_idx" ON "users"("external_squad_uuid");
CREATE INDEX CONCURRENTLY "users_telegram_id_idx" ON "users"("telegram_id");
CREATE INDEX CONCURRENTLY "users_created_at_idx" ON "users"("created_at");
CREATE INDEX CONCURRENTLY "user_traffic_online_at_idx" ON "user_traffic"("online_at");
CREATE INDEX CONCURRENTLY "hosts_config_profile_inbound_uuid_idx" ON "hosts"("config_profile_inbound_uuid");
CREATE INDEX CONCURRENTLY "internal_squad_inbounds_inbound_uuid_idx" ON "internal_squad_inbounds"("inbound_uuid");Alternatively, if a maintenance window is acceptable, the existing migration can be kept as-is, but that should be a conscious decision documented in the PR.
| @@index([expireAt]) | ||
| @@index([externalSquadUuid]) | ||
| @@index([telegramId]) |
There was a problem hiding this comment.
Consider partial indexes for nullable sparse columns
telegramId and externalSquadUuid are both optional (BigInt? / String?), meaning a significant proportion of rows may store NULL. PostgreSQL still indexes every row — including NULL values — in a plain B-tree index, which wastes space and slows down index scans on large tables.
A partial index restricted to non-null rows is smaller, fits better in memory, and gives the planner a stronger cardinality estimate:
CREATE INDEX "users_telegram_id_idx" ON "users"("telegram_id") WHERE telegram_id IS NOT NULL;
CREATE INDEX "users_external_squad_uuid_idx" ON "users"("external_squad_uuid") WHERE external_squad_uuid IS NOT NULL;This can be expressed in Prisma using a @@index with a raw where argument (via the extendedIndexes preview feature), or by editing the generated SQL directly. Worth considering if the columns are sparse.
added indexes for more perfomance
[status, expire_at],[traffic_limit_strategy, status],[expire_at],[external_squad_uuid],[telegram_id],[created_at]— cron tasks/users dashboard/get user by param in API[online_at]— online calculation[config_profile_inbound_uuid]— hot path for subscription generation[inbound_uuid]— hot path for subscription generation