Ruby, Python та Go: Практичне порівняння для розробників (Частина 2)

Дослідження відмінностей ORM та оптимізації запитів

При створенні додатків вибір правильного фреймворку та системи ORM (Object-Relational Mapping) є критичним для продуктивності та підтримуваності. У другій частині серії ми розглянемо, як Ruby, Python і Go працюють з складними запитами, базуючись на основних відмінностях, які ми розглянули в першій частині. У цьому блозі обговорюються відмінності між Rails Active Record, Django ORM, Flask + SQLAlchemy і Go для реалізації складних запитів, таких як групування та віконні функції.

Типовий сценарій: обчислення доходу та топ-замовлень

Уявімо собі платформу для читання, де нам потрібно:

  1. Обчислити загальний дохід кожного автора за останні 30 днів.
  2. Обчислити середнє значення топ-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)

Leave a Reply

Your email address will not be published. Required fields are marked *