본문 바로가기
Programming/python

[SQLALCHEMY] session 관리

by 유주원 2017. 11. 15.

SQLAlchemy에서는 정말 편리하게도 많은 기능들을 제공해 주고 있다.

그 중 하나로 pool 관리도 해주는데 대부분 아래와 같이 사용할 것이다.

from sqlalchemy import create_engine

from sqlalchemy.orm import scoped_session, sessionmaker


engine = create_engine('mysql 주소', convert_unicode=False, pool_size=20, pool_recycle=500, max_overflow=20)

session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))


간략하게 create_engine의 parameter에 대해 설명하자면, 우선 convert_unicode를 True로 설정하면 String 기반의 모든 column 값을 python unicode object를 수용할 수 있는 값으로 변환한다.

poolsize의 경우 연결할 수 있는 connection의 크기를 지정하고, pool_recycle의 경우 주어진 초 이후에 connection을 재사용하겠다는 뜻이다. 즉 위의 예에서는 500초 이후에 해당 connection을 재사용하겠다는 뜻을 나타낸다. mysql의 경우 일정 시간동안 connection이 없을 경우 connection을 끊어버리게 되는데 pool_recycle을 설정함으로써 강제로 끊어지는 현상을 막을 수가 있다. 참고로 pool_recycle 시간이 mysql의 wait_timeout 시간보다 작게 설정되어야 한다. (더 크게 설정이 된다면 mysql에서 이미 connection을 끊었기 때문에 의미가 없다.) -1로 설정할 경우에는 따로 timeout을 두지 않겠다는 뜻이다.

max_overflow는 허용된 connection 수 이상이 들어왔을 때, 최대 얼마까지는 허용하겠다는 것을 나타낸다.


이렇게 mysql engine을 생성했으면, sessionmaker와  scoped_session을 이용해서 session을 만들자.

sessionmaker는 sqlalchemy에서 제공하는 class로 session을 만들어 주는 factory라고 생각하면 될 것 같다. 일반적으로 sessionmaker를 db 엔진과 연결한 후 session을 생성한다.

또한 session 생성 시 scoped_session을 이용하는데, 이는 session의 범위를 쓰레드 단위로 생성해 준다고 생각하면 된다.

사실 이 부분이 정말 궁금했고, 혹시 session 충돌나는거 아니야?? 하는 걱정때문에 아래와 같이 테스트를 진행해 봤다.

우선 scoped_session을 사용하지 않았을 때의 session 값들을 찍어봤다.

from sqlalchemy import create_engine

from sqlalchemy.orm import scoped_session, sessionmaker


engine = create_engine('mysql://주소', convert_unicode=False, pool_size=20, pool_recycle=500, max_overflow=20)

session = sessionmaker(autocommit=False, autoflush=False, bind=engine)


for i in range(5):

    print(session()) 


결과가 과연 어떨까?? 아래의 그림과 같이 각각 다른 객체를 생성한다.


그럼 이번에는 scoped_session을 이용한 후 session 객체를 찍어보자.

session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))


같은 객체로 session을 만들어 주는 것을 확인해 볼 수 있다.

동일한 thread에서의 session 충돌 방지를 위해 scoped_session을 많이 사용한다. 가령 하나의 thread에서 동일한 session을 이용해서 각기 다른 작업을 해야 할 경우 session을 파라미터로 넘겨줘서 session을 유지하는 경우가 많은데 scoped_session과 session_maker를 활용해서 간단하게 코드를 작성할 수가 있다.


그럼 진짜로 scoped_session은 다른 thread 에서는 다른 객체를 생성하는게 맞는 것일까? 만약에 이게 아니라면 이건 정말 큰 문제라고 할 수 있다. A라는 사용자가 데이터를 INSERT 하고 있는데 B라는 사용자가 같은 session을 사용해서 먼저 세션을 끝내버리면 데이터 정합성에서도 크게 문제가 발생할 수 있기 때문이다.

아래와 같이 쓰레드를 5개를 생성한 후 각각의 쓰레드에서 session을 찍어봤다.

import threading

class myThread(threading.Thread):

    def __init__(self):

        threading.Thread.__init__(self)

    def run(self):

        print(session())


thread = []

for i in range(5):

    thread.append(myThread())

for i in thread:

    i.start()


scoped_session의 결과가 과연 어떻게 되었을까? 다행스럽게 아래와 같이 각각의 다른 session 결과를 뿌려주고 있었다.


결과로 찍어보고 나니.. 이제야.. 안심하고 써볼 수 있을 것 같다!!!

(물론 sqlalchemy 홈페이지에서는 이렇게 나올 거라고 충분히 설명이 되어 있긴 하다. -_-;;)