Foundry
성능 최적화
중급
핵심

커넥션 풀과 타임아웃 설계

장애는 대개 커넥션 풀 고갈에서 시작한다

커넥션 풀과 타임아웃 설계

풀 고갈이 전체 장애가 되는 경로

외부 API 응답 지연
  -> 요청 스레드가 오래 점유
  -> 커넥션과 스레드 풀 소진
  -> 무관한 API까지 대기
  -> 서비스 전체 장애

풀 크기 감각

  • DB 커넥션 풀은 크게 잡을수록 좋은 것이 아니다. DB의 동시 처리 능력을 넘으면 경합만 늘어난다
  • 코어 수를 기준으로 작은 값에서 시작해 부하 테스트로 조정한다
  • 인스턴스 수 곱하기 풀 크기가 DB의 최대 접속 수를 넘지 않아야 한다

타임아웃 계층

계층설정없으면
커넥션 획득pool timeout요청이 무한 대기
연결 수립connect timeout죽은 노드에 계속 매달림
응답 대기read timeout스레드가 영구 점유됨
쿼리statement timeout무거운 쿼리가 DB를 점유

실무 포인트

  • 모든 외부 호출에 타임아웃을 명시한다. 기본값이 무한인 클라이언트가 흔하다
  • 상위 타임아웃은 하위 타임아웃 합보다 커야 한다. 재시도에는 지수 백오프와 횟수 상한을 둔다
  • 실패가 이어지면 서킷 브레이커로 빠르게 실패시키는 편이 전체 가용성에 유리하다
면접에서 이렇게 나옵니다

Q.커넥션 풀 크기를 어떤 기준으로 정하나요?

DB 가 동시에 처리할 수 있는 양에서 거꾸로 잡습니다. 크게 잡으면 빨라지는 것이 아닙니다.

요소내용
DB 코어 수실제 병렬 처리 능력의 상한
쿼리 시간짧으면 적은 커넥션으로 많은 요청을 처리한다
전체 인스턴스 수서버 10대 × 풀 20 = DB 커넥션 200
DB 최대 커넥션이것을 넘으면 접속 자체가 실패한다

계산의 출발점은 필요 커넥션 = 목표 처리량 × 평균 쿼리 시간입니다. 초당 500건에 쿼리가 20밀리초면 10개면 됩니다.

크게 잡으면 나빠지는 이유가 있습니다. 커넥션이 늘면 DB 안에서 경합이 생겨 개별 쿼리가 느려지고, 결과적으로 처리량이 떨어집니다. 그래서 코어 수의 두 배에서 네 배 정도가 실용적인 상한입니다.

주의내용
인스턴스 수를 곱해서 본다오토스케일링이면 최대 대수로 계산한다
대기 시간에 상한을 둔다무한정 기다리면 장애가 전파된다
트랜잭션 밖에서 외부 호출을 하지 않는다커넥션을 쥔 채 기다리면 풀이 마른다

흔한 실수: 요청이 밀리니 풀을 늘리는 것. 원인이 느린 쿼리면 풀을 늘려도 DB 대기만 길어집니다. 쿼리를 고치는 것이 먼저입니다.

Q.커넥션 풀이 고갈되면 어떤 증상이 나타나나요?

증상내용
응답 지연 급증쿼리는 빠른데 풀에서 기다린다
타임아웃 오류대기 시간 초과 예외가 몰린다
자원은 한가하다CPU 와 메모리는 여유인데 느리다
특정 API 만 정상DB 를 쓰지 않는 경로는 멀쩡하다
전파그 서비스를 부르는 다른 서비스도 함께 느려진다

세 번째가 판별 지점입니다. 자원이 한가한데 느리면 어딘가에서 기다리는 것이고, 커넥션 풀이 첫 번째 후보입니다.

원인은 대개 셋 중 하나입니다.

원인확인
쿼리가 느려졌다쿼리 시간이 길어져 커넥션 회전이 느려진다
커넥션을 반납하지 않는다예외 경로에서 닫지 않거나, 트랜잭션이 길다
트래픽이 늘었다처리량 대비 풀이 작다

두 번째가 가장 위험합니다. 조금씩 새면 며칠 뒤에 갑자기 고갈되고, 재시작하면 잠시 정상으로 보여 원인을 놓칩니다.

흔한 실수: 풀 대기 시간을 지표로 남기지 않는 것. 남기지 않으면 이 증상을 "DB 가 느리다"로 오진하게 됩니다.

Q.외부 API 호출에 어떤 타임아웃을 설정하나요?

세 층을 따로 정해야 합니다. 하나만 걸면 나머지에서 무한정 기다립니다.

타임아웃대상기준
연결TCP 연결 수립짧게. 수백 밀리초에서 1초
읽기응답 대기상대의 p99 를 기준으로
전체재시도까지 포함한 총 시간우리 API 의 응답 목표 안에 들어와야 한다

가장 중요한 것이 세 번째입니다. 우리 API 가 3초 안에 응답해야 하는데 외부 호출에 5초 타임아웃을 걸면 이미 약속을 못 지킵니다. 상위 목표에서 거꾸로 나눠야 합니다.

함께 갈 것이유
차단기상대가 죽었을 때 계속 기다리지 않고 즉시 실패한다
대체 동작캐시된 값이나 축소된 응답을 준다
격리외부 호출용 스레드나 커넥션을 따로 둔다

격리가 장애 전파를 막습니다. 외부 호출이 느려질 때 같은 풀을 쓰면 무관한 요청까지 함께 막힙니다.

흔한 실수: 기본값에 맡기는 것. 많은 HTTP 클라이언트가 읽기 타임아웃 기본값이 없거나 매우 깁니다. 상대가 응답을 안 주면 우리 스레드가 그만큼 잡혀 있습니다.

Q.재시도를 넣을 때 주의할 점은 무엇인가요?

재시도는 상대가 이미 힘들 때 부하를 더 얹는 행위입니다. 조건을 좁게 걸어야 합니다.

주의내용
안전한 요청만같은 요청을 두 번 해도 결과가 같아야 한다. 결제 생성은 그렇지 않다
재시도할 오류를 구분연결 실패나 5xx 는 재시도, 4xx 는 하지 않는다
간격을 늘린다즉시 재시도를 반복하면 상대를 밀어붙인다
편차를 더한다여러 클라이언트가 같은 시점에 몰리는 것을 막는다
횟수에 상한2회에서 3회. 그 이상은 대개 무의미하다
차단기와 함께실패가 지속되면 재시도 자체를 멈춘다
계층을 겹치지 않는다각 층이 3회씩 하면 최종 27회가 된다

마지막이 실제 장애를 키우는 자리입니다. 클라이언트, 게이트웨이, 서비스가 각자 재시도하면 원래 요청 하나가 수십 개가 되어 상대를 완전히 무너뜨립니다.

첫 번째는 요청에 고유 키를 붙여 서버가 중복을 걸러내게 만들면 해결됩니다. 그러면 안전하지 않은 요청도 재시도할 수 있습니다.

흔한 실수: 타임아웃 후 재시도하면서 전체 시간 예산을 늘리지 않는 것. 5초 타임아웃 3회면 최악의 경우 15초이고, 그 시간 동안 우리 스레드와 커넥션이 잡혀 있습니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

더 깊이 공부하기

읽었으면 문제로 확인해보세요

성능 최적화 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.