Transaction(node.js)

5BRack·2022년 9월 29일

백엔드 로드맵

목록 보기
15/28

Transaction

더이상 쪼개어 질 수 없는 업무처리단위
작업의 단위

  • 트랜잭션은 하나 이상의 SQL문을 포함하는 논리적이고 원자적인 작업 단위
  • 쉽게 말해 단일 작업을 수행하기 위해 명령문(쿼리)의 순차적 그룹
  • 트랜잭션이 성공적을 실행될 경우 COMMIT이라고 한다.
  • 트랜잭션의 원소 중 하나의 작업이 실패할 경우 ROLLBACK이 실행되어 트랜잭션 실행 전으로 상태를 되돌린다.

특징(ACID)

  1. 원자성(Automicity)
    다 성공할 것이 아니면 모두 다 실패
  • 트랜잭션 단위 내 모든 명령 또는 작업이 실행될 때 작업 실패시, 트랜잭션이 중단 되고 이전 상태로 돌아간다.
  1. 일관성(Consistency)
    같은 쿼리는 조회할 때마다 동일
  • 성공적으로 커밋이 될 경우에만 데이터베이스 상태를 변경할 수 있다.
  1. 격리성(Isolation) - 격리 수준 레벨이 존재
    트랜잭션이 진행중일 때는 해당 데이터에 대한 접근은 대기 상태
  • 각 트랜잭션의 내부의 각 작업은 독립적으로 작동해야 한다.
  1. 영구성(Durability)
    장애가 발생하여도 트랜잭션의 결과는 유지
  • 작업이 실패하거나 충돌할 경우에 커밋된 트랜잭션의 결과가 영구적으로 유지되어야 한다.

사용이유

  • 최악의 경우를 피하기 위하여 사용한다.
  • 트랜잭션을 만들어 처리하지 않고 하나씩 각 작업을 처리하다, 후위의 작업이 실패하게 되면 데이터 일관성이 깨지게 된다. 예) 결제 내역 테이블에는 데이터가 생겨났지만, 그 후 오류가 발생하여 유저의 포인트 데이터는 업데이트 되지 않았을시, 모두 ROLLBACK 시켜 데이터 일관성을 유지시킨다.

사용방법

//생성자에서 DataSource 객체 생성
 private readonly dataSource: DataSource,
   
   // 트랜잭션 사용방법
  	const queryRunner = this.dataSource.createQueryRunner();
	await queryRunner.connect();
       try{
			  await queryRunner.startTransaction();     // Transaction start
         	
         		queryRunner.manaer.메서드를 이용하여 DB 로직 처리
         
	 	      await queryRunner.commitTransaction();	 // 성공시, Commit
       }catch{
		      await queryRunner.rollbackTransaction();   // 오류시, RollBack
       }finally{
			  await queryRunner.release();				 // Transaction close
       }
	

DataSource의 메서드

  • createQueryBuilder : 복잡한 쿼리가 필요할 때 select() from() where() 등의 메서드를 사용하여 복잡한 쿼리를 사용할 수 있다.
  • createQueryRunner : 단순한 datasource 메서드를 사용할 때 사용
  • node에서 트랜잭션 사용시 내부에서 connection pool을 사용한다. transaction을 close 해주지 않아 max_connections 설정값보다 많은 사용자들이 접속 중인 상태에서 추가적인 접속을 하게 된다면 DB가 다운될 수도 있으므로 꼭 close를 해주어야 한다.

Isolation-Level

  • 격리 수준이 높아질 수록 안전성(격리성)이 높아지나, 속도(동시성)가 느려진다.
  • 1레벨(Read-Uncommitted) : Dirty-Read , Non-Repeatable-Read, Phantom-Read

  • 2레벨(Read-committed) : Non-Repeatable-Read, Phantom-Read

  • 3레벨(Repeatable-Read) : Phantom-Read

  • 4레벨(Serializable) : X

  • Dirty : commit이 되지 않은 것들이 조회되는 것 (일관성 무시)

  • Repeatable : 반복적으로 조회되는 시, 비일관적인 데이터가 조회되는 것 ex)외부에서 데이터 조작 (격리성 무시)

  • Phantom : 존재하지 않는 데이터가 조회되는 것

  • Serializable : 데이터에 대한 Rock을 걸어 트랜잭션이 종료될 때까지 데이터에 대한 접근을 하지 못하게 한다.(성능이 상대적으로 좋지 않다.)

    격리 수준이 높아질 수록 속도가 느려진다.

  • mysql은 기본 3레벨이 설정되어 있으면 3레벨에서 phantom-Read 까지 차단한다.

Isolation-Level 설정 방법

  • Transaction 시작 메서드의 매개변수로 설정한다.
await queryRunner.startTransaction('READ UNCOMMITTED');

낙관적락 과 비관적락

  • Serializable 설정시, 트랜잭션이 작업하는 데이터에 대한 접근을 락을 걸어버린다.
  • 낙관적락 : 동시에 데이터에 대한 접근이 될 수가 없다고 생각
  • 비관적락 : 동시에 데이터에 대한 접근이 될 수 있다고 생각 (공유락과 베타락이 존재)
  • 공유락 - Shared Lock(S-Lock) : 읽기 전용 - 쓰기 잠금
  • 베타락 - Exclusice Lock(X-Lock) : 읽기 잠금 - 쓰기 잠금

설정 방법

	await queryRunner.startTransaction('SERIALIZABLE');
  // 조회시 락을 걸고 조회함으로써, 다른 쿼리에서 조회 못하게 막음(대기시킴) => Select ~ For Update
      const payment = await queryRunner.manager.find(Payment, {
        lock: { mode: 'pessimistic_write' },
      });

정리 : isolation-level 및 락 설정을 상황에 맞게 적용하여야 한다.

데드락(Dead Lock)

  • 프로세스 1 과 프로세스 2가 서로 다음 단계에 있는 테이블에 락을 걸어놔서 교착 상태가 되어 버림
  • 교착 상태가 되면 직접 교착상태에 있는 프로세스를 종료시켜야 함
    해결법
  • 같은 데이터를 사용시, 비즈니스 로직의 순서를 맞춰준다.
  • DB의 컬럼의 용도에 따라 분리시킨다.

0개의 댓글