bigywater.tech
MongoDB · 59

change stream 的过滤 pipeline 与 fullDocument

change stream 的 update 事件里为什么没有整个文档?updateLookup 拿到的是不是修改后的样子?怎么在服务端过滤?这篇逐个验证事件内容、两种镜像方案和 pipeline 过滤。

fullDocument.n = 2
updateLookup 在两次快速修改后,第一个事件(这次修改后 n=1)取回的 n 值——中间状态丢了
10 → 4
带 $match 的 stream 与不过滤的 stream 收到的事件数
required
前后镜像的取值:没有镜像就报错而不是给过期的文档

各类事件的内容

实测 · 本机执行输出
  {  operationType: 'insert',  documentKey: {   _id: 2  },  fullDocument: {   _id: 2,   status: 'new',   n: 0  } }
  {  operationType: 'update',  documentKey: {   _id: 2  },  updateDescription: {   updatedFields: {    status: 'paid'   },   removedFields: [ 'n' ],   truncatedArrays: []  } }
  {  operationType: 'replace',  documentKey: {   _id: 2  },  fullDocument: {   _id: 2,   status: 'replaced'  } }
  {  operationType: 'delete',  documentKey: {   _id: 2  } }

insert / replace 带完整文档,update 只带 updateDescription(updatedFields、removedFields、truncatedArrays),delete 只有 documentKey。

updateLookup

实测 · 本机执行输出
  update 事件的 updatedFields={'n': 1};updateLookup 取回的 fullDocument.n = 2
  update 事件的 updatedFields={'n': 2};updateLookup 取回的 fullDocument.n = 2
  (两个事件的 fullDocument.n 都是 2:第一个事件对应的 n=1 那个中间状态丢了)

fullDocument: "updateLookup" 在读取事件时去查当前文档,所以连续修改时看到的是最新状态,不是该次修改后的样子。

前后镜像

实测 · 本机执行输出
  update 事件:前镜像 n = 2,后镜像 n = 10  (updatedFields={'n': 10})
  update 事件:前镜像 n = 10,后镜像 n = 20  (updatedFields={'n': 20})
  delete 事件:operationType=delete,documentKey={'_id': 1};fullDocumentBeforeChange = {'_id': 1, 'status': 'new', 'n': 20, 'tags': [1, 2, 3]}(删除前的完整文档)

集合上打开 changeStreamPreAndPostImages 后,required 能拿到精确的前镜像与后镜像,delete 事件也能拿到删除前的文档。

服务端过滤

实测 · 本机执行输出
  不带过滤的 stream 收到 10 个事件;带 $match 的 stream 收到 4 个,documentKey: [0, 3, 6, 9](i % 3 == 0 的 4 个)

watch 的 pipeline 里 $match 在服务端过滤,只把匹配的事件发给客户端。

$project

实测 · 本机执行输出
  收到 2 个事件(只有 vip 的):[{'operationType': 'insert', 'fullDocument': {'kind': 'vip', 'name': 'a'}}, {'operationType': 'insert', 'fullDocument': {'kind': 'vip', 'name': 'c'}}]
  去掉 _id 的 $project:OperationFailure: Executor error during getMore :: caused by :: Encountered an event whose _id field, which contains the resume token, was modified 

可以裁剪字段减少传输,但不能改动 _id(它就是 resume token)。

drop 与 invalidate

实测 · 本机执行输出
  drop 之后收到: ['drop', 'invalidate'];stream.alive = False

集合被删除时 stream 依次收到 drop 和 invalidate 然后关闭,需要重新打开。

分步演示

change stream 的 pipeline 与 fullDocument
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MongoDB 9.0.2(Homebrew 的 mongod、mongos)/ mongosh 2.12.0 / pymongo 4.18.3;每个实验在临时目录里起全新的 mongod(独立 dbpath,端口 9900 以上,只监听 127.0.0.1,WiredTiger 缓存 0.25 GB),需要副本集或分片集群时在本机起多个进程;命令输出来自真实的 mongosh,批量写入、计时与多线程用 pymongo;计时用 time.perf_counter,数字每次运行会有小幅抖动,ObjectId、时间戳等每次运行都不同。单节点副本集;事件内容通过 pymongo 的 watch 读取
    • 没有验证:预镜像占用的存储与过期策略(changeStreamOptions.preAndPostImages.expireAfterSeconds);showExpandedEvents 下的更多事件类型(createIndexes、modify 等);数据库级与整个部署级的 change stream;分片集群上事件的全局顺序;大量事件时 $match 的性能与 CPU 开销
    京ICP备2026067502号-1京公网安备11010502063487号