Saya Me-rollback 2.724 Baris Setelah Satu Perubahan AI Audio Merusak Production
Saya mendorong perubahan yang seharusnya jadi peningkatan pada sistem suara platform podcast saya. Enam hari dan beberapa commit kemudian, saya menghapus 2.724 baris kode dan me-rollback ke versi yang stabil. Inilah yang terjadi dan apa yang saya pelajari tentang menguji perubahan AI di production.
Sebenarnya saya ingin menulis postingan ini lebih cepat. Tapi semuanya memakan waktu lebih lama dari yang saya duga, dan saya ingin memastikan ceritanya sudah benar sebelum membagikannya.
Pada 28 April 2026, saya men-deploy apa yang saya kira merupakan peningkatan pada sistem text-to-speech DIALØGUE. Model suara yang berbeda terlihat menjanjikan di atas kertas. Saya mendengar -- dan juga mengalami sendiri -- bahwa suara dari model saat ini cenderung berubah-ubah pada podcast yang lebih panjang. Jadi saya beralih.
Menjelang 30 April, saya sudah menghapus 2.724 baris kode dan me-rollback ke jalur stabil per 22 April.
Podcast seorang pengguna asli terjebak. Sistem tidak bisa menghasilkan audio. Sistem menandainya AUDIO_FAILED lalu melempar 409 Conflict saat retry, yang awalnya terlihat seperti bug sebenarnya. Ternyata itu hanya gejala -- podcast sudah dalam kondisi gagal saat retry terjadi. Masalah aslinya lebih sederhana dan lebih parah: sebuah segment timeout pada konfigurasi baru. Fallback-nya juga timeout. Dan sistem akhirnya menyerah begitu saja.
Parahnya lagi, ini adalah skrip yang mengarah ke audio bicara sekitar 65 menit. Median podcast di platform ini 26 menit. Saya menerima skrip yang ukurannya lebih dari dua kali lipat itu melalui sistem yang belum pernah berhasil menghasilkan audio sedekat itu. Itulah risiko sebenarnya -- dan perubahan model-lah yang membukanya.
Saya harus mengakui, ini bukan momen terbaik saya sebagai seorang builder. Saya mendorong perubahan konfigurasi tanpa stress testing yang layak didapatkan sistem production. Ini yang terjadi, apa yang rusak, dan apa yang saya pelajari.
Apa yang DIALØGUE lakukan
Kalau belum pernah melihatnya, DIALØGUE adalah generator podcast AI. Anda memberinya topik, PDF, atau episode acara, dan hasilnya adalah podcast konversasi dua pembawa acara. Semua ini berjalan di atas sintesis text-to-speech Gemini dari Google.
Pipeline audio bekerja seperti ini:
- Buat outline dari materi sumber
- Kembangkan outline menjadi skrip lengkap
- Bagi skrip menjadi segment-segment
- Sintesis audio untuk setiap segment menggunakan Gemini TTS
- Gabungkan semua segment, normalisasi loudness, dan upload MP3 final
Default production adalah model Gemini TTS yang bekerja baik untuk sebagian besar kasus. Tapi semakin panjang skrip, semakin terdengar pergeseran suara antar segment. Pembawa A di segment satu tidak terdengar persis sama dengan Pembawa A di segment enam. Untuk podcast 25 menit, ini terdengar tapi lama-lama terbiasa. Untuk yang mendekati 40 menit, ini mulai terasa kasar.
Saya memutuskan mencoba konfigurasi model yang berbeda. Idennya adalah memperbaiki konsistensi suara di podcast yang lebih panjang.
Apa yang saya bangun
Selama sekitar seminggu di akhir April, saya mendorong serangkaian commit untuk mencoba konfigurasi model yang berbeda. Ini bukan perubahan satu baris yang sederhana. Saya membangun pergeseran arsitektur yang cukup besar:
- Mengganti model TTS default ke konfigurasi baru
- Menambahkan fallback chain -- jika model utama timeout, fallback ke model production yang stabil
- Membangun sistem QA level chunk: bagi transkrip jadi unit lebih kecil, sintesis masing-masing, validasi kualitas audio dengan analisis ffmpeg
- Menambahkan tracking progress workflow agar UI bisa menampilkan status sintesis per segment
- Memperkuat logika retry -- tiga percobaan per chunk, exponential backoff
- Menambahkan quality gate audio panjang yang akan memblokir
COMPLETEkecuali MP3 final yang sudah digabung lolos QA audio
Idennya adalah bergerak dari "hasilkan audio per segment dan semoga terdengar konsisten" menjadi "chunk transkrip, QA tiap chunk, retry yang gagal, validasi file final."
Menjelang 28 April, kode sudah ter-deploy ke production. Unit test lulus. Integration test lulus. Yang tidak saya punya -- dan seharusnya saya punya -- adalah load test yang menjalankan export penuh 50+ menit terhadap konfigurasi baru. Saya melewatkan satu-satunya test yang sebenarnya penting.
Apa yang rusak
Hampir seketika, sebuah podcast production gagal.
Ini podcast yang panjang -- analisis capex Big Tech yang mengarah ke sekitar 65 menit audio bicara. Sistem mencapai langkah audio dan mengembalikan AUDIO_FAILED.
Awalnya, saya melihat 409 Conflict dan mengira orchestrator punya bug. Ternyata 409 itu gejala sekunder -- percobaan retry setelah podcast sudah ditandai gagal. Kegagalan pertamanya jauh lebih sederhana.
Segment 0 timeout pada konfigurasi model baru. Timeout-nya disetel ke 60 detik. Model fallback -- yang merupakan model sama seperti yang dipakai jalur stabil -- juga timeout. Sistem menandai podcast sebagai gagal dan melanjutkan.
Fallback-nya seharusnya bekerja. Tapi fallback dipanggil dengan budget timeout 60 detik yang sama, pada segment besar yang sama, dalam konteks request yang sudah menghabiskan waktu pada pemanggilan primary yang gagal. Jalur stabil, sebaliknya, memanggil model yang sama itu dalam keadaan fresh dengan timeout penuh tersedia dan memproses segment secara individual. Model sama, kondisi berbeda -- itulah kenapa versi stabil bisa menanganinya dengan baik tapi fallback tidak.
Saya menonaktifkan sistem chunk QA di production dengan perubahan satu baris. Itu tidak memperbaiki timeout. Konfigurasi baru masih timeout pada segment yang ditangani model stabil tanpa masalah.
Nah, ini jenis momen di mana Anda harus memilih: terus debug jalur baru, atau kembali ke apa yang berhasil.
Rollback
Pada 30 April, saya membuat keputusan.
Commit rollback (4a5bfc8) menghapus 2.724 baris kode:
- Seluruh modul TTS chunker
- Gate analisis kualitas audio
- Tracking progress workflow
- Test suite retry QA chunk
- Konfigurasi chain model fallback
Dan menambahkan 321 baris untuk mempertahankan bagian yang penting -- frase prompt suara terlokalisasi, kompatibilitas frontend, dan coverage regression untuk jalur stabil.
Itu banyak baris yang dihapus. Jujur, rasanya seperti mengakui kekalahan. Tapi bukan itu. Itu memilih jalur jangka panjang yang benar atas sesuatu yang terlihat seperti kemajuan tapi belum siap.
Setelah rollback, saya:
- Rebuild Docker image base shared
- Redeploy service
generate-speech - Reset podcast yang gagal kembali ke persetujuan skrip
- Klik "Generate Audio" melalui UI normal
Hasilnya:
- Model: Gemini TTS (konfigurasi production stabil)
- Segment: 6 dari 6 selesai
- Durasi: 1.527 detik -- sekitar 25 menit
- MP3 final: 30,5 MB
- Status:
COMPLETE
Podcast yang sudah terjebak dua hari selesai dalam sekitar 11 menit setelah rollback. Episode pengguna itu terkirim -- dua hari lebih lambat dari rencana, tapi selesai.
Satu hal tentang angka 65 menit: itu estimasi durasi berdasarkan panjang skrip mentah, bukan output final. Setelah reset, skrip melewati pipeline pemendekan normal, dan audio final yang dirakit keluar sekitar 25 menit. Skrip aslinya akan lebih panjang lagi -- yang juga menjadi bagian dari kenapa itu gagal.
Data
Sebelum memutuskan apa yang harus dilakukan selanjutnya, saya ingin angka yang nyata. Bukan feeling -- data. Saya query database production untuk informasi durasi podcast.
| Kohort | Jumlah | Median Durasi | p90 | Maks |
|---|---|---|---|---|
| 30 hari terakhir | 8 | 26 mnt | 31 mnt | 34 mnt |
| 90 hari terakhir | 72 | 27 mnt | 36 mnt | 45 mnt |
| Sepanjang waktu | 120 | 26 mnt | 30 mnt | 45 mnt |
Jadi median podcast sekitar 26 menit. p90 sekitar 34-36 menit. Podcast terpanjang yang pernah selesai adalah 45 menit.
Dan ini data yang lebih keras lagi -- podcast yang mencapai tahap audio, dibagi berdasarkan estimasi durasi:
| Estimasi Durasi | Mencapai Audio | Selesai | Gagal | In Progress |
|---|---|---|---|---|
| Di bawah 15 mnt | 18 | 17 | 0 | 1 |
| 15-30 mnt | 36 | 31 | 2 | 3 |
| 30-40 mnt | 24 | 23 | 0 | 1 |
| 40-50 mnt | 3 | 1 | 2 | 0 |
| 50+ mnt | 2 | 0 | 2 | 0 |
"In Progress" mencakup podcast yang dimulai tapi tidak pernah selesai -- ditinggalkan pengguna, dibatalkan saat generation, atau tertinggal dalam status pending. Dua podcast dicoba pada 50+ menit. Keduanya gagal di tahap audio. Nol yang selesai.
Tidak ada podcast production yang pernah mencapai 50 menit. Sistem bekerja baik untuk rentang 15-40 menit. Di atas 40 menit, risikonya naik tajam. Dan di atas 50 menit, itu pada dasarnya wilayah yang belum teruji.
Apa yang saya pelajari
1. Perubahan konfigurasi butuh stress testing level production
Saya harus jujur soal ini. Konfigurasi model baru mungkin bekerja baik di test prompt pendek. Tapi di production, dengan skrip asli dan batasan timeout yang nyata, itu gagal pada segment yang ditangani konfigurasi stabil tanpa masalah.
Saya tidak melakukan benchmark dengan benar sebelum deploy. Test prompt pendek akan berhasil. Podcast 65 menit dengan banyak segment di bawah timeout 60 detik adalah konteks yang sama sekali berbeda. Jarak antara dua skenario itu adalah tempat insiden production tinggal.
Saya mungkin salah tentang konfigurasi itu sendiri -- mungkin itu hanya butuh lebih banyak penyetelan atau timeout yang lebih panjang. Tapi poinnya tetap: saya mengubah dependensi production inti tanpa stress testing yang layak didapatkan sistem yang benar-benar diandalkan orang.
2. Inkonsistensi suara adalah masalah sebenarnya, bukan timeout
Timeout adalah kegagalan yang terlihat. Tapi alasan saya mencoba konfigurasi berbeda sejak awal adalah inkonsistensi suara. Bahkan di model stabil, suara bergeser antar pemanggilan sintesis. Pembawa A di segment satu tidak terdengar persis sama dengan Pembawa A di segment enam.
Untuk podcast pendek, ini nyaris tidak terasa. Untuk yang lebih panjang, ini terakumulasi. Dan untuk podcast 50+ menit -- yang sekali lagi, belum ada yang selesai di production -- ini mungkin akan sangat jelas terasa.
Pendekatan chunked mencoba menyelesaikan ini dengan membuat tiap chunk lebih kecil dan lebih terkontrol. Itu arah yang benar, saya rasa. Implementasinya saja yang belum siap untuk production.
3. Saya butuh telemetry sebelum insiden, bukan sesudahnya
Saat kegagalan terjadi, saya tidak bisa memetakan biaya atau performa TTS ke ID podcast spesifik. Log tidak punya entri yang berguna. Event workflow tidak punya catatan generasi TTS.
Saya harus mendiagnosa kegagalan dari field status podcast, error 409 yang membingungkan, dan reproduksi lokal dari perilaku timeout. Itu berhasil pada akhirnya, tapi bukan pengalaman debugging yang ingin saya alami lagi.
Saya menambahkan telemetry biaya TTS setelahnya -- model yang dipakai, model fallback, jumlah retry, status per percobaan, karakter transkrip, byte audio output, durasi audio. Ini seharusnya sudah ada sebelum insiden. Dari pengalaman saya, memang selalu begitu. Anda membangun observabilitas setelah kebakaran, bukan sebelumnya.
4. Rollback bukan kegagalan
Menghapus 2.724 baris kode terasa buruk. Saya tidak akan pura-pura sebaliknya. Anda menghabiskan seminggu membangun sesuatu, Anda bangga dengan arsitekturnya, lalu Anda merobek semuanya karena belum siap.
Tapi itu keputusan yang tepat. Sistem chunk QA adalah desain yang baik. Itu akan kembali -- sebagai perubahan yang lebih kecil dan terisolasi dengan validasi canary yang tepat. Hanya bukan sebagai bagian dari perubahan konfigurasi model. Dan bukan saat setup baru masih timeout pada segment yang ditangani jalur stabil tanpa masalah.
5. Episode 50 menit adalah produk yang berbeda
Ini yang mengejutkan saya. Saya mengira sistem akan menangani skrip sepanjang apapun. Data mengatakan sebaliknya.
Kalau seseorang benar-benar ingin podcast 50 menit, itu profil generasi yang berbeda. Mungkin butuh skrip yang diperketat menjadi 45 menit atau kurang sebelum generasi audio. Gate review manual sebelum TTS. Budget timeout per segment yang lebih kuat. Mungkin bahkan strategi sintesis yang sama sekali berbeda.
Mengoptimalkan jalur default untuk rentang 15-40 menit adalah keputusan yang tepat. Episode 50+ menit seharusnya diperlakukan sebagai jalur pengecualian, bukan norma. Saya rasa itu keputusan produk, bukan hanya engineering.
Di mana posisi sekarang
Production sudah kembali ke konfigurasi TTS stabil. Tidak sempurna. Inkonsistensi suara di podcast yang lebih panjang masih ada, dan saya bisa mendengarnya. Tapi cukup stabil sehingga platform bekerja untuk sebagian besar kasus penggunaan.
Kalau ingin mendengar apa yang dihasilkan DIALOGUE, Anda bisa mencobanya sendiri di https://podcast.chandlernguyen.com.
Laporan insiden sudah di-commit ke repository. Rollback didokumentasikan dengan bentuk SQL yang tepat untuk mereset podcast yang gagal. Telemetry sudah terpasang sekarang untuk percobaan berikutnya.
Dan pelajarannya lebih jelas dari yang saya inginkan:
Di production AI, biaya mengubah dependensi inti tanpa stress testing yang tepat bukan eksperimen yang gagal. Itu pengalaman pengguna yang gagal.
Saya akan mencoba lagi dengan konfigurasi model yang berbeda. Tapi lain kali, dengan telemetry yang lebih baik, jalur deployment canary, dan stress test yang menjalankan export penuh 50+ menit sebelum apapun menyentuh production.
Sebelum mengubah dependensi AI production, inilah checklist yang seharusnya saya ikuti:
- Stress test workload terburuk. Jalankan workload terpanjang dan terberat yang Anda antisipasi -- bukan cuma test prompt pendek. Kalau sistem Anda menangani podcast 40 menit, test dengan yang 50 menit.
- Log telemetry per percobaan dari hari pertama. Nama model, model fallback, jumlah retry, timeout, karakter transkrip, byte audio output, durasi audio. Ini seharusnya sudah ada sebelum insiden. Memang selalu begitu.
- Canary satu job asli. Sebelum membalik saklar untuk semua orang, jalankan satu job production asli end to end pada konfigurasi baru dan verifikasi outputnya.
- Verifikasi fallback dengan budget timeout yang sama. Fallback yang berbagi timeout sama dengan primary bukan fallback -- itu kesempatan kedua untuk gagal dalam kondisi yang sama.
- Tentukan SQL rollback dan runbook sebelum deploy. Tahu persis cara mereset job yang stuck. Dokumentasikan perintahnya. Kalau tidak punya ini, Anda akan improvisasi saat insiden.
Kalau Anda membangun sistem AI production, apa pola Anda untuk menguji perubahan konfigurasi model tanpa merusak pengguna? Saya sungguh ingin tahu. Apakah Anda pernah mengalami momen "mengubah satu hal dan semuanya rusak" yang serupa?
Salam,
Chandler





