Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Running as a single node replica set #74

Closed
sebastian-nowak opened this issue Aug 27, 2018 · 21 comments
Closed

Running as a single node replica set #74

sebastian-nowak opened this issue Aug 27, 2018 · 21 comments

Comments

@sebastian-nowak
Copy link

sebastian-nowak commented Aug 27, 2018

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

@nodkz
Copy link
Collaborator

nodkz commented Aug 27, 2018

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!

@nodkz
Copy link
Collaborator

nodkz commented Aug 27, 2018

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.

@mathieug
Copy link
Contributor

mathieug commented Sep 5, 2018

👍 great request.
I'll see if I can help with a PR.

@jloveridge
Copy link
Contributor

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.

@nodkz
Copy link
Collaborator

nodkz commented Sep 27, 2018

Related pull request by @jloveridge #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.

@jloveridge
Copy link
Contributor

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.

@jloveridge
Copy link
Contributor

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.

@sebastian-nowak
Copy link
Author

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.

@jloveridge
Copy link
Contributor

jloveridge commented Oct 2, 2018

@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:

  • Add members one at a time.
    • Wait for first member to be identified as master via isMaster command.
    • Add any additional members based on options used to create the replica set.
    • Wait for all added members to be in SECONDARY state.
  • Instead of just waiting for all members to be identified as PRIMARY or SECONDARY add in a final step that will wait until the PRIMARY also identifies as master via isMaster.
    • This may require making a new connection to the server designated as PRIMARY.
  • Alternatively once all members are designated as PRIMARY or SECONDARY attempt to connect to the replica set using the URI string returned from getConnectionString/getUri and attempt to run a query.
    • Repeat query attempts until 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.

@flpStrri
Copy link

@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.

@nodkz
Copy link
Collaborator

nodkz commented Oct 23, 2018

@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.

@flpStrri
Copy link

flpStrri commented Dec 3, 2018

@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.

@nodkz
Copy link
Collaborator

nodkz commented Dec 4, 2018

@flpStrri no problem. Let keep this issue opened.
I'm also terrible busy for now ;)

@jloveridge
Copy link
Contributor

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.

@fracasula
Copy link
Contributor

fracasula commented Mar 13, 2019

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? 🤔

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.

@fracasula
Copy link
Contributor

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.

@fracasula
Copy link
Contributor

fracasula commented Mar 13, 2019

I managed to get it working in under 6 seconds but I had to make some changes, here's the Pull Request: #155 @nodkz @jloveridge

@rochdev
Copy link

rochdev commented Jun 13, 2019

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.

@jardakotesovec
Copy link
Contributor

#203 is my attempt to make this faster&reliable. Getting times 2100ms for single node and 4100ms for 3nodes. Hopefully I did not miss something. Please check it out. Thanks!

@nodkz
Copy link
Collaborator

nodkz commented Oct 22, 2019

In 5.2.11 was fixed "Maximum call stack size exceeded" 470f094

@hasezoey
Copy link
Member

closing thanks to the support of MongoDB-ReplSet's and __waitForPrimary function since c081ece

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants