One of the reasons for using MongoDB is its simplicity in scaling its structure, either as Replicaset(scale-up) or as Sharded Cluster(scale-out).
Although a bit tricky in Sharded environments, the reverse process is also feasible.
From the possibilities, you can:
- Reduce the number of shards from your Sharded Cluster as documented here.
- You can also reduce the number of nodes from a Replica Set, as detailed here.
- You can even convert from Replicaset to Sharded Cluster, and the opposite is possible to be achieved and documented at Sharded Cluster to Replicaset.
So far, so good, but if for some reason that mongo project which started as a Replica Set does not require such structure and now a Standalone server meets the requirements;
- Can you shrink from Replica Set to a Standalone server?
- If possible, what is the procedure to achieve this configuration?
This blog post will walk through the necessary steps to convert a Replica Set into a Standalone server.
Observations:
Before starting the process, it is important to mention that it’s not advised to run Standalone configuration on production servers as your entire availability will only rely on a single point.
Another observation is this activity requires downtime, that’s because you will need to remove the replication parameter, which can not be at runtime.
Last note; to avoid any data change during the procedure, it’s recommended to stop writing from the application. You will need to adjust the connection string at the end of the process for the new configuration.
How To:
The testing environment is a standard Replica Set configuration with three nodes, as seen at the following replication status.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | [ { "_id" : 0, "name" : "node1:27017", "stateStr" : "PRIMARY" }, { "_id" : 1, "name" : "node2:27017", "stateStr" : "SECONDARY", }, { "_id" : 2, "name" : "node3:27017", "stateStr" : "SECONDARY", ] |
- The
PRIMARY
is our reliable data source; thus, it will be the Standalone server at the end of the process.
It is not needed to trigger any election
process unless you have a preferred node to be the Standalone. If that’s your case and that node is not the PRIMARY yet, please make sure to make
PRIMARY
before starting the procedure.
That said, let’s get our hands dirty!
1) Removing the secondaries:
- From the
PRIMARY
node, let’s remove node 2 and 3:
1 2 3 4 5 6 7 8 9 10 11 12 | rs0:PRIMARY> rs.remove("node2:27017") { "ok" : 1, "$clusterTime" : { "clusterTime" : Timestamp(1640109346, 1), "signature" : { "hash" : BinData(0,"663Y5u3GkZkQ0Ci7EU8IYUldVIM="), "keyId" : NumberLong("7044208418021703684") } }, "operationTime" : Timestamp(1640109346, 1) } |
1 2 3 4 5 6 7 8 9 10 11 12 | rs0:PRIMARY> rs.remove("node3:27017") { "ok" : 1, "$clusterTime" : { "clusterTime" : Timestamp(1640109380, 1), "signature" : { "hash" : BinData(0,"RReAzLZmHWfzVcdnLoGJ/uz04Vo="), "keyId" : NumberLong("7044208418021703684") } }, "operationTime" : Timestamp(1640109380, 1) } |
Please note: Although my Replica Set comprises three nodes, you must let only the PRIMARY
on replication at this step. If you have more nodes, the process is the same.
Then, the expected status should be:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | rs0:PRIMARY> rs.status().members [ { "_id" : 0, "name" : "node1:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 1988, "optime" : { "ts" : Timestamp(1640109560, 1), "t" : NumberLong(1) }, "optimeDate" : ISODate("2021-12-21T17:59:20Z"), "syncSourceHost" : "", "syncSourceId" : -1, "infoMessage" : "", "electionTime" : Timestamp(1640107580, 2), "electionDate" : ISODate("2021-12-21T17:26:20Z"), "configVersion" : 5, "configTerm" : 1, "self" : true, "lastHeartbeatMessage" : "" } ] |
2) Changing the configuration:
- Even though node2 and node3 are no longer part of the replication, let’s stop the
mongod
process. Keeping their data safe, as it can be used in a recovery scenario if necessary:
1 2 | node2-shell> systemctl stop mongod node3-shell> systemctl stop mongod |
- Then, you can remove the replication parameter from
PRIMARY
and restart it, but first! In the configuration file:- Please remove or comment the lines:
1 2 | #replication: # replSetName: "rs0" |
If you start MongoDB via the command line, please remove the option --replSet.
- Once changed, you can restart the
mongod
onPRIMARY
:
1 | node1-shell> systemctl restart mongod |
3) Removing replication objects:
- Once connected to former
PRIMARY
, now Standalone, MongoDB will warn you with the following message:
1 | 2021-12-21T18:23:52.056+00:00: Document(s) exist in 'system.replset', but started without --replSet. Database contents may appear inconsistent with the writes that were visible when this node was running as part of a replica set. Restart with --replSet unless you are doing maintenance and no other clients are connected. The TTL collection monitor will not start because of this. For more info see http://dochub.mongodb.org/core/ttlcollections |
That is because the node was working under a Replica Set configuration, and although it’s a Standalone now, there is the old structure existing.
For further detail, that structure resides under the local database, which holds all necessary data for replication, and for the database itself:
1 2 3 4 5 6 7 8 9 | mongo> use local switched to db local mongo> show collections oplog.rs replset.election replset.minvalid startup_log system.replset |
To remove that warning, you should remove the old Replica Set object on local.system.replset
.
local.system.replset
holds the replica set’s configuration object as its single document. To view the object’s configuration information, issuers.conf()
from mongosh. You can also query this collection directly.
However, it’s important to mention the local.system.replset
is a system object, and there is no built-in role that you can use to remove objects – Unless you create a custom role that grants anyAction on
anyResource to the user, Or you grant the internal role
__system to a user.
⚠️
Please note, both of that options gives access to every resource in the system and is intended for internal use. Do not use this permissions, other than in exceptional circumstances.
For this process, we will create a new user, temporary, with __system
privilege in which we can drop later after the action.
3. a) Creating the temporary user:
1 | mongo> db.getSiblingDB("admin").createUser({user: "dba_system", "pwd": "sekret","roles" : [{ "db" : "admin", "role" : "__system" }]}); |
Confirmation output:
1 2 3 4 5 6 7 8 9 | Successfully added user:{ "user":"dba_system", "roles":[ { "db":"admin", "role":"__system" } ] } |
3. b) Removing the object:
- First; Connect with the created user.
- Then:
1 2 | mongo> use local; mongo> db.system.replset.remove({}) |
Confirmation output:
1 | WriteResult({ "nRemoved" : 1 }) |
3. c) Dropping the temporary user:
- Disconnect from that system user.
- Then:
1 | mongo> db.dropUser("dba_system") |
4) Restart the service:
1 | shell> systemctl restart mongod |
Once reconnected, you will not see that warning again, and the server is ready!
5) Connection advice:
- Please make sure to adjust the connection string from the app and clients, passing only the Standalone server.
Conclusion
The conversion process, in general, is simple and should not face problems. But is essential to highlight that we strongly recommend you test and run this procedure in a lower environment. And if you plan to use it on production, please bear in mind the said before.
And if you have any questions, feel free to contact us in the comment section below!
See you!
Another simpler option is just to remove the extra members and leave it as a single-member replicaset — that may seem silly at first glance, but it leaves you flexibility for some things which you otherwise couldn’t do:
* Change Streams
* Add a hidden secondary to have a “hot backup”
* Be able to quickly and easily scale back up later if you need to
Personally it baffles me why anyone would run standalone in a production environment — there is basically no cost to having a single-member system and it leaves you options.
In my practice, I have come across the fact that, in replica mode, Mongo works slower if there are active records or document changes (for example, aggregation with output to a new collection), because in addition to the records themselves, Mongo also writes a oplog. Therefore, it works faster in standalone mode, because it does not write a oplog.