도커 서버를 5개로 늘린 이후 대시보드에 채점 결과가 업데이트 되지 않는 문제가 발생했다.

원인을 찾아 본 결과, 채점 서버에서 들어오는 요청을 처리하는데 비동기?벙렬?적으로 처리하는 바람에 문제가 발생한 것이다.

캡처.PNG

// 가져오기
const submission = await this.submissionRepository.findOneBy({
  id: scoreResultDto.submissionId,
});
if (!submission) throw new NotFoundException('제출 기록이 없습니다.');

const result = {
  testcaseId: scoreResultDto.testcaseId,
  result: scoreResultDto.result,
  timeUsage: scoreResultDto.timeUsage,
  memoryUsage: scoreResultDto.memoryUsage,
};

submission.detail.push(result);
// 저장
await this.submissionRepository.save(submission);

채점 서버가 채점이 완료되면 api 서버에 요청을 보낸다.

api 서버는 요청을 받아 처리하는데, 각 요청을 개별적으로 처리한다. (https://stackoverflow.com/questions/58102508/concurrency-in-express?rq=3)

문제 시나리오

  1. 1번 테스트 케이스에서 제출 데이터를 db에서 가져와 result 부분을 수행하고 있을 때, 2번 테스트 케이스에서 제출 데이터를 db에서 가져온다.
  2. 1번 테스트 케이스에서 저장을 수행하고, 2번 테스트 케이스에서 마찬가지로 저장을 수행한다.
  3. 이렇게 되면 1번 테스트케이스 정보가 증발해버리는 경쟁조건이 발생한다.

해결

typeorm에서 save를 사용하여 데이터 수정을 하면, select와 update, 두 쿼리가 실행된다.

save → update로 수정해 주었다.

문제를 해결해주기 위해 트랜잭션 코드를 추가해 주었다.

let submission: Submission;
const result = {
  testcaseId: scoreResultDto.testcaseId,
  result: scoreResultDto.result,
  timeUsage: scoreResultDto.timeUsage,
  memoryUsage: scoreResultDto.memoryUsage,
};

const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();

try {
  submission = await queryRunner.manager.findOneBy(Submission, {
    id: scoreResultDto.submissionId,
  });
  if (!submission) throw new NotFoundException('제출 기록이 없습니다.');

  submission.detail.push(result);

  await queryRunner.manager.update(Submission, submission.id, { detail: submission.detail });
  await queryRunner.commitTransaction();
  await queryRunner.release();
} catch (error) {
  await queryRunner.rollbackTransaction();
  await queryRunner.release();
  throw error;
}