새소식

인기 검색어

개발일기

쿼리에 집착하지 않기

  • -

들어가면서

최근에 쿼리 자체로만 해결할 수 없는 일인데 쿼리에 집착해서 시간을 잡아먹었던 일이 2가지 있었다. 아무래도 쿼리는 수치로 얼만큼 느린지 바로 보이기도 하고, DB 영역이 문제 발생시 원인이 되는 주 영역이기 때문에 더욱 신경쓰느라 그런 것 같다.

 

이번 일기에서는 해당 사건들을 서술하고, 배운 점들에 대해 적어보았다.

 

 

사례 1: 쿼리 튜닝 말고 주어진 환경 이해하고 변경하기

재고의 수를 동기화하는 API에서 자꾸 timeout이 발생해서 동기화 태스크가 실행 안 된다는 문제가 있었다. 직접 실행하다 보니, 이 문제는 동기화 과정에서 특정 조건에 대한 COUNT와 column에 대해 GROUP BY SELECT하는 복합 쿼리가 문제였다.

 

나는 해당 쿼리의 문제점이 1. GROUP BY에 의해 임시 테이블 생성 2. JOIN하는 드라이빙 테이블의 row 수가 너무 많음. 이 문제라고 생각했고, 이를 해결하기 위해 (동작은 제대로 하고 있으니) 쿼리는 같게 하면서 인덱스를 바꾸거나 물리적 JOIN(sort-merge / hash join)으로 바꿔보는 등의 방법을 채택하도록 힌트를 사용하던가 하는 전략을 생각해 보았다.

 

이때 문제점은 django ORM에서 힌트를 주거나 물리적 JOIN 방법을 바꾸거나 하는 방법을 사용하려면 RAW SQL을 사용해야 한다는 것이었다.

 

그래서 이 방법으로 쿼리를 개선해서 해결해 보는 것은 어떤가 하고 리뷰를 부탁드렸더니 들었던 대답은 "혹시 이 쿼리가 이 API에서 어떤 걸 위해 사용되는 것인지 아시나요?" 였다.

 

부끄럽게도 이 API의 내용이라던가 SQL 내용을 확인해볼 생각은 하지 못했었다. 이 태스크의 목적은 "timeout을 해결"이라고만 생각해, 기존 동작은 유지하되 성능만 개선하면 된다고 생각했던 것이다.

 

리뷰어분이 이어서 설명해 주신 것은 현재 이 쿼리가 API에서 하는 동작은 당일 배송 주문에 대해서 걸러내는 로직인데, 내부 정책에 따라 당일 배송 대상이 아닌 주문에 대해 걸러내기 위한 쿼리라고 하셨다.

 

그런데 이 쿼리 도중에 예전 주문에 대한 정합성을 맞추기 위한다는 이유로 걸러내는 범위가 너무 넓은 것이 문제라고 하셨다. 그래서 JOIN에 대한 드라이빙 테이블의 rows가 너무 많아 쿼리가 느린 것이었다.

 

해당 정책을 확인해 보니, 걸러내는 대상이 "현금 결제" 신청으로 인해 주문은 했지만 결제는 태스크 실행 시점에 이루어지지 않아 당일 배송이 불가한 경우를 의미하고 있었다.

 

또한 내부에서 현금 결제는 24시간 이내에 이루어지지 않으면 안 되는 것이 정책이였다. 따라서, 이전 쿼리처럼 X달 동안의 주문을 전부 뒤질 필요가 없던 것이였다.

 

리뷰어 분은 버퍼 기간에 따라 7일 이내로 WHERE로 필터링하는 것은 어떻겠냐고 제안을 주셨고, 이를 적용했더니 눈에 띄게 개선됨을 확인할 수 있었다.

 

이 사건으로 인해 배운건

 

1. 내가 맡은 문제의 배경사항도 철저히 조사하고, 어떤 흐름으로 이루어지는지 이해한 상태에서 태스크를 진행할 것

2. 내가 운영하고 있는 시스템에서 각 부품들이 어떻게 동작하고 있는지 상태 알고 있기

3. 임시테이블이라고 해서 무조건 느린 것은 아니다. 또한 쿼리 튜닝은 가장 마지막에 선택하는 수단이다.

 

가 있다.

사례 2: 쿼리 수치에 현혹되지 않기

주말에 카페에서 공부하다가 API timeout이 계속 발생한다는 알람 메시지를 받은 적이 있다. 우리 회사 분들은 제품에 열정이 많은 분들이 많아서 주말이나 야밤이더라도 문제가 있다고 판단되면 개의치 않고 문제를 해결하려 하시는 분들이 많음에도 불구하고 아무도 해당 문제에 관심을 가지지 않았다.

 

따라서 내가 해결해보면 나도 드디어 기여할 수 있겠구나 하는 생각이 들어서 문제를 확인하다 보니 쿼리가 너무 느려서 API timeout이 발생하던 것이었다.

 

따라서 이걸 해결하려고 해당 로직도 뒤져보고, 앱에서 재발도 시켜보고 여러 시도들을 해봤는데 정말 느린 것이 확인되었다.

 

그런데 이상한 점은 다시 확인했을 때는 잘 동작하는 것을 발견할 수 있었다.

 

알고 보니 이 문제는 며칠 전 내부에서 캐시를 비우기로 결정했는데, 그 과정에서 캐시를 타고 있던 요청들이 캐시를 타지 않아 발생하는 문제였다.

 

따라서 이게 사실 문제는 아니였고, 그 다음 사람들부터 재요청하면 캐시에 있는 내용을 확인하기 때문에 전혀 문제가 아닌 것이였다.

 

또 문제 리소스를 봤을 때도 정말 캐싱하기 좋은 구조였다. 아 이래서 이 쿼리가 느리더라도 따로 리소스를 사용하지 않은 거구나. 싶었다.

 

여기서 배운 점은

 

1. 문제처럼 보이는 것이 진짜 문제가 아닐 수도 있다.

2. 주어진 환경에 대해 복합적으로 생각할 수 있는 능력이 필요하다.

 

가 있다.

 

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.