Приховані витрати абстракції: Коли ORM стають вузькими місцями.
ORM пришвидшують розробку, приховуючи повторювану логіку персистентності. Однак у великих масштабах та сама абстракція може приховувати неефективні запити, надмірну гідратацію об'єктів, шаблони N+1 та стратегії доступу до бази даних, які більше не відповідають робочому навантаженню.
Mihail Leybuk-GalinСистемний архітекторПоділитися статтею
1. Абстракція має вартість виконання
Об'єктно-реляційні мапери вирішують реальну інженерну проблему. Вони усувають повторюваний код персистентності, нормалізують поширені шаблони доступу до даних і дозволяють командам швидко рухатися без написання SQL від руки для кожної операції.
Небезпека виникає, коли інженери починають вважати, що простий код програми завжди відповідає простій роботі з базою даних. Один виклик методу може викликати десятки запитів, великі об'єднання, непотрібний вибір стовпців, гідратацію вкладених об'єктів або приховані мережеві цикли.
3. Пошук справжнього вузького місця в базі даних
Рішення полягає не в заміні кожного виклику ORM необробленим SQL. Воно полягає в тому, щоб зробити поведінку бази даних доступною для спостереження. Інженери повинні мати можливість бачити згенерований SQL, тривалість запиту, повернуті рядки, час очікування пулу підключень та загальну кількість запитів, виконаних на запит.
catalog_repository.go
// Generic entity hydration
var products []Product
db.
Preload("Images").
Preload("Categories").
Preload("Attributes").
Find(&products)
// Purpose-built read projection
type ProductCard struct {{
ID string
Title string
Price float64
Thumbnail string
}}
db.Raw(`
SELECT p.id, pt.title, p.price, i.url AS thumbnail
FROM products p
JOIN product_translations pt ON pt.product_id = p.id
LEFT JOIN images i ON i.product_id = p.id AND i.sort = 1
WHERE p.active = TRUE
ORDER BY p.popularity DESC
LIMIT 24
`).Scan(&cards)Після того, як дороговартісний запит ізольовано, EXPLAIN ANALYZE стає кориснішим, ніж спекуляції на рівні програми. План виконання виявляє послідовні сканування, погані стратегії об'єднання, розливи сортування, погані оцінки кардинальності та відсутні індекси.
Очікування пулу підключень має вимірюватися окремо від виконання запиту. Запит тривалістю 20 мс все ще може створити інтервал бази даних 200 мс, якщо запит очікує вільного з'єднання 180 мс.
4. Знання того, коли потрібно перейти до SQL
Зрілі рівні персистентності рідко обирають між «ORM скрізь» та «сирим SQL скрізь». Вони використовують найкориснішу абстракцію для кожного робочого навантаження.
ORM
CRUD-операції
Транзакційні записи
Проста фільтрація
Адміністративні кінцеві точки
Стандартне завантаження сутностей
SQL / Query Layer
Високопродуктивне читання
Складні агрегації
Звітність
Масові операції
Функції, специфічні для бази даних
Прагматичним підходом є збереження багатих моделей доменів, що підтримуються ORM, для запису, водночас створюючи цільові проекції для шляхів читання з великим обсягом даних. ORM залишається корисним інструментом впровадження; він просто перестає диктувати архітектуру.
5. Результати та показники
Оптимізація лише критичних шляхів збереження може призвести до непропорційного покращення без необхідності повного переписування рівня доступу до даних застосунку.
- Запити / Запити
- 84→6
- Цільова база даних p95
- 95мс
- Пропускна здатність кінцевої точки
- 2.3x
Мета полягає не в тому, щоб усунути абстракцію. Мета полягає в тому, щоб знати, де абстракція більше не відповідає робочому навантаженню, і надати доступ до керування базою даних нижчого рівня лише тим критично важливим для продуктивності шляхам.