Дослідження відмінностей ORM та оптимізації запитів
При створенні додатків вибір правильного фреймворку та системи ORM (Object-Relational Mapping) є критичним для продуктивності та підтримуваності. У другій частині серії ми розглянемо, як Ruby, Python і Go працюють з складними запитами, базуючись на основних відмінностях, які ми розглянули в першій частині. У цьому блозі обговорюються відмінності між Rails Active Record, Django ORM, Flask + SQLAlchemy і Go для реалізації складних запитів, таких як групування та віконні функції.
Типовий сценарій: обчислення доходу та топ-замовлень
Уявімо собі платформу для читання, де нам потрібно:
- Обчислити загальний дохід кожного автора за останні 30 днів.
- Обчислити середнє значення топ-5 найбільших замовлень для кожного автора.
Ось як ця проблема реалізована в кожному фреймворку:
1. Реалізація в Rails Active Record
Author
.select(
"authors.id, authors.name,
SUM(orders.amount) FILTER (WHERE orders.created_at >= CURRENT_DATE - INTERVAL '30 days') AS total_income_30_days,
AVG(orders.amount) FILTER (WHERE orders.id IN (
SELECT id FROM orders o WHERE o.author_id = authors.id ORDER BY o.amount DESC LIMIT 5
)) AS avg_top_5_orders"
)
.joins(:orders)
.group("authors.id, authors.name")
- Переваги: Відносно компактний синтаксис.
- Недоліки: Active Record використовує сирий SQL для складних запитів, що зменшує переносимість та підтримуваність. Абстракція обмежена в порівнянні з Django ORM.
2. Реалізація в Django ORM
thirty_days_ago = now() - timedelta(days=30)
authors = Author.objects.annotate(
total_income_30_days=Sum('orders__amount', filter=Q(orders__created_at__gte=thirty_days_ago)),
avg_top_5_orders=Avg(
Window(
expression='amount',
partition_by=F('id'),
order_by=F('amount').desc(),
frame=WindowFrame(range=(None, 5))
)
)
- Переваги: Django ORM надає високорівневі абстракції, що дозволяють виконувати складні запити при збереженні продуктивності.
- Недоліки: Трохи крутіша крива навчання для таких просунутих функцій, як віконні функції.
3. Реалізація в Flask + SQLAlchemy
thirty_days_ago = datetime.now() - timedelta(days=30)
query = (
db.session.query(
Author.id,
Author.name,
func.sum(Order.amount).filter(Order.created_at >= thirty_days_ago).label('total_income_30_days'),
func.avg(Order.amount).filter(Order.id.in_(
db.session.query(Order.id)
.filter(Order.author_id == Author.id)
.order_by(desc(Order.amount))
.limit(5)
)).label('avg_top_5_orders')
)
.join(Order, Author.id == Order.author_id)
.group_by(Author.id, Author.name)
)
result = query.all()
- Переваги: SQLAlchemy дає точний контроль над запитами та підтримує сирий SQL, коли це необхідно.
- Недоліки: Більш багатослівний порівняно з Django ORM. Може вимагати додаткової конфігурації для налаштування продуктивності.
4.
Реалізація в Go + вручну написаний SQL
func main() {
var db *sql.DB
thirtyDaysAgo := time.Now().AddDate(0, 0, -30)
query := `
SELECT
a.id,
a.name,
SUM(CASE WHEN o.created_at >= $1 THEN o.amount ELSE 0 END) AS total_income_30_days,
(SELECT AVG(sub.amount)
FROM (SELECT amount FROM orders WHERE orders.author_id = a.id ORDER BY amount DESC LIMIT 5) sub
) AS avg_top_5_orders
FROM authors a
LEFT JOIN orders o ON a.id = o.author_id
GROUP BY a.id, a.name
`
rows, err := db.Query(query, thirtyDaysAgo)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var authorID int
var name string
var totalIncome float64
var avgTop5 float64
err := rows.Scan(&authorID, &name, &totalIncome, &avgTop5)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Author: %s, Total Income: %.2f, Avg Top 5 Orders: %.2f\\n", name, totalIncome, avgTop5)
}
}
- Переваги: Написання сирого SQL в Go дає повний контроль над оптимізацією запитів та продуктивністю.
- Недоліки: Розробка займає більше часу і підвищує ймовірність помилок. Синтаксис SQL необхідно керувати вручну.
Підсумки та рекомендації
1. Rails Active Record:
- Сила: Швидке впровадження базових запитів.
- Слабкість: Обмежена гнучкість для складних запитів; значна залежність від сирого SQL.
2. Django ORM:
- Сила: Розвинені функції ORM дозволяють виконувати складні запити при збереженні продуктивності.
- Слабкість: Потрібно розуміти складні концепції, такі як віконні функції.
3. Flask + SQLAlchemy:
- Сила: Висока гнучкість і розширюваність.
- Слабкість: Більш багатослівно і складно налаштовувати порівняно з Django ORM.
4. Go + вручну написаний SQL:
- Сила: Оптимальна продуктивність для систем з високою пропускною здатністю.
- Слабкість: Велика трудомісткість у розробці; вимагає ручного керування SQL.
Основні висновки
- Використовуйте Rails Active Record для швидкого прототипування та простих запитів.
- Використовуйте Django ORM для балансу між абстракцією та продуктивністю.
- Використовуйте Flask + SQLAlchemy для гнучкості і коли потрібно підтримувати сирий SQL.
- Використовуйте Go для високопродуктивних сценаріїв, де важлива оптимізація SQL вручну.
Залишайтеся з нами для наступної частини цієї серії, де ми розглянемо Обмеження паралельності в сценаріях високої продуктивності.
Перекладено з: Ruby, Python, and Go: A Practical Comparison for Developers (Part 2)