Skip to content

[Performance] 廣播路徑與部署設定雜項:Redis pubsub ×2、冗餘 last_committed、production 開著 --reload #36

Description

@SeanGau

廣播路徑與部署設定上的三個獨立小項,都不影響正確性,但會持續墊高 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_tasksactive_scribe_managersactive_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.htmlyt.html(SSE 端的對應處理)
  • live_server/docker-compose.yml

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions