Skip to content
··閱讀時間4分鐘

一次 AI 音訊改動搞冧咗線上,我 rollback 咗 2,724 行 code

我 push 咗一個本來應該係升級嘅改動去我嘅 podcast 平台語音系統。 六日之後,幾幾個 commit 之後,我刪走咗 2,724 行 code,rollback 返去之前用到嘅版本。 以下係成件事嘅經過,以及佢教我關於測試 production AI 改動嘅教訓。

我本來想早啲寫呢篇 post 嘅。但成件事花咗我多過預期嘅時間先搞得掂,我想確認自己將個故事理顺咗先分享出嚟。

2026 年 4 月 28 號,我 deploy 咗一個我自以為係對 DIALØGUE 文本轉語音系統嘅改進。另一個 voice model 喺 paper 上面睇落好有前景。我一直聽到——自己都聽到——現有嘅 model 喺較長嘅 podcast 入面 voice 會漂移。所以我切換咗過去。

去到 4 月 30 號,我刪走咗 2,724 行 code,rollback 返去 4 月 22 號嘅 stable path。

一個真實用戶嘅 podcast 卡住咗。佢生成唔到音訊。系統 mark 咗 AUDIO_FAILED,然後 retry 嗰陣 throw 咗個 409 Conflict——起初睇落似係真正嘅 bug。但原來只係一個症狀——retry 嘅時候個 podcast 已經喺失敗狀態。真正嘅問題更簡單亦更嚴重:一個 segment 喺新配置上面 timeout 咗。fallback 都 timeout 咗。然後個系統就係咁放棄咗。

更糟糕嘅係,呢個係一個即將到達 65 分鐘语音时长量嘅 script。平台上 podcast 嘅中位數係 26 分鐘。我放咗一個超過呢個體量兩倍幾嘅 script 通過一個從未成功生成過接近呢個體量內容嘅系統。先係真正嘅風險——而 model 改動正正暴露咗佢。

我承認,作為一個 builder,呢個唔係我最好嘅時刻。我 push 咗一個配置改動,但冇做一個 production system 應有嘅 stress testing。以下係成件事嘅經過、邊度出咗問題,以及我學到啲咩。

DIALØGUE 做啲咩

如果你未見過,DIALØGUE 係一個 AI podcast 生成器。你俾佢一個話題、一份 PDF 或者一集節目素材,佢就會生成一段雙人對談式嘅 podcast。成個過程基於 Google 嘅 Gemini 文本轉語音合成。

音訊 pipeline 係咁樣運作嘅:

  1. 從素材生成一個 outline
  2. 將 outline 擴展成完整嘅 script
  3. 將 script 拆分做 segments
  4. 用 Gemini TTS 為每個 segment 合成音訊
  5. 拼接所有 segments、標準化音量,上傳最終嘅 MP3

線上嘅 default Gemini TTS model 喺大多數場景下表現唔錯。但 script 越長,你就越能聽到 segments 之間嘅 voice 漂移。segment 一嘅主持人 A 同 segment 六嘅主持人 A 聽落唔完全一樣。對於 25 分鐘嘅 podcast,你能注意到但你會習慣。對於接近 40 分鐘嘅,就比較難受啦。

我決定試一個唔同嘅 model 配置。目的係改善較長 podcast 中語音嘅一致性。

我做咗啲咩

喺四月底大約一個星期入面,我 push 咗一系列 commit 嚟試唔同嘅 model 配置。呢個唔係一行 code 嘅 casual 改動。我 build 咗成個架構遷移:

  • 將 default TTS model 切換到新配置
  • 加咗 fallback chain——如果 primary model timeout,就 fallback 去 stable production model
  • build 咗 chunk level 嘅 QA 系統:將文字稿拆分成更小嘅單元,逐個合成,用 ffmpeg analysis 驗證音訊質量
  • 加咗 workflow 進度追蹤,讓 UI 能展示每個 segment 嘅合成狀態
  • 加固咗 retry logic——每個 chunk 三次嘗試,exponential backoff
  • 加咗一個長音訊 quality gate,只有最終拼接好嘅 MP3 通過咗音訊 QA 先會 mark 做 COMPLETE

思路係由「每個 segment 生成音訊然後祈禱聽落一致」轉變為「拆分文字稿、每個 chunk 做 QA、失敗 retry、驗證最終文件」。

去到 4 月 28 號,code 已經 deploy 到線上。unit tests 過咗。integration tests 都過咗。我缺嘅——亦都係本來應該有嘅——係一個針對新配置運行完整 50 分鐘以上 export job 嘅 load test。我跳過咗唯一一個真正重要嘅測試。

邊度出咗問題

幾乎即刻,一個線上 podcast 失敗咗。

嗰個係一個長任務——分析 Big Tech capex 嘅 script,即將到達 65 分鐘嘅语音时长量。系統執行到音訊步驟,返回咗 AUDIO_FAILED

起初,我見到個 409 Conflict,以為 orchestrator 有 bug。後來發現 409 只係一個次生症狀——podcast 已經被 mark 咗做失敗之後嘅 retry。第一次失敗嘅原因簡單得多。

Segment 0 喺新 model 配置上面 timeout 咗。timeout 設置係 60 秒。fallback model——即係 stable path 用緊嗰個——都 timeout 咗。系統將個 podcast mark 做失敗,然後繼續。

fallback 本來應該 work 嘅。但佢係喺同樣嘅 60 秒 timeout budget 下被呼叫嘅,同樣嘅大 segment,同樣嘅 request context——而且呢個 context 已經喺失敗嘅 primary call 上面消耗咗時間。相比之下,stable path 係喺全新嘅 call 中以完整嘅 timeout budget 嚟處理每個 segment 嘅。同樣嘅 model,唔同嘅條件——呢個就係點解 stable version 能正常處理而 fallback 唔得嘅原因。

我用一行改動喺線上 disable 咗 chunk QA 系統。但呢個冇解決到 timeout 問題。新配置仍然喺啲 stable model 能正常處理嘅 segment 上面 timeout。

好啦,呢種就係你要做選擇嘅時刻:繼續 debug 新 path,定係返去用到嘅版本。

Rollback

4 月 30 號,我做咗決定。

Rollback commit(4a5bfc8)刪走咗 2,724 行code:

  • 成個 TTS chunker module
  • 音訊質量分析 gate
  • workflow 進度追蹤
  • chunk QA retry 嘅 test suite
  • fallback model chain 配置

並加咗 321 行嚟保留重要嘅部分——本地化語音提示措辭、前端兼容性,以及 stable path 嘅 regression coverage。

刪走咁多行 code 感覺唔好。老實講,感覺似係承認失敗咁。但其實唔係。呢個係揀咗正確嘅長期 path,而唔係一個睇落似進步但其實未準備好嘅嘢。

Rollback 之後,我做咗以下嘢:

  1. 重新 build 咗 shared base Docker image
  2. 重新 deploy 咗 generate-speech service
  3. 將失敗嘅 podcast reset 返去 script approval 狀態
  4. 透過正常 UI click「Generate Audio」

結果:

  • Model:Gemini TTS(stable production 配置)
  • Segments:6 個全部完成
  • 時長:1,527 秒——大約 25 分鐘
  • 最終 MP3:30.5 MB
  • 狀態:COMPLETE

嗰個卡咗兩日嘅 podcast,喺 rollback 之後大約 11 分鐘內就完成咗。用戶嘅節目出到街——比計劃遲咗兩日,但完整交付咗。

關於嗰個 65 分鐘嘅數字:嗰個係基於原始 script 長度嘅估算時長,唔係最終 output。Reset 之後,個 script 經過咗正常嘅縮短 pipeline,最終拼接出嚟嘅音訊大約 25 分鐘。原始 script 本來仲會長——呢個都係佢失敗嘅部分原因。

數據

喺決定下一步點做之前,我想要實際嘅數字。唔係感覺——係數據。我從線上 database 查詢咗 podcast 時長嘅資訊。

Cohort數量中位數時長p90最長
過去 30 日826 分鐘31 分鐘34 分鐘
過去 90 日7227 分鐘36 分鐘45 分鐘
全部12026 分鐘30 分鐘45 分鐘

所以 podcast 嘅中位數時長大約係 26 分鐘。p90 大約喺 34-36 分鐘。有史以嚟最長完成嘅 podcast 係 45 分鐘。

仲有更加難睇嘅數據——進入音訊階段嘅 podcast,按估算時長分類:

估算時長進入音訊階段完成失敗進行中
15 分鐘以下181701
15-30 分鐘363123
30-40 分鐘242301
40-50 分鐘3120
50 分鐘以上2020

「進行中」指嘅係開始咗但從未完成嘅 podcast——用戶放棄咗、生成中途取消嘅,或者停留喺 pending 狀態嘅。兩個嘗試喺 50 分鐘以上嘅 podcast,全部喺音訊階段失敗,零個完成。

冇一個完成嘅線上 podcast 到達過 50 分鐘。系統喺 15-40 分鐘範圍內運行良好。超過 40 分鐘,風險急劇上升。超過 50 分鐘,基本上係冇經過測試嘅領域。

我學到啲咩

1. 配置改動需要 production level 嘅 stress testing

我要誠實面對呢一點。新嘅 model 配置喺短嘅 test prompt 下可能表現好好。但喺線上,面對真實嘅 script 同真實嘅 timeout 約束,佢喺 stable 配置毫無問題嘅 segment 上面失敗咗。

我喺 deploy 之前冇做好 benchmark 測試。一個短嘅 test prompt 會成功。一個 65 分鐘、多個 segment 喺 60 秒 timeout 下嘅 podcast 係完全唔同嘅 context。呢兩種 scenario 之間嘅差距,就係 production incident 生活嘅地方。

我可能對配置本身判斷有誤——也許佢只係需要多啲 tuning 或者更長嘅 timeout。但重點不變:我改變咗一個核心嘅 production dependency,但冇對人們真正依賴嘅系統做應有嘅 stress testing。

2. Voice 唔一致先係真正嘅問題,唔係 timeout

Timeout 係可見嘅失敗。但我一開始嘗試唔同配置嘅原因就係 voice 唔一致。即使在 stable model 上面,voice 都會喺多次合成 call 之間漂移。segment 一嘅主持人 A 同 segment 六嘅主持人 A 聽落唔完全一樣。

對於短 podcast,呢個幾乎注意唔到。對於長嘅,會累積。而對於 50 分鐘以上嘅 podcast——再講一次,線上冇人成功完成過——可能會好明顯。

Chunk 化嘅思路係透過讓每個 chunk 更小、更可控嚟解決呢個問題。我認為方向係啱嘅。只係 implementation 未準備好上線。

3. 我需要喺事故發生之前而唔係之後就有 telemetry

當故障發生嗰陣,我冇辦法將 TTS 成本或 performance map 到具體嘅 podcast ID 上面。log 入面冇有用嘅 entry。workflow events 入面冇 TTS 生成記錄。

我只能從 podcast 嘅 status field、令人困惑嘅 409 error、以及本地重現 timeout behavior 嚟 diagnose 故障。最終能搵到問題,但嗰種 debugging 體驗我真係唔想再經歷多一次。

事後我加咗 TTS 成本 telemetry——用咗嘅 model、fallback model、retry 次數、每次嘗試嘅狀態、文字稿字符數、output 音訊 byte 數、音訊時長。呢啲本來應該喺事故發生之前就存在嘅。根據我嘅經驗,向嚟都係咁。你總係喺火災之後先建 observability,而唔係之前。

4. Rollback 唔係失敗

刪走 2,724 行 code 感覺唔好。我唔會假裝冇所謂。你花咗一個星期 build 嘢,對架構感到自豪,然後你又將佢哋全部拆走,因為未準備好。

但呢個係正確嘅決定。chunk QA 系統設計得好好。佢會返嚟嘅——以一個更小嘅、isolated 嘅改動,加上合適嘅 canary 驗證。只係唔會作為 model 配置改動嘅一部分返嚟。亦都唔會喺新配置仍然喺 stable path 能正常處理嘅 segment 上面 timeout 嘅情況下返嚟。

5. 50 分鐘嘅節目係另一個 product

呢個令我意外。我原本以為系統能處理任意長度嘅 script。數據話我知唔係。

如果有人真係想要一個 50 分鐘嘅 podcast,嗰係唔同嘅 generation profile。佢可能需要在音訊生成之前將 script 壓縮到 45 分鐘或更短。TTS 之前需要一個人工 review gate。更強嘅每個 segment timeout budget。甚至可能需要完全唔同嘅 synthesis 策略。

將 default path 優化喺 15-40 分鐘範圍內係正確嘅選擇。50 分鐘以上嘅節目應該被當作 exceptional path 嚟處理,而唔係 norm。我認為呢個係一個 product decision,唔單止係 engineering decision。

而家嘅情況

線上已經返到 stable 嘅 TTS 配置。佢唔完美。較長 podcast 中嘅 voice 唔一致仍然存在,我都聽到。但佢夠 stable,平台喺絕大多數場景下能正常工作。

如果你想聽下 DIALOGUE 生成嘅係咩,可以自己試下:https://podcast.chandlernguyen.com

Incident report 已經 commit 咗去 code repository。Rollback 已經記錄咗 reset 失敗 podcast 嘅具體 SQL 寫法。Telemetry 而家已經到位,為下一次嘗試做準備。

而呢個教訓比我期望嘅更清晰:

喺 production AI 入面,喺冇適當 stress testing 嘅情況下改變核心 dependency 嘅代價,唔係一個失敗嘅 experiment。而係一個失敗嘅 user experience。

我會再試唔同嘅 model 配置。但下一次,會有更好嘅 telemetry、一個 canary deployment path,以及一個喺 touch 線上之前運行完整 50 分鐘以上 export 嘅 stress test。

喺改動線上 AI dependency 之前,以下係我本來應該跟足嘅 checklist:

  1. Stress test 最壞情況嘅 job。 運行你預期最長、最重嘅 work load——唔只係短嘅 test prompt。如果你嘅系統能處理 40 分鐘嘅 podcast,就用一個 50 分鐘嘅嚟測試。
  2. 由第一日開始就記錄每次嘗試嘅 telemetry。 Model 名稱、fallback model、retry 次數、timeout、文字稿字符數、output 音訊 byte 數、音訊時長。呢啲本來應該喺事故發生之前就存在嘅。向嚟都係咁。
  3. Canary 運行一個真實 job。 喺對所有人切換之前,喺新配置上 end to end 運行一個真實嘅 production job 並驗證 output。
  4. 喺同樣嘅 timeout budget 下驗證 fallback。 一個同 primary 共享同樣 timeout 嘅 fallback 唔係 fallback——佢係喺同樣條件下第二次失敗嘅機會。
  5. 喺 deploy 之前就定義好 rollback SQL 同 runbook。 確切知道點樣 reset 一個卡住嘅 job。將命令寫好 document。如果你冇呢個,你將會喺 incident 中即興發揮。

如果你都係 build production AI system,你點樣喺 model 配置改動時唔搞冧用戶?我真係好想知道。你有冇經歷過類似「改咗一樣嘢然後全部冧晒」嘅時刻?

Cheers,

Chandler

繼續閱讀