SMAIVNN
article thumbnail
Published 2024. 4. 27. 00:52
[Nest.JS] TypeORM 삽입 최적화 Web

TypeORM을 활용하며 데이터를 삽입할 때 주로.save()를 사용하곤 합니다.

하지만 성능 최적화를 위해서 .insert().update()를 적극 활용하면 좋은데요. 단순하게 계산하면 2배 이상 차이나는 이 메서드들에 대해 알아보려고 합니다.

 

1. .save()의 동작

.save()는 아래와 같이 동작을 합니다.

  1. 아직 존재하지 않는 경우 엔티티를 삽입한다.
  2. 엔티티가 존재하는 경우 기존 엔티티를 업데이트 하려고 시도한다.

그럼 이미 존재하는지에 대한 확인은 어떻게 할까요?
우선 SELECT 쿼리를 통해 엔티티 객체에 정의된 Primary Key (기본 키)를 사용하여 데이터베이스 내 해당 엔터티의 존재 여부를 확인합니다.

만약 없다면 TypeORM은 이를 새로운 엔티티로 간주하고 삽입 작업을 수행합니다. 만약 엔티티가 존재하는 것으로 확인되면, .save() 메소드는 전달된 엔티티 객체의 모든 필드를 기존 데이터베이스 레코드의 필드와 비교하여 변경된 부분만을 업데이트합니다.
이 과정에서 INSERTUPDATE가 활용됩니다.

또한 .save() 메소드는 실행 전 트랜잭션이 사용됩니다. 따라서 데이터의 무결성에 유용합니다. (트랜잭션은 옵션을 통해 실행하지 않을 수 있습니다.)

코드로 확인하면 아래와 같다고 할 수 있습니다.

<code />
START TRANSACTION SELECT COUNT(*) FROM "user" WHERE "id" = 1; UPDATE "user" SET "name" = 'John Doe' WHERE "id" = 1;-- 존재한다면 INSERT INTO "user"("id", "name") VALUES (1, 'John Doe');-- 존재하지 않는다면 COMMIT -- await this.userRepository.save(User, { transaction: false }); SELECT COUNT(*) FROM "user" WHERE "id" = 1; UPDATE "user" SET "name" = 'John Doe' WHERE "id" = 1;-- 존재한다면 INSERT INTO "user"("id", "name") VALUES (1, 'John Doe');-- 존재하지 않는다면

 

2. .insert()와 .update()

반면 .insert().update() 메서드는 주어진 엔티티 객체를 데이터베이스에 삽입합니다. 단순하게 INSERTUPDATE 쿼리만을 사용합니다. 엔티티의 존재 여부를 확인하지 않기 때문에 에러 가능성이 발생하나, 서비스 레이어에서 대부분 삽입 전 조회 및 검증을 사용하기에 괜찮다고 생각듭니다.

코드로 확인하면 아래와 같다고 할 수 있습니다.

<code />
INSERT INTO "user"("id", "name") VALUES (1, 'John Doe');

단순하게 동일한 수의 .save().insert() 사용했다고 생각하면 2배 이상의 데이터베이스 접근이 일어나는 것입니다.

 

3. 언제 사용할까?

그렇다면 이 두가지를 언제 구분하여 사용하면 좋을까요?
.save()는 새로운 레코드의 삽입 이후 업데이트 된 완전한 엔티티의 상태를 반환받습니다.

즉 아래와 같은 코드를 작성하고 해당하는 데이터를 리턴받을 수 있습니다.

<code />
const newUser = await this.userRepository.save(user); // newUser.추가적인 작업 return newUser;

반면 .insert().update() 메서드의 경우 삽입 작업의 결과로 생성된 기본 키 값, 삽입된 레코드의 수와 같은 메타데이터를 포함하는 결과 객체인 insertResult, updateResult를 반환받습니다. 이는 엔티티를 추가적으로 가공할 수 없다는 것입니다.

<code />
const newUser = await this.userRepository.insert(user); return newUser; /* * newUser : * { * "identifiers": [ * ... * ], * "generatedMaps": [ * ... * ], * "raw": { * "fieldCount": 0, * "affectedRows": 1, * ... * } * } */

따라서, 저는 추가적으로 엔티티를 활용해야할 때, .save()를 활용하고 N : M 관계의 중간테이블과 같이 단순하게 생성되면 되는 부분에서 .insert().update()를 활용하고 있습니다.

 

RDBMS를 사용하며 성능에 대해서 더욱 다양하게 고려해보게 되는것 같습니다. 추가적으로 성능을 향상시킬 수 있는 방법이 있다면 댓글 부탁드립니다.

profile

SMAIVNN

@SMAIVNN

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!