Em muitos pipelines corporativos de ETL (Extract, Transform, Load), o maior gargalo não está no processamento dos dados, mas no tempo gasto aguardando respostas externas.
Consultas a bancos de dados, chamadas para APIs de terceiros, leitura de serviços internos e processos de scraping costumam consumir muito mais tempo esperando por I/O do que utilizando CPU. Quando essas operações são executadas de forma sequencial, o desperdício de recursos é inevitável.
Foi justamente esse cenário que motivou a adoção de uma arquitetura assíncrona utilizando asyncio e Quart.
Por que Asyncio?
O asyncio permite que uma única aplicação gerencie centenas ou até milhares de operações de entrada e saída simultaneamente sem a necessidade de criar uma thread para cada requisição.
Enquanto uma conexão aguarda a resposta de uma API, o event loop continua executando outras tarefas. Na prática, isso significa maior aproveitamento do hardware e uma redução significativa no tempo total de execução.
Esse modelo é especialmente eficiente para aplicações com alta carga de I/O, como:
- Integrem-se entre sistemas corporativos;
- Coleta de dados em múltiplas APIs;
- Scrapers executando em paralelo;
- Processamento de filas;
- Serviços de automação;
- Gateways de integração.
O papel do Quart
Para os microsserviços, a escolha foi o Quart, um framework compatível com a API do Flask, porém totalmente baseado em ASGI.
Essa compatibilidade facilitou bastante a migração de serviços existentes, permitindo manter uma estrutura familiar enquanto aproveitávamos todos os benefícios da programação assíncrona.
Com Quart foi possível criar endpoints capazes de lidar com centenas de requisições simultâneas sem bloquear a aplicação durante chamadas externas.
Concorrência na prática
Um exemplo simples demonstra como diversas requisições HTTP podem ser executadas simultaneamente utilizando aiohttp e asyncio.
import asyncio
import aiohttp
async def fetch_api(session, url):
async with session.get(url) as response:
return await response.json()
async def main():
async with aiohttp.ClientSession() as session:
tasks = [
fetch_api(session, f"https://api.example.com/data/{i}")
for i in range(10)
]
results = await asyncio.gather(*tasks)
print(f"Processados {len(results)} registros com sucesso!")
asyncio.run(main())
Embora o exemplo utilize apenas dez requisições, o mesmo padrão pode ser aplicado para centenas de chamadas concorrentes, respeitando os limites de taxa (rate limiting) das APIs utilizadas.
Lições aprendidas
A migração para um modelo assíncrono trouxe ganhos que vão além da velocidade.
O código ficou mais escalável, reduziu a necessidade de múltiplos processos em paralelo e tornou muito mais simples lidar com grandes volumes de integrações simultâneas.
Outro benefício importante foi a redução do consumo de recursos. Em vez de manter dezenas de threads bloqueadas aguardando respostas de rede, o event loop aproveita esse tempo para executar outras tarefas, aumentando significativamente a eficiência da aplicação.
Naturalmente, programação assíncrona também exige alguns cuidados. Bibliotecas bloqueantes podem comprometer todo o desempenho do sistema, e é importante utilizar ferramentas compatíveis com asyncio, como aiohttp, asyncpg e outros drivers assíncronos.
Resultados
Após migrar diversos microsserviços legados para uma arquitetura totalmente não bloqueante, observamos uma redução de aproximadamente 70% no tempo total de execução dos pipelines operacionais.
Além da melhora no desempenho, a arquitetura tornou-se mais preparada para crescer horizontalmente, suportando um número muito maior de integrações simultâneas sem necessidade de aumentar proporcionalmente os recursos computacionais.
Para aplicações onde o gargalo está em operações de rede e acesso a serviços externos, a combinação de Python, Asyncio e Quart mostrou ser uma solução extremamente eficiente, mantendo a simplicidade da linguagem sem abrir mão de escalabilidade e alto desempenho.