Zettelkasten

커넥션 풀 대기는 프로세스당 실사용량으로 풀 고갈과 재연결 churn을 가른다

·수정 6

요약

  • 커넥션 풀 대기 시간이 잡힌다고 해서 풀이 꽉 찼다(고갈)는 뜻은 아니다. 그 시간 안에는 빈 커넥션을 기다린 시간과, 커넥션을 새로 맺느라 든 핸드셰이크 시간이 섞여 있다.
  • 둘을 가르려면 풀 천장(task 수 × worker 수 × 워커당 풀 크기)을 구한 뒤, 실제로 열린 커넥션을 프로세스 수로 나눠 본다. 프로세스당 사용량이 상한에 한참 못 미치면 고갈이 아니라 재연결 churn이다.

본문

서버가 느린데 DB 커넥션 수는 천장 부근에서 평평하다. 이때 "풀이 꽉 차 막혔다"고 결론짓기 쉽지만, 평평하다는 사실만으로는 고갈인지 알 수 없다. 풀 대기 시간이 실제로 잡혀도 마찬가지다. 그 정체가 고갈인지 재연결 churn인지에 따라 고쳐야 할 곳이 완전히 달라지므로, 먼저 가려야 한다. 아래 순서로 판별한다.

1. 풀 대기가 무엇을 잰 값인지 보고, 시간대로 빠르게 거른다 풀에서 커넥션을 꺼내는 데 걸린 시간을 재면, 성격이 다른 두 가지가 한 값에 섞여 들어온다. 하나는 풀이 가득 차서(size == max) 다른 작업이 커넥션을 돌려줄 때까지 기다린 시간이고, 이게 진짜 고갈이다. 다른 하나는 풀에 쓸 커넥션이 없어 새로 맺느라 든 시간이다. TCP·TLS·DB 인증 핸드셰이크가 수백 ms를 잡아먹는다. 메트릭 하나로는 둘이 구분되지 않으니 "대기가 있으니 고갈"이라고 건너뛰면 안 된다. 본격 계산에 들어가기 전에, 시간대만 봐도 빠르게 의심할 수 있다. 새벽처럼 부하가 낮을 때도 평균 대기가 똑같이 나온다면 고갈이 아니다. 고갈은 요청이 몰릴 때 생기는데, 한가한 시간엔 풀이 찰 일이 없기 때문이다. 부하와 상관없이 일정하게 깔리는 대기는 커넥션을 새로 맺는 비용, 즉 churn 쪽을 가리킨다.

2. 천장을 계산한다 풀은 대개 프로세스(워커) 하나당 상한이 걸린다. 그래서 시스템 전체가 열 수 있는 커넥션의 천장은 이렇게 나온다.

천장 = (떠 있는 task 수) × (task당 worker 수) × (워커당 풀 크기)
예: 15 task × 4 worker × 100 = 6,000

오토스케일 환경이라면 그 순간 실제로 떠 있던 task 수를 써야 한다(컨테이너 메트릭의 RunningTaskCount).

3. 프로세스 하나가 실제로 몇 개를 쥐고 있는지 역산한다 — 여기서 갈린다 DB가 보여주는 값은 보통 모든 프로세스를 합친 총 커넥션 수다. 이걸 프로세스 수로 나눠 한 프로세스가 실제로 몇 개를 쓰는지 본다.

프로세스당 ≈ 총 커넥션 ÷ (task 수 × worker 수)
예: 961 ÷ (8 × 4 = 32) ≈ 30 / 100

고갈이라면 프로세스마다 상한(100)에 바짝 붙어 있어야 하는데, 30밖에 안 된다면 여유가 넘친다. 고갈이 아니라는 뜻이다. 한 가지 더, task가 두 배로 늘어도 커넥션이 그만큼 폭발하지 않는다면, 천장에 막힌 게 아니라 필요한 만큼만 쓰고 있다는 신호다.

4. churn을 확정한다 고갈이 아니라면 남는 설명은 커넥션을 새로 맺는 churn이다. 가장 흔한 원인은 max_lifetime이다. 수명이 다한 커넥션을 버리고 새로 맺는 과정에서 핸드셰이크 비용이 계속 발생하고, 워커와 풀이 많을수록 이 재연결이 더 자주 일어난다.

5. 영향이 얼마인지 잰다 원인을 확정했으면, 고칠 만한 크기인지 따로 잰다. 느린 대기가 전체 요청의 몇 %인지(예: 0.07%), p50은 멀쩡한데 p99만 튀는지를 보면, 체감 느림의 주범인지 아니면 꼬리 지연일 뿐인지 드러난다.

왜 합계를 프로세스 수로 나누는 게 통하나 고갈은 프로세스 하나하나의 상태(size == max)인데, 모니터링은 대개 전체를 합친 값만 보여준다. 합계를 프로세스 수로 되돌리면 개별 프로세스 상태를 간접적으로 들여다볼 수 있다. 다만 한계가 있다. 부하가 한쪽으로 쏠려 특정 프로세스 하나만 순간적으로 꽉 차는 경우는 평균에 묻혀 보이지 않는다. 이런 건 최대 대기 시간이 튀는 지점(예: 2.8초)이나 host별 분포를 따로 봐야 잡힌다.

원인에 따라 고칠 곳이 다르다

  • 고갈이면 풀 크기나 워커당 max_conns를 올린다. 단 DB max_connections 한도 안에서.
  • churn이면 풀을 키워봐야 소용없다. max_lifetime을 늘려 재연결 빈도를 줄이고, 스케일아웃 직후 풀을 미리 채워 cold-connect 버스트를 줄이는 쪽이 낫다.

관련 노트

참고