안녕하세요. 주니어 백엔드 개발자 입니다.
심바 블로그
안녕하세요. 주니어 백엔드 개발자 입니다.
>> Github 바로가기
  • 분류 전체보기 (31)
    • AWS (3)
    • DATABASE (2)
    • Docker (1)
    • Architecture (3)
    • GraphQL (5)
    • JS (1)
    • NestJS (8)
    • 테스트 코드 (3)
    • 세미나 (4)
    • 기타 (1)
    • 이전 블로그 자료들 (0)

블로그 메뉴

  • 홈
  • 방명록

공지사항

인기 글

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
안녕하세요. 주니어 백엔드 개발자 입니다.

심바 블로그

DATABASE

TypeORM에서 불필요한 SELECT 실행

2023. 6. 12. 10:52
반응형

현재 회사에서 데이터베이스를 다룰 일이 있으면 TypeORM + MySQL으로 조합해서 사용하고 있다. ORM 특성상 객체지향적으로 데이터베이스를 다룰 수 있기 때문에 좀 더 직관적이고 쿼리 짜는 게 편리한 장점이 있다. 하지만 작성되어 있는 ORM 쿼리 실행문을 보다 보면 불필요한 SELECT 쿼리가 실행되는 문제가 있었다. 

 

1. 불필요한 SELECT 문 실행 문제

아래는 불필요한 SELECT 문이 실행되는 부분의 코드이다. 

 

const result = await this.hashTagsRepository.findOne({ name: 'test1' });
result.name = 'test2';
await this.hashTagsRepository.save(result);

 

ORM을 사용해서 특정 데이터를 찾아오고, 해당 데이터의 값을 변경 후 save() 하는 로직이다. ORM를 사용하게 되면 이런 직관적인 객체형태의 쿼리를 사용할 수 있게 되는데 내부에서 실행되는 로우쿼리를 보면 아래처럼 SELECT 문이 두 번 실행되는 문제가 있었다.

 

SELECT `HashTags`.`id` AS `HashTags_id`, `HashTags`.`name` AS `HashTags_name` FROM `HashTags` `HashTags` WHERE `HashTags`.`name` = ? LIMIT 1 -- PARAMETERS: ["test1"]
SELECT `HashTags`.`id` AS `HashTags_id`, `HashTags`.`name` AS `HashTags_name` FROM `HashTags` `HashTags` WHERE `HashTags`.`id` IN (?) -- PARAMETERS: [12]
START TRANSACTION
UPDATE `HashTags` SET `name` = ? WHERE `id` IN (?) -- PARAMETERS: ["test2",12]
COMMIT

 

해당 로우 쿼리를 보면 TypeORM의 save() 메서드는 특정 아이디 값으로 데이터를 찾아와 바뀐 객체를 저장하는 걸 알 수 있다. 찾아보니 PK가 있는 데이터를 저장할 때 id값으로 데이터를 찾은 후 업데이트 하는 현상이라고 하는데 사실 SELECT 문은 findOne() 부분에서 이미 한 번 실행됐기 때문에 save()에서 반복적으로 실행될 경우 리소스가 낭비되는 문제가 있었다.

 

2. findOne, save VS update

사실 지금까지는 쿼리로 찾아온 데이터를 객체로 관리하는 게 훨씬 편하다고 느껴서 findOne() -> save()의 형태를 사용했었다. 하지만 불필요한 SELECT 문의 실행이 있기 때문에 update() 메서드로 변경하고 두 조건의 속도를 비교해 봤다.

 

3. findOne, save 속도 측정

먼저 쿼리 작성 부분과 속도 측정 부분 코드이다. 속도 측정은 JS 내장 객체 performance 사용해서 측정했다.

    // 데이터베이스 쿼리 부분
    
    async test2() {
        const result = await this.hashTagsRepository.findOne({ name: 'test1' });
        result.name = 'test2';
        await this.hashTagsRepository.save(result);
    }
    
    
    // 속도 측정 부분
    
    async test2() {
        const start = performance.now();
        
        await this.tagsService.test2();
        
        const end = performance.now();
        
        const executionTime = end - start;
        console.log(executionTime);
    }

 

아래는 실행된 로우 쿼리와 속도 결과 값이다.

 

SELECT `HashTags`.`id` AS `HashTags_id`, `HashTags`.`name` AS `HashTags_name` FROM `HashTags` `HashTags` WHERE `HashTags`.`name` = ? LIMIT 1 -- PARAMETERS: ["test1"]
SELECT `HashTags`.`id` AS `HashTags_id`, `HashTags`.`name` AS `HashTags_name` FROM `HashTags` `HashTags` WHERE `HashTags`.`id` IN (?) -- PARAMETERS: [19]
START TRANSACTION
UPDATE `HashTags` SET `name` = ? WHERE `id` IN (?) -- PARAMETERS: ["test2",19]
COMMIT

// 속도 측정 결과 : 23.628125190734863

 

 

4. update 속도 측정

쿼리 작성은 createQueryBuilder() 사용해서 작성했다. where 조건으로 name이 test1 인걸 찾아서 test2로 변경하는 쿼리이다.

 

    // 데이터베이스 쿼리 부분
    
    async test3() {
        await this.hashTagsRepository
            .createQueryBuilder()
            .update(HashTags)
            .set({ name: 'test2' })
            .where('name = :name', { name: 'test1' })
            .execute();
    }
    
    
    // 속도 측정 부분
    
    async test3() {
        const start = performance.now();
        
        await this.tagsService.test3();
        
        const end = performance.now();
        
        const executionTime = end - start;
        console.log(executionTime);
    }

 

아래는 로우 쿼리와 속도 결과 값이다. update 쿼리의 경우 SELECT 문의 실행 없이 바로 UPDATE 쿼리를 통해서 데이터를 업데이트하고 있어 속도 결과가 좀 더 빠르게 나왔다.

UPDATE `HashTags` SET `name` = ? WHERE `name` = ? -- PARAMETERS: ["test2","test1"]

// 속도 측정 결과 : 14.257957935333252

 

이처럼 update 쿼리로 변경해 쿼리 실행 속도가 빨라지는 것을 볼 수 있었다. 사실 결과만 놓고 보면 너무 당연한 거라고 생각할 수 있지만 별생각 없이 사용하던 TypeORM 쿼리 메서드들도 정말 단순한 방법으로 성능을 향상할 수 있다는 게 놀라웠다. 앞으로 단순히 기능 구현만 돌리지 않고 ORM 사용환경에서도 최대한의 최적화를 진행해 봐야겠다.

반응형

'DATABASE' 카테고리의 다른 글

Data Mapper 패턴, Active Record패턴  (0) 2023.02.24
    'DATABASE' 카테고리의 다른 글
    • Data Mapper 패턴, Active Record패턴
    안녕하세요. 주니어 백엔드 개발자 입니다.
    안녕하세요. 주니어 백엔드 개발자 입니다.

    티스토리툴바