Skip to content
··10 min basahin

Nag-rollback ako ng 2,724 Lines Matapos Sirain ng Isang AI Audio Change ang Production

Nag-push ako ng isang inaakalang upgrade sa voice system ng podcast platform ko. Anim na araw at ilang commits ang nakalipas, tinanggal ko ang 2,724 lines ng code at nag-rollback sa dating gumagana. Narito ang nangyari at ang itinuro nito sa akin tungkol sa pagte-test ng production AI changes.

Gusto kong isulat ang post na ito nang mas maaga. Pero mas matagal inasahan ko ang pag-unravel ng buong bagay, at gusto kong siguraduhing tama ang kwento bago ko i-share.

Noong April 28, 2026, nag-deploy ako ng inaakalang improvement sa text-to-speech system ng DIALØGUE. May ibang voice model na mukhang promising sa papel. Naririnig ko — at naririnig mismo sa sarili ko — na nagdri-drift ang voice ng current model kapag mas mahahaba ang podcast. Kaya lumipat ako.

Pagdating ng April 30, tinanggal ko ang 2,724 lines ng code at nag-rollback sa April 22 stable path.

May totoong user na podcast ang na-stuck. Hindi makagenerate ng audio. Na-mark ng system ang AUDIO_FAILED at pagkatapos ay nag-throw ng 409 Conflict sa retry — sa una, mukhang ito mismo ang bug. Pero pala symptom lang pala — nasa failed state na ang podcast nung nangyari ang retry. Ang totoong problema ay mas simple at mas masama: may nag-timeout na segment sa bagong configuration. Nag-timeout din ang fallback. At pagkatapos, sumuko na lang ang system.

Para lumala pa ang lahat, ito ay isang script na papuntang 65 minutes ng spoken audio. Ang median podcast sa platform ay 26 minutes. Tinanggap ko ang script na higit sa doble ng laki nito sa isang system na hindi pa nag-generate ng kahit katulad nito. Ito mismo ang totoong risk — at ang model change ang nag-expose nito.

Kailangang aminin ko, hindi ito ang pinakamagandang moment ko bilang builder. Nag-push ako ng configuration change nang walang stress testing na deserve ng production system. Narito ang nangyari, ang nasira, at ang natutunan ko.

Ano ang ginagawa ng DIALØGUE

Kung hindi mo pa nakikita, ang DIALØGUE ay isang AI podcast generator. Bibigyan mo lang ng topic, PDF, o show episode, at gagawa ito ng two-host conversational podcast. Ang buong proseso ay naka-base sa Gemini text-to-speech synthesis ng Google.

Ang audio pipeline ay ganito:

  1. Gumenerate ng outline mula sa source material
  2. I-expand ang outline sa buong script
  3. Hatiin ang script sa mga segments
  4. I-synthesize ang audio para sa bawat segment gamit ang Gemini TTS
  5. I-stitch ang segments, i-normalize ang loudness, at i-upload ang final MP3

Ang production default ay Gemini TTS model na gumagana nang maayos sa karamihan ng use cases. Pero habang humahaba ang script, mas naririnig mo ang voice drift sa pagitan ng segments. Ang Host A sa segment one ay hindi eksaktong katulad ng Host A sa segment six. Para sa 25-minute podcast, noticeable pero sanay ka lang. Para sa anuman na papuntang 40 minutes, mahirap na.

Napagdesisyunan kong subukan ang ibang model configuration. Ang idea ay i-improve ang voice consistency sa mas mahahabang podcast.

Ang binuo ko

Sa loob ng mga isang linggo sa huling bahagi ng April, nag-push ako ng serye ng commits para subukan ang ibang model configuration. Hindi ito casual one-line change. Nagbuo ako ng buong architecture shift:

  • Nilipat ang default TTS model sa bagong configuration
  • Nagdagdag ng fallback chain — kapag nag-timeout ang primary model, magfa-fallback sa stable production model
  • Nagbuo ng chunk-level QA system: hatiin ang transcripts sa maliliit na units, i-synthesize ang bawat isa, i-validate ang audio quality gamit ang ffmpeg analysis
  • Nagdagdag ng workflow progress tracking para maipakita ng UI ang per-segment synthesis state
  • Nilakas ang retry logic — tatlong attempts bawat chunk, exponential backoff
  • Nagdagdag ng long-form audio quality gate na magba-block sa COMPLETE maliban kung pumasa sa audio QA ang final assembled MP3

Ang idea ay lumipat mula sa "generate audio per segment at sana consistent ang tunog" patungo sa "chunk transcripts, QA bawat chunk, retry failures, i-validate ang final file."

Pagdating ng April 28, na-deploy na ang code sa production. Pumasok ang unit tests. Pumasok ang integration tests. Ang wala ako — at dapat meron — ay load test na nagpapatakbo ng buong 50+ minute export sa bagong configuration. Nilaktawan ko ang isang test na pinaka-importante mismo.

Ano ang nasira

Halos agad-agad, may nag-fail na production podcast.

Mahaba ito — isang analysis ng Big Tech capex na papuntang 65 minutes ng spoken audio. Hinampas ng system ang audio step at nag-return ng AUDIO_FAILED.

Sa una, nakita ko ang 409 Conflict at iniisip kong may bug ang orchestrator. Lumabas na ang 409 ay secondary symptom — isang retry attempt pagkatapos na ma-mark na failed ang podcast. Mas simple ang unang failure.

Nag-timeout ang Segment 0 sa bagong model configuration. Ang timeout ay naka-set sa 60 seconds. Ang fallback model — pareho ng model na ginagamit ng stable path — nag-timeout din. Na-mark ng system ang podcast bilang failed at nagpatuloy.

Dapat gumana ang fallback. Pero nainvoke ang fallback sa parehong 60-second timeout budget, sa parehong malaking segment, sa parehong request context na nakagastos na ng oras sa failed primary call. Ang stable path, sa kabilang banda, tatawag sa parehong model nang fresh with full timeout available at i-process ang segments individually. Pareho ang model, iba ang conditions — kaya nga kayang-kaya ng stable version pero hindi ng fallback.

Dinisable ko ang chunk QA system sa production sa isang-line na change. Hindi naayos ang timeout. Nagti-timeout pa rin ang bagong configuration sa mga segment na kayang-kaya ng stable model.

Eto na ang moment kung saan kailangang pumili: magpatuloy sa debugging ng bagong path, o bumalik sa gumagana.

Ang rollback

Noong April 30, ginawa ko ang desisyon.

Ang rollback commit (4a5bfc8) ay nag-delete ng 2,724 lines ng code:

  • Ang buong TTS chunker module
  • Audio quality analysis gates
  • Workflow progress tracking
  • Ang chunk QA retry test suites
  • Ang fallback model chain configuration

At nagdagdag ng 321 lines para ma-preserve ang mga mahahalaga — localized voice prompt phrasing, frontend compatibility, at regression coverage para sa stable path.

Sobrang laking lines na tinanggal. Feeling ko, talo ako, honestly. Pero hindi. Ito ay pagpili ng tamang long-term path sa halip na isang bagay na parang progress pero hindi pa handa.

Pagkatapos ng rollback, ginawa ko ang mga sumusunod:

  1. Muling binuo ang shared base Docker image
  2. Muling na-deploy ang generate-speech service
  3. Na-reset ang failed podcast pabalik sa script approval
  4. Nag-click ng "Generate Audio" sa normal na UI

Ang resulta:

  • Model: Gemini TTS (stable production config)
  • Segments: 6 sa 6 ay kumpleto
  • Duration: 1,527 seconds — mga 25 minutes
  • Final MP3: 30.5 MB
  • Status: COMPLETE

Ang podcast na na-stuck sa loob ng dalawang araw ay natapos sa mga 11 minutes pagkatapos ng rollback. Na-ship ang episode ng user — dalawang araw na huli sa plano, pero kumpleto.

Isang bagay tungkol sa 65-minute number: ito ay estimated duration base sa raw script length, hindi ang final output. Pagkatapos ng reset, ang script ay dumaan sa normal na shortening pipeline, at ang final assembled audio ay lumabas na mga 25 minutes. Mas mahaba pa dapat ang original script — at ito mismo ang bahagi ng dahilan kung bakit nag-fail.

Ang data

Bago magdesisyon kung ano ang gagawin, gusto ko ng totoong numbers. Hindi vibes — data. Nag-query ako sa production database para sa podcast duration information.

CohortCountMedian Durationp90Max
Last 30 days826 min31 min34 min
Last 90 days7227 min36 min45 min
All time12026 min30 min45 min

Kaya ang median podcast ay mga 26 minutes. Ang p90 ay nasa 34-36 minutes. Ang pinakamahabang completed podcast kailanman ay 45 minutes.

At heto ang mas masakit na data — ang mga podcast na umabot sa audio stage, naka-breakdown ng estimated duration:

Estimated DurationReached AudioCompletedFailedIn Progress
Under 15 min181701
15-30 min363123
30-40 min242301
40-50 min3120
50+ min2020

Ang "In Progress" ay tumutukoy sa mga podcast na nagsimula pero hindi kumpleto — iniwan ng user, na-cancel mid-generation, o naiwan sa pending state. Dalawang podcast na sinubukan na 50+ minutes. Parehong nag-fail sa audio stage. Zero ang kumpleto.

Walang completed production podcast na umabot ng 50 minutes. Gumagana nang maayos ang system para sa 15-40 minute range. Kapag lampas 40 minutes, tumataas ang risk nang malaki. At kapag lampas 50 minutes, untested territory na talaga.

Ang natutunan ko

1. Kailangan ng production-level stress testing ang configuration changes

Kailangang maging honest ako dito. Ang bagong model configuration ay baka gumana nang maayos sa short test prompts. Pero sa production, with real scripts at real timeout constraints, nag-fail ito sa mga segment na walang issue ang stable configuration.

Hindi ko na-benchmark ito nang maayos bago mag-deploy. Papasok ang short test prompt. Ang 65-minute podcast na may maraming segments sa ilalim ng 60-second timeout ay ganibang ibang context. Ang gap sa pagitan ng dalawang scenarios na ito ay kung saan nakatira ang production incidents.

Maaaring mali ako sa configuration mismo — baka kailangan lang ng more tuning o mas mahabang timeout. Pero ang punto ay nananatili: Nagbago ako ng core production dependency nang walang stress testing na deserve ng system na talagang umaasa ang mga tao.

2. Ang voice inconsistency ang totoong problema, hindi timeouts

Ang timeout ang visible failure. Pero ang reason bakit sinubukan ko ang ibang configuration sa simula pa lang ay ang voice inconsistency. Kahit sa stable model, nagshi-shift ang voice sa pagitan ng synthesis calls. Ang Host A sa segment one ay hindi eksaktong katulad ng Host A sa segment six.

Para sa maikling podcast, halos hindi napapansin ito. Para sa mas mahahaba, na-a-accumulate. At para sa 50+ minute podcast — na ulit, wala pa nakakakompleto sa production — baka talagang obvious.

Ang chunked approach ay sinubukang ayusin ito sa paggawa ng bawat chunk na mas maliit at mas kontrolado. Tamang direction iyon, sa tingin ko. Ang implementation lang ay hindi pa handa para sa production.

3. Kailangan ko ng telemetry bago ang incident, hindi pagkatapos

Nang mangyari ang failure, hindi ko ma-map ang TTS cost o performance sa specific podcast IDs. Wala nang useful entries ang logs. Wala nang TTS generation records ang workflow events.

Kailangan kong i-diagnose ang failure mula sa status field ng podcast, nakakalito na 409 error, at local reproduction ng timeout behavior. Gumana sa huli, pero hindi ito ang klase ng debugging experience na gusto kong maranasan ulit.

Nagdagdag ako ng TTS cost telemetry pagkatapos — model used, fallback model, retry count, per-attempt status, transcript characters, output audio bytes, audio duration. Dapat umiral na ito bago ang incident. Sa experience ko, ganito palagi. Binubuo mo ang observability pagkatapos ng sunog, hindi bago.

4. Hindi failure ang pagba-back

Masama ang pakiramdam mag-delete ng 2,724 lines ng code. Hindi ko papanggapin ang kabaligtaran. Ginugol mo ang isang linggo sa pagbuo ng isang bagay, proud ka sa architecture, at pagkatapos ay winasak mo lahat dahil hindi pa handa.

Pero tama ang desisyon. Maganda ang design ng chunk QA system. Babalik ito — bilang mas maliit na isolated change with proper canary validation. Pero hindi bilang bahagi ng model configuration change. At hindi habang nagti-timeout pa rin ang bagong setup sa mga segment na kayang-kaya ng stable path.

5. Ang 50-minute episode ay iba't product

Ito ang nag-surprise sa akin. Inakala ko na kahit anong haba ng script ay kaya ng system. Sabi ng data, hindi.

Kung may gusto talaga ng 50-minute podcast, iba ang generation profile nito. Baka kailangan ng script na matipid hanggang 45 minutes o mas mababa bago ang audio generation. Manual review gate bago ang TTS. Mas malakas na per-segment timeout budgets. Baka kahit ibang synthesis strategy mismo.

I-optimize ang default path para sa 15-40 minute range ang tamang desisyon. Ang 50+ minute episode ay dapat tratuhin bilang exceptional path, hindi ang norm. Sa tingin ko, product decision ito, hindi lang engineering.

Kung nasaan ang mga bagay ngayon

Bumalik ang production sa stable TTS configuration. Hindi pa rin perfect. Ang voice inconsistency sa mas mahahabang podcast ay nandoon pa rin, at naririnig ko. Pero stable enough na gumagana ang platform sa karamihan ng use cases.

Kung gusto mong marinig ang nililikha ng DIALOGUE, pwede mong subukan mismo sa https://podcast.chandlernguyen.com.

Naka-commit na ang incident report sa repository. Nakadokumento ang rollback with the exact SQL shape para i-reset ang failed podcast. Nasa lugar na ang telemetry ngayon para sa susunod na attempt.

At mas malinaw ang aral kaysa gusto ko:

Sa production AI, ang cost ng pagbabago ng core dependencies nang walang tamang stress testing ay hindi failed experiment. Ito ay failed user experience.

Susubukan ko ulit ng iba't model configurations. Pero sa susunod, with better telemetry, canary deployment path, at stress test na nagpapatakbo ng buong 50+ minute export bago humipo ang anuman sa production.

Bago magbago ng production AI dependencies, heto ang checklist na dapat kong sundin:

  1. I-stress test ang worst-case job. I-run ang pinakamahaba, pinakamabigat na workload na inaasahan mo — hindi lang short test prompt. Kung kaya ng system mo ang 40-minute podcasts, mag-test ka ng 50-minute.
  2. Mag-log ng per-attempt telemetry mula sa araw uno. Model name, fallback model, retry count, timeout, transcript characters, output audio bytes, audio duration. Dapat umiral na ito bago ang incident. Palagi na ganito.
  3. Mag-canary ng isang totoong job. Bago i-flip ang switch para sa lahat, mag-run ng isang totoong production job end to end sa bagong config at i-verify ang output.
  4. I-verify ang fallback sa parehong timeout budget. Ang fallback na share ng timeout ng primary ay hindi fallback — ito ay second chance na mag-fail sa parehong conditions.
  5. I-define ang rollback SQL at runbook bago ka mag-deploy. Alam mo nang eksakto kung paano i-reset ang na-stuck job. I-dokumento ang commands. Kung wala ito, mag-i-improvise ka during ang incident.

Kung nagbuo ka ng production AI systems, ano ang pattern mo para mag-test ng model configuration changes nang hindi nae-break ang users? Gusto ko talagang malaman. Nakaranas ka na ba ng katulad na "nagbago ako ng isang bagay at lahat nasira" na moment?

Cheers,

Chandler

Ipagpatuloy ang Pagbasa