Обработка 500 тыс. записей приводит к сбою сервера. Как это пакетировать?

У меня есть таблица базы данных, содержащая 500 тыс. записей. Мне нужно загрузить эти записи, выполнить вызов API, получить данные из этой конечной точки API и сохранить новые данные обратно в базу данных.

Я пытался сделать это так (упрощенный код):

task get_new_data: :environment do
  data = Model.where('column IS NOT NULL).order('id DESC')
  data.each do |d|
    puts '...info print...'
    api_call = API::Call(...)
    d.assign_attributes(attr1: api_call.data1,
                        attr2: api_call.data2,
                        ...)
    d.save!(validate: false)
    put 'another info print...'
    sleep(0.5)
  end
end

Итак, когда я запустил этот код, произошло то, что, возможно, в течение первых 3-4 минут я ничего не видел в окне терминала, когда запускал эту команду rake на сервере. После этого я начал видеть утверждения puts. Еще через несколько минут (может быть, 5) окно терминала зависло. В таблице базы данных я увидел, что записи обновились, поэтому подумал, что «сломалось» только окно терминала.

Еще через 15-20 минут процессор и оперативная память сервера были загружены до 100%, и я не смог подключиться к серверу по SSH (чего и следовало ожидать из-за 100% использования процессора и ОЗУ). Точно так же приложение Rails было недоступно — синий экран ошибки (тоже ожидалось).

В этот момент я просто ждал, когда сервер полностью рухнет. Еще через 30 минут или около того память ЦП/ОЗУ освободилась, приложение Rails снова стало доступным – задача рейка завершилась. Но он не завершился полностью, обработал «всего» около 350 тысяч записей, поэтому мне пришлось снова запустить задачу rake, чтобы завершить оставшиеся 150 тысяч записей. Теперь второй запуск завершился успешно.

Дело в том, что мне нужно будет запустить аналогичную задачу и перебрать 500 тысяч записей как минимум еще 6 раз, и я не хочу проходить через опыт, аналогичный описанному выше.

Как правильно обрабатывать такие наборы данных, не убивая сервер? Должен ли я каким-то образом группировать записи, скажем, по 10 тысяч кусков? Или есть лучший способ справиться с этим по-другому? В настоящее время я не использую Sidekiq в проекте.

Можно ли группировать вызовы API с помощью Typheos? Ограничением здесь действительно будет HTTP IO, а не ваша база данных.

max 12.06.2024 09:30

Я не знаю Typeos, надо посмотреть, как это работает. Что касается вызова API, провайдер ограничивает меня выполнением не более 2 вызовов API в течение 1 секунды, поэтому я ставлю sleep в конце цикла (я не включил его в исходное сообщение).

user984621 12.06.2024 09:34

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

max 12.06.2024 09:41

@Stefan при рекордах на 500 м, работающих с оптимальной скоростью два запроса в секунду, для выполнения задачи потребуется чуть меньше 8 лет. О боже.

max 12.06.2024 11:33

«Как правильно обрабатывать такие наборы данных, не убивая сервер?» — вместо встроенной обработки моделей я бы создал фоновое задание для каждой модели, которую необходимо обновить, и поручил бы работнику обрабатывать их асинхронно (в пределах ограничений API). Это также позволяет легко повторить задания, которые не удалось выполнить из-за проблем с сетью.

Stefan 12.06.2024 11:59

Извиняюсь и спасибо, что заметили. Это действительно 500к, а не М!

user984621 12.06.2024 13:59
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
118
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Rails поддерживает пакетную обработку записей, и ее легко реализовать.

Вместо

 data.each do |d|

Использовать

 data.find_each do |d|

По умолчанию размер пакета ограничен 1000 записями, но вы можете переопределить это с помощью аргумента :batch_size.

Официальная документация здесь...

https://api.rubyonrails.org/classes/ActiveRecord/Batches.html

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

max 12.06.2024 09:43
sleep(0.5) тоже выглядит не очень. Если какой-то запрос завершается неудачно, все задачи завершаются неудачей, необходимо перехватить исключение
mechnicov 12.06.2024 12:08

Использование find_in_batch и find_each решит проблемы с оперативной памятью и процессором. Однако я вижу проблему со слишком большим количеством вызовов API. Существует ли API, который извлекает информацию в пакетном режиме, или вы можете создать его на стороне источника данных?

К сожалению не могу. Я могу либо использовать этот API-сервис бесплатно (с ограничением количества запросов в 1 секунду), либо использовать другую платную альтернативу. Однако из-за характера проекта мне не разрешено нести новые расходы.

user984621 12.06.2024 13:58

Другие вопросы по теме