왜 단순한 script 실행만으로는 부족했을까

cron이 script를 실행하는 단순한 구조에서 출발해, 왜 데이터 작업을 상태로 다뤄야 한다는 생각까지 이어졌는지 정리한다.

이 글의 출발점은 FSM이라는 용어가 아니었다. 훨씬 먼저 떠오른 건 실무적인 질문이었다. 데이터 동기화 작업을 애플리케이션에서 관리하려고 할 때, 어디까지 script에 맡기고 어디부터 애플리케이션이 책임져야 하는지가 먼저 헷갈렸다.

처음에는 꽤 단순하게 생각했다. 정해진 시간에 동기화 script를 실행하고, 그 뒤에 검증, 정리, 메타데이터 수집 같은 후속 작업을 이어가면 될 것 같았다.

겉으로 보기에는 이 구조가 충분해 보인다.

cron
-> sync script
-> verify
-> retention
-> harvest

하지만 운영 관점으로 조금만 들어가면 바로 질문이 생긴다.

  • 지금 이 대상은 실행 중인가
  • 같은 대상 작업이 동시에 다시 시작되면 어떻게 되는가
  • script가 중간에 죽으면 어디까지 성공한 것인가
  • 취소 요청이 들어오면 무엇을 멈춰야 하는가
  • 다음 실행은 이전 실패를 어떻게 해석해야 하는가

이 질문들이 중요했던 이유는, 데이터 작업이 단순한 함수 호출이 아니기 때문이다. 요청 하나가 들어오고 응답 하나가 돌아오면 끝나는 작업이 아니라, 오래 실행되고, 외부 명령을 호출하고, 파일 시스템이나 외부 저장소를 바꾸고, 중간 결과를 남긴다.

그래서 실행했다끝났다 사이에 생각보다 많은 상태가 생긴다.

아직 대기 중
누군가 가져감
실제로 실행 중
성공함
실패함
취소됨
죽은 것 같음

처음에는 이 상태들을 굳이 모델링하지 않아도 된다고 생각하기 쉽다. 로그를 보면 되고, lock file을 두면 되고, 실패하면 다시 실행하면 된다고 생각할 수 있다.

하지만 같은 대상을 동시에 건드리면 상태가 꼬일 수 있고, 실패한 작업을 무작정 다시 실행하면 이미 처리된 파일이나 레코드를 다시 훑게 된다. 특히 retention처럼 오래된 object를 정리하는 작업은 매번 전체를 스캔하기 시작하면 시간이 계속 늘어난다.

이쯤 되면 문제는 더 이상 script를 어떻게 실행할 것인가가 아니게 된다. 진짜 질문은 다음에 가깝다.

이 작업이 지금 어떤 상태인지 시스템이 알고 있는가

이 질문이 생기고 나면 자연스럽게 실행 구조도 바뀐다. script가 모든 흐름을 끌고 가는 대신, 애플리케이션이 작업을 등록하고, 실행할 차례가 된 작업을 골라 실제 실행을 시작하며, 마지막 결과까지 기록하는 쪽으로 생각이 옮겨 간다.

그런데 애플리케이션이 작업을 관리한다고 해서 scheduler가 직접 작업을 끝까지 실행하는 것은 아니다. scheduler의 역할은 “언제 실행해야 하는가”를 판단하는 쪽에 가깝고, 실제로 오래 걸리는 작업을 붙잡고 실행하는 역할은 따로 분리하는 편이 자연스럽다.

그래야 schedule 판단과 작업 실행이 서로 묶이지 않는다. 동기화 작업 하나가 오래 걸린다고 해서 다음 schedule 확인이 막히면 곤란하고, 나중에 동시에 여러 대상을 처리하려면 실행 역할을 여러 개로 늘릴 수도 있어야 한다.

그래서 애플리케이션 안에는 대기 중인 작업을 가져와 실제로 실행하는 주체가 필요해진다. 이 주체를 worker라고 부른다.

요청
-> 작업 등록
-> worker가 작업을 가져감
-> 실행
-> 결과 기록

여기서 worker가 갑자기 튀어나온 것처럼 보일 수 있지만, 실제로는 cron이 직접 script를 실행하던 구조를 애플리케이션 안으로 옮기는 순간 생기는 실행 역할이다. scheduler가 작업을 만들고, worker가 작업을 실행한다. 이 둘을 나누면 나중에 worker 수를 조절해 병렬 처리량을 제한하거나 늘릴 수 있다.

이때부터 작업은 단순한 명령 실행이 아니라 상태를 가진 대상이 된다.

QUEUED
-> RUNNING
-> SUCCEEDED / FAILED / CANCELLED / STALE

여기까지 오면 FSM이라는 말이 뒤늦게 붙는다. 처음부터 상태 기계를 만들고 싶었던 것이 아니라, 운영 중 애매해지는 지점을 하나씩 정리하다 보니 상태 전이가 필요해진 것이다.

이 구분이 중요하다. 상태 모델은 멋진 구조를 만들기 위한 장식이 아니다. 오래 걸리는 데이터 작업에서 중복 실행, 실패 복구, 취소, 결과 추적을 명확히 하기 위해 생긴다.

정리하고 나니 다음 질문이 바로 따라왔다.

  • 작업이 상태를 가져야 한다면, worker는 그 작업을 어떻게 안전하게 가져가야 하는가

이어서 읽기

작성 수정