Улучшение неэффективного запроса

#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)