Escrever async def numa rota não transforma chamadas síncronas em operações assíncronas. Se essa rota chama uma função que bloqueia, o event loop fica ocupado até ela terminar. Enquanto isso, outras tarefas daquele worker não conseguem avançar.
Um exemplo pequeno
Esta rota espera dois segundos usando time.sleep:
import time
from fastapi import FastAPI
app = FastAPI()
@app.get('/espera')
async def espera():
time.sleep(2)
return {'ok': True}
time.sleep bloqueia a thread que executa o event loop. Com um worker do Uvicorn, envie duas requisições a /espera ao mesmo tempo. Elas tendem a ser atendidas em sequência: a segunda precisa esperar a primeira liberar o loop. O tempo exato depende do ambiente; o ponto do exemplo é o bloqueio, não um número de desempenho.
Trocar a espera por await asyncio.sleep(2) permite que o loop trabalhe em outras tarefas enquanto aguarda:
import asyncio
from fastapi import FastAPI
app = FastAPI()
@app.get('/espera')
async def espera():
await asyncio.sleep(2)
return {'ok': True}
Essa troca serve para demonstrar a diferença de agendamento. Em código real, use uma biblioteca assíncrona compatível com await para a operação de I/O; asyncio.sleep não torna uma chamada bloqueante qualquer assíncrona.
O que fazer com uma biblioteca síncrona
O FastAPI executa rotas e dependências declaradas com def em uma thread pool. Isso ajuda quando a própria rota usa uma biblioteca síncrona. Mas uma função auxiliar síncrona chamada diretamente de dentro de uma rota async def não é movida automaticamente para essa pool: a chamada bloqueante roda ali mesmo.
Para I/O, as opções mais simples costumam ser usar um cliente assíncrono e aguardá-lo com await, ou declarar a rota como def quando a biblioteca disponível é síncrona. Em código assíncrono que precisa chamar uma função bloqueante de I/O, asyncio.to_thread pode executá-la em outra thread. Trabalho pesado de CPU pede outra análise; mover a chamada para uma thread não garante paralelismo de CPU em Python.
Onde entra o Kubernetes
O Kubernetes não causa esse bloqueio. Ele pode tornar os efeitos mais visíveis se uma sonda HTTP depender da mesma aplicação que está com o event loop ocupado. Uma falha de prontidão marca o pod como não pronto para receber tráfego; uma falha de liveness, após os limites configurados, pode levar ao reinício do container.
Reiniciar pode recuperar um processo que não progride, mas não corrige a chamada bloqueante. Uma sonda agressiva pode transformar lentidão em reinícios repetidos. Primeiro vale distinguir latência, falhas de prontidão, falhas de liveness e reinícios; depois, verificar se coincidem com o trabalho executado no worker.
Este exemplo explica um mecanismo possível. Ele não identifica a causa de um incidente específico nem substitui medições da aplicação e da configuração real do cluster.