Docker Compose로 운영 중인 자체 개발 라이브 스트림 녹화 도구(Python/FastAPI + yt-dlp 기반)에서 겪은 상태 동기화 버그를 정리한다.
증상
매일 자정 cron으로 발송되는 “어제 하루 녹화 요약” 메일에서, 실제로는 자정 시점에 라이브 녹화가 진행 중이었는데도 메일에는 “현재 녹화 중 – 0건”으로 표시됐다.
요약 메일 발송 스크립트는 별도로 동작하며, 앱이 디스크에 기록하는 상태 파일(state.json)을 직접 읽어서 status == "recording"인 레코드를 집계하는 방식이었다.
원인 추적
먼저 요약 스크립트 로직 자체를 확인했다. 상태값이 "recording"인 레코드를 찾아 카운트하는 로직은 문제가 없었다.
elif status == "recording":
started_dt = datetime.fromtimestamp(created_at) if created_at else None
elapsed = (datetime.now().timestamp() - created_at) if created_at else None
active_by_platform[platform].append({...})
다음으로 실제 상태 파일 내용을 확인했다. 문제가 된 레코드는 저녁에 시작해서 다음날 새벽까지, 자정을 관통해서 이어진 녹화였다. 상태 파일에는 이 레코드가 status: "completed"로 남아 있었는데, 조회 시점 자체가 녹화 종료 이후였기 때문에 당연한 결과였다. 진짜 확인해야 할 건 자정 그 순간의 상태값이었다.
앱의 상태 저장 로직을 추적해보니, 저장은 디바운스 방식으로 처리되고 있었다.
async def _debounced_save():
"""Wait for writes to settle, then flush once."""
global _save_state_pending
while True:
_save_state_pending = False
await asyncio.sleep(2) # coalesce window
if not _save_state_pending:
break
_save_state_sync()
디바운스 지연(2초) 자체는 문제가 아니었다. 진짜 원인은 녹화가 “시작”되는 지점에 저장 호출 자체가 없었다는 것이었다.
rec["status"] = "recording"
rec["started_at"] = time.time()
rec["log"] = []
size_task = None
proc = None
duration_task = None
file_captured = False
logger.info("Recording started: %s for %s (%s)", rec_id, username, url)
# ↑ 여기까지 상태 저장 호출이 전혀 없음
코드 전체에서 저장 함수가 호출되는 지점을 모두 찾아봤더니, 다음 두 경우에만 호출되고 있었다.
- 녹화 시작 직후 곧바로 정지 요청이 들어온 예외 케이스
- 녹화가 종료된 뒤, 후처리(포맷 변환 등)가 끝나는 시점
즉 녹화가 몇 시간을 진행 중이어도, 그 사이 상태 파일은 단 한 번도 갱신되지 않는 구조였다. 자정처럼 녹화 도중에 다른 프로세스(요약 메일러)가 상태 파일을 읽으면, 그 시점 실제로 진행 중인 방송이 파일에 전혀 반영되어 있지 않아 통째로 누락됐던 것이다.
해결
녹화 시작 처리부에 상태 저장 호출을 한 줄 추가했다.
rec["status"] = "recording"
rec["started_at"] = time.time()
rec["log"] = []
_save_state() # 추가
size_task = None
...
이제 녹화가 시작되는 즉시(디바운스 최대 2초 이내) 상태 파일에 반영되므로, 요약 메일이 언제 실행되든 그 시점 실제 진행 중인 목록을 정확히 읽는다.
배운 점
“상태가 바뀌는 시점”과 “상태가 저장(영속화)되는 시점”이 분리된 구조에서는, 상태가 바뀌는 모든 경로에 실제로 저장 호출이 붙어 있는지 반드시 확인해야 한다. 이번 케이스처럼 종료 시점에만 저장하는 흐름은, 진행 중 상태를 참조하는 외부 프로세스(모니터링, 알림, 요약 리포트 등) 입장에서는 치명적인 사각지대가 된다. 특히 오래 지속되는 작업(장시간 녹화, 긴 배치 작업 등)일수록 “시작 시점 저장 누락”은 오랫동안 발견되지 않고 잠복하기 쉽다.