Как я могу ускорить простой запрос к базе данных Rails?

#ruby-on-rails #ruby #ruby-on-rails-4

#ruby-on-rails #ruby #ruby-on-rails-4

Вопрос:

Я использую rails 4.2.11.1 на ruby 2.6.3

У меня были чрезвычайно медленные запросы с использованием rails, поэтому я провел сравнительный анализ своего кода и нашел главного виновника. Большая часть замедления происходит при вызове базы данных, когда я выбираю одну строку из таблицы в базе данных. Я попробовал несколько разных версий одной и той же идеи

Используя эту версию

 Rails.logger.info Benchmark.measure{
  result = Record.find_by_sql(['SELECT column FROM table WHERE condition']).first.column
}
  

в выходных данных rails говорится, что sql занимает 54,5 мс, но тест выводит 0,043427 0,006294 0,049721 (1,795859), а общий запрос занимает 1,81 секунды. Когда я запускаю приведенный выше sql непосредственно в своем терминале postgres, это занимает 42 мс.

Очевидно, проблема не в том, что мой sql работает медленно. 42 миллисекунды не заметны. Но 1,79 секунды — это слишком медленно и создает ужасный пользовательский интерфейс.

Я немного почитал и пришел к выводу, что замедление было вызвано созданием объекта rails (что кажется странным, но, по-видимому, это может быть очень медленно), поэтому я попытался использовать pluck, чтобы минимизировать количество созданных объектов:

 Rails.logger.info Benchmark.measure{
    result = Record.where(condition).pluck(column).first
}
  

Теперь rails говорит, что sql занял 29,3 мс, а тест дает 0,017989 0,006119 0,024108 (0,713973)
Весь запрос занимает 0,731 секунды. Это огромное улучшение, но 0,7 секунды по-прежнему являются серьезным замедлением и по-прежнему подрывают удобство использования моего приложения.

Что я делаю не так? Мне кажется безумным, что что-то настолько простое должно иметь такое огромное замедление. Если rails работает именно так, я не могу представить, что кто-то использует его для серьезных приложений!

Комментарии:

1. Трудно сказать, исходя из общего способа, которым вы его представили. Сколько столбцов в строке? Создается ли объект необычно большого размера?

2. Также почему вы используете where, если вам нужен только первый? Вы просите его выполнить поиск совпадений по всей базе данных и вернуть их все, а затем предоставить первое.

3. @Beartech похоже, что в таблице около 50 столбцов. Я не думаю, что это очень большой. Rails выводит для меня именно то, какой оператор sql он выполняет, а для второго он выполняет SELECT "table"."column" FROM "table" WHERE condition

4. Таким образом, он не возвращает их все. Он автоматически отфильтровывает их на уровне sql

5. Также на какой системе вы запускаете все это приложение? Я разрабатываю на своем ноутбуке 6-летней давности, и rails может замедлиться, если я его нажму, но он вылетает в процессе производства на Heroku. Это ваш производственный сервер? С чем вы это сравниваете?

Ответ №1:

find_by_sql выполняет пользовательский SQL-запрос к вашей базе данных и возвращает все результаты.

Это означает, что все записи в вашей базе данных возвращаются и создаются экземпляры. Только тогда вы выбираете первый из этого массива, вызывая first результаты.

Когда вы вызываете first ActiveRecord::Relationship, он добавит ограничение к вашему запросу и выберет только то поведение, которое вы хотите.

Это означает, что вы должны сами ограничить запрос:

   result = Record.find_by_sql(['SELECT column FROM table WHERE condition LIMIT 1']).first.column
  

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

Комментарии:

1. Я бы добавил ORDER BY id ASC , чтобы получить более предсказуемый результат.

2. Условие уникально для каждой строки, поэтому оно в любом случае вернет только одну запись. Я только сейчас попробовал это со встроенным ограничением 1, и оно все еще составляет 1,7 секунды. Версия с pluck была намного быстрее

Ответ №2:

Как я упоминал выше, не уверен, почему вы запрашиваете все совпадения, если вам нужно только первое.

Если я сделаю:

  Rails.logger.info Benchmark.measure{
   result = User.where(email: 'foo@bar.com').pluck(:email).first 
 }

(9.6ms)  SELECT "users"."email" FROM "users" WHERE "users"."email" = $1  [["email", "foo@bar.com"]]
#<Benchmark::Tms:0x00007fc2ce4b7998 @label="", @real=0.6364280000561848,     @cstime=0.00364, @cutime=0.000661, @stime=0.1469640000000001, @utime=0.1646029999999996, @total=0.3158679999999997>

 Rails.logger.info Benchmark.measure{
   result = User.where(email: 'foo@bar.com').limit(1).pluck(:email) 
 }

 (1.8ms)  SELECT  "users"."email" FROM "users" WHERE "users"."email" = $1 LIMIT $2  [["email", "foo@bar.com.com"], ["LIMIT", 1]]
#<Benchmark::Tms:0x00007fc2ce4cd838 @label="", @real=0.004004000045824796, @cstime=0.0, @cutime=0.0, @stime=0.0005539999999997214, @utime=0.0013550000000002171, @total=0.0019089999999999385>
  

Rails также выполняет обналичивание. Если вы снова запустите свой запрос, он должен быть быстрее во второй раз. Насколько сложным является ваше where условие? Это может быть частью этого.

Комментарии:

1. Условие where — это простая проверка равенства в столбце, where column = option и это уникальный столбец. Таким образом, этот запрос всегда должен возвращать одну строку.

2. Я попытался использовать limit (1), как вы предлагаете, и, похоже, в среднем это занимает около 1 секунды.

3. Какую базу данных вы используете?

4. Я использую postgres

5. Индексируется ли столбец в БД?