#쿼리
「쿼리」 태그가 붙은 글 20편이다.
글 목록
20편 중 1–20
느린 쿼리 1등을 고쳤는데 왜 그대로일까
느린 쿼리 1등을 3초에서 0.5초로 줄였는데 서비스 체감은 그대로였다. 하루 500만 번 도는 50ms 쿼리가 총 69시간. 진짜 짐은 그쪽이었다.
쿼리는 그대로인데 조인이 100배 느려졌다
쿼리도 인덱스도 그대로인데 리포트 쿼리가 어느 날부터 100배 느려졌다. EXPLAIN 을 보니 조인 순서가 뒤집혀 있었다. 뒤집힌 이유는 조인이 아니라 행 수 추정에 있었다.
Index Scan 이라고 찍혔는데 왜 3초나 걸릴까
EXPLAIN 에 인덱스 스캔이 찍혔는데도 쿼리가 3초씩 걸린다. 인덱스를 타는 건 시작일 뿐이다. 시간이 어디로 새는지는 계획 아래 숫자 몇 개가 말해 준다.
다시 만들었더니 인덱스가 절반이 됐다, 인덱스 팽창
테이블은 그대로인데 인덱스만 몇 달에 걸쳐 조금씩 커진다. 다시 만들어 보면 크기가 절반으로 줄기도 한다. 그동안 늘린 서버 비용이 사실은 빈 공간 값이었다.
COUNT(*) 는 결국 다 세는 일이다
목록 스무 개는 10ms 에 나오는데 전체 개수를 세는 데는 3초가 걸린다. 인덱스를 더해도 크게 안 준다. 이 비용은 구조에서 나온다. 고칠 곳은 쿼리보다 화면 쪽일 때가 많다.
NOT IN 결과가 0건, 범인은 NULL 하나
NOT IN 으로 걸렀는데 에러도 없이 결과가 0건이다. 데이터는 분명히 있는데. NULL 하나가 결과를 통째로 비우고, DB 를 옮기면 정렬 순서까지 바뀐다.
OR 하나 붙였더니 인덱스를 안 탄다
검색 조건에 OR 하나를 더했을 뿐인데 쿼리가 갑자기 느려진다. AND 로 바꾸면 멀쩡하다. 어떤 OR 은 괜찮고 어떤 OR 만 탈이 나는지는 같은 컬럼이냐 다른 컬럼이냐로 갈린다.
왜 하필 여섯 번째 실행부터 쿼리가 느려질까
같은 쿼리가 다섯 번까지는 빠르다가 여섯 번째부터 느려지고, 서버를 재시작하면 다시 빨라진다. 우연 같은 이 숫자는 PostgreSQL 옵티마이저가 정해 둔 규칙이다.
트래픽 몰릴 때만 느린 정렬, work_mem 이 곱해진다
정렬이 들어간 쿼리가 평소엔 빠른데 트래픽이 몰리는 시간에만 수십 배 느려진다. 혼자 재현하면 멀쩡하다. 메모리 설정을 크게 잡으면 낫겠다 싶지만, 그러다 서버가 죽기도 한다.
인덱스에 다 넣었는데 PostgreSQL 이 테이블을 또 읽는다
필요한 컬럼을 전부 인덱스에 넣었는데 PostgreSQL 이 여전히 테이블을 읽는다. 대량 갱신 직후엔 느리다가 한참 지나면 저절로 빨라진다. 그 「한참」 사이에 VACUUM 이 하는 일이 있다.
LIMIT 10 은 0.1초인데 LIMIT 100 은 왜 12초일까
LIMIT 10 을 붙였더니 오히려 느려졌다. LIMIT 10 은 0.1초인데 LIMIT 100 은 12초가 걸리기도 한다. 옵티마이저가 LIMIT 값을 보고 계획을 통째로 바꿨다.
EXPLAIN ANALYZE 는 추정이 빗나간 곳을 보라고 있다
느린 쿼리를 잡으려고 EXPLAIN 을 열어도 숫자가 무슨 뜻인지 모르겠다. 인덱스 스캔인데도 느리면 더 막막하다. 먼저 볼 건 추정 행 수와 실제 행 수다.
대량 적재 뒤에만 느려지는 쿼리, 범인은 낡은 통계
데이터를 왕창 넣은 직후부터 쿼리가 느려졌다가 시간이 지나면 저절로 낫는다. 그래서 원인을 못 찾고, 다음 적재 때 똑같이 또 느려진다.
인덱스가 분명히 있는데 순차 스캔이 뜬다
인덱스를 만들었는데 쿼리는 그대로 느리다. 개발 DB 에선 잘 타던 인덱스를 운영에서만 안 탄다. 그런데 옵티마이저가 인덱스를 안 쓰는 게 옳은 판단일 때가 생각보다 많다.
deleted_at 만 채웠더니 2년 뒤 테이블이 느려졌다
지운 데이터를 실제로 지우지 않고 표시만 해 뒀더니, 2년 뒤 테이블의 70%가 삭제된 행이 됐다. 조회는 점점 느려지고, 탈퇴한 이메일로는 재가입도 막힌다.
안 쓰는 인덱스, 지워도 되는지 어떻게 알까
느릴 때마다 인덱스를 하나씩 더하다 보니 테이블 하나에 열두 개가 됐다. 조회는 그대로인데 쓰기만 느려졌다. 그렇다고 사용 횟수 0 이라는 숫자를 그대로 믿으면 곤란하다.
100페이지만 가면 타임아웃, OFFSET 페이지네이션
목록 1페이지는 10ms 인데 100페이지쯤 가면 타임아웃이 난다. 그 깊이까지 넘기는 건 사람이 아니라 크롤러다. OFFSET 은 건너뛰는 척하면서 사실 다 읽고 있다.
운영에서만 느린 목록 API, 범인은 ORM N+1
목록 API 가 로컬에선 50ms 인데 운영에선 1.5초가 걸렸다. 느린 쿼리는 하나도 없었다. 범인은 쿼리 하나가 아니라 101번이라는 개수였다.
인덱스를 걸었는데 LIKE '%검색어%' 가 느리다
검색을 LIKE '%키워드%' 로 만들었더니 데이터가 늘면서 몇 초씩 걸렸다. 인덱스를 걸어 뒀는데도. 앞이 열린 패턴 앞에서는 B-tree 가 손쓸 방법이 없다.
컬럼은 있는데 인덱스를 안 탄다면, 복합 인덱스 순서 문제
(status, created_at) 인덱스를 만들었는데 created_at 만으로 찾으면 인덱스를 안 탄다. 그래서 인덱스를 자꾸 더 만들게 된다. 순서 기준을 알면 오히려 개수가 준다.
조건에 맞는 글이 없다. 브루니가 숨었나 보다.