풀스택으로 시작해 대시보드를 덜어냈다 — 1인 AI 영상 플랫폼의 기술 선택 (1편)

앞선 숏폼 자동화는 Python 중심의 파이프라인으로 만들었다. 이번에는 작업 관리와 상태 추적, 자산 저장을 갖춘 AI 영상 플랫폼을 시도했다. 영상을 생성하는 기능에 관리 기능을 더하면서 개발하고 유지할 부분도 늘어났다. 이 글에서는 그때의 기술 선택과 실제 제작에서 사용하지 않게 된 부분을 돌아본다.

1. 문제 정의

혼자 만들고 혼자 운영한다는 제약이 모든 결정의 출발점이었다. 구현한 뒤의 유지보수와 디버깅도 내 몫이었으므로, 기능을 만들 수 있는지만큼 계속 관리할 수 있는지가 중요했다.

범위도 정해야 했다. 작업 요청부터 생성 상태 확인까지 모두 자동화하면 편리하겠지만, 그 기능을 관리하는 데 시간이 들면 정작 영상 제작은 늦어질 수 있었다. 필요한 기능을 고르는 기준은 플랫폼이라는 이름에 걸맞은 구성이 아니라 내 제작 과정에 도움이 되는지였다.

2. 접근 — 익숙한 기술을 고른 이유

스택 선택 원칙

사이드 프로젝트에 쓸 수 있는 시간은 한정돼 있었다. 새 기술을 배우는 일보다 영상 제작 기능을 만드는 데 시간을 쓰고 싶었다. 그래서 익숙한 기술을 우선한다는 원칙을 세웠다. API와 작업 관리에는 주력인 Java/Spring Boot를 선택했다.

역할로 언어를 가른다

두 역할은 별도 프로세스로 나눴다. 시간이 오래 걸리는 영상 처리를 API 요청 안에서 끝까지 기다리지 않고, 큐에 작업을 넣은 뒤 워커가 처리하도록 했다. 같은 프로세스에서도 비동기로 처리할 수 있지만, 워커를 따로 두면 실행 환경과 재시작 범위를 나누기 쉬웠다. 다만 같은 호스트의 CPU나 메모리, Redis를 공유한다면 프로세스 분리만으로 모든 장애가 격리되지는 않는다.

언어를 나누면 경계에서 관리할 것도 생긴다. Java 쪽에서 만든 작업 데이터를 JSON으로 직렬화하고, Python 쪽에서는 Pydantic 모델로 읽었다. 필드 이름과 타입을 바꿀 때 양쪽을 맞춰야 했고 로그도 두 프로세스에서 확인해야 했다. 실제로 작업 식별자는 JSON의 jobId와 Python의 job_id를 별칭으로 연결했다.

전부 Python으로 만들면 언어 사이의 데이터 변환 부담은 줄일 수 있었다. 그래도 백엔드는 내가 익숙한 Java에 두는 편이 낫다고 판단했다. 그렇다고 경계 비용이 한 번의 설계로 끝나는 것은 아니다. 메시지 형식을 바꾸거나 오류를 추적할 때마다 양쪽 구현을 살펴야 한다. 그 부담을 감수하고도 작업 관리 코드를 주력 언어로 다루는 쪽을 택했다.

여기에 욕심을 더했다

작업 현황을 한눈에 보는 Next.js 대시보드도 구상했다. 진행 중인 영상과 완료된 영상, 비용을 표시하고 상태 변화도 실시간으로 전달하려 했다. 이 과정에서 워커의 결과를 받는 Redis Pub/Sub과 브라우저에 이벤트를 전달할 WebSocket 구조를 마련했다.

3. 구현 — 모노레포와 큐 경계

API, 워커, 프론트엔드를 하나의 저장소에 묶었다. 프로젝트 이름은 일반화했다.

video-platform/
├── core-api/      # Java/Spring Boot — 작업 관리·API
├── core-worker/   # Python — AI 호출·FFmpeg·TTS
└── frontend/      # Next.js 대시보드 — 사용 중단, 코드는 보존

작업 전달에는 Redis List를 썼다. API가 LPUSH로 작업 메시지를 넣으면 워커가 BRPOP으로 꺼내 처리했다. 워커는 처리 결과를 Redis Pub/Sub으로 발행하고, API의 구독자가 이를 받아 작업 상태를 갱신했다. 작업을 기다리는 큐와 결과를 알리는 채널은 같은 Redis를 사용하지만 역할은 달랐다.

당시 Redis와 Kafka 중에서는 Redis를 골랐다. 설계 문서에는 작업 큐, 캐시, Pub/Sub을 한 서비스로 다루고 운영 구성을 줄이려는 이유가 남아 있다. 실제 작업 전달은 List, 결과 알림은 Pub/Sub으로 구현했다. 처리량 비교보다 운영 구성을 단순하게 유지하는 데 무게를 둔 선택이었다.

이 단순한 구성에는 전달 보장의 한계도 있었다. BRPOP은 메시지를 꺼내면서 목록에서 제거하므로, 이후 워커가 종료되면 이 코드만으로 작업이 다시 큐에 들어가지는 않는다. Pub/Sub도 구독자가 놓친 메시지를 재전송하지 않는다. 따라서 Redis를 붙였다는 사실만으로 작업 복구까지 갖췄다고 볼 수는 없다. (BRPOP 공식 문서, Pub/Sub 전달 보장)

도표 크게 보기

도표는 초기 플랫폼의 작업·결과 전달 경로와 대시보드 연동 구상을 구분한 것이다. 워커가 브라우저로 직접 상태를 보내는 구조는 아니다. Redis를 통해 API가 결과를 받고, 화면에는 WebSocket으로 전달하려 했다. 점선은 완성하지 않은 대시보드 연동을 뜻하며, 현재 사용 중인 전체 제작 경로를 나타내지는 않는다.

4. 결과 — 사용을 중단한 부분

대시보드는 레이아웃을 만들고 영상 생성 연동을 시도하다 멈췄다. 연동을 끝까지 진행하지 않은 데는 두 가지 이유가 있었다.

만들다 멈춘 대시보드 — 진행 중·완료·비용 카드에 고정된 0 표시

만들다 멈춘 대시보드. 진행 중, 완료, 이번 달 비용의 0은 실적을 집계한 값이 아니라 화면 코드에 고정된 초기 표시값이다.

첫째, 당시 내 작업 방식에서는 대시보드를 유지할 이유가 크지 않았다. 혼자 영상을 만드는 데 필요한 확인은 터미널과 작업 결과를 보면서 할 수 있었다. 대시보드를 계속 쓰려면 상태와 비용을 화면에 연결하고, 백엔드 변경에 맞춰 프론트엔드도 관리해야 했다. 레이아웃 이후의 연동을 끝까지 진행하지 않았다.

둘째, 자동화 범위를 넓힐수록 비용과 관리할 항목도 늘었다. 선택한 구성은 AI 서비스를 종량제 API로 호출하는 방식이었다. 자동화를 계속 운영하려면 호출 비용과 실패한 작업을 확인하고 재시도도 관리해야 했다. 내가 만들 영상의 양에 비해 이 구성을 계속 운영할 필요가 있는지 의문이 들었다.

이후에는 Claude Code 스킬을 중심으로 영상을 제작하면서 대시보드와 Redis 기반 파이프라인을 거의 쓰지 않게 됐다. 여기서 덜어냈다는 말은 코드 삭제를 뜻하지 않는다. 저장소에는 프론트엔드와 큐, 이벤트 처리 코드가 남아 있다. 대시보드만 제거하고 나머지 서버 구성을 계속 운영한 것과도 다르다.

5. 학습

1인 개발에서는 만들지 않거나 사용을 중단하는 것도 설계 결정이다. 대시보드가 모든 1인 작업에 불필요한 것은 아니다. 내 경우에는 실시간 현황을 보는 편익보다 연동을 완성하고 유지할 부담이 컸다. 그래서 화면 개발을 멈추고 실제 영상 제작에 쓰는 경로를 줄였다.

익숙한 기술은 선택의 근거가 되지만 운영을 대신해 주지는 않는다. Java와 Python의 역할을 나눈 판단에는 각각의 숙련도와 도구 생태계가 반영됐다. 대신 메시지 형식을 양쪽에서 맞추고 상태 전달의 실패를 다뤄야 했다. 언어를 고른 뒤에도 남는 비용이었다.

이 경험을 돌아보며 컴포넌트를 판단할 질문 세 가지를 정리했다.

  1. 혼자 쓰는 현재의 작업 방식에서도 이 기능이 충분한 가치를 주는가? 대시보드는 이 기준에서 우선순위가 낮았다.
  2. 운영과 유지보수의 부담이 얻는 편익보다 크지 않은가? 종량제 API를 묶은 자동화 구성을 계속 쓸지 판단할 기준이었다.
  3. 문제가 생겼을 때 익숙한 도구로 원인을 찾고 수정할 수 있는가? 백엔드 언어를 고를 때 중요하게 본 조건이었다.

대시보드를 중단하면서 시작한 범위 조정은 파이프라인 전체를 계속 운영할지 묻는 데까지 이어졌다. 이미 만든 기능의 수보다 실제 영상 제작에 도움이 되는지가 더 중요해졌다. 2편에서는 제작 방식을 바꾼 과정을 다룬다.

← 전체 글 목록