성능을 좌우하는 DB 설계와 쿼리
성능에 핵심인 DB
- DB 성능 문제로 전체 서비스가 먹토잉 되는 상황이 존재함
- 특정 서비스가 10초가 넘도록 응답을 못하는 상황이 발생
- 문제는 DB
- DB CPU 사용률이 90%를 넘김
- 문제는 호출 빈도가 높은 쿼리가 풀 스캔으로 수행중이었음
- DB성능은 연동하는 모든 서버 서능에 영향을 줌
- DB 성능 문제로 전체 서비스가 먹토잉 되는 상황이 존재함
조회 트래픽을 고려한 인덱스 설계
- 일반적인 시스템은 조회 기능의 실행 비율이 높음
- 예를 들면 게시판의 경우 대부분 글 읽기임
- DB테이블을 설계할 때는 조회 기능과 트래픽 규모를 고려해야함
select id, category, writerId, title, content
from article
where category = 10 order by id desc limit 20, 10
- 위와 같은 쿼리가 존재함, category에 인덱스가 없다면?
- 풀스캔 발생
select * from activityLog where userId = 123 and activityDate = '2024-07-31'
order by activityDateTime desc
- 선택도를 고려한 인덱스 컬럼 선택
- 커버링 인덱스 활용하기
- 인덱스는 필요한 만큼 만들기
몇 가지 조회 성능 개선 방법
- 미리 집계하기
- 페이지 기준 목록 조회 대신 ID 기준 목록 조회 방식 사용하기
- 조회 범위를 시간 기준으로 제한하기
- 전체 개수 세지 않기
- 오래된 데이터 삭제 및 분리 보관하기
- DB장비 확장하기
- 별도 캐시 서버 구성하기
알아두면 좋을 몇 가지 주의 사항
- 쿼리 타임아웃
- 동시 접속이 증가해서 특정 쿼리 수행 시간이 15초 이상으로 늘어난 경우를 가정
- 사용자는 몇 초만 지나도 새로고침을 수행해 재시도할 수 있음
- 이 상황이 반복되면 서버는 처리중인 요처이 있는데 새로운 요청이 또 유입되는 상황
- 이런 상황을 방지하기 위한 방법으로 쿼리 실행 시간을 제한(타임아웃 설정)을 처리하고 서버(정확하게는 DB)의 부하를 줄일 수 있음
- 상태 변경 기능은 복제 DB에서 조회하지 않기
- 배치 쿼리 실행 시간 증가
- 타입이 다른 컬럼 조인 주의
- 테이블 변경은 신중하게
- DB 최대 연결 개수
실패와 트랜잭션 고려하기
- 코드는 항상 정상적으로 동작하지 않기 때문에 비정상 적인 상황에서 트랜잭션 처리를 반드시 고민해야함
- 아니면 일관성 문제가 생길수 있음
- 여러 데이터를 수정할때는 명확하게 트랜잭션을 처리해야함
- 때로는 일부 기능에 대해서 오류가 발생해도 커밋해야하는 상황이 존재함
- 예를 들면 회원가입에서 메일 전송(외부 API)까지 한 트랜잭션으로 처리할 필요하는 없음, 메일 전송 실패시 롤백할 필요가 없기 때문
- 스프링에서는
@Transactional을 사용해서 다음과 같이 처리가능
@Transactional // 트랜잭션 범위
public void join(JoinRequest join) {
...
memberDao.insert(member); // DB애 데이터 추가
try {
mailClient.sendMail(...); // 메일 발송
} catch (Exception ex) {
// 메일 발송 오류 무시
// 로그로 기록해 모니터링
}
}