> ## Documentation Index
> Fetch the complete documentation index at: https://villagesql.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# MySQL 연결 풀링

> MySQL 연결 풀링의 동작 방식, 영구 연결이 성능에 중요한 이유, ProxySQL과 애플리케이션 수준 풀러를 구성하는 방법입니다.

> [card] [VillageSQL is a drop-in replacement for MySQL with extensions.](https://villagesql.com/docs/en/8.4/stable/install-server.md)
>
> All examples on this page work on VillageSQL. Install Now →

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

## 연결 비용이 큰 이유

각 MySQL 연결은 서버의 스레드 하나입니다. 연결 시점의 비용은 다음과 같습니다:

* TCP/TLS 핸드셰이크
* 인증(자격 증명 확인, 권한 해석)
* 세션 변수 초기화

바쁜 애플리케이션 서버에서는 이 오버헤드가 빠르게 쌓입니다. 실행에 1ms 걸리는 쿼리가 연결 설정을 포함하면 총 10ms가 걸릴 수 있습니다.

```sql theme={null}
-- See how many connections are currently active
SHOW STATUS LIKE 'Threads_connected';

-- See the server's connection limit
SHOW VARIABLES LIKE 'max_connections';

-- See peak connections since last restart
SHOW STATUS LIKE 'Max_used_connections';
```

## 애플리케이션 수준 풀링

대부분의 애플리케이션 프레임워크와 데이터베이스 드라이버에는 연결 풀이 내장되어 있습니다. 이 풀은 열린 연결의 집합을 유지하고, 요청이 있을 때 애플리케이션 스레드에 빌려줍니다.

**Node.js (mysql2):**

```javascript theme={null}
import mysql from 'mysql2/promise';

const pool = mysql.createPool({
  host: 'localhost',
  user: 'app',
  database: 'mydb',
  waitForConnections: true,
  connectionLimit: 10,       // max simultaneous connections
  queueLimit: 0              // unlimited queue (0 = no limit)
});

// connection is returned to the pool automatically
const [rows] = await pool.execute('SELECT * FROM orders WHERE id = ?', [42]);
```

**Python (SQLAlchemy):**

```python theme={null}
from sqlalchemy import create_engine

engine = create_engine(
    'mysql+mysqlconnector://user:pass@localhost/mydb',
    pool_size=10,        # persistent connections to keep open
    max_overflow=5,      # extra connections allowed under peak load
    pool_timeout=30,     # seconds to wait for a connection from the pool
    pool_recycle=3600    # recycle connections after 1 hour (avoids stale connections)
)
```

모든 풀의 핵심 설정은 다음과 같습니다:

* **풀 크기**: 유지할 영구 연결의 수
* **최대 오버플로 / 큐**: 순간 부하 시 허용되는 추가 연결의 수
* **재활용 / 타임아웃**: 연결을 닫고 다시 여는 시점(MySQL의 `wait_timeout`으로 인한 오래된 연결을 방지)

## MySQL의 wait\_timeout과 오래된 연결

MySQL은 유휴 연결을 `wait_timeout`초(기본값: 28800초 / 8시간) 후에 닫습니다. 이는 비대화형 연결, 즉 애플리케이션 풀이 사용하는 종류의 연결에 적용됩니다. 풀이 이보다 오래 연결을 보유하면 연결이 오래되어 다음 쿼리가 `MySQL server has gone away` 오류로 실패합니다.

```sql theme={null}
-- Check the server's idle connection timeout
SHOW VARIABLES LIKE 'wait_timeout';
```

풀 구성에서 해결하세요: `pool_recycle`(또는 동등한 설정)을 `wait_timeout`보다 짧은 값으로 설정합니다. 3600초(1시간)가 안전한 기본값입니다.

## 다중 서버 구성을 위한 ProxySQL

애플리케이션 수준 풀은 단일 서버에서 잘 동작합니다. 복제본이나 여러 애플리케이션 서버가 있으면 ProxySQL 같은 전용 프록시가 애플리케이션과 MySQL 사이에 위치하여 인프라 수준에서 풀링을 처리합니다.

ProxySQL의 이점:

* **쿼리 라우팅**: 읽기는 복제본으로, 쓰기는 기본 서버로 전송
* **연결 멀티플렉싱**: 많은 애플리케이션 연결이 더 적은 수의 MySQL 연결을 공유
* **장애 조치**: 서버가 다운되면 트래픽을 다른 곳으로 재라우팅

기본 ProxySQL 설정은 쿼리 접두사로 읽기와 쓰기를 라우팅합니다:

```sql theme={null}
-- In ProxySQL admin interface
-- Route SELECT queries to read hostgroup (replicas)
INSERT INTO mysql_query_rules (rule_id, active, match_digest, destination_hostgroup)
VALUES (1, 1, '^SELECT', 2);

-- All other queries go to write hostgroup (primary)
INSERT INTO mysql_query_rules (rule_id, active, match_digest, destination_hostgroup)
VALUES (2, 1, '.*', 1);

LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;
```

## max\_connections 조정

`max_connections`는 서버 측 상한을 설정합니다. 각 연결은 메모리를 소비합니다 — 기본 `thread_stack`인 약 1MB를 기준으로, 세션의 다른 정적 버퍼와 쿼리 실행 중 필요에 따라 할당되는 추가 메모리(정렬 버퍼, 읽기 버퍼, 임시 테이블)가 더해집니다. 쿼리 부하가 높으면 연결당 비용이 수 MB에 이를 수 있습니다. `max_connections`를 너무 높게 설정하면 메모리를 낭비하고, 너무 낮게 설정하면 `ERROR 1040 (08004): Too many connections`가 발생합니다.

```sql theme={null}
-- Increase max connections (requires SUPER privilege)
SET GLOBAL max_connections = 500;
```

대략적인 크기 산정 공식: `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 같은 프록시)을 사용하세요.

## 문제 해결

| 문제 | 해결 방법 |
| :- | :- |
| `ERROR 1040 (08004): Too many connections` | `max_connections`를 늘리거나, 풀 크기를 줄이거나, ProxySQL을 추가 |
| `MySQL server has gone away` | 오래된 연결 — `pool_recycle`을 `wait_timeout`보다 짧게 설정 |
| `Threads_connected`는 높지만 처리량이 낮음 | 풀이 너무 큼 — 유휴 연결이 메모리를 소비하므로 풀 크기를 줄임 |
| 부하 시 연결 지연 시간 급증 | 풀이 너무 작음 — 요청이 연결을 기다리며 대기하므로 풀 크기를 늘리거나 프록시를 추가 |

## 함께 보기

* [MySQL 느린 쿼리 로그](https://villagesql.com/docs/ko/guides/slow-query-log.md) — 쿼리 지연 시간과 연결 오버헤드를 구분하기
* [MySQL 복제 기초](https://villagesql.com/docs/ko/guides/replication-basics.md) — 기본 서버와 복제본에 걸친 풀링
* [MySQL 읽기 복제본](https://villagesql.com/docs/ko/guides/read-replicas.md) — 읽기와 쓰기가 분리될 때 역할별 풀링
* [ORM과 함께 MySQL 사용하기: Django, Rails, Prisma](https://villagesql.com/docs/ko/guides/mysql-with-orms.md) — 모든 ORM이 여는 풀


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.