이번 글은 정리가 잘 안되어 있습니다.
보시기 전에 호흡을 가다듬고
테스트는 그동안 비동기로 작성하고자 했지만, 정작 내 코드는 비동기로 작성하지 않고 있었다.
엘라스틱 서치를 사용한다고 막 알아볼때 까지 말이다.
엘라스틱 서치에서는 AsyncElasticSearch라는 비동기 클라이언트를 제공한다.
sqlmodel도 같이 AysncSession을 사용하고 있다.
*비동기는 fastapi가 정확히는 unicorn이 알아서 해준다.
참고: 경로 작동 함수에서 필요한만큼 def와 async def를 혼용할 수 있고, 가장 알맞은 것을 선택해서 정의할 수 있습니다. FastAPI가 자체적으로 알맞은 작업을 수행할 것입니다.
어찌되었든, 상기 어떠한 경우라도, FastAPI는 여전히 비동기적으로 작동하고 매우 빠릅니다.
그러나 상기 작업을 수행함으로써 어느 정도의 성능 최적화가 가능합니다.
그래도 DB를 사용하는 부분에 쿼리 처리를 대기하는 부분이 있으니 이를 비동기로 하면 참 좋겠다.
[시작!]
그래서 하나둘 코드를 수정하기 시작했다. 먼저 Session부터 AsyncSession으로 바꿨다.
근데, AsyncSession는 AsyncEngine이 필요한데, sqlmodel 패키지에서 불러올 수 없었다.
그냥 sqlalchemy에서 불러와야 한다;;
<잡것>
+ 코루틴이 자바스크립트의 promise와 비슷한 줄 알았더만, 결과값을 다른 promise에 넘기는 방식을 할 수 없다는 것이 좀 불편했다.
[의존성 함수도 비동기]
fastapi에서 이왕 async def 쓰고자 한다면 의존성 함수도 전부 이렇게 바꾸라고 한다.
```
경로 작동 함수¶
경로 작동 함수를 async def 대신 일반적인 def로 선언하는 경우, (서버를 차단하는 것처럼) 그것을 직접 호출하는 대신 대기중인 외부 스레드풀에서 실행됩니다.
만약 상기에 묘사된대로 동작하지 않는 비동기 프로그램을 사용해왔고 약간의 성능 향상 (약 100 나노초)을 위해 def를 사용해서 계산만을 위한 사소한 경로 작동 함수를 정의해왔다면, FastAPI는 이와는 반대라는 것에 주의하십시오. 이러한 경우에, 경로 작동 함수가 블로킹 I/O를 수행하는 코드를 사용하지 않는 한 async def를 사용하는 편이 더 낫습니다.
하지만 두 경우 모두, FastAPI가 당신이 전에 사용하던 프레임워크보다 더 빠를 (최소한 비견될) 확률이 높습니다.
의존성¶
의존성에도 동일하게 적용됩니다. 의존성이 async def가 아닌 표준 def 함수라면, 외부 스레드풀에서 실행됩니다.
[비동기 드라이버 오류]
E sqlalchemy.exc.InvalidRequestError: The asyncio extension requires an async driver to be used. The loaded 'pysqlite' is not async.
=> 비동기 드라이버 aiosqlite로 혼내줬다.
[greenlet 오류]
E ValueError: the greenlet library is required to use this function. No module named 'greenlet'
=> 무심코 지나친 경고 메시지가 떠올랐다.
=> 으으 M1에서는 python3에서 greenlet이 빌트인 되지 않아서 생기는 문제라고 한다.
=> 그래서 sqlalchemy[asyncio]를 설치하라고 하는데.. poetry 에서 설치가 안되서 그냥 poetry add greenlet 했더니 잘 되었다.
[테스트 오류]
저번에 테스트 코드를 직렬로 하기 위해서 코루틴을 모두 빼버렸는데, AysncEngine이 여기까지 사용되어서 에러가 발생했다.
때문에 테스트 코드는 별개의 드라이버를 사용하는 엔진 객체를 만들어서 사용했다. (사실 이게 맞는 것 같기도 하다)
## Implicit I/O 오류
AsyncSession을 사용할때 연관관계 속성에 접근할때 Implicit IO 오류가 생기는 것이 문제가 되었다.
이 오류는 주로 I/O 처리를 동기식으로 하는 경우에 발생한다고 한다.
대표적인게 연관관계에 접근할때 lazy loading하는 경우인데, sqlalchemy가 동기식으로 쿼리를 날리는 것이 문제다.
그래서 구글링을 좀 해봤다.
### 방법 1 - eager load
sqlmodel를 쓸때는 eager load를 할 수 있다고 한다.
https://github.com/tiangolo/sqlmodel/issues/74
eager load하는 방법으로는 여러 옵션이 있다.
#### 연관관계 매핑시 지정
아래와 같이 기본으로 같이 가져오게 할 수 있다.
class Node(SQLModel, table=True):
…
children: List['Node'] = Relationship(
link_model=LinkNodes,
sa_relationship_kwargs={
"lazy": "selectin",
})
#### 쿼리를 날릴때 지정
이렇게 하면 연관관계가 불필요할때/필요할때 별개로 지정해주면 된다.
타입힌트가 잘 먹히지 않는다는 단점이 있다.
async with AsyncSession(engine) as session:
team: Optional[Team] = await session.get(
entity=Team,
ident=team_id,
options=[
joinedload(Team.heroes) # explicit load of relationship supports async session
],
)
### 방법 2 - AsyncAttr
sqlalchemy 문서에선 AsyncAttrs을 이용하면 awaitable한 연관관계를 만들 수 있다고 한다.
이 방식은 타입힌트 지원이 안된다…
-> 이 방법은 다음 일기에서 이어서 한다.
'프로그래밍 > 스팀 게임 퀴즈' 카테고리의 다른 글
#14 프론트 엔드 구축 (0) | 2024.01.11 |
---|---|
#13 비동기 SQLModel (0) | 2024.01.08 |
#8 HttpUrl vs str (0) | 2023.12.30 |
#7 테스트 코드 내 세션 관리 (0) | 2023.12.29 |
#6 퀴즈 정답 제출은 어떻게 할까? (0) | 2023.12.28 |