일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- java
- 블록체인
- Nestia
- 도서
- nodejs
- spring
- 리뷰
- terraform
- 글또
- blockchain
- 유데미
- terraform cloud
- gradle
- TypeScript
- docker
- 이더리움
- restdocs
- Database
- corretto
- 백엔드
- mysql
- Redis
- Mocha
- ChatGPT
- nestjs
- IAC
- chai
- class-transformer
- typeorm
- 온라인강의
- Today
- Total
끄적끄적
class-transformer 에 의한 Nest.js 성능 문제 본문
한국에서 nestjs 로 개발하는 개발자라면 typia 에 대해서 한번쯤은 들어봤을 것 같다. 이 라이브러리에서 문제로 지적하는 부분이 nestjs 공식문서 예시로 제시하는 class-validator 가 성능적으로 문제가 크다는 것이다. 때문에 이러한 느린 라이브러리의 문제로 "typescript(javascript) 가 느리다" 라는 잘못된 인식까지 퍼질 수 있다고도 한다. 특히나 대부분의 nestjs 개발자라면 class-transformer + class-validator 기반으로 request 검증을 할테고, 객체 transform 액션에도 class-transformer 를 적극적으로 활용하고 있을 것이라 느리다는 인식이 더 커질 수 있을 것 같다.
여기서 성능이 느려지는 가장 큰 이유중 하나는 class-transformer 가 객체를 변환하는데 매우 느리기 때문이다. 필자의 회사에서도 class-transformer 를 주로 사용고 있었는데 대량 발행 등의 액션을 할 때 조금씩 이슈들이 생겨 최근에 이 라이브러리를 최대한 걷어내는 작업을 진행하고 있다. 평상시에 트래픽이 많이 몰리지 않을때는 크게 문제가 없었지만 확실히 많은 객체를 처리하다 보니 이 라이브러리만 걷어내도 꽤 큰 성능적 이점을 가져갈 수 있었다. 그래서 이번에 간단한 성능테스트를 하면서 단순 transform 작업시 얼마나 차이가 나는지에 대해 테스트를 해보고자 한다.
테스트 준비
테스트할 액션은 "변환" 과정이다. 3-layered 아키텍처 혹은 hexagonal 아키텍처 사용시 DB Model ↔ Domain Model 변환은 자주 일어나는 액션 중 하나이므로 이를 테스트해보고자 한다. (API call 또한 비슷한 액션이라 가정할 수도 있다.) 아래와 같이 세 가지 변환 로직을 테스트해보고자 한다.
- 객체 → 객체
- 객체 → 클래스 (직접 생성)
- 객체 → 클래스 (class-transformer 사용)
변환에 사용한 객체 형태는 아래와 같다. 이 객체는 단순 테스트를 위해 씨아이보드 메뉴얼에 존재하는 Member 객체를 참고했다.
export interface IMember {
id: string;
userid: string;
email: string;
password: string;
username: string;
nickname: string;
level: number;
homepage: string;
phone: string;
birthday: string;
sex: string;
zipcode: string;
address1: string;
address2: string;
address3: string;
address4: string;
receiveEmail: boolean;
receiveSms: boolean;
useNote: boolean;
openProfile: boolean;
denied: boolean;
emailCert: boolean;
registeredAt: Date;
registeredIp: string;
lastLoginAt: Date;
lastLoginIp: string;
isAdmin: boolean;
profileContent: string;
adminMemo: string;
following: number;
followed: number;
icon: string;
photo: string;
}
export class Member implements IMember {
// IMember 상속 및 contstructor 로직만 존재
}
그 후 Repository 클래스에서는 IMember 객체가 DB 에서 가져오는 로직이라 가정하고 각각의 지정된 형태로 변환하는 과정을 거쳤다. 위에서 테스트하고자 하는 세 가지 형태를 지녔으며 단순 테스트를 위한 용도이므로 각각의 메서드로 만들었다.
export class MemberRepository {
private getSampleDbMemberModel(id: string): IMember {
return {
// 객체에 맞는 랜덤값 지정
// ...
}
}
async getMemberByIdWithObject(id: string) {
const member: IMember = this.getSampleDbMemberModel(id);
return {
id: member.id,
userid: member.userid,
email: member.email,
// ...
}
}
async getMemberByIdWithClass(id: string) {
const member: IMember = this.getSampleDbMemberModel(id);
return new Member({
id: member.id,
userid: member.userid,
email: member.email,
// ...
});
}
async getMemberByIdWithClassTransformer(id: string) {
const member: IMember = this.getSampleDbMemberModel(id);
return plainToInstance(Member, member);
}
}
그 후 테스트를 통해 시간을 측정해 보았다.
테스트 결과
테스트 결과는 아래와 같다. 각각 테스트마다 100번정도 테스트를 한 후 평균을 낸 값이며 ms 단위로 측정했다.
변환 갯수 | interface (Object) | new Class | class-transformer |
10 개 | 0.11 (ms) | 0.12 (ms) | 0.74 (ms) |
100 개 | 0.56 (ms) | 0.7 (ms) | 3.58 (ms) |
1000 개 | 5.38 (ms) | 5.25 (ms) | 28.26 (ms) |
10000 개 | 44.51 (ms) | 50.04 (ms) | 249.49 (ms) |
100000 개 | 436.65 (ms) | 458.36 (ms) | 2451.48 (ms) |
단순한 테스트만 해봤음에도 결과는 처참하다. transformer 사용시 6배 정도의 성능 저하가 발생하는 것을 확인할 수 있다. 만약 class-validator 를 사용한다면 결과는 더 처참해질 것이 분명해 보인다. 특히나 이 테스트는 단순 변환 테스트라서 5~6 배 정도밖에 차이가 나지 않았지만 typia 라이브러리에서 진행한 결과를 보면 성능차이가 더 벌어진 것을 확인할 수 있다.
결론
매퍼 라이브러리가 성능이 좋지 않다는 것은 당연히 알고 있었지만 처음 개발할 때는 대부분의 성능 저하의 경우 DB 에서 발생한다고 생각했기 때문에 class-transformer 를 적극적으로 사용했었다. 하지만 최근에 성능 최적화를 해야 하는 상황이 발생했고 class-transformer 에 의해 성능이 떨어질 수 있다는 소리를 들어 실제로 테스트 해보니 class-transformer 만 제거해도 평균적으로 2~3 배 정도 최적화 할 수 있었다.
class-transformr 의 경우 여러 다른 기능들도 포함하고 있어 무조건적으로 나쁘다 라는 것은 아니지만 경험상으로 대부분의 경우 매퍼 라이브러리를 사용하지 않아도 충분히 해결할 수 있는 부분이 많았다. 오히려 라이브러리 사용시 디펜던시가 꼬이게 되면서 예상과 다른 transform 을 하는 현상도 발생해서 해결하는데 어려움을 겪기도 했다. 현재 레거시 코드가 많아 request 검증에는 어쩔 수 없이 class-validator + class-transformer 를 사용하고 있지만 다른 대부분의 코드에서는 제거한 상황이며 request 검증의 경우에도 제거하기 위한 사전작업을 진행중이다. 물론 PoC 상황에서는 빠르게 개발해야 하니 사용하는 걸 권장할 수 있지만 어차피 변경해야 하는 라이브러리임이 분명해 보여 프로덕션 레벨에서는 최대한 안쓰는 것이 더 좋은 방향이 아닐까 싶다.
'개발 > js & ts & node.js' 카테고리의 다른 글
Repository 의 추상화 (2) | 2023.12.23 |
---|---|
[NestJS] custom parameter decorator (6) | 2023.10.27 |
Typescript type 을 활용해 BIP44 Path 를 컴파일 타임에 검사하기 (0) | 2023.07.16 |
ChatGPT API 사용해보기 (0) | 2023.03.26 |
백엔드 DTO 를 공유받고 싶어요. (0) | 2023.02.26 |