메인 콘텐츠로 건너뛰기

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples on this page work on VillageSQL. Install Now →
MySQL 연결을 여는 데는 시간이 걸립니다 — TCP 핸드셰이크, 인증, 세션 초기화가 필요합니다. 부하가 걸리면 요청마다 연결을 열고 닫는 애플리케이션은 쿼리보다 연결에 더 많은 시간을 씁니다. 연결 풀링은 요청마다 새 연결을 만드는 대신 기존 연결을 재사용합니다.

연결 비용이 큰 이유

각 MySQL 연결은 서버의 스레드 하나입니다. 연결 시점의 비용은 다음과 같습니다:
  • TCP/TLS 핸드셰이크
  • 인증(자격 증명 확인, 권한 해석)
  • 세션 변수 초기화
바쁜 애플리케이션 서버에서는 이 오버헤드가 빠르게 쌓입니다. 실행에 1ms 걸리는 쿼리가 연결 설정을 포함하면 총 10ms가 걸릴 수 있습니다.

애플리케이션 수준 풀링

대부분의 애플리케이션 프레임워크와 데이터베이스 드라이버에는 연결 풀이 내장되어 있습니다. 이 풀은 열린 연결의 집합을 유지하고, 요청이 있을 때 애플리케이션 스레드에 빌려줍니다. Node.js (mysql2):
Python (SQLAlchemy):
모든 풀의 핵심 설정은 다음과 같습니다:
  • 풀 크기: 유지할 영구 연결의 수
  • 최대 오버플로 / 큐: 순간 부하 시 허용되는 추가 연결의 수
  • 재활용 / 타임아웃: 연결을 닫고 다시 여는 시점(MySQL의 wait_timeout으로 인한 오래된 연결을 방지)

MySQL의 wait_timeout과 오래된 연결

MySQL은 유휴 연결을 wait_timeout초(기본값: 28800초 / 8시간) 후에 닫습니다. 이는 비대화형 연결, 즉 애플리케이션 풀이 사용하는 종류의 연결에 적용됩니다. 풀이 이보다 오래 연결을 보유하면 연결이 오래되어 다음 쿼리가 MySQL server has gone away 오류로 실패합니다.
풀 구성에서 해결하세요: pool_recycle(또는 동등한 설정)을 wait_timeout보다 짧은 값으로 설정합니다. 3600초(1시간)가 안전한 기본값입니다.

다중 서버 구성을 위한 ProxySQL

애플리케이션 수준 풀은 단일 서버에서 잘 동작합니다. 복제본이나 여러 애플리케이션 서버가 있으면 ProxySQL 같은 전용 프록시가 애플리케이션과 MySQL 사이에 위치하여 인프라 수준에서 풀링을 처리합니다. ProxySQL의 이점:
  • 쿼리 라우팅: 읽기는 복제본으로, 쓰기는 기본 서버로 전송
  • 연결 멀티플렉싱: 많은 애플리케이션 연결이 더 적은 수의 MySQL 연결을 공유
  • 장애 조치: 서버가 다운되면 트래픽을 다른 곳으로 재라우팅
기본 ProxySQL 설정은 쿼리 접두사로 읽기와 쓰기를 라우팅합니다:

max_connections 조정

max_connections는 서버 측 상한을 설정합니다. 각 연결은 메모리를 소비합니다 — 기본 thread_stack인 약 1MB를 기준으로, 세션의 다른 정적 버퍼와 쿼리 실행 중 필요에 따라 할당되는 추가 메모리(정렬 버퍼, 읽기 버퍼, 임시 테이블)가 더해집니다. 쿼리 부하가 높으면 연결당 비용이 수 MB에 이를 수 있습니다. max_connections를 너무 높게 설정하면 메모리를 낭비하고, 너무 낮게 설정하면 ERROR 1040 (08004): Too many connections가 발생합니다.
대략적인 크기 산정 공식: max_connections = (연결에 사용 가능한 RAM) / (연결당 메모리). Max_used_connections를 모니터링하여 실제 최대치를 파악한 다음, max_connections를 그보다 20–30% 높게 설정하세요.

자주 묻는 질문

풀에 연결이 몇 개 있어야 하나요?

데이터베이스 서버의 CPU 코어 수로 시작한 다음 부하에 따라 늘려가며 조정하세요. I/O 바운드가 심한 워크로드는 CPU 코어보다 많은 연결에서 이점을 얻을 수 있지만, CPU 바운드 워크로드는 그렇지 않습니다. HikariCP의 잘 알려진 공식 connections = (core_count * 2) + effective_spindle_count는 회전 디스크를 위해 설계된 것으로, SSD에는 적용되지 않습니다. 최신 인프라에서는 최대 부하 시 Threads_running으로 실제 동시성을 측정하고 그 값을 기준선으로 사용하세요.

Threads_connected와 Threads_running의 차이는 무엇인가요?

Threads_connected는 풀에서 유휴 상태로 있는 연결을 포함하여 열려 있는 모든 연결을 셉니다. Threads_running은 지금 이 순간 쿼리를 실제로 실행 중인 연결을 셉니다. 두 값의 차이가 유휴 연결 수입니다. Threads_connected가 크고 Threads_running이 작은 것은 정상적인 풀에서 흔한 모습입니다.

PHP에서 영구 연결을 사용해야 하나요?

피하세요. PHP에는 재사용된 연결을 초기화하는 기능이 없습니다 — 열린 채 남은 트랜잭션, 테이블 잠금, 임시 테이블, 변경된 세션 변수가 다음 요청으로 넘어갑니다. 이는 교착 상태, 데이터 손상, 재현하기 어려운 버그를 일으킬 수 있습니다. 대신 적절한 연결 풀(풀링이 있는 PDO, 또는 ProxySQL 같은 프록시)을 사용하세요.

문제 해결

함께 보기