ORM vs Raw SQL: The Winner is…

ORM vs Raw SQL: The Winner is…

Pertarungan sengit tiada berujung

Intro

Kalau di dunia sepakbola, mungkin udah ga asing di telinga kita soal perdebatan yang masih belum jelas kapan selesainya: CR7 atau Messi? Menurut saya sendiri sih, ini betul-betul murni preferensi pribadi aja. Kehebatan mereka berdua sudah diakui oleh dunia, dibuktikan dengan deretan trofi kejuaraan dan penghargaan pribadi yang mereka dapatkan. Cuma mungkin ada yang lebih suka gaya permainan CR7, ada yang lebih suka gaya permainan Messi.

Nah pertanyaannya, apakah di dunia teknologi ada juga perdebatan kayak gitu? Perdebatan yang sampai bisa bikin netizen saling “baku hantam”?

Sebetulnya, namanya perdebatan di bidang apapun pasti bakal selalu ada. Termasuk di dunia teknologi pun ada aja silang pendapat yg berujung ke “baku hantam”. Dan salah satu perdebatan yang selalu menarik dibicarakan adalah: apakah lebih baik menggunakan ORM atau raw SQL?

Di tulisan kali ini saya akan coba bedah sedikit kenapa perdebatan itu bisa muncul, dan kenapa topik ini selalu menarik buat dijadikan bahan “baku hantam”.

Pengertian ORM

ORM, singkatan dari Object Relational Mapping, merupakan salah satu metode yang lumrah digunakan untuk manajemen data di database. ORM bertujuan melakukan “konversi” terhadap data menjadi objek, sehingga memungkinkan kita untuk manipulasi data menggunakan paradigma pemrograman berorientasi objek.

Tujuan dari ORM adalah menjadi layer abstaksi antara aplikasi dengan database sehingga memungkinkan kita melakukan manipulasi data tanpa harus menulis SQL, karena ORM yang akan menuliskan SQL-nya untuk kita.

Umumnya jika kita menggunakan ORM, kita menuliskan class yang akan menjadi representasi dari suatu tabel di database. Kemudian, objek dari class tersebut merupakan representasi dari satu atau lebih baris data di database. ORM akan menyediakan library yang memungkinkan kita untuk melakukan operasi create, read, update dan delete terhadap objek data, yang akan diterjemahkan oleh ORM menjadi operasi SQL INSERT, SELECT, UPDATE dan DELETE di balik layar.

Contoh ORM

Beberapa contoh library ORM yang cukup populer diantaranya:

ORM vs Raw SQL: sintaks

Jika tadi kita masih membahas kulitnya, sekarang kita mulai masuk ke topik yang lebih dalam yaitu perbedaan sintaks penulisan antara ORM dengan raw SQL. Di bawah ini kita bisa melihat beberapa contoh sintaks penulisan ORM dan sintaks SQL yang ekivalen.

Django ORM:

books = Book.objects.all()

Ekivalen dengan SQL:

SELECT * FROM books;

Django ORM

from django.db.models import Sum

expensive_customers = Customer.objects.annotate(
    total_order_cost=Sum('order__total_cost')
).filter(
    total_order_cost__gt=1000
)

Ekivalen dengan SQL:

SELECT c.name, SUM(o.total_cost) as total_order_cost
FROM customers c
JOIN orders o ON c.id = o.customer_id
GROUP BY c.name
HAVING SUM(o.total_cost) > 1000;

Django ORM

from django.db.models import Sum

ordered_products = Product.objects.filter(
    order_items__order__customer__state__in=['NY', 'CA']
).annotate(
    total_quantity=Sum('order_items__quantity')
)

Ekivalen dengan SQL:

SELECT p.name, SUM(oi.quantity) as total_quantity
FROM products p
JOIN order_items oi ON p.id = oi.product_id
JOIN orders o ON oi.order_id = o.id
JOIN customers c ON o.customer_id = c.id
WHERE c.state IN ('NY', 'CA')
GROUP BY p.name;

Catatan: seluruh ORM menggunakan sintaks Django ORM. Untuk library ORM yang lain tentu saja akan ada perbedaan, tapi ide dasarnya tetap sama. Class merepresentasikan tabel di database, dan objek merepresentasikan satu atau lebih baris data. Dari contoh di atas, Book , Customer dan Product adalah class yang merepresentasikan tabel books, customers dan products. Sementara itu, books, expensive_customers, dan ordered_products merupakan objek yang merepresentasikan data yang didapatkan dari database. Dari objek yang dihasilkan tadi, layaknya objek pada pemrograman berorientasi objek, kita dapat melakukan manipulasi data dengan menggunakan fungsi-fungsi yang terdaftar pada class masing-masing.

Django ORM

product = Product.objects.filter(price__lt=10)
product.update(price=12.99)

Ekivalen dengan SQL:

UPDATE products SET price = 12.99 WHERE price < 10;

Pada contoh di atas, objek product menggunakan fungsi update() yang terdapat pada classProduct untuk melakukan operasi SQL update.

Jika dilihat dari sintaks, kita sebagai programmer tentunya akan "dimanjakan" dengan ORM ini. Selain sintaks penulisan ORM yang relatif lebih sederhana, penggunaan objek sebagai representasi data sangat memudahkan kita apabila kita ingin melakukan manipulasi data. Selain itu, ada satu lagi daya tarik ORM yang mungkin membuat orang menggunakan ORM pada software yang dibuat. Karena ORM bertugas melakukan penulisan raw SQL yang ekivalen, ORM dapat memberikan sintaks SQL yang sesuai dengan engine database yang digunakan. Fitur ini sangat bermanfaat apabila software yang kita buat menggunakan lebih dari beberapa engine database yang berbeda (misal, SQL Server dan MySQL).

Django ORM

# ambil 5 produk dari database
products = Product.objects.all()[:5]

Sintaks SQL Server yang ekivalen:

SELECT TOP 5 * FROM products;

Sintaks MySQL yang ekivalen

SELECT * FROM products LIMIT 5;

Walaupun terdapat perbedaan dari sintaks SQL Server dan MySQL di atas, sintaks ORM tetaplah sama. ORM yang akan mendeteksi engine database yang digunakan, untuk kemudian menghasilkan SQL yang sesuai. Sehingga kita sebagai software developer tidak perlu menghafal sintaks SQL untuk masing-masing engine database, dan serahkan ke ORM untuk melakukan tugasnya.

ORM vs Raw SQL: performance

Apabila sebelumnya kita sudah membahas sintaks ORM dan raw SQL yang ekivalen, bagaimana dengan performancenya?

Sebetulnya dengan segala fitur yang ditawarkan ORM, sekilas terdengar too good to be true. Sintaks yang relatif lebih sederhana, dapat melakukan manipulasi data menggunakan pendekatan objek, dapat mendeteksi engine database yang digunakan, lalu apakah ini artinya kita betul-betul sudah tidak membutuhkan raw SQL?

Layaknya peribahasa "tak ada gading yang tak retak" maka begitu juga dengan ORM. Karena ORM berfungsi sebagai layer abstraksi tambahan antara software dengan database, tentu saja akan ada overhead yang dihasilkan oleh ORM.

Berikut merupakan kode sederhana yang saya gunakan untuk melakukan perbandingan performance antara ORM dengan raw SQL. Kode berikut ditulis dalam bahasa Python dan menggunakan framework Django, namun hasilnya harusnya kurang lebih akan mirip jika menggunakan ORM dalam bahasa lain.

from django.db import connection
from django.shortcuts import render
from django.http import HttpResponse
from memory_profiler import memory_usage
from memory_profiler import profile
from .models import User
import time

# Function to reset the query log
def reset_queries():
    connection.queries_log.clear()

# Function to measure execution time
def measure_time(func):
    start = time.time()
    result = func()
    end = time.time()
    return result, end - start

# Django ORM query
@profile
def orm_query():
    return list(User.objects.all()[:1000000])

# Raw SQL query
@profile
def raw_sql_query():
    with connection.cursor() as cursor:
        cursor.execute("SELECT * FROM user LIMIT 1000000")
        return list(cursor.fetchall())

# Create your views here.
def index(request):
   # loop 10 times to get the average time
    total = 0
    for x in range(10):
        reset_queries()
        # measure execution time
        raw_result, raw_time = measure_time(orm_query)
        total += raw_time

        # measure memory consumption 
        mem_usage = memory_usage(orm_query)
        max_usage = max(mem_usage)
        total += max_usage

    # calculate the average time
    average = total / 10

    return HttpResponse(average)

Kode tersebut membandingkan dua hal: waktu eksekusi (dalam detik) dan penggunaan memory (dalam MiB). Dalam melakukan perbandingan tersebut, saya menggunakan database SQLite. Mesin yang digunakan adalah laptop dengan CPU Core i7 1255U dan RAM 16GB. Berikut hasil yang didapatkan disajikan dalam grafik:

ORM vs Raw SQL: Waktu Eksekusi (dalam detik)

Jumlah RowDjango ORMRaw SQL
1000.007820.00238
10000.056660.00313
100000.500690.01425
1000005.527760.11043
100000053.645501.11739

ORM vs Raw SQL: Konsumsi Memory (dalam MiB)

Jumlah RowDjango ORMRaw SQL
10043.3335943.28085
100045.1988244.42343
1000053.1828154.51219
100000132.36835142.86562
1000000913.72148567.93789

Disclaimer: hasil bisa berbeda-beda, karena banyak variabel yang mempengaruhi seperti mesin yang digunakan, ORM/bahasa yang digunakan, engine database yang digunakan dll.

Kesimpulan: Kapan harus pakai yang mana?

Dari hasil perbandingan di atas, terbukti bahwa ada harga yang harus dibayar sebagai kompensasi atas bermacam fitur tambahan yang disematkan dalam ORM. Pada jumlah data yang masih kecil, perbedaan masih tidak terasa. Namun jika aplikasi sudah berurusan dengan jumlah data yang besar, perbedaan tersebut akan sangat terasa. Jadi, kapan kita dapat menggunakan ORM, dan kapan kita menggunakan raw SQL?

Gunakan ORM jika...

  • Aplikasi masih dalam tahap pengembangan, karena kesederhanaan sintaks ORM dapat mempercepat pengembangan

  • Aplikasi masih dalam tahap early stage, dimana data yang digunakan / jumlah user masih sedikit

  • Performance bukan metric utama dari aplikasi, karena memang ada skenario dimana metric lain lebih penting dari performance

  • Aplikasi menggunakan beberapa engine database yang berbeda, yang berpotensi banyak terjadi perbedaan sintaks SQL. Umumnya ORM cukup portable sehingga dengan kode ORM yang sama dapat memberikan sintaks SQL yang sesuai dengan engine database yang digunakan

Gunakan SQL jika...

  • Aplikasi sudah memiliki data besar, atau jumlah data relatif sedikit namun basis user yang besar. Dari perbandingan di atas, dapat kita lihat pada saat jumlah data sedikit sebenarnya perbedaan yang ada tidaklah terasa. Namun jika dihadapkan dengan jumlah user yang besar, perbedaan sekecil apapun dapat terakumulasi sehingga cepat menghabiskan resource

  • Performance merupakan metric yang critical dari aplikasi

  • Aplikasi hanya menggunakan satu engine database, atau menggunakan beberapa engine database namun potensi perbedaan sintaks sangat minim

Jadi, mana yang lebih baik, ORM atau raw SQL? 😁