mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-07-12 13:28:55 +00:00
perf(db): covering partial index for the reporting measures aggregate
The reporting 'where the money goes' panel joins the latest plan per property to its recommendations ON plan_id, filters to the default active set, and sums estimated_cost / counts property_id per measure_type. The only plan_id index (idx_recommendation_plan_id) is plain, so the aggregate heap-fetches every recommendation to apply the filter and read those columns — hundreds of thousands of fetches on large portfolios (434), which times the panel out. idx_recommendation_plan_default carries plan_id + measure_type + property_id + estimated_cost as key columns (drizzle-orm has no INCLUDE), partial on default = true AND already_installed = false, so the aggregate runs index-only. Added to the schema and generated via drizzle-kit. OPS: build CONCURRENTLY out-of-band on prod before the migration runs so it no-ops there (0258 pattern); the migration uses IF NOT EXISTS for fresh environments. See the migration file header. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
4c65bca0c3
commit
4fd1e00e63
4 changed files with 12202 additions and 0 deletions
21
src/app/db/migrations/0266_hesitant_bromley.sql
Normal file
21
src/app/db/migrations/0266_hesitant_bromley.sql
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
-- Covering partial index for the reporting "where the money goes" aggregate.
|
||||
-- The query joins the latest plan per property to its recommendations ON
|
||||
-- plan_id, filters to default = true AND already_installed = false, and sums
|
||||
-- estimated_cost / counts property_id per measure_type. The plain
|
||||
-- idx_recommendation_plan_id only locates the rows; the aggregate then
|
||||
-- heap-fetches every recommendation to apply the filter and read these
|
||||
-- columns, which times out on large portfolios (e.g. 434). Carrying
|
||||
-- measure_type, property_id and estimated_cost as key columns makes the whole
|
||||
-- aggregate index-only.
|
||||
--
|
||||
-- OPS: `recommendation` is large, so build this CONCURRENTLY out-of-band on
|
||||
-- prod BEFORE this migration runs, so the migration no-ops there (matches the
|
||||
-- 0258 idx_plan_recommendations_recommendation_id pattern). A plain
|
||||
-- CREATE INDEX here would lock writes on the table. Fresh environments build
|
||||
-- it inline via IF NOT EXISTS.
|
||||
--
|
||||
-- CREATE INDEX CONCURRENTLY IF NOT EXISTS "idx_recommendation_plan_default"
|
||||
-- ON "recommendation" USING btree ("plan_id","measure_type","property_id","estimated_cost")
|
||||
-- WHERE "default" = true AND "already_installed" = false;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "idx_recommendation_plan_default" ON "recommendation" USING btree ("plan_id","measure_type","property_id","estimated_cost") WHERE "recommendation"."default" = true AND "recommendation"."already_installed" = false;
|
||||
12154
src/app/db/migrations/meta/0266_snapshot.json
Normal file
12154
src/app/db/migrations/meta/0266_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1856,6 +1856,13 @@
|
|||
"when": 1783446767021,
|
||||
"tag": "0265_uprn_corrections",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 266,
|
||||
"version": "7",
|
||||
"when": 1783511384352,
|
||||
"tag": "0266_hesitant_bromley",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -118,6 +118,26 @@ export const recommendation = pgTable(
|
|||
sql`${table.default} = true AND ${table.alreadyInstalled} = false`,
|
||||
),
|
||||
|
||||
// Covering partial index for the reporting "where the money goes"
|
||||
// aggregate, which joins the latest plan per property to its
|
||||
// recommendations ON plan_id, filters to the default active set, and sums
|
||||
// estimated_cost / counts property_id per measure_type. The plain
|
||||
// idx_recommendation_plan_id only locates the rows — the aggregate then
|
||||
// heap-fetches every recommendation to apply the filter and read these
|
||||
// columns, which times out on large portfolios. Carrying measure_type,
|
||||
// property_id and estimated_cost as key columns (drizzle-orm has no
|
||||
// INCLUDE) makes the whole aggregate index-only.
|
||||
index("idx_recommendation_plan_default")
|
||||
.on(
|
||||
table.planId,
|
||||
table.measureType,
|
||||
table.propertyId,
|
||||
table.estimatedCost,
|
||||
)
|
||||
.where(
|
||||
sql`${table.default} = true AND ${table.alreadyInstalled} = false`,
|
||||
),
|
||||
|
||||
index("idx_recommendation_material_id").on(table.materialId),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue