Pada pipeline citra ber-throughput tinggi, pertanyaan utamanya bukan sekadar “bisa memproses gambar atau tidak”, tetapi bagaimana sistem tetap responsif saat data masuk terus-menerus, pemrosesan berat berjalan paralel, dan hasil harus tersedia cepat. Dalam praktik backend, pilihan antara monolit modular, worker terpisah berbasis queue, atau microservice sangat memengaruhi latensi, throughput, backpressure, biaya infrastruktur, dan beban operasional tim.
Analogi yang berguna adalah video Atomic Force Microscope (AFM) berkecepatan tinggi: sistem menghasilkan rangkaian frame/data sangat cepat, dan nilai utamanya muncul hanya jika seluruh alur—ingest, pemrosesan, penyimpanan, dan penyajian—mampu mengikuti ritme data. Jika satu tahap melambat, antrean menumpuk. Jika semua dipaksa berjalan dalam satu proses tanpa batas yang jelas, permukaan sistem tampak sederhana tetapi rapuh saat beban naik.
Masalah inti pada pipeline citra ber-throughput tinggi
Dalam konteks citra atau frame ilmiah seperti AFM, pola datanya sering mirip:
- Ingest cepat dan terus-menerus: file/frame datang berurutan, kadang dalam burst.
- Pemrosesan CPU-bound: decoding, denoise, alignment, feature extraction, thumbnailing, kompresi, atau analisis numerik.
- Penyimpanan multi-lapis: objek mentah, metadata, hasil turunan, indeks pencarian, dan cache hasil.
- Penyajian hasil: UI perlu melihat progres, preview, atau hasil akhir tanpa menunggu semua batch selesai.
Arsitektur yang dipilih harus menjawab beberapa pertanyaan praktis:
- Apakah request API harus menunggu pemrosesan selesai?
- Jika laju ingest lebih tinggi daripada laju proses, bagaimana backpressure diterapkan?
- Apakah kegagalan satu job boleh menjatuhkan API utama?
- Apakah tim kecil mampu mengoperasikan banyak layanan sekaligus?
Model 1: Monolit modular
Monolit modular berarti satu aplikasi utama menangani API, logika bisnis, akses database, dan sering kali juga job background, tetapi dengan pemisahan modul yang jelas di level kode. Ini bukan “satu file besar”; idealnya, ada batas domain yang rapi walau tetap dideploy sebagai satu unit.
Kapan monolit modular cukup
- Volume data masih moderat dan pola beban relatif dapat diprediksi.
- Tim kecil butuh kecepatan iterasi tinggi.
- Kebutuhan deployment sederhana lebih penting daripada isolasi penuh.
- Pemrosesan citra belum terlalu berat, atau masih bisa dipindah ke background dalam proses yang sama.
- Observability dan debugging lebih mudah jika semua log dan trace berada dalam satu tempat.
Kelebihan monolit modular
- Kompleksitas operasional rendah: satu codebase, satu pipeline deploy, satu tempat konfigurasi utama.
- Latensi internal rendah: komunikasi antarmodul lewat pemanggilan fungsi, bukan jaringan.
- Produktivitas tim kecil lebih tinggi: perubahan skema data, API, dan proses background lebih mudah disinkronkan.
- Debugging lebih langsung: lebih sedikit moving parts.
Keterbatasan monolit modular
- Failure isolation lemah: bug memory leak atau proses CPU berat dapat mengganggu API.
- Skalabilitas tidak selektif: jika hanya modul pemrosesan yang butuh scale, Anda tetap cenderung men-scale seluruh aplikasi.
- Backpressure lebih sulit jika semua masuk lewat request sinkron.
- Kontensi resource: thread worker, koneksi database, dan CPU saling berebut dengan traffic pengguna.
Pola monolit yang sehat
Jika memilih monolit, usahakan tetap modular dan pisahkan concern berikut:
- Ingest API: validasi, autentikasi, pencatatan metadata awal.
- Storage adapter: simpan objek mentah ke object storage atau filesystem terkelola.
- Job dispatcher: membuat tugas pemrosesan asinkron.
- Processing module: decoding dan transformasi berat tidak dijalankan di jalur request utama.
- Result presenter: endpoint untuk polling status, mengambil preview, atau stream hasil.
Catatan praktis: Monolit sering gagal bukan karena “salah arsitektur”, tetapi karena semua pekerjaan dilakukan sinkron di request thread. Jika upload file langsung memicu analisis berat sebelum respons dikembalikan, API akan cepat mengalami timeout atau antrian koneksi.
Model 2: Worker terpisah dengan queue
Untuk banyak sistem citra, ini adalah titik tengah paling masuk akal. Aplikasi utama tetap monolit atau semi-monolit, tetapi pekerjaan berat dipindahkan ke worker terpisah yang mengambil job dari queue. Dengan begitu, API fokus pada ingest, orkestrasi, dan penyajian status, sementara CPU-bound processing berjalan di proses atau mesin lain.
Mengapa worker terpisah sering menjadi langkah scaling pertama yang tepat
Pada pipeline seperti AFM, data masuk bisa konstan, sedangkan waktu proses tiap frame dapat bervariasi. Queue memberi tiga hal penting:
- Decoupling: ingest tidak harus menunggu pemrosesan selesai.
- Buffering: lonjakan trafik bisa ditahan sementara di antrean.
- Control: concurrency worker bisa diatur sesuai kapasitas CPU, memori, dan storage.
Alur data yang umum
- Client atau alat akuisisi mengirim frame/file ke API ingest.
- API menyimpan objek mentah ke storage dan mencatat metadata ke database.
- API membuat job ke queue, misalnya
process_frameataubuild_preview. - Worker mengambil job, memuat file dari storage, lalu menjalankan pemrosesan CPU-bound.
- Worker menyimpan hasil turunan, memperbarui status, dan mengirim event progres jika perlu.
- Client mengambil status/hasil lewat API atau menerima notifikasi asinkron.
Contoh struktur job sederhana
POST /ingest/frame
{
"dataset_id": "exp-2026-08-13-a",
"frame_id": "000123",
"object_key": "raw/exp-2026-08-13-a/000123.bin",
"processing_profile": "afm_fast_preview"
}
Setelah metadata tersimpan, aplikasi membuat pesan queue seperti:
{
"job_type": "process_frame",
"dataset_id": "exp-2026-08-13-a",
"frame_id": "000123",
"object_key": "raw/exp-2026-08-13-a/000123.bin",
"profile": "afm_fast_preview",
"attempt": 1
}
Contoh pseudocode worker
while true:
job = queue.reserve(timeout=5)
if not job:
continue
try:
mark_status(job.frame_id, "processing")
raw = storage.read(job.object_key)
image = decode_frame(raw)
preview = build_preview(image)
features = extract_features(image)
preview_key = storage.write(preview)
save_result(job.frame_id, preview_key, features)
mark_status(job.frame_id, "done")
queue.ack(job)
except TemporaryError:
queue.retry(job, delay=10)
except Exception as err:
mark_status(job.frame_id, "failed", error=str(err))
queue.move_to_dead_letter(job)
Kelebihan model worker queue
- Failure isolation lebih baik: crash pada decoder atau library citra tidak langsung mematikan API.
- Scale lebih efisien: tambah worker saat CPU-bound stage menjadi bottleneck.
- Backpressure lebih eksplisit: panjang antrean menjadi sinyal kapasitas sistem.
- Resilience lebih baik: retry, dead-letter queue, dan rate limiting lebih mudah diterapkan.
Trade-off model worker queue
- Kompleksitas meningkat: harus mengelola queue, idempotency, retry policy, dan observability lintas proses.
- Latensi end-to-end bisa bertambah jika antrean panjang, walau latensi request API turun.
- Konsistensi menjadi asinkron: hasil tidak langsung tersedia setelah upload selesai.
- Debugging perlu disiplin tracing: tanpa correlation ID, penyelidikan masalah cepat melelahkan.
Kapan perlu memisahkan worker CPU-bound
Pemisahan worker hampir selalu layak dipertimbangkan saat salah satu kondisi berikut muncul:
- Request upload atau create task sering melampaui timeout.
- CPU aplikasi utama sering penuh karena decoding atau transformasi citra.
- Peningkatan traffic pengguna membuat pemrosesan batch ikut terganggu, atau sebaliknya.
- Ada library native untuk image processing yang berpotensi crash, leak memory, atau memakan banyak RAM.
- Pemrosesan per item dapat berjalan independen dan cocok diantrikan.
Untuk tim kecil, pola ini biasanya memberi rasio manfaat-kompleksitas terbaik: tetap satu aplikasi utama, tetapi worker dipisah sebagai proses/servis khusus.
Model 3: Microservice
Microservice memecah pipeline ke layanan-layanan terpisah dengan batas domain dan deployment independen, misalnya ingest-service, processing-service, metadata-service, notification-service, dan query-service. Pendekatan ini berguna bila skala organisasi, variasi beban, atau kebutuhan isolasi sudah melampaui kenyamanan monolit + worker.
Kapan microservice layak dipilih
- Setiap domain punya kebutuhan scale, release cycle, atau dependensi runtime yang berbeda jauh.
- Tim sudah terbagi jelas dan dapat memiliki layanan masing-masing.
- Pipeline mencakup banyak tahap independen dengan SLA berbeda.
- Kebutuhan failure isolation dan security boundary sangat tinggi.
- Volume sistem membuat bottleneck operasional monolit + worker sulit dikendalikan.
Kelebihan microservice
- Isolasi dan skalabilitas paling granular.
- Kebebasan teknologi lebih tinggi bila memang dibutuhkan.
- Boundary domain lebih eksplisit jika desainnya matang.
Biaya nyata microservice
- Deployment jauh lebih kompleks: service discovery, secrets, jaringan internal, kontrak API, dan kompatibilitas versi.
- Observability wajib matang: distributed tracing, metrik per layanan, log agregat.
- Biaya infrastruktur meningkat karena lebih banyak proses, image, dan jalur komunikasi.
- Kesalahan desain lebih mudah menyebar: chatty API, duplikasi model data, dan ownership kabur.
Prinsip aman: Jangan memakai microservice hanya karena pipeline memiliki banyak tahap. Banyak tahap pemrosesan tidak otomatis berarti banyak layanan. Jika satu tim kecil masih mengelola semuanya, monolit modular dengan worker biasanya lebih ekonomis dan lebih mudah dipelihara.
Membandingkan latensi, throughput, dan backpressure
Latensi
Monolit sinkron memberi latensi hasil rendah hanya jika prosesnya ringan. Begitu decoding atau analisis frame menjadi berat, latensi request melonjak. Worker queue menurunkan latensi request API karena pekerjaan dipindah ke background, tetapi latensi hasil akhir bergantung pada waktu antrean. Microservice menambah hop jaringan, sehingga desain kontrak dan cache menjadi lebih penting.
Throughput
Untuk throughput tinggi, yang penting bukan hanya kecepatan satu request, tetapi berapa banyak item yang bisa diproses stabil per satuan waktu. Worker terpisah unggul karena concurrency dapat diatur spesifik untuk stage CPU-bound. Microservice memberi kontrol lebih granular lagi, tetapi hanya bermanfaat jika tiap stage memang punya pola bottleneck berbeda.
Backpressure
Backpressure adalah mekanisme agar sistem tidak menerima beban melebihi kapasitasnya. Dalam pipeline citra, ini krusial. Tanpa backpressure, antrean bisa membesar, storage penuh, dan waktu tunggu hasil menjadi tidak berguna.
Strategi backpressure yang umum:
- Batasi jumlah upload simultan per sumber data.
- Terapkan kuota atau rate limit per dataset/proyek.
- Batasi concurrency worker sesuai CPU dan RAM.
- Gunakan antrean terpisah untuk prioritas berbeda, misalnya preview cepat vs analisis penuh.
- Tolak atau tunda ingest jika queue depth melewati ambang operasional.
Tabel keputusan arsitektur
| Kriteria | Monolit Modular | Worker Terpisah + Queue | Microservice |
|---|---|---|---|
| Ukuran tim | Sangat cocok untuk tim kecil | Cocok untuk tim kecil-menengah | Lebih cocok jika ownership sudah terbagi |
| Kompleksitas deployment | Rendah | Sedang | Tinggi |
| Isolasi kegagalan | Terbatas | Baik untuk job berat | Paling baik jika diimplementasikan benar |
| Skalabilitas selektif | Terbatas | Baik pada stage CPU-bound | Sangat granular |
| Latensi request API | Baik jika proses ringan | Baik karena asinkron | Bergantung desain antar layanan |
| Observability | Paling sederhana | Perlu tracing job | Perlu observability matang |
| Biaya infra | Paling rendah | Menengah | Paling tinggi |
| Cocok untuk pipeline citra throughput tinggi | Ya, pada fase awal dan beban terkendali | Sering menjadi pilihan terbaik | Layak jika skala organisasi/sistem sudah besar |
Contoh keputusan praktis untuk pipeline mirip AFM
Skenario A: tahap awal produk atau riset internal
Jika alat menghasilkan frame berurutan, pengguna hanya perlu upload, preview sederhana, dan metadata dasar, monolit modular biasanya cukup. Simpan file mentah, buat status dataset, dan jalankan job background sederhana dari aplikasi yang sama jika perlu. Fokus utama adalah reliabilitas ingest dan struktur data yang rapi, bukan pemecahan layanan.
Skenario B: pemrosesan mulai berat dan CPU jadi bottleneck
Saat preview, denoise, segmentasi, atau ekstraksi fitur mulai memakan CPU signifikan, pisahkan worker. API tetap ringan, worker bisa discale terpisah, dan antrean memberi kontrol kapasitas. Ini adalah langkah paling umum dan paling masuk akal sebelum berpikir ke microservice.
Skenario C: banyak konsumen dan banyak tahap terpisah
Jika hasil tidak hanya dipakai satu UI tetapi juga untuk analitik, notifikasi, indexing, batch export, dan integrasi eksternal, Anda mungkin butuh boundary yang lebih kuat. Di titik ini, sebagian komponen bisa berkembang menjadi layanan terpisah, tetapi lakukan karena kebutuhan operasional nyata, bukan asumsi bahwa microservice selalu lebih modern.
Implementasi yang sehat: status, idempotency, dan retry
Status pipeline yang eksplisit
Jangan gunakan satu flag “done/failed” untuk seluruh lifecycle. Minimal, status item sebaiknya mencerminkan fase:
receivedstoredqueuedprocessingprocessedfailed
Status seperti ini membantu UI, operasional, dan debugging.
Idempotency
Dalam sistem queue, pesan bisa terkirim ulang atau diproses dua kali. Karena itu, worker harus aman terhadap duplikasi. Misalnya, hasil turunan disimpan dengan key deterministik atau ada pengecekan apakah frame tertentu sudah selesai diproses sebelum menulis ulang hasil.
Retry yang selektif
Jangan semua error di-retry. Pisahkan:
- Error sementara: gangguan jaringan, storage timeout, dependency sibuk.
- Error permanen: file korup, format tidak dikenali, metadata tidak valid.
Retry buta pada error permanen hanya akan membanjiri queue.
Observability yang benar-benar dibutuhkan
Pada pipeline citra, observability bukan tambahan opsional. Tanpa ini, Anda sulit menjawab apakah masalah ada di ingest, queue, worker, storage, atau database.
Metrik minimum
- Request rate dan error rate API ingest
- Queue depth dan age of oldest message
- Durasi proses per job type
- Success/failure rate worker
- CPU, memori, dan I/O worker
- Waktu dari ingest hingga hasil siap
Log dan tracing
Gunakan correlation ID atau dataset/frame ID secara konsisten di API, queue, worker, dan storage log. Ini jauh lebih penting daripada sekadar log verbose. Tanpa identitas lintas tahap, menelusuri satu frame yang gagal akan sulit.
Debugging gejala umum
- API cepat, hasil lama: cek queue depth, concurrency worker, dan bottleneck storage read/write.
- Worker sering restart: cek penggunaan memori, ukuran file ekstrem, atau library native yang tidak stabil.
- Database lambat: mungkin status update terlalu sering atau hasil metadata terlalu granular untuk ditulis sinkron.
- Preview tersedia, hasil analitik tidak: pisahkan antrean prioritas agar job ringan tidak menunggu job berat.
Anti-pattern yang sering terjadi
1. Semua pemrosesan dijalankan sinkron di request
Ini paling sering terjadi pada fase awal. Sistem terasa sederhana sampai dataset besar masuk, lalu timeout bermunculan. Solusinya hampir selalu memindahkan kerja berat ke background.
2. Memecah ke microservice terlalu cepat
Tim kecil sering berakhir dengan banyak repo, banyak pipeline deploy, dan debugging lintas layanan yang mahal, padahal bottleneck utamanya hanya satu: worker CPU-bound yang belum dipisah.
3. Queue tanpa strategi kapasitas
Menambahkan queue saja tidak cukup. Jika concurrency worker tidak dibatasi atau queue bisa tumbuh tanpa kontrol, Anda hanya memindahkan bottleneck, bukan menyelesaikannya.
4. Penyimpanan hasil terlalu chatty
Menulis status atau metrik per frame terlalu sering ke database relasional bisa menciptakan bottleneck baru. Pertimbangkan agregasi status atau update periodik untuk data non-kritis.
5. Tidak membedakan jalur cepat dan jalur berat
Dalam banyak pipeline, preview cepat untuk UI dan analisis penuh punya SLA berbeda. Mencampur keduanya di antrean yang sama bisa merusak pengalaman pengguna.
Checklist evaluasi sebelum scaling
- Apakah bottleneck sudah terukur? CPU worker, database, storage I/O, atau jaringan?
- Apakah request API perlu sinkron? Jika tidak, pindahkan ke model job.
- Apakah pemrosesan benar-benar CPU-bound? Jika ya, pisahkan worker sebelum memecah layanan lain.
- Apakah tim mampu mengoperasikan observability lintas layanan? Jika belum, tahan dulu microservice.
- Apakah boundary domain jelas? Jika tidak, microservice akan memperbesar kebingungan.
- Apakah ada mekanisme backpressure? Queue depth, rate limit, dan concurrency limit harus nyata, bukan asumsi.
- Apakah job idempotent dan punya retry policy?
- Apakah jalur preview dan analitik berat perlu dipisah?
- Apakah biaya infra tambahan sebanding dengan manfaat isolasi?
- Apakah kebutuhan scaling bersifat teknis atau hanya dugaan masa depan?
Rekomendasi praktis untuk tim kecil
Untuk sebagian besar backend pipeline citra ber-throughput tinggi, urutan keputusan yang sehat adalah:
- Mulai dari monolit modular dengan boundary kode yang rapi.
- Saat proses berat muncul, pisahkan worker CPU-bound menggunakan queue.
- Tambah observability, backpressure, dan status pipeline yang eksplisit.
- Baru pertimbangkan microservice jika ada kebutuhan organisasi, isolasi, atau skala yang benar-benar membenarkannya.
Dengan pendekatan ini, Anda menghindari dua ekstrem yang sama-sama mahal: monolit yang terlalu padat dan rapuh, atau microservice yang terlalu dini dan membebani tim. Untuk kasus seperti aliran frame AFM berkecepatan tinggi, arsitektur terbaik biasanya bukan yang paling kompleks, melainkan yang mampu menyerap data stabil, memisahkan beban CPU berat, dan tetap mudah dioperasikan.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!