Skip to content

Instantly share code, notes, and snippets.

@kapetan

kapetan/mongo.js Secret

Last active August 29, 2015 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kapetan/652ebeac2be444f7bae6 to your computer and use it in GitHub Desktop.
Save kapetan/652ebeac2be444f7bae6 to your computer and use it in GitHub Desktop.
Identity crisis

There is a strange document in our production database, which isn't possible to find itself. This was tried with mongodb version 2.4 and 2.6.

Mongojs

With mongojs it can be reproduced with the following code

var mongojs = require('mongojs');

// production is the name of my local production database copy
var db = mongojs('production', ['settings']);

var raise = function(err) {
	if(err) throw err;
};

// This is the actual id of the document in the production database
var id = new mongojs.ObjectId('5158a0272f191a8d64ec25d3');

db.settings.findOne({ _id: id }, function(err, setting) {
	raise(err);
	if(!setting) raise(new Error('Setting missing'));

	db.settings.findOne(setting, function(err, self) {
		raise(err);

		console.log(self); // <-- self is null
	});
});

Mongo shell without serialization

Doing the equivalent queries in the mongodb shell actually finds the document.

// self.js
var id = ObjectId("5158a0272f191a8d64ec25d3");

var setting = db.settings.findOne({ _id: id });
var self = db.settings.findOne(setting);

print(tojson(self)); // <-- self is the correct object

It can be run with the following command. production refers to the database name (can be an URL to a mongohq database).

$mongo production ./self.js

Mongo shell with serialization

But if we use the shell to first store the document in a file and use that file to find the document it fails.

First save the document

// self.js
var id = ObjectId("5158a0272f191a8d64ec25d3");
var setting = db.settings.findOne(setting);

print(tojson(setting));

And run it with

$mongo production ./self.js > doc.js

doc.js content should be something like

MongoDB shell version: 2.6.1
connecting to: production
{
	"_id" : ObjectId("5158a0272f191a8d64ec25d3"),
	...
}

Delete the first two lines and declare the object in a variable, so it ends up looking like

var s = {
	"_id" : ObjectId("5158a0272f191a8d64ec25d3"),
	...
}

Now in a new mongo shell script file, we load the file and try to fetch the document from the database. But null is returned.

load('./doc.js');

var self = db.settings.findOne(s);

print(tojson(self)); // <-- self is null

There doesn't seem to be a problem using the ruby driver for mongo.

Copying the document first (both using mongojs and the shell) "fixes" the document, so it can find itself.

Possible problems

  • There are decimals numbers in the document. May be some kind of rounding error when serializing and again loading the document.
  • Weird unicode characters in strings. Some of the property names in the document are user defined, there could be some characters which are lost during serialization.

I've tried to investigate both of these, by removing properties containing numbers and strings, but couldn't come to a definite conclusion.

The following seems to be the minimal document for which this bug is visible (it was obtained by removing properties one by one from the original). Furthermore it seems to be caused by the two last objects (5497017302350460 and 72135999), if they are both present it fails, but if one of them is removed the query succedes.

{
	"_id" : ObjectId("5158a0272f191a8d64ec25d3"),
	"bankAccounts" : {
		"38216677" : {
			
		},
		"5497017302350460" : {
			
		},
		"72135999" : {
			
		}
	}
}

production.zip contains a mongodump of the document.

var mongojs = require('mongojs');
// production is the name of my local production database copy
var db = mongojs('production', ['settings']);
var raise = function(err) {
if(err) throw err;
};
// This is the actual id of the document in the production database
var id = new mongojs.ObjectId('5158a0272f191a8d64ec25d3');
db.settings.findOne({ _id: id }, function(err, setting) {
raise(err);
if(!setting) raise(new Error('Setting missing'));
db.settings.findOne(setting, function(err, self) {
raise(err);
console.log(self); // <-- self is null
});
});
var mongodb = require('mongodb');
var raise = function(err) {
if(err) throw err;
};
var id = new mongodb.ObjectID('5158a0272f191a8d64ec25d3');
mongodb.MongoClient.connect('mongodb://localhost:27017/production', function(err, db) {
raise(err);
var collection = db.collection('settings');
collection.find({ _id: id }).toArray(function(err, docs) {
raise(err);
if(!docs[0]) raise(new Error('Setting missing'));
collection.find(docs[0]).toArray(function(err, selfs) {
raise(err);
console.log(selfs); // empty array
});
});
collection.find({ _id: id, bankAccounts: { '38216677': {}, '5497017302350460': {}, '72135999': {} } }).toArray(function(err, selfs) {
raise(err);
console.log(selfs); // empty array
});
});
require 'mongo'
include Mongo
db = MongoClient.new.db('production')
settings = db.collection('settings')
id = BSON::ObjectId('5158a0272f191a8d64ec25d3')
document = settings.find_one({ '_id' => id })
raise StandardError.new('No document') unless document
me = settings.find_one(document)
puts me # me is the correct document
me = settings.find_one({ '_id' => id, 'bankAccounts' => { '38216677' => {}, '5497017302350460' => {}, '72135999' => {} } })
puts me # me is the correct document
// $mongo production ./mongosh.js
var self = db.settings.findOne({ _id: ObjectId("5158a0272f191a8d64ec25d3"), bankAccounts: { '38216677': {}, '5497017302350460': {}, '72135999': {} } })
print(tojson(self)); // <-- self is null
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment