SQLAlchemy

GreenBean·2023년 1월 20일
0
post-thumbnail

SQLAlchemy

Connection Pool

  • 연결 풀링은 차후에 발생할 데이터베이스 요청에 대비하여 데이터베이스 연결을 캐싱하는 기법
    • 빈번한 데이터베이스 요청이 여러 사용자에 의해 발생할 때 매번 연결을 생성하고 닫는 과정을 반복하면 이에 대한 비용이 크기 때문에 이 기법을 사용하여 연결 생성 과정을 줄일 수 있음
    • 짧은 요청이 빈번하게 발생하는 웹 서비스와 같은 형태가 연결 풀과 궁합이 잘 맞음

QueuePool

  • SQLAlchemy 역시 연결 풀을 기본적으로 채택하고 있는데, 그 중 기본으로 제공하는 것은 큐 풀 ( QueuePool)
    • 큐 풀은 설정된 pool_sizemax_overflow 를 바탕으로 복수의 연결 풀을 구성해서 운용
    • SQLite 를 제외한 모든 데이터베이스에서 기본값으로 이용함

Tip! 추가 내용

  • SQLAlchemy 0.7 부터 SQLite 같은 파일 기반 데이터베이스에서는 기본적으로 NullPool 을 채택
    • 파일 기반 데이터베이스에는 네트워크 연결이 일어나지 않기 때문에 연결 비용이 적기 때문
    • NullPool 은 이름에서 알 수 있듯이 연결 풀을 유지하지 않고 풀에 연결이 들어오는 즉시 폐기
  • 큐 풀의 pool_size0 으로 하는 것과 같다고 착각할 수 있으나, 큐 풀은 pool_size0 일 때 pool_size 가 무한대인 것으로 인식
    • 따라서 풀을 만들지 않으려면 NullPool 을 쓰는 것이 적절

QueuePool 의 생애 주기

1. 큐 풀이 처음부터 연결을 미리 만드는 것은 아님 ( 0개로 시작 )

2. 요청이 들어올 때, 큐 풀에 유효 연결이 없으면 하나 생성

3. 설정된 pool_size 까지는 더 연결이 필요하지 않은 상황이라도 연결을 종료하지 않음

4. 요청이 들어올 때, pool_size 까지 다 찼다 할지라도 유효 연결이 없으면 초과하여 하나 생성

5. 4번 이후부터는 오버플로 상황이기 때문에, 큐 풀은 적극적으로 오버플로를 방지하기 위해 새로 들어오는 연결을 종료하여 pool_size 에 총 연결 수를 맞춤

6. QueuePool 이 관리하는 연결이 pool_size + max_overflow 까지 다 찬 상황에서 요청이 들어오면, 일단 기다리게 함 ( 기본값으로는 30초를 기다림 )

7. 30초를 기다려도 반환되는 연결이 없다면 TimeoutError 예외를 발생시킴

적절한 QueuePool 설정값

  • 서비스가 작을 때는 기본값이면 충분하지만 서비스 사용량이 많아지고 규모 문제가 발생하게 된다면 설정을 현재 상황에 맞춰 바꿔주는 게 좋음
    • 보통 QueuePool 관련 위 언급한 2가지 값 ( pool_size , max_overflow ) 을 바꿔주는 게 좋은데 기본값은 5, 10
  • pool_size
    • 현재 구성에서 연결 생성 부담을 최소화할 수 있는 가장 작은 값이 되어야 함
  • max_overflow
    • 현재 구성에서 데이터베이스 ∙ 웹 인스턴스가 물리적으로 버틸 수 있는 최댓값이 되어야 함
  • pool_size 가 과하게 설정되어 있으면 데이터베이스 입장에서 너무 많은 연결을 점유하고 있으니 비효율적
    • 그렇다고 너무 적게 설정한다면 오버플로가 자주 발생하여 풀링으로 얻을 수 있는 효율을 누리지 못함
    • 즉, 파이썬 측에서 비효율적
  • max_overflow 가 데이터베이스나 웹 인스턴스의 한계치보다 너무 빡빡하게 잡혀있으면 조금만 사용자 유입이 늘어도 TimeoutError 를 쉽게 만나거나 서비스 속도 저하를 자주 경험하게 됨
    • 그렇다고 무한으로 두면 사용량 폭증 시 에러 파티를 경험
  • 결국 서비스마다 그만의 퍼포먼스와 장비 한계치가 있으니만큼 내부에서 스트레스 테스트를 통한 벤치마킹으로 적정 값을 뽑아내는 것을 추천

QueuePool 관련 자주 발생하는 문제

개발할 때는 문제가 없었는데, 상용 서버를 띄우면 수분 이내로 서버가 TimeoutError 예외를 발생하며 응답을 안 합니다.

  • SQLAlchemy 쓰는 서비스를 만들어서 개발 잘 하고 배포했는데 프로덕션에서 잠깐 잘 돌더니 TimeoutError 를 내며 죽는 경험
    • 이 에러 자체는 Session 이 큐 풀에 연결을 받기 위해 기다리다가 못 참고 TimeoutError 를 내는 것
    • 큐 풀의 생애주기 기준 7번에 해당하는 상황
    • 큐 풀의 timeout 기본값은 30 이므로 30초 동안 풀의 모든 연결이 점유된 상태에서 아무것도 받지 못한 상태가 된 것
  • 위와 같은 경험이라면 서비스 사용량이 폭증하는 쪽보다는 십중팔구 기존에 점유한 Session 에서 제대로 연결을 반환해주지 않아서 발생하는 문제
    • 특히 웹서비스라면 Flask 등에서 요청 시마다 Session 이 연결을 불러다 써놓고 Pool 에 돌려주는 일을 빼먹는 실수가 잦은데 현재 구조상에서 요청이 끝나는 시점에 맞춰 session.close() 를 적절히 호출해주면 됨

어느 날 갑자기 연결이 왕창 늘어버렸어요.

  • SQLAlchemy 를 쓰면 Session 활용을 암시적으로 하게 될 때가 많음
    • Session 이 실제로 요청을 보내는 시점에서야 연결을 시도하기 때문에 예상치 못한 기능 변경으로 연결 폭증을 겪는 것
  • 전역적인 영역에서 데이터베이스 접근을 하는 시나리오를 최소화하는 정책으로 실수를 완화할 수 있음
profile
🌱 Backend-Dev | hwaya2828@gmail.com

0개의 댓글