brunch

You can make anything
by writing

C.S.Lewis

by DaeHyun Kim Jul 24. 2024

초짜 파이썬 개발자가 서비스 만든 썰 푼다 -01-

01. 무식하면 용감하지만 몸이 피곤하고 마음이 불편하며 버그가 생긴다

0. "이거 좀 이상해요. API를 두개 동시 호출하면 하나는 성공하는데 다른건 예외가 떠요."

F/E는 React를 쓰기 때문에, API를 async하게 호출할수 있다. B/E입장에서는 아주 약간의 시간차가 발생하겠지만 거의 동시에 치고 들어오는거나 마찬가지의 일이 발생할수 있다.

그리고 그 찰나의 순간을 잘못 관리하는 순간부터 모든게 꼬이기 마련이다.


1. 예외 메시지는 아래와 같았다.

This session is provisioning a new connection; concurrent operations are not permitted (Background on this error at: https://sqlalche.me/e/20/isce

이 에러메시지에 대해 SqlAlchemy 공식 문서(링크)에서는 아래와 같이 설명하고 있다.

SQLAlchemy 2.0 introduced a new system described at Session raises proactively when illegal concurrent or reentrant access is detected, which proactively detects concurrent methods being invoked on an individual instance of the Session object and by extension the AsyncSession proxy object.

처음 이 메시지를 보았을때 나는 완벽한 혼란에 빠졌다. 이유는,

- (최소 내가 알고 있기에는)FastAPI로 들어오는 모든 요청들은 각각 별개의 스레드로 처리된다.

- 요청들을 처리하기 위해서 필요한 자원은 해당 요청을 처리하는 과정에서 직접 만들어 사용한다.

- 공유되는 자원은 DB하나밖에 없는데, 어짜피 요청마다 새로 커넥션을 만들게 구현했는데 왜 저런 메시지가 발생하는가?


2. 그럼 여기서 왜 저런 문제가 발생했는지, 문제를 일으킨 DB 코드를 보자. 파이썬을 아는 사람이라면 배를 잡고 웃을것이다.

class CountryModel(BaseModel, Base):      
    name = Column(.....)      
    db_session: Session = SessionLocal()
    
    def find_country(....)


class UserModel(BaseModel, Base):
    name = Column(....)
    db_session:Session = SessionLocal()

    def find_user(.....)

사실 여기까지 구현했을때만해도, 위 코드처럼 객체마다 각기 connection을 들고 있는 상황에 대해서 당장은 문제가 생기지 않을것이라고 생각했다. 물론 불필요하게 connection이 낭비되는 상황은 발생하겠지만 일단 돌아가게 만든 다음에 차차 고치면 되것지...라 생각하고 있었던 것.

하지만 내 예상과 다르게 초창기부터 문제는 터지기 시작했으니 결론적으로 내가 파이썬을 잘못 이해하고 있었기 때문이다.


3. C, Java, PHP에 익숙한 나에게는 저렇게 코드를 작성하면, 두개의 Model 클래스 인스턴스가 생성되었을때 각각의 db_session 멤버는 서로 다른 인스턴스가 된다, 그렇게 생각하고 있었다. 

하지만 파이썬의 경우에는 전혀 다르게 동작한다. 요약하자면 아래와 같다.

class Person()
    age:int. -> 모든 객체가 공유한다.
    def  __init__(self, name, age):
        self.age = age -> 생성된 객체별로 존재한다

이게 말이 되냐...싶은데 DRY(Don't Repeat Yourself)원칙을 지키기 위해서 이렇게 만들었다 카더라. 그러니까 중복을 줄이기 위해서 저렇게 했슈....라는 소리인데, 인스턴스마다 동일한 값이 들어있음 모를까 그렇지 않은 케이스도 더 많구만 이렇게 까지 해야하는 이유를 아는 사람이 있음 제발 나에게 설명좀.


4. 사실 FastAPI의 공식 문서에서는, Request마다 DB 연결을 종속시킬수 있도록 가이드 문서를 제공하고 있다. 그럼에도 불구하고 저 가이드를 따르지 않은 이유는 "DB 관련된 코드가 model 레이어 밖에서 보이는 것은 사문난적으로 취급하겠다" 식으로 기합이 아주 흥선대원군스러운 마인드가 있었던것.

그러나 결국 이런 의도는 선무당 사람잡는 결과로 끝나고 말은것이다. 아아...


그래서, 결국 지금의 코드는 FastAPI의 가이드를 충실히 따라는 코드로 만들어져 있다. 하지만 그럼에도 불구하고, Python, FastAPI, SQLAlchemy를 몰라서 저지른 헛짓거리 대행진은 끝나지 않은 거시다....

작가의 이전글 초짜 파이썬 개발자가 서비스 만든 썰 푼다 -00-
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari