Evite timeouts de exportação: jobs assíncronos, progresso e streaming
Evite timeouts de exportação com jobs assíncronos, indicadores de progresso, paginação e downloads por streaming para relatórios CSV e PDF grandes.

Por que exportações expiram, de forma simples
Uma exportação expira quando o servidor não termina o trabalho antes de um prazo. Esse prazo pode ser definido pelo navegador, por um proxy reverso, pelo servidor da aplicação ou pela conexão com o banco de dados. Para os usuários parece muitas vezes aleatório, porque a exportação às vezes funciona e às vezes falha.
Na tela, normalmente aparece assim:
- Um spinner que nunca termina
- Um download que começa e depois para com um "erro de rede"
- Uma página de erro depois de uma longa espera
- Um arquivo que baixa mas está vazio ou corrompido
Exportações grandes são estressantes porque atingem várias partes do sistema ao mesmo tempo. O banco precisa localizar e montar muitas linhas. O servidor da aplicação precisa formatá-las em CSV ou renderizá-las em PDF. Depois o navegador tem que receber uma resposta grande sem a conexão cair.
Conjuntos enormes de dados são o gatilho óbvio, mas exportações “pequenas” também podem pesar. Joins caros, muitos campos calculados, lookups por linha e filtros sem índices podem transformar um relatório normal em um timeout. PDFs são especialmente arriscados porque envolvem layout, fontes, imagens, quebras de página e muitas vezes consultas extras para coletar dados relacionados.
Tentativas repetidas frequentemente pioram. Quando um usuário atualiza ou clica em Exportar de novo, o sistema pode começar o mesmo trabalho duas vezes. Agora o banco executa consultas duplicadas, o servidor gera arquivos duplicados e você tem um pico justamente quando o sistema já está sobrecarregado.
Se quer evitar timeouts de exportação, trate a exportação como uma tarefa em segundo plano, não como um carregamento de página normal. Mesmo em um construtor no-code como o AppMaster, o padrão importa mais que a ferramenta: trabalhos longos precisam de um fluxo diferente de “clicar no botão, esperar pela resposta.”
Escolha o padrão de exportação certo para seu app
A maioria das falhas de exportação acontece porque o app usa um único padrão para todas as situações, mesmo quando o tamanho dos dados e o tempo de processamento variam bastante.
Uma exportação síncrona simples (usuário clica, servidor gera, download começa) serve quando a exportação é pequena e previsível. Pense em algumas centenas de linhas, colunas básicas, sem formatação pesada e sem muitos usuários fazendo isso ao mesmo tempo. Se consistentemente termina em alguns segundos, o simples costuma ser o melhor.
Para qualquer coisa longa ou imprevisível, use jobs de exportação assíncronos. Isso serve para grandes volumes, cálculos complexos, renderização de PDFs e servidores compartilhados onde uma exportação lenta pode bloquear outras requisições.
Jobs assíncronos são indicados quando:
- Exportações frequentemente demoram mais de 10 a 15 segundos
- Usuários pedem intervalos amplos de datas ou “tudo”\n- Você gera PDFs com gráficos, imagens ou muitas páginas
- Equipes múltiplas exportam em horários de pico
- Você precisa de retries seguros quando algo falha
Downloads por streaming também ajudam quando a exportação é grande mas pode ser produzida em ordem. O servidor começa a enviar bytes imediatamente, o que dá uma sensação de velocidade e evita montar todo o arquivo em memória antes. É ótimo para CSVs longos, mas menos útil se você precisa calcular tudo antes de escrever a primeira linha.
Você pode combinar abordagens: execute um job assíncrono para gerar a exportação (ou preparar um snapshot) e depois faça o streaming do download quando estiver pronto. No AppMaster, uma abordagem prática é criar um registro “Export Requested”, gerar o arquivo em um processo de backend e deixar o usuário baixar o resultado final sem manter a requisição do navegador aberta.
Passo a passo: construir um job de exportação assíncrono
A maior mudança é simples: pare de gerar o arquivo dentro da mesma requisição em que o usuário clica.
Um job de exportação assíncrono divide o trabalho em duas partes: uma requisição rápida que cria um job e um trabalho em segundo plano que monta o arquivo enquanto o app permanece responsivo.
Um fluxo prático em 5 passos
- Capture o pedido de exportação (quem pediu, filtros, colunas selecionadas, formato de saída).
- Crie um registro de job com status (queued, running, done, failed), timestamps e um campo de erro.
- Execute o trabalho pesado em background usando uma fila, um worker agendado ou um processo worker dedicado.
- Grave o resultado em armazenamento (object storage ou file store) e salve a referência de download no registro do job.
- Notifique o usuário quando estiver pronto usando notificação in-app, e-mail ou um canal de mensagens que sua equipe já usa.
Mantenha o registro do job como fonte da verdade. Se o usuário atualizar, trocar de dispositivo ou fechar a aba, você ainda pode mostrar o mesmo status do job e o mesmo botão de download.
Exemplo: um gerente de suporte exporta todos os tickets do último trimestre. Em vez de esperar com uma aba girando, ele vê uma entrada de job passando de queued para done e então o download aparece. No AppMaster, você modela a tabela de jobs no Data Designer, constrói a lógica em segundo plano no Business Process Editor e usa um campo de status para guiar o estado da UI.
Indicadores de progresso em que os usuários realmente confiam
Um bom indicador de progresso reduz ansiedade e evita que as pessoas cliquem em Exportar cinco vezes. Também ajuda a prevenir timeouts indiretamente, porque os usuários estão mais dispostos a esperar quando o app mostra movimento real.
Mostre progresso em termos compreensíveis. Percentual sozinho é frequentemente enganoso, então combine com algo concreto:
- Etapa atual (Preparando dados, Buscando linhas, Montando arquivo, Fazendo upload, Pronto)
- Linhas processadas do total (ou páginas processadas)
- Hora de início e última atualização
- Tempo estimado restante (só se for razoavelmente estável)
Evite falsa precisão. Se você não conhece o total ainda, não mostre 73%. Use marcos primeiro e depois mude para percentual quando souber o denominador. Um padrão simples é 0% a 10% para preparação, 10% a 90% baseado em linhas processadas e 90% a 100% para finalização do arquivo. Para PDFs com tamanhos de página variáveis, acompanhe verdades menores como “registros renderizados” ou “seções concluídas”.
Atualize com frequência suficiente para dar sensação de vida, mas não tanto que você sobrecarregue o banco ou a fila. Uma abordagem comum é gravar progresso a cada 1 a 3 segundos, ou a cada N registros (como a cada 500 ou 1.000 linhas), o que ocorrer menos frequentemente. Também registre um timestamp de heartbeat leve para que a UI possa dizer "Ainda trabalhando" mesmo quando o percentual não se move.
Dê controle aos usuários quando as coisas demorarem mais do que o esperado. Permita cancelar uma exportação em execução, iniciar uma nova sem perder a primeira e ver o histórico de exportações com status (Queued, Running, Failed, Ready) junto com uma mensagem de erro curta.
No AppMaster, um registro típico fica assim: ExportJob (status, processed_count, total_count, step, updated_at). A UI consulta esse registro e mostra progresso honesto enquanto o job assíncrono gera o arquivo em segundo plano.
Paginação e filtros para manter o trabalho limitado
A maioria dos timeouts acontece porque a exportação tenta fazer tudo de uma vez: linhas demais, colunas demais, joins demais. A correção mais rápida é manter o trabalho limitado para que os usuários exportem um slice menor e mais claro de dados.
Comece pelo objetivo do usuário. Se alguém precisa de “faturas do mês passado que falharam”, não coloque por padrão “todas as faturas de sempre”. Faça filtros parecerem normais, não algo que dá trabalho. Um intervalo de datas simples com padrões sensíveis (últimos 7 ou 30 dias), um ou dois status chave, busca opcional ou seleção de cliente/equipe e uma prévia de contagem quando possível frequentemente reduzem o dataset em 90%.
No servidor, leia os dados em pedaços usando paginação. Isso mantém a memória estável e te dá checkpoints naturais para progresso. Sempre use uma ordenação estável ao paginar (por exemplo, order by created_at, depois id). Sem isso, novas linhas podem entrar em páginas anteriores e você perde ou duplica registros.
Os dados mudam durante exportações longas, então decida o que “consistente” significa. Uma abordagem simples é registrar um timestamp de snapshot quando o job começa e exportar apenas linhas até esse timestamp. Se precisar de consistência estrita, use leitura consistente ou uma transação onde o banco suporte. Em uma ferramenta no-code como o AppMaster, isso mapeia bem para um business process: valide filtros, defina o snapshot time e então itere pelas páginas até não sobrar mais nada para buscar.
Streaming de downloads sem sobrecarregar o servidor
Streaming significa começar a enviar o arquivo ao usuário enquanto você ainda o está gerando. O servidor não precisa montar todo o CSV ou PDF em memória primeiro. É uma das maneiras mais confiáveis de evitar timeouts quando arquivos crescem.
Streaming não torna consultas lentas instantâneas. Se o trabalho do banco leva cinco minutos antes do primeiro byte, a requisição ainda pode expirar. A correção usual é combinar streaming com paginação: busque um chunk, escreva, e continue.
Para manter a memória baixa, escreva conforme avança. Gere um chunk (por exemplo, 1.000 linhas de CSV ou uma página de PDF), escreva na resposta e faça flush para que o cliente continue recebendo dados. Evite coletar linhas em um grande array “só para ordenar depois”. Se precisar de ordenação estável, ordene no banco.
Cabeçalhos, nomes e tipos de conteúdo
Use cabeçalhos claros para que navegadores e apps móveis tratem o download corretamente. Defina o content-type certo (como text/csv ou application/pdf) e um nome de arquivo seguro. Nomes devem evitar caracteres especiais, ser curtos e incluir timestamp se os usuários exportam o mesmo relatório várias vezes.
Retomar e downloads parciais
Decida cedo se suporta resume. Streaming básico muitas vezes não suporta byte-range resume, especialmente para PDFs gerados. Se for suportar, você deve lidar com requests Range e gerar saída consistente para o mesmo job.
Antes de lançar, certifique-se de:
- Enviar headers antes de escrever o corpo, então escrever em chunks e dar flush
- Manter tamanhos de chunk constantes para que a memória se mantenha plana sob carga
- Usar ordenação determinística para que os usuários confiem na saída
- Documentar se o resume é suportado e o que acontece se a conexão cair
- Adicionar limites do lado servidor (max rows, max time) e retornar um erro amigável quando atingidos
Se construir exportações no AppMaster, mantenha a lógica de geração em um fluxo backend e faça o streaming do lado servidor, não do navegador.
CSVs grandes: táticas práticas
Para CSVs grandes, pare de tratar o arquivo como um blob único. Construa como um loop: leia um slice de dados, escreva linhas, repita. Isso mantém a memória estável e torna retries mais seguros.
Escreva o CSV linha a linha. Mesmo gerando a exportação em um job assíncrono, evite “coletar todas as linhas e então stringificar”. Mantenha um writer aberto e anexe cada linha assim que estiver pronta. Se seu stack suportar, use um cursor do banco ou paginação para nunca carregar milhões de registros de uma vez.
Correção do CSV importa tanto quanto velocidade. Um arquivo pode parecer OK até alguém abrir no Excel e metade das colunas moverem.
Regras de CSV que evitam arquivos quebrados
- Sempre escape vírgulas, aspas e quebras de linha (envolva todo o campo em aspas e duplique qualquer aspas interna)
- Saída em UTF-8 e teste nomes não-ingleses de ponta a ponta
- Use uma linha de cabeçalho estável e mantenha a ordem de colunas fixa entre execuções
- Normalize datas e decimais (escolha um formato e mantenha)
- Evite fórmulas se os dados puderem começar com =, +, - ou @
Performance costuma morrer no acesso a dados, não na escrita. Cuidado com N+1 lookups (por exemplo, carregar cada cliente dentro de um loop). Busque dados relacionados em uma única query ou preload o que precisar antes, então escreva linhas.
Quando exportações forem realmente enormes, divida de propósito. Uma abordagem prática é um arquivo por mês, por cliente ou por tipo de entidade. Uma exportação de “5 anos de pedidos” pode virar 60 arquivos mensais, cada um gerado independentemente, assim um mês lento não bloqueia tudo.
Se estiver usando AppMaster, modele o dataset no Data Designer e rode a exportação como um business process em background, escrevendo linhas enquanto pagina pelos registros.
PDFs grandes: mantenha previsibilidade
Geração de PDF costuma ser mais lenta que CSV porque é CPU-heavy. Você não está apenas movendo dados, está posicionando elementos, fontes, desenhando tabelas e muitas vezes redimensionando imagens. Trate PDF como um job em background com limites claros, não uma resposta rápida.
Escolhas de template decidem se uma exportação de 2 minutos vira 20 minutos. Layouts simples vencem: menos colunas, poucas tabelas aninhadas e quebras de página previsíveis. Imagens são uma das formas mais rápidas de desacelerar, especialmente se forem grandes, alta DPI ou buscadas remotamente durante a renderização.
Decisões de template que normalmente melhoram velocidade e confiabilidade:
- Use uma ou duas fontes e evite cadeias de fallback pesadas
- Mantenha cabeçalhos e rodapés simples (evite gráficos dinâmicos em cada página)
- Prefira ícones vetoriais a grandes raster images
- Limite layouts “auto fit” que re-mensuram texto várias vezes
- Evite transparência complexa e sombras
Para grandes exportações, renderize em lotes. Gere uma seção ou um pequeno range de páginas por vez, grave em um arquivo temporário e então monte o PDF final. Isso mantém memória estável e torna retries mais seguros se um worker cair no meio. Também combina bem com jobs assíncronos e progresso que avança em passos significativos (por exemplo: “Preparando dados”, “Renderizando páginas 1–50”, “Finalizando arquivo”).
Questione também se PDF é realmente o que o usuário precisa. Se eles querem principalmente linhas e colunas para análise, ofereça CSV além do “Exportar PDF”. Você pode gerar um PDF resumo menor para relatórios e manter o conjunto completo em CSV.
No AppMaster, isso se encaixa naturalmente: rode a geração de PDF como um job em background, reporte progresso e entregue o arquivo pronto como download quando o job completar.
Erros comuns que causam timeouts
Falhas de exportação geralmente não são misteriosas. Algumas escolhas funcionam bem com 200 linhas e quebram com 200.000.
Erros mais comuns:
- Executar toda a exportação dentro de uma requisição web. O navegador espera, o worker do servidor fica ocupado e qualquer consulta lenta ou arquivo grande empurra você além dos limites de tempo.
- Mostrar progresso com base em tempo em vez de trabalho. Um temporizador que corre até 90% e então trava faz usuários atualizarem, cancelarem ou iniciarem outra exportação.
- Ler todas as linhas na memória antes de escrever o arquivo. Fácil de implementar e um jeito rápido de atingir limites de memória.
- Manter transações de banco longas ou ignorar locks. Queries de exportação podem bloquear writes ou serem bloqueadas por writes, e a lentidão se espalha pelo app.
- Permitir exportações ilimitadas sem limpeza. Cliques repetidos empilham jobs, enchem o armazenamento e deixam arquivos antigos por aí.
Um exemplo concreto: um líder de suporte exporta todos os tickets dos últimos dois anos e clica duas vezes porque nada parece acontecer. Agora duas exportações idênticas competem pelo mesmo banco, ambas geram arquivos enormes em memória e ambas expiram.
Se estiver construindo isso em uma ferramenta no-code como AppMaster, as mesmas regras valem: mantenha exportações fora do caminho da requisição, acompanhe progresso por linhas processadas, escreva saída conforme avança e coloque limites simples sobre quantas exportações um usuário pode rodar ao mesmo tempo.
Verificações rápidas antes de liberar
Antes de liberar um recurso de exportação em produção, faça uma checagem rápida com a mentalidade de tempo. Trabalho longo acontece fora da requisição, usuários veem progresso honesto e o servidor nunca tenta fazer tudo de uma vez.
Uma checklist rápida:
- Exportações grandes rodem como jobs em background (as pequenas podem ser síncronas se terminarem rápido)
- Usuários vejam estados claros como queued, running, done ou failed, com timestamps
- Dados sejam lidos em chunks com ordenação estável (por exemplo, created time mais um tie-breaker por ID)
- Arquivos finalizados possam ser baixados depois sem reexecutar a exportação, mesmo que o usuário feche a aba
- Haja um limite e plano de limpeza para arquivos antigos e histórico de jobs (deleção por idade, max jobs por usuário, limites de armazenamento)
Um teste de sanidade é tentar seu pior caso: exporte o maior intervalo que você permite enquanto outra pessoa está adicionando registros. Se vir duplicatas, linhas faltando ou progresso travado, sua ordenação ou chunking não é estável.
Se construir sobre AppMaster, essas checagens mapeiam para peças reais: um processo em background no Business Process Editor, um registro de job no banco e um campo de status que sua UI consulta.
Faça a falha parecer segura. Um job falhado deve manter sua mensagem de erro, permitir retry e evitar criar arquivos parciais que pareçam “prontos” mas estão incompletos.
Exemplo: exportando anos de dados sem travar o app
Um gerente de operações precisa de duas exportações todo mês: um CSV com os últimos 2 anos de pedidos para análise e um conjunto de PDFs mensais de faturas para contabilidade. Se seu app tentar montar qualquer um durante uma requisição web normal, você vai bater nos limites de tempo.
Comece limitando o trabalho. A tela de exportação pede um intervalo de datas (padrão: últimos 30 dias), filtros opcionais (status, região, representante de vendas) e uma escolha clara de colunas. Essa mudança costuma transformar um problema de 2 anos e 2 milhões de linhas em algo gerenciável.
Quando o usuário clicar em Exportar, o app cria um registro Export Job (type, filters, requested_by, status, progress, error_text) e o coloca em uma fila. No AppMaster, isso é um modelo no Data Designer mais um Business Process que roda em background.
Enquanto o job roda, a UI mostra um status confiável: queued, processing (por exemplo, 3 de 20 chunks), generating file, ready (botão de download) ou failed (erro claro e opção de retry).
Chunking é o detalhe chave. O job CSV lê pedidos em páginas (por exemplo, 50.000 por vez), grava cada página na saída e atualiza progresso após cada chunk. O job de PDF faz o mesmo por lote de faturas (por exemplo, um mês por vez), assim um mês lento não bloqueia tudo.
Se algo quebrar (filtro inválido, permissão ausente, erro de armazenamento), o job é marcado como Failed com uma mensagem curta que o usuário possa agir: "Não foi possível gerar faturas de março. Tente novamente ou contate o suporte com Job ID 8F21." Um retry reaproveita os mesmos filtros para que o usuário não precise recomeçar.
Próximos passos: torne exportações um recurso padrão, não um incêndio
A maneira mais rápida de prevenir timeouts a longo prazo é parar de tratar exportações como um botão pontual e torná-las um recurso padrão com um padrão repetível.
Escolha uma abordagem por padrão e use-a em todos os lugares: um job assíncrono gera o arquivo em background e o usuário recebe a opção de download quando estiver pronto. Essa única decisão remove a maioria dos “funcionou nos testes” surpresas, porque a requisição do usuário não precisa esperar pelo arquivo completo.
Facilite para as pessoas encontrarem o que já geraram. Uma página de histórico de exportações (por usuário, workspace ou conta) reduz exports repetidos, ajuda suporte a responder “onde está meu arquivo?” e dá um lugar natural para mostrar status, erros e expiração.
Se construir esse padrão no AppMaster, ajuda o fato de a plataforma gerar código fonte real e suportar lógica backend, modelagem de banco e UI web/móvel num só lugar. Para times que querem entregar jobs assíncronos confiáveis rápido, o appmaster.io é frequentemente usado para montar a tabela de jobs, o processo em background e a UI de progresso sem ter que fio a fio montar tudo do zero.
Depois meça o que realmente dói. Acompanhe queries lentas, tempo gasto gerando CSV e tempo de render PDF. Você não precisa de observabilidade perfeita para começar: logar duração e contagens de linhas por exportação já mostra rápido qual relatório ou combinação de filtros é o problema real.
Trate exportações como qualquer outro recurso do produto: consistente, mensurável e fácil de suportar.
FAQ
Uma exportação expira quando o trabalho não termina antes de um prazo definido em algum ponto do caminho da requisição. Esse limite pode vir do navegador, de um proxy reverso, do servidor da aplicação ou da conexão com o banco de dados, então pode parecer aleatório mesmo quando a causa raiz é carga consistente ou consultas lentas.
Use uma exportação síncrona simples apenas quando ela realmente terminar em poucos segundos e o tamanho dos dados for previsível. Se as exportações frequentemente demoram mais de 10–15 segundos, envolvem grandes intervalos de datas, cálculos pesados ou PDFs, passe para um job assíncrono para que a requisição do navegador não precise ficar aberta.
Crie primeiro um registro de job, depois execute o trabalho pesado em segundo plano e, por fim, permita que o usuário baixe o arquivo pronto. No AppMaster, é comum ter um modelo ExportJob no Data Designer e um Business Process backend que atualiza status, campos de progresso e a referência do arquivo armazenado enquanto roda.
Monitore trabalho real, não tempo decorrido. Uma abordagem prática é armazenar campos como step, processed_count, total_count (quando conhecido) e updated_at, e fazer o UI consultar esses campos para mostrar mudanças claras de estado, evitando que usuários sintam que está travado e spamem o botão de exportar.
Torne a requisição de exportação idempotente e mantenha o registro do job como fonte da verdade. Se o usuário clicar de novo, mostre o job já em execução (ou bloqueie duplicatas com os mesmos filtros) em vez de iniciar o mesmo trabalho caro duas vezes.
Leia e escreva em blocos para manter a memória estável e obter pontos de verificação naturais. Use paginação estável com uma ordenação determinística (por exemplo, por created_at e depois id) para não perder ou duplicar registros enquanto os dados mudam durante uma exportação longa.
Registre um timestamp de snapshot quando o job começa e exporte apenas linhas até esse momento para que a saída não "ande" enquanto roda. Se precisar de garantias mais fortes, use leituras consistentes ou transações suportadas pelo banco de dados, mas comece com uma regra de snapshot clara que a maioria dos usuários entenda.
Streaming ajuda quando você pode produzir saída em ordem e começar a enviar bytes cedo, especialmente para CSVs grandes. Não vai consertar consultas lentas que levam minutos antes do primeiro byte e ainda pode expirar se nada for escrito por muito tempo, então streaming funciona melhor combinado com paginação que escreve chunks continuamente.
Escreva linhas conforme avança e siga escaping estrito de CSV para que o arquivo não quebre no Excel ou em outras ferramentas. Mantenha codificação consistente (geralmente UTF-8), cabeçalhos e ordem de colunas estáveis, e evite lookups por linha que transformem uma exportação em milhares de consultas extras.
Geração de PDF é mais pesada porque envolve layout, fontes, imagens e quebras de página, então trate-a como um job em segundo plano com limites claros. Mantenha templates simples, evite imagens grandes ou remotas durante a renderização e reporte progresso em passos significativos para que os usuários saibam que está funcionando.


