廣播路徑與部署設定上的三個獨立小項,都不影響正確性,但會持續墊高 CPU / Redis / 頻寬。可以分開處理。
1. 每次廣播走兩趟 Redis pubsub
sio 用的是 AsyncRedisManager:
https://github.com/g0v/OpenTransLive/blob/main/live_server/app/__init__.py#L502
mgr = socketio.AsyncRedisManager(REDIS_URL)
所以 _emit_now 裡的兩行各是一趟 Redis pubsub:
https://github.com/g0v/OpenTransLive/blob/main/live_server/app/__init__.py#L2682-L2683
await sio.emit('transcription_update', p, room=session_id)
await _publish_transcription_update(session_id, p)
sio.emit(room=...) 會 publish 到 Redis 再繞回本 process 才實際送出;_publish_transcription_update 是給 SSE viewer 用的另一次 publish。
但程式其他地方已經明確假設 SERVER_WORKERS == 1:
https://github.com/g0v/OpenTransLive/blob/main/live_server/app/__init__.py#L142-L147
Valid only while SERVER_WORKERS == 1 (single uvicorn process / event loop); with multiple workers this must move to Redis-level CAS.
_partial_debounce_tasks、active_scribe_managers、active_translation_managers、_socket_limiter 也全都是 process-local 狀態。既然多 worker 本來就不能跑,AsyncRedisManager 的跨 process 能力目前是純成本。
建議:改用預設的記憶體 client manager,省下廣播路徑上的一趟 Redis 往返。要留下明確註解說明這和 SERVER_WORKERS == 1 的假設綁在一起,未來要支援多 worker 時得一起處理。
2. 每則 partial 都夾帶完整的 last_committed
https://github.com/g0v/OpenTransLive/blob/main/live_server/app/__init__.py#L2664-L2666
payload = sync_data.copy()
if last_committed:
payload["last_committed"] = last_committed
last_committed 是一整段 committed segment,含 result.corrected 和所有目標語言的譯文。它被塞進每一則 partial 廣播裡,但前端早就會去重:
https://github.com/g0v/OpenTransLive/blob/main/live_server/app/templates/panel.html#L1736-L1743
if (data.last_committed) {
const lc = data.last_committed
if (!renderedStartTimes.has(lc.start_time)) {
renderedStartTimes.add(lc.start_time)
appendCommitted(lc)
}
}
也就是同一份 last_committed 會被重複傳送多次、每次都被前端丟掉。下行頻寬要乘以觀眾數,多語言 session 尤其明顯。
建議:只在 last_committed.start_time 相對於「上次廣播時所帶的值」有變化時才附上。要注意這個欄位同時是新觀眾中途加入時的補洞機制,所以不能單純拿掉——需要確認 SSE 的 last_event_id replay(_iter_replay_transcription_events)和 socket.io 端的 joined_session 初始載入是否已經涵蓋這個情境,再決定能省到什麼程度。
3. docker-compose.yml 掛著 --reload
https://github.com/g0v/OpenTransLive/blob/main/live_server/docker-compose.yml
command: ["./.venv/bin/uvicorn", "app:socket_app", "--host", "0.0.0.0", "--port", "5000", "--workers", "${SERVER_WORKERS:-1}", "--ws", "websockets", "--reload"]
而 ./app 是 volume mount:
volumes:
- ./app:/app/app
--reload 會多跑一個 file watcher process,而且任何檔案變動都會重啟整個服務——直播中重啟等於所有 scribe session 斷線、所有 socket 重連。
建議:拆成 dev / prod 兩種 command,或用環境變數控制(例如 UVICORN_RELOAD),預設關閉。順帶注意 --workers ${SERVER_WORKERS:-1} 這個參數其實不能設成 1 以外的值(見第 1 點的 process-local 狀態),可以考慮直接寫死並加註解,避免誤用。
相關檔案
live_server/app/__init__.py:502, 2664-2666, 2682-2683
live_server/app/templates/panel.html:1736-1753
live_server/app/templates/rt.html、yt.html(SSE 端的對應處理)
live_server/docker-compose.yml
廣播路徑與部署設定上的三個獨立小項,都不影響正確性,但會持續墊高 CPU / Redis / 頻寬。可以分開處理。
1. 每次廣播走兩趟 Redis pubsub
sio用的是AsyncRedisManager:https://github.com/g0v/OpenTransLive/blob/main/live_server/app/__init__.py#L502
所以
_emit_now裡的兩行各是一趟 Redis pubsub:https://github.com/g0v/OpenTransLive/blob/main/live_server/app/__init__.py#L2682-L2683
sio.emit(room=...)會 publish 到 Redis 再繞回本 process 才實際送出;_publish_transcription_update是給 SSE viewer 用的另一次 publish。但程式其他地方已經明確假設
SERVER_WORKERS == 1:https://github.com/g0v/OpenTransLive/blob/main/live_server/app/__init__.py#L142-L147
_partial_debounce_tasks、active_scribe_managers、active_translation_managers、_socket_limiter也全都是 process-local 狀態。既然多 worker 本來就不能跑,AsyncRedisManager的跨 process 能力目前是純成本。建議:改用預設的記憶體 client manager,省下廣播路徑上的一趟 Redis 往返。要留下明確註解說明這和
SERVER_WORKERS == 1的假設綁在一起,未來要支援多 worker 時得一起處理。2. 每則 partial 都夾帶完整的
last_committedhttps://github.com/g0v/OpenTransLive/blob/main/live_server/app/__init__.py#L2664-L2666
last_committed是一整段 committed segment,含result.corrected和所有目標語言的譯文。它被塞進每一則 partial 廣播裡,但前端早就會去重:https://github.com/g0v/OpenTransLive/blob/main/live_server/app/templates/panel.html#L1736-L1743
也就是同一份
last_committed會被重複傳送多次、每次都被前端丟掉。下行頻寬要乘以觀眾數,多語言 session 尤其明顯。建議:只在
last_committed.start_time相對於「上次廣播時所帶的值」有變化時才附上。要注意這個欄位同時是新觀眾中途加入時的補洞機制,所以不能單純拿掉——需要確認 SSE 的last_event_idreplay(_iter_replay_transcription_events)和 socket.io 端的joined_session初始載入是否已經涵蓋這個情境,再決定能省到什麼程度。3.
docker-compose.yml掛著--reloadhttps://github.com/g0v/OpenTransLive/blob/main/live_server/docker-compose.yml
而
./app是 volume mount:--reload會多跑一個 file watcher process,而且任何檔案變動都會重啟整個服務——直播中重啟等於所有 scribe session 斷線、所有 socket 重連。建議:拆成 dev / prod 兩種 command,或用環境變數控制(例如
UVICORN_RELOAD),預設關閉。順帶注意--workers ${SERVER_WORKERS:-1}這個參數其實不能設成 1 以外的值(見第 1 點的 process-local 狀態),可以考慮直接寫死並加註解,避免誤用。相關檔案
live_server/app/__init__.py:502, 2664-2666, 2682-2683live_server/app/templates/panel.html:1736-1753live_server/app/templates/rt.html、yt.html(SSE 端的對應處理)live_server/docker-compose.yml