Hey all! I jumped onto the nosql bandwagon with Mongo. Today I'll document my process in creating a Mongo database with a replica set.

For this to work you will need three hosts. I installed Mongo on CentOS 7 virtual machines; below is the IP scheme for my VMs and their host names. Mongo-01 will be the initial primary.

IP Scheme:

192.168.14.77   =  mongo-01
192.168.14.96   =  mongo-02
192.168.14.32   =  mongo-03

Install Mongo

  1. Edit the host file (/etc/hosts) and add the IP address mappings to the three MongoDB hosts.
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.14.77   mongo-01
192.168.14.96   mongo-02
192.168.14.32   mongo-03

2. Create a file for the Mongo repo.

vim /etc/yum.repos.d/mongodb-org-4.2.repo

3. Paste in the details for the Mongo repo in the file.

[mongodb-org-4.2]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/4.2/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-4.2.asc

4. Install Mongo.

yum install -y mongodb-org

5. Open port 27017 tcp.

firewall-cmd --permanent --add-port=27017/tcp
firewall-cmd --reload

6. Edit Mongo's config file (/etc/mongod.conf).

# network interfaces
net:
  port: 27017
  bindIp: 192.168.14.77  # Enter 0.0.0.0,:: to bind to all IPv4 and IPv6 addresses or, alternatively, use the net.bindIpAll setting.


replication:
  replSetName: 01-repl

Within this file, edit the bindIp and replace it with the IP address of the host. Next uncomment replication:, beneath it add replSetName: and give it a name. I named mine 01-repl.

7. Start mongo.

systemctl start mongod

8. Repeat steps 1 - 7 on the other two mongo hosts.


Configure Replica Set

Now that Mongo is installed and running on all three hosts, it's time to force them to talk and share their data like a good replica set!

Most of the configuration changes will occur on your primary Mongo host. Mine is mongo-01 (192.168.14.77). Connect to your primary host now.

  1. Connect to the mongo shell.
mongo --host mongo-01 --port 27017

2. Initiate the replica set.

rs.initiate()

3. Add the other two hosts as secondaries with the rs.add() command.

rs.add(mongo-02:27017)
rs.add(mongo-03:27017)

*NOTE:

If you want to add all of the secondaries when you initiate the replica set, you can supply them in the argument. This bypassess the need to run the rs.add function.

rs.initiate(
   {
      _id: "01-repl",
      version: 1,
      members: [
         { _id: 0, host : "mongo-01:27017" },
         { _id: 1, host : "mongo-02:27017" },
         { _id: 2, host : "mongo-03:27017" }
      ]
   }
)

4. Check the replica set configuration.

rs.conf()	

After executing the command above you should see the following:

{
	"_id" : "01-repl",
	"version" : 3,
	"protocolVersion" : NumberLong(1),
	"writeConcernMajorityJournalDefault" : true,
	"members" : [
		{
			"_id" : 0,
			"host" : "mongo-01:27017",
			"arbiterOnly" : false,
			"buildIndexes" : true,
			"hidden" : false,
			"priority" : 1,
			"tags" : {

			},
			"slaveDelay" : NumberLong(0),
			"votes" : 1
		},
		{
			"_id" : 1,
			"host" : "mongo-02:27017",
			"arbiterOnly" : false,
			"buildIndexes" : true,
			"hidden" : false,
			"priority" : 1,
			"tags" : {

			},
			"slaveDelay" : NumberLong(0),
			"votes" : 1
		},
		{
			"_id" : 2,
			"host" : "mongo-03:27017",
			"arbiterOnly" : false,
			"buildIndexes" : true,
			"hidden" : false,
			"priority" : 1,
			"tags" : {

			},
			"slaveDelay" : NumberLong(0),
			"votes" : 1
		}
	],
	"settings" : {
		"chainingAllowed" : true,
		"heartbeatIntervalMillis" : 2000,
		"heartbeatTimeoutSecs" : 10,
		"electionTimeoutMillis" : 10000,
		"catchUpTimeoutMillis" : -1,
		"catchUpTakeoverDelayMillis" : 30000,
		"getLastErrorModes" : {

		},
		"getLastErrorDefaults" : {
			"w" : 1,
			"wtimeout" : 0
		},
		"replicaSetId" : ObjectId("5d52e6e81d4e191c0ff940b7")
	}
}

Notice how the output is using key value pairs. This will be important because Mongo can be queried using JavaScript. For example rs.conf().members[1] will return the output for mongo-02.

01-repl:PRIMARY> rs.conf().members[1]
{
	"_id" : 1,
	"host" : "mongo-02:27017",
	"arbiterOnly" : false,
	"buildIndexes" : true,
	"hidden" : false,
	"priority" : 1,
	"tags" : {

	},
	"slaveDelay" : NumberLong(0),
	"votes" : 1
}

5. Check the replication lag on a secondary. Connect to either mongo-02 or mongo-03 (mongo --host mongo-0X) and then execute the command below.

rs.printSlaveReplicationInfo()

You should get an output like this:

source: mongo-02:27017
	syncedTo: Tue Aug 13 2019 16:32:15 GMT-0400 (EDT)
	0 secs (0 hrs) behind the primary
source: mongo-03:27017
	syncedTo: Tue Aug 13 2019 16:32:15 GMT-0400 (EDT)
	0 secs (0 hrs) behind the primary

Anything under 10 secs is a good indication that Mongo is not having any problems replicating.


Testing the Replica Set

It's now time to give your Mongo DB, two tests. The first test will check if the data is replicated across the all three mongo hosts.

  1. Connect to the primary host.
mongo --host mongo-01 --port 27017

2. Create and use a new database.

use myNewDb

You will get a message that will say switched to db myNewDb.

3. Create a new collection and insert new data into it. The data I'll insert is a car and its model.

db.aNewCollection.insert({ car : "WRX STI" })

You will get a message that something was inserted into the DB like below:

WriteResult({ "nInserted" : 1 })

4. Verify the data is present on the primary.

db.myNewDb.find()

You should see an output similar to this:

{ "_id" : ObjectId("5d5340e40b8992c95fe56189"), "car" : "WRX STI" }

5. Verify the data is present on the secondaries.

mongo --host mongo-02 --port 27017

mongo --host mongo-03 --port 27017

6. To run queries on a secondary execute the command below first.

db.getMongo().setSlaveOk()

7. Check if the DB is present on the secondaries.

show dbs

You should see the following output:

admin   0.000GB
config  0.000GB
local   0.000GB
myNewDb  0.000GB

The DB that was created on the primary is now present on the secondaries.

8. Verify the data is on the secondaries.

use myNewDb
db.aNewCollection.find()

Woot! Fantastic it worked! The output below was shown.

{ "_id" : ObjectId("5d5340e40b8992c95fe56189"), "car" : "WRX STI" }

9. Test the new primary election process.

This is important to ensure that if the primary is ever lost, an eligible secondary can take its place.

Execute the following on the primary:

rs.stepDown()

You should see the prompt change from primary to secondary.

10. Find which host is now the primary.

db.isMaster()

The output will be something like this:

{
	"hosts" : [
		"mongo-01:27017",
		"mongo-02:27017",
		"mongo-03:27017"
	],
	"setName" : "pafable-repl",
	"setVersion" : 3,
	"ismaster" : false,
	"secondary" : true,
	"primary" : "mongo-02:27017",
	"me" : "192.168.122.186:27017",
	"lastWrite" : {
		"opTime" : {
			"ts" : Timestamp(1565741143, 1),
			"t" : NumberLong(2)
		},
		"lastWriteDate" : ISODate("2019-08-14T00:05:43Z"),
		"majorityOpTime" : {
			"ts" : Timestamp(1565741143, 1),
			"t" : NumberLong(2)
		},
		"majorityWriteDate" : ISODate("2019-08-14T00:05:43Z")
	},
	"maxBsonObjectSize" : 16777216,
	"maxMessageSizeBytes" : 48000000,
	"maxWriteBatchSize" : 100000,
	"localTime" : ISODate("2019-08-14T00:05:44.870Z"),
	"logicalSessionTimeoutMinutes" : 30,
	"minWireVersion" : 0,
	"maxWireVersion" : 7,
	"readOnly" : false,
	"ok" : 1,
	"operationTime" : Timestamp(1565741143, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1565741143, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

Notice the line that says primary, it's now mongo-02! You can also execute rs.status() to find the primary. By default, the election process will choose the host that had the fastest connection with former primary. In this case it was mongo-02.

*BONUS:

If you want to influence the election process so that mongo-03 will become the new primary, you can set the priority of mongo-02 to zero.

Create an object called x.

x = rs.config()

Modify mongo-02's priority to 0.

x.members[1].priority = 0

Save the configuration changes.

rs.reconfig(x)

Now when you do a step down, mongo-02 is excluded from the election process, allowing mongo-03 to become the primary.

Well that wraps up this tutorial for mongo. In a future tutorial, I'll connect this database to a node.js application.