readPreference is the MongoDB node-selection strategy for read operations in replica sets. It supports read/write separation by reducing load on the primary node and can help globally distributed deployments read from lower-latency nodes.
Applicable scenarios:
- Replica Set deployment
- Read/write separation to reduce load on the primary node
- Globally distributed deployments that need lower-latency reads
- Analytics/report queries that can tolerate eventual consistency
Restrictions:
config.readPreferencesets the connection-level default.- Individual read operations can pass
readPreferencein their options to override the default for that operation. - The current monSQLize runtime is MongoDB-only; this option maps to MongoDB driver read preference behavior.
- Reads from secondary nodes may observe replication lag, so read-after-write paths should use
primaryorprimaryPreferred. - Reads inside MongoDB transactions should stay on
primary; keep transaction reads on the connection default or override them toprimary. - It has no useful effect in stand-alone mode because there is only one node.
| Mode | Read Node | Data Consistency | Applicable Scenarios |
|---|---|---|---|
| primary | Primary node only | Strong consistency | Default, latest data required |
| primaryPreferred | Primary first; secondary when the primary is unavailable | Usually strong consistency | Strong consistency with failover tolerance |
| secondary | Secondary nodes only | Eventual consistency | Analytics/reporting, primary write-load isolation |
| secondaryPreferred | Secondary first; primary when no secondary is available | Eventual consistency | Read-heavy workloads, reduced primary load |
| nearest | Lowest-latency node (primary or secondary) | Eventual consistency | Globally distributed deployment, low latency |
const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'test_db',
config: {
uri: 'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0',
readPreference: 'secondaryPreferred' // Global configuration
}
});Features:
- Configure once and all queries will take effect
- Use query-level options only for operations that need a different read route
- Simplify the default path and reduce repeated configuration
| Parameters | Type | Required | Default | Description |
|---|---|---|---|---|
| config.readPreference | string | ❌ | 'primary' |
Replica set read preference mode |
| read operation options.readPreference | string | ❌ | Connection default | Per-operation override for read methods that pass MongoDB driver options |
Optional values:
'primary'- Read only from the primary node (default)'primaryPreferred'- Read from the primary first, then a secondary when the primary is unavailable'secondary'- Read only from secondary nodes'secondaryPreferred'- Read from secondary nodes first, then the primary when no secondary is available'nearest'- Read from the nearest node (low latency)
const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'test_db',
config: {
uri: 'mongodb://localhost:27017',
// If readPreference is not configured, the default is 'primary' (read only from the primary)
}
});
await msq.connect();
const { collection } = msq;
// Query operations automatically read from the primary node
const users = await collection('users').find({});
console.log(`✅ Read ${users.length} documents from the primary node`);
await msq.close();Applicable scenarios: read-heavy workloads that should reduce load on the primary node.
const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'test_db',
config: {
uri: 'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0',
readPreference: 'secondaryPreferred' // Prefer secondary nodes
}
});
await msq.connect();
const { collection } = msq;
// Queries read from secondary nodes first (reduces load on the primary)
const products = await collection('products').find({ category: 'electronics' });
console.log(`✅ Read ${products.length} products from secondary nodes`);
// NOTE: secondary nodes may have replication lag
console.log('NOTE: secondary data may lag by a few milliseconds to a few seconds');
await msq.close();Applicable scenarios: analytics/reporting queries that should isolate primary write load.
const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'analytics_db',
config: {
uri: 'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0',
readPreference: 'secondary' // Read only from secondary nodes
}
});
await msq.connect();
const { collection } = msq;
// Applicable scenarios: analytics/reporting query with primary write-load isolation
const reports = await collection('sales').aggregate([
{ $match: { date: { $gte: new Date('2025-01-01') } } },
{ $group: { _id: '$category', total: { $sum: '$amount' } } }
]);
console.log(`✅ Generated ${reports.length} report rows from secondary nodes`);
console.log('✅ The primary node can continue focusing on write operations.');
await msq.close();Applicable scenario: strong consistency is required, but reads should have a fallback when the primary node is unavailable.
const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'test_db',
config: {
uri: 'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0',
readPreference: 'primaryPreferred' // Read from the primary first, then from a secondary if needed
}
});
await msq.connect();
const { collection } = msq;
// Applicable scenario: strong consistency with a fallback when the primary is unavailable
const orders = await collection('orders').find({ status: 'pending' });
console.log(`✅ Read ${orders.length} orders from the primary node first`);
console.log('✅ If the primary is unavailable, reads can fall back to a secondary');
await msq.close();Applicable scenarios: Global distributed deployment, nearby reading to reduce latency
const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'test_db',
config: {
uri: 'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0',
readPreference: 'nearest' // Lowest-latency node (primary or secondary)
}
});
await msq.connect();
const { collection } = msq;
// Applicable scenario: global deployments where nearby reads reduce latency
const articles = await collection('articles').find(
{ published: true },
{ limit: 10 }
);
console.log(`✅ Read ${articles.length} articles from the node with the lowest latency`);
console.log('✅ Suitable for global distributed deployment scenarios');
await msq.close();const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'test_db',
config: {
uri: 'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0',
readPreference: 'secondaryPreferred' // Read preference
},
maxTimeMS: 3000, // Query timeout
slowQueryMs: 500 // Slow query threshold
});
await msq.connect();
const { collection } = msq;
// Query-level readPreference overrides the connection default for this operation.
const results = await collection('products').find(
{ price: { $gt: 100 } },
{
readPreference: 'primary',
hint: { category: 1, price: 1 }, // Index hint
comment: 'expensive-products-query', // Query comment
maxTimeMS: 2000 // Per-query timeout
}
);
console.log(`✅ Query with multiple options returned ${results.length} results`);
await msq.close();-
Use
secondaryPreferredfor read-heavy workloads// Recommendation: reduce load on the primary node readPreference: 'secondaryPreferred'
-
Use
primary(default) for strong-consistency reads// Recommendation: use the default when the latest data is required // Do not configure readPreference, or explicitly configure it as 'primary'
-
Use
nearestfor globally distributed deployments// Recommendation: lower-latency reads readPreference: 'nearest'
-
Use
secondaryfor analytics/reporting queries// Recommendation: isolate primary write load readPreference: 'secondary'
-
Replication delay problem
// Avoid: reading from secondary nodes immediately after writing data await collection('users').insertOne({ name: 'Alice' }); // Write to primary // The newly written data may not be visible yet because of replication lag const users = await collection('users').find({ name: 'Alice' }); // Solution: use 'primary' for read-after-write or wait for replication to complete. const freshUsers = await collection('users').find( { name: 'Alice' }, { readPreference: 'primary' } );
-
Transaction reads
// MongoDB transaction reads should use the primary node. await msq.withTransaction(async (tx) => { const order = await collection('orders').findOne( { orderNo: 'SO-1001' }, { session: tx.session, readPreference: 'primary' } ); // ...write operations in the same transaction });
-
Single mode is invalid
// In stand-alone mode, readPreference is ineffective because there is only one node. const msq = new MonSQLize({ config: { uri: 'mongodb://localhost:27017', // stand-alone mode readPreference: 'secondary' // Ineffective configuration } });
-
Replica Set URI Format
// Correct: contains multiple nodes + replicaSet parameter uri: 'mongodb://host1:27017,host2:27018,host3:27019/?replicaSet=rs0' // Incorrect: single-node URI (cannot separate reads and writes) uri: 'mongodb://localhost:27017'
-
Cross-database compatibility
// readPreference is MongoDB-specific. // PostgreSQL/MySQL do not have the same concept. // Remove this configuration when switching database adapters.
| Read preference | Master node load | Latency | Data consistency | Applicable scenarios |
|---|---|---|---|---|
| primary | High | Low | Strong consistency | Write-heavy workloads, latest data required |
| primaryPreferred | High | Low | Usually strongly consistent | Requires consistency + fault tolerance |
| secondary | Low | Medium (replication latency) | Eventually consistent | Analytics/reporting, primary isolation |
| secondaryPreferred | Low | Medium (replication latency) | Eventually consistent | Read-heavy workloads, reduced primary load |
| nearest | Medium | Lowest | Eventually consistent | Globally distributed, low latency |
- Read-heavy workloads: Use
secondaryPreferredto move tolerant reads away from the primary. - Globally distributed: Use
nearestwhen lower latency matters more than always reading from the primary. - Analytics/reporting: Use
secondaryto isolate primary write load
A: Yes. config.readPreference is the default, and read methods that pass MongoDB driver options can override it per operation:
const users = await collection('users').find(
{ status: 'active' },
{ readPreference: 'primary' }
);Use this sparingly. Keep the connection-level default for most reads, and override only read-after-write or analytics queries that need a different route.
A:
- Check the MongoDB log/profile to confirm that the read operation hits a secondary node
- Add delay on a secondary node and observe whether query results lag.
- Use
db.currentOp()to view the read preferences of active connections
A:
- LAN replica set: typically 10-100ms
- Cross-region replica set: maybe 100ms-1s
- When the network jitters: maybe 1s-5s
- It is recommended to monitor
optimeDatedifferences inrs.status()
A:
- Read immediately after writing: use
primaryorprimaryPreferred - Acceptable delay: Use
secondaryPreferredorsecondary - Mixed strategy: Use
primaryfor key queries andsecondaryfor analytical queries.
A:
readPreference is invalid in stand-alone mode. Suggestions:
- Use Docker Compose to build a local replica set
- Use MongoDB Atlas free cluster (M0)
- Use
mongodb-memory-serverto simulate a replica set (configuration required)