티스토리 뷰

Chapter3. 성능을 좌우하는 DB 설계와 쿼리
조회 트래픽을 고려한 인덱스 설계
- 풀 스캔이 발생하지 않도록 하려면 조회 패턴을 기준으로 인덱스를 설계해야 한다.
- 엘라스틱서치 같은 검색 엔진을 사용하면 DB를 사용하지 않고 검색 기능을 구현할 수 있다.
- 인덱스는 단일 인덱스로도 사용 가능하고 필요하다면 복합 인덱스도 가능하다.
ex) 단일인덱스 : userId만 인덱스로 사용
ex) 복합인덱스 : (userId, activityDate)를 인덱스로 사용 - 인덱스를 생성할 때는 선택도가 높은 칼럼을 골라야 한다.
ps) 선택도? 인덱스에서 특정 칼럼의 고유한 값 비율 (선택도↑ = 해당 칼럼에 고유한 값이 많음) - 커버링 인덱스 : 특정 쿼리를 실행하는 데 필요한 칼럼을 모두 포함하는 인덱스
ex) 아래 쿼리는 실제 데이터에 접근하지 않음.
select activityDate, activityType
from activityLog
where activityDate = '2024-07-31' and activityType='VISIT'; - 효과가 적은 인덱스를 추가하면 오히려 성능이 나빠질 수 있다.
인덱스는 조회 속도를 빠르게 해 주지만 데이터 추가, 변경, 삭제 시에는 인덱스 관리에 따른 비용(시간이) 추가되기 때문이다.
인덱스가 많아질수록 메모리와 디스크 사용량도 함께 증가한다.
몇 가지 조회 성능 개선 방법
1. 미리 집계하기
count나 sum과 같은 집계 쿼리를 조회 시점에 실행하면 성능 문제가 발생한다 (like 서브쿼리)
이러한 집계 데이터는 미리 계산해서 별도 칼럼에 저장해서 성능을 개선할 수 있다.
2. 페이지기준 목록조회 대신 ID기준 목록조회 방식 사용하기
처음 10개 데이터를 읽어옴 -> 다음 10개 데이터를 읽을 때는 앞서 읽어온 마지막 id를 사용해서 조회
3. 조회 범위를 시간 기준으로 제한하기
ex) 뉴스 기사 목록 / 회원의 주문내역
최신 데이터만 조회하는 것도 방법이다. 고객은 최근 결과에만 관심이 있지 과거 결과는 관심이 없음.
DB는 성능을 높이기 위해 메모리 캐시를 사용하여 다음 동일한 요청이 들어올 때 빠르게 응답할 수 있다.
일반적으로 최신 데이터가 많이 조회되기 때문에 캐시에 최신 데이터가 적재될 확률이 높다.
4. 전체 개수 세지 않기
count(*)와 같은 경우, 조건에 해당하는 모든 데이터를 탐색해야 하기 때문에 실행 시간이 증가한다.
커버링 인덱스를 사용하더라도 전체 인덱스를 스캔해야 하며, 커버링 인덱스가 아닌 경우에는 실제 데이터를 전부 읽어야 한다.
5. 오래된 데이터 삭제 및 분리 보관하기
데이터 개수를 일정수준으로 유지하려면 과거 데이터를 삭제해야 한다.
ex) 로그인 시도 내역 ← 최신3개월치만 DB에 남겨두고 나머지는 별도 서버에 저장
6. DB 장비 확장하기
클라우드를 사용하거나, 더 빠른 CPU, 더 많은 CPU를 사용하여 메모리를 증설하여 DB 성능을 확장할 수 있다(=수직확장)
7. 별도 캐시 서버 구성하기
DB를 확장하는 것보다 Redis와 같은 캐시서버를 구성하는 것이 DB수직확장보다 상대적으로 부담이 적다.
알아두면 좋을 몇 가지 주의 사항
1. 쿼리 타임아웃
쿼리 타임아웃을 설정해서 해당 요청이 에러라도 종료처리 될 수 있게 한다.
다만, 블로그 글을 조회하는 기능은 타임아웃을 짧게 설정해도 되지만, 상품 결제 기능은 처리 중 타임아웃으로 에러가 발생하면 후속 처리와 데이터 정합성이 복잡해질 수 있기 때문에 기능의 특성에 따라 다르게 설정해야 한다.
2. 상태 변경 기능은 복제DB에서 조회하지 않기
- 주DB-복제DB는 순간적으로 데이터가 일치하지 않을 수 있기 때문에
- 트랜잭션 문제가 발생할 수 있다. (얘도 어차피 이유는 데이터 불일치)
=> 회원가입, 변경, 등록, 삭제와 같이 INSERT, UPDATE, DELETE쿼리를 실행하는 기능에서 변경 대상 데이터를 조회해야 한다면, 복제DB가 아닌 INSERT, UPDATE, DELETE 쿼리를 실행하는 주 DB에서 SELECT 쿼리를 실행하자
3. 배치 쿼리 실행 시간 증가
배치에서 사용하는 쿼리의 실행 시간을 지속적으로 추적해야 한다.
쿼리 실행 시간이 갑자기 큰 폭으로 증가했는지 감지했다면 해결방법은
- 가장 빠른 해결책 DB장비의 사양을 높임
- 커버링 인덱스 활용
→ 데이터를 직접 읽지 않고 인덱스만 스캔해 집계를 수행하기 때문에, 처리 속도는 빨라지고 DB가 사용하는 메모리도 줄어든다.
- 데이터를 일정 크기로 나눠 처리
4. 타입이 다른 칼럼 조인 주의
칼럼의 타입이 서로 다르면, 두 칼럼의 값을 비교하는 과정에서 DB는 타입 변환을 수행한다.
전체 데이터를 스캔하지 않고 인덱스만 스캔하는 경우에도, 데이터가 많다면 변환 작업으로 인해 쿼리 실행시간이 길어질 수밖에 없다.
(※ 문자열 타입을 비교할 때는 칼럼의 캐릭터셋이 같은지 확인해야 한다. 캐릭터셋이 다르면 그 자체로도 변환이 발생할 수 있기 때문이다.)
5. 테이블 변경은 신중하게
DB의 테이블 변경 방식으로 인해 테이블에 컬럼을 추가하거나 하는 변경 시에는 매우매우매우 주의해야한다
ex) MySQL의 테이블 변경 방식 : 새 테이블 생성 → 원본 테이블의 데이터 복사 → 복사가 완료되면 새 테이블로 대체
이렇게 테이블이 변경되는 시간 동안 DML작업이 중단됨
6. DB 최대 연결 개수
API서버 4대(커넥션풀 30 * 4 = 120) / DB 최대 연결 개수 100개 → DB의 최대 연결 개수를 늘려주면 문제 해결
※ DB서버의 CPU사용률이 70%를 넘어가면 절대 연결 개수를 늘리지 말 것! DB부하 증가
실패와 트랜잭션 고려하기
1. 트랜잭션을 고려하지 않은 경우
계약데이터 존재하는지 확인(SELECT) → 없으면 계약 (INSERT) → userState 상태값 변경 (UPDATE)
이 과정에서 update에 문제가 생기면 이미 insert 된 상황으로 정합성이 맞지 않음. 다시 계약하려 해도 이미 있는 계약이라 안된다고 뜰 거임
회원가입 → 메일발송 실패
의 경우에는 회원가입까지만 트랜잭션으로 묶고 메일은 따로 에러핸들링 처리해서
메일발송 실패했다고 회원가입 안되게 하는 참사는 막자!
💡결론
풀스캔 하지마라
테이블 구조 함부로 바꾸지마라 다친다
적절한 인덱스 사용해서 효율화시켜라
트랜잭션을 잘 사용해라
'Programming > BackEnd' 카테고리의 다른 글
| [BE] 주니어 백엔드 개발자가 반드시 알아야 할 실무 지식 Ch01~02 (0) | 2025.11.14 |
|---|---|
| [Back] 토큰 기반 인증 시스템 JWT의 특징 + SpringBoot 적용 (2) | 2022.06.15 |
| [Back] Lombok 롬복 (JAVA) (3) | 2022.04.14 |
- Total
- Today
- Yesterday
- OS
- 완전탐색
- bruteforce
- BFS
- 토큰기반인증
- docker
- EffectiveJava
- 아이템61
- Java
- 순열
- BOJ
- 이펙티브자바
- IMAGE
- 조합
- 완탐
- docker-compose
- 운영체제
- 알고리즘
- Container
- springboot
- 아이템60
- 백준
- cicd
- DevOps
- dp
- 그래프탐색
- Retrofit2
- dfs
- 아이템59
- subset
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |