Ketika Anda menjalankan broker mobil di Azerbaijan dan mengimpor kendaraan salvage dari Amerika Serikat, masalah perangkat lunak Anda akan terlihat berbeda dari startup di Silicon Valley. Anda tidak sedang mengoptimalkan untuk satu juta pengguna bersamaan. Anda sedang mengoptimalkan kejelasan, uptime, dan kemampuan untuk memperbaiki masalah sendiri pada tengah malam sambil berkoordinasi dengan rumah lelang yang berada di zona waktu dua belas jam jauhnya. Itulah tepatnya situasi yang saya hadapi saat membangun AutoMakler. Platform ini menangani segalanya, mulai dari scraping lelang langsung dan pengecekan Carfax hingga estimasi pengiriman dan pemrosesan pembayaran. Ini adalah sistem produksi nyata yang melayani pelanggan nyata, dan berjalan di atas apa yang oleh kebanyakan pengembang disebut sebagai stack yang sangat membosankan.

Stack yang Tidak Ingin Dipamerkan Siapapun

Tidak ada React. Tidak ada Vue. Tidak ada Redis, tidak ada Celery, dan tidak ada server WebSocket. Backend-nya menggunakan FastAPI dengan Python murni. Databasenya adalah PostgreSQL. Frontend-nya adalah HTML yang dirender di sisi server menggunakan template Jinja2, Bootstrap, dan sedikit vanilla JavaScript. Untuk scraping, saya menggunakan Playwright. Semuanya berjalan sebagai satu proses Python tunggal yang menyajikan HTML secara langsung.

Tidak ada langkah build. Tidak ada folder node_modules untuk diaudit, tidak ada transpiler untuk dikonfigurasi, dan tidak ada perubahan framework frontend yang harus terus diikuti. Saat saya melakukan deploy, saya hanya memindahkan file Python dan template, bukan mengorkestrasi pipeline bundler. Kesederhanaan itu bukanlah sebuah kompromi. Itu adalah tujuan utamanya.

Cara Mengantrekan Pekerjaan Tanpa Message Broker

Scraping lelang mobil secara langsung tidak bisa dilakukan secara sinkron. Satu kali scraping bisa memakan waktu beberapa detik saat Playwright memuat halaman, mengeksekusi JavaScript, dan mengekstrak data. Membiarkan pengguna menunggu saat hal ini terjadi bukanlah sebuah pilihan. Panduan standar menyarankan untuk menginstal Redis, mengonfigurasi Celery, dan menjalankan worker pool. Saya melewati semua itu.

Sebagai gantinya, AutoMakler menggunakan Postgres sebagai job queue miliknya sendiri. Ketika pengguna memicu scraping, aplikasi menulis baris baru ke dalam tabel tasks dengan status pending. Sebuah background task asyncio mengambil baris tersebut dan menjalankan scraping browser. Sementara itu, browser melakukan polling ke endpoint ringan setiap tiga detik untuk memeriksa status. Ketika baris tersebut diperbarui menjadi completed, halaman akan menyegarkan diri dan menampilkan hasilnya.

Pola ini berhasil karena interval polling cukup singkat untuk terasa responsif, namun cukup lama untuk menghindari beban berlebih pada server. Tiga detik adalah waktu yang sangat lama bagi komputer dan hampir tidak terasa bagi manusia yang sedang menunggu situs lelang eksternal. Database menangani konkurensi secara asli, dan karena pekerjaan tersebut hanyalah baris di Postgres, saya dapat memeriksa antrean dengan query SQL sederhana alih-alih harus menggali log Celery atau key Redis.

Menjaga Server Tetap Hidup Tanpa Worker Pool

Otomasi browser sangat haus memori. Jalankan terlalu banyak instance Playwright sekaligus dan server Anda akan tumbang. Solusi konvensionalnya adalah menggunakan managed worker pool dengan batasan konkurensi, yang sering kali didukung oleh kombinasi Redis dan Celery yang sama. Saya hanya menggunakan satu baris Python: sebuah asyncio.Semaphore.

Semaphore ini membatasi berapa banyak instance browser simultan yang dapat berjalan. Ketika permintaan scraping baru masuk, ia akan langsung mengambil slot yang tersedia atau menunggu hingga ada slot yang terbuka. Semua ini terjadi di dalam proses yang sama. Tidak ada orkestrator eksternal yang bisa gagal, tidak ada proses worker yang mati secara diam-diam, dan tidak ada infrastruktur tambahan untuk dipantau. Penggunaan memori saya tetap dapat diprediksi, dan kode yang melindungi server berada tepat di samping kode yang menggunakannya, tidak tersembunyi dalam manifest deployment.

Mengarahkan Uang dengan Satu URL Callback

Pemrosesan pembayaran menghadirkan batasan yang tidak bisa saya ubah. Payment gateway saya mengizinkan tepat satu URL callback per akun merchant, tetapi saya perlu memproses transaksi untuk dua proyek terpisah melalui satu akun tersebut. Membangun profil merchant kedua akan berarti biaya tambahan, kepatuhan tambahan, dan dokumen tambahan yang tidak dimiliki oleh broker kecil.

Solusinya adalah dengan menyandikan (encode) nama proyek langsung ke dalam string ID pesanan sebelum mengirim pelanggan ke gateway. Saat callback mencapai server saya, AutoMakler mendekode ID tersebut, mengidentifikasi proyek mana yang terkait dengan pembayaran tersebut, dan mengarahkan notifikasi ke handler internal yang benar. Logika yang ada tetap tidak tersentuh. Ini adalah desain aditif: saya tidak menulis ulang alur pembayaran, saya hanya membuat pengidentifikasi tersebut membawa sedikit lebih banyak konteks. Ini adalah jenis hack yang terlihat jelas setelah dilakukan, namun dapat menghemat berjam-jam kerumitan arsitektural.

Chat yang Berjalan Tanpa WebSockets

Chat dukungan pelanggan biasanya menjadi titik di mana para engineer menyerah dan menambahkan WebSockets. Saya membutuhkan pesan dalam aplikasi, tetapi saya juga perlu menjaga jejak infrastruktur tetap kecil. Jadi, saya menggunakan kembali strategi polling yang sama yang menggerakkan scraping lelang.

Pesan disimpan di Postgres. Saat pengguna mengirim pesan, pesan tersebut ditulis ke tabel. Klien melakukan polling untuk pembaruan, dan UI menampilkan pesan baru serta tanda pesan telah dibaca secara hampir real-time. Untuk menjaga kecepatan ini bahkan saat tabel percakapan bertambah besar, saya menambahkan partial index Postgres yang hanya mencakup pesan yang belum dibaca untuk percakapan aktif. Database tidak membuang siklus untuk memindai riwayat lama, dan query planner dapat memenuhi sebagian besar pencarian chat dengan pemindaian rentang indeks yang ketat.

Untuk chat dukungan di mana latensi beberapa detik masih dapat diterima, cara ini sangat memadai. Pengguna mendapatkan umpan balik yang mereka butuhkan, dan saya tidak pernah harus men-debug koneksi WebSocket yang usang atau mengelola server socket terpisah.

Kekurangan yang Jujur

Arsitektur ini melibatkan trade-off yang nyata, dan berpura-pura sebaliknya akan menjadi tidak jujur. Polling itu bersifat "chatty". Setiap tiga detik, setiap klien aktif memukul server. Bandwidth dan beban query lebih tinggi daripada yang dibutuhkan oleh koneksi socket yang persisten. Jika proses Python dimulai ulang, tugas latar belakang yang sedang berjalan akan langsung mati karena tidak ada worker eksternal untuk mengambilnya kembali. Saya menerima hal ini karena tugas-tugasnya kecil dan biaya untuk mencoba lagi sangat rendah. Scraping browser yang gagal cukup dipicu ulang oleh pengguna.

Ada juga batasan untuk pendekatan ini. Jika AutoMakler perlu melayani ribuan scraping simultan, model proses tunggal dengan polling akan kewalahan. Namun, bukan itu bisnis yang saya jalani. Saya membutuhkan keandalan untuk puluhan pengguna konkuren, bukan