DB관련 알아두면 좋을 몇 가지 주의사항

쿼리 타임아웃

  • 실제 서비스에서 응답시간이 길어지면 처리량만 감소할까? 아니다
  • 동접이 증가하면서 특정 쿼리 실행시간이 15초 이상이 되었다 가정
  • 사용자는 몇 초만 지나도 서비스가 느리다 느끼고 다시 몇 초 후에 재시도를 하게 된다

  • 응답 지연으로 인한 재시도는 서버 부하를 더욱 가중
  • 아직 앞선 요청 처리중인 상황에 추가적인 요청이 유입되기 때문
  • 이런 재시도가 누적되면 요청 수가 기하급수적으로 늘어난다

  • 이런 상황 방지하는 방법중 하나는 쿼리 실행 시간을 제한(타임아웃)하는 것
    • 5초로 제한했다 하자, 트래픽이 증가해 쿼리 실행 시간이 5초를 넘기면 제한시간 초과로 에러가 발생
    • 사용자는 에러 화면을 보게 되지만 서버 입장에서는 해당 요청을 정상적으로 처리한셈
  • 타임아웃은 서비스와 기능의 특성에 따라 다르게 설정해야한다
  • 블로그 글 조회는 몇 초 이내로 하면되겠지만 상품 결제 기능은 보다 긴 타임아웃이 필요
  • 결제 처리 중 타임아웃으로 에러가 발생하면 후속 처리와 데이터 정합성이 복잡해 질 수 있기 때문

상태 변경 기능은 복제 DB 조회하지 않기

  • 주 - 복제 디비 구조 쓸때 변경은 주 DB를 사용하고 조회는 복제 DB를 사용한다
  • 그런데 이를 잘 못 이해해 모든 SELECT를 무조건 복제 DB 에서 실행하는 경우 있다
    • 이는 2가지 측면에서 문제를 일으킬 수 있다.

첫째, 주 DB와 복제 DB는 순작적으로 데이터가 불일치할수있다. 주 DB 변경 후 복제 DB에 반영된다.

  • 네트워크를 통해 복제 DB에 전달
  • 복제 DB는 자체 데이터에 변경 내용을 반영
    • 이 과정이 시간이 꽤 걸린다
  • 데이터 복제에는 지연이 발생한다
  • 아직 복제 DB에 변경이 반영되기전에 SELECT 가 발생할 수 있다
  • 이 경우 잘못된 데이터를 조회하게 되어 사용자 요청을 제대로 처리할 수 없게 된다

둘째, 트랜잭션 문제가 발생할 수 있다.

  • 주 DB와 복제 DB 간 데이터 복제는 트랜잭션 커밋 시점에 이뤄진다
  • 주 DB의 트랜잭션 범위 내에서 데이터를 변경하고, 복제 DB에서 변경 대상이 될 수 있는 데이터를 조회하면 데이터 불일치로 문제가 생길 수 있다
  • C U D 하고 이 데이터를 반영한걸 안전히 보려면 주 DB를 조회하자

배치 쿼리 실행 시간 증가

  • 배치 프로그램은 데이터를 일괄 조회하거나 집게하거나 생성하는 작업 수행
  • 예를 들어 일별 회원 통계, 사용자 사용 내역 기준으로 요금 계산

  • 문제는 데이터가 쌓이고 집계 쿼리를 쓰면 많은 양의 메모리 쓰고 특정 임계점 넘기면 실행시간이 예측할 수 없을 만큼 길어질 수 있다는 것
  • 이런 문제 예방하려면 배치에서 사용하는 쿼리의 실행 시간을 지속적으로 추적해야한다
  • 추적을 통해 쿼리 실행 시간이 갑자기 큰 폭으로 증가했는지 감지할 수 있고, 문제가 되는 쿼리 발견시 원인 찾아 해결 가능
  • 해결책은 장비 사용 높이기가있지만 항상 만능은 아니기에(돈) 다른 방법도 고려 ㄱㄱ
  • 커버링 인덱스 활용
    • 집계 쿼리는 특성상 많은 데이터를 스캔한다
    • 이때 집계 대상 칼럼이 인덱싱 되있다면 데이터를 직접 읽지 않고도 인덱스만 스캔해 집계를 수행할 수 있다
    • 커버링 인덱스를 쓰면 처리속도는 빨라지고 DB가 쓰는 메모리도 준다
  • 데이터를 일정 크기로 나눠 처리
  • 예를 들어 접속 로그로 한 달간 다양한의 데이터 추출해야한다고 가정
SELECT ... 각종 집계
FROM accessLog al
WHERE al.accessDatetime >= ~9월
AND al. < 8월
GROUP BY ...
  • 이걸 하루로 나눠 말일 까지 실행하고 각 결과를 다시 합치면 같은결과가 나옴
  • 다른 방법으로는, 데이터를 나눠 처리하면 짧은 간격으로 집계쿼리를 실행할 수 도 있다
  • 예를 들어 새벽에 배치 처리 안하고 다음 처럼 10분 간격으로 집계 작업실행할 수 도 있다
    1. 통계 테이블에 반영된 마지막 accessDatetime 시간을 구한다
    2. [마지막 accessDatetime, 마지막 accessDatetime + 10분]에 속하는 accessLog 데이터를 집계한다.
    3. 2에서구한 집계 데이터를 통계 테이블에 반영한다.
  • 이렇게 하면 쿼리 실행시간 단축도 하고 필요 집계 데이터를 안정적으로 생산 가능함.

타입이 다른 컬럼 조인 주의

  • 조인시 비교하는 칼럼의 타입이 달라서 인덱스를 활용하지 못하는 문제를 해결하려면 두 칼럼의 타입을 맞춰 비교해야함
  • 다은은 MySQL에서 타입을 변환해 두 칼럼의 타입을 일치시킨 후 비교하는 예시
SELECT u.userId, u.name, p.*
FROM user u, push p
WHERE u.userId = 145
AND CAST(u.userId as char set utf8mb4) collate 'utf8mb4_unicode_ci' = p.receiverId
AND p.receiverType = 'MEMBER'
ORDER BY p.id DESC
LIMIT 100;
  • 이렇게 비교 대상 칼럼 타입 맞추면 쿼리 실행중 발생하는 불필요한 타입 변환을 줄일 수 있다.

테이블 변경은 신중하게

  • 데이터가 많은 테이블에 새로운 칼럼추가나 기존 열거 타입 칼럼을 변경할 때는 매우 주의해야한다
  • 정말로 주의해야한다

  • 그 이유는 DB의 테이블 변경 방식 때문이다
  • 예를 들어 MySQL은 테이블 변경마다 새 테이블 생성하고 원본 테이블의 데이터를 복사한 뒤, 복사가 완료되면 새 테이블로 대체함
  • 이 복사 과정에서는 UPDATA, INSERT, DELETE 같은 DML 작업을 허용하지 않기에 복사 시간만큼 서비스가 멈춘다
  • DML 허용하면서 테이블 변경하는 기능도 있지만 항상 가능한 것은 아님
  • 그래서 이런 작업은 점검 시간을 따로 잡고 하는 경우가 많다

DB 최대 연결 개수

  • 다음 상황 가정 ㄱ
    • API 서버는 세 대
    • 트래픽이 증가하고 있어 수평 확장이 필요
    • DB서버의 CPU 사용률은 20% 수준으로 여유있다
  • 트래픽 증가를 감당하기 위해 API 서버를 추가할 수 있다
  • 그런데 추가한 API 서버에서 DB 커넥션 생성에 실패한다면 뭐가 문제인가?
  • DB 서버 자원에는 여유가 있지만 API 서버에서 DB에 연결되지 않는다면 DB에 설정된 최대 연결 개수를 확인해야한다
    • 예를들어 DB 최대 연결 개수가 100개라면 API 서버의 커넥션 풀 개수가 30개일때 API 서버를 네 대로 늘리면 필요한 커넥션 수는 120개
    • 이것때문에 실패한것
    • DB의 최대 연결 개수를 120개로 늘리면 해결될것임
  • 단 주의해야하는게 무작정 연결 개수 늘리면안됨
  • CPU 사용률 70% 정도 찍으면 스탑
  • 이런경우는 캐시 서버 구상이나 쿼리 튜닝같은 조치로 DB 부하줄이고 해야됨