Serverless vs Container Apps bukan soal mana yang lebih modern, tetapi mana yang lebih cocok dengan pola trafik, karakter workload, dan kapasitas operasional tim. Untuk backend/API, serverless sering unggul saat trafik tidak stabil, event-driven, dan tim ingin meminimalkan pengelolaan infrastruktur. Sebaliknya, container apps biasanya lebih tepat ketika aplikasi butuh kontrol runtime yang lebih penuh, proses berjalan lebih lama, koneksi yang lebih stabil, atau perilaku scaling yang lebih dapat diprediksi.

Jika diringkas: serverless mengurangi beban operasional dengan konsekuensi batasan runtime dan perilaku cold start, sedangkan container apps memberi fleksibilitas dan kontrol lebih besar dengan tanggung jawab operasional yang ikut naik. Keputusan yang baik biasanya lahir dari evaluasi trade-off, bukan dari preferensi tool.

Memahami perbedaan dasar: model eksekusi dan unit scaling

Serverless

Pada model serverless, unit deployment biasanya berupa fungsi atau service yang dijalankan saat ada request atau event. Platform menangani provisioning, autoscaling, dan sebagian besar manajemen infrastruktur. Aplikasi cenderung didorong menuju desain stateless, durasi request yang terbatas, dan dependency yang ringan agar startup cepat.

Konsekuensinya, perilaku aplikasi sangat dipengaruhi oleh karakter platform: waktu cold start, batas concurrency, durasi eksekusi maksimum, serta cara platform mengatur instance baru saat terjadi lonjakan trafik.

Container apps

Pada container apps, unit deployment biasanya berupa container image yang menjalankan web server, worker, atau proses lain secara lebih persisten. Platform tetap bisa menyediakan autoscaling, rolling deployment, health check, dan service discovery, tetapi Anda memiliki kontrol lebih besar atas runtime, proses startup, dependency sistem, dan topologi aplikasi.

Trade-off-nya adalah tim perlu lebih aktif mengelola image, lifecycle aplikasi, tuning resource, observability, dan kadang juga networking serta security hardening.

Tabel perbandingan utama

AspekServerlessContainer Apps
Skalabilitas awalSangat cepat diadopsi, scaling otomatis umumnya mudahJuga bisa autoscale, tetapi butuh konfigurasi dan tuning lebih aktif
Cold startPerlu diperhitungkan, terutama untuk runtime berat atau trafik sporadisBiasanya lebih stabil jika instance minimum dijaga tetap hidup
Biaya idleSering efisien saat hampir tidak ada trafikBisa tetap membayar instance minimum atau resource yang aktif
Biaya lonjakan trafikBisa efisien untuk burst pendek, tetapi perlu waspada biaya per request/durasiLebih mudah diprediksi jika throughput per instance diketahui
Kontrol runtimeTerbatas pada batasan platformLebih fleksibel, termasuk proses tambahan dan dependency sistem
ConcurrencyBergantung platform; bisa sangat baik, tetapi harus paham model scaling-nyaLebih mudah disetel lewat worker/process/thread dan resource container
StatefulnessHarus diasumsikan statelessMasih sebaiknya stateless untuk scale-out, tetapi lebih mudah menangani proses persisten
Background jobCocok untuk job event-driven pendek dan terpisahCocok untuk worker jangka panjang, queue consumer, scheduler, dan job khusus
Koneksi databasePerlu hati-hati terhadap connection storm saat scale-outLebih mudah mengontrol pooling dan jumlah koneksi aktif
ObservabilityTracing lintas fungsi/event perlu disiplin tinggiLebih mudah menjaga konsistensi logging/metrics antar proses yang lama hidup
Vendor lock-inBiasanya lebih tinggi karena integrasi event, IAM, dan trigger platformUmumnya lebih portabel selama image dan kontrak runtime standar
Maintainability tim kecilBagus jika arsitektur tetap sederhana dan tidak terlalu terfragmentasiBagus jika tim sudah nyaman dengan container, deployment, dan observability
Maintainability tim besarBisa rumit jika fungsi terlalu banyak dan ownership kaburLebih cocok untuk boundary service yang jelas dan praktik platform engineering

Trade-off arsitektur yang paling sering menentukan pilihan

Pola traffic: stabil, bursty, atau sangat tidak terduga

Ini biasanya faktor pertama yang paling berpengaruh.

  • Traffic rendah dan sporadis: serverless sering menang karena biaya idle mendekati nol dan tidak perlu menjaga instance tetap hidup.
  • Traffic stabil sepanjang hari: container apps sering lebih ekonomis dan lebih mudah diprediksi karena instance aktif terus bekerja.
  • Traffic bursty: keduanya bisa bekerja, tetapi perlu melihat bentuk burst-nya. Jika burst sangat tajam dan singkat, serverless bisa menguntungkan. Jika burst menyebabkan banyak koneksi DB atau butuh warm capacity yang konsisten, container apps bisa lebih aman.

Kesalahan umum adalah hanya melihat “autoscaling tersedia” tanpa mengevaluasi bagaimana scaling terjadi dan dampaknya ke dependency seperti database, cache, dan downstream API.

Cold start dan latency tail

Cold start adalah waktu tambahan saat platform perlu menyiapkan instance baru. Masalah ini tidak selalu terlihat pada rata-rata latency, tetapi sering muncul pada tail latency seperti p95 atau p99. Untuk API yang sensitif terhadap respon cepat, cold start bisa menjadi faktor penting.

Cold start biasanya lebih terasa jika:

  • trafik jarang sehingga instance sering turun ke nol,
  • runtime startup lambat,
  • dependency berat dimuat saat boot,
  • aplikasi membuka banyak koneksi atau melakukan inisialisasi mahal saat start.

Pada container apps, cold start tidak hilang sepenuhnya, tetapi bisa dikurangi dengan menjaga minimum instances atau proses tetap hidup. Artinya, Anda menukar biaya idle dengan latency yang lebih stabil.

Jika SLA API menuntut latency konsisten pada jam-jam sepi, serverless perlu diuji dengan skenario cold start nyata, bukan hanya benchmark lokal.

Autoscaling dan concurrency

Autoscaling bukan sekadar menambah instance. Yang penting adalah pemicu scaling dan jumlah request yang bisa ditangani per instance.

Pada serverless, model scaling sering berbasis request atau event. Ini memudahkan scale-out cepat, tetapi bisa memicu ledakan concurrency ke dependency bersama seperti database. Pada container apps, Anda biasanya lebih leluasa menentukan concurrency per instance, worker count, dan resource CPU/memori sehingga throughput lebih bisa dituning.

Pertanyaan praktis yang perlu dijawab:

  • Berapa request paralel yang aman per instance?
  • Apakah setiap request membuka koneksi baru ke database?
  • Apakah beban kerja lebih CPU-bound atau I/O-bound?
  • Apakah ada downstream yang punya rate limit ketat?

Jika jawaban atas pertanyaan-pertanyaan itu belum jelas, autoscaling yang “otomatis” bisa justru memperbesar kegagalan.

Statefulness dan penyimpanan lokal

Untuk backend/API modern, desain stateless tetap ideal pada kedua pendekatan. Namun, tingkat toleransi terhadap proses persisten berbeda.

Serverless kurang cocok jika aplikasi bergantung pada:

  • session in-memory lokal,
  • cache lokal yang harus bertahan,
  • file sementara yang diasumsikan selalu tersedia antar request,
  • proses yang perlu menjaga koneksi jangka panjang secara konsisten.

Container apps lebih ramah untuk kebutuhan tersebut, meskipun secara arsitektur tetap lebih sehat memindahkan state ke service eksternal seperti Redis, object storage, atau database.

Background job dan worker

Untuk job berbasis event, retry sederhana, atau tugas singkat seperti resize image, kirim webhook, atau transformasi ringan, serverless sangat masuk akal. Namun untuk worker yang:

  • menarik pesan terus-menerus dari queue,
  • memproses batch besar,
  • memerlukan library sistem khusus,
  • berjalan lama atau butuh kontrol ketat atas paralelisme,

container apps biasanya lebih nyaman dioperasikan. Anda bisa memisahkan API service dan worker service dalam image atau deployment terpisah.

Anti-pattern yang sering muncul adalah memaksa semua background job ke serverless walau sebagian job sebenarnya lebih cocok menjadi worker persisten. Hasilnya biasanya biaya sulit diprediksi, retry berulang, dan observability yang buruk.

Koneksi database: sumber masalah yang sering diremehkan

Salah satu masalah paling umum pada serverless untuk backend/API adalah connection storm. Saat trafik naik, platform membuat banyak eksekusi paralel. Jika tiap eksekusi membuka koneksi database sendiri, jumlah koneksi bisa melonjak melebihi kapasitas database.

Mitigasi yang umum dipakai:

  • gunakan connection pooling atau proxy jika tersedia,
  • batasi concurrency efektif ke database,
  • pisahkan workload baca dan tulis bila memungkinkan,
  • cache hasil query yang mahal,
  • hindari membuka koneksi baru pada tiap jalur request tanpa reuse yang aman.

Pada container apps, connection pooling cenderung lebih stabil karena proses hidup lebih lama. Namun ini bukan jaminan. Jika jumlah container bertambah tanpa kontrol, total koneksi tetap bisa meledak.

Prinsipnya sederhana: scaling aplikasi harus disejajarkan dengan kapasitas database, bukan hanya dengan kapasitas platform compute.

Observability dan debugging produksi

Serverless bisa sangat nyaman saat deployment awal, tetapi debugging lintas banyak fungsi, event trigger, dan retry otomatis bisa menjadi sulit. Anda perlu disiplin pada:

  • correlation ID untuk setiap request/event,
  • structured logging,
  • distributed tracing,
  • monitoring retry, dead-letter queue, dan timeout,
  • dashboard untuk latency, error rate, dan throttling.

Pada container apps, observability biasanya lebih familiar karena pola proses dan service lebih tradisional. Meski begitu, jika Anda menjalankan banyak service dan worker, kompleksitas tetap tinggi. Bedanya, kontrol atas agen log, sidecar, exporter metrics, dan konfigurasi runtime biasanya lebih besar.

Jika tim kecil belum punya standar observability, serverless yang tersebar ke banyak fungsi dapat menjadi sulit dipelihara meski infrastruktur dasarnya lebih sederhana.

Vendor lock-in dan portabilitas

Serverless sering terikat erat dengan ekosistem vendor: trigger event, IAM, queue, scheduler, observability, hingga format deployment. Lock-in ini tidak selalu buruk. Jika tim memang ingin bergerak cepat dan menerima ekosistem vendor sebagai platform utama, integrasi yang erat justru bisa menghemat waktu.

Container apps umumnya lebih portabel karena image OCI, proses startup, dan banyak komponen runtime relatif standar. Tetapi jangan berasumsi sepenuhnya bebas lock-in. Networking, autoscaling policy, secret management, dan service integration tetap bisa spesifik platform.

Pertanyaan yang lebih berguna bukan “apakah ada lock-in?”, melainkan “apakah lock-in ini sepadan dengan penghematan operasional yang kita dapat?”

Biaya idle vs biaya lonjakan trafik

Perbandingan biaya paling sering salah karena hanya melihat tagihan compute, bukan biaya total sistem.

Serverless cenderung unggul ketika:

  • trafik rendah untuk waktu lama,
  • request pendek,
  • burst tidak terus-menerus,
  • tim ingin menghindari biaya instance idle.

Container apps cenderung unggul ketika:

  • trafik stabil,
  • instance bisa dimanfaatkan terus-menerus,
  • aplikasi punya startup cost tinggi,
  • workload membutuhkan proses persisten atau pooling yang efisien.

Jangan lupa biaya tidak langsung:

  • waktu engineer untuk debugging scaling issue,
  • biaya observability dan log ingestion,
  • downtime akibat connection storm,
  • overprovisioning untuk menghindari cold start atau lonjakan antrian.

Dampak pada maintainability: tim kecil vs tim yang sudah tumbuh

Tim kecil

Untuk tim kecil, serverless sering menarik karena mengurangi pekerjaan seperti patching host, deployment cluster, dan capacity planning dasar. Namun manfaat ini cepat berkurang jika arsitektur pecah menjadi terlalu banyak fungsi kecil tanpa boundary yang jelas.

Tanda serverless masih sehat untuk tim kecil:

  • jumlah service/fungsi masih terbatas,
  • event flow mudah ditelusuri,
  • observability sudah standar,
  • tidak banyak kebutuhan runtime khusus.

Container apps juga bisa sangat masuk akal bagi tim kecil jika aplikasi utamanya monolit modular atau hanya beberapa service, dan tim sudah terbiasa dengan Docker serta pipeline deployment. Dalam banyak kasus, satu container app yang rapi lebih mudah dipelihara daripada puluhan fungsi serverless yang terpisah.

Tim yang sudah tumbuh

Ketika tim bertambah, kebutuhan governance ikut naik: standar deployment, ownership service, versioning kontrak API, tracing, secret management, dan kontrol biaya. Pada fase ini, container apps sering lebih mudah diorganisasi karena boundary service lebih eksplisit dan tooling operasional lebih seragam.

Serverless tetap cocok pada organisasi besar, terutama untuk pipeline event-driven dan integrasi asinkron, tetapi memerlukan platform standardization yang matang. Tanpa itu, tim bisa terjebak pada sprawl: fungsi banyak, trigger beragam, dependency tidak terlihat jelas, dan debugging lintas tim menjadi lambat.

Kapan serverless lebih tepat?

  • API atau backend dengan trafik tidak stabil dan ada periode idle panjang.
  • MVP yang perlu diluncurkan cepat tanpa menyiapkan banyak infrastruktur.
  • Workload event-driven seperti webhook receiver, transformasi file, notifikasi, atau task reaktif.
  • Tim kecil yang ingin fokus ke logic bisnis dan menerima batasan platform.
  • Use case yang secara alami stateless dan request-nya relatif pendek.

Contoh cocok: endpoint webhook yang memvalidasi request, menyimpan event, lalu meneruskan ke queue untuk diproses asinkron.

Kapan container apps lebih tepat?

  • API dengan trafik relatif stabil atau dapat diprediksi.
  • Aplikasi membutuhkan kontrol runtime lebih dalam, dependency sistem, atau proses startup yang tidak ringan.
  • Perlu worker persisten, scheduler, stream consumer, atau background job jangka panjang.
  • Database connection pooling harus dijaga ketat.
  • Tim sudah siap mengelola CI/CD container, health check, resource tuning, dan observability yang lebih aktif.

Contoh cocok: backend SaaS B2B dengan API utama, worker queue, scheduled reconciliation, dan kebutuhan logging/tracing yang konsisten di banyak komponen.

Skenario nyata

1) MVP produk baru

Kondisi: trafik belum pasti, tim kecil, prioritas utama validasi produk, anggaran terbatas, fitur backend masih berubah cepat.

Pendekatan yang sering masuk akal: mulai dengan serverless untuk endpoint sederhana dan event-driven flow. Gunakan database terkelola, object storage, dan queue bila perlu. Jaga jumlah fungsi tetap sedikit; jangan pecah terlalu dini.

Kenapa: biaya idle rendah dan beban operasional minim. Tim bisa fokus membangun fitur, bukan platform.

Risiko: jika MVP berkembang menjadi API dengan trafik stabil, query kompleks, dan worker berat, arsitektur awal bisa mulai terasa sempit.

2) SaaS B2B

Kondisi: ada jam sibuk yang cukup jelas, SLA mulai diperhatikan, integrasi antar sistem meningkat, audit log dan observability penting, ada background job rutin.

Pendekatan yang sering masuk akal: container apps untuk API utama dan worker, ditambah komponen serverless untuk task event-driven tertentu jika benar-benar menguntungkan.

Kenapa: latency lebih konsisten, koneksi database lebih mudah dikontrol, dan boundary operasional lebih jelas untuk tim yang mulai tumbuh.

Risiko: tim harus siap dengan pengelolaan image, deployment strategy, autoscaling policy, dan tuning resource.

3) Workload bursty

Kondisi: trafik melonjak saat kampanye, impor data periodik, atau webhook massal dari pihak ketiga.

Pendekatan yang sering masuk akal: kombinasikan ingestion layer yang elastis dengan pemrosesan asinkron yang terkontrol. Serverless bisa menerima lonjakan event dengan cepat, lalu queue menahan laju pemrosesan ke database atau downstream. Worker container memproses queue dengan concurrency yang dibatasi.

Kenapa: ini memisahkan kebutuhan scale-out di sisi masuk dari kebutuhan proteksi pada dependency inti.

Risiko: jika seluruh jalur dipaksa sinkron sampai database, scaling di sisi depan hanya memindahkan bottleneck ke belakang.

Contoh arsitektur praktis: hybrid yang sering lebih sehat

Dalam praktik, keputusan tidak harus biner. Banyak sistem backend/API yang lebih sehat menggunakan pendekatan hybrid.

Client -> API Gateway -> Ingress Service
                     ├─> Validasi ringan dan autentikasi
                     ├─> Simpan request metadata
                     └─> Push ke Queue

Queue -> Worker Container Apps
      ├─> Batasi concurrency
      ├─> Gunakan connection pooling ke database
      └─> Retry terkontrol + dead-letter queue

Observability
├─ Structured logs
├─ Trace ID / Correlation ID
└─ Metrics untuk latency, queue depth, error rate

Arsitektur ini bekerja karena memisahkan komponen yang perlu elastis dari komponen yang perlu kestabilan. Ingress dapat menyerap burst, sementara worker menjaga database dan sistem downstream tetap aman.

Anti-pattern umum

Anti-pattern pada serverless

  • Terlalu banyak fungsi kecil tanpa kontrak dan ownership yang jelas.
  • Request sinkron memanggil banyak fungsi berantai sehingga latency dan debugging memburuk.
  • Mengabaikan database connection limits saat scale-out.
  • Memuat dependency besar di startup sehingga cold start membesar.
  • Menggunakan serverless untuk proses panjang yang lebih cocok jadi worker persisten.

Anti-pattern pada container apps

  • Migrasi ke container terlalu dini hanya karena ingin “lebih fleksibel”, padahal beban operasional belum sepadan.
  • Tidak menetapkan resource limit/request sehingga performa tidak stabil.
  • Menyatukan API, worker, scheduler, dan batch job dalam satu proses tanpa boundary jelas.
  • Autoscaling tanpa backpressure ke database dan downstream service.
  • Menganggap container otomatis portabel padahal deployment dan operasi masih sangat spesifik vendor.

Tanda bahwa tim perlu migrasi atau mengubah pendekatan

Dari serverless ke container apps

  • Cold start mulai mengganggu SLA atau pengalaman pengguna.
  • Biaya meningkat karena trafik stabil, bukan lagi sporadis.
  • API utama membutuhkan connection pooling yang lebih konsisten.
  • Background job semakin kompleks dan durasinya makin panjang.
  • Observability lintas fungsi terlalu sulit dikelola.
  • Tim ingin standardisasi runtime dan deployment di banyak service.

Dari container apps ke serverless

  • Banyak service jarang dipakai tetapi harus tetap hidup.
  • Tim kecil kewalahan oleh pekerjaan operasional dasar.
  • Workload sebenarnya event-driven dan singkat.
  • Biaya idle terlalu besar dibanding trafik nyata.
  • Sebagian komponen bisa dipisahkan menjadi fungsi stateless yang sederhana.

Checklist keputusan saat review arsitektur

  1. Bagaimana pola trafiknya? Stabil, sporadis, atau bursty?
  2. Seberapa sensitif latency API terhadap cold start?
  3. Apakah workload stateless? Jika tidak, state bisa dipindah ke sistem eksternal atau tidak?
  4. Berapa batas koneksi database yang aman? Apa dampak scale-out pada DB?
  5. Apakah ada background job panjang atau worker persisten?
  6. Apakah concurrency perlu dikontrol ketat?
  7. Bagaimana strategi observability? Log terstruktur, trace, metrics, correlation ID?
  8. Seberapa besar toleransi terhadap vendor lock-in?
  9. Mana yang lebih dominan: biaya idle atau biaya lonjakan trafik?
  10. Apakah tim mampu mengelola container runtime, image, health check, dan scaling policy?
  11. Apakah arsitektur perlu portabilitas tinggi dalam 12-24 bulan ke depan?
  12. Jika sistem gagal scale, bottleneck pertama ada di mana? Compute, DB, queue, atau downstream API?

Tips implementasi agar keputusan tidak hanya teoritis

  • Uji dengan traffic shape yang realistis, bukan hanya request rata-rata.
  • Monitor p95/p99, bukan hanya latency rata-rata.
  • Hitung kapasitas database terlebih dahulu sebelum membuka scaling besar di aplikasi.
  • Gunakan queue untuk menyerap burst jika downstream tidak mampu menerima lonjakan sinkron.
  • Jaga deployment unit tetap masuk akal; jangan memecah layanan tanpa alasan operasional atau domain yang jelas.
  • Dokumentasikan batasan platform seperti timeout, retry behavior, dan concurrency model agar tim tidak salah asumsi.

Kesimpulan

Serverless vs Container Apps untuk backend/API adalah soal kompromi antara elastisitas, kontrol, biaya, dan beban operasional. Serverless lebih tepat ketika trafik tidak stabil, workload stateless, dan tim ingin bergerak cepat dengan overhead operasional minimal. Container apps lebih tepat ketika aplikasi membutuhkan latency yang lebih konsisten, kontrol runtime lebih besar, worker persisten, dan pengelolaan koneksi atau concurrency yang lebih ketat.

Tidak ada pilihan yang selalu unggul. Untuk banyak sistem nyata, solusi terbaik justru hybrid: gunakan komponen yang sangat elastis untuk menerima event atau burst, lalu gunakan container apps untuk pemrosesan yang butuh kestabilan dan kontrol. Keputusan yang matang bukan ditentukan oleh tren, tetapi oleh bentuk trafik, perilaku dependency, dan kapasitas tim untuk mengoperasikan sistemnya.