FastAPI 엔진: Uvicorn은 어떻게 속도를 제공하는가: Python의 ASGI 서버 심층 분석
Jun 09, 2025
# python
Ethan Miller
Product Engineer · Leapcell

TCP 기반 고성능 ASGI 서버 구현의 기술 분석
Ⅰ. ASGI 프로토콜의 핵심 아키텍처
ASGI(비동기 서버 게이트웨이 인터페이스)는 비동기 웹 서버와 애플리케이션 프레임워크 간의 통신 사양을 정의하며, 다음 세 가지 핵심 구성요소로 구성됩니다.
- Scope: 프로토콜 유형(http/websocket), 네트워크 주소, 요청 메서드 등과 같은 메타데이터를 포함합니다.
- 수신 채널: 요청 본문과 메시지를 비동기적으로 수신합니다.
- 송신 채널: 응답 헤더, 응답 본문 및 종료 신호를 비동기적으로 보냅니다.
일반적인 ASGI 애플리케이션 구조:
async def my_asgi_app(scope, receive, send): assert scope['type'] == 'http' await send({ 'type': 'http.response.start', 'status': 200, 'headers': [[b'content-type', b'text/plain']] }) await send({ 'type': 'http.response.body', 'body': b'Hello, ASGI!' })
Ⅱ. TCP 서버 인프라 설계
2.1 비동기 IO 모델 선택
Python의 내장 asyncio
프레임워크를 사용하여 비동기 TCP 서버를 구현하며, 핵심 구성 요소는 다음과 같습니다.
asyncio.start_server()
: TCP 수신 소켓을 생성합니다.StreamReader/StreamWriter
: 비동기 IO 읽기 및 쓰기를 처리합니다.asyncio.Protocol
에서 상속받는 사용자 정의 프로토콜 클래스입니다.
2.2 연결 관리 모듈
import asyncio from typing import Dict, List, Any class ASGIServerProtocol(asyncio.Protocol): def __init__(self): self.reader = None self.writer = None self.scope: Dict[str, Any] = {} self.app = None # ASGI application instance def connection_made(self, transport: asyncio.Transport): self.transport = transport self.reader = asyncio.StreamReader() self.writer = asyncio.StreamWriter( transport, self, self.reader, loop=transport.get_loop() )
Ⅲ. HTTP 프로토콜 파싱 엔진 구현
3.1 요청 라인 파싱
async def parse_request_line(self): line = await self.reader.readline() if not line: return None parts = line.split() if len(parts) != 3: await self.send_error_response(400, b"Bad Request") return None method, path, version = parts return { 'method': method.decode(), 'path': path.decode(), 'version': version.decode() }
3.2 헤더 파싱 최적화
메모리 복사를 줄이기 위해 버퍼를 미리 할당합니다.
HEADERS_BUFFER_SIZE = 4096 async def parse_headers(self): headers = [] buffer = bytearray() while True: data = await self.reader.read(HEADERS_BUFFER_SIZE) if not data: break buffer.extend(data) while b'\r\n' in buffer: line, buffer = buffer.split(b'\r\n', 1) if not line: # End of headers return headers key, value = line.split(b': ', 1) headers.append((key.lower(), value))
3.3 전체 파싱 프로세스
async def handle_connection(self): request_line = await self.parse_request_line() if not request_line: return headers = await self.parse_headers() body = await self.reader.read() self.scope = { 'type': 'http', 'method': request_line['method'], 'path': request_line['path'], 'headers': headers, 'query_string': b'', # 간소화된 구현, 실제 쿼리 매개변수 파싱 필요 'server': ('127.0.0.1', 8000), 'client': ('127.0.0.1', 54321) } await self.invoke_asgi_app(body)
Ⅳ. ASGI 프로토콜 어댑터 구현
4.1 채널 래퍼
class ASGIChannelWrapper: def __init__(self, writer: asyncio.StreamWriter): self.writer = writer self.response_started = False self.response_headers: List[List[bytes]] = [] self.response_body = bytearray() async def receive(self): # ASGI receive channel (simplified implementation, actual chunked request handling needed) return {'type': 'http.request', 'body': b''} async def send(self, message: Dict[str, Any]): if message['type'] == 'http.response.start': self.send_headers(message) elif message['type'] == 'http.response.body': self.send_body(message) def send_headers(self, message: Dict[str, Any]): status = message['status'] headers = message['headers'] # HTTP 응답 헤더 빌드 response = [ f"HTTP/1.1 {status} OK\r\n".encode(), b''.join([k + b': ' + v + b'\r\n' for k, v in headers]), b'\r\n' ] self.writer.write(b''.join(response)) self.response_started = True def send_body(self, message: Dict[str, Any]): body = message.get('body', b'') self.writer.write(body) if not message.get('more_body', False): self.writer.write_eof() self.writer.close()
4.2 애플리케이션 호출 체인
async def invoke_asgi_app(self, body: bytes): channel = ASGIChannelWrapper(self.writer) # ASGI 수신 채널 생성 receive = channel.receive send = channel.send # ASGI 애플리케이션 호출 await self.app(self.scope, receive, send)
Ⅴ. 고성능 최적화 전략
5.1 이벤트 루프 최적화
# Windows 모범 사례 사용 (ProactorEventLoop가 Windows에서 더 나은 성능을 가짐) if sys.platform == 'win32': loop = asyncio.ProactorEventLoop() asyncio.set_event_loop(loop) else: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop)
5.2 버퍼 관리
- 제로 카피 데이터 연결을 위해
bytearray
를 사용합니다. - 적절한 읽기 버퍼 크기를 설정합니다(기본값 4096바이트).
- 큰 요청 본문을 청크 단위로 처리합니다 (청크 전송 지원 필요).
5.3 연결 재사용
# HTTP/1.1 keep-alive 연결 처리 if b'connection: keep-alive' in headers: while True: await self.handle_connection() # 연결 시간 초과 감지 로직 추가
5.4 비동기 IO 모범 사례
asyncio.wait_for()
를 사용하여 작업 시간 초과를 설정합니다.- 작업 풀을 사용하여 동시 연결을 관리합니다.
create_task()
를 합리적으로 사용하여 백그라운드 작업을 생성합니다.
Ⅵ. 전체 서버 구현
6.1 메인 진입점 모듈
class UvicornServer: def __init__(self, app): self.app = app self.loop = asyncio.get_event_loop() self.server = None async def start(self, host='0.0.0.0', port=8000): protocol_factory = lambda: ASGIServerProtocol(self.app) self.server = await asyncio.start_server( protocol_factory, host, port, loop=self.loop ) print(f"Server running on http://{host}:{port}") async def shutdown(self): if self.server: self.server.close() await self.server.wait_closed() self.loop.stop() # 사용 예 if __name__ == "__main__": async def test_app(scope, receive, send): await send({ 'type': 'http.response.start', 'status': 200, 'headers': [[b'content-type', b'text/plain']] }) await send({ 'type': 'http.response.body', 'body': b'Hello from custom ASGI server!' }) server = UvicornServer(test_app) try: server.loop.run_until_complete(server.start()) server.loop.run_forever() except KeyboardInterrupt: server.loop.run_until_complete(server.shutdown())
6.2 전체 프로토콜 처리 클래스
class ASGIServerProtocol(asyncio.Protocol): def __init__(self, app): super().__init__() self.app = app self.reader = None self.writer = None self.transport = None self.scope = {} self.channel = None def connection_made(self, transport: asyncio.Transport): self.transport = transport self.reader = asyncio.StreamReader(limit=10*1024*1024) # 10MB 요청 제한 self.writer = asyncio.StreamWriter( transport, self, self.reader, transport.get_loop() ) self.loop = transport.get_loop() async def handle_request(self): try: request_line = await self.parse_request_line() if not request_line: return headers = await self.parse_headers() body = await self.reader.read() self.build_scope(request_line, headers) await self.invoke_app(body) except Exception as e: await self.send_error_response(500, str(e).encode()) finally: self.writer.close() def build_scope(self, request_line, headers): self.scope = { 'type': 'http', 'method': request_line['method'], 'path': request_line['path'], 'headers': [(k.lower(), v) for k, v in headers], 'query_string': b'', 'server': ('0.0.0.0', 8000), 'client': self.transport.get_extra_info('peername') or ('127.0.0.1', 0) } async def invoke_app(self, body): self.channel = ASGIChannelWrapper(self.writer) receive = self.channel.receive send = self.channel.send await self.app(self.scope, receive, send) # 파싱 및 오류 처리 메서드 생략 (이전 구현과 동일)
Ⅶ. 성능 최적화에 대한 심층 분석
7.1 비동기 IO 이벤트 기반 모델
- 단일 스레드에서 수만 개의 동시 연결을 처리합니다.
- epoll/kqueue 기반의 효율적인 이벤트 알림 메커니즘입니다.
- 비차단 IO 작업으로 인한 낮은 컨텍스트 전환 오버헤드입니다.
7.2 프로토콜 파싱 최적화
- 상태 머신을 사용하여 HTTP 프로토콜을 파싱합니다.
- 일반적인 헤더 필드(예: Connection, Content-Length)를 미리 파싱합니다.
- 인코딩 변환 오버헤드를 피하기 위해 바이너리 데이터를 직접 처리합니다.
7.3 메모리 관리 전략
- 제로 카피 데이터 연결을 위해
bytearray
를 사용합니다. - 연결 수준 버퍼를 재사용합니다(객체 풀 구현 필요).
- 메모리 스파이크를 피하기 위해 큰 요청 본문을 청크 단위로 처리합니다.
7.4 동시성 모델 선택
# 다중 프로세스 모드 (Linux 전용) if sys.platform != 'win32': import multiprocessing workers = multiprocessing.cpu_count() * 2 + 1 for _ in range(workers): process = multiprocessing.Process(target=run_single_process) process.start()
Ⅷ. 프로덕션 환경 개선
8.1 보안 강화
- HTTP 요청 본문 크기 제한을 추가합니다.
- 요청 경로 보안 유효성 검사를 구현합니다.
- CORS 헤더 지원을 추가합니다.
8.2 프로토콜 확장
- HTTPS를 지원합니다 (SSLContext 추가 필요).
- WebSocket 프로토콜 지원 (WSGI 호환성 레이어 구현 필요).
- HTTP/2 프로토콜 지원 (IO 엔진 업그레이드 필요).
8.3 모니터링 및 디버깅
- 요청 처리 시간 통계를 추가합니다.
- 연결 수/처리량 모니터링을 구현합니다.
- 오류 요청을 기록합니다.
Ⅹ. 요약 및 확장 방향
이 구현은 asyncio
를 사용하여 기본 ASGI 서버 프레임워크를 구축하여 핵심 HTTP 프로토콜 파싱 및 ASGI 프로토콜 어댑터 기능을 제공합니다. 프로덕션 환경에서는 추가 개선이 필요합니다.
- 프로토콜 완전성: 청크 전송, HTTPS, HTTP/2 및 기타 프로토콜 지원을 구현합니다.
- 성능 최적화: 연결 풀링, 객체 재사용, JIT 컴파일 및 기타 기술을 도입합니다.
- 기능 확장: WebSocket, 시작 매개변수 구성, 핫 리로딩 등을 지원합니다.
- 안정성: 오류 처리, 연결 시간 초과 및 리소스 누수 감지를 개선합니다.
ASGI 프로토콜 사양과 비동기 IO 모델을 깊이 이해하면 고도의 동시성 시나리오를 충족하는 웹 서버를 구축할 수 있습니다. 실제로 특정 비즈니스 요구 사항에 따라 적절한 최적화 전략을 선택하여 기능 완전성과 성능 간의 최상의 균형을 찾으십시오.
Leapcell: 최고의 서버리스 웹 호스팅
Leapcell은 Python 서비스를 배포하기 위한 최고의 플랫폼으로 권장됩니다.
🚀 좋아하는 언어로 빌드
JavaScript, Python, Go 또는 Rust로 간편하게 개발하세요.
🌍 무제한 프로젝트 무료 배포
사용한 만큼만 지불하세요. 요청도 없고, 요금도 없습니다.
⚡ 사용량에 따라 지불, 숨겨진 비용 없음
유휴 요금 없이 원활한 확장성만 제공합니다.
🔹 Twitter에서 팔로우하세요: @LeapcellHQ