Rails counter_cache 对于 Model.count 没有任何关联,是为了让 SELECT COUNT(*) 更快

发布于 2024-11-06 19:45:38 字数 237 浏览 0 评论 0原文

我正在使用 Model.count 计算其中一个模型中的行数,并且有点担心性能,因为最终该模型将变得非常大,因此,SELECT COUNT ( *) 非常慢。

有没有办法在没有 :belongs_to 关系的情况下使用 counter_cache ?或者另一种性能友好的行数计数方式?我考虑过制作另一个模型,只是一个我存储这样的计算结果的模型,但不确定这是最好的方法。

I'm totaling the rows in one of my models using Model.count and am a bit concerned about performance, as eventually, this model will get very large, and, therefore, SELECT COUNT (*) very slow.

Is there a way to use counter_cache without the :belongs_to relationship? Or another performance-friendly way of counting the rows? I thought about making another model, just one where I store calculations like this but not sure that's the best way.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

李白 2024-11-13 19:45:38

比创建 Cache 模型更简单的是仅使用 Rails.cache

Rails.cache.read("elephant_count") #=> nil
Rails.cache.write("elephant_count", 1) #=> true
Rails.cache.read("elephant_count") #=> 1

Rails 默认使用文件存储(tmp/cache)。

然后,您可以将 Rails.cache.write 增量和减量放入模型的 after_createafter_destroy 挂钩中,并使用调用 Rails.cache.read。

每当 Rails 首次初始化时,您都可以通过在 config/initializers 中放置一个名为 initialize_cache.rb 的文件来初始化缓存,其中包含:

Rails.cache.write('elephant_count', 0) if Rails.cache.read('elephant_count').nil?

Even more trivial than making a Cache model is to just use Rails.cache.

Rails.cache.read("elephant_count") #=> nil
Rails.cache.write("elephant_count", 1) #=> true
Rails.cache.read("elephant_count") #=> 1

Rails uses a file store by default (tmp/cache).

Then you could just place a Rails.cache.write increment and decrement into your model's after_create and after_destroy hooks, and override Model.size with a call to Rails.cache.read.

You could initialize the cache whenever Rails first initializes by placing a file named something like initialize_cache.rb in config/initializers containing:

Rails.cache.write('elephant_count', 0) if Rails.cache.read('elephant_count').nil?
沧桑㈠ 2024-11-13 19:45:38

如果您想拥有一个维护的计数器,无论是使用 counter_cache 还是手动执行,Rails 都会使用回调来维护您的计数器,这将在创建/销毁新的后代时增加/减少计数器。

我不知道在不使用“belongs_to”关系的情况下存储“counter_cache”的方法,因为只有父级可以存储子级的计数。

权衡性能

如果您的表将变得“大”,请使用大量行填充您的测试数据库,然后使用 EXPLAIN 开始运行一些 SQL 查询来获取数据库查询的性能。查看使用 counter_cache 创建/销毁记录时的性能损失是否会被您首先需要访问这些计数器的频率所抵消。

如果计数器不需要始终 100% 准确,您可以使用 cron 作业或后台工作程序定期更新缓存。

总之:

  1. 如果您需要这些计数器足以抵消创建/销毁记录所需的稍长的时间,则应该仅使用 counter_cache 。
  2. 据我所知,与使用回调的手动替代方案相比,使用 counter_cache 不太可能对性能造成太大损害。
  3. 如果缓存不需要准确,请利用它并减少执行计算的频率。

If you want to have a maintained counter at all, whether using counter_cache or doing it manually, Rails will maintain your counters using callbacks, which will increase/decrease the counter when a new descendant is created/destroyed.

I am not aware of a means to store a counter_cache without using the belongs_to relationship, because only the parent can store the count of the children.

Weighing Performance

If your table is going to get 'large', populate your test database with a large number of rows then start running some SQL queries using EXPLAIN to get the performance of your database queries. See if the performance hit in doing record creation/destruction with counter_cache is offset by how often you need to access these counters in the first place.

If the counter does not need to be 100% accurate at all times, you can instead update the caches periodically using a cron job or background worker.

In summary:

  1. You should only use counter_cache if you need those counters enough to offset the slightly longer time taken to create/destroy a record.
  2. Using counter_cache vs a manual alternative that uses callbacks is, as far as I am aware, unlikely to result in much of a detriment to performance.
  3. If the cache does not need to be accurate, take advantage of that and perform the calculations less often.
清风挽心 2024-11-13 19:45:38

查看http://guides.rubyonrails.org/caching_with_rails.html
具体来说,您需要查看有关缓存存储的部分。使用缓存存储,您可以将任意事物的值存储到缓存中。

例如,您可以在模型上调用一个名为 get_count 的方法,该方法最初由计数填充,但通过 after_create 回调递增 1。如果不需要保持最新,您可以每 x 分钟更新一次,以便基本准确。

我个人使用 memcache 作为此类内容的存储。只需确保根据您的需要保持缓存最新即可。

Take a look at http://guides.rubyonrails.org/caching_with_rails.html
Specifically, you'll want to take a look at the section regarding cache stores. Using cache stores, you can store values into cache for arbitrary things.

For example, you could have a method called on the Model called get_count which would be filled initially by the count but incremented by 1 with an after_create callback. If it's not necessary to keep it up to date, you can update this every x minutes so that you're mostly accurate.

I personally use memcache as a store for things like this. Just make sure you keep the cache up to date according to your needs.

锦上情书 2024-11-13 19:45:38

像这样定义一个 CachedCount 关注点怎么样?

module CachedCount
  extend ActiveSupport::Concern

  included do
    after_create :increment_cached_count
    after_destroy :decrement_cached_count
  end

  class_methods do
    def count
      return cached_count if cached_count
      Rails.cache.write(cached_count_key, super)
      Rails.cache.read(cached_count_key) || super # fallback because in some Rails env. Rails.cache may not be available
    end

    def cached_count_key
      "#{model_name.collection}_count"
    end

    def cached_count
      Rails.cache.read(cached_count_key)
    end
  end

  def increment_cached_count
    return self.class.count unless self.class.cached_count
    Rails.cache.write(self.class.cached_count_key, self.class.cached_count + 1)
  end

  def decrement_cached_count
    return self.class.count unless self.class.cached_count
    Rails.cache.write(self.class.cached_count_key, self.class.cached_count - 1)
  end
end

然后将其包含在您的计算模型中:

class MyNumerousModel
include CachedCount

# [...]

end

现在,每次调用 MyNumerousModel.count 时,您实际上是在调用关注点中的类方法。当您创建或销毁 MyNumerousModel 的一个实例时,after_createafter_destroy 回调负责更新 MyNumerousModel.cached_count

How about defining a CachedCount concern like this?

module CachedCount
  extend ActiveSupport::Concern

  included do
    after_create :increment_cached_count
    after_destroy :decrement_cached_count
  end

  class_methods do
    def count
      return cached_count if cached_count
      Rails.cache.write(cached_count_key, super)
      Rails.cache.read(cached_count_key) || super # fallback because in some Rails env. Rails.cache may not be available
    end

    def cached_count_key
      "#{model_name.collection}_count"
    end

    def cached_count
      Rails.cache.read(cached_count_key)
    end
  end

  def increment_cached_count
    return self.class.count unless self.class.cached_count
    Rails.cache.write(self.class.cached_count_key, self.class.cached_count + 1)
  end

  def decrement_cached_count
    return self.class.count unless self.class.cached_count
    Rails.cache.write(self.class.cached_count_key, self.class.cached_count - 1)
  end
end

Then you include it in your numerous model:

class MyNumerousModel
include CachedCount

# [...]

end

Now, every time you call MyNumerousModel.count you're actually calling the class method in the concern. And when you create or destroy one instance of MyNumerousModel, the after_create and after_destroy callbacks take care of updating MyNumerousModel.cached_count.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文