Skip to content

Commit d90ac52

Browse files
committed
Add dashboard composite indexes
1 parent 8abe5a9 commit d90ac52

3 files changed

Lines changed: 61 additions & 2 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- Add ordered composite indexes for dashboard list and summary queries.
2+
3+
-- Campaign dashboard/export filters by org and status, then orders/paginates by createdAt.
4+
CREATE INDEX "Campaign_orgId_status_createdAt_idx" ON "Campaign"("orgId", "status", "createdAt");
5+
6+
-- Aid package dashboard filters packages within a campaign by status, then orders by createdAt.
7+
CREATE INDEX "AidPackage_campaignId_status_createdAt_idx" ON "AidPackage"("campaignId", "status", "createdAt");

app/backend/prisma/schema.prisma

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ model AidPackage {
3939
remainingAmount Float @default(0)
4040
4141
@@index([campaignId])
42-
@@index([campaignId, status])
42+
@@index([campaignId, status])
43+
@@index([campaignId, status, createdAt])
4344
}
4445

4546
/// Tracks every balance event (lock / unlock / disburse) for a campaign.
@@ -243,7 +244,7 @@ model Claim {
243244
244245
@@index([status])
245246
@@index([campaignId])
246-
@@index([campaignId, status])
247+
@@index([campaignId, status])
247248
@@index([createdAt])
248249
@@index([deletedAt])
249250
@@index([reissuedFromId])
@@ -369,6 +370,7 @@ model Campaign {
369370
@@index([archivedAt])
370371
@@index([ngoId])
371372
@@index([orgId])
373+
@@index([orgId, status, createdAt])
372374
@@index([deletedAt])
373375
}
374376

docs/db/indexes.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Database Indexes
2+
3+
This document records non-obvious composite indexes and the query shapes they
4+
are intended to support.
5+
6+
## Dashboard Composites
7+
8+
The dashboard and export paths filter by tenant/campaign and status before
9+
ordering or constraining by creation time. Single-column indexes on `orgId`,
10+
`campaignId`, `status`, or `createdAt` do not give PostgreSQL the same ordered
11+
access path as a composite index with equality filters first and the ordered
12+
column last.
13+
14+
| Model | Index | Query shape |
15+
| ------------ | ------------------------------------------ | ----------------------------------------------------------------------------------------------- |
16+
| `Claim` | `@@index([campaignId, status])` | Claims within a campaign filtered by status. |
17+
| `Campaign` | `@@index([orgId, status, createdAt])` | Organization dashboard/export queries filtered by `orgId` and `status`, ordered by `createdAt`. |
18+
| `AidPackage` | `@@index([campaignId, status, createdAt])` | Package dashboard queries filtered by `campaignId` and `status`, ordered by `createdAt`. |
19+
20+
## CI EXPLAIN Check
21+
22+
For PostgreSQL-backed CI environments, verify the dashboard plan with
23+
`EXPLAIN ANALYZE` after migrations are applied. The expected plan should use an
24+
index scan or bitmap index scan over the matching composite index, not a
25+
sequential scan.
26+
27+
```sql
28+
EXPLAIN ANALYZE
29+
SELECT id, name, status, "createdAt"
30+
FROM "Campaign"
31+
WHERE "orgId" = 'org_test'
32+
AND status = 'active'
33+
ORDER BY "createdAt" DESC
34+
LIMIT 50;
35+
36+
EXPLAIN ANALYZE
37+
SELECT id, status, "createdAt"
38+
FROM "AidPackage"
39+
WHERE "campaignId" = 'campaign_test'
40+
AND status = 'active'
41+
ORDER BY "createdAt" DESC
42+
LIMIT 50;
43+
44+
EXPLAIN ANALYZE
45+
SELECT id, status
46+
FROM "Claim"
47+
WHERE "campaignId" = 'campaign_test'
48+
AND status = 'approved'
49+
LIMIT 50;
50+
```

0 commit comments

Comments
 (0)