Каков наилучший способ хранения многомерного counter_cache? - PullRequest
0 голосов
/ 10 января 2020

В моем приложении у меня есть модель search_volume.rb , которая выглядит следующим образом:

search_volume.rb :

class SearchVolume < ApplicationRecord
   # t.integer "keyword_id"
   # t.integer "search_engine_id"
   # t.date "date"
   # t.integer "volume"
  belongs_to :keyword
  belongs_to :search_engine
end

keyword.rb :

class Keyword < ApplicationRecord
 has_and_belongs_to_many :labels
 has_many :search_volumes

end

search_engine.rb :

class SearchEngine < ApplicationRecord
  belongs_to :country
  belongs_to :language
end

label.rb :

class Label < ApplicationRecord
 has_and_belongs_to_many :keywords
 has_many :search_volumes, through: :keywords

end

На странице ярлыка # index я пытаюсь показать сумму search_volumes для ключевых слов в каждом ярлыке за последний месяц для search_engine, который пользователь приготовил. Я могу сделать это с помощью следующего:

<% @labels.each do |label| %>
  <%= number_with_delimiter(label.search_volumes.where(search_engine_id: cookies[:search_engine_id]).where(date: 1.month.ago.beginning_of_month..1.month.ago.end_of_month).sum(:volume)) %>
<% end %>

Это работает хорошо, но у меня есть ощущение, что вышеупомянутое очень неэффективно. При нынешнем подходе я также затрудняюсь выполнять операции с объемами поиска. Большую часть времени я просто хочу узнать об объеме поиска в прошлом месяце.

Обычно я бы создавал counter_cache для модели ключевых слов, чтобы отслеживать последние параметры search_volume, но, поскольку существуют десятки search_engines, мне пришлось бы создавать по одному для каждого, что также неэффективно.

Какой самый эффективный способ хранить объем поиска за последний месяц для всех различных поисковых систем отдельно?

1 Ответ

1 голос
/ 12 января 2020

Прежде всего, вы можете оптимизировать текущую реализацию, выполнив один запрос для всех задействованных меток, например:

# models
class SearchVolume < ApplicationRecord
  # ...

  # the best place for your filters!
  scope :last_month, -> { where(date: 1.month.ago.beginning_of_month..1.month.ago.end_of_month) }
  scope :search_engine, ->(search_engine_id) { where(search_engine_id: search_engine_id) }
end

class Label < ApplicationRecord
  # ...

  # returns { label_id1 => search_volumn_sum1, label_id2 => search_volumn_sum2, ... }
  def self.last_month_search_volumes_per_label_report(labels, search_engine_id:)
    labels.
      group(:id).
      left_outer_joins(:search_volumes).
      merge(SearchVolume.last_month.search_engine(search_engine_id)).
      pluck(:id, 'SUM(search_volumes.volume)').
      to_h
  end
end

# controller
class LabelsController < ApplicationController
  def index
    @labels = Label.all

    @search_volumes_report = 
      Label.last_month_search_volumes_per_label_report(
        @labels, search_engine_id: cookies[:search_engine_id]
      )
  end
end

# view
<% @labels.each do |label| %>
  <%= number_with_delimiter(@search_volumes_report[label.id]) %>
<% end %>

Обратите внимание, что я не тестировал его с той же архитектурой, но с аналогичной модели у меня на моей локальной машине. Это может сработать, настроив несколько вещей.

Мой предложенный подход все еще жив, запрашивая базу данных. Если вам действительно нужно где-то хранить значения, потому что у вас очень большие наборы данных, я предлагаю два решения:
- используя материализованные представления, вы можете обновлять sh каждый месяц (scenic gem предлагает хороший способ обработки представлений в приложении Rails : https://github.com/scenic-views/scenic)
- реализация новой таблицы со стандартными отношениями между моделями, которая может хранить ваши расчеты по идентификаторам и месяцам и которую вы могли бы заполнять каждый месяц с помощью рейковых задач, тогда у вас будет просто чтобы загрузить ваши расчеты

Пожалуйста, дайте мне знать ваши отзывы!

...