#ruby-on-rails #ruby #database #postgresql #activerecord
#ruby-on-rails #ruby #База данных #postgresql #activerecord
Вопрос:
У меня возникла проблема, из-за которой у моего приложения истекает время ожидания, потому что запрос занимает слишком много времени. Я использую бесплатный уровень Heroku, поэтому я точно не получаю приоритет по скорости, и поэтому я получаю тайм-ауты, которые я не вижу локально. Я надеялся, что кто-нибудь сможет увидеть проблему с моим запросом, которая позволит мне ускорить работу.
Мой запрос выглядит следующим образом:
def index
# file = File.read('appcontrollersrecipe.csv')
clear_database
file = get_file
recipe_array = file.split("n")
dbUser = User.find_by(id: 999999999)
recipe_array.each do |recipe|
# I'm saving here becuase I need the ID later
dbRecipe = dbUser.recipes.create
recipe = recipe.split(",")
url_index = recipe.length - 1
img_url_index = recipe.length - 2
recipe.each_with_index do |item, index|
if index == 0
dbRecipe.name = item.strip.downcase
dbRecipe.save
elsif index == url_index
dbRecipe.url = item
dbRecipe.save
elsif index == img_url_index
dbRecipe.img_url = item
dbRecipe.save
elsif index.odd?
count = item
food = recipe[index 1]
dbIngredient = Ingredient.new
dbFood = Food.find_by_name(food)
if dbFood.nil?
dbFood = Food.new
dbFood.name = food.strip.downcase
dbFood.save
end
# populate ingredient
dbIngredient.unit_type = item.split(" ").last
dbIngredient.quantity = item.split(" ").first
# I'm saving so much above because I need the id's
dbIngredient.recipe_id = dbRecipe.id
dbIngredient.food_id = dbFood.id
dbIngredient.save
end
end
end
end
Мои данные состоят из рецептов, которые хранятся в файле CSV. Существует около 300 строк, которые выглядят следующим образом:
"Sirloin Steak with Blue Cheese Compound Butter,0.6 oz, Butter,2 count, Garlic Cloves,2 count, Green Onions,12 oz, Fingerling Potatoes,8 oz, Green Beans,12 oz, Sirloin Steaks,1 oz, Blue Cheese,https://homechef.imgix.net/https://asset.homechef.com/uploads/meal/plated/2543/2543SirloinSteakwithBlueCheeseCompoundButterReshoot2__1_of_1_-b04048840f58000cef80b38fc3f77856-b04048840f58000cef80b38fc3f77856.jpg?ixlib=rails-1.1.0amp;w=425amp;auto=formatamp;s=eeba60ce35bcee4938a11286cbea0203,https://www.homechef.com/meals/sirloin-steak-with-blue-cheese-compound-butter
Teriyaki Ginger-Glazed Salmon,1 Tbsp, Chopped Ginger,2 count, Garlic Cloves,2 count, Green Onions,8 oz, Carrot,2 count, Heads of Baby Bok Choy,1 count, Red Fresno Chile,2 oz, Teriyaki Glaze,12 oz, Salmon Fillets,https://homechef.imgix.net/https://asset.homechef.com/uploads/meal/plated/3429/3429TeriyakiGinger-GlazedSalmonReshoot3__1_of_1_-73adcd6ad23cc72b28fdba85387fa18a-73adcd6ad23cc72b28fdba85387fa18a.jpg?ixlib=rails-1.1.0amp;w=425amp;auto=formatamp;s=9e6b37380203ec5a58a5ddb906b5ae8b,https://www.homechef.com/meals/teriyaki-ginger-glazed-salmon
Al Pastor Pork Flautas,1 count, Shallot,1 count, Lime,3 oz, Pineapple Chunks,1 oz, Queso Fresco,12 oz, Ground Pork,1 tsp, Chipotle Seasoning,6 count, Small Flour Tortillas,0.5 oz, Baby Arugula,1 oz, Sour Cream,10 oz, Ground Beef,https://homechef.imgix.net/https://asset.homechef.com/uploads/meal/plated/4290/4290AlPastorPorkFlautasFinal2__1_of_1_-4e7fe04ac157a463b4d93eb57e9b93f9-4e7fe04ac157a463b4d93eb57e9b93f9.jpg?ixlib=rails-1.1.0amp;w=425amp;auto=formatamp;s=de2e2403d7261f2697567faf5f477359,https://www.homechef.com/meals/al-pastor-pork-flautas
Ответ №1:
Вы пытаетесь сделать слишком много за один цикл http-запроса / ответа. Для каждой строки в вашем CSV вы выполняете sql INSERT
(с dbUser.recipes.create
), UPDATE
(с dbRecipe.save
), а также SELECT
(с Food.find_by_name(food)
).
Даже если вы произведете некоторую оптимизацию, уверены ли вы, что в CSV будет всего ~ 300 строк за все время работы вашего приложения? И даже если ответ положительный, вообще говоря, хорошей практикой является реагировать на действия пользователя как можно быстрее, вместо того чтобы заставлять их смотреть, как их браузер ожидает ответа.
Итак, я рекомендую вам пересмотреть свой подход. Если для выполнения одного действия требуется выполнить множество команд sql, подумайте о способах выполнения задачи асинхронно. Это то, что нравится инструментам ActiveJob
(https://edgeguides.rubyonrails.org/active_job_basics.html ) и sidekiq (https://github.com/mperham/sidekiq /) были разработаны для.
Например, разработайте свое приложение таким образом, чтобы пользователь нажимал какую-либо кнопку для загрузки CSV и отвечал: «Спасибо за ваше представление, мы работаем над этим!». Пользователь всегда может вернуться и проверить статус обрабатываемого файла или обновить экран. Или вы могли бы получить более сложные и автоматизированные проверки состояния с помощью опроса AJAX или двунаправленной связи через websockets. На Railsy это можно было бы сделать с помощью ActionCable
(https://guides.rubyonrails.org/action_cable_overview.html).
Комментарии:
1. Я использую это как способ заполнить свою базу данных prod из CSV, который создается webscraper. В blue moon это должно произойти только один раз, поскольку я разрабатываю приложение, и страница доступна только мне. После того, как я закончу разработку, я больше не буду использовать этот метод.
2. Вы должны использовать начальную базу данных, она не будет показывать вам тайм-аут, поскольку выполняется в фоновом режиме в командной строке rails db:seed
Ответ №2:
На самом деле это не запрос, а целая куча других вещей. Вы анализируете файл и выполняете запись в базу данных на основе содержимого этого файла. Кроме того, если все, что вы делаете, это заполняете базу данных, нет причин вообще подключать приложение или браузер. Эту задачу было бы лучше выполнить с помощью задачи rake или из консоли rails. Вы можете избежать тайм-аута базы данных, разбив действия на отдельные части, например
- загрузите файл в память
- для каждой строки файла выполните запись в базу данных и сохраните запись
В любом случае вам следует полностью удалить это заполнение базы данных из приложения.
Ответ №3:
Вы могли бы использовать activerecord-import gem, основная идея которого заключается в загрузке множества записей одним запросом.
Вы должны указать имена столбцов в виде массива:
column_names = [:name, :quantity, :unit_type, :food_id]
Далее вы должны загрузить данные из csv в том же порядке:
row_values = [
["Sirloin Steak with Blue Cheese Compound Butter", 1, "Butter", 2],
["Something else", 2, "else", 32],
...
]
и импорт:
DbIngredient.import(column_names, row_values)