쿼리 타임아웃

실제 서비스에서 응답시간이 길어지면 처리량만 감소할까? 아니다. 동접이 증가하면서 특정 쿼리 실행시간이 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 부하줄이고 해야한다.