수동 실행할 때마다 완벽하게 동작하던 스크립트는 사람을 그 루프에서 빼는 순간 한 번도 본 적 없는 방식으로 실패할 수 있다 — 인터랙티브 세션이 스크립트 대신 조용히 하고 있던 일(환경 로딩, 동시실행 방지, 결과 검사)이 있었기 때문이다.

배경
[20편](./20-persona-pipeline-architecture.ko.md)에서 다룬 5-레이어 아키텍처와 [21편](./21-persona-qc-consistency.ko.md)에서 다룬 QC 게이트까지 갖추고 나니, Project Persona는 한 번의 실행을 사람 개입 없이 안전하게 끝낼 수 있는 상태가 됐다. 남은 문제는 완전히 다른 종류였다: 그 실행을 하루 여러 번, 두 트랙에 걸쳐, 내가 버튼을 누르지 않아도 스스로 반복하게 만드는 것.
첫 시도 — "이미 되니까 스케줄만 걸면 된다"
수동 실행에서 이미 잘 동작하던 파이프라인 진입점을 셸스크립트로 감싸서 Argus의 크론 스케줄에 트랙별로 등록했다. 서류상으로는 이게 전부여야 했다 — 파이프라인 로직은 그대로고, 그걸 누가(혹은 무엇이) 호출하는지만 바뀌었으니까.
오래가지 못했다. 인터랙티브 셸에서 스크립트를 실행하는 것과 크론이 같은 스크립트를 실행하는 것은, 같은 머신·같은 사용자라도 같은 실행 환경이 아니다. 인터랙티브 셸은 셸 프로필을 소싱하고 그 세션에 익스포트된 환경변수를 물려받는다. 크론은 둘 다 하지 않는다 — 로그인 셸이 아닌 최소 환경에서 명령을 실행한다. "어쨌든 되던데"에 깔려 있던 모든 암묵적 가정이, 같은 명령이 크론에서 대신 실행되는 순간 지뢰로 바뀐다. 이걸 설계 리뷰가 아니라 실제로 무인 실행을 돌려보고 결과를 재확인해서 알아냈다.
조사 — 아무도 지켜보지 않을 때만 존재했던 버그들
버그 1 — 크론에서 사라진 인증 정보. 트랙 A의 스케줄 작업 하나가 특정 시간대에 실패하기 시작했다. 실행 로그를 슬쩍 보면 문제없어 보였다 — 이런 자기보고식 "성공"이 바로 의심해야 할 대상이다. 실제 로그를 직접 열어보니 RuntimeError: 필수 인증 환경변수가 없습니다가 찍혀 있었다. 크론은 로그인 셸을 실행하지 않으므로 인증 정보를 익스포트하는 셸 프로필을 소싱하지 않는다. 이미 알려진 유형의 문제였다 — 같은 파이프라인의 다른 크론 작업에서 한 번 해결한 적이 있었고, 해법은 환경/설정을 명시적으로 로드하는 공용 헬퍼 스크립트였다. 새로 만든 트랙 A 작업만 그 헬퍼에 연결돼 있지 않았다. 크론의 실제 최소 환경을 직접 재현해서 재확인한 뒤에야 확신할 수 있었다.
버그 2 — 아무도 운전하지 않을 때 달라지는 네트워크 경로. 별도로, 인터랙티브로 트리거하면 정상 동작하던 업로드 단계가 무인 실행에서는 간헐적으로 실패했다 — 재시도 소진, 전송 도중 연결 끊김. 손으로 같은 명령을 재실행해서는 재현되지 않았다. 파이프라인 자체 로그보다 낮은 레벨에서 요청을 직접 트레이싱해보니, 무인 실행 경로가 인터랙티브 경로와 다른 네트워크 경로를 타고 있었고, 그 경로가 대용량 업로드를 소규모 요청과 다르게 처리했다. 수정은 업로드 로직이 아니라 스케줄 작업의 네트워크 경로를 물려받는 대신 명시적으로 고정하는 것이었다. 버그 1과 같은 근본 교훈을 다른 서브시스템에서 한 번 더 확인한 셈이다.
버그 3 — 소스 영상 1개에서 업로드 열 개 이상. 가장 심각한 문제는 크래시가 아니었다. 성공을 보고했지만 실제로는 훨씬 많은 일을 해버린 실행이었다. 사용자 불만이 아니라 "기록된 업로드와 실제 라이브 상태가 일치하는가"를 정기적으로 확인하는 스케줄된 감사에서 발견했다: 씬 분할 단계가 한 소스 영상에서 여러 하이라이트 세그먼트를 반환했고, 업로드 단계는 받은 리스트를 성실하게 전부 업로드해버렸다. 클립 하나하나는 21편의 QC 게이트를 전부 통과할 만큼 품질이 정상이었다 — 그래서 QC가 이 문제를 잡을 수 없었다. 문제는 개수였지 품질이 아니었다. 근본 원인은 누락된 점검이 아니라 누락된 불변식이었다: 세그먼트를 반환하는 함수는 찾은 후보 전부를 돌려주도록 설계돼 있었고, 업로드 단계는 받은 걸 그대로 순회하며 업로드하도록 설계돼 있었다 — 사람이 후보를 보고 하나를 고르던 시절에는 합리적이었지만, 그 사람이 루프에서 빠지는 순간 조용한 배수 버그가 됐다.
조치
버그 1·2는 "물려받지 말고 명시하라"는 같은 원칙으로 고쳤다: 모든 스케줄 작업이 실행 시작 시 자체 환경/설정을 명시적으로 로드하고, 서비스 엔드포인트와 프록시 경로를 고정값으로 박아뒀다. 여기에 겹치는 실행을 막는 락도 추가했다 — 수동 재실행이나 지연된 크론 슬롯이 같은 실행과 경합하지 않도록.
버그 3은 이중 안전장치로 고쳤다: 전용 선택 단계가 목표 길이 기준에 맞는 최적 후보 하나만 고르도록 하고, 오케스트레이터 자체에도 선택 단계가 어떤 이유로든 하나 이상을 반환하더라도 소스 하나당 업로드 하나 이상은 절대 내보내지 않는 독립적인 안전장치를 뒀다. 벨트와 멜빵을 동시에 채운 셈이다 — 애초에 이 불변식을 강제하는 지점이 단 한 곳뿐이었기 때문에 벌어진 버그였으니까.
마지막으로 각 스케줄 슬롯에 대해 "실제로 실행됐는가, 에러 없이 끝났는가, 산출물 개수가 트랙별 기대치와 맞는가"를 자동으로 확인하는 헬스체크 계층을 추가했다. 버그 1을 찾은 방식 — 뭔가 이상하다고 느낀 사람이 로그를 수동으로 다시 열어보는 것 — 은 운영 모델로 확장되지 않는다.
검증
- 크론 환경 수정: 실제 크론의 최소 환경을 직접 재현해 인증 정보가 정상 로드되는지 확인한 뒤에야 스케줄에 다시 등록했다.
- 네트워크 수정: 실제 대용량 파일을 무인 경로 전체로 엔드투엔드 업로드해, 이전의 연결 끊김 패턴 없이 완료됨을 확인했다.
- 중복 업로드 수정: 이전에 사고를 냈던 정확히 그 파이프라인 단계를 재실행해 같은 소스에서 출력이 정확히 하나만 나옴을 확인했고, 이후 여러 번 별도로 실행해 이미 처리된 소재에 대한 중복 업로드가 없음을 반복 확인했다.
- 이후 두 트랙 모두 각자 스케줄대로 하루 여러 번 계속 돌고 있고, 헬스체크가 매 슬롯의 실행 여부와 산출물 개수를 계속 확인해준다.
생성 로직과 QC 게이트, 아키텍처는 시스템이 "자율적"이 되기 위한 필요조건이지 충분조건이 아니다. 자동화의 마지막 구간 — 아무도 지켜보지 않아도 시스템이 살아남게 만드는 일 — 은 직접 해보기 전에는 과소평가하기 쉽다: 상속받은 가정 대신 명시적인 환경과 설정, 한 코드 경로가 항상 올바르게 동작하리라 믿는 대신 아이도포턴시 가드, 그리고 사람이 의심을 품기를 기다리는 대신 실패를 스스로 드러내는 헬스체크. 중복 업로드 버그를 사용자 불만이 아니라 의도적으로 스케줄된 감사로 찾아낸 것도 같은 규율의 연장이다 — 무인으로 돌아가게 만든 무언가에 대해서는, 기록된 상태가 실제 상태와 일치하는지 확인하는 일이 사고 대응이 아니라 정기 루틴이어야 한다.
---
이 글은 완전 자율 콘텐츠 파이프라인 구축기 4부작 중 3부다. 다음 편([23편](./23-persona-feedback-loop.ko.md))에서는 파이프라인이 스스로 돌아가게 된 다음 실제로 "더 똑똑해지고" 있는지 확인하는 성과 피드백 루프를 다룬다.
첫 댓글을 남겨보세요