오류의 종류는 PrismaClientKnownRequestError, 오류 로그를 읽어보면 upsert문에서 발생한 것으로 error code는 2002, unique name constraints에 위배되는 작업을 했다고 한다.
즉 이미 저장하려고는 하는 name이 row에 존재하므로 create 작업을 할 수 없다는 이야기인데
Prisma에서 upsert는 create, update, where 필드를 지정하여 where 조건에 맞는 데이터가 존재하면 update를 없다면 create를 실행하는건데 어떻게 contraint 에러가 날 수 있는 거지?????라고 생각했다.
그래서 오늘도 공식문서와 구글링을 열심히 해봤는데 공식문서에 원인와 솔루션이 나와있었다.
문제 상황/전제 조건
원인
해결 방법
// index.ts
const pushGymToDatabase = async (gym: RockTaGym["Gym"]) => {
const { address, phone, brand, ...others } = gym;
return prisma.$transaction(async (tx) => {
const gymAddress = await tx.address.findFirst({
where: {
...address,
},
});
const gymInput = {
...others,
address: {
...(gymAddress
? { connect: { id: gymAddress.id } }
: { create: { ...address } }),
},
...(!phone.phoneNumber
? {}
: {
phone: {
connectOrCreate: {
create: { phoneNumber: phone.phoneNumber },
where: {
phoneNumber: phone.phoneNumber,
},
},
},
}),
brand: {
connectOrCreate: {
create: {
name: brand.name,
relatedBrand: brand.relatedBrand ?? null,
},
where: {
name: brand.name,
},
},
},
};
// 여기가 바로 문제의 upsert
await tx.gym.upsert({
create: gymInput,
update: gymInput,
where: {
name: others.name,
},
});
});
};
// modules/retryCatch.ts
export async function retryCatch<T>(
callback: () => Promise<T>, // 메인 콜백함수
retryCondition?: (error: any) => boolean, // 조건 함수
times = 1 // retry 횟수 (여기서는 1번만 필요하니 1로 기본값을 둔다.)
): Promise<T> {
try {
return await callback();
} catch (error) {
// error retry times가 유효하고, retryCondition에 부합하는 에러이면 함수를 다시 실행한다.
if (times > 0 && retryCondition?.(error)) {
return await retryCatch(callback, retryCondition, times - 1);
} else {
// 그 이외의 경우에는 error을 throw 한다.
throw error;
}
}
}
// index.ts
import { retryCatch } from "modules/retryCatch";
...
const pushGymToDatabase = async (gym: RockTaGym["Gym"]) => {
const { address, phone, brand, ...others } = gym;
return retryCatch(
() =>
prisma.$transaction(async (tx) => {
const gymAddress = await tx.address.findFirst({
where: {
...address,
},
});
const gymInput = {
...others,
address: {
...(gymAddress
? { connect: { id: gymAddress.id } }
: { create: { ...address } }),
},
...(!phone.phoneNumber
? {}
: {
phone: {
connectOrCreate: {
create: { phoneNumber: phone.phoneNumber },
where: {
phoneNumber: phone.phoneNumber,
},
},
},
}),
brand: {
connectOrCreate: {
create: {
name: brand.name,
relatedBrand: brand.relatedBrand ?? null,
},
where: {
name: brand.name,
},
},
},
};
await tx.gym.upsert({
create: gymInput,
update: gymInput,
where: {
name: others.name,
},
});
}),
// retry 여부를 판단하는 함수를 넣어주었다.
// error 객체 code 값에 P2002를 넣으면 첫 번째 파라미터에 넣은 Promise 함수를 재실행한다.
(error) => error?.code === "P2002" // race condition error
);
};