주뇽's 저장소
Uvicorn에서 Gunicorn으로 전환 시 발생한 Kafka 문제 해결: 비동기, 동기, 블로킹, 논블로킹 개념과 적용 방법 본문
Uvicorn에서 Gunicorn으로 전환 시 발생한 Kafka 문제 해결: 비동기, 동기, 블로킹, 논블로킹 개념과 적용 방법
FastAPI 프로젝트에서 Kafka를 사용해 비동기 메시지 처리를 구현하다 보면, Uvicorn 단독 실행에서는 문제없이 동작하던 코드가 Gunicorn과 함께 실행할 때 문제를 일으킬 수 있다. 이 글에서는 Uvicorn과 Gunicorn의 차이, 비동기/동기, 블로킹/논블로킹 개념을 바탕으로 Kafka와 관련된 문제를 해결하는 방법에 대해 설명한다.
문제점: Gunicorn 환경에서 비동기 처리가 제대로 되지 않음 😓
FastAPI와 Kafka를 연동해 Uvicorn에서 workers=4
로 설정하여 실행했을 때는 비동기 처리가 잘 이루어졌다. 그러나 Gunicorn과 Uvicorn을 함께 사용하면서 asyncio.to_thread
를 통해 비동기 처리를 하던 코드가 무한 루프에 빠지거나, 요청이 끝나지 않는 문제가 발생했다. 반면, aiokafka
와 같은 완전한 비동기 라이브러리를 사용하자 문제가 해결되었다.
해결책: 비동기 라이브러리 사용 🛠️
Gunicorn에서의 문제는 동기 작업을 비동기로 바꾸기 위한 asyncio.to_thread
와, 이벤트 루프와 스레드 관리 방식의 차이에서 비롯되었다. 비동기적으로 동작하는 aiokafka
를 사용하면, 이런 복잡한 스레드 문제 없이 원활한 처리가 가능하다. 이를 해결하기 위해 비동기 프로그래밍과 비동기 라이브러리의 차이점을 이해하는 것이 중요하다.
비동기와 동기, 블로킹과 논블로킹 개념 이해하기 📚
1. 동기(Synchronous)와 비동기(Asynchronous)
- 동기: 작업을 순차적으로 처리하며, 한 작업이 끝나야 다음 작업을 시작할 수 있다.
예시: 학생들이 줄을 서서 하나씩 급식을 받는 상황. - 비동기: 여러 작업을 동시에 처리할 수 있으며, 앞의 작업이 끝나기를 기다리지 않고 다음 작업을 진행할 수 있다.
예시: 뷔페 식당에서 여러 학생이 동시에 음식을 받는 상황.
2. 블로킹(Blocking)과 논블로킹(Non-blocking)
- 블로킹: 작업이 완료될 때까지 프로그램이 멈추고 기다린다. 다른 작업을 할 수 없다.
예시: 전화 상담에서 한 고객이 상담을 마칠 때까지 다른 고객은 기다려야 한다. - 논블로킹: 작업이 끝나기를 기다리지 않고 바로 다음 작업을 진행할 수 있다.
예시: 채팅 상담에서 여러 고객과 동시에 상담이 가능하다.
Uvicorn과 Gunicorn의 차이 🖥️
Uvicorn 단독 실행:
Uvicorn에서 workers=4
로 설정하면, 각 워커가 독립적으로 비동기 작업을 처리한다. 각 워커는 자신의 이벤트 루프를 관리하며, 동기 작업을 asyncio.to_thread
로 처리할 때 스레드 간 통신에 문제가 발생하지 않는다. 즉, 작은 규모에서는 비동기 처리 방식이 제대로 동작한다.
Gunicorn + Uvicorn 실행:
Gunicorn은 애플리케이션을 실행할 때 여러 프로세스를 관리하는 역할을 한다. 그러나 이 과정에서 각 프로세스가 생성하는 이벤트 루프와 스레드 관리 방식이 복잡해진다. Gunicorn은 Uvicorn과는 다른 방식으로 프로세스 간 통신과 스레드 관리가 이루어지며, 이로 인해 asyncio.to_thread
가 제대로 작동하지 않는 문제가 발생할 수 있다. Gunicorn 환경에서는 스레드 간의 통신이 혼란스러워질 수 있고, 그 결과로 요청이 끝나지 않거나 무한 루프에 빠지는 현상이 발생한다.
Kafka에서 발생한 문제 해결하기 🛠️
Gunicorn에서 asyncio.to_thread
를 사용해 동기 작업을 처리하려 하면, 스레드와 프로세스 간의 복잡한 상호작용 때문에 제대로 동작하지 않을 수 있다. 하지만 aiokafka
와 같은 비동기 라이브러리는 처음부터 이벤트 루프 내에서 비동기로 처리되기 때문에, 스레드 관리 문제 없이 Gunicorn 환경에서도 원활하게 동작한다.
Uvicorn과 Gunicorn의 차이점 더 깊게 이해하기
Uvicorn에서 멀티 워커 사용
- Uvicorn은 자체적으로 여러 개의 워커 프로세스를 생성할 수 있다.
- 각 워커 프로세스는 독립적으로 동작하며, 자신의 이벤트 루프와 스레드 풀을 가진다.
asyncio.to_thread
를 사용할 때, 각 워커 내에서 스레드가 생성되고 관리되므로 문제가 없다.
Gunicorn + Uvicorn 사용 시 차이
- Gunicorn은 애플리케이션을 실행하기 위해 워커 프로세스를 생성하고, 그 안에서 Uvicorn이 동작한다.
- 그러나 Gunicorn은 Uvicorn과는 다른 방식으로 워커와 이벤트 루프를 관리한다.
- 이로 인해 이벤트 루프와 스레드 풀의 초기화 순서나 관리 방식이 달라질 수 있다.
- 이러한 차이로 인해
asyncio.to_thread
가 예상대로 작동하지 않을 수 있고, 스레드와 이벤트 루프 간의 통신에 문제가 생길 수 있다.
구체적 예시를 통한 설명
Uvicorn에서 workers=4
로 실행한 경우
- 각각의 교실(Uvicorn 워커 프로세스)에 선생님이 있고, 학생들은 자기 교실의 선생님에게 질문한다.
- 선생님은 어려운 질문이 오면 같은 교실 내에서 보조 교사(스레드)에게 도움을 요청한다.
- 보조 교사는 그 교실의 선생님과 원활하게 소통하므로 문제가 없다.
Gunicorn + Uvicorn을 사용한 경우
- Gunicorn은 여러 교실을 관리하는 관리자이고, 각 교실에서 Uvicorn이 선생님 역할을 한다.
- 그러나 이번에는 선생님이 보조 교사에게 도움을 요청할 때, 보조 교사가 어느 교실의 선생님을 도와야 할지 혼란스러워진다.
- 왜냐하면 Gunicorn의 관리 방식 때문에 보조 교사(스레드)와 선생님(이벤트 루프) 간의 연결이 올바르게 설정되지 않을 수 있기 때문이다.
- 그 결과, 질문에 대한 답변이 전달되지 않거나 시간이 오래 걸리게 된다.
aiokafka
가 왜 문제없이 동작할까?
aiokafka
는 보조 교사(스레드)를 필요로 하지 않고, 선생님(이벤트 루프)이 직접 모든 질문을 처리할 수 있게 해준다.- 따라서 보조 교사와의 소통 문제나 혼란이 없어서, Gunicorn + Uvicorn 환경에서도 원활하게 동작한다.
핵심 포인트
- 스레드를 사용하는
asyncio.to_thread
방식은 Gunicorn과 같이 여러 프로세스를 관리하는 환경에서 스레드와 프로세스 간의 복잡한 상호 작용 때문에 문제가 발생할 수 있다. - 비동기 라이브러리인
aiokafka
를 사용하는 방식은 스레드를 사용하지 않고, 이벤트 루프 내에서 직접 작업하므로 이러한 문제가 발생하지 않는다.
구현 단계: 비동기 Kafka 설정 및 실행 🔧
aiokafka
설치하기:pip install aiokafka
- Kafka 소비자(Consumer) 설정:
비동기 Kafka 클라이언트를 설정하고, 메시지를 처리하는 함수를 비동기로 작성한다. from aiokafka import AIOKafkaConsumer import asyncio async def consume(): consumer = AIOKafkaConsumer( 'my_topic', bootstrap_servers='localhost:9092', group_id="my_group" ) await consumer.start() try: async for message in consumer: print(f"Consumed message: {message.value}") finally: await consumer.stop() asyncio.run(consume())
- Gunicorn을 사용해 FastAPI 실행:
Gunicorn에서 Uvicorn 워커를 사용해 애플리케이션을 실행한다. gun
icorn -w 4 -k uvicorn.workers.UvicornWorker myapp:app
```
결론: 비동기 라이브러리로 안정적인 처리 구현 🏆
비동기 프로그래밍은 여러 작업을 동시에 처리할 수 있어 고성능 웹 애플리케이션 개발에 매우 유용하다. 특히, Kafka와 같은 대규모 메시지 시스템을 사용할 때는 비동기 라이브러리(aiokafka
)를 사용하는 것이 Gunicorn과 같은 복잡한 환경에서도 안정적인 처리를 보장하는 데 효과적이다.
이를 통해 프로그램이 무한 루프에 빠지거나 요청이 끝나지 않는 문제를 해결할 수 있으며, 비동기 처리로 성능을 극대화할 수 있다.
'웹개발' 카테고리의 다른 글
Docker 네트워크로 FastAPI와 Kafka 컨테이너 연동하기: 주의할 점과 해결 방법 (0) | 2024.09.13 |
---|---|
리액트에서 HTTPS 설정을 받고 스프링으로 HTTP 요청을 유지하는 방법: Nginx를 이용한 SSL 종료 구현 🔒 (4) | 2024.09.10 |
[MSA] Spring Eureka Service에 FastAPI 등록하기 (0) | 2024.07.13 |