FastAPI로 백엔드 서비스를 운영하면서 PostgreSQL 연결 문제를 겪은 적이 있다. API 자체의 로직이나 SQL 문법에서 발생한 오류가 아니라, 어느 순간 PostgreSQL에 새로운 연결을 만들 수 없다는 메시지가 나타났다.
로그에서 가장 눈에 들어온 문구는 다음과 같았다.
too many clients already
처음에는 메시지 그대로 받아들였다. PostgreSQL이 허용하는 최대 연결 수를 모두 사용했으니 max_connections를 늘리면 해결할 수 있지 않을까 생각했다.
실제로 PostgreSQL에서 동시에 허용할 수 있는 연결 수에는 제한이 있기 때문에 이 접근 자체가 틀린 것은 아니다. 문제는 왜 현재 서비스가 그 한계까지 연결을 사용하게 되었는지 설명하지 못한 상태에서 제한값만 높이는 것이었다.
당시 백엔드는 FastAPI로 구성되어 있었고 PostgreSQL 연결에는 asyncpg를 사용하고 있었다. Connection Pool 설정도 이미 적용하고 있었다.
내가 사용하던 설정은 대략 다음과 같은 형태였다.
pool = await asyncpg.create_pool(
dsn=DATABASE_URL,
min_size=1,
max_size=50
)
처음에는 이 설정을 보고 크게 문제될 것이 없다고 생각했다. 최소 연결은 1개만 유지하고, 요청이 늘어날 때 최대 50개까지 사용할 수 있으니 애플리케이션 하나가 PostgreSQL 연결을 무제한으로 만들지는 않을 것이라고 생각했기 때문이다.
그런데 too many clients already 오류를 겪고 나니 max_size=50이라는 숫자를 바라보는 방식부터 다시 생각해야 했다.
처음에는 PostgreSQL의 max_connections가 부족하다고 생각했다
PostgreSQL의 최대 연결 수는 다음 명령으로 확인할 수 있다.
SHOW max_connections;
max_connections는 PostgreSQL 서버가 동시에 허용하는 클라이언트 연결 수를 결정한다.
오류 메시지만 놓고 보면 해결 방향은 간단해 보인다.
현재 연결 수가 한계에 도달했다.
→ max_connections를 늘린다.
→ 다시 연결할 수 있게 된다.
당장 장애를 해소해야 하는 상황이라면 실제로 이 방법이 필요할 수도 있다.
나 역시 처음에는 max_connections를 상당히 크게 올리는 방안까지 검토했다. 숫자를 충분히 크게 만들어 놓으면 적어도 같은 오류는 다시 발생하지 않을 것처럼 보였다.
하지만 여기서 한 가지 의문이 생겼다.
FastAPI 애플리케이션에서 이미 Connection Pool을 사용하고 있는데, 왜 PostgreSQL 연결 한계가 문제가 될 정도로 연결 수가 늘어난 것일까?
이 질문을 해결하지 않은 상태에서 max_connections만 계속 높이는 것은 문제를 뒤로 미루는 것에 가까웠다.
연결 수를 100에서 300으로 늘렸는데 애플리케이션 구조상 연결이 계속 증가한다면 언젠가는 300도 부족해질 수 있다. 300을 1,000으로 올린다고 해서 연결을 사용하는 방식이 바뀌는 것은 아니다.
그래서 이후부터는 PostgreSQL 설정값 자체보다 먼저 애플리케이션이 DB 연결을 어떻게 사용하고 있는지를 보게 됐다.
Connection Pool을 쓴다고 연결 문제가 자동으로 해결되는 것은 아니었다
Connection Pool을 처음 적용할 때는 상당히 단순하게 이해했다.
매 API 요청마다 PostgreSQL에 새로운 연결을 만드는 것은 비용이 크기 때문에 일정 수의 Connection을 미리 만들어 두고 재사용한다는 개념이다.
흐름을 단순하게 표현하면 다음과 같다.
API 요청
↓
Connection Pool
↓
사용 가능한 Connection 대여
↓
SQL 실행
↓
Connection 반환
이 구조라면 요청이 끝날 때마다 DB 연결 자체를 닫았다가 새로 연결하지 않아도 된다.
FastAPI처럼 비동기 요청을 처리하는 서비스에서는 특히 유용하다.
문제는 Pool을 사용한다는 사실 하나만으로 연결 관리가 올바르다고 판단하면 안 된다는 것이었다.
Pool에는 적어도 몇 가지를 함께 봐야 한다.
- Pool을 언제 생성하는가
- 애플리케이션 안에 Pool이 몇 개 존재하는가
- 하나의 Pool에서 최대 몇 개의 연결을 허용하는가
- Connection을 사용한 뒤 정상적으로 반환하는가
- FastAPI 프로세스가 몇 개인가
- 동일한 애플리케이션이 여러 컨테이너에서 실행되고 있는가
- 같은 PostgreSQL을 사용하는 다른 프로그램이 있는가
내 설정에 보이는 max_size=50이라는 숫자도 이 전체 구조에서 봐야 했다.
max_size=50은 PostgreSQL 전체 연결이 50개라는 뜻이 아니다
이 부분이 연결 문제를 다시 보면서 가장 중요하게 느껴졌던 부분이다.
다음과 같은 설정이 있다고 하자.
pool = await asyncpg.create_pool(
min_size=1,
max_size=50
)
코드만 보면 최대 연결은 50개다.
그래서 얼핏 보면 PostgreSQL의 max_connections가 100 이상이라면 충분해 보인다.
하지만 이것은 Pool 하나를 기준으로 한 숫자다.
FastAPI 애플리케이션이 여러 프로세스로 실행되고 있다면 각 프로세스에서 별도의 Pool이 만들어질 수 있다.
예를 들어 개념적으로 worker가 4개이고 각 worker마다 최대 50개의 Connection을 가진 Pool이 만들어진다면 계산은 달라진다.
50 connections
×
4 workers
=
최대 200 connections
여기에 애플리케이션을 Docker 컨테이너 두 개로 운영한다면 이론적인 최대치는 더 커질 수 있다.
50
× 4 workers
× 2 containers
=
최대 400 connections
물론 실제 서비스에서 항상 이 최대치까지 Connection을 사용하는 것은 아니다.
하지만 중요한 것은 코드에 max_size=50이라고 적혀 있다는 이유만으로 PostgreSQL에는 최대 50개만 연결된다고 생각하면 안 된다는 것이다.
Pool은 프로세스의 메모리 안에 존재하기 때문에 프로세스가 여러 개라면 Pool도 여러 개가 될 수 있다.
이후부터는 Pool 크기를 정할 때 다음과 같이 생각하게 됐다.
Pool max_size
×
프로세스 수
×
컨테이너 수
×
DB를 사용하는 애플리케이션 수
이 값을 정확한 최대 연결 수로 단정할 필요는 없지만 적어도 PostgreSQL 전체 연결 규모를 예상하는 기준으로는 훨씬 유용했다.
요청마다 Pool을 만드는 코드가 없는지도 확인해야 한다
Connection Pool과 관련해서 가장 먼저 확인해야 하는 부분 중 하나는 Pool을 생성하는 위치다.
예를 들어 아래 코드처럼 DB 함수 내부에서 Pool을 생성한다고 생각해 보자.
async def get_books():
pool = await asyncpg.create_pool(
dsn=DATABASE_URL,
min_size=1,
max_size=50
)
async with pool.acquire() as conn:
rows = await conn.fetch(
"SELECT * FROM books"
)
return rows
코드만 보면 asyncpg.create_pool()을 사용하고 있기 때문에 Connection Pool을 제대로 사용하고 있는 것처럼 보인다.
하지만 get_books()가 API 요청마다 실행된다면 요청이 들어올 때마다 새로운 Pool 객체를 만드는 구조가 된다.
Connection을 재사용하기 위해 Pool을 도입했는데 정작 Pool 자체를 계속 새로 만드는 셈이다.
그래서 Pool은 일반적으로 애플리케이션이 시작될 때 한 번 생성하고 여러 API에서 공유하는 구조가 이해하기 쉽다.
FastAPI의 lifespan을 사용하면 다음과 같은 형태로 구성할 수 있다.
from contextlib import asynccontextmanager
import asyncpg
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.db_pool = await asyncpg.create_pool(
dsn=DATABASE_URL,
min_size=1,
max_size=50
)
yield
await app.state.db_pool.close()
app = FastAPI(lifespan=lifespan)
애플리케이션이 시작되면 Pool을 만들고:
FastAPI 시작
→ asyncpg Pool 생성
API 요청에서는 이미 만들어진 Pool을 사용한다.
@app.get("/books")
async def get_books():
async with app.state.db_pool.acquire() as conn:
rows = await conn.fetch(
"SELECT * FROM books LIMIT 10"
)
return rows
그리고 애플리케이션이 종료될 때 Pool도 닫는다.
FastAPI 종료
→ Pool close
이렇게 하면 적어도 DB Pool의 생명주기가 애플리케이션의 생명주기와 맞아 떨어진다.
Pool을 어디에서 만들고 있는지가 불명확하면 연결 수 문제를 추적할 때도 어려워진다.
Connection을 가져온 뒤 제대로 반환하는지도 중요했다
Pool 자체가 하나만 존재하더라도 Connection을 가져온 뒤 정상적으로 반환하지 않으면 문제가 생길 수 있다.
asyncpg에서는 다음과 같이 Connection을 가져올 수 있다.
conn = await pool.acquire()
이렇게 직접 가져왔다면 사용이 끝난 뒤 Pool에 돌려줘야 한다.
await pool.release(conn)
하지만 실제 코드에서는 DB 처리 도중 예외가 발생할 수 있다.
예를 들면:
conn = await pool.acquire()
rows = await conn.fetch(...)
result = process(rows)
await pool.release(conn)
conn.fetch()나 process()에서 예외가 발생한다면 마지막의 release()까지 코드가 실행되지 않을 수 있다.
그래서 직접 acquire/release를 관리한다면 적어도 다음처럼 정리할 필요가 있다.
conn = await pool.acquire()
try:
rows = await conn.fetch(...)
return rows
finally:
await pool.release(conn)
또는 가능하다면 다음과 같이 context manager를 사용하는 편이 코드 흐름이 명확하다.
async with pool.acquire() as conn:
rows = await conn.fetch(...)
async with 블록을 벗어나면 Connection을 Pool에 반환하는 구조이기 때문에 예외 처리와 연결 반환 코드를 직접 관리해야 하는 부담도 줄어든다.
too many clients already 같은 문제를 겪고 나면 이런 코드가 단순한 스타일 차이로 보이지 않는다.
Connection은 생성하는 것만큼 반환하는 것도 중요하다.
PostgreSQL에서는 전체 숫자보다 연결 상태를 같이 보는 것이 좋았다
연결 문제가 발생했을 때 PostgreSQL에서 현재 몇 개의 Connection이 존재하는지 보는 가장 기본적인 방법은 pg_stat_activity다.
전체 연결 개수만 확인한다면:
SELECT COUNT(*)
FROM pg_stat_activity;
조금 더 중요한 것은 상태별로 보는 것이다.
SELECT
state,
COUNT(*)
FROM pg_stat_activity
GROUP BY state;
환경에 따라 다음과 같은 상태가 보일 수 있다.
active
idle
idle in transaction
여기에서 각 상태를 무조건 정상이나 비정상으로 나눌 수는 없다.
Connection Pool을 사용하면 사용하지 않는 Connection이 idle 상태로 존재하는 것은 자연스러운 경우도 있다. Pool 자체가 Connection을 유지하면서 다음 요청에서 재사용하기 때문이다.
그래서 idle이 있다는 사실 자체가 문제는 아니다.
대신 다음과 같이 봐야 한다.
현재 서비스 규모와 Pool 설정을 고려했을 때 이 숫자가 설명 가능한가?
예를 들어 애플리케이션 하나가 max_size=10인 Pool 하나만 가지고 있다고 생각했는데 PostgreSQL에는 동일한 애플리케이션에서 들어온 연결이 예상보다 훨씬 많이 보인다면 다음 질문으로 넘어갈 수 있다.
혹시 Pool이 여러 개 만들어지고 있나?
worker가 여러 개인가?
같은 컨테이너가 여러 개 떠 있나?
다른 서비스도 같은 DB를 사용하고 있나?
pg_stat_activity는 문제의 정답을 바로 알려주는 도구라기보다 현재 애플리케이션 구조에 대한 내 예상과 실제 PostgreSQL 상태가 일치하는지를 확인하는 도구로 보는 것이 유용했다.
어떤 프로그램이 연결하고 있는지도 같이 보면 좋다
Connection 개수만으로 원인을 찾기 어렵다면 조금 더 자세히 볼 수 있다.
예를 들어 다음과 같이 확인할 수 있다.
SELECT
pid,
usename,
application_name,
client_addr,
state,
backend_start,
query_start,
query
FROM pg_stat_activity
ORDER BY backend_start;
여기서 확인하고 싶은 것은 여러 가지다.
어떤 사용자로 연결했는가
어느 주소에서 들어왔는가
언제 Connection이 만들어졌는가
현재 어떤 상태인가
어떤 Query를 실행하고 있는가
특히 PostgreSQL 하나를 여러 서비스가 공유하고 있다면 단순한 연결 총합만으로는 어느 서비스가 많은 Connection을 사용하고 있는지 알기 어렵다.
서비스별로 PostgreSQL 계정을 분리하거나 application_name을 구분해 놓으면 이런 상황에서 훨씬 분석하기 쉽다.
운영하면서 문제가 생긴 뒤에야 로그와 식별 정보의 중요성을 느끼는 경우가 많다.
평소에는 단순히 연결만 되면 충분해 보이지만 장애가 발생하면 다음 질문에 답할 수 있어야 한다.
이 Connection은 어디에서 만들어진 것인가?
idle in transaction은 별도로 보는 편이 좋다
pg_stat_activity를 보다 보면 idle과 별개로 idle in transaction 상태가 존재할 수 있다.
이 상태는 Transaction을 시작한 뒤 Commit 또는 Rollback이 끝나지 않은 채 클라이언트가 기다리고 있는 상황에서 나타날 수 있다.
예를 들어:
async with pool.acquire() as conn:
tx = conn.transaction()
await tx.start()
await conn.execute(...)
# commit 또는 rollback이 정상적으로 수행되지 않는 흐름
Transaction 관리가 복잡한 코드에서는 예외가 발생했을 때 Transaction 정리가 누락되는지도 확인할 필요가 있다.
가능하면 다음처럼 context manager를 이용하면 코드 흐름을 줄일 수 있다.
async with pool.acquire() as conn:
async with conn.transaction():
await conn.execute(...)
물론 idle in transaction이 발견됐다고 해서 무조건 이것이 too many clients already의 원인이라고 단정해서는 안 된다.
다만 연결 수 문제를 보고 있는데 이런 상태가 오래 유지되는 Connection이 많다면 추가로 확인해 볼 가치가 있다.
max_connections를 무작정 크게 만들지 않으려고 한 이유
PostgreSQL의 max_connections는 높이면 높일수록 좋은 성능 옵션이 아니다.
Connection마다 PostgreSQL 서버의 리소스가 필요하고, 동시에 많은 Query가 실행되면 메모리 사용량과 CPU 부하도 같이 고려해야 한다.
예를 들어 work_mem은 Query 실행 과정에서 정렬이나 해시 작업 등에 사용될 수 있다.
단순히 다음처럼 생각하면 안 된다.
RAM이 많다
→ Connection도 몇 천 개로 올리면 된다
실제 메모리 사용량은 Query 패턴, 동시 실행 수, PostgreSQL 설정 등에 따라 달라지기 때문이다.
그래서 too many clients already가 나타났을 때:
max_connections를 100에서 300으로 올렸다
→ 해결
이라고 끝내기보다,
왜 100개 이상의 Connection이 필요했는가?
를 먼저 설명할 수 있어야 한다고 생각하게 됐다.
물론 트래픽이 실제로 증가했고 정상적인 동시 요청을 처리하기 위해 더 많은 Connection이 필요한 상황이라면 max_connections 자체를 조정하는 것이 맞을 수 있다.
중요한 것은 연결 누수와 정상적인 확장을 구분하는 것이다.
Pool을 무조건 크게 잡는 것도 좋은 방법은 아니었다
Pool 최대 크기를 크게 설정하면 요청이 몰렸을 때 더 많은 Query를 동시에 처리할 수 있으니 성능이 좋아질 것처럼 보인다.
하지만 DB가 처리할 수 있는 양보다 많은 Query를 동시에 밀어 넣는다고 전체 시스템의 처리량이 반드시 높아지는 것은 아니다.
예를 들어:
FastAPI 요청 500개
↓
DB Connection 500개
↓
PostgreSQL Query 500개 동시 실행
보다 경우에 따라:
FastAPI 요청 500개
↓
DB Pool 20개
↓
PostgreSQL에는 최대 20개씩 처리
↓
나머지는 Pool에서 Connection 대기
가 오히려 데이터베이스를 안정적으로 운영하는 데 도움이 될 수 있다.
이 때문에 Pool은 단순히 “연결을 많이 만들어 주는 기능”이 아니라 DB에 들어가는 동시 작업량을 제한하는 역할도 한다.
max_size=50 역시 절대적인 정답은 아니다.
서비스의 Query 속도, 요청량, PostgreSQL 사양, worker 수 등을 보면서 결정해야 한다.
FastAPI worker를 늘릴 때 DB 연결 수도 같이 봐야 한다
웹 API 성능을 높이려고 worker를 추가할 때도 DB 연결 수를 같이 확인해야 한다.
예를 들어 Pool 설정이 다음과 같다고 하자.
max_size=50
worker가 하나일 때와 8개일 때 DB가 보는 구조는 같지 않다.
worker 1
→ Pool 최대 50
반면 각 worker가 독립적으로 Pool을 생성한다면:
worker 8
→ Pool 최대 50 × 8
가 될 가능성이 있다.
그래서 애플리케이션 프로세스를 늘릴 때 단순히 CPU 사용률이나 API 처리량만 봐서는 안 된다.
내가 지금 확인하는 식은 다음에 가깝다.
예상 DB Connection 수
=
Pool max_size
×
FastAPI worker 수
×
애플리케이션 인스턴스 수
여기에 다른 백엔드와 배치 프로그램이 같은 PostgreSQL을 사용한다면 그것까지 추가한다.
이 계산은 정확한 사용량을 예측하려는 목적보다는 설정값 사이에 큰 모순이 없는지 확인하기 위한 것이다.
하나의 PostgreSQL을 여러 API가 공유하면 더 복잡해진다
서비스가 커지면서 다음과 같은 구조가 될 수도 있다.
검색 API ─┐
추천 API ─┤
관리 API ─┼── PostgreSQL
배치 작업 ─┤
로그 API ─┘
각 애플리케이션에서 별도로 Connection Pool을 사용하면 모든 Pool의 최대 연결 수를 합해서 생각해야 한다.
예를 들어 각 서비스에서 편하게:
max_size=50
으로 설정해 놓았다면 서비스 수가 늘어날수록 PostgreSQL 입장에서는 잠재적인 연결 수가 계속 증가한다.
그래서 DB 연결은 각 애플리케이션 개발자가 독립적으로 정하는 값이라기보다 공유 인프라의 제한된 자원을 나누는 문제에 가깝다.
예를 들면 전체 Connection 예산을 대략 다음처럼 보는 방식이다.
PostgreSQL max_connections
- 관리자 및 운영용 여유
- 검색 API Pool
- 추천 API Pool
- 배치 프로그램
- 기타 서비스
= 전체 연결 예산
이렇게 생각하면 새로운 서비스를 추가할 때도 기존 서비스의 Pool 설정까지 같이 보게 된다.
지금 같은 오류를 다시 본다면 확인할 순서
이후에는 too many clients already가 보인다고 바로 PostgreSQL 설정 파일부터 수정하지 않는다.
먼저 현재 상태를 확인한다.
1. PostgreSQL 최대 연결 수 확인
SHOW max_connections;
이 값은 현재 서버가 허용하는 연결 상한을 알려준다.
2. 현재 사용 중인 Connection 수 확인
SELECT COUNT(*)
FROM pg_stat_activity;
실제 Connection 수가 상한에 얼마나 가까운지 본다.
3. 상태별로 나눠 확인
SELECT
state,
COUNT(*)
FROM pg_stat_activity
GROUP BY state
ORDER BY COUNT(*) DESC;
active, idle, idle in transaction 등이 어떤 비율인지 확인한다.
4. 어느 클라이언트에서 들어왔는지 확인
SELECT
usename,
application_name,
client_addr,
state,
COUNT(*)
FROM pg_stat_activity
GROUP BY
usename,
application_name,
client_addr,
state
ORDER BY COUNT(*) DESC;
하나의 서비스에서 Connection이 집중되고 있는지 확인하는 데 도움이 된다.
5. asyncpg Pool 설정 확인
코드에서 다음 설정을 찾는다.
min_size
max_size
숫자만 확인하는 게 아니라 그 Pool이 몇 개의 프로세스에서 만들어지는지까지 같이 본다.
6. create_pool 호출 위치 검색
프로젝트 전체에서:
asyncpg.create_pool
이 어디에서 실행되는지 확인한다.
애플리케이션 시작 시 한 번만 실행되는지, 요청 처리 함수 안에서 반복적으로 실행되는지 보는 것이다.
7. Connection 반환 구조 확인
다음 패턴이 있는지 확인한다.
await pool.acquire()
직접 acquire한다면 대응되는 release가 모든 코드 흐름에서 실행되는지도 본다.
가능하면:
async with pool.acquire() as conn:
형태로 관리하면 확인하기가 상대적으로 쉽다.
8. worker와 컨테이너 수 확인
코드의 Pool 크기만 보고 끝내지 않는다.
max_size
× worker
× container
를 계산해서 현재 PostgreSQL 설정과 비교한다.
오류를 겪고 나서 가장 크게 바뀐 생각
이 문제를 보기 전에는 PostgreSQL 연결 수를 주로 서버 설정이라고 생각했다.
max_connections는 PostgreSQL 설정이고 max_size는 asyncpg 설정이라고 각각 따로 봤다.
하지만 실제 서비스에서는 둘이 완전히 연결되어 있다.
FastAPI 요청
↓
FastAPI worker
↓
asyncpg Pool
↓
PostgreSQL Connection
↓
max_connections
Docker를 사용한다면 그 위에 컨테이너 개수까지 추가된다.
결국 PostgreSQL의 too many clients already 오류는 DB 설정 하나만의 문제가 아니라 애플리케이션 배포 구조 전체에서 Connection이 어떻게 만들어지는지를 보여주는 결과일 수 있다.
이후부터는 같은 문제가 발생하면 단순히:
DB 연결이 부족하다.
라고 생각하지 않는다.
대신 다음 질문부터 한다.
현재 애플리케이션 구조를 기준으로 PostgreSQL에 몇 개의 Connection이 만들어질 수 있는가?
이 질문에 답할 수 있어야 max_connections를 늘리는 것이 필요한 조치인지, Pool을 수정해야 하는지, worker 수를 조정해야 하는지 판단할 수 있다.
내가 사용하는 PostgreSQL 연결 문제 체크리스트
마지막으로 비슷한 상황에서 다시 확인하기 위해 정리한 항목이다.
PostgreSQL
SHOW max_connections로 최대 연결 수를 확인했는가pg_stat_activity로 현재 Connection 수를 확인했는가active,idle,idle in transaction상태를 구분해서 봤는가- 특정 사용자나 클라이언트에서 Connection이 집중되는지 확인했는가
- 장시간 유지되는 비정상적인 Connection이 있는지 확인했는가
FastAPI / asyncpg
create_pool()이 애플리케이션 시작 시 한 번만 실행되는가min_size와max_size가 현재 트래픽에 적절한가- 직접
acquire()한 Connection을 정상적으로release()하는가 - 가능하면
async with pool.acquire()를 사용하고 있는가 - Transaction이 정상적으로 commit 또는 rollback되는가
배포 환경
- FastAPI worker가 몇 개인가
- Docker 컨테이너가 몇 개인가
- 각 프로세스에서 독립적으로 Pool을 만들고 있는가
- 같은 PostgreSQL을 사용하는 다른 서비스가 있는가
- 모든 서비스의 Pool 크기를 합쳐도 DB가 감당할 수 있는 수준인가
마무리
too many clients already라는 메시지만 보면 해결 방법은 단순해 보인다.
PostgreSQL Connection 부족
→ max_connections 증가
나도 처음에는 그렇게 접근했다.
하지만 FastAPI와 asyncpg Connection Pool을 함께 사용하는 환경에서는 PostgreSQL의 제한값만 봐서는 전체 구조를 이해하기 어렵다.
Pool 하나의 max_size, FastAPI의 worker 수, Docker의 컨테이너 수, Connection 반환 방식, 같은 DB를 사용하는 다른 애플리케이션까지 모두 연결되어 있기 때문이다.
그래서 지금은 too many clients already를 만나면 얼마까지 연결을 늘릴 수 있는가보다 왜 현재 연결 수가 여기까지 늘어났는가를 먼저 확인한다.
max_connections를 늘려야 하는 상황도 분명히 있다. 하지만 그 결정을 하기 전에 현재 서비스가 몇 개의 Connection을 만들 수 있는 구조인지부터 설명할 수 있어야 한다.
이 문제를 겪고 나서 데이터베이스 Connection Pool을 단순한 성능 옵션으로 보지 않게 됐다.
Connection Pool은 PostgreSQL이라는 제한된 자원을 애플리케이션이 어떻게 나눠 사용할 것인지 결정하는 운영 설정에 더 가깝다.
그리고 이 관점으로 바꾸고 나니 PostgreSQL 연결 문제를 볼 때 확인해야 할 위치도 훨씬 명확해졌다.