Rails counter_cache для Model.count без какой-либо ассоциации, чтобы сделать SELECT COUNT (*) быстрее

Я суммирую строки в одной из своих моделей, используя Model.count, и меня немного беспокоит производительность, так как в конечном итоге эта модель станет очень большой и, следовательно, SELECT COUNT (*) очень медленной.

Есть ли способ использовать counter_cache без отношения :belongs_to? Или другой удобный для производительности способ подсчета строк? Я думал о создании другой модели, просто такой, в которой я храню такие расчеты, но не уверен, что это лучший способ.


person Slick23    schedule 12.05.2011    source источник


Ответы (4)


Еще более тривиальным, чем создание модели 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_create и after_destroy вашей модели и переопределить Model.size вызовом Rails.cache.read.

Вы можете инициализировать кеш всякий раз, когда Rails впервые инициализируется, поместив файл с именем что-то вроде initialize_cache.rb в config/initializers, содержащий:

Rails.cache.write('elephant_count', 0) if Rails.cache.read('elephant_count').nil?
person danneu    schedule 12.05.2011

Если вы вообще хотите иметь поддерживаемый счетчик, независимо от того, используете ли вы counter_cache или делаете это вручную, Rails будет поддерживать ваши счетчики с помощью обратных вызовов, которые будут увеличивать/уменьшать счетчик при создании/уничтожении нового потомка.

Я не знаю способа сохранить counter_cache без использования отношения belongs_to, потому что только родитель может хранить количество дочерних элементов.

Производительность взвешивания

Если ваша таблица станет «большой», заполните тестовую базу данных большим количеством строк, а затем начните выполнять некоторые SQL-запросы, используя EXPLAIN, чтобы получить производительность ваших запросов к базе данных. Посмотрите, компенсируется ли падение производительности при создании/удалении записей с помощью counter_cache тем, как часто вам нужно обращаться к этим счетчикам в первую очередь.

Если счетчику не требуется постоянная точность на 100 %, вы можете вместо этого периодически обновлять кэши с помощью задания cron или фонового исполнителя.

В итоге:

  1. Вы должны использовать counter_cache только в том случае, если вам нужно, чтобы этих счетчиков было достаточно, чтобы компенсировать немного большее время, необходимое для создания/уничтожения записи.
  2. Насколько мне известно, использование counter_cache по сравнению с ручной альтернативой, использующей обратные вызовы, вряд ли приведет к значительному снижению производительности.
  3. Если кэш не должен быть точным, воспользуйтесь этим и выполняйте вычисления реже.
person sscirrus    schedule 12.05.2011

Взгляните на http://guides.rubyonrails.org/caching_with_rails.html. Я хочу взглянуть на раздел, посвященный хранилищам кеша. Используя кеш-хранилища, вы можете хранить значения в кеше для произвольных вещей.

Например, у вас может быть метод, вызываемый в Модели с именем get_count, который изначально будет заполнен счетчиком, но увеличен на 1 с обратным вызовом after_create. Если нет необходимости поддерживать его в актуальном состоянии, вы можете обновлять его каждые x минут, чтобы быть в основном точным.

Я лично использую memcache как хранилище для подобных вещей. Просто убедитесь, что вы обновляете кеш в соответствии с вашими потребностями.

person Robert    schedule 12.05.2011

Как насчет определения проблемы 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_create и after_destroy заботятся об обновлении MyNumerousModel.cached_count.

person Darme    schedule 15.01.2021