首页 > 解决方案 > 猫鼬填充缓存

问题描述

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

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

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

改为使用猫鼬引用并使用填充会感觉更美观。

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

主要问题

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

指导子问题

  1. mongoose populate 真的适合高流量应用吗?
  2. 自己使用填充而不是缓存文档有什么好处吗?
  3. 自己缓存模型(例如使用redis)在性能方面可以忽略不计吗?
  4. 最佳做法是什么?使用猫鼬的大型应用程序公司是做什么的?
  5. 您会根据不同的用例混合填充猫鼬引用和缓存层,还是选择一个并与之保持一致?

示例用例

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

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

  1. 用户拥有 App 的引用
  2. 应用程序有一个参考研究所

现在我是:

  1. 从缓存层获取用户,其中包含一个 app_id
  2. 从缓存层获取应用程序,其中包含 Institute_id
  3. 从缓存层取研究所

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

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

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

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

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

标签: node.jsmongoose

解决方案


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

猫鼬种群的优点

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

猫鼬种群的缺点

  • 该数据库适用于每个填充和查询,而不是您的服务器或缓存层。如果在同一实例上有大量写入,如果需要重新计算索引或进行处理器密集型查询,这将影响某些写入的性能。
  • 依赖猫鼬和 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是你的代码接口的功能。


推荐阅读