随着收集量的增加(文档数量),Upsert性能下降

时间:2017-12-28 02:06:36

标签: javascript node.js mongodb performance upsert

使用案例

我正在使用REST Api来提供视频游戏的战斗结果。这是一个团队与团队在线游戏,每个团队由3名玩家组成,他们可以从100个不同的角色中选择不同的角色。我想计算每个球队组合的胜负数和平局数。我每秒大约获得1000次战斗结果。我连接每个团队的角色ID(升序)然后我保存每个组合的赢/输和抽奖。

我目前的实施:

const combinationStatsSchema: Schema = new Schema({
  combination: { type: String, required: true, index: true },
  gameType: { type: String, required: true, index: true },
  wins: { type: Number, default: 0 },
  draws: { type: Number, default: 0 },
  losses: { type: Number, default: 0 },
  totalGames: { type: Number, default: 0, index: true },
  battleDate: { type: Date, index: true, required: true }
});

对于每个返回的日志,我执行upsert并将这些查询批量发送(5-30行)到MongoDB:

const filter: any = { combination: log.teamDeck, gameType, battleDate };
if (battleType === BattleType.PvP) {
  filter.arenaId = log.arena.id;
}
const update: {} = { $inc: { draws, losses, wins, totalGames: 1 } };
combiStatsBulk.find(filter).upsert().updateOne(update);

我的问题:

只要我的集合中只有几千个条目combinationStats,mongodb只需要0-2%的cpu。一旦该集合有几百万个文档(由于可能的组合数量很快发生),MongoDB不断需要50-100%的CPU。显然我的方法根本不可扩展。

我的问题:

这些选项中的任何一个都可以解决我上面定义的问题:

  1. 我可以优化上面描述的MongoDB解决方案的性能,以便它不需要那么多CPU吗? (我已经将我过滤的字段编入索引,并且我批量执行upsert)。是否有助于创建一个哈希(基于我的所有过滤器字段),我可以使用它来过滤数据然后提高性能?
  2. 是否有更适合汇总此类数据的数据库/技术?我可以设想更多的用例,我希望/需要增加给定标识符的计数器。
  3. 编辑:在khang评论说它可能与upsert性能有关后,我将$inc替换为$set,实际上性能同样“差”。因此,我尝试了建议的find(),然后手动update()方法,但结果没有变得更好。

2 个答案:

答案 0 :(得分:1)

在(1.)中,断言您批量执行upsert。但根据这看起来如何扩展,你可能会在每批中发送太少的行。每次存储行加倍时,请考虑将批量大小加倍。请为您的设置发布mongo的explain()查询计划。

在(2.)中,您考虑切换到,例如,mysql或postgres。是的,这绝对是一个有效的实验。同样,请务必在时间数据旁边发布EXPLAIN输出。

只有一百万个可能的团队成员,并且有一些分布,有些比其他人更受欢迎。您只需要维护一百万个计数器,这不是一个很大的数字。但是,执行1e6磁盘I / O可能需要一段时间,特别是如果它们是随机读取。考虑远离磁盘驻留数据结构,您可能正在进行频繁的COMMIT,并切换到内存驻留散列或b树。听起来ACID类型的持久性保证对您的应用程序非常重要。

此外,一旦你组装了#34;大"输入批次,肯定超过一千,也许大约一百万,确保在处理之前对批次进行排序。那么你的计数器维护问题看起来就像内部存储器或外部存储器上的merge-sort。

缩放批量的一种原则性方法是在一些方便大小的已排序内存缓冲区中累积观察值,并且当缓冲区中不同团队成分的数量超过某个阈值K时,仅从该流水线阶段释放聚合(计数)观察值.Mongo或者你的管道中的下一个阶段。如果K远远超过1e6的1%,那么即使对存储在磁盘上的计数器进行顺序扫描,也很有可能在每个读取的磁盘块上找到有用的更新工作。

答案 1 :(得分:1)

在过滤条件上创建哈希:

我能够将CPU从80-90%降低到1-5%,并且经历了更高的吞吐量。

显然过滤器是问题所在。而不是在这三个条件上进行过滤:{ combination: log.teamDeck, gameType, battleDate }我在节点应用程序中创建了128位哈希。我使用此哈希进行upserting并将组合,gameType和battleDate设置为我的更新文档中的附加字段。

为了创建哈希,我使用了metrohash库,可以在这里找到:https://github.com/jandrewrogers/MetroHash。不幸的是,我无法解释为什么表现会好得多,特别是因为我将所有以前的条件编入索引。