개발 이슈

[TypeORM] transaction 이 깨질 때 해결방법

주인장 꼬비 2023. 1. 12. 00:07

회사에서 개발을 하던 도중 한 테이블의 특정 컬럼이 잘못되었다는 것을 발견했었다. 해당 컬럼이 잘못 들어간 것을 확인하고 나서 바로잡기 위해서 migration 스크립트를 만들어서 별도의 프로세스로 돌렸는데 DEAD_LOCK이 발생하면서 트랜잭션이 깨지는 문제가 발생하였다. 

 

프로세스 A : 초단위로 a, b, c 테이블에 insert or update 하는 역할 + temp table 을 create 하고 데이터를 insert 했다가 d 테이블에 insert 하는 역할

프로세스 B : a 테이블을 처음부터 N개씩 읽고 필요한 데이터를 b에 insert or update 하는 역할

-> b 테이블에서 잘못 들어간 특정 컬럼을 바로잡기 위해서 프로세스 B를 돌리다가 DEAD_LOCK이 발생하면서 프로세스 A의 트랜잭션이 깨지는 문제가 발생

 

 

원인

1. transaction 안에서 create / delete 테이블을 함.

2. transaction 안에서 성능 개선을 위해 Promise.all 을 사용함

 

해결

1. transaction 안에서 create / delete 테이블을 하지 않게 하였다. create table, alter table, drop table 등의 DDL, DCL 은 auto commit이 되기 때문에 typeORM에서 하나의 트랜잭션으로 묶었다 한들 중간에 커밋된다.

2. typeORM 를 사용할 때 Promise.all로 insert/update/delete를 하면 안 된다. 퍼포먼스 때문에 promise.all로 처리하려고 했는데 promise.all을 사용하면 안 된다고 한다. (https://github.com/typeorm/typeorm/issues/1014)

 

export default class Test {
  constructor(private connection: Connection) {}

  async writeDB(a, b, c): Promise<void> {
      await this.connection.transaction(async (entityManager) => {
        try {
          await entityManager.createQueryBuilder().insert().into(a).values(~~~).updateEntity(false).execute();
          await entityManager.createQueryBuilder().insert().into(b).values(~~~).updateEntity(false).execute();
          await entityManager.createQueryBuilder().insert().into(c).values(~~~).updateEntity(false).execute();
        } catch(err) {
          console.error(`db error`, err);
          throw err;
        }
      });
    } 
  }
}