Gara-Gara Serializer Nested, API Gw Jadi 6 Detik
Gara-Gara Serializer Nested, API Gw Jadi 6 Detik
Gara-Gara Serializer Nested, API Gw Jadi 6 Detik
Awalnya gw kira:
“ah paling query database biasa”
Ternyata bukan 😭
API homepage yang tadinya:
200–300ms
pelan-pelan naik jadi:
2 detik
Terus:
4 detik
Dan akhirnya:
6 DETIK 💀
Padahal:
- CPU normal
- server gak down
- PostgreSQL gak merah
- Redis aman
Jadi problemnya di mana?
Endpoint Monster
Endpoint-nya kira-kira begini:
class HomepageAPIView(APIView): def get(self, request): campaigns = Campaign.objects.all()[:20]
serializer = CampaignSerializer(
campaigns,
many=True
)
return Response(serializer.data)
Kelihatannya innocent.
Masalahnya ada di:
CampaignSerializer
😭
Nested Serializer Dari Neraka
Isi serializer-nya kira-kira:
class CampaignSerializer(serializers.ModelSerializer): user = UserSerializer() category = CategorySerializer() donations = DonationSerializer(many=True)
class Meta:
model = Campaign
fields = "\_\_all\_\_"
Kelihatannya clean.
Kelihatannya elegant.
Kelihatannya reusable.
Tapi ternyata…
ini pembuka gerbang penderitaan 😭
Yang Sebenarnya Terjadi
Misalnya:
- 20 campaign
- tiap campaign punya:
- user
- category
- donations
Tanpa sadar yang terjadi:
1 query campaign 20 query user 20 query category 20 query donation
Belum kalau:
- donation serializer nested lagi
- user serializer nested profile lagi
- profile nested address lagi
😭😭😭
Akhirnya total query bisa:
200+
buat SATU endpoint.
Dan Parahnya…
Waktu local development:
gak kerasa
Karena:
- data masih sedikit
- database kosong
- internet cepat
- laptop ngangkat
Pas production:
- data ratusan ribu
- relation makin banyak
- serializer makin nested
baru mulai meledak 🔥
Ketahuan Gara-Gara Django Silk
Akhirnya gw coba profiling pakai:
Django Silk
Dan hasilnya:
214 SQL queries
💀
Pas buka detail request:
- query sama diulang terus
- SELECT user WHERE id=...
- SELECT category WHERE id=...
- SELECT donation WHERE ...
Classic:
N+1 Query Apocalypse
Penyebab Utamanya
Masalah utama nested serializer biasanya:
- lazy loading relation
- looping relation tanpa prefetch
- serializer manggil query tambahan
- SerializerMethodField query DB lagi
- nested terlalu dalam
Dan yang paling bahaya:
SerializerMethodField
😭
Karena sering gak sadar bikin query baru tiap object.
Contoh Dosa Yang Sering Terjadi
Misalnya:
class CampaignSerializer(serializers.ModelSerializer): total_donation = serializers.SerializerMethodField()
def get\_total\_donation(self, obj):
return Donation.objects.filter(
campaign=obj
).count()
Kelihatannya normal.
Padahal kalau ada:
100 campaign
Maka:
100 query tambahan 😭
Solusi Pertama: select_related
Untuk ForeignKey / OneToOne:
Campaign.objects.select_related( "user", "category" )
Ini bakal JOIN query jadi satu.
Solusi Kedua: prefetch_related
Untuk reverse relation / many-to-many:
Campaign.objects.prefetch_related( "donations" )
Ini bikin Django ambil relation sekaligus.
Bukan query satu-satu.
Setelah Dioptimasi
Query turun dari:
214 query
jadi:
8 query 🔥
Latency:
6 detik
jadi:
400ms
Dan server langsung adem 😭
Pelajaran Yang Gw Dapet
1. Serializer Itu Bisa Mahal
Serializer DRF kelihatannya “clean”.
Tapi abstraction tinggi sering nyembunyiin query mahal.
2. Nested Serializer Harus Hati-Hati
Bukan berarti gak boleh.
Tapi:
- relation harus diprefetch
- jangan nested terlalu dalam
- jangan recursive tanpa limit
3. SerializerMethodField Itu Tersangka Utama
Kadang innocent.
Kadang monster 😭
Selalu cek:
- apakah dia query DB?
- dipanggil berapa kali?
- bisa dianotasi gak?
Tips Yang Sekarang Selalu Gw Pakai
Selalu Profiling
Pakai:
- Django Silk
- Django Debug Toolbar
- query logging
Jangan nebak-nebak.
Selalu Cek Jumlah Query
Kadang endpoint:
300ms
Tapi query:
400 query
Artinya tinggal tunggu waktu buat meledak 😭
Jangan Takut Flat Serializer
Kadang lebih bagus:
CampaignListSerializer CampaignDetailSerializer CampaignSimpleSerializer
Daripada:
UniversalSerializerDariNerakaSerializer
😭
Realita Backend Engineering
Mayoritas performance issue backend itu bukan:
- Python lambat
- Django lambat
- PostgreSQL jelek
Tapi:
query yang gak sadar kebanyakan
Dan nested serializer sering jadi:
penyebab utamanya 😭
Penutup
DRF serializer itu powerful banget.
Tapi kalau gak hati-hati:
- query meledak
- latency naik
- CPU naik
- database nangis
Dan yang paling serem:
semuanya masih “jalan normal”
Sampai traffic naik 💀
Jadi sekarang tiap bikin serializer, gw selalu mikir:
"ini bakal query berapa kali ya?"
Karena kadang…
1 baris nested serializer bisa berubah jadi:
6 detik penderitaan 😭