[Study] Transaction

조혜인·2022년 8월 22일
post-thumbnail

서비스를 제공할 때 가장 큰 문제점은 바로 데이터의 오염이다. 서비스의 안정성은 유지하고있지만 데이터가 오염되면 절대 안됀다. 예를 들어 데이터를 저장하는 모듈이 진행 중에 에러로 인하여 종료되었을 때 어떤 데이터는 성공하여 DB에 저장되었고 어떤 데이터는 저장되지 못하였을 때와 같은 상황을 데이터가 오염되었다고 한다. 데이터가 오염이 될 바에는 차라리 모든 기능을 실패하고 다시 시도하는 것이 안전하다. 이렇게 DB가 꼬이는 상황은 절대적으로 피하기 위해 사용하는 것이 바로 Transaction(트랜잭션)이다.

📌 Transaction(트랜잭션)

  • 트랜잭션이란 처리되는 작업의 단위로 기능들을 하나로 묶는 것을 의미한다.

  • 트랜잭션은 묶여져 있는 기능 모두가 전부 성공하던지 아니면 전부 다 실패하던지 둘 중 하나가 가능하도록 해준다. 좀 더 자세히 설명하자면 서로 다른 트랜잭션들을 처리하는 도중 하나의 단위 트랜잭션에서 에러가 발생한다면 이전에 성공했던 트랜잭션들을 다시 rollback시켜 데이터의 일관성이 깨지지 않도록 해주는 것이다. 모두 성공하였을 경우에는 commit을 통해 확정지어주게 된다.

Example

프론트엔드(클라이언트)에서 결제 완료 저장 API가 요청되면 백엔드(Server)에서는 트랜잭션을 시작하여 결제 정보 저장 로직을 실행한다. API가 진행되는 와중에 에러가 발생되지 않았다면 commit 성공을 확정시켜준다. 이 순간 트랜잭션에 저장된 작업들은 수행되게 된다. 만약 에러가 발생되었다면 rollback으로 진행된 트랜잭션 작업들을 원상복구시킨다.

아래는 직접 트랜잭션을 사용하여 작성한 코드이다.

async create({ impUid, amount, user: _user }) {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
  
  	/* 트랜잭션 시작 */
    await queryRunner.startTransaction();

    try {
      //1. PointTransaction 테이블에 거래기록 1줄 생성
      const pointTransaction = this.pointTransactionRepository.create({
        impUid,
        amount,
        user: _user,
        status: POINT_TRANSACTION_STATUS_ENUM.PAYMENT,
      });
      //queryRunner에 임시저장
      await queryRunner.manager.save(pointTransaction);

      //2. 유저의 돈 찾아오기
      const user = await this.userRepository.findOne({
        where: { id: _user.id },
      });

      //3. User의 돈 업데이트
      const updatedUser = await this.userRepository.create({
        ...user,
        point: user.point + amount,
      });
      //queryRunner에 임시저장
      await queryRunner.manager.save(updatedUser);

      //commit 성공 확정!
      await queryRunner.commitTransaction();

      //4. 최종결과 프론트엔드에 돌려주기
      return pointTransaction;
    } catch (error) {
      //에러로 인해 rollback
      await queryRunner.rollbackTransaction();
    } finally {
      //성공, 실패 모두 무조건 실행되는 구간으로 한 번 connect된 연결은 relase해주어야한다.
      await queryRunner.release();
    }
  }
profile
코딩은 역시 재밌군

0개의 댓글