DB 커넥션풀의 증가와 성능의 관계
DB 커넥션풀의 증가와 성능의 관계
커넥션 풀이 크면 그만큼 동시에 많은 요청을 처리하니까 빠를 거라고 막연히 생각했다. 응답이 느리면 풀 크기부터 올리는 식이었다. 그런데 정말 그런지 재본 적은 없어서, 풀 크기만 바꿔가며 처리량이 어떻게 변하는지 직접 부하를 줘봤다. 결과는 예상과 달랐다. 어느 지점을 넘으니 풀을 키울수록 오히려 느려졌다.
커넥션은 공짜 슬롯이 아니다
먼저 왜 그런지부터. 커넥션 풀 크기는 "DB에 동시에 던질 수 있는 쿼리 수"다. 그런데 그 쿼리를 실제로 굴리는 건 DB 서버의 CPU 코어다. 코어가 10개인 DB에 커넥션 20개로 동시에 쿼리를 던져도, 진짜로 동시에 계산되는 건 10개뿐이고 나머지 10개는 CPU를 기다린다. 커넥션을 더 늘리면 대기 줄만 길어지고, DB는 그 많은 커넥션 사이를 오가며 컨텍스트 스위칭을 하느라 오히려 일이 는다. 커넥션은 공짜 슬롯이 아니라 DB 쪽 스레드와 메모리를 잡아먹는 실체가 있는 자원이다.
이론상 그렇다는 거고, 실제로 어디서 꺾이는지는 재봐야 안다.
실제 측정 후기
10코어짜리 PostgreSQL에, CPU를 한동안 쓰는 무거운 쿼리(약 60ms짜리) 96개를 던지는 작업을 고정해두고, 그걸 처리하는 풀 크기만 1부터 48까지 바꿨다. 워커 하나가 커넥션 하나다. 같은 작업량이니 풀이 클수록 빨리 끝나야 "풀은 클수록 좋다"가 맞다. 처리량(초당 쿼리)은 이렇게 나왔다.
풀 크기 처리량(q/s)
1 25
2 45
4 82
8 124
12 134 ← 정점
16 132
24 121
48 116

풀이 코어 수보다 작을 때는 늘리는 대로 처리량이 붙었다. 1→2→4에서 25, 45, 82로 거의 두 배씩. 커넥션이 4개면 코어 10개 중 4개만 일하니, 커넥션을 늘리는 게 곧 노는 코어를 깨우는 거였다. 그런데 8을 넘어가면서 곡선이 눕는다. 12에서 134로 정점을 찍고, 그 위로는 더 안 오른다. 코어 10개가 다 찼으니 커넥션을 더 줘도 굴릴 CPU가 없다.
여기까진 "커봐야 본전"이라 쳐도, 문제는 그 뒤다. 24, 48로 가면 처리량이 121, 116으로 오히려 떨어진다. 풀을 4배(12→48) 키웠는데 결과는 더 느려진 거다. 커넥션 48개가 코어 10개를 두고 서로 밀치면서, DB가 실제 계산보다 스케줄링에 시간을 쓴 만큼이 그대로 손해로 잡혔다. 응답이 느릴 때 풀부터 올리던 습관이 정확히 이 구간에선 상황을 악화시키는 선택이었다.
풀 크기는 코어 수에 묶인다
HikariCP 문서가 권하는 공식이 이 실측과 정확히 맞는다.
커넥션 수 = (코어 수 × 2) + 디스크 스핀들 수
핵심은 풀 크기의 상한을 **애플리케이션 서버가 아니라 DB의 물리 자원(코어·디스크)**이 정한다는 점이다. 내 실험은 CPU 바운드라 스핀들 항이 없어서 대략 코어 수 근처(10~20)에서 정점이었고, I/O 대기가 섞이면 CPU가 다른 커넥션 일을 할 수 있어 실제로 커넥션 풀대로 성능이 향상된다. 하지만 둘 다 커넥션 풀은 수십 개 선에 맞춘다. 톰캣 기본 스레드가 200개라고 커넥션 풀도 200개로 맞추면 DB는 감당 못 할 커넥션을 받아놓고 스케줄링만 하다 멈춰 버릴 것이다.
마치며
요즘 개발할 때는 병목이 발생하면 해당 로직이 CPU 바운드 때문인지 IO 바운드인지, 커넥션 풀을 늘리면 해결될지 아니면 CPU 작업을 비동기로 따로 빼야 하는지 판단할 수 있는 능력이 생겼다.