비동기 큐 & 관측성

인프라 · 2026년 8월 31일 기준

  • 테스트
  • 실측
  • Celery 재시도
  • 에러로그
  • 구조화 로깅
  • NullPool
목차 6
  1. 개요
  2. 왜 이 설계가 필요했는가 (Problem)
  3. 핵심 설계 결정
  4. 설계가 만든 결과 (Result)
  5. 주요 설정값
  6. 관측성의 실제 공백 — 실패는 남지만 느림은 안 남는다 (2026-08-30)

개요

무거운 STT+요약을 Celery 백그라운드로 처리하고, 실패를 추적·통보하는 운영 기반.

  • 동기인 Celery 태스크와 async 파이프라인을 안전하게 잇는다
  • async DB 커넥션은 멀티프로세스 환경에서 NullPool로 운용한다
  • 구조화 JSON 로깅 + 에러 로그 테이블 + 통합 헬스체크로 관측성을 확보했다
Celery 동시성 4 동기↔async 브리지 NullPool 구조화 JSON 로깅 헬스체크 4점검
Redis broker asyncio.run 멀티프로세스 안전 운영=JSON / 개발=컬러 DB·Redis·STT·LLM

왜 이 설계가 필요했는가 (Problem)


핵심 설계 결정

1. 동기↔async 브리지 + 결과는 DB 직접 저장

# 동기 Celery 태스크 → async 파이프라인 실행
@Celery 태스크
def 전사·요약 파이프라인(작업 ID, 오디오 경로):
    try:
        return asyncio.run(배치 파이프라인(작업 ID, 오디오 경로))
    except 예외 as e:
        async def 실패 처리():  # 새 이벤트 루프에서 후처리
            await 에러 로그 기록(작업 ID, 단계, e)
            await 상태 갱신(작업 ID, 실패, 메시지=e)
        asyncio.run(실패 처리())

2. NullPool — 멀티프로세스/루프 안전

# 멀티프로세스/루프 환경에선 풀링을 끈 NullPool 로
from sqlalchemy.pool import NullPool
엔진 = create_async_engine(DB 주소, poolclass=NullPool, ...)

3. 구조화 JSON 로깅 + 환경별 포맷

# JSON 포매터 — 로그 한 건을 JSON 한 줄로
로그 = {시각(UTC), 레벨, 로거 이름, 메시지}
if 추가 데이터가 있으면: 로그에 합침  # 작업 ID, 수치 등
if 예외 정보가 있으면: 로그["exception"] = 예외 포맷(예외 정보)
return JSON 문자열(로그)  # 한글 그대로

4. 에러 로그 테이블 + 통합 헬스체크

# 헬스체크 — 핵심 의존성 점검, 하나라도 실패면 503
DB = 연결 확인 결과
STT = 모델 로드 여부
서비스 = {api: ok, DB, Redis, STT, LLM 제공자}
if LLM 을 뺀 항목 중 하나라도 ok 가 아니면:
    return 503 + 점검 결과

설계가 만든 결과 (Result)

검증 결과

실패 후처리 테스트 · 13/13 통과 (pytest)

구분 검증 내용
정상 예외가 없으면 아무 부수효과도 남기지 않는다
순서 에러 로그 → 상태 갱신 → 이벤트 통보 순서대로 수행
전달 에러 메시지가 세 경로에 일관되게 전달 (작업 ID · 단계 · 페이로드 구조 포함)
상세 DB 에러 로그에는 스택 트레이스가 함께 저장된다
정책 기본은 예외 재전파 / 재전파를 끄면 소멸
정책 실패 상태를 지정하면 상태·이벤트 페이로드 모두 반영
★ 내성 한 단계가 실패해도 뒤 단계는 계속 수행된다 (3개 실패 지점 파라미터화)
★ 내성 세 단계가 모두 실패해도 원본 예외가 그대로 전파된다
가드 작업 상태 대역이 원본 정의와 일치하는지 드리프트 검사

진짜 가치는 3단계 수행이 아니라 부분 실패 내성이다

주입한 실패 기대 동작
에러 로그 실패 (DB 다운) 상태 갱신 · 이벤트 통보는 계속 수행
상태 갱신 실패 이벤트 통보는 계속 수행
이벤트 통보 실패 (Redis 다운) 원본 예외 전파 결정은 유지
세 단계 모두 실패 원본 예외(“진짜 원인”)가 그대로 상위로 올라간다

마지막 항목이 핵심이다 — 후처리 실패가 원인 예외를 가려버리면 디버깅이 불가능해진다.

대역을 쓰되 원본과의 어긋남을 자동 감지한다

이 검증이 말하지 않는 것


주요 설정값

항목 값
Celery 동시성 4
방 요약 재시도 최대 5회
STT 스트림 워커 3
DB 커넥션 FastAPI 풀 (기본 3 + overflow 5) / Celery NullPool
STT beam / 정밀도 실시간 beam 1 (greedy) · 배치 beam 5 · int8
Celery broker Redis

관측성의 실제 공백 — 실패는 남지만 느림은 안 남는다 (2026-08-30)

무엇이 막혔나

실시간 스트리밍의 자막 지연 5.5초를 구간별로 분해하려 했으나, 아래까지만 확인하고 멈췄다.

구간 알 수 있었나 근거
STT 추론 ✅ ~0.7~0.9s 벤치마크 RTF 0.15 × 발화길이로 산출
VAD 침묵 확정 ✅ 0.19~0.39s DB 에 기록된 세그먼트 시작·끝 시각의 공백
🔴 나머지 ~4초 (75%) ❌ 구간 특정 불가 해당 구간을 찍는 로그 마커가 없다

필요한 계측 (미구현)

부수 발견 — 설정이 코드에 가려 무력화돼 있었다