Asynchroniczność: async/await, asyncio
Backend ZEUS jest asynchroniczny od HTTP aż po bazę i kolejkę ARQ. Asynchroniczność daje ogromną przepustowość I/O, ale tylko gdy nie blokujesz pętli zdarzeń. Ta lekcja to zestaw zasad, których trzymamy się w praktyce.
Co robi async/await
async def tworzy korutynę. await oddaje sterowanie pętli zdarzeń na czas
operacji I/O (sieć, baza, dysk), żeby obsłużyć inne żądania.
async def fetch_forest(forest: str) -> dict:
async with httpx.AsyncClient() as client:
resp = await client.get(f"/api/forest/{forest}")
return resp.json()
Najczęstszy błąd: blokowanie pętli
Wywołanie synchronicznej, wolnej operacji w korutynie blokuje cały serwer — wszystkie inne żądania czekają.
# źle — blokuje pętlę zdarzeń
async def bad():
time.sleep(5) # zatrzymuje cały event loop
data = requests.get(url) # synchroniczny HTTP — też blokuje
# dobrze
async def good():
await asyncio.sleep(5)
async with httpx.AsyncClient() as c:
data = await c.get(url)
Standard ProfessNet: w kodzie async używamy bibliotek async (
httpx,asyncpg/SQLAlchemy async,redis.asyncio). Bibliotek synchronicznych nie wołamy bezpośrednio.
Kod synchroniczny w świecie async
Gdy musisz użyć blokującej biblioteki (np. CPU-bound parsing albo stary klient SDK), zepchnij ją do puli wątków:
import asyncio
result = await asyncio.to_thread(blocking_parse, raw_bytes)
Równoległość: asyncio.gather
Kilka niezależnych operacji I/O uruchamiamy współbieżnie, nie sekwencyjnie.
# sekwencyjnie — wolno (suma czasów)
a = await fetch_forest("corp")
b = await fetch_forest("dmz")
# współbieżnie — szybko (czas najwolniejszego)
a, b = await asyncio.gather(
fetch_forest("corp"),
fetch_forest("dmz"),
)
Limit współbieżności
Przy wielu probe'ach nie odpalamy tysiąca zadań naraz — ograniczamy semaforem, żeby nie zatkać sieci ani serwera docelowego.
sem = asyncio.Semaphore(10)
async def guarded(forest: str) -> dict:
async with sem:
return await fetch_forest(forest)
results = await asyncio.gather(*(guarded(f) for f in forests))
Timeouty i anulowanie
Każde zewnętrzne wywołanie ma timeout. Bez tego zawieszony peer wisi w nieskończoność.
async with asyncio.timeout(30):
data = await collect_inventory(forest)
| Wzorzec | Do czego |
|---|---|
await | pojedyncza operacja I/O |
asyncio.gather | wiele operacji współbieżnie |
Semaphore | limit równoległości |
asyncio.to_thread | wepchnięcie kodu blokującego |
asyncio.timeout | twardy limit czasu |
Wskazówka: długie zadania (skany całego forestu) nie żyją w handlerze HTTP — trafiają do ARQ. Endpoint zwraca
202ijob_id, a worker robi resztę.
Złota zasada: w korutynie albo await-ujesz coś asynchronicznego, albo spychasz
blok do to_thread. Wszystko inne zatrzymuje serwer. Trzymaj się tego, a async
da ci wydajność bez niespodzianek.