- Startup yang baru saja mengaktifkan monetisasi mengalami gangguan pembayaran langganan, tetapi karena tidak bisa direproduksi secara internal, identifikasi penyebabnya tertunda selama 5 hari
- Gangguan bermula saat format konversi Prisma/TypeScript→Python/SQLAlchemy buatan ChatGPT disalin, lalu string ID yang di-hardcode masuk sebagai nilai default alih-alih fungsi pembuat UUID
- Karena arsitekturnya terdiri dari 8 task AWS ECS dan masing-masing 5 instance, pengguna bisa bertemu salah satu dari maksimal 40 pool ID unik, sehingga pada siang hari masalahnya tertutupi oleh deploy yang sering
- Saat malam deploy berhenti, satu ID pada tiap server habis terpakai, dan setelah itu upaya langganan baru gagal karena tabrakan unique ID
- Dengan 50 keluhan per hari, selama 5 hari, dan biaya langganan $40 per bulan, kerugiannya diperkirakan mencapai $10,000 per bulan; ketiadaan testing, logging, alerting, dan praktik menyalin kode memperbesar dampak insiden
Gangguan langganan yang muncul tepat setelah monetisasi
- Startup tersebut pertama kali mengaktifkan monetisasi pada bulan Mei dan mendapatkan pelanggan pertama dalam 1 jam setelah peluncuran
- Keesokan paginya, Gmail sudah dipenuhi lebih dari 40 keluhan pengguna
- Pengguna tidak bisa menyelesaikan langganan
- Saat menekan tombol langganan, muncul spinner loading tanpa henti
- Mereka membuat akun baru dan mencoba memeriksa sendiri, tetapi di lingkungan internal langganan tetap berjalan normal sehingga penyebabnya tidak bisa direproduksi
- Pada jam kerja hampir tidak ada keluhan, dan gangguan terutama menumpuk pada malam hari
Implementasi monetisasi di bawah tekanan waktu
- Mei adalah saat batch YC S23 dimulai, dan tim belum yakin arah mana yang paling tepat setelah peluncuran
- Dalton, group partner YC, menyarankan agar pelanggan berbayar dijadikan metrik penentu arah, dan harga bulanan yang dipikirkan dinaikkan dua kali lipat
- Harga akhirnya ditetapkan menjadi $40 per bulan
- Proyek awalnya full-stack NextJS, tetapi sebelum dan sesudah pekerjaan monetisasi mereka sedang memindahkan sistem ke Python/FastAPI
- Mereka memanfaatkan ChatGPT selama proses migrasi
- Integrasi Stripe juga sudah selesai
- Selama 5 hari berikutnya, waktu tidur berkurang drastis dan mereka harus menangani 30–50 email keluhan setiap hari
Format konversi model buatan ChatGPT
- Dalam proses migrasi backend, model database dipindahkan dari Prisma/TypeScript ke Python/SQLAlchemy
- Karena konversi model terasa membosankan dan ChatGPT dinilai melakukannya dengan baik, mereka menggunakannya untuk hampir seluruh migrasi
- Kode yang dihasilkan disalin-tempel lalu dicek apakah berjalan, dan karena tampak normal di production, proses itu diteruskan
- Saat itu, penyisipan database masih ditangani oleh Next API, dan backend Python hanya membaca database
- Ketika fitur langganan diimplementasikan, untuk pertama kalinya mereka mulai menyisipkan record DB dari Python
- Model SQLAlchemy baru dibuat sendiri, tetapi format buatan ChatGPT dari model lama disalin apa adanya
- Masalah yang sama masuk ke metode pembuatan ID di semua model
Penyebab sebenarnya dan mengapa tidak terlihat di siang hari
- Kesalahan intinya adalah memberikan satu string ID yang di-hardcode, bukan memberikan fungsi atau lambda yang menghasilkan UUID
- Jika satu pengguna di instance backend tertentu berhasil menyelesaikan langganan dengan ID itu, maka upaya langganan berikutnya pada instance yang sama akan memicu tabrakan ID unik
- Konfigurasi backend membuat masalah ini tersembunyi lebih lama
- Mereka menjalankan 8 task ECS di AWS
- Setiap task menjalankan 5 instance backend
- Pengguna berpotensi mencapai salah satu dari 40 ID berbeda
- Pada siang hari mereka langsung commit ke branch main 10–20 kali per hari, dan setiap kali itu memicu deploy backend baru
- Setiap deploy menciptakan 40 ID baru yang bisa dipakai pelanggan
- Pada malam hari, commit dan deploy berhenti sehingga satu ID pada tiap server cepat habis terpakai
- Awalnya ada hampir 40 server yang masih bisa menerima langganan, tetapi seiring waktu jumlah itu turun mendekati 0
Besarnya kerugian dan langkah setelahnya
- Kerugian dihitung sebagai
50 emails/day x 5 days x $40/monthdan diperkirakan mencapai $10,000 pendapatan bulanan yang hilang- Perhitungan ini hanya berdasarkan pengguna yang mengirim keluhan
- Untuk menemukan penyebabnya dibutuhkan 5 hari, sangat banyak email, ratusan log Sentry, percakapan Discord yang panjang dengan engineer Stripe, dan peninjauan lima file inti
- Setelah penyebab ditemukan, Adam segera mengunggah perbaikannya
- Setelah itu mereka menambahkan unit/integration test yang kuat, alerting, dan logging
- Insiden ini menunjukkan bahwa kesalahan manusia, testing yang kurang, penyalinan kode, dan push langsung ke main dapat membuat satu baris kecil berujung pada kehilangan pendapatan yang besar
2 komentar
Eh, kode yang dibuat otomatis oleh AI itu tentu harus ditinjau dulu, kenapa langsung dipakai begitu saja?
Opini Hacker News
Tidak adanya monitoring itulah yang membuat mereka kehilangan 10 ribu dolar. Aplikasi terus-menerus mengeluarkan exception database dalam jumlah besar, tetapi tidak ada yang menerima notifikasi
Kalau ada notifikasi seperti itu, investigasinya akan selesai dalam 5 menit, bukan 5 hari. Jika sistem notifikasinya tidak diperbaiki, sebenarnya tidak ada yang benar-benar diperbaiki
Pemrograman itu mudah saat semuanya berjalan baik; bagian yang sulit adalah menangani masalah
Begitu ada pelanggan berbayar yang masuk, dibutuhkan orang yang punya pengetahuan dan pengalaman untuk menangani logging, monitoring, notifikasi, keamanan, dan sebagainya. DevOps tidak boleh diperlakukan secara amatiran
Namun bagian yang benar-benar gila adalah database tidak punya logging error dan notifikasi. Ini bukan kode legacy berusia 20 tahun, melainkan produk baru, dan juga bukan kode dari masa ketika error DB dipakai seperti validasi data
Artikel blog-nya menampilkan 404, jadi saya tinggalkan tautan Web Archive
https://web.archive.org/web/20240610032818/https://asim.bear...
Penulis menambahkan koreksi penting: praktik yang dilakukan di sini sangat buruk dan memalukan, dan setelah itu mereka menambahkan unit/integration test yang solid serta notifikasi/logging. Intinya, ini adalah kesalahan manusia, dan jika dilihat ke belakang jelas bisa dihindari
Ia juga menambahkan bahwa kejadian ini terjadi dalam beberapa minggu awal perusahaan, di bawah tekanan waktu yang besar, dan meminta agar ini dilihat sebagai cerita lucu tentang bug yang secara unik bisa direproduksi di production
Memang kesalahan bodoh, tetapi manusia, baik individu maupun kelompok, memang pasti melakukan kesalahan bodoh
https://0912i390129ionkjan.bearblog.dev/how-a-single-chatgpt...
https://webcache.googleusercontent.com/search?q=cache%3Ahttp...
Kesalahannya langsung terlihat. Dengan tetap menghormati timnya, ini tidak terlalu berkaitan dengan ChatGPT, melainkan lebih karena mereka memakai model pemrograman yang belum cukup familier bagi tim
Bahkan andaipun lolos code review, besar kemungkinan ini bisa tertangkap hanya dengan tool monitoring yang bisa disiapkan dalam 5 menit
Judul seperti “Kami membuat kesalahan pemrograman saat menggunakan LLM, dan karena tidak melakukan quality assurance, biayanya 10 ribu dolar” tidak akan memicu reaksi eksekutif seperti “Kalau ChatGPT mengacau, berapa eksposur kerugian kita?” Akan ada sangat banyak manajer tingkat menengah dan senior yang membagikan tulisan ini di LinkedIn
LLM tidak bisa “melakukan kesalahan”. Ia tidak deterministik, tidak bernalar, tidak berpikir, dan tidak menjalankan logika. Ia adalah generator word salad yang sangat mewah berbasis probabilitas statistik, dan karena tidak ada jaminan bahwa keluarannya benar atau akurat, secara definisi istilah kesalahan juga tidak tepat
Edit: setelah tulisan ini banyak di-downvote karena alasan yang jelas, peringkatnya tiba-tiba melonjak, yang tampaknya berarti moderator mem-boost-nya: https://hnrankings.info/40627558/
Lucu bahwa tulisan clickbait yang menurut aturan seharusnya judulnya diubah justru di-boost oleh moderator. Selain itu, fakta bahwa penulisnya tampaknya berasal dari perusahaan Y Combinator tentu saja pasti hanya kebetulan belaka: https://news.ycombinator.com/item?id=40629998
Untuk pembuatan UUID pada primary key, seharusnya memakai callable
uuid.uuid4secara langsung, bukanstr(uuid.uuid4()), dan SQLAlchemy akan memanggil fungsi itu saat membuat nilai. Untuk default tanggal, ia mengatakanserver_default=text("(now())")mungkin tidak berjalan sesuai harapan sehingga sebaiknya memakaifunc.now(), juga meminta memastikan imporuuiddantextdari SQLAlchemy, serta menyarankan mempertimbangkanDateTime(timezone=True)untuk penanganan zona waktuSetelah itu ia mengusulkan kode perbaikan
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()), unique=True, nullable=False), dan penambahanlambda:di sini memperbaiki masalahnyauuid.uuid4()dianggap seperti definisi skema di Prisma atau semacamnya. Jadi bug ini sendiri tidak mengejutkan, dan saya pun bisa saja membuat kesalahan yang samaTetap saja, satu kali
kubectl logsseharusnya langsung menangkapnya. Selain itu, dari Next.js dan Prisma lalu pindah ke Python? Kenapa?Kesalahan itu sendiri bisa dipahami. Bahkan tanpa ChatGPT, kode seperti ini tampaknya cukup mudah terlewat
Namun saya tidak paham mengapa setelah kegagalan pertama ini tidak tertangkap. Apakah perusahaan ini tidak punya logging? Fakta bahwa backend mencoba memakai ulang UUID seharusnya langsung terlihat dari error-nya
Tidak jarang orang tidak sengaja mengoper string ke fungsi yang seharusnya menerima objek callable, bukan
String. Kalau tidak memakai ORM, masalah spesifik ini mungkin bisa dihindari, meski itu mungkin hanya bias pribadi saya terhadap ORM. Bug serupa juga sangat mungkin terjadi dalam konteks selain databaseOrang-orang yang dengan yakin mengatakan pasti akan menangkap bug ini entah memang engineer yang jauh lebih hebat daripada saya, atau, lebih mungkin, agak salah menilai kemampuan diri sendiri
Namun tidak adanya log, atau tidak melihat log, benar-benar sulit dipahami. Kalau memakai ECS, sepertinya exception Duplicate Key akan diteruskan ke CloudWatch tanpa konfigurasi khusus; saya penasaran apakah itu tidak terjadi, atau terjadi tetapi tidak ada yang memeriksa exception apa yang muncul semalaman
Dalam situasi seperti ini, berguna untuk bertanya mengapa deteksinya terlambat dan mengapa diagnosisnya memakan waktu lama
Saya sudah beberapa kali melihat kesalahan yang sama di kode buatan manusia. Terutama di React / TypeScript / JavaScript, orang sering lupa menambahkan lambda
Tulisan blog itu terasa tidak benar-benar menjelaskan akar masalahnya dan langsung menyalahkan ChatGPT. Kalau bekerja terburu-buru, lalu memasukkan commit berisi perubahan besar atau tanpa review rekan ke main, hal seperti ini memang terjadi
Masalah sebenarnya adalah kalau kita terburu-buru, mengambil jalan pintas, serta tidak melakukan pengujian yang memadai dan code review oleh rekan, error akan muncul. Rasanya ini pasti langsung ketahuan kalau saja ada tes yang mencoba beberapa opsi pendaftaran
Kalau orang seperti ini ditempatkan di dekat kode yang penting secara finansial, masalah serupa akan muncul, dan saya akan mempertanyakan penilaian orang yang memutuskan untuk men-deploy kode itu dengan hampir tanpa tes
Mengejutkan bahwa tidak ada aturan lint untuk kasus ini
Semoga saja tidak begitu
Bagian “proyek aslinya full-stack NextJS, tetapi saya ingin lebih dulu memigrasikan semuanya ke Python/FastAPI” cukup membuka mata
Saya tidak mengerti bagaimana startup yang belum punya pelanggan bisa membenarkan penulisan ulang
Terlepas dari ada atau tidaknya pelanggan, saya tidak mengerti mengapa pada tahap sedini ini melakukan perpindahan horizontal yang pada dasarnya dari Node ke Python. Kalau mereka sudah punya ratusan pelanggan dan ingin beralih ke sesuatu seperti Go, mungkin masih bisa sedikit dimengerti, meski tetap meragukan
Misalnya, saya harus membuat banyak objek DTO, tetapi AutoMapper tidak berjalan dengan kombinasi versi dan konfigurasi proyek yang saya pakai, dan Entity Framework serta serialisasi/deserialisasi JSON juga lebih banyak menyakitkan daripada memberi manfaat
Tentu ini bisa diselesaikan secara bertahap. Bisa dengan menggali dokumentasi dalam-dalam, mencampurnya dengan hack, meng-upgrade paket, dan menulis ulang konfigurasi. Namun sebagai manusia, rasanya ingin mengambil jeriken bensin metaforis, membakar semuanya, lalu membuat sistem kedua yang lebih baik. Tentu pada kenyataannya belum tentu lebih baik; biasanya hanya muncul titik sakit lain, dan mungkin juga tidak bisa melakukan semua hal yang dilakukan sistem pertama atau tidak melakukannya dengan benar
Di tempat kerja pun, setiap melihat sistem legacy atau sistem yang merepotkan, dorongan yang sama muncul. Dibutuhkan usaha aktif dan terus-menerus untuk mengalahkan otak yang berteriak agar menulis ulang. Kadang penulisan ulang atau perubahan arsitektur seperti adopsi container memang berhasil, tetapi biasanya berakhir masuk ke kobaran api atau menjadi pekerjaan tanpa akhir
Kecuali ada keyakinan tinggi bahwa itu akan meningkatkan operasi sistem atau developer experience bagi developer lain, untungnya saya tidak menyerah pada dorongan itu
ChatGPT justru pihak yang menghasilkan uang untuk aplikasi itu. Karena tanpa ChatGPT, mereka tidak punya kemampuan untuk mengimplementasikannya
Kurangnya kemampuan untuk coding, debugging, logging, dan monitoring-lah yang membuat 10 ribu dolar melayang, dan dalam cerita ini efek bersih ChatGPT positif
Semua pesan commit berisi emoji. Monyet, pisang, roket, kembang api, dan entah apa lagi
https://grook.ai/share?id=e269e88a7b1a71eff4f176c864b30161&x...
Katanya awalnya full-stack NextJS, lalu saat memigrasikan backend ke Python/FastAPI, mereka menerjemahkan model database Prisma/Typescript ke Python/SQLAlchemy. Pekerjaan ini membosankan, dan karena menilai ChatGPT cukup bagus melakukannya, mereka memakainya untuk hampir seluruh migrasi
Kalau sejak awal tidak ada ChatGPT, mereka mungkin tidak akan mencoba migrasi pendahuluan ini, jadi sulit menyebut efek bersihnya positif. Stack lama mungkin punya logging error yang lebih baik, mungkin juga tidak; dan karena kodenya ditulis sendiri, mereka mungkin lebih memahami strukturnya sehingga kebutuhan itu lebih kecil
Keputusan untuk “menulis ulang semua kode untuk kedua kalinya” sebelum mengaktifkan monetisasi itu sendiri memang menarik
Ada kalimat, “Saya ingin terlebih dahulu mengatakan bahwa praktik di sini buruk dan bisa dihindari. Ini terjadi pada periode lain ketika ada tekanan waktu besar. Mohon baca dengan mempertimbangkan hal ini”
Karena batasan seperti inilah langganan software terasa menakutkan
Karena race condition, kami pernah menagih pengguna dua kali. Jadi setiap melihat timeout atau error yang berkaitan dengan uang, saya jadi paranoid sampai-sampai pertama-tama mengasumsikan pembayaran sudah terjadi lalu memeriksanya lagi nanti
Kode yang ditulis dengan TypeScript dan Python, framework seperti Next.js, menjalankan masing-masing 5 instance untuk 8 task AWS, tapi pendapatannya hanya 40 dolar dan masa pengembangannya cuma beberapa minggu?
Saya jadi bertanya-tanya sebenarnya apa yang terjadi. Mereka memperbaikinya dengan alasan kodenya berantakan karena keterbatasan waktu, tetapi yang lebih buruk adalah mereka justru menghabiskan waktu untuk refactoring lintas bahasa dan membangun sistem terdistribusi tanpa alasan jelas
Ini adalah kompleksitas yang melukai diri sendiri, karena mereka mencoba menjuggling fitur dan kompleksitas teknis yang tidak masuk akal sekaligus. Entah apa yang mereka pikirkan
Koreksi: Ini perusahaan YC musim panas 2023, tetapi bahkan pada musim panas 2024 produknya tampaknya masih berada di balik daftar tunggu. Mungkin karena sedang ditulis ulang dengan Rust
Rasanya mereka bahkan belum pernah menulis total 1000 baris Python, tetapi berhasil menemukan masalahnya dengan tepat
Python punya cacat karena gagal menyalin strategi evaluasi Common Lisp dengan benar. Jika ada argumen seperti
foo=obj.whatever()pada ekspresi nilai default untuk argumen fungsi opsional,obj.whatever()dievaluasi saat definisi fungsi diproses, bukan saat fungsi dipanggilSaya curiga itu sengaja dibuat begitu demi efisiensi. Python juga punya cacat lain: tidak ada sintaks literal sejati untuk objek umum seperti list.
[1, 2, 3]bukan literal, melainkan lebih mirip konstruktor, dan setiap kali dievaluasi harus membuat list baru lalu mengisinya dengan nilaiPerancangnya mungkin tidak ingin parameter seperti
list=[]membuat list kosong baru setiap kali argumen dihilangkan. Di Lisp,'(1 2 3)dan'()adalah literal sejati dan menunjuk ke objek yang sama setiap kali direferensikan. Programmer bisa memilih apakah akan memakai(list 1 2 3)atau'(1 2 3)sebagai ekspresi nilai defaultYang pertama membuat objek mutable baru setiap kali seperti
[1, 2, 3], sedangkan yang kedua hampir pasti mengembalikan objek yang sama dan tidak dapat dimodifikasi secara andal serta portabel. Bahasa populer modern sudah memiliki sebagian besar fitur Lisp, jadi ini terasa seperti lelucon bahwa tidak ada ruginyafoo=obj.whatever(),obj.whatever()dievaluasi saat definisi fungsi diproses, bukan saat fungsi dipanggil, rasanya tidak mungkin benarKalau
.whatever()bergantung pada state internal yang berubah setelah inisialisasi objek, lalu apa yang terjadi?