MongoDB 按顺序而不是并行执行查询
MongoDB executes queries sequentially instead of in parallel
我有一个 API 端点,我正在尝试对其进行压力测试,它读取一个非常大的 MongoDB
数据库集合(200 万个文档)。每个查询大约需要 2 秒 ,但是我遇到的问题是与数据库的连接没有正确合并,因此每个查询 顺序运行 而不是 同时.
我正在使用 Mongoose to connect to my database and I am using artillery.io 进行测试。
这是我的连接代码:
const mongoose = require('mongoose');
const Promise = require('bluebird');
const connectionString = process.env.MONGO_DB || 'mongodb://localhost/mydatabase';
mongoose.Promise = Promise;
mongoose.connect(connectionString, {
server: { poolSize: 10 }
});
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error: '));
db.once('open', function() {
console.log('Connected to: ' + connectionString);
});
module.exports = db;
这是你的标准连接程序,但最重要的部分可能是 server: { poolSize: 10 }
行。
我正在使用以下脚本进行 artillery.io 测试:
config:
target: 'http://localhost:1337'
phases:
-
duration: 10
arrivalRate: 5
name: "Warm-up"
scenarios:
-
name: "Search by postcodes"
flow:
-
post:
url: "/api/postcodes/gb_full/search"
headers:
Content-Type: 'application/json'
json:
postcodes:
- ABC 123,
- DEF 345,
- GHI 678
此测试在 10 秒 内执行 50 次对 API 的调用。现在问题出在这里,API 似乎按顺序 执行查询 ,请参阅下面的测试结果:
"latency": {
"min": 1394.1,
"max": 57693,
"median": 30222.7,
"p95": 55396.8,
"p99": 57693
},
并且数据库日志如下:
connection accepted from 127.0.0.1:60770 #1 (1 connection now open)
...
2017-04-10T18:45:55.389+0100 ... 1329ms
2017-04-10T18:45:56.711+0100 ... 1321ms
2017-04-10T18:45:58.016+0100 ... 1304ms
2017-04-10T18:45:59.355+0100 ... 1338ms
2017-04-10T18:46:00.651+0100 ... 1295ms
看起来 API 似乎只使用一个连接,这似乎是正确的,但据我了解,这将自动充分利用 poolSize
并执行这些查询 并发 而不是一次一个。
我在这里做错了什么?如何并行执行这些数据库查询?
编辑 1 - 模型和查询
为了让事情更清楚一些,我使用了以下模型:
const mongoose = require('mongoose');
const db = require('...');
const postcodeSchema = mongoose.Schema({
postcode: { type: String, required: true },
...
location: {
type: { type: String, required: true },
coordinates: [] //coordinates must be in longitude, latitude order.
}
});
//Define the index for the location object.
postcodeSchema.index({location: '2dsphere'});
//Export a function that will allow us to define the collection
//name so we'll pass in something like: GB, IT, DE ect for different data sets.
module.exports = function(collectionName) {
return db.model('Postcode', postcodeSchema, collectionName.toLowerCase());
};
其中 db
对象是此问题顶部解释的连接模块。
我正在使用以下内容执行查询:
/**
* Searches and returns GeoJSON data for a given array of postcodes.
* @param {Array} postcodes - The postcode array to search.
* @param {String} collection - The name of the collection to search, i.e 'GB'.
*/
function search(postcodes, collection) {
return new Promise((resolve, reject) => {
let col = new PostcodeCollection(collection.toLowerCase());
col.find({
postcode: { $in: postcodes }
})
.exec((err, docs) => {
if (err)
return reject(err);
resolve(docs);
});
});
}
下面是如何调用该函数的示例:
search(['ABC 123', 'DEF 456', 'GHI 789'], 'gb_full')
.then(postcodes => {
console.log(postcodes);
})
.catch(...);
重申一下,这些查询是通过 node.js
API 执行的,因此它们应该 已经 是异步的,但是查询本身正在执行一个接一个地。因此,我认为问题可能出在 MongoDB 方面,但我什至不知道从哪里开始寻找。如果已经有一个 运行.
,就好像 MongoDB 正在阻止对集合执行任何其他查询。
我是 运行 mongod.exe
的实例,位于 Windows 10 机器上。
好的,所以我设法找出问题所在。
首先,MongoDB在发出查询时有一个读锁(参见here)。这就是它按顺序执行查询的原因。进一步改进这一点的唯一方法是 分片 集合。
此外,正如 Jorge 所建议的,我在 postcode 字段上添加了一个索引,这大大减少了延迟。
postcodeSchema.index({ postcode: 1 }); //, { unique: true } is a tiny bit faster.
具体来说,以下是新指标的压力测试结果:
"latency": {
"min": 5.2,
"max": 72.2,
"median": 11.1,
"p95": 17,
"p99": null
},
中值延迟从 30 秒 下降到 11 毫秒,这是一个惊人的改进。
Firstly, MongoDB has a read lock when a query is issued (see here). That's why it was executing queries sequentially. The only way to improve this further is by sharding the collection.
如果您使用 mongo 3.0+ 和 wiredtiger 作为存储引擎,您将拥有文档级锁定。查询不应按顺序执行,分片肯定会有助于并行性,但 2kk 文档对于大多数现代 computer/server 硬件来说应该不是问题。
你在第一个问题中提到了 mongodb 的日志文件,你应该打开了多个连接,是这样吗?
我有一个 API 端点,我正在尝试对其进行压力测试,它读取一个非常大的 MongoDB
数据库集合(200 万个文档)。每个查询大约需要 2 秒 ,但是我遇到的问题是与数据库的连接没有正确合并,因此每个查询 顺序运行 而不是 同时.
我正在使用 Mongoose to connect to my database and I am using artillery.io 进行测试。
这是我的连接代码:
const mongoose = require('mongoose');
const Promise = require('bluebird');
const connectionString = process.env.MONGO_DB || 'mongodb://localhost/mydatabase';
mongoose.Promise = Promise;
mongoose.connect(connectionString, {
server: { poolSize: 10 }
});
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error: '));
db.once('open', function() {
console.log('Connected to: ' + connectionString);
});
module.exports = db;
这是你的标准连接程序,但最重要的部分可能是 server: { poolSize: 10 }
行。
我正在使用以下脚本进行 artillery.io 测试:
config:
target: 'http://localhost:1337'
phases:
-
duration: 10
arrivalRate: 5
name: "Warm-up"
scenarios:
-
name: "Search by postcodes"
flow:
-
post:
url: "/api/postcodes/gb_full/search"
headers:
Content-Type: 'application/json'
json:
postcodes:
- ABC 123,
- DEF 345,
- GHI 678
此测试在 10 秒 内执行 50 次对 API 的调用。现在问题出在这里,API 似乎按顺序 执行查询 ,请参阅下面的测试结果:
"latency": {
"min": 1394.1,
"max": 57693,
"median": 30222.7,
"p95": 55396.8,
"p99": 57693
},
并且数据库日志如下:
connection accepted from 127.0.0.1:60770 #1 (1 connection now open)
...
2017-04-10T18:45:55.389+0100 ... 1329ms
2017-04-10T18:45:56.711+0100 ... 1321ms
2017-04-10T18:45:58.016+0100 ... 1304ms
2017-04-10T18:45:59.355+0100 ... 1338ms
2017-04-10T18:46:00.651+0100 ... 1295ms
看起来 API 似乎只使用一个连接,这似乎是正确的,但据我了解,这将自动充分利用 poolSize
并执行这些查询 并发 而不是一次一个。
我在这里做错了什么?如何并行执行这些数据库查询?
编辑 1 - 模型和查询
为了让事情更清楚一些,我使用了以下模型:
const mongoose = require('mongoose');
const db = require('...');
const postcodeSchema = mongoose.Schema({
postcode: { type: String, required: true },
...
location: {
type: { type: String, required: true },
coordinates: [] //coordinates must be in longitude, latitude order.
}
});
//Define the index for the location object.
postcodeSchema.index({location: '2dsphere'});
//Export a function that will allow us to define the collection
//name so we'll pass in something like: GB, IT, DE ect for different data sets.
module.exports = function(collectionName) {
return db.model('Postcode', postcodeSchema, collectionName.toLowerCase());
};
其中 db
对象是此问题顶部解释的连接模块。
我正在使用以下内容执行查询:
/**
* Searches and returns GeoJSON data for a given array of postcodes.
* @param {Array} postcodes - The postcode array to search.
* @param {String} collection - The name of the collection to search, i.e 'GB'.
*/
function search(postcodes, collection) {
return new Promise((resolve, reject) => {
let col = new PostcodeCollection(collection.toLowerCase());
col.find({
postcode: { $in: postcodes }
})
.exec((err, docs) => {
if (err)
return reject(err);
resolve(docs);
});
});
}
下面是如何调用该函数的示例:
search(['ABC 123', 'DEF 456', 'GHI 789'], 'gb_full')
.then(postcodes => {
console.log(postcodes);
})
.catch(...);
重申一下,这些查询是通过 node.js
API 执行的,因此它们应该 已经 是异步的,但是查询本身正在执行一个接一个地。因此,我认为问题可能出在 MongoDB 方面,但我什至不知道从哪里开始寻找。如果已经有一个 运行.
我是 运行 mongod.exe
的实例,位于 Windows 10 机器上。
好的,所以我设法找出问题所在。
首先,MongoDB在发出查询时有一个读锁(参见here)。这就是它按顺序执行查询的原因。进一步改进这一点的唯一方法是 分片 集合。
此外,正如 Jorge 所建议的,我在 postcode 字段上添加了一个索引,这大大减少了延迟。
postcodeSchema.index({ postcode: 1 }); //, { unique: true } is a tiny bit faster.
具体来说,以下是新指标的压力测试结果:
"latency": {
"min": 5.2,
"max": 72.2,
"median": 11.1,
"p95": 17,
"p99": null
},
中值延迟从 30 秒 下降到 11 毫秒,这是一个惊人的改进。
Firstly, MongoDB has a read lock when a query is issued (see here). That's why it was executing queries sequentially. The only way to improve this further is by sharding the collection.
如果您使用 mongo 3.0+ 和 wiredtiger 作为存储引擎,您将拥有文档级锁定。查询不应按顺序执行,分片肯定会有助于并行性,但 2kk 文档对于大多数现代 computer/server 硬件来说应该不是问题。
你在第一个问题中提到了 mongodb 的日志文件,你应该打开了多个连接,是这样吗?