๐ ์ฝ 2๋ถ ์ฝ๋ ๋ฐ ์์๋ฉ๋๋ค
์ด์ ๊ธ ๋ณด๊ธฐ(FastAPI์์์ Request์ Response ์๋ฒฝ ์ดํด)
โ async/await ๋ฌธ๋ฒ๊ณผ ๋น๋๊ธฐ ํจ์์ ์ฐจ์ด๋ฅผ ์์ ๋ก ๋ฐฐ์ฐ๊ธฐ
“FastAPI๋ ์ ์ด๋ ๊ฒ ๋น ๋ฅผ๊น?”
๊ทธ ๋น๋ฐ์ ๋ฐ๋ก ๋น๋๊ธฐ(Async) ๊ธฐ๋ฐ์ ์ํคํ ์ฒ์ ์๋ค.
FastAPI๋ ๋น๋๊ธฐ I/O ์ฒ๋ฆฌ์ ํนํ๋ Python์ ์ต์ ๊ธฐ๋ฅ์ ์ ๊ทน ํ์ฉํ๋ค. ์ด๋ฅผ ํตํด ๋๊ท๋ชจ ํธ๋ํฝ๋ ๋น ๋ฅด๊ณ ์์ ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๊ฒ ๋๋ค.
๐ ๋น๋๊ธฐ๋ ๋ฌด์์ธ๊ฐ?
์ฐ๋ฆฌ๊ฐ ํํ ์์ฑํ๋ Python ์ฝ๋๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋๊ธฐ(Synchronous) ๋ฐฉ์์ด๋ค. ์์ ์ด ์์ฐจ์ ์ผ๋ก ์งํ๋๊ณ , ํ๋์ ์์ ์ด ๋๋์ผ ๋ค์ ์์ ์ด ์์๋๋ค.
ํ์ง๋ง ์น ์๋ฒ์์๋ ์ด๋ฐ ๋ฐฉ์์ด ๋นํจ์จ์ ์ผ ์ ์๋ค. ์๋ฅผ ๋ค์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ๋ ์ธ๋ถ API ์์ฒญ์ฒ๋ผ ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆฌ๋ ์์ ์ด ์๋ค๋ฉด, ๊ทธ ์๊ฐ ๋์ ์๋ฒ๊ฐ ์๋ฌด ์ผ๋ ๋ชป ํ๊ณ ๊ฐ๋งํ ์๊ฒ ๋๋ค.
โ ๋น๋๊ธฐ๋ ์ด๋ ๊ฒ ํด๊ฒฐํด์ค
- ๋น๋๊ธฐ ํจ์๋
await
ํค์๋๋ฅผ ํตํด ๋ค๋ฅธ ์์ ์ด ๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๋์ , ๊ทธ ์๊ฐ ๋์ ๋ค๋ฅธ ์์ฒญ์ ์ฒ๋ฆฌํ ์ ์๊ฒ ํด์ค๋ค. - CPU๋ฅผ ํจ์จ์ ์ผ๋ก ํ์ฉํ๊ณ , ๋ณ๋ ฌ์ฑ์ด ํฅ์๋๋ค.
๐งช async / await ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
FastAPI๋ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ํด Python์ async
/await
๋ฌธ๋ฒ์ ์ฌ์ฉํ๋ค.
์ฐ์ ๋ฌธ๋ฒ๋ถํฐ ์ดํด๋ณด์.
1. ๊ธฐ๋ณธ ์์
import asyncio
async def say_hello():
await asyncio.sleep(1)
print("Hello after 1 second")
async def
๋ก ์ ์ธ๋ ํจ์๋ ๋น๋๊ธฐ ํจ์์ด๋ค.await
๋ ๋น๋๊ธฐ ์์ ์ ๊ธฐ๋ค๋ฆฌ๋ ํค์๋.asyncio.sleep(1)
์ ์ค์ ๋ก 1์ด ๋์ ๊ธฐ๋ค๋ฆฌ์ง๋ง, ๊ทธ ์ฌ์ด์ ๋ค๋ฅธ ์์ ์ ์ฒ๋ฆฌํ ์ ์๋ค.
๐งฌ FastAPI ์์ ๋น๋๊ธฐ ํจ์ ์ฌ์ฉ
FastAPI๋ ๋ผ์ฐํธ ํจ์์์ async def
๋ฅผ ์ฐ๋ ๊ฑธ ์์ฐ์ค๋ฝ๊ฒ ์ง์ํ๋ค. ๊ทธ๋ผ ์ค์ API ์์ ๋ฅผ ๋ณด์.
from fastapi import FastAPI
import asyncio
app = FastAPI()
@app.get("/wait")
async def wait_response():
await asyncio.sleep(2)
return {"message": "2์ด ๊ธฐ๋ค๋ ธ์ด์!"}
- ์ด API์ ์ ์ํ๋ฉด 2์ด ํ์ ์๋ต์ด ๋์์จ๋ค.
- ์ค์ํ ๊ฑด ๋ค๋ฅธ ํด๋ผ์ด์ธํธ๋ ์ด ์๊ฐ ๋์์๋ ์์ฒญ์ ํ ์ ์๋ค๋ ๊ฒ.
- ์ฆ, **๋์์ฑ(concurrency)**์ด ์๊ธด๋ค.
โ ์ผ๋ฐ ํจ์ vs ๋น๋๊ธฐ ํจ์
์ด์ ๋๊ธฐ ํจ์์ ๋น๋๊ธฐ ํจ์์ ์ฐจ์ด์ ์ ๋น๊ตํด๋ณด์.
1. ๋๊ธฐ ํจ์ (blocking)
import time
@app.get("/sync")
def sync_route():
time.sleep(2) # CPU๊ฐ ์ด ๋์ ๋๊ธฐ
return {"message": "๋๊ธฐ ์๋ต"}
time.sleep()
์ CPU ์์ฒด๋ฅผ 2์ด ๋์ ๋ถ์ก๊ณ ์์ด์ ๋ค๋ฅธ ์์ฒญ๋ ๋งํ๋ค.- ์ ์ ๊ฐ ๋ง์์ง๋ฉด ๋ณ๋ชฉ ํ์์ด ์๊ธด๋ค.
2. ๋น๋๊ธฐ ํจ์ (non-blocking)
import asyncio
@app.get("/async")
async def async_route():
await asyncio.sleep(2) # CPU๋ ๋ค๋ฅธ ์ผ๋ ๊ฐ๋ฅ
return {"message": "๋น๋๊ธฐ ์๋ต"}
asyncio.sleep()
์ CPU๋ฅผ ์ ์ ํ์ง ์๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ ์์ ์ ๋์์ ์ฒ๋ฆฌํ ์ ์๋ค.- ๋น๋๊ธฐ ์ฒ๋ฆฌ๊ฐ ํ์ํ ์์ : DB I/O, API ์์ฒญ, ํ์ผ ์ฝ๊ธฐ ๋ฑ
๐จ ์ธ์ async๋ฅผ ์จ์ผ ํ ๊น?
1. ๋น๋๊ธฐ I/O๊ฐ ํ์ํ ๊ฒฝ์ฐ
- ์ธ๋ถ API ํธ์ถ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ (async ๋๋ผ์ด๋ฒ ์ฌ์ฉ ์)
- ํ์ผ ์ ์ถ๋ ฅ
- ๋๊ธฐ์๊ฐ์ด ๊ธด ์์
2. ๋น๋๊ธฐ๋ฅผ ์ฐ๋ฉด ์ ๋๋ ๊ฒฝ์ฐ
- CPU ๋ฐ์ด๋ ์์ (ex. ๋ณต์กํ ์ํ ๊ณ์ฐ)
await
๋ฅผ ์ ์ฐ๋ ํจ์ โ ๊ตณ์ดasync def
๋ก ๋ง๋ค ํ์ ์์
์ฆ, ๋น๋๊ธฐ๋ ๊ธฐ๋ค๋ฆผ์ด ๋ง์ ์์ ์ ๊ฐํ๊ณ , ๋ณต์กํ ์ฐ์ฐ์๋ ์ ํฉํ์ง ์๋ค.
โฑ FastAPI ๋น๋๊ธฐ ์์: ์ธ๋ถ API ํธ์ถ
์๋ฅผ ๋ค์ด ์ธ๋ถ ๋ ์จ API์์ ์ ๋ณด๋ฅผ ๋ฐ์์ค๋ API๋ฅผ ๋ง๋ ๋ค๊ณ ํด๋ณด์.
import httpx
from fastapi import FastAPI
app = FastAPI()
@app.get("/weather")
async def get_weather():
async with httpx.AsyncClient() as client:
response = await client.get("https://api.weatherapi.com/...") # ์์
return response.json()
httpx.AsyncClient()
๋ ๋น๋๊ธฐ HTTP ํด๋ผ์ด์ธํธawait client.get()
์ HTTP ์์ฒญ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ๋์ ๋ค๋ฅธ ์์ฒญ์ ์ฒ๋ฆฌํ ์ ์๊ฒ ํด์ค- ๋๊ท๋ชจ ํธ์ถ์ด ํ์ํ ์ํฉ์์ ํจ๊ณผ๊ฐ ๊ทน๋ํ๋จ
๐ก ๋น๋๊ธฐ DB ๋๋ผ์ด๋ฒ์์ ์ฐ๋
FastAPI์ ์ง์ง ํ์ ๋น๋๊ธฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํจ๊ป ์ฌ์ฉํ ๋ ๊ทน๋ํ๋๋ค.
- ์:
Databases
,Tortoise ORM
,SQLModel
(async ์ง์) - ๋น๋๊ธฐ DB ๋๋ผ์ด๋ฒ(ex.
asyncpg
,aiomysql
)์ ํจ๊ป ์ฐ๋ฉด ์์ฒ ๊ฐ์ ๋์ ์ ์๋ ์์ ์ ์ผ๋ก ์ฒ๋ฆฌ ๊ฐ๋ฅ
from databases import Database
database = Database("sqlite+aiosqlite:///./test.db")
@app.on_event("startup")
async def startup():
await database.connect()
@app.on_event("shutdown")
async def shutdown():
await database.disconnect()
๐งพ ์์ฝ: FastAPI์์ ๋น๋๊ธฐ์ ์ฅ์ ์?
ํญ๋ชฉ | ๋๊ธฐ(Sync) | ๋น๋๊ธฐ(Async) |
---|---|---|
์ฝ๋ ์ ์ธ | def | async def |
๋๊ธฐ ์ค CPU ์ฌ์ฉ | ์ ์ ํจ | ํด์ ํจ |
์ฒ๋ฆฌ ์๋ | ๋๋ฆผ (๋์ ์์ฒญ ์ ํ) | ๋น ๋ฆ (๋์ ์์ฒญ ๊ฐ๋ฅ) |
์ฌ์ฉ ๋์ | ๊ฐ๋จํ ์ฐ์ฐ | I/O ์ค์ฌ ์์ |
FastAPI๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋น๋๊ธฐ๋ฅผ ์ง์ํ๋ฏ๋ก, ์ฒ์๋ถํฐ ๋น๋๊ธฐ ์คํ์ผ๋ก API๋ฅผ ์ค๊ณํ๋ ๊ฒ์ด ์ข๋ค. ๋จ, async
ํจ์ ์์์๋ ๋ฐ๋์ await
๊ฐ๋ฅํ ์ฝ๋๋ง ์ฌ์ฉํด์ผ ํ๋ค๋ ์ ๋ ๊ธฐ์ตํด ๋์.
FastAPI ๊ณต์ ๋ฌธ์ : https://fastapi.tiangolo.com/ko/
