Обновление огромного количества записей - оптимизация производительности - PullRequest
6 голосов
/ 16 ноября 2011

У меня есть инструмент для бейсбола, который позволяет пользователям анализировать историческую статистику ватина игрока.Например, сколько хитов у A-Rod за последние 7 дней в ночных условиях?Я хочу расширить временные рамки, чтобы пользователь мог анализировать статистику ватина игрока вплоть до 365 дней.Однако это требует серьезной оптимизации производительности.Вот мой текущий набор моделей:

class AtBat < ActiveRecord::Base
  belongs_to :batter
  belongs_to :pitcher
  belongs_to :weather_condition

  ### DATA MODEL ###
  # id
  # batter_id
  # pitcher_id
  # weather_condition_id
  # hit (boolean)
  ##################
end

class BattingStat < ActiveRecord::Base
  belongs_to :batter
  belongs_to :recordable, :polymorphic => true # e.g., Batter, Pitcher, WeatherCondition

  ### DATA MODEL ###
  # id
  # batter_id
  # recordable_id
  # recordable_type
  # hits7
  # outs7
  # at_bats7
  # batting_avg7
  # ...
  # hits365
  # outs365
  # at_bats365
  # batting_avg365
  ##################
end

class Batter < ActiveRecord::Base
  has_many :batting_stats, :as => :recordable, :dependent => :destroy
  has_many :at_bats, :dependent => :destroy
end

class Pitcher < ActiveRecord::Base
  has_many :batting_stats, :as => :recordable, :dependent => :destroy
  has_many :at_bats, :dependent => :destroy
end

class WeatherCondition < ActiveRecord::Base
  has_many :batting_stats, :as => :recordable, :dependent => :destroy
  has_many :at_bats, :dependent => :destroy
end

Чтобы держать мой вопрос в разумных пределах, позвольте мне рассказать, что я делаю, чтобы обновить таблицу batting_stats вместо того, чтобы копировать кучу кода.Давайте начнем с 7 дней.

  1. Извлечение всех записей at_bat за последние 7 дней.
  2. Итерация по каждой записи at_bat…
  3. Учитывая запись at_bat, захватитьсоответствующий тест и соответствующее условие погоды, найдите правильную запись batting_stat (BattingStat.find_or_create_by_batter_and_recordable (batter, weather_condition)), затем обновите запись batting_stat.
  4. Повторите шаг 3 для теста и кувшина (записи).*

    Шаги 1-4 повторяются и для других периодов времени - 15 дней, 30 дней и т. Д.

    Теперь я представляю, как трудно было бы каждый день запускать скрипт для выполнения этих обновленийесли бы я увеличил периоды времени с 7/15/30 до 7/15/30/45/60/90/180 / 365.

    Итак, мой вопрос, как бы вы подошли к получению этогоработать на высочайшем уровне производительности?

Ответы [ 4 ]

3 голосов
/ 18 ноября 2011

AR на самом деле не предназначен для массовой обработки, подобной этой.Вам, вероятно, лучше делать свои пакетные обновления, перейдя к собственно SQL и выполнив INSERT FROM SELECT (или, возможно, используя гем, который сделал это для вас.)

1 голос
/ 16 ноября 2011

По сути, вам нужно хранить данные таким образом, чтобы вы могли выделить последний день и заменить его новым первым днем, чтобы вам не пришлось пересчитывать итоговые данные.

Один из способов сделать это - сохранить предыдущее значение сложения и вычесть из него значение последнего дня, а затем добавить новое значение дня и затем разделить его на 15/30/90/365.

Что превращает 366 операций в 3. Теперь считывание из базы данных выполняется медленнее, чем 363 операции?

Это также экономит ваши итерации, поэтому все, что вам нужно делать, это каждый день проверять, какойпогодные условия должны быть обновлены.

0 голосов
/ 15 декабря 2011

Когда мне приходилось выполнять такую ​​работу раньше, я выкидывал ссылки на SQL и освежал свои мысли о том, как выполнять сложные обновления. Обычно вы можете сделать много коротких обновлений с хорошим запросом. Кроме того, вы сможете найти прямую помощь с запросом (опубликуйте свою схему и начните запросы в гисте, если они действительно огромны)

Мне недавно пришлось заполнить значение counter_cache, и перед тем, как сделать это в виде набора кода ruby, загружающего родителей и подсчитывающего их детей, я дал этому запросу шанс:

UPDATE rates r SET children_count = child_counts.my_count from (SELECT parent_id, count(*) as my_count FROM rates GROUP BY parent_id having parent_id is not null) as child_counts where child_counts.parent_id = r.id;

, который обновил 200 тыс. Строк всего за несколько секунд

Если вы не можете сделать это в одном запросе, и если это одноразовая операция, вы можете разбить ваш процесс на 2 шага. Сначала сделайте тяжелую работу и сохраните результаты в новой таблице, затем прочитайте из этой таблицы и сделайте окончательное обновление. В последнее время мне пришлось провести массивное агрегирование данных, и все тяжелые работы заняли 2 дня обработки и расчетов. Результат был помещен в новую таблицу с идентификатором соответствующей строки и итоговой суммой. В процессе производства у меня был быстрый скрипт, который считывал из этой новой таблицы и обновлял соответствующие строки. Это также позволило мне остановиться и перезапустить с того места, где я остановился, и предварительно проверить результаты перед обновлением продукта. Кроме того, это сделало обновление продукта очень быстрым.

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

0 голосов
/ 24 ноября 2011

У нас похожая проблема с пакетной загрузкой 600 000 записей данных аренды в США каждую неделю. Для последовательной обработки каждой записи потребуется более 24 часов. Но это не обязательно была база данных, которая была узким местом - хотя каждая вставка занимала фиксированное время, база данных не была максифицирована / привязана / выровнена действием.

Я знал, что разбить файл на отдельные строковые записи было легко и быстро. В нашем случае входной файл был в форме XML, и я использовал простой Java StringTokenizer, чтобы разделить файл на теги ....

Это быстро дало мне большой массив фрагментов XML, содержащих информацию о арендуемой собственности, необходимую для анализа и импорта.

Затем я использовал соглашение Java ThreadPoolExecutor / FutureTask / Callable, чтобы создать пул из 20 потоков, который будет принимать каждый фрагмент XML в качестве входных данных, извлекать соответствующие данные и выполнять вставку базы данных. Я не знаю, какой будет ваша архитектура, но я предполагаю, что есть нечто подобное.

В итоге я смог настроить размер пула потоков, чтобы максимизировать пропускную способность записи, наблюдая за нагрузкой сервера базы данных в различных условиях тестирования. Мы остановились на размере резьбы 25.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...