메모리에서 기다리는 순서와 DB에 남는 상태는 다르다

Kaya Toast의 글 생성은 Claude CLI를 호출하는 비동기 작업이다. 요청마다 즉시 실행하면 동시에 시작하는 수를 제어할 수 없고, 메모리 큐만 쓰면 프로세스 종료 뒤 어떤 작업이 남았는지 알 수 없다. 구현은 실행 조정과 작업 기록을 두 층으로 나눴다.

당시 설계는 Astro SSR과 큐를 하나의 Node·PM2 프로세스에 두고 동시 실행 수를 3으로 정했다. p-queue는 콜백의 동시성을 제한하고, SQLite는 큐에 넣기 전에 pending 작업 행을 만든다. 별도 워커나 Redis를 구성하지 않는 형태를 유지하면서 재시작 뒤 판단할 최소 상태를 남기는 구조다.

  1. SQLite에 pending 저장작업 ID와 생성 대상을 먼저 기록한다.
  2. 메모리 큐에 실행 콜백 추가한 프로세스 안에서 최대 3개를 실행한다.
  3. running에서 done 또는 failed로 변경성공한 글의 연결이나 실패 사유를 DB에 남긴다.

대기 작업은 다시 넣고, 중단된 실행은 실패로 정리한다

재시작하면 메모리의 실행 순서는 사라진다. 애플리케이션은 재시작 후 최초 사용 시 DB와 큐를 준비하면서 running 행을 failed, 오류를 restarted로 바꾼다. 이어 pending ID를 읽어 새 메모리 큐에 다시 넣는다. DB 기록이 있으므로 대기 작업까지 모두 잃는 구조는 아니다.

남은 상태처리
pending같은 작업 ID로 재투입
running중단된 작업을 failed로 변경 · 실행 재개 없음
failed 수동 재시도기존 대상과 날짜로 새 작업 ID를 만들어 추가
done완료 기록과 결과 글 연결 유지

실행을 자동으로 이어가는 대신 중단 사실을 남기고 재시도를 별도 작업으로 만드는 정책이다. 외부 CLI가 결과를 만들었지만 로컬 완료 저장 전에 프로세스가 죽었다면, 실패 행만으로 외부 작업의 완료 여부를 확정할 수 없다. 새 작업을 만드는 수동 재시도도 외부 생성의 정확히 한 번 실행을 보장하지 않는다.

작업 ID를 넘긴다고 중복 실행이 막히지는 않는다

큐의 add(_key, task)는 key를 사용하지 않는다. DB의 running 변경도 pending 조건 없이 ID로 갱신한다. 같은 ID를 두 번 넣으면 두 번 실행할 수 있고, 다른 프로세스의 큐와 실행 허가를 공유하지 않는다. 프로세스 내부 이벤트 알림 역시 놓친 이벤트를 영구 저장해 재전송하는 기능은 아니다.

큐 테스트는 실제 비동기 콜백 10개로 최대 동시 실행 수가 3 이하인지 확인하고, 실패한 콜백 옆의 작업들이 계속 실행되는지 확인한다. runner 테스트는 mock CLI의 성공·종료 실패·잘못된 응답을 다룬다. 부팅 복구, 같은 ID의 중복 투입, 다중 프로세스 경합은 이 테스트들의 범위 밖이다.

Kaya Toast의 이 구조는 한 프로세스의 실행 제한과 상태별 재시작 정책을 함께 제공한다. 프로세스를 늘리려면 상태 기록에 더해 조건부 실행권 획득과 워커 간 조정이 필요하다. SQLite에 이력이 있다는 사실만으로 그 실행 보장까지 얻는 것은 아니다.