I have decentralized application working on several nodes (with sharded collections) and I’m using mongo change-streams to gather inserts/updates for statistics etc.
During some testing I caught a case where the change-stream updates are mixed (and reverse).
The test:
- inserts a new document with a field called
size
with value 5 - overwrites the same document with
size
3 (and savingold_size
as 5 in another field) - “delete” the document by updating the
deleted
field (when a document is created, the value is 0 and in this update becomes 1)
In the change stream I expect 3 rows:
- insert operation
- update operation with the
size
/old_size
changed - update operation with the
deleted
field changed
On the other hand, this is what I get: (removed and changed some fields to show only what is relevant)
{'operationType': 'insert', 'clusterTime': Timestamp(1655982833, 4), 'fullDocument': {'_id': 'testobjbar', 'created_at': '1655982833.69878', 'deleted': 0, 'old_size': 0, 'size': 5}, 'ns': {'db': 'test', 'coll': 'col_test'}, 'documentKey': {'_id': 'testobjbar'}}
{'operationType': 'update', 'clusterTime': Timestamp(1655982835, 1), 'ns': {'db': 'test', 'coll': 'col_test'}, 'documentKey': {'_id': 'testobjbar'}, 'updateDescription': {'updatedFields': {'created_at': '1655982835.02931', 'old_size': 5, 'size': 3}, 'removedFields': []}, 'fullDocument': {'_id': 'testobjbar', 'created_at': '1655982835.02931', 'deleted': 1, 'old_size': 0, 'retain_until': 0.0, 'size': 3}}
{'operationType': 'update', 'clusterTime': Timestamp(1655982835, 2), 'ns': {'db': 'test', 'coll': 'col_test'}, 'documentKey': {'_id': 'testobjbar'}, 'updateDescription': {'updatedFields': {'deleted': 1, 'old_size': 0}, 'removedFields': []}, 'fullDocument': {'_id': 'testobjbar', 'created_at': '1655982835.02931', 'deleted': 1, 'old_size': 0, 'retain_until': 0.0, 'size': 3}}
As seen above:
- The document is inserted to db
- In the second row, instead of seeing data related only to the overwrite (size change) we can also observe that the
deleted
flag changed to 1 in thefullDocument
fields, meaning that the row includes the overwrite and the subsequent deletion change that was done right after - Then only the last row in the change stream indicates that
deleted
update occurred as part of theupdatedFields
Some points to observe:
- I don’t make deletion as long as the document is already marked as deleted, and don’t make any operation on deleted documents, so I never expect to see a deleted document twice in a row in the change stream.
- I know that there might be races between
fullDocument
andupdatedFields
so I try to take updates fromupdatedFields
and if they don’t exist we take them fromfullDocument
- Is this a know issue? I didn’t see any other related post
- How can I solve this to have operations in order in the change-stream? or what is a valid technique to identify this?
To compare, if I run the test line by line and wait, I get good results in the change stream:
{'operationType': 'insert', 'clusterTime': Timestamp(1655984572, 3), 'fullDocument': {'_id': 'testobjbar', 'created_at': '1655984572.96063', 'deleted': 0, 'old_size': 0, 'retain_until': 0.0, 'size': 5}, 'ns': {'db': 'test', 'coll': 'col_test'}, 'documentKey': {'_id': 'testobjbar'}}
{'operationType': 'update', 'clusterTime': Timestamp(1655984581, 1), 'ns': {'db': 'test', 'coll': 'col_test'}, 'documentKey': {'_id': 'testobjbar'}, 'updateDescription': {'updatedFields': {'created_at': '1655984581.21706', 'old_size': 5, 'size': 3}, 'removedFields': []}, 'fullDocument': {'_id': 'testobjbar', 'created_at': '1655984581.21706', 'deleted': 0, 'old_size': 5, 'retain_until': 0.0, 'size': 3}}
{'operationType': 'update', 'clusterTime': Timestamp(1655984591, 2), 'ns': {'db': 'test', 'coll': 'col_test'}, 'documentKey': {'_id': 'testobjbar'}, 'updateDescription': {'updatedFields': {'deleted': 1, 'old_size': 0}, 'removedFields': []}, 'fullDocument': {'_id': 'testobjbar', 'created_at': '1655984581.21706', 'deleted': 1, 'old_size': 0, 'retain_until': 0.0, 'size': 3}}