💡 DB 성능은 연동하는 모든 서버 성능에 영향을 준다. DB 성능 문제로 전체 서비스가 먹통이 되는 상황도 빈번하게 발생한다. 쿼리 실행 시간이 길어지면 전체 서비스가 느려지는 성능 문제는 흔히 발생하는 문제이다. 하지만 DB 자체가 문제인 상황은 많지 않다. 오히려 DB를 잘못 사용해서 발생한 문제가 더 많다. DB를 전문가 수준으로 깊이 이해할 수 있다면 이상적이겠지만, 서버 개발자 입장에서 조금만 신경써도 DB 성능 문제를 충분히 줄이거나 없앨 수 있다.
DB 테이블을 설계할 때는 조회 기능과 트래픽 규모를 고려해야 한다. 전체 게시글이 1000만건 이상 적제된 테이블을 대상으로 사용자가 특정 카테고리의 게시글 조회 요청을 할 경우, (카테고리 컬럼에) 인덱스가 없다면 1000만건의 데이터를 비교해야한다.
많은 사용자가 동시에 게시글 조회를 하게되면 다수의 풀 스캔이 발생하고 DB의 CPU 사용률이 100%에 도달할 경우 DB가 제 기능을 하지 못하게 된다.
데이터의 특징을 파악하면 단일 인덱스/복합 인덱스 중 더욱 적절한 형태의 인덱스가 적절한지 판단하는데 도움이 된다.
인덱스를 생성할 때는 선택도가 높은 컬럼을 골라야 한다.
선택도가 높으면 해당 칼럼에 고유값이 많다는 뜻, 선택도가 높을 수록 인덱스를 이용한 조회 효율이 높아진다.
작업 상태 컬럼과 같이 특정 배치 작업에서 일정 상태값을 조회하는 경우 선택도가 낮더라도 반복 작업의 효율을 높히기 위해 선택도가 낮은 컬럼을 인덱스로 대상으로 선정하여 효율을 높일 수도 있다.
커버링 인덱스는 특정 쿼리를 실행하는 데 필요한 컬럼을 모두 포함하는 인덱스를 말한다.
select activityDate, activityType
from activityLog
where activityDate = '2025-01-01' and activityType = 'VISIT';
위와 같은 쿼리를 실행할 때 (activityDate, activityType)을 사용하는 커버링 인덱스가 생성되어 있다면, 실제 데이터를 읽어오는 과정을 생략하고 인덱스에서 필터링과 조회가 가능하기 때문에 더욱 빠른 응답이 가능하다.
효과가 적은 인덱스를 추가할 경우 오히려 성능에 악영향을 줄 수 있음. 인덱스는 조회 속도를 빠르게 하지만 데이터 추가, 변경, 삭제 시에는 인덱스 관리에 따른 추가 비용이 발생하기 때문. 또한 인덱스 자체도 데이터이기 때문에 인덱스가 많아질수록 메모리와 디스크 사용량도 함께 증가
인덱스를 적용하지 않고 조회 성능을 개선하는 몇가지 방법
게시글에 대한 댓글, 좋아요 와 같은 정보가 서로 다른 테이블에 저장되어있을 경우 1개의 (집계 쿼리가 포함된)게시물 조회 요청에 대하여 N개의 댓글 + M개의 좋아요로 인해 과도한 쿼리 수행 시간이 소요될 수 있다.
이 경우 반정규화(비정규화)를 통해 게시글 테이블에 좋아요 수를 관리할 컬럼을 추가하는 등의 방식으로 추가적인 연산에 소요되는 시간을 줄일 수 있다.
조회 범위를 제한하여 최신 데이터만 조회할 수 있도록 설계할 경우 전체 데이터를 조회하지 않기 때문에 조회 성능을 개선할 수 있음
대량의 데이터를 보관하고있는 테이블을 대상으로 전체 개수를 세지 않도록 기능 협의 혹은 반정규화 등으로 해결
데이터 개수가 늘어날 수록 쿼리 수행 시간은 증가한다. 대부분의 조회는 최신 데이터를 기준으로 발생하고 오래된 데이터를 사용하는 빈도는 줄어들기 때문에 일정 크기의 데이터 사이즈를 유지하며 오래된 데이터는 삭제(혹은 별도 테이블,데이터베이스 이동)하는 방식으로 조회 성능을 개선(일정 수준 유지)할 수 있다.
그 외 조회 성능을 개선하기 위한 방법은 DB 장비 확장(스케일), 별도 캐시 서버 구성 등이 있다.
응답 시간은 처리량에 큰 영향을 준다. 동시 사용자가 증가할 때 응답 시간이 길어지면 처리량은 감소한다.
앞선 요청이 처리되지 않은 상태에서의 사용자 요청은 서버 부하를 더욱 가중시킨다. 사용자의 재시도가 반복되면 서버 부하는 폭증하게 된다.
이런 상황을 방지하는 방법 중 하나는 쿼리 실행 시간(쿼리 타임아웃)을 설정하는 것. 예를 들어 쿼리 실행 시간을 5초로 제한 할 경우 5초를 초과하는 쿼리에 대하여 타임아웃 에러를 발생시켜 사용자에게 빠르게 오류 응답을 전달할 수 있다.
결제와 같은 데이터 정합성이 중요한 요청의 경우 타임아웃 에러가 발생하면 후속 처리와 데이터 정합성에 대해 고려해야한다.
primary DB - replica DB 구조를 사용하는 경우 데이터 변경은 primary, 데이터 조회는 replica를 사용한다.
하지만 데이터 변경을 위한 select 실행을 replica DB대상으로 실행하였을 경우 문제가 발생할 수 있다.
때문에 데이터 변경 시 데이터 변경 대상 데이터(primary)를 조회하여 처리해야한다.
배치 프로그램은 데이터를 일괄로 조회, 집계, 생성하는 등의 작업을 수행한다. 데이터가 많아질 수록 일괄 처리 쿼리를 수행하는 시간도 함께 증가한다. 이런 문제를 예방하려면 배치에서 사용하는 쿼리 실행 시간을 지속적으로 추적해야할 필요가 있다. 만약 배치 쿼리에서 문제가 발생한다면 아래와 같은 방식으로 성능 최적화를 진행할 수 있다.
만약 join에 활용되는 컬럼의 타입이 테이블별로 상이하다면 데이터베이스는 두 테이블을 조인하는 과정에서 타입 변환을 추가적으로 수행한다.
만약 서로 다른 타입의 값을 조인에 활용해야한다면 cast() 등을 활용하여 불필요한 타입 변환을 줄일 수 있다.
데이터가 많은 테이블에 새로운 컬럼을 추가하거나, 기존 열거 타입을 변경할 때 데이터베이스는 변경된 새 테이블을 생성하고, 원본 테이블의 데이터를 복사하여 최종 대체하는 방식으로 테이블 변경 작업을 진행한다. 테이블 복사 과정에서 DML의 실행은 허용하지 않기 때문에 복사 시간만큼 서비스가 멈춘다.
만약 복사 시간이 길어진다면, 복사 시간만큼 서비스가 먹통이 되는 위험한 상황이 발생할 수 있어 테이블 변경은 항상 신중하게 진행해야한다.