오늘 배운 것

오늘은 드디어 알림 개발을 완료했다(버그가 있으면 수정해야 하니 일단은 1차 완료이다). 프론트에서 알림 코드가 해당된 API를 호출했을 때 오류가 없는 것을 확인하였다. 

 

이제는 다음 태스크인 '비동기 뷰 변환'을 해볼 차례이다. 이는 여러 뷰들 중에서 openAI API를 사용하는 뷰가 있는데, 해당 뷰에 한해서는 응답을 비동기로 처리해주면 되는 태스크이다. 

 

그런데 사실 비동기 뷰라는 개념을 확실히는 모른다. 지금까지 개발한 뷰는 모두 동기 뷰였고, 요청이 오면 그걸 다 처리할 때까지 기다렸다가 응답을 리턴했다. 그렇다면 비동기 뷰는 뭘까. 동기 뷰의 반대니까 요청이 왔어도 응답을 리턴하지 않고 필요한 작업이 다 되면 최종 응답을 리턴하는 식일까? 라는 의문이 들었다. 

 

비동기 뷰에 대해 정리한 블로그를 보고 감을 잡을 수 있게 되었다. 위에서 생각한 작업이 맞았다. 동기 작업의 단점은 오래 걸리는 태스크가 있을 때 그 태스크의 수행을 기다리느라 다른 작업들을 하지 못한다는 점이다. 그러니 오래 걸리는 태스크가 있으면 그게 다 될 때까지 둔 다음에, 그 사이에 들어오는 다른 요청을 처리할 수 있다. 

 

참고로 비동기 뷰뿐만 아니라 쿼리셋에서도 비동기를 처리할 수 있다고 한다. 가령 'objects.get'으로 시작하던 기존 쿼리 대신에 'objects.async_get'을 사용하면, DB에서 데이터를 가져올 때까지 서버가 동기 방식으로 기다리지 않는다. 대신 비동기는 동기와 달리 실행 흐름이 한 줄기가 아니므로 불필요하게 남발하는 것은 실행 플로우나 디버깅을 복잡하게 만들 수 있다. 

 

어쨌든 이제 비동기 뷰를 만들 필요성에 대해서 다시 납득했으니 만들어 보자. 만드는 법은 매우 간단했다. 공식문서를 봤더니 함수형 뷰의 경우는 기존의 'def' 대신 'async def'으로 만들어주면 되었고, 클래스형 뷰의 경우는 개별 메소드의 앞에 async를 붙여주면 되었다. 비동기 뷰는 함수를 리턴하는 동기 뷰와 달리 coroutine을 리턴한다고 나와있다. 

 

이 coroutine은 저번에 '실행을 중단하거나 다시 재개할 수 있는 컴퓨터 프로그램의 구성 요소'라고 잠깐 언급했었는데 사실 나도 와닿지 않는다. 이게 도대체 뭘까. 이걸 알아야 비동기 뷰들과 동기 뷰들이 어떻게 하나의 서버 안에서 호출되고 동작하는지를 이해할 수 있을 것 같아서 찾아보았다. 

 

친절한 블로그 글의 설명과 이미지를 가져오자면 coroutine은 함수와 비슷하다. 다만 함수는 대개 실행 흐름을 통째로 가져가서 처음부터 끝까지 한 번에 실행된 다음 결과를 반환하는 반면 coroutine은 실행 중간중간 실행 흐름을 자신을 호출했던 기존 caller에게 다시 반환한다. 

 

이게 가능하려면 중간에 coroutine이 자신의 실행권을 내려놓겠다는 신호를 줘야 하는데, 파이썬에서는 yield, 자바스크립트에서는 async/await 등의 키워드가 그 역할을 한다. 그리고 실행권을 내려놓은 시점의 위치나 메모리 상태 등을 기억한다면, 다시 실행권을 가져왔을 때 멈춘 지점부터 실행할 수 있다. 

 

그리고 공식문서를 보면서 새로 안 사실인데, 비동기 뷰가 섞여 있을 때 서버의 성능을 최적화하려면 동기 환경만 지원하는 미들웨어가 없어야 한다고 나와있었다. 왜냐하면 그런 미들웨어가 하나라도 있으면 장고는 요청 하나당 하나의 스레드를 자동으로 할당해 버리기 때문에, 비동기 뷰의 이점을 누릴 수 없다는 거였다. 

 

미들웨어는 기본값으로는 동기 환경만 지원하도록 되어 있다. 이를 동기와 비동기 환경을 모두 지원하도록 만들어주면 된다고 한다. 그렇다면 우선 비동기 뷰를 만든 다음에 이 작업도 같이 해 보자. 

 

현재 API에서는 투두를 LLM을 통해 하위 투두로 나눠주는 API에서만 외부 openAI API를 사용하고 있다. 이 앞에 'async' 키워드를 붙여줬다. 공식문서를 잘 읽어보니 해줘야 할 후속 작업들이 많았다. 우선 이렇게만 써 놓으면 장고는 여전히 WSGI 기반(한 번에 하나의 요청만을 처리)에서 동작한다. 

 

이것 자체의 문제는 없으나, 그러면 롱 폴링이나 슬로우 스트리밍 등의 ASGI 기반에서 동작하는 것들을 하지 못한다. 만약 이 작업들을 하고 싶다면 장고가 ASGI를 사용하도록 배포해야 한단다. 이 작업을 해 주면 되겠다. 

 

궁금한 점

1. 동기 뷰를 사용할 때와 비동기 뷰를 사용할 때의 장고의 동작 방식은 똑같을까? 아니면 비동기 뷰와 동기 뷰를 같이 사용하게 되면 장고 뷰 로직의 동작 방식이 바뀔지 궁금하다. 

2. 비동기 쿼리셋에서 나온 objects.get과 objects.async_get의 동작 방식의 차이가 궁금하다. 

3. 왜 ASGI 기반의 서버로 바꿔줘야 롱 폴링, 슬로우 스트리밍 등을 사용할 수 있는 걸까? 대강은 알겠는데 이를 스스로 설명하지는 못하는 것 같다. 

4. 파이썬의 yield 키워드는 어떻게 동작할까?

5. 장고에서 마이그레이션이나 크론 잡들도 당연하지만 동기적으로 실행되고 있었다..! 이 작업들은 Celery를 사용하면 비동기적으로 처리할 수 있다고 한다. 이번 이슈가 끝나면 이것도 도입해 봐야겠다. 

 

+ Recent posts