게시:

서비스를 운영하다보면 DB Connection 관리에 대한 이슈가 종종 발생한다. 이번 포스팅에서는 DB 커넥션 풀 (DB Connection Pool)과 Django에서는 DB 커넥션을 어떻게 관리하는지 알아보겠다.

DB Connection Pool

DB와 애플리케이션 사이에서 DB 연결을 간소화하기 위해서 DB와 미리 연결한 커넥션(Connection) 객체들을 풀(Pool)에 저장해두었다가 요청이 들어오면 풀에서 유휴 커넥션을 꺼내 사용하고 다시 풀에 저장하는 방식을 말한다.

애플리케이션과 DB의 TCP 연결을 위해서는 3-way-handshaking이 필요하다. 매 연결마다 이를 수행하면 비용이 커지므로 비용을 줄이기 위해 DB Connectio Pool 방식을 사용한다.

-- 커넥션 풀(max_connections) 값 보기
SHOW GLOBAL VARIABLES LIKE 'max_connections';  -- MySQL
SHOW max_connections;  -- PostgreSQL

Connection Timeout

만약 허용된 커넥션 개수를 초과하여 요청이 들어오면 커넥션 풀에 유휴 커넥션이 없으므로 연결을 예약하고 기다리게 되는데 이때 대기하는 시간이 길어지면 애플리케이션의 성능이 저하될 수 있기 때문에 일정시간 이상 연결되지 않은 커넥션은 자동으로 버릴 수 있도록 설정해야 한다.

  • MySQL : wait_timeout
    SET wait_timeout = {seconds};         -- 세션 설정
    SET GLOBAL wait_timeout = {seconds};  -- 전역 설정
    
  • PostgreSQL : statement_timeout
    SET statement_timeout = {milliseconds};                      -- 세션 설정
    ALTER DATABASE {db_name} SET statement_timeout = {milliseconds};  -- 전역 설정
    

Idle in transaction session timeout

PostgreSQL은 9.6 버전부터 오래된 Idle_in_transaction을 제거하는 기능을 제공한다. 이 기능을 사용하면 트랜잭션 내에서 오래된 커넥션을 자동으로 종료할 수 있다.

  • PostgreSQL : idle_in_transaction_session_timeout (9.6 이상)
    ALTER DATABASE {db_name} SET idle_in_transaction_session_timeout = {milliseconds};
    
  • PostgreSQL: Docker-compose 사용 시
    command:
    [
      "-c",
      "max_connections={max connections}",
      "-c",
      "idle_in_transaction_session_timeout={milliseconds}",
      "-c",
      "statement_timeout={milliseconds}",
    ]
    

커넥션만 늘리면 만사 OK?

그렇다면 커넥션 풀을 계속 늘리면 연결 문제가 해결되지 않을까? 대답은 No이다. 커넥션을 늘리면 DB에 부하가 줄어들고 애플리케이션의 성능이 향상될 것이라고 생각될 수 있지만 커넥션은 공짜로 만들 수 있는 것이 아니다. 늘리면 DB의 컴퓨팅 리소스가 소모되기 때문에 부하가 늘어나 계속 늘릴 수만은 없다.

출처: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Limits

위의 표는 AWS RDS의 최대 커넥션 수를 나타낸 것이다. 이를 참고했을 떄 각 DB의 커넥션당 약 10MB의 메모리가 필요하다는 것을 알 수 있다.

  • MySQL: 커넥션당 약 12 MB
  • PostgreSQL: 커넥션당 약 9.1 MB
  • Oracle: 커넥션당 약 9.4 MB

AWS RDS PostgreSQL 최대 커넥션 계산
AWS RDS 인스턴스의 메모리가 16GB일 때 PostgreSQL 최대 커넥션 수는 다음과 같이 구할 수 있다. 하지만 이는 산술적으로 계산된 값이므로 여유율을 고려했을 때 실제로는 더 적은 커넥션 수를 사용할 수 있다. \begin{align} 1,802 \fallingdotseq (16 \times 1024 \times 1024 \times 1024) \div 9531392 \end{align}

Django에서의 DB Connection 관리

Django에서는 DB 커넥션 풀을 지원하지 않는다. 기본적으로 모든 요청(Request)마다 커넥션을 맺고 끊는다. 그렇다고 연결 오버헤드를 방치하는 것은 아니고 Persistent Connection 방식을 적용하여 DB 쿼리를 처음 만들 때 DB 커넥션을 열고 이 연결을 열린 상태로 유지하고 후속 요청에서 이 연결을 재사용한다.

Persistent connections avoid the overhead of reestablishing a connection to the database in each request. They’re controlled by the CONN_MAX_AGE parameter which defines the maximum lifetime of a connection. It can be set independently for each database. Databases - Django Document

커넥션 재사용 시간(CONN_MAX_AGE)

Persistent Connection을 사용하려면 CONN_MAX_AGE를 설정해야 한다. 이 값은 DB 커넥션의 재사용 시간을 의미한다. 따로 설정하지 않으면 모든 요청마다 커넥션을 열고 닫는다. 백엔드의 CONN_MAX_AGE 매개변수로 커넥션의 재사용 시간을 정할 수 있는데 초 단위의 정수 값으로 설정한다.

DATABASES = {
  'default': {
    'ENGINE': 'django.db.backends.postgresql',
    'NAME': 'mydatabase',
    'USER': 'mydatabaseuser',
    'PASSWORD': 'password',
    'HOST': '127.0.0.1',
    'PORT': '5432',
    'OPTIONS': {
      'CONN_MAX_AGE': 60,
    }
  }
}

기본값은 0이고 이 값이 0보다 크면 커넥션을 열고 닫는 대신 해당 시간동안 커넥션을 열고 닫지 않고 재사용한다. 무제한으로 열린 상태를 유지시키고 싶으면 None으로 설정한다.

이와 별개로 DB 오류로 해당 연결이 동작하지 않는 경우에도 Django는 커넥션을 닫고 새로 열게 된다. 이는 하나의 DB의 오류가 여러 개의 Django 프로세스 및 스레드에 영향을 미칠 수도 있음을 의미한다.

더 고려할 점

멀티 스레드(Thread)를 사용하여 DB를 호출하는 경우 각 스레드는 고유한 커넥션을 갖는다. 따라서 스레드가 많아지면 커넥션 수도 많아야 헌다. 이는 DB 커넥션 수가 제한되어 있을 때 문제가 될 수 있으므로 DB 커넥션 풀은 최소한 스레드 개수 이상을 확보할 수 있도록 설정해야 한다.

또한 Django로 작성된 코드 외에도 DB에 직접 접근하는 코드가 있을 수 있으므로 DB에 직접 접근하는 커넥션도 고려해야 한다. 배치(Batch) 작업이나 DB 관리 작업 등을 고려하지 않고 커넥션 수를 설정하면 too many connections를 마주할 수 있다. (배치작업 마지막에는 반드시 커넥션을 명시적으로 닫는 것이 좋다.)

추가로 Django의 CONN_MAX_AGE는 DB 커넥션의 수명을 정하는 것이 아니라 커넥션을 재사용하는 시간을 정하는 것이지 ORM 쿼리 시 커넥션의 수명을 결정하는 것이 아니라는 점이다. DB 커넥션의 수명을 정하려면 DB의 설정을 변경해야 한다.

이외에도 Disk I/O 성능에 따른 병목 현상이 발생할 수 있으므로 DB 커넥션 풀의 크기를 설정할 때는 DB의 성능도 고려해야 한다.

Reference

  • https://docs.djangoproject.com/en/4.1/ref/databases/#general-notes
  • https://aws.amazon.com/ko/premiumsupport/knowledge-center/rds-mysql-max-connections

댓글남기기