MongoDB read preferences let you control which replica set member handles read operations.
Here’s a typical scenario: you have a primary and a couple of secondaries. By default, all reads go to the primary. But what if your application is read-heavy and the primary is getting bogged down? You can offload some of that read traffic to the secondaries.
Let’s see this in action. Imagine we have a replica set with a primary mongo1:27017 and secondaries mongo2:27017 and mongo3:27017.
First, let’s check the current read preference on a connection. If you’re using the mongo shell, you can see it by typing:
db.getMongo().getReadPreference()
By default, this will likely output primary.
Now, let’s imagine we want to direct reads to any available secondary. We can set the read preference for our connection. If you’re connecting via the mongo shell, you can do this by appending the read preference to the connection string:
mongo "mongodb://mongo1:27017,mongo2:27017,mongo3:27017/?replicaSet=myReplSet&readPreference=secondary"
Once connected with this preference, running db.getMongo().getReadPreference() will now show secondary. Any read operations performed with this shell session will now be routed to mongo2 or mongo3.
There are several read preference modes:
primary: Reads go only to the primary. This is the default. It offers the strongest consistency because the primary is always the most up-to-date.primaryPreferred: Reads go to the primary if it’s available. If the primary is down, reads will go to a secondary. This balances consistency with availability.secondary: Reads go to any secondary member. If no secondaries are available, the read will fail. This is good for offloading read traffic but sacrifices immediate consistency.secondaryPreferred: Reads go to secondaries if available. If no secondaries are available, reads will go to the primary. This is a good general-purpose preference for read scaling.nearest: Reads go to the member with the lowest network latency, regardless of its role (primary or secondary). This minimizes network latency for reads but offers the weakest consistency.
Beyond just the mode, you can also specify a tagSets option. Tags are arbitrary labels you can assign to replica set members in your configuration. This allows for fine-grained control. For example, you can tag members based on their data center, hardware capabilities, or the type of data they hold.
Let’s say you have members tagged like this:
mongo1(primary):{ "dc": "east", "type": "analytics" }mongo2(secondary):{ "dc": "west", "type": "reporting" }mongo3(secondary):{ "dc": "east", "type": "reporting" }
You could then connect with a preference to read from secondaries in the "east" data center:
mongo "mongodb://mongo1:27017,mongo2:27017,mongo3:27017/?replicaSet=myReplSet&readPreference=secondary&readPreferenceTags=dc:east,type:reporting"
This command will attempt to route reads to mongo3. If mongo3 is unavailable, it will try other secondaries that match the tags. If no matching secondaries are available, the read will fail. You can also provide multiple tag sets, and MongoDB will try them in order.
The mental model here is that you’re not just telling MongoDB where to read from, but how to choose. The modes give you broad strokes (primary only, any secondary, nearest), while tags let you paint with a finer brush, directing reads to specific subsets of your replica set based on operational or data-centric criteria. This is crucial for optimizing performance, managing load, and even implementing geo-distributed read strategies.
A key detail often overlooked is that even when using secondary or secondaryPreferred, the connection string still needs to list the primary. MongoDB uses the primary to discover the state and members of the replica set, including which members are secondaries. So, the primary is always part of the initial connection, even if it’s not intended to handle reads.
Next, you’ll likely want to explore write concerns and how they interact with read preferences to manage consistency across your distributed data.