Pro ORM

Серед багатьох досвічених розробників існує думка що ORM це щось погане. Його звинувачують в низькому перформенсі, непотрібних абстракціях та вихованню поганого смаку.

Щоразу коли читаю ті історії то ніяк не можу второпати що авторам не подобається, бо я з ORMами ніяких проблем не маю.

На першій роботі у бородаті роки в нас був самописний ORM, але він дуже повільно працював (проте мав інші переваги). Ми про цю особливість знали, тому для наших задач написали власний і не знали горя.

Згодом я успішно користувався JPA/Hibernate і мав халепу тільки зі специфічними речами типу bigint який не хотів мапитися чи то через багу хібера, чи то через особливості MariaDB.

А після ActiveRecord я не дуже уявляю як можна бути продуктивним без ORM. У всіх своїх проєктах SQL мені доводилося писати тільки для запитів пов'язаних зі статистикою.

Читачам відома моя любов до Rails, але ActiveRecord я люблю найбільше, ось, наприклад запит на витягування чергового щоденного збору для @Donate1024Bot

Post.where(state: "approved")
    .where.not(amount: nil)
    .where.not(goal: nil)
    .where(posted_count: 0)
    .where(scheduled_at: Time.now.utc.to_date)
    .order(:id)
    .first

Не буду по пунктах розбивати класичні аргументи проти, натомість напишу що потрібно вам, щоб бути ефективним.

Головне — добре знати та розуміти SQL. Коли ви вже розумієте як зробити потрібний вам запит то не буде жодних проблем з тим щоб конвертувати його в ефективний ORM-код. Мені здається що часто люди скаржаться на код програмістів які почали користуватися ORM без хороших знань SQL і нарубали дров. Таке звісно нікуди не годиться.

Друге — розібратися як зробити, щоб ваш ORM-фреймворк друкував у лог всі запити які він генерує та робить. Те що не бачиш не можеш поміряти та подебажити. Всі ORM-и вміють це робити, але з невідомих причин, це не всюди увімкнено за замовчуванням, наприклад у Spring/JPA це треба конфігурувати окремо. Одна з величезних переваг ActiveRecord це такий лог, наприклад запит зверху покаже нам ось таке:

Post Load (56.3ms)  SELECT "posts".* FROM "posts" WHERE "posts"."state" = $1 AND "posts"."amount" IS NOT NULL AND "posts"."goal" IS NOT NULL AND "posts"."posted_count" = $2 AND "posts"."scheduled_at" = $3 ORDER BY "posts"."id" ASC LIMIT $4  [["state", "approved"], ["posted_count", 0], ["scheduled_at", "2023-09-18"], ["LIMIT", 1]]

В консолі воно ще й красиво розфарбовано, тут ми можемо бачити: час виконання запиту, сам запит та «бінди», ті параметри які в нього були передані. Цієї інформації більш ніж достатньо для того, щоб писати ефективні запити.

Третє — розуміти що таке N+1 та як у вашому ORM-фреймворку цього уникнути. Здається що N+1 це найрозповсюдженіша проблема з ORM на що лаються ті самі досвічені програмісти, хоча вона дуже легко виявляється та виправляється. Просто почитайте як у вас робиться includes або FetchMode.EAGER і більше не матимете з тим клопоту. Гадаю що 95% всіх проблем з ORM зводиться саме до N+1 та нерозуміння як з ним впоратися.

Четверте — розуміти ціну створення об'єктів та використовувати методи які дозволяють уникнути цього. Наприклад, якщо нам треба взяти з таблиці тільки одне поле, id, то замість ids = User.all.map(&:id) можна використати ids = User.all.pluck(:id) як не буде створювати об'єкти User, а дістане лише масив id. Rails люб'язно друкує статистику щодо кількості інстанційованих об'єктів в лог, тому там відразу зрозуміло де це вже впливає на швидкодію.

Люблю ORM, використовую у всіх проєктах, раджу робити те ж і вам.

☝️Практична порада

👉Увімкнути логи свого ORM та подивитися на запити які він генерує. Подумати що можна покращити та почитати документацію або Tips&Tricks до свого фреймворку.


Сподобалось? Долучайтеся до мого телеграм каналу: https://t.me/full_of_hatred