Knowledge

Buscas e listagens em MongoDB com stream em chunks: cursor, batchSize e getMore

Em MongoDB, buscas grandes nao voltam "de uma vez": o banco responde em lotes por cursor, e sua API pode transmitir os dados em chunks conforme esses lotes chegam.

27/02/202610 min de leituraKnowledge
Buscas e listagens em MongoDB com stream em chunks: cursor, batchSize e getMore

Resumo executivo

Em MongoDB, buscas grandes nao voltam "de uma vez": o banco responde em lotes por cursor, e sua API pode transmitir os dados em chunks conforme esses lotes chegam.

Ultima atualizacao: 27/02/2026

Resumo executivo

O ponto central que quase sempre gera confusao e este:

  • MongoDB nao devolve uma busca grande inteira em uma unica resposta.
  • O banco abre um cursor e retorna a primeira batelada (firstBatch).
  • Conforme o cliente consome, driver e servidor trocam chamadas getMore para buscar os proximos lotes.

Isso significa que "streaming" em listagens costuma ser um pipeline em duas camadas:

  1. stream em lotes entre banco e aplicacao (cursor + getMore);
  2. stream em chunks entre aplicacao e cliente HTTP (NDJSON/SSE/chunked transfer, por exemplo).

Quando esse modelo e entendido, fica muito mais facil projetar APIs de listagem sem explodir memoria nem latencia.

1) Como o MongoDB realmente entrega resultados

Consultas como find() e aggregate() retornam cursor. Pela documentacao oficial:

  • o lote inicial padrao e o menor entre 101 documentos e 16 MiB;
  • lotes seguintes tambem respeitam limite de 16 MiB;
  • batchSize pode diminuir esse volume por rodada.

Em termos operacionais:

  1. sua API envia find;
  2. MongoDB retorna firstBatch + cursorId;
  3. ao esgotar firstBatch, o driver emite getMore;
  4. o ciclo se repete ate cursor encerrar.

Ou seja, o banco ja trabalha em "chunks" (batches), mesmo antes de voce decidir como responder no HTTP.

2) Busca/listagem em stream: o papel da sua API

A aplicacao pode escolher como expor esses dados para o cliente:

  • toArray() -> junta tudo em memoria e responde no fim;
  • iteracao de cursor (for await) -> responde progressivamente;
  • cursor.stream() -> integra com pipeline Node Readable.

Para listagens grandes, o segundo e o terceiro caminho costumam ser mais robustos, porque permitem comecar a enviar resposta antes do fim da consulta.

3) Exemplo pratico: listagem de pedidos em chunks

Imagine uma rota /orders/export.

  • Colecao: orders
  • Filtro: periodo + status
  • Ordenacao: por _id asc
  • Saida: NDJSON em streaming
tsimport { once } from 'node:events';

app.get('/orders/export', async (req, res) => {
  res.setHeader('Content-Type', 'application/x-ndjson; charset=utf-8');
  res.setHeader('Transfer-Encoding', 'chunked');

  const cursor = db.collection('orders')
    .find({ status: 'paid' })
    .sort({ _id: 1 })
    .batchSize(500);

  try {
    for await (const doc of cursor) {
      const ok = res.write(JSON.stringify(doc) + '\n');
      if (!ok) {
        await once(res, 'drain'); // backpressure do socket HTTP
      }
    }
    res.end();
  } finally {
    await cursor.close();
  }
});

O que acontece por baixo:

  • o driver nao busca um documento por vez no banco;
  • ele busca em lotes (chunks internos) e vai emitindo docs conforme consome o batch;
  • sua API pode escrever cada doc (ou grupos de docs) para o cliente em chunks HTTP.

4) Onde times costumam errar

Erro A: usar toArray() para tudo

Funciona em volume pequeno. Em volume grande, vira consumo alto de memoria e tempo de resposta pior para o primeiro byte.

Erro B: batchSize extremo

  • muito pequeno: excesso de round trips (getMore) e overhead de rede;
  • muito grande: maior uso de memoria e latencia por lote.

batchSize deve ser calibrado por payload medio, rede e perfil de cliente.

Erro C: achar que chunked HTTP resolve query ruim

Se filtro/ordenacao nao usam indice adequado, o gargalo continua no banco. Streaming ajuda entrega progressiva, mas nao corrige plano de execucao ruim.

5) Como isso conversa com streaming de eventos (Kafka/Flink)

Para nao misturar conceitos:

  • listagem/busca: leitura sob demanda, cursor baseado em query;
  • event streaming: fluxo continuo de mudancas/eventos.

MongoDB entra nos dois cenarios:

  • query/list por cursor para APIs de leitura;
  • Change Streams para CDC e publicacao em pipelines de evento.

Os dois usam ideia de processamento progressivo, mas com contratos diferentes de consumo.

Checklist pratico para sua API de listagem MongoDB

  1. Evitar toArray() em rotas de alto volume.
  2. Usar cursor iteravel (for await) ou cursor.stream().
  3. Ajustar batchSize com testes de latencia/memoria.
  4. Implementar backpressure ao escrever chunks HTTP.
  5. Fechar cursor explicitamente em cancelamento/erro.
  6. Medir TTFB, throughput e memoria por endpoint.

Conclusao

Se voce quer explicar corretamente "buscas e listagens em streaming no MongoDB", a formulacao mais precisa e:

  • o MongoDB responde consulta em batches de cursor;
  • o driver recupera proximos lotes via **getMore**;
  • sua API pode transformar esse fluxo em chunks HTTP progressivos.

Pergunta para validar seu endpoint atual: hoje sua rota de listagem com maior volume transmite dados conforme chegam ou so responde quando terminou de carregar tudo em memoria?

Fontes

Leituras relacionadas