【问题标题】:Mongoose Populate Cache猫鼬填充缓存
【发布时间】:2020-05-09 01:42:56
【问题描述】:

我有许多不同的猫鼬模式通过 id 字符串相互引用。

我正在使用 redis 来缓存 mongoose 文档。

例如,getUser(id) 将返回一个先前缓存的用户对象(如果存在),否则它会调用 mongoose find。

使用 mongoose 引用并使用填充会感觉更美观。

但是,据我了解,它只是 find 的语法糖,没有任何缓存层。

主要问题

什么时候应该使用 mongoose 填充而不是缓存层,在使用 mongoose 的稳定高流量应用程序中的最佳实践是什么?

指导子问题

  1. 猫鼬填充真的适合高流量应用吗?
  2. 使用填充而不是缓存文档有什么好处 我自己?
  3. 自己缓存模型(例如使用 redis)在性能方面是否可以忽略不计?
  4. 最佳做法是什么?使用什么大型应用程序公司 猫鼬呢?
  5. 你会混合填充猫鼬引用和缓存层吗 取决于不同的用例,或者你会选择一个并成为 符合吗?

示例用例

这是我的应用程序中的一个常见的简单示例。

我有 3 个集合:用户、应用程序、研究所。

  1. 用户有一个 App 的引用
  2. 应用有一个对 Institute 的引用

现在我是:

  1. 从缓存层获取用户,其中包含一个 app_id
  2. 从缓存层获取应用,其中包含 Institute_id
  3. 从缓存层获取机构

给定一个用户,从缓存层获取应用程序和机构实际上是 O(1)。

但是,如果我选择进行纯猫鼬填充,则需要对数据库进行 2 次额外的查找调用 - 用于应用程序,然后用于研究所。

我需要用户在向服务器发送的每个经过身份验证的请求中填充应用程序和机构。

当然还有更复杂的用例,但这是最常见的一种。

我最简单的请求平均需要填充 4 个引用,而更复杂的请求可以填充更多。

【问题讨论】:

  • 取决于你如何“烹饪”你的模型 :) 根据我的经验,我每秒可以向 mongodb 插入 100 000 个文档,但我承认服务器有 32 gigs 的 ram 和 8 个线程(4 核) .还取决于业务逻辑您可以自定义_id(字符串),这可以帮助通过id获取文档而不是通过条件查询。 (例如:users collection: _id = "someusername" 不在username 字段上创建索引)
  • 如果您想要最佳实践,请更好地分享您的模型(架构)和用例。这将帮助 SO 成员提供最佳解决方案。
  • 听起来很多,但它确实是一台强大的机器。就您而言,您是否使用填充?在我的业务逻辑中,我实际上对普通的旧自动生成的 ID 感到满意。我在原始问题中添加了一个简单的用例。
  • 一切都取决于用例。关于您的用例:无论如何您正在向缓存层发出请求,所以如果缓存层不能承受高负载怎么办? :) 通过 id 获取文档也比通过某些字段获取文档更快(即使它已被索引)。复杂的用例:我认为与其拥有缓存层,不如拥有 Elastic Search 来响应变化并准备好使用包含必要数据的搜索索引。我使用消息总线(事件总线)并通过收集和存储将所有关系数据保存在 CouchBase 中的数据集来对更改做出反应
  • 在任何情况下,如果是通过_id 获取文档,对数据库进行额外查询或对缓存进行第二次查询都不是什么大问题。考虑在某个文件夹中打开一些名称类似于 _id 的文件。但是对于复杂的用例,请使用 ElasticSearch 或自己的工作人员,这些工作人员将预取数据并将其放在单独的地方以加快访问速度。附言不要忘记放置适当的索引以提高查找速度(可能会花费额外的存储空间)

标签: node.js mongoose


【解决方案1】:

这是我对两者优缺点的一些理解。

猫鼬种群的优点

  • 无需额外设置缓存(更简单的基础架构)
  • 可以深度填充(多层次填充)
  • 它可以从多个数据库中填充。
  • 这是一个简单干净的语法
  • 缓存和数据库之间不需要同步,因为它是“单一”事实来源。

猫鼬种群的缺点

  • 数据库适用于每个填充和查询,而不是您的服务器或缓存层。如果在同一实例上有大量写入,如果需要重新计算索引或进行处理器密集型查询,这将影响某些写入的性能。
  • 依靠 mongoose 和 MongoDB 数据库的内部工作。
  • 需要控制,因为深度填充可能会因多个级别而失控。

缓存层的优点

  • 可以是多级缓存。每台服务器一些和一个全局缓存。
  • 使用缓存引擎的特殊力量。
  • 将一些工作卸载到缓存并可能卸载到数据库。

缓存层的缺点

  • 需要在缓存和数据库之间同步状态
  • 更多基础设施。
  • 更多代码(如果你想要一个干净的抽象)

总体上回答您的子问题, 1. 填充可能在一些高流量应用程序中用于无法缓存且需要实时或不经常执行的内容。

  1. 使用填充而不是缓存更简单,更少的基础设施,更少的代码,没有同步。

  2. 根据我的经验,我会选择缓存,因为它在大型数据库上会更快。当扩展数据库时,往往需要更多的 CPU 并花费更多的钱。另一方面,缓存更便宜并且可以很好地扩展。此外,可以缓存每个实例。即我的服务器在访问远程缓存之前有一个本地缓存。这使得性能非常快,但它可能会影响服务器性能,具体取决于托管。

  3. 我不在一家大公司,但我们的产品需要交易信息和固定状态。填充可以用于这种情况,因为数据库是唯一的事实来源,我们不希望有不正确的状态。由于我们数据库的复制,它不是单一来源,但至少我们会接近数据库。我们在其他任何地方都使用缓存。我们有多个数据库和多个数据库类型,缓存为我们提供了更高的性能。我们面向微服务的架构也从缓存中受益匪浅,并确保数据并非全部在同一个数据库中,但仍然可以快速访问。

  4. 是的,混合是一个不错的选择,具体取决于用例。一般提示是了解潜在热点并尝试分散工作负载以确保基础架构的一部分不是瓶颈。

最后提示:如有疑问,请确保在数据层和代码层之间保持代码接口。如果需要使用 ElasticSearch 而不是 Redis 或任何其他缓存服务,这种抽象非常有用。代码接口将推迟作出承诺的需要。

示例:不要直接在一段代码中使用App.populate,而是在您的架构中添加一个方法getFullApp(),该方法调用this.populate()


const AppSchema = new mongoose.Schema({...});

AppSchema.static({
   getFullApp(query) {
      return this.find(query).populate()
   }
})

module.exports = mongoose.model("App", AppSchema);

如果你想摆脱填充,只有一个地方可以改变它或摆脱猫鼬getFullApp是你的代码接口的功能。

【讨论】:

    猜你喜欢
    • 2016-06-10
    • 2012-11-11
    • 2015-07-13
    • 2016-10-10
    • 1970-01-01
    • 2019-09-16
    • 2015-07-13
    • 2020-01-17
    相关资源
    最近更新 更多