(Не всегда) Крутые методы, которые должны быть осторожны при работе с rails

Каждый разработчик ruby, вероятно, хотя бы пару раз слышал, как кто-то говорил, что rails — это волшебство. Обычно это говорят из-за некоторых крутых методов, которые есть в rails и которые облегчают нашу жизнь как разработчиков, а иногда мы даже не знаем, что происходит внутри этих методов. И это хорошо, потому что эти методы помогают поддерживать хорошую читаемость нашего кода, но незнание того, что происходит при вызове метода, может привести к неожиданному поведению при работе с большими масштабами. Давайте попробуем разобраться в трех таких методах и их проблемах.

Содержание
  1. pluck
  2. ids
  3. find_each

pluck

pluck — это полезный метод активной записи, облегчающий получение массива значений из определенного столбца таблицы. Однако у него есть опасное применение, которое может повлиять на плохую производительность запросов к базе данных. Глядя на копы rubocop, мы видим конфликт. Есть статья, которая рекомендует использовать pluck вместо map, но также есть статья, которая рекомендует использовать select вместо pluck.

Проблема с pluck, как мы видим в rubocop cop, заключается в том, что он используется внутри условия where. Например, если у нас есть код типа Book.where(author_id: Author.brazilian.pluck(:ids)), то произойдет следующее:

  • Он откроет первое соединение с базой данных, чтобы сначала выполнить запрос автора, выполнив что-то вроде SELECT authors.id FROM authors WHERE ... .
  • После выполнения запроса авторов активная запись получит все идентификаторы, возвращенные из базы данных, и сохранит их в массиве памяти.
  • Наконец, она откроет второе соединение с базой данных для выполнения запроса books и будет использовать массив, возвращенный первым запросом, в качестве параметра запроса, выполняя что-то вроде этого SELECT * FROM books WHERE books.author_id IN (1, 2, 3, ..., 100, 101).

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

Лучшим подходом было бы заменить метод pluck на метод select в этом сценарии, что привело бы только к одному запросу в базе данных, выполнив что-то вроде этого SELECT * FROM books WHERE books.author_id IN (SELECT authors.id FROM authors WHERE ...). Это позволяет избежать как ненужного запроса, так и расхода памяти, который происходит при использовании pluck внутри where.

ids

Как мы можем видеть в rails doc, это просто псевдоним pluck(:id), поэтому у нас возникают те же проблемы, что и при использовании его в предложении where. Тем не менее, rubocop рекомендует использовать его вне пункта where, как мы видим в этом примере с PluckId.

find_each

В зависимости от размера списка записей базы данных, с которым нам нужно работать, запрос всего сразу может привести к проблемам с памятью. Например, если нам нужно выполнить итерацию в таблице книг, содержащей миллионы записей, использование Book.all.each приведет к тому, что все миллионы записей попадут в память, а за ними последует итерация в массиве памяти. Один из способов избежать этого — использовать метод find_each, который позволяет избежать одновременного занесения всех записей в память и разделить их на несколько пакетных запросов.

Это действительно хорошо и полезно, но использование find_each без заботы о размере пакета может привести к другой проблеме — разбиению результата на слишком большое количество пакетов, что приводит нас к проблеме слишком большого количества запросов, выполняемых в базе данных. Возвращаясь к примеру с миллионами книг, допустим, что у нас есть 10 миллионов книг, и нам нужно итерировать каждую, использование find_each с его стандартным batch_size, равным 1000, приведет к 10000 запросов в нашей базе данных. Если мы знаем, что у нас достаточно памяти для использования 10000 батчей и используем find_each с этим размером батча, а не по умолчанию, мы можем получить менее 9000 запросов в той же задаче.

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


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

Оцените статью
devanswers.ru
Добавить комментарий