TypeOrm으로 Union 쓰기

InnomDB·2022년 7월 12일
2

typeorm

목록 보기
2/5

비상!! 비상!! slow query 발생!!

서비스 운영중 slow query가 발생했습니다.

서비스 운영 중에 slow query가 발생하면 어떡해야 할까요? 음음~ 사용자들에게 인내와 끈기를 알려줘야지 하면서 그대로 두실건가요?? 개발자마다 다르겠지만 저는 바로 SQL 튜닝에 들어갑니다.

slow query가 발생하는 이유는 여러가지가 있겠지만 저의 경우에는 OR 조건이 문제였습니다.
삼중 JOIN을 한 데이터를 가지고 다른 테이블의 컬럼들로 OR 조건을 걸어버려 INDEX를 타지 못해 발생한 문제였습니다.

여러분은 OR 조건이 INDEX를 타지 못할때의 조건을 아시나요?? 이 부분은 무척 중요합니다.

예를 들어 드리겠습니다.

SELECT * from articles where author = '이놈' or author = '디비'

아래와 결과가 똑같습니다. (저는 같은 컬럼이라면 IN을 씁니다. OR 별로 안좋아합니다.)

SELECT * from articles where author IN ('이놈','디비')

위와 같은 상황에서는 INDEX를 사용할 수 있습니다. 옵티마이저가 알아서 기깔나게 사용해서 결과를 줍니다.

!!!하지만 아래의 경우는 INDEX를 사용할 수 없습니다.!!!

SELECT * from articles where author = '이놈' or title = 'TypeOrm'

타고싶다.. Index 하지만 탈 수 없어...

author에 Index가 있고 title에도 인덱스가 있다고 가정합니다.
Mysql은 2개의 인덱스 중 어떤 인덱스를 사용해야 하는지 모릅니다. 만약 author의 index를 사용한다면 title에 대해서는 table-scan을 하며 'TypeOrm'을 찾아야 합니다 ㅠㅠ 반대의 상황도 마찬가지 입니다.
title에 대해서 index를 사용하면 author에 대해서 table-scan을 해야합니다.
그냥 눈물만 납니다. 데이터양이 수천만개라면 모니터를 부수는게 더 빠릅니다.

게다가 저의 상황은 또 같은 테이블도 아닌 다른 Join의 다른 열의 조건을 검사하니 이건 뭐 시간 더 걸립니다. 이것을 해결하기 위한 방법들이 있지만 저는 Union을 선택했습니다.

Union을 사용하면 각각의 조건들을 나누어 Index를 사용해 결과를 뽑아낸다음 합칩니다.
이게 합쳐질 때 중복을 제거하기 위해선 결과 집합을 정렬해야 하는데 이렇게 실행하면 쿼리 실행 속도가 느려집니다. 허나, 인덱싱된 조회에 의해 결과 집합이 작아지는게 일반적으로 table-scan보다 저렴합니다.
저의 경우도 훨씬 저렴해져 1초 이상을 줄이는데 성공했습니다.

결과적으로 사용자가 직접 EXPALIN하면서 자신의 상황에 맞게 튜닝하는게 BEST!! 아잉교?

TypeOrm에선 RawQuery로...

typeorm뭐 이런기능도 없나요?? 너무 후졌어요

제가 했던 방식만 알려드리겠습니다.. 이거 정답아닙니다. 하지만 굴러는 갑니다

const tableUn = this.createQueryBuilder().select()
const tableTg = this.createQueryBuilder().select()

const [sql1, params1] = tableUn.getQueryAndParameters();
const [sql2, params2] = tableTg.getQueryAndParameters();

const rawQuery = await getManager().query(
      `(${sql1}) UNION (${sql2})`,
      [...params1, ...params2, limit],
);

return rawQuery;

큰 틀은 위와 같고 뭐 중간중간 쿼리문을 입맛에 맛게 스까 드시면 됩니다.

const tableUn = this.createQueryBuilder().select().where();
const tableTg = this.createQueryBuilder().select().where();

if (author) {
  tableUn.andWhere('author =:author ', { author });
}
tableTg.andWhere('쿼리문', { 조건값 });
const [sql1, params1] = tableUn.getQueryAndParameters();
const [sql2, params2] = tableTg.getQueryAndParameters();

const rawQuery = await getManager().query(
      `(${sql1}) UNION (${sql2})`,
      [...params1, ...params2, limit],
);

return rawQuery;

대충 위와 같은 방식으로 사용하시면 됩니다.
근데 이렇게 작성하면 코드 진짜 지저분하고 더러워질뿐만 아니라 entity mapping도 안되서 받을 때 Class Type이 아니라 plain Object로 반환됩니다. 저는 데이터 왔다갔다 할때 Class Type이 좋아서
this.Repository.create(rawQuery) 형식으로 다시 Class Type으로 변환해줍니다.

각 자 입맛에 맞게 잘 사용합시다!! 그럼 20000~

참고: 스택이 터졌어요
github어딘가

profile
이노오오옴

0개의 댓글