비동기 통신 - React에서 Python, FastAPI까지

kimjayhyun·2025년 6월 7일
post-thumbnail

🔁 async 완전 정복

Javascript에서의 비동기 통신을 시작으로 Python에서의 비동기 통신,

이를 활용한 Python 라이브러리 사용법까지 확인


1. JavaScript에서의 async function

JavaScript에서 비동기 함수(asynchronous function)를 사용하는 이유는

이벤트 루프(event loop)렌더링 최적화 측면에서 매우 중요합니다.


1. 📘 이론적 배경

✅ 1. JavaScript의 실행 방식 요약: 싱글 스레드 + 이벤트 루프

JavaScript는 싱글 스레드(single-threaded) 언어입니다.

즉, 한 번에 하나의 작업만 수행할 수 있어요.

그런데 브라우저에서 동작하는 JavaScript는 다양한 작업을 처리해야 하죠:

  • 사용자 입력 처리 (클릭, 키보드 입력 등)
  • UI 렌더링
  • API 요청 처리
  • 타이머 등

이런 작업들을 효율적으로 처리하기 위해 이벤트 루프(event loop)가 사용됩니다.


✅ 2. 이벤트 루프와 렌더링의 관계

브라우저는 약 60fps(초당 60프레임) 속도로 화면을 렌더링합니다.

즉, 약 16.67ms마다 화면(프레임)을 업데이트하려고 시도합니다.

하지만 JavaScript 코드 실행이 오래 걸리면,

이 프레임 렌더링이 지연되거나 막히게 됩니다.

이를 렌더링 블로킹이라고 하며, 사용자에게는 버벅임이나 응답 없음처럼 보이게 됩니다.


✅ 3. 비동기 함수의 역할

비동기 함수는 오래 걸리는 작업 (예: API 호출, 파일 읽기, 타이머 등)을

백그라운드(콜백 큐, 마이크로태스크 큐)로 넘기고,

메인 스레드를 블로킹하지 않도록 합니다.

예: 동기 코드

// ⚠️ 가상의 동기 API 예시 — 실제로 fetchSync는 존재하지 않음
function getData() {
  const data = fetchSync("https://example.com/data"); // 오래 걸림
  console.log("데이터 도착:", data);
}
getData();

이 코드는 fetchSync()데이터를 모두 받아올 때까지
JavaScript의 메인 스레드를 블로킹합니다.
따라서 UI 렌더링이나 사용자 입력 처리도 일시적으로 멈추게 됩니다.

예: 비동기 코드

const url = "https://jsonplaceholder.typicode.com/posts/1";

// 비동기 함수에서 사용
async function getDataAwait() {
  console.log("1. await fetch 호출 전");
  const data = await fetch(url); // 비동기
  console.log("await 데이터 도착:", data);
  console.log("2. await fetch 호출 후");
}

// 요청은 백그라운드에서 실행되지만 응답은 무시됨
function getData() {
  console.log("3. fetch 호출 전");
  fetch(url) // 비동기 요청
    .then((response) => response.json()) // 응답을 JSON으로 파싱
    .then((data) => {
      console.log("데이터 도착:", data);
    })
    .catch((error) => {
      console.error("에러 발생:", error);
    });
  console.log("4. fetch 호출 후");
}

getDataAwait();
getData();
1. await fetch 호출 전
2. fetch 호출 전
3. fetch 호출 후
데이터 도착: {
	...
}
await 데이터 도착: Response {
	...
}
4. await fetch 호출 후

이 코드는 fetch()가 완료될 때까지 기다리는 동안
이벤트 루프는 다른 작업(UI 렌더링, 입력 처리 등)을 계속 수행함.
awaitasync 함수 내의 실행 순서를 보장


2. 📘 비동기 함수 사용 - Promise 객체

✅ 1. Promise란?

Promise미래에 완료될 수도 있고 실패할 수도 있는 비동기 작업을 표현하는 객체입니다.

즉, “지금은 없지만 나중에 어떤 값이 생길 거야” 를 약속(Promise)하는 객체입니다.


🔄 2. Promise의 상태 (State)

Promise는 3가지 상태를 가집니다.

상태설명
pending대기 중 (비동기 작업이 아직 완료되지 않음)
fulfilled성공 (작업이 완료되고 값이 반환됨)
rejected실패 (에러가 발생함)

🛠 3. Promise 생성 방법

Promise는 비동기 작업의 성공 또는 실패를 처리하기 위한 객체입니다.
가장 기본적인 생성 방법은 new Promise를 사용하는 것입니다.

new Promise
const promise = new Promise((resolve, reject) => {
  // 비동기 작업 수행
  const success = true;

  if (success) {
    resolve("작업 성공!");
  } else {
    reject("작업 실패...");
  }
});
  • resolve(value) → 작업 성공 시 호출
  • reject(error) → 작업 실패 시 호출
async function

async 함수는 내부적으로 자동으로 Promise를 반환하므로, Promise를 간단히 정의할 수 있는 방법입니다.

async function asyncTask() {
  return "작업 성공!";
}
  • return → 내부적으로 resolve()와 동일
  • throw → 내부적으로 reject()와 동일

예를 들어:

async function asyncTask() {
  const success = true;

  if (success) {
    return "작업 성공!"; // resolve("작업 성공!")
  } else {
    throw new Error("작업 실패..."); // reject("작업 실패...")
  }
}

async function을 사용하면 Promise를 더 간결하고 가독성 좋게 정의할 수 있습니다.


4. 📌 await란?

  • awaitPromise를 반환하는 함수 앞에 사용해서,
    해당 Promise가 해결(resolve)되거나 거부(reject)될 때까지 기다립니다.

  • 반드시 async 함수 안에서만 사용할 수 있습니다.

✅ 왜 await를 사용하는가?

1. 순차적인 비동기 작업 처리
  • await를 사용하면 여러 비동기 작업을 순서대로 실행할 수 있음.
async function doTasks() {
  const user = await getUser();
  const posts = await getPostsByUser(user.id);
  console.log(posts);
}
2. 비동기 코드의 가독성 향상
  • 기존에는 .then() 체인을 사용해야 했지만, await를 사용하면 마치 동기 코드처럼 작성할 수 있어서 훨씬 읽기 쉬움.
// 전통적인 방식 (then)
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data));

// await 사용
async function getData() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  console.log(data);
}

// 예외처리
async function loadData() {
  try {
    const data = await fetchData();
    console.log(data);
  } catch (error) {
    console.error('에러 발생:', error);
  }
}

🧾 정리 및 결론

JavaScript의 메인 스레드는, 비동기 함수가 처리되기를 기다리는 동안에도 다른 작업을 계속 실행할 수 있습니다.

이는 JavaScript가 싱글 스레드이면서도 비동기적으로 동작할 수 있는 이유이기도 합니다.

비동기 프로그래밍은 JavaScript에서 사용자 경험(UX)과 성능을 높이는 핵심 개념입니다.

그 중심에 있는 것이 바로 Promise, async/await이고,

이를 통해 복잡한 비동기 흐름을 더 예측 가능하고 가독성 있게 만들 수 있습니다.

  • Promise비동기 작업의 상태와 결과를 표현하는 객체로,
    .then() / .catch()를 통해 결과를 처리할 수 있습니다.
  • async 함수는 자동으로 Promise를 반환하며,
    await 키워드를 사용하면 마치 동기 코드처럼 자연스럽게 비동기 흐름을 작성할 수 있습니다.
  • await는 여러 비동기 작업을
    순차적으로 실행하거나, 예외 처리(try/catch) 와 함께 사용할 때 특히 유용합니다.

✅ 결론적으로, Promiseasync/await를 이해하면
복잡한 비동기 로직도 간결하고 직관적으로 작성할 수 있으며,
JavaScript의 이벤트 루프와 싱글 스레드 구조에 맞춰 효율적인 프로그램을 만들 수 있습니다.


2. Python에서의 async def

참고 자료

Python에서는 async def로 정의된 함수는 Promise 대신 Coroutine 객체를 반환합니다.

async def foo():
    return 42

coro = foo() # 실행되지 않음

result1 = await foo()
result2 = await coro

이때 foo()Coroutine 객체이며,

실행하려면 await하거나 asyncio.run() 같은 방식으로 명시적으로 실행시켜야 합니다.


📌 JavaScript vs Python 비교

개념JavaScriptPython
비동기 함수 키워드async functionasync def
반환 객체PromiseCoroutine
실행 방식자동 실행 (.then(), await)명시적 실행 (await, asyncio.run)
대기 키워드awaitawait
실행 시점 차이즉시 실행 (Promise 생성 시)await될 때 실행 (지연 실행)

💡 Python의 Coroutine은 JavaScript의 Promise와 유사하지만
lazy evaluation (지연 실행)이라는 중요한 차이가 있습니다.


✅ Coroutine의 특징

  • await을 만나면 중단되며, 나중에 재개됨
  • async for, async with와 같이 비동기 컨텍스트와 함께 사용 가능
  • asyncio 이벤트 루프에서 스케줄되어 실행
  • 비동기 함수 안에서는 await 키워드를 통해 다른 비동기 함수 호출 가능

✅ 예제: aiohttp를 사용한 비동기 HTTP 요청

import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    urls = [
        'https://api.github.com',
        'https://api.github.com/events',
        'https://api.github.com/repos/python/cpython'
    ]

    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url) for url in urls]
        responses = await asyncio.gather(*tasks)

        for url, response in zip(urls, responses):
            print(f"URL: {url}\n응답 길이: {len(response)}\n")

asyncio.run(main())

⚠️ 주의: 순차 실행 vs 병렬 실행

❌ 순차 실행 예시 (비효율적)

# URL 3개를 순서대로 하나씩 처리
await fetch(session, urls[0])
await fetch(session, urls[1])
await fetch(session, urls[2])

✅ 병렬 실행 예시 (효율적)

# URL 3개를 동시에 요청
tasks = [fetch(session, url) for url in urls]
responses = await asyncio.gather(*tasks)

gather()는 여러 Coroutine을 동시에 실행하고, 결과를 한 번에 반환합니다.


🧾 정리

Python의 메인 쓰레드 또한 Coruotine 이 처리되기 위해 기다리는 동안 다른 작업을 진행한다.

  • Python에서 async defCoroutine을 반환하며, 이는 명시적으로 실행되어야 합니다.

  • JavaScript의 Promise처럼 비동기 처리를 표현하지만,
    즉시 실행되지 않고 지연된다는 점이 큰 차이입니다.

  • asyncio.gather()async with, Semaphore 같은 기능을 활용하면
    복잡한 동시성 작업도 효과적으로 처리할 수 있습니다.

  • 실무에서는 예외 처리 및 리소스 제한도 함께 고려해야 안정적인 비동기 코드가 됩니다.


3. FastAPI에서의 async def

🚀 1. FastAPI는 ASGI 기반 웹 프레임워크

  • FastAPI는 ASGI(Application Server Gateway Interface) 표준을 따릅니다.

  • 내부적으로 FastAPI는 요청을 받으면
    ASGI 이벤트 루프 상에서 코루틴을 실행하여 비동기 처리합니다.

  • 덕분에 WebSocket, Server-Sent Events, 백그라운드 작업, 비동기 DB 호출 등을 자연스럽게 처리할 수 있습니다.


🔄 2. async def 함수 내부에서 await 사용 가능

@app.get("/user/{user_id}")
async def get_user(user_id: int):
    user = await fetch_user_from_db(user_id)
    return user
  • fetch_user_from_db()는 비동기 함수 (async def)이며, await으로 호출합니다.
  • FastAPI는 이 await을 감지하고
    I/O 대기 시간 동안 다른 요청을 처리할 수 있도록 이벤트 루프에 양보합니다.

즉, ASGI 서버는 비동기 처리를 통해 요청 처리 중 발생하는 대기 시간을 활용하여 다른 요청도 효율적으로 동시에 처리합니다.

🟢 결과적으로:

  • 서버는 I/O 작업 중 멈추지 않고
  • 더 많은 요청을 동시에 처리할 수 있어
  • 성능이 크게 향상됩니다.

⏳ 3. 비동기 테스트

Server side code

# fastapi_async.py
import asyncio
import time
from datetime import datetime

import uvicorn
from fastapi import FastAPI

app = FastAPI()


# ✅ 비동기 버전
@app.get("/ping_async")
async def ping_async():
    print(f"[{datetime.now().strftime('%H:%M:%S')}] 비동기 요청 수신")
    await asyncio.sleep(2)  # 비동기 대기
    print(f"[{datetime.now().strftime('%H:%M:%S')}] 비동기 응답 반환")
    return {"message": "pong_async", "timestamp": datetime.now().isoformat()}


# ❌ 동기 버전
@app.get("/ping_sync")
def ping_sync():
    print(f"[{datetime.now().strftime('%H:%M:%S')}] 동기 요청 수신")
    time.sleep(2)  # 동기 대기 (이 동안 서버가 멈춤)
    print(f"[{datetime.now().strftime('%H:%M:%S')}] 동기 응답 반환")
    return {"message": "pong_sync", "timestamp": datetime.now().isoformat()}
  • 실행 스크립트
gunicorn fastapi_async:app \
    -k uvicorn.workers.UvicornWorker \
    -w 1 \
    -b 0.0.0.0:18080 \
    --thread 1

Client side code

# client.py
import asyncio
from datetime import datetime

import aiohttp


async def fetch(session, url, i):
    print(f"[{datetime.now().strftime('%H:%M:%S')}] 요청 {i} 시작 → {url}")
    async with session.get(url) as response:
        json_data = await response.json()
        print(
            f"[{datetime.now().strftime('%H:%M:%S')}] 요청 {i} 응답 수신: {json_data['message']}"
        )
        return json_data


async def run_test(endpoint: str):
    print(f"\n▶️ [{endpoint}] 테스트 시작")
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, endpoint, i) for i in range(5)]
        results = await asyncio.gather(*tasks)
    print(f"✅ [{endpoint}] 테스트 완료")


if __name__ == "__main__":
    asyncio.run(run_test("http://127.0.0.1:18080/sleep/"))  # 비동기 테스트
    asyncio.run(run_test("http://127.0.0.1:18080/sleep-sync"))  # 동기 테스트

🎯 결론

FastAPI 공식문서

▶️ [http://127.0.0.1:18080/sleep/] 테스트 시작
[18:16:24] 요청 0 시작 → http://127.0.0.1:18080/sleep/
[18:16:24] 요청 1 시작 → http://127.0.0.1:18080/sleep/
[18:16:24] 요청 2 시작 → http://127.0.0.1:18080/sleep/
[18:16:24] 요청 3 시작 → http://127.0.0.1:18080/sleep/
[18:16:24] 요청 4 시작 → http://127.0.0.1:18080/sleep/
[18:16:29] 요청 0 응답 수신: FastAPI sleep finished
[18:16:29] 요청 1 응답 수신: FastAPI sleep finished
[18:16:29] 요청 2 응답 수신: FastAPI sleep finished
[18:16:29] 요청 3 응답 수신: FastAPI sleep finished
[18:16:29] 요청 4 응답 수신: FastAPI sleep finished
✅ [http://127.0.0.1:18080/sleep/] 테스트 완료

▶️ [http://127.0.0.1:18080/sleep-sync] 테스트 시작
[18:16:29] 요청 0 시작 → http://127.0.0.1:18080/sleep-sync
[18:16:29] 요청 1 시작 → http://127.0.0.1:18080/sleep-sync
[18:16:29] 요청 2 시작 → http://127.0.0.1:18080/sleep-sync
[18:16:29] 요청 3 시작 → http://127.0.0.1:18080/sleep-sync
[18:16:29] 요청 4 시작 → http://127.0.0.1:18080/sleep-sync
[18:16:34] 요청 0 응답 수신: FastAPI sleep finished
[18:16:34] 요청 1 응답 수신: FastAPI sleep finished
[18:16:34] 요청 3 응답 수신: FastAPI sleep finished
[18:16:34] 요청 2 응답 수신: FastAPI sleep finished
[18:16:34] 요청 4 응답 수신: FastAPI sleep finished
✅ [http://127.0.0.1:18080/sleep-sync] 테스트 완료

테스트 결과 두 Endpoint의 차이가 없다.

사실 FastAPI 공식문서에서도 아래처럼 말한다.

모르겠다면, 그냥 `def`를 사용하십시오.

즉, FastAPI에서 알아서 최적화를 해주는 것으로 아래를 참고하면 된다.

**참고**: `path operation functions`에서 필요한만큼 `def`와 `async def`를 혼용할 수 있고, 가장 알맞은 것을 선택해서 정의할 수 있습니다. 

FastAPI가 자체적으로 알맞은 작업을 수행할 것입니다.

어찌되었든, 상기 어떠한 경우라도, FastAPI는 여전히 비동기적으로 작동하고 매우 빠릅니다.

그러나 `asyns def`을 수행함으로써 어느 정도의 성능 최적화가 가능합니다.

🚦 4. FastAPIasync def 사용 가이드

✅ 언제 써야 할까?
사용 상황설명
FastAPI 엔드포인트HTTP 요청을 처리하는 라우터 함수는 비동기로 처리하는 게 일반적입니다.
I/O 작업이 포함된 함수DB 쿼리, 파일 읽기, 외부 API 요청 등 시간이 오래 걸리는 작업에는 await을 사용해야 하므로 async def 필요합니다.

❌ 언제 쓰지 않아도 될까?
사용 상황설명
순수 계산 로직CPU만 사용하는 작업(예: 수학 계산, 문자열 처리 등)은 def가 더 효율적입니다.
단순한 데이터 조작예: dict 필터링, 리스트 정렬 등 동기적 로직은 굳이 async로 만들 필요 없습니다.

🔍 5. FastAPI vs Django REST Framework (DRF) 비교

항목FastAPIDjango REST Framework (DRF)
기반 인터페이스ASGI (비동기) 전용🔶 WSGI (동기) 기반
✅ ASGI 부분 지원 (Django 3.0+)
비동기 처리async def 완전 지원100% 비동기 코루틴 설계제한적 지원async def 지원은 있지만
일부 라이브러리는 비동기 미지원
성능 지향성고성능, 낮은 레이턴시, 병렬성 강점기능 풍부하지만 상대적으로 느림
(특히 I/O 병목 상황에서)
WebSocket 지원✅ 완전 지원 (ASGI 구조)❌ DRF는 WebSocket 비지원
Django Channels 별도 필요
설계 철학비동기 웹 API 서버에 최적화(Fast, Modern, Lightweight)전통적인 웹 앱 + API에 적합
(Admin, 인증, ORM 통합 등)
생태계경량, 자유로운 설계직접 구성 필요대규모 웹앱에 적합한 내장 기능 (Admin, Auth 등)
대표 서버uvicorn, hypercorn (ASGI 서버 필수)gunicorn, uwsgi
(기본 WSGI 서버)

참고자료

🌐 서버와 APP 연결 방식

참고 자료 1
참고자료 2
ASGI 공식문서

🔸 1. CGI (Common Gateway Interface)

  • 역할
    웹 서버(Nginx, Apache 등)와 웹 애플리케이션(예: Python, Perl, PHP 등)을 연결하는
    표준 인터페이스.

  • 필요성
    웹 서버와 애플리케이션 언어가 각기 다르면 직접 통신이 번거로움
    공통된 규칙(CGI)을 통해 요청 처리 표준화.
  • 과거 상황
    • 초기 웹은 정적 HTML 위주 → 별도의 처리 로직 필요 없음.
    • 이후 동적인 데이터 요청이 많아지면서 웹 서버가 처리할 수 없는 요청을 외부 애플리케이션이 처리해야 했고, CGI가 중개자 역할을 하게 됨.
  • 단점
    • 요청마다 새로운 프로세스를 생성 및 삭제해야 함.
    • 동시 사용자가 많을 경우, 프로세스 생성/제거가 반복되며 커널 리소스 낭비.
    • 결과적으로 오버헤드가 크고, 성능 저하 발생.

🎯 2. FastCGI

  • 역할
    CGI의 성능 문제(요청마다 프로세스 생성)를 해결하기 위해 등장한 고성능 인터페이스.

  • 특징

    • 하나의 프로세스로 여러 요청 처리
      프로그램을 메모리에 상주시켜 재사용.
    • 결과적으로 CGI 대비 오버헤드가 크게 줄고, 처리 속도 향상.
  • 활용 예시

    • Java의 Tomcat은 Web Server + FastCGI 형태로 다수 사용자 요청을 효율적으로 처리.
    • Java 진영에서는 WAS(웹 애플리케이션 서버) 사용이 보편적 (Tomcat, JBoss 등).
    • 일부 언어는 FastCGI를 수동으로 붙여 사용하기도 함.
  • Python에서는:

    • Tomcat과 같은 별도 WAS가 존재하지 않음.
    • 대신 Python 전용 인터페이스인 WSGI 또는 FastCGI 기반 서버를 사용하여 WAS 형태 구성

  • 활용: Tomcat, JBoss 등 자바 기반 WAS에서 유사한 구조로 사용됨.

🔹 3. WSGI란?

WSGI를 위스키라고 읽는다.

WSGI(Web Server Gateway Interface)동적인 웹 페이지 요청을 처리하는 핵심 요소로 사용됩니다.

🔄 동적 요청 처리 흐름

  1. 사용자가 웹사이트에서 동적 페이지(예: 로그인, 게시글 작성 등)를 요청하면,
  2. 웹 서버(Apache, Nginx 등)는 해당 요청을 직접 처리하지 않고,
  3. WSGI 서버(Gunicorn, uWSGI 등)를 호출합니다.
  4. WSGI 서버는 요청을 받아서,
    최종적으로 WSGI 애플리케이션(Django, Flask 등)에 전달합니다.
  5. WSGI 애플리케이션이 동적 로직을 실행하고,
    결과를 WSGI 서버 → 웹 서버 → 사용자 순으로 반환합니다.

📌 즉, 실제 동적 처리를 담당하는 주체는 WSGI 애플리케이션이며,
WSGI 서버는 그 중간 연결 통로(미들웨어) 역할을 합니다.


🧵 왜 WSGI는 동기적으로 동작할까?

WSGI는 PEP 3333에 정의된 Python 웹 애플리케이션 인터페이스의 표준으로,

함수 호출 기반의 동기적 구조를 전제로 설계되었습니다.

즉, 요청(request)이 들어오면, 응답(response)을 반환할 때까지 한 번에 처리하는 구조입니다.

이는 다음과 같은 특징을 가집니다:

  • 하나의 요청이 처리되는 동안, 해당 작업이 완료되어야 다음 요청을 처리할 수 있음
  • 멀티스레딩 혹은 멀티프로세싱 방식으로 동시성을 구현해야 함
  • 비동기(IO 기반 처리)에는 적합하지 않음

👉 이런 한계 때문에,
비동기 처리가 필요한 경우 ASGI(Asynchronous Server Gateway Interface)가 사용됩니다
(예: FastAPI, Django Channels).


⚙️ Python에서 자주 사용하는 WSGI 서버

Python 생태계에서는 다음과 같은 WSGI 서버가 많이 사용됩니다:

서버 이름특징
Gunicorn가장 널리 사용되는 WSGI 서버. 멀티프로세싱 기반
uWSGI성능과 확장성 뛰어나며, 많은 옵션 제공
Waitress순수 Python 기반, Windows 지원 우수
mod_wsgiApache 서버에 내장되어 직접 실행 가능

🔒 WSGI 서버의 한계

WSGI 서버는 단독으로 사용 가능하지만, 일반적으로 Nginx 같은 프록시 서버와 함께 구성됩니다. 그 이유는 다음과 같은 단점 때문입니다:

  1. 외부 공격(DDoS 등)에 취약
  2. HTTPS 처리를 직접 지원하지 않음
  3. 정적 파일 전송 성능이 낮음
  4. 동시 접속 처리 능력이 제한적

👉 그래서 Nginx는 보안, 성능, 정적 파일 처리 등을 맡고, WSGI 서버는 실제 파이썬 애플리케이션 실행을 담당하는 구조가 일반적입니다.


📌 정리

WSGI는 웹 서버가 Python 웹 애플리케이션의 동적 요청을 처리하도록 연결해주는 표준 인터페이스입니다.
WSGI 서버(Gunicorn 등)는 웹 서버로부터 요청을 받아,
최종적으로 Django 같은 WSGI 애플리케이션이 실제 응답을 생성하도록 중계합니다.
결과적으로, 동적 웹 페이지 처리는 항상 WSGI 애플리케이션이 담당합니다.

🧠 그리고 WSGI는 구조적으로 동기 방식이기 때문에, 고성능 비동기 처리를 위해서는
ASGI 같은 새로운 인터페이스가 필요합니다


🔁 4. ASGI

ASGI(Asynchronous Server Gateway Interface)

오랫동안 사용되어 온 WSGI(Web Server Gateway Interface)의 정신적 후속 모델

비동기 Python으로의 전환을 위한 인터페이스.

  • 특징

    • 비동기(asynchronous) 방식 + 동기 코드도 병행 가능
    • WebSocket, HTTP/2 등 최신 프로토콜 지원
    • 여러 이벤트 루프/메시지 기반 통신 구조 지원
    • 예: Django 3.0+, FastAPI, Starlette, Uvicorn, Daphne
  • 구성 요소

    • Scope: 연결 정보 (예: HTTP, WebSocket 요청의 메타데이터)
    • Event loop: asyncio 기반 비동기 실행
    • Application callable: async def app(scope, receive, send)

❓ WSGI의 한계는 무엇인가요?

“WSGI를 그냥 업그레이드하면 안 돼?”라는 질문은 자주 제기되었지만,

다음과 같은 구조적 한계 때문에 어렵습니다:

  • WSGI는 동기 방식의 단일 호출(callable) 인터페이스를 사용합니다.
  • 요청(request)을 받고 응답(response)을 반환하는 방식만 지원하며,
  • WebSocket이나 long-polling처럼 연결이 오래 유지되는 통신 방식에는 부적합합니다.
  • WSGI를 비동기로 바꾸더라도 여전히 이벤트가 하나만 들어오는 구조라서,
    WebSocket처럼 여러 이벤트가 순차적으로 들어오는 프로토콜을 처리할 수 없습니다.

⚙️ ASGI는 어떻게 동작하나요?

ASGI는 다음 세 가지 인자를 받는 비동기 함수(async callable)로 구성됩니다:

async def application(scope, receive, send):
  • scope: 연결에 대한 정보가 담긴 딕셔너리
  • receive: 클라이언트로부터 이벤트를 비동기적으로 수신
  • send: 클라이언트로 이벤트를 비동기적으로 전송

이 구조는 다음을 가능하게 합니다:

  • 여러 이벤트를 순차적으로 처리
  • 외부 이벤트(예: Redis 큐)를 감지하는 백그라운드 작업 실행

🔸 예시

HTTP 요청 수신 이벤트 예시

{
    "type": "http.request",
    "body": b"Hello World",
    "more_body": False
}

WebSocket 메시지 전송 예시

{
    "type": "websocket.send",
    "text": "Hello world!"
}

모든 이벤트는 type 키를 가지며, 이 키를 통해 해당 이벤트의 구조를 알 수 있습니다.


🔹 Uvicorn
  • ASGI 기반 Python 웹 애플리케이션 서버.
  • 내부적으로 uvloop 사용 → 고성능 이벤트 루프 제공.
  • uvlooplibuv 기반이며, libuv는 JavaScript 엔진 V8(Node.js) 에서도 사용되는 비동기 처리 모듈.

🔄 WSGI와의 호환성

ASGI는 WSGI의 상위 호환(superset)으로 설계되었습니다.
즉, WSGI 애플리케이션도 ASGI 서버 내에서 실행할 수 있습니다.

  • asgiref 라이브러리에서 제공하는 변환 래퍼(wrapper)를 통해 가능합니다.
  • 비동기 이벤트 루프 외부에서 WSGI 애플리케이션을 실행하기 위해 스레드풀(thread pool)을 사용할 수 있습니다.

0개의 댓글