Hello,
Is it possible to run mongodb-memory-server as a standalone replica set, so that it could be used to test transactions?
I think it should be easy to add: https://docs.mongodb.com/manual/tutorial/convert-standalone-to-replica-set/
Regards,
Sebastian
It's a good feature request. 馃憤
You need to write a new module MongoInstancesReplicaSet.js:
1) Should reuse existed methods in https://github.com/nodkz/mongodb-memory-server/blob/master/src/util/MongoInstance.js
2) Will be nice if it will have the same Interface like use MongoInstance
3) Somehow provide additional properties prepareCommandArgs for MongoInstance
4) Tune run() method for starting and awaiting all replicas to be ready.
5) Tune kill() method for killing all mongod processes
6) Will be nice if this module will be covered by tests
Sorry, I have no bandwidth for this feature realisation.
But will be happy if somebody adds it.
PR welcome!
But for the fast start, you need to improve prepareCommandArgs method https://github.com/nodkz/mongodb-memory-server/blob/master/src/util/MongoInstance.js#L76 It should allow to provide additional options, like replicaSet name.
馃憤 great request.
I'll see if I can help with a PR.
I have created a pull request that makes it possible to run the mongod server in replica set mode. In my implementation does not attempt to address changes to run() or kill() that was mentioned above. I have augmented prepareCommandArgs to handle passing of auth to run the server with auth enabled if desired, args for passing arbitrary arguments such as --notablescan, and replSet to allow specifying the replica set name. Tests were added to make sure the above changes to prepareCommandArgs are handled correctly.
How I use this in my project:
In my mongo-environment file that I use with jest I added the following to the environment config that I export:
async initReplSet() {
if (!this.global.__MONGO_REPLSET_NAME) { return; }
const parsed = muri(this.global.__MONGO_URI__);
const members = parsed.hosts.reduce((pv, cv, idx) => {
const member = {_id: idx, host: `${cv.host}:${cv.port}`};
pv.push(member);
return pv;
}, []);
const rsConfig = {
_id: 'rsTest',
members,
};
const conn = await MongoClient.connect(this.global.__MONGO_URI__, {useNewUrlParser: true});
const db = await conn.db(this.global.__MONGO_DB_NAME__);
const admin = db.admin();
await admin.command({'replSetInitiate': rsConfig});
let isMaster = await admin.command({isMaster: 1});
while(!isMaster.ismaster) {
await new Promise((resolve) => setTimeout(resolve, 1000));
isMaster = await admin.command({isMaster: 1});
}
await conn.close();
}
// in the `setup` method I await on `this.initReplSet()` so that before the setup function exits
// the server is up and running as a single node replica set and has transitioned into `MASTER` mode.
Please let me know your thoughts on this implementation.
Related pull request by @jloveridge https://github.com/nodkz/mongodb-memory-server/pull/79
It's really cool implementation 馃憤
Need to write a new src/MongoReplicaSet.js class. Just copy code from src/MongoMemoryServer.js and put your initReplSet logic to it.
As discussed and agreed to in #79 I will create an additional PR that will add a MongoReplicaSet class which will allow for more hands off running of a replica set. Thanks for your feedback @nodkz and for being so quick to merge my PR contribution.
For anyone watching this issue it is now possible, since 2.4.0, to spin up a replica set by using the new MongoMemoryReplSet class. The README has details as to how to do this. With the provided implementation you can actually spin up either a single node or multiple node replica set. Hopefully this will prove useful.
Thanks everyone! This is really awesome.
I just gave it a try and I've run into a small issue. Sometimes when starting the script I get MongoError: not master failure. Should I be somehow waiting for the single node to become master?
I was using the sample code from the docs for initialization:
import { MongoMemoryReplSet } from 'mongodb-memory-server';
const replSet = new MongoMemoryReplSet();
await replSet.waitUntilRunning();
const uri = await mongod.getConnectionString();
const port = await mongod.getPort();
const dbPath = await mongod.getDbPath();
const dbName = await mongod.getDbName();
// connected and launched some queries here
I tried adding a small delay before launching queries and it started working correctly, so it looks like replica isn't fully initialized when waitUntilRunning is resolved.
@sebastian-nowak Yes, I have noticed that as well in my own testing. I also had to introduce an additional 2s, 1s wasn't enough, delay before the tests would reliably pass. I plan to make some changes to the waitUntilRunning function in the coming week and will add tests to verify that once waitUntilRunning has returned I can immediately perform queries. I honestly don't know what in the heck MongoDB is doing that is causing this issue but it will be worked around in this module soon.
I am not sure if you read through the implementation at all but the promise returned by waitUntilRunning does not resolve until all members of the replica set have been designated as either a PRIMARY or a SECONDARY node, yes I realize this doesn't allow for arbiters at the moment but I didn't consider that necessary for this library. I would have expected that to be sufficient to allow queries to be performed but apparently MongoDB has other ideas. (I will probably file a ticket against them since we pay for support and I think this is really something that should be fixed by them.) In the meantime I will figure out some reliable way to work around the issue. It may require that I change the initialization behavior to add one member at a time and wait for the first member to be identified as master through use of the isMaster command prior to adding the other members and doing a subsequent reconfiguration of the replica set. This is not, however, my preferred method of dealing with it.
I will not be able to make the changes until later this week as I am out of town for a few days. If someone else wants to tackle the issue feel free.
Some of the ways I have considered resolving the issue are as follows:
master via isMaster command.SECONDARY state.PRIMARY or SECONDARY add in a final step that will wait until the PRIMARY also identifies as master via isMaster.PRIMARY.PRIMARY or SECONDARY attempt to connect to the replica set using the URI string returned from getConnectionString/getUri and attempt to run a query.MongoError is no longer returned indicating there is no master.I am not 100% sure that I particularly like any of these options but all seem like viable ways of preventing the issue.
@jloveridge, you got any updates on this?
I could try to help you guys out on this. I never touched this lib but could give a try if you give me a warm up of what you did / code.
@flpStrri will be amazing!
Just need to improve this method with additional awaits while all RS members became PRIMARY/SECONDARY states:
https://github.com/nodkz/mongodb-memory-server/blob/master/src/MongoMemoryReplSet.js#L146-L169
Better to do it via reading its log files.
@nodkz I tried to do it but it was taking too much time to understand the code, I won't be able to do it for now.
@flpStrri no problem. Let keep this issue opened.
I'm also terrible busy for now ;)
I will try to get around to fixing these outstanding issues in the near future. For my own tests I just added an additional timeout to wait for the replica set to actually be ready since it wasn't ready on the initial transition. Sorry for the long delay in getting an actual fix out.
Hello guys, I've been trying to use a replica set because I'm planning to use change streams in my application.
I'm trying to spin up a replica in my test suite but it hangs for a very long time saying No PRIMARY yet. Waiting....
Here's my test:
// @flow
import { MongoClient } from "mongodb"
import { MongoMemoryReplSet } from "mongodb-memory-server"
jest.setTimeout(30000) // 30 seconds
describe("Change streams", () => {
test("test", async done => {
const dbName = "mytest"
const replicaSetName = "testset"
// $FlowFixMe
const replSet = new MongoMemoryReplSet({
autoStart: true,
binary: {
version: "4.0.3",
},
instanceOpts: [
{ storageEngine: "wiredTiger" },
{ storageEngine: "wiredTiger" },
],
replSet: {
dbName,
name: replicaSetName,
},
debug: true,
})
await replSet.waitUntilRunning()
const uri = await replSet.getConnectionString()
console.log("URI", uri) // prints mongodb://127.0.0.1:34587,127.0.0.1:43473/mytest
const client = await MongoClient.connect(
uri + `?replicaSet=${replicaSetName}`,
{ useNewUrlParser: true },
)
const db = client.db(dbName)
const product1 = {
id: "p01",
name: "Product 01",
}
const product2 = {
id: "p01",
name: "Product 01",
}
const collection = db.collection("products")
await collection.insert(product1)
await collection.insert(product2)
const results = await (await collection.find()).toArray()
console.log(JSON.stringify(results, null, 4))
done()
})
})
After a while I see:
console.log node_modules/mongodb-memory-server/lib/MongoMemoryReplSet.js:58
No PRIMARY yet. Waiting...
Mongo[34587] 2019-03-13T11:44:53.056+0100 I REPL [replexec-0] Starting an election, since we've seen no PRIMARY in the past 10000ms
Then after some more time:
Mongo[43473] 2019-03-13T11:44:53.614+0100 I REPL [replexec-2] Member 127.0.0.1:34587 is now in state PRIMARY
Mongo[43473] +513ms
So it seems like one member manages to go PRIMARY but then the script ends with this MongoError right after:
MongoError: no primary found in replicaset or invalid replica set name
What am I missing? :thinking:
Also, is it possible to make one node PRIMARY right away without having to wait those 10 seconds? The test timeout is 30s but it fails with that MongoError after 13s.
OK I think I got it working by adding a sleep between await replSet.waitUntilRunning() and await MongoClient.connect():
const uri = await replSet.getConnectionString()
console.log("URI", uri)
await sleep(2000)
const client = await MongoClient.connect(
uri + `?replicaSet=${replicaSetName}`,
{ useNewUrlParser: true },
)
Is there anything you can think of that I could use to speed up the bootstrap time? 13s + 2s of sleep is a very long time for a test.
I managed to get it working in under 6 seconds but I had to make some changes, here's the Pull Request: https://github.com/nodkz/mongodb-memory-server/pull/155 @nodkz @jloveridge
The above PR doesn't solve the original issue. No matter which value is used with the new configuration option, the tests will still result in a MongoError: not master error.
In 5.2.11 was fixed "Maximum call stack size exceeded" https://github.com/nodkz/mongodb-memory-server/commit/470f09485d5caa9062fc1c24fb5efc0eb3d7d32f
closing thanks to the support of MongoDB-ReplSet's and __waitForPrimary function since c081eced9a454fe00ef3051ed9cdefbdf14dde4d
Most helpful comment
馃憤 great request.
I'll see if I can help with a PR.