Para responder a alterações no banco de dados em tempo real, assine os change streams do MongoDB. Este tópico explica o que são change streams, como utilizá-los e apresenta as melhores práticas.
O que é um change stream?
Um change stream converte eventos de alteração do banco de dados em um fluxo em tempo real. Os clientes podem assinar esse fluxo e receber notificações imediatas sempre que dados forem inseridos, atualizados ou excluídos. Os cenários típicos incluem:
Sincronização de dados entre clusters: replique dados incrementalmente entre clusters MongoDB.
Auditoria de operações: rastreie operações de alto risco, como a exclusão de um banco de dados ou coleção.
Arquitetura orientada a eventos: envie alterações para sistemas downstream para análises em tempo real, atualizações de cache ou notificações.
Limites
Tipos de instância suportados: instância de replica set ou instância de sharded cluster.
Configurar change stream
Escutar eventos DDL adicionais
Pré-requisitos
MongoDB 6.0 ou posterior (Guia de upgrade).
Procedimento
-
Execute o comando
watchcomshowExpandedEvents: true:// mongo shell or mongosh v1.x cursor = db.getSiblingDB("test").watch([], { showExpandedEvents: true // Enable listening for more DDL events } ); cursor.next();
Verificar resultados
Em uma nova janela SQL, execute uma instrução de alteração, como
db.createCollection("myCollection1").-
Verifique na janela SQL original a saída relacionada à instrução executada.
mg xxx test> cursor = db.getSiblingDB("test").watch([], ... { ... showExpandedEvents: true // Enable more DDL event listening ... } ... ); ... cursor.next(); Warning: If there are no documents in the batch, next will block. Use tryNext if you want to check if there are any documents without waiting. { _id: { _data: '8268777614000000012B042C0100296E5A100434919C64814940F3A61F9D97A3F60D5C463C6Fxxx03C63726561746500466F70657274696F6E4465737372697074696F6E0046466964496E6465787800461E76002B04466B657990046E5F6964002B02003C6E616D65003C5F69645F000000004' }, operationType: 'create', clusterTime: Timestamp({ t: 1752659476, i: 1 }), collectionUUID: UUID('34919c64-8149-40f3-a61f-9xxx'), wallTime: ISODate('2025-07-16T09:51:16.043Z'), ns: { db: 'test', coll: 'myCollection1' }, operationDescription: { idIndex: { v: 2, key: { _id: 1 }, name: '_id_' } } }
Ativar pre-image
No MongoDB, uma pre-image é um snapshot completo de um documento antes da modificação ou exclusão. Ela registra os valores originais antes que a alteração ocorra.
Pré-requisitos
MongoDB 6.0 ou posterior (Guia de upgrade).
Procedimento
-
Ative a pre-image no nível do banco de dados:
db.adminCommand({ setClusterParameter: { changeStreamOptions: { preAndPostImages: { expireAfterSeconds: "off" } // "off" uses oplog retention period } } }) -
Ative a pre-image no nível da coleção:
NotaPara ativar a pre-image para todas as coleções de um banco de dados, primeiro ative-a no nível do banco de dados. Em seguida, ative-a separadamente para cada coleção desse banco.
Modifique uma coleção existente
db.runCommand({ collMod: "myCollection", changeStreamPreAndPostImages: { enabled: true } })Especifique ao criar uma nova coleção
db.createCollection("myCollection", { changeStreamPreAndPostImages: { enabled: true }}) -
Crie um listener de change stream (especifique as opções de pre-image):
// Create a listener on the target collection cursor = db.getCollection("myCollection").watch([], { fullDocument: 'required', // or 'whenAvailable' fullDocumentBeforeChange: 'required' // or 'whenAvailable' } ) cursor.next();required: o servidor deve retornar a pre/post-image. Caso contrário, retorna um erro.whenAvailable: o servidor tenta retornar a imagem, mas não garante seu retorno.
Verificar resultados
-
Verifique se a configuração no nível do banco de dados está ativada:
db.adminCommand( { getClusterParameter: "changeStreamOptions" } )Se bem-sucedido, o comando retorna uma saída semelhante à seguinte:
test> db.adminCommand( { getClusterParameter: "changeStreamOptions" } ) { clusterParameters: [ { _id: 'changeStreamOptions', clusterParameterTime: Timestamp({ t: 1752655937, i: 1 }), preAndPostImages: { expireAfterSeconds: Long('100') } } ], ok: 1, '$clusterTime': { clusterTime: Timestamp({ t: 1752656717, i: 1 }), signature: { hash: Binary.createFromBase64('xxx=', 0), keyId: Long('xxx') } }, operationTime: Timestamp({ t: 1752656717, i: 1 }) } -
Verifique a configuração da coleção:
db.getCollectionInfos({name: "myCollection"}) // or db.runCommand({listCollections: 1})Saída esperada: encontre um campo semelhante a
"options" : { "changeStreamPreAndPostImages" : { "enabled" : true } }no documento retornado.test> db.getCollectionInfos({name: "myCollection"}) [ { name: 'myCollection', type: 'collection', options: { changeStreamPreAndPostImages: { enabled: true } }, info: { readOnly: false, uuid: UUID('55ed1b7c-7575-4ba3-8afa-xxx') }, idIndex: { v: 2, key: { _id: 1 }, name: '_id_' } } ] Em outra janela do mongosh, atualize um documento em
myCollection.-
Observe o evento retornado pelo
cursor. Ele deve incluir o campofullDocumentBeforeChange(o documento antes da alteração).... cursor.next(); { _id: { _data: '8268786B15000000012B042C0100296E5A100455ED1B7C75754BA38AFA0C5B56A360BC463C6F7065726174696F6E54797065003C7570646174650046646F63756D656E744B65790046xxx697CDDF6EE279xxx' }, operationType: 'update', clusterTime: Timestamp({ t: 1752722197, i: 1 }), wallTime: ISODate('2025-07-17T03:16:37.626Z'), fullDocument: { _id: ObjectId('6878697cddfxxx'), name: 'test', count: 111 }, ns: { db: 'test', coll: 'myCollection' }, documentKey: { _id: ObjectId('6878697cddfxxx') }, updateDescription: { updatedFields: { count: 111 }, removedFields: [], truncatedArrays: [] }, fullDocumentBeforeChange: { _id: ObjectId('6878697cddf6xxx'), name: 'test' } }
Para mais informações, consulte Change Streams with Document Pre- and Post-Images.
Ativar post-image
No MongoDB, uma post-image é um snapshot completo de um documento após a ocorrência de uma alteração. Ela registra o conteúdo integral do documento depois da mudança.
Pré-requisitos
MongoDB 3.6 ou posterior (Guia de upgrade).
Procedimento
Ao executar o comando watch, defina fullDocument: 'updateLookup'.
cursor = db.getSiblingDB("test").myCollection.watch([],
{
fullDocument: 'updateLookup'
}
);
cursor.next();
Verificar resultados
Em outra janela do mongosh, insira ou atualize um documento em
myCollection.-
Observe o evento retornado pelo
cursor. Ele deve incluir o campofullDocument(o documento após a alteração).xxx [primary] test> cursor = db.getSiblingDB("test").myCollection.watch([], ... { ... fullDocument: 'updateLookup' ... } ... ); ... cursor.next(); [... { _id: { _data: 'xxx100296E5A10040D6C3FBC28484F08240555576066945463C6F7065726174696F6E54797065003C757064617465500046646F63756D656E744B6579005F6964004B657390046645F696400646881ADE741E7D4B638ED7CA1000004' }, operationType: 'update', clusterTime: Timestamp({ t: 1753329166, i: 1 }), wallTime: ISODate('2025-07-24T03:52:46.966Z'), fullDocument: { _id: ObjectId('xxx8ed7ca1'), name: 'test1', age: 12, count: 2222 }, ns: { db: 'test', coll: 'myCollection' }, documentKey: { _id: ObjectId('xxx7ca1') }, updateDescription: { updatedFields: { count: 2222 }, removedFields: [], truncatedArrays: [] } }]
O documento completo retornado pode estar vazio ou não refletir um estado preciso em um ponto específico no tempo. Por exemplo:
Se o mesmo documento for atualizado várias vezes em rápida sucessão, o evento de alteração da primeira atualização pode retornar o estado do documento após a conclusão da atualização mais recente.
Se um documento for atualizado e imediatamente excluído, seu evento de alteração mostrará um campo fullDocument vazio, pois o documento pós-alteração não existe mais.
Para mais informações, consulte Lookup Full Document for Update Operations.
Lidar com eventos de alteração muito grandes (>16 MB)
Pré-requisitos
MongoDB 7.0 ou posterior (Guia de upgrade).
Procedimento
Inclua o estágio $changeStreamSplitLargeEvent no pipeline do comando watch():
myChangeStreamCursor = db.myCollection.watch(
[ { $changeStreamSplitLargeEvent: {} } ], // Add split stage
{
fullDocument: "required",
fullDocumentBeforeChange: "required"
}
)
Verificar resultados
Realize uma operação que gere um evento de alteração maior que 16 MB (por exemplo, atualizar um documento contendo um array muito grande).
Observe que o fluxo de eventos retornado é dividido em vários eventos
fragmentconsecutivos, terminando com um eventofragmentfinal.
Reduzir a sobrecarga de armazenamento de pre-images
Por padrão, as pre-images expiram junto com o oplog. Defina um tempo de expiração menor para economizar espaço:
Se você definir expireAfterSeconds com um valor muito curto e seu consumidor downstream não conseguir acompanhar o processamento, poderá ocorrer um erro ChangeStreamHistoryLost (porque a pre-image expira cedo demais). Para detalhes, consulte Change Streams with Document Pre- and Post-Images.
db.adminCommand({
setClusterParameter: {
changeStreamOptions: {
preAndPostImages: { expireAfterSeconds: 100 } // Unit: seconds
}
}
})
Melhores práticas para change streams
-
Use pre-images e post-images com cautela:
Ativar
fullDocumentBeforeChange(pre-image) efullDocument(post-image) aumenta a sobrecarga de armazenamento (na coleçãoconfig.system.preimages) e a latência das requisições.Ative esses recursos apenas quando sua aplicação realmente precisar do conteúdo completo do documento antes e depois de uma alteração.
-
Considerações importantes para implantações de sharded cluster:
Sempre crie listeners de change stream no mongos para garantir a ordenação global dos eventos.
Sob altas cargas de escrita, os change streams podem se tornar um gargalo, pois o
mongosprecisa ordenar e mesclar eventos provenientes dos shards.Uma distribuição desigual de escritas entre os shards (por exemplo, devido a uma chave de shard mal projetada) aumenta significativamente a latência do change stream.
-
Evite updateLookup:
O
updateLookupexecuta uma consultafindOneseparada para cada evento de atualização, o que é ineficiente.Em sharded clusters, as operações de
moveChunkagravam ainda mais a latência doupdateLookup.
-
Previna interrupções no change stream:
-
⚠️ Os cenários a seguir tornam um cursor de change stream inválido (
operationType: "invalidate") ou geram erros.Atraso no consumidor downstream: o consumidor processa eventos mais lentamente do que são gerados, fazendo com que o
resumeTokenfique fora da janela do oplog.**resumeToken inválido: uso de um
resumeTokendesatualizado cujo timestamp não está mais presente no oplog.Impacto de failover: após um failover, o oplog do novo nó primário pode não conter o
resumeTokenoriginal.Alterações de metadados: operações como
drop,renameedropDatabasepodem disparar um evento deinvalidate.Expiração da pre-image: definir
expireAfterSecondscom um valor muito curto enquanto o consumo é lento causa perda de pre-images.
-
Estratégias de mitigação:
Monitore a latência do change stream.
Garanta que a janela do oplog seja suficientemente grande.
Implemente lógica robusta de tratamento de erros e recuperação: capture eventos de
invalidate, registre o últimoresumeTokenválido e recrie o listener.Defina um valor razoável para
expireAfterSeconds.
-
-
Estratégia de seleção de escopo:
-
Change stream único vs. múltiplos change streams no nível da coleção:
Stream único (nível de banco de dados/instância): menor sobrecarga de recursos (busca de oplog single-threaded), mas exige filtragem e despacho no downstream. Sob alto volume de eventos, o
mongospode se tornar um gargalo.Múltiplos streams no nível da coleção: podem aproveitar a filtragem no lado do servidor para reduzir o tráfego de rede e oferecer melhor concorrência. No entanto, muitos streams aumentam a contenção por leituras do
oploge o consumo de recursos.
Recomendação: teste com base na sua carga de trabalho (volume de eventos, número de coleções) e escolha a abordagem ideal. Geralmente, use streams dedicados para um pequeno número de coleções altamente ativas e utilize streams no nível de banco de dados/instância com filtragem downstream para muitas coleções com baixa atividade.
FAQ
**1. Por que os slow logs de change stream sempre mostram
COLLSCAN?**Esse comportamento é normal e esperado. Nenhuma otimização é necessária. Um cursor de change stream lê, em última instância, de
local.oplog.rs(o único local que registra todas as modificações da instância). Essa coleção não possui índices, portanto, sempre realiza umCOLLSCAN. Isso não pode ser evitado e não há margem para otimização.Se você usar
COLLSCANcomo palavra-chave para filtrar slow logs em busca de consultas a otimizar, filtre também a palavra-chave$changeStream.Investigue slow logs relacionados a change streams apenas se encontrar problemas de desempenho (por exemplo, aumento de latência no consumo downstream).
2. Por que, em uma instância com shard, vejo cursores de change stream em shards diferentes do shard primário ao escutar uma coleção sem shard?
Isso prepara o sistema para possíveis operações de shardCollection. Você pode converter uma coleção sem shard com um listener ativo em uma coleção com shard a qualquer momento, distribuindo seus dados por todos os shards (semelhante ao movePrimary). Criar cursores de change stream em outros shards antecipadamente lida com esse cenário. Normalmente, esses cursores em outros shards não retornam eventos de alteração e incorrem em sobrecarga mínima de desempenho.
Da mesma forma, para lidar com potenciais operações de
addShard/removeShard, quando você configura um listener em uma instância com shard, o mongos também cria os cursores correspondentes no Config Server.3. Por que o slow log de um cursor de change stream aparece no nó primário mesmo eu tendo especificado readPreference:secondary ao criar o cursor?
Um cursor fica "fixado" em um nó específico após a criação e não migra automaticamente quando as funções dos nós mudam. Uma vez criado, um cursor de change stream permanece fixo em um nó mongod específico (primário ou secundário) e continua consumindo via getMore. Após eventos como troca de primário/secundário, redimensionamento de instância ou migração, um cursor originalmente em um secundário pode acabar no primário.
Se você não quiser que essa carga persista no nó primário, limpe os cursores de change stream relevantes usando
killCursors. Sua lógica de consumidor downstream pode então restaurar o cursor para um nó secundário usando oresumeTokenereadPreference. Esta operação não causa perda ou interrupção de eventos.db.runCommand( { killCursors: <collection>, cursors: [ <cursor id1>, ... ], comment: <any> } ) db.getSiblingDB("<testDB>").runCommand( { killCursors: "<testColl>", cursors: [NumberLong("2452840976689696187") ] } )4. Por que vejo muitos slow logs sobre change streams no mongos com duração de 1000 ms?
2020-08-26T04:34:45.045+0000 I COMMAND [conn21283] command altconfig-b2b-perf.oplog command: getMore \{ getMore: 3513599116181216748, collection: "oplog", $db: "altconfig-b2b-perf", $clusterTime: { clusterTime: Timestamp(1598416483, 1), signature: { hash: BinData(0, EC3841EB1FB7A34F897688BB5983E32E2ADF6763), keyId: 6855385090000683010 } }, lsid: \{ id: UUID("72864d11-c0f8-48bb-823e-de5f18a9c409") } } originatingCommand: \{ find: "oplog", filter: { timestamp: { $gte: new Date(1597895390009) } }, tailable: true, awaitData: true, $db: "altconfig-b2b-perf", $clusterTime: \{ clusterTime: Timestamp(1597895487, 1), signature: { hash: BinData(0, AAAE33D5688F935C70469CBDB8EB1F6882749A40), keyId: 6855385090000683010 } }, lsid: \{ id: UUID("72864d11-c0f8-48bb-823e-de5f18a9c409") } } nShards:1 cursorid:3513599116181216748 numYields:0 nreturned:0 reslen:237 protocol:op_msg 1000msEste é um comportamento normal em versões do MongoDB anteriores à 6.0. Indica um timeout de espera, não um gargalo de desempenho. Em arquiteturas de sharded cluster, o mongos cria cursores de change stream nos shards com
tailable:true,awaitData:trueemaxTimeMS:1000para retornar o máximo possível de eventos de alteração dentro de 1000 ms.Nas versões principais anteriores à 6.0, o mongos registrava esses slow logs de 1000 ms, o que poderia enganar os usuários. O MongoDB otimizou esse comportamento. Para detalhes, consulte SERVER-50559.
-