본문 바로가기
Python

Python - Session, HTTPAdapter 효율적인 ConnectionPool 관리

by 올엠 2024. 3. 14.
반응형

파이썬에서 HTTP Session을 통해 빠르게 연결을 지속 사용할 수 있다.

여기에서는 실제 사용중 발생했던 내용에 대해 정리하여, 추후 HTTP Connection 이해를 하는데 도움이 되고자 한다.

Session

TCP 연결시 본격적으로 데이터를 요청하기전에 네트워크를 연결하는 작업을 진행하게 된다.

이를 TCP 3-Way Handshake 라고 하며, 매 데이터를 요청할 때 마다 새로 연결 작업을 한다는 것은 데이터 처리에 그만큼 시간이 더 걸리게된다.

이러한 부분을 개선해서 동작할 수 있는 구조가 최초 연결한 Session을 만든이후 만든 세션을 재사용하여 요청을 처리하는 구조이다.

이는 보통  HTTP Keep-alive 와 같은 구조로 Python 코드로는 다음과 같이 이용할 수 있다.

import time
import requests
import logging


logging.basicConfig(level=logging.DEBUG)

url = "https://httpbin.org"

start = time.perf_counter()

s = requests.Session()
s.get(url)
s.get(url)
s.get(url)
s.get(url)
s.get(url)
end = time.perf_counter()- start
print(end)

예전에 작성한 문서를 참고하자.

HTTPAdapter

requests에서 Sessoin을 사용하고자 한다면, HTTPAdapter에 대해서 이해해두는 것이 좋다.

Session을 생성하게 되면, 기본적으로 지정한 URL 기반으로 Session 별 Connection Pool을 생성하게 되며, 이를 HTTPAdapter가 관리하게된다.

만약 Keep-alive를 사용할 때 주의할 부분이 바로 Connection Pool이다. 

각 세션별 Connection Pool은 기본적으로 동시에 10개 까지 사용할 수 있도록 구성되어 있다.

동시에 10개 이상 요청이 필요한 경우이를 HTTPAdapter를 설정하여 변경해야 한다.

특히 멀티 스레드를 Python에서 사용한다면 Session은 프로세스 기반으로 관리되기 때문에 반드시 종료해주는 것이 좋다. 즉 스레드가 종료되어도 Session 종료가 없으면 프로세스에 남아있게 된다.

ConnectionPool 조정

ConnectionPool을 사용하기 위해서는 pool_connections과 pool_maxsize를 이해할 필요가 있다.

pool_connections 지정한 URL에 연결할 수 있는 connection 갯수이다 만약 1개로 지정하게 된다면, 1개 이상의 Connection Pool을 사용할 수 없기 때문에 새로운 Connection Pool 을 생성하게 된다. 즉 매 요청시 새로운 Session이 만들어지는 것과 같다.

pool_maxsize 재사용 횟수이다. 지정한 URL에 대해 pool_maxsize를 2로 설정하면 2번 까지 재사용이 가능하다. 

아래 예제를 통해서 pool_connection와 pool_maxsize를 이해해보자.

"""로그 출력을 위한 부분"""
import logging
import sys

root = logging.getLogger()
root.setLevel(logging.DEBUG)

handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
root.addHandler(handler)

"""로그 출력을 위한 부분 끝"""

import requests

a_url = "https://httpbin.org"
b_url = "https://www.naver.com"

with requests.Session() as s:

    s.mount('https://', requests.adapters.HTTPAdapter(pool_connections=2, pool_maxsize=1)) #https에 대한 기본 Connection pool
    s.get(a_url)
    s.get(a_url)
    s.get(b_url)
    s.get(b_url)

    #신규 생성 유무
    s.get(a_url)
    s.get(b_url)

위와 같이 1개로 pool_connection을 설정하게 된다면, pool_connections이 1개이기 때문에 주소가 변경되는 시점에 New HTTPS Connections을 진행하게 된다. 

만약 pool_connection을 2개로 설정하면 신규 연결없이 사용이 가능한것을 알 수 있다.


pool_maxsize은 동시에 재사용하는 횟수로, 멀티 스레드와 연관성이 있다. 파이썬은 기본적으로 싱글 스레드이기 때문에 문제가 없지만 멀티 스레드인 경우 동시에 URL을 요청할 수 있게 된다. 이때 pool_maxsize의 갯수만큼 요청이 가능하며 그 이상의 연결을 시도하게 되면, Connection pool is full 오류가 발생하게 된다.

따라서

- 글로벌 Session 사용하고자 한다면, 다중 스레드 환경에서 이러한 풀을 사용할 계획이라면 pool_maxsize 를 스레드 수와 동일하게 하거나 더 높은 숫자로 설정해야 한다.

- 스레드 pool_maxsize가 어려운 경우 스레드 내에서 Session을 만들어 관리하면 pool_maxsize가 글로벌 Session을 사용하지 말고, 스레드 별로 Session을 생성되어 관리하면 문제를 해결할 수 있다.

추가로 URL별로 세션을 다르게 관리할 수 있으므로, 만약 요청 빈도에 따라서 다르게 관리해야할 경우 코드를 추가할 수 있다.

import requests

a_url = "https://httpbin.org"
b_url = "https://www.naver.com"

with requests.Session() as s:
    s.mount('https://', requests.adapters.HTTPAdapter(pool_connections=1, pool_maxsize=1)) #https에 대한 기본 Connection pool
    s.mount(b_url, requests.adapters.HTTPAdapter(pool_connections=2, pool_maxsize=1)) #b_url에 대한 Connection pool

    s.get(a_url)
    s.get(a_url)
    s.get(a_url)
    s.get(b_url)
    s.get(b_url)

이외에도 HTTPAdapter를 통해 재시도 간격이라거나, 재시도 횟수도 Retry 함수로 지정할 수 있다.

from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter

def requests_retry_session(
    retries=10, # 재시도 횟수
    backoff_factor=0.3, # 재시도 간격, 곱으로 증가
    status_forcelist=(500, 502, 504), # 무시할 Status 코드
    session=None,
):

    session = session or requests.Session()
    retry = Retry(
        total=retries,
        read=retries,
        connect=retries,
        backoff_factor=backoff_factor,
        status_forcelist=status_forcelist,
    )

    adapter = HTTPAdapter(max_retries=retry)
    session.mount('http://', adapter)
    session.mount('https://', adapter)
    return session

with 사용

with는 파이썬에서 자원을 자동으로 반환하고자 할 때 유용하게 사용할 수 있는 메소드이다.

with 구문이 끝나면 자동으로 사용 리소스를 반환하므로, Session 생성시 함께 활용하면 유용하다.\

import time
import requests
import logging

logging.basicConfig(level=logging.DEBUG)

url = "https://httpbin.org"

start = time.perf_counter()
with requests.Session() as s:
    s.get(url)
    s.get(url)
    s.get(url)
    s.get(url)
    s.get(url)
    end = time.perf_counter()- start
    print(end)

이렇게 Python Session에 대해서 알아보았다.

 

공식 문서는 아래에서 확인 할 수 있으며, 실제 urllib3의 HTTPAdapter를 함께 사용하는 구조이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형