У меня есть таблица базы данных, содержащая 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 в проекте.
Я не знаю Typeos, надо посмотреть, как это работает. Что касается вызова API, провайдер ограничивает меня выполнением не более 2 вызовов API в течение 1 секунды, поэтому я ставлю sleep в конце цикла (я не включил его в исходное сообщение).
При такой скорости вам придется выполнять задачу непрерывно почти три дня, что будет проблематично. Возможно, я бы добавил столбец, чтобы пометить строки, которые были обновлены, а затем попросил бы задачу обновить несколько тысяч строк и заставить бегуна периодически перезапускать ее.
@Stefan при рекордах на 500 м, работающих с оптимальной скоростью два запроса в секунду, для выполнения задачи потребуется чуть меньше 8 лет. О боже.
«Как правильно обрабатывать такие наборы данных, не убивая сервер?» — вместо встроенной обработки моделей я бы создал фоновое задание для каждой модели, которую необходимо обновить, и поручил бы работнику обрабатывать их асинхронно (в пределах ограничений API). Это также позволяет легко повторить задания, которые не удалось выполнить из-за проблем с сетью.
Извиняюсь и спасибо, что заметили. Это действительно 500к, а не М!





Rails поддерживает пакетную обработку записей, и ее легко реализовать.
Вместо
data.each do |d|
Использовать
data.find_each do |d|
По умолчанию размер пакета ограничен 1000 записями, но вы можете переопределить это с помощью аргумента :batch_size.
Официальная документация здесь...
https://api.rubyonrails.org/classes/ActiveRecord/Batches.html
Это будет довольно большим улучшением в использовании памяти, но запуск HTTP-вызова настолько затруднит его, что на его выполнение уйдут несколько дней.
sleep(0.5) тоже выглядит не очень. Если какой-то запрос завершается неудачно, все задачи завершаются неудачей, необходимо перехватить исключение
Использование find_in_batch и find_each решит проблемы с оперативной памятью и процессором. Однако я вижу проблему со слишком большим количеством вызовов API. Существует ли API, который извлекает информацию в пакетном режиме, или вы можете создать его на стороне источника данных?
К сожалению не могу. Я могу либо использовать этот API-сервис бесплатно (с ограничением количества запросов в 1 секунду), либо использовать другую платную альтернативу. Однако из-за характера проекта мне не разрешено нести новые расходы.
Можно ли группировать вызовы API с помощью Typheos? Ограничением здесь действительно будет HTTP IO, а не ваша база данных.