perf(db): add missing indexes, fix N+1 batch delete, add pagination limits
- Add indexes on Resource(blueprintId, roleId), DemandRequirement(roleId),
Assignment(roleId) — commonly filtered FK columns that were missing indexes
- Replace N+1 batch delete pattern (2N queries) with findAllocationEntries()
that does 2 total queries via findMany({ id: { in: ids } })
- Add take/skip pagination with default limit of 500 to listDemands and
listAssignments to prevent unbounded result sets
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,7 @@ export {
|
||||
|
||||
export {
|
||||
findAllocationEntry,
|
||||
findAllocationEntries,
|
||||
loadAllocationEntry,
|
||||
type AllocationEntryResolution,
|
||||
} from "./use-cases/allocation/load-allocation-entry.js";
|
||||
|
||||
@@ -2,10 +2,7 @@ import type { Prisma, PrismaClient } from "@capakraken/db";
|
||||
import type { AllocationWithDetails } from "@capakraken/shared";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { buildSplitAllocationReadModel } from "./build-split-allocation-read-model.js";
|
||||
import {
|
||||
ASSIGNMENT_RELATIONS_INCLUDE,
|
||||
type AssignmentWithRelations,
|
||||
} from "./create-assignment.js";
|
||||
import { ASSIGNMENT_RELATIONS_INCLUDE, type AssignmentWithRelations } from "./create-assignment.js";
|
||||
import {
|
||||
DEMAND_REQUIREMENT_RELATIONS_INCLUDE,
|
||||
type DemandRequirementWithRelations,
|
||||
@@ -40,9 +37,7 @@ function toDemandAllocationEntry(
|
||||
}).allocations[0] as AllocationWithDetails;
|
||||
}
|
||||
|
||||
function toAssignmentAllocationEntry(
|
||||
assignment: AssignmentWithRelations,
|
||||
): AllocationWithDetails {
|
||||
function toAssignmentAllocationEntry(assignment: AssignmentWithRelations): AllocationWithDetails {
|
||||
return buildSplitAllocationReadModel({
|
||||
demandRequirements: [],
|
||||
assignments: [assignment],
|
||||
@@ -115,3 +110,50 @@ export async function loadAllocationEntry(
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch-loads allocation entries by ID (2 queries total instead of 2N).
|
||||
* Each ID is resolved against both demand_requirements and assignments.
|
||||
* IDs that don't match either table are silently skipped.
|
||||
*/
|
||||
export async function findAllocationEntries(
|
||||
db: DbClient,
|
||||
ids: string[],
|
||||
): Promise<AllocationEntryResolution[]> {
|
||||
if (ids.length === 0) return [];
|
||||
|
||||
const [demandRequirements, assignments] = await Promise.all([
|
||||
db.demandRequirement.findMany({
|
||||
where: { id: { in: ids } },
|
||||
include: DEMAND_REQUIREMENT_RELATIONS_INCLUDE,
|
||||
}),
|
||||
db.assignment.findMany({
|
||||
where: { id: { in: ids } },
|
||||
include: ASSIGNMENT_RELATIONS_INCLUDE,
|
||||
}),
|
||||
]);
|
||||
|
||||
const results: AllocationEntryResolution[] = [];
|
||||
|
||||
for (const dr of demandRequirements) {
|
||||
results.push({
|
||||
kind: "demand",
|
||||
entry: toDemandAllocationEntry(dr),
|
||||
demandRequirement: dr,
|
||||
projectId: dr.projectId,
|
||||
resourceId: null,
|
||||
});
|
||||
}
|
||||
|
||||
for (const a of assignments) {
|
||||
results.push({
|
||||
kind: "assignment",
|
||||
entry: toAssignmentAllocationEntry(a),
|
||||
assignment: a,
|
||||
projectId: a.projectId,
|
||||
resourceId: a.resourceId,
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user