perf: optimisations DB — batch queries et index

- createEvaluation: remplace N create() par un createMany() (N→1 requête)
- updateEvaluation: regroupe les upserts en $transaction() parallèle
- Ajout d'index sur Evaluation.evaluatorId, Evaluation.templateId,
  EvaluationShare.userId et AuditLog.evaluationId

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 13:27:57 +01:00
parent 99e1a06137
commit 27866091bf
3 changed files with 50 additions and 29 deletions

View File

@@ -0,0 +1,11 @@
-- CreateIndex
CREATE INDEX "AuditLog_evaluationId_idx" ON "AuditLog"("evaluationId");
-- CreateIndex
CREATE INDEX "Evaluation_evaluatorId_idx" ON "Evaluation"("evaluatorId");
-- CreateIndex
CREATE INDEX "Evaluation_templateId_idx" ON "Evaluation"("templateId");
-- CreateIndex
CREATE INDEX "EvaluationShare_userId_idx" ON "EvaluationShare"("userId");

View File

@@ -66,6 +66,9 @@ model Evaluation {
isPublic Boolean @default(false) // visible par tous (ex. démo) isPublic Boolean @default(false) // visible par tous (ex. démo)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@index([evaluatorId])
@@index([templateId])
} }
model EvaluationShare { model EvaluationShare {
@@ -77,6 +80,7 @@ model EvaluationShare {
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@unique([evaluationId, userId]) @@unique([evaluationId, userId])
@@index([userId])
} }
model DimensionScore { model DimensionScore {
@@ -106,4 +110,6 @@ model AuditLog {
newValue String? newValue String?
userId String? userId String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@index([evaluationId])
} }

View File

@@ -72,11 +72,12 @@ export async function createEvaluation(data: {
}, },
}); });
for (const dim of template.dimensions) { await prisma.dimensionScore.createMany({
await prisma.dimensionScore.create({ data: template.dimensions.map((dim) => ({
data: { evaluationId: evaluation.id, dimensionId: dim.id }, evaluationId: evaluation.id,
}); dimensionId: dim.id,
} })),
});
revalidatePath("/dashboard"); revalidatePath("/dashboard");
return { success: true, data: { id: evaluation.id } }; return { success: true, data: { id: evaluation.id } };
@@ -177,30 +178,33 @@ export async function updateEvaluation(id: string, data: UpdateEvaluationInput):
} }
if (dimensionScores && Array.isArray(dimensionScores)) { if (dimensionScores && Array.isArray(dimensionScores)) {
for (const ds of dimensionScores) { const validScores = dimensionScores.filter((ds) => ds.dimensionId);
if (ds.dimensionId) { if (validScores.length > 0) {
await prisma.dimensionScore.upsert({ await prisma.$transaction(
where: { validScores.map((ds) =>
evaluationId_dimensionId: { evaluationId: id, dimensionId: ds.dimensionId }, prisma.dimensionScore.upsert({
}, where: {
update: { evaluationId_dimensionId: { evaluationId: id, dimensionId: ds.dimensionId },
score: ds.score, },
justification: ds.justification, update: {
examplesObserved: ds.examplesObserved, score: ds.score,
confidence: ds.confidence, justification: ds.justification,
candidateNotes: ds.candidateNotes, examplesObserved: ds.examplesObserved,
}, confidence: ds.confidence,
create: { candidateNotes: ds.candidateNotes,
evaluationId: id, },
dimensionId: ds.dimensionId, create: {
score: ds.score, evaluationId: id,
justification: ds.justification, dimensionId: ds.dimensionId,
examplesObserved: ds.examplesObserved, score: ds.score,
confidence: ds.confidence, justification: ds.justification,
candidateNotes: ds.candidateNotes, examplesObserved: ds.examplesObserved,
}, confidence: ds.confidence,
}); candidateNotes: ds.candidateNotes,
} },
})
)
);
} }
} }