쿼리 성능 가이드라인
GitLab v19.4요약
이 문서는 SQL 쿼리를 최적화할 때 따라야 할 여러 가이드라인을 설명합니다. SQL 쿼리를 최적화할 때는 두 가지 축에 주의합니다. 플래너가 IN (...) 술어를 잘못 추정해 인덱스 스캔 대신 순차 스캔을 선택하는 경우에는 값마다 인덱스 탐색을 한 번씩 강제하는 재작성 방법을 LATERAL 조인으로 인덱스 탐색 강제하기에서 확인합니다.
이 문서는 SQL 쿼리를 최적화할 때 따라야 할 여러 가이드라인을 설명합니다.
SQL 쿼리를 최적화할 때는 두 가지 축에 주의합니다.
- 쿼리 실행 시간. 사용자가 GitLab을 어떻게 체감하는지를 그대로 반영하므로 가장 중요합니다.
- 쿼리 실행 계획. 실행 계획을 최적화하면 시간이 지나도 쿼리가 독립적으로 확장될 수 있습니다. 테이블이 커져도 인덱스 덕분에 쿼리 성능이 떨어지지 않고 유지된다는 점을 확인하는 것이 실행 계획을 분석하는 이유 중 하나입니다.
플래너가 IN (...) 술어를 잘못 추정해 인덱스 스캔 대신 순차 스캔을 선택하는 경우에는
값마다 인덱스 탐색을 한 번씩 강제하는 재작성 방법을 LATERAL 조인으로 인덱스 탐색 강제하기에서 확인합니다.
쿼리 시간 가이드라인#
| 쿼리 유형 | 최대 쿼리 시간 | 비고 |
|---|---|---|
| 일반 쿼리 | 100ms |
엄격한 상한은 아니지만, 쿼리가 이 시간을 넘기기 시작하면 최적화가 가능한지 또는 불가능한지를 시간을 들여 파악하는 일이 중요합니다. |
| 마이그레이션 내 쿼리 | 100ms |
전체 마이그레이션 시간 과는 다른 기준입니다. |
| 마이그레이션 내 동시 작업 | 5min |
동시 작업은 데이터베이스를 차단하지는 않지만 GitLab 업데이트를 차단합니다. add_concurrent_index, add_concurrent_foreign_key, 제약 조건 검증(예를 들어 add_text_limit로 텍스트 길이 제한 추가) 같은 작업이 여기에 해당합니다. |
| 포스트 마이그레이션 내 동시 작업 | 20min |
동시 작업은 데이터베이스를 차단하지는 않지만 GitLab 포스트 업데이트 과정을 차단합니다. add_concurrent_index, add_concurrent_foreign_key, 제약 조건 검증(예를 들어 add_text_limit로 텍스트 길이 제한 추가) 같은 작업이 여기에 해당합니다. 인덱스 생성이 20분을 넘긴다면 비동기 인덱스 생성을 검토합니다. |
| 백그라운드 마이그레이션 | 1s |
|
| Service Ping | 1s |
자세한 내용은 메트릭 계측 문서를 참고합니다. |
- 쿼리 성능을 분석할 때는 측정된 시간이 콜드 캐시인지 웜 캐시인지를 확인합니다. 이 가이드라인은 두 캐시 유형 모두에 적용됩니다.
- 배치 쿼리를 다룰 때는 범위와 배치 크기를 바꿔 가며 쿼리 시간과 캐싱에 어떤 영향을 주는지 확인합니다.
- 기존 쿼리의 성능이 좋지 않다면 개선을 시도합니다. 너무 복잡하거나 개발을 지연시킬 정도라면 후속 작업을 만들어 제때 처리할 수 있도록 합니다. 언제든 데이터베이스 리뷰어나 메인테이너에게 도움과 조언을 요청할 수 있습니다.
콜드 캐시와 웜 캐시#
쿼리 성능을 평가할 때는 콜드 캐시 쿼리와 웜 캐시 쿼리의 차이를 이해하는 일이 중요합니다.
쿼리를 처음 실행하면 "콜드 캐시" 상태에서 실행됩니다. 즉 디스크에서 읽어야 합니다. 같은 쿼리를 다시 실행하면 데이터를 캐시, 즉 PostgreSQL 이 공유 버퍼라고 부르는 영역에서 읽을 수 있습니다. 이것이 "웜 캐시" 쿼리입니다.
EXPLAIN 실행 계획을 분석할 때는 시간뿐 아니라
EXPLAIN(analyze, buffers)로 실행해 Buffers 출력을 보면 차이를 확인할 수
있습니다. Database Lab은
이 옵션을 자동으로 포함합니다.
웜 캐시 쿼리를 실행하면 shared hits만 표시됩니다.
예를 들어 Database Lab을 사용하면 다음과 같습니다.
Shared buffers:
- hits: 36467 (~284.90 MiB) from the buffer pool
- reads: 0 from the OS file cache, including disk I/O
psql의 실행 계획에서는 다음과 같습니다.
Buffers: shared hit=7323
캐시가 콜드 상태라면 reads도 함께 표시됩니다.
Database Lab을 사용하면 다음과 같습니다.
Shared buffers:
- hits: 17204 (~134.40 MiB) from the buffer pool
- reads: 15229 (~119.00 MiB) from the OS file cache, including disk I/O
psql에서는 다음과 같습니다.
Buffers: shared hit=7202 read=121
느린 목록 뷰와 API#
GitLab에서는 여러 필터와 정렬 옵션을 갖춘 필터링 목록 뷰와 API를 자주 만듭니다. 이런 옵션은 대개 파인더에 캡슐화되고 API/GraphQL 인자로 노출됩니다. 페이지네이션 성능 최적화 방법이 여러 가지 있지만, 정렬과 필터링의 모든 조합을 빠르게 만들 방법은 없는 경우가 많습니다. 많은 옵션을 빠르게 만들려다 보면 인덱스를 지나치게 많이 추가 하게 되고 이는 주 데이터베이스의 성능을 희생시킵니다. 이런 방식은 흔한 사용 사례에 한해서만 정당화되며, 필터와 정렬의 모든 조합을 빠르게 만드는 수단으로 보아서는 안 됩니다. 실제로는 특정 정렬이나 필터링 옵션을 적용했을 때 타임아웃이 발생하는 필터링 뷰와 API 요청이 존재할 수밖에 없다는 뜻입니다. 특정 필터링·정렬 조합이 일부 고객에게 도움이 된다면 팀이 그런 옵션을 추가하는 것을 여전히 허용하지만, 일부 사용자에게는 타임아웃이 발생한다는 점을 받아들여야 합니다.