콘텐츠로 이동

개발계 cron 운영 흐름

문서 역할

  • 역할: 시나리오
  • 문서 종류: flow
  • 충돌 시 우선 문서: 작업 목록·주기는 Cron 작업, 상태 전이·알림·삭제 판정은 각 도메인 정책
  • 기준 성격: as-is

목적

  • 공유 개발계에서 운영과 같은 시간 기반 상태 전이를 재현하되 실제 FCM 발송, 무단 호출, 의도하지 않은 개인정보·프로필 삭제를 기본 차단한다.
  • 서버의 수동 endpoint별 crontab 대신 repository가 소유한 단일 dispatcher를 배포해 스케줄 유실과 중복 실행을 방지한다.

기본 안전 모드

항목 기본값 효과
NODE_ENV development 다른 환경에서 development cron 실행 차단
DEV_CRON_ENABLED true 인증된 개발 dispatcher만 허용
DEV_CRON_TOKEN 32자 이상 무작위 값 x-dev-cron-token 인증, 로그·repository 저장 금지
DEV_CRON_BASE_URL http://127.0.0.1:3002 같은 EC2의 API만 호출
DEV_CRON_EXTERNAL_DELIVERY_ENABLED false cron 문맥의 실제 FCM 전송 차단
DEV_CRON_DESTRUCTIVE_ENABLED false 회원 자동삭제·오래된 프로필 삭제 차단

개발 cron이 변경하는 DB 상태, 키 환불, 예약 매칭, 내부 알람 row는 실제 QA 대상이다. 외부 FCM만 차단된다고 해서 DB write가 dry-run이 되는 것은 아니다. API는 기존 DB configuration으로 dev-data CLI와 같은 전역 advisory lock을 사용하고, 합성 member root가 존재하는 동안 모든 handler를 maintenance SKIP한다.

설치 전 확인

  1. 개발 API 배포 경로와 PM2 process coupler-api가 현재 개발 DB를 사용하는지 확인한다.
  2. 배포 SHA에 development cron access guard, 외부 전송 억제 context, destructive guard, dispatcher가 포함됐는지 확인한다.
  3. command -v pnpm, /usr/bin/flock, loopback API 응답을 확인한다.
  4. 기존 user/root crontab과 /etc/crontab, /etc/cron.d를 백업·검색해 /admin/cron/ 직접 호출과 기존 dispatcher가 없는지 확인한다. installer도 현재 user crontab의 legacy·unmanaged entry를 발견하면 변경 없이 중단한다.
  5. 개발 DB의 비어 있지 않은 FCM token 수와 두 삭제성 작업의 예상 대상 건수를 집계한다. 개별 token·회원정보는 출력하지 않는다.

환경 파일 준비

PNPM_BIN="$(command -v pnpm)"
DEV_CRON_TOKEN="$(openssl rand -hex 32)"
sudo install -d -m 755 /etc/coupler-api
sudo install -m 600 -o "$USER" -g "$(id -gn)" /dev/null /etc/coupler-api/dev-cron.env
printf '%s\n' \
  'NODE_ENV=development' \
  'DEV_CRON_ENABLED=true' \
  "DEV_CRON_TOKEN=$DEV_CRON_TOKEN" \
  'DEV_CRON_BASE_URL=http://127.0.0.1:3002' \
  'DEV_CRON_EXTERNAL_DELIVERY_ENABLED=false' \
  'DEV_CRON_DESTRUCTIVE_ENABLED=false' \
  "PNPM_BIN=$PNPM_BIN" > /etc/coupler-api/dev-cron.env
chmod 600 /etc/coupler-api/dev-cron.env
  • 환경 파일은 cron을 실행하는 OS 사용자 소유, mode 600이어야 한다.
  • 실제 token을 터미널 로그, PR, 이슈, 문서 증빙에 남기지 않는다.

API 재시작과 scheduler 설치

cd /home/projects/ritzy/ritzy-api
pnpm install --frozen-lockfile
set -a
. /etc/coupler-api/dev-cron.env
set +a
pm2 restart coupler-api --update-env
pm2 save

./scripts/install-development-cron.sh install
crontab -l
  • installer는 기존 crontab을 보존하고 BEGIN/END COUPLER DEVELOPMENT CRON marker 구간만 원자적으로 교체한다.
  • ops/cron/development.crontab의 단일 1분 dispatcher와 flock만 설치한다.
  • endpoint별 cron 표현식은 lib/development-cron-schedule.ts가 소유한다.
  • installer는 .runtime directory를 mode 700, log와 flock file을 mode 600으로 만들고 symlink·비정규 파일이면 중단한다. log는 runner가 10 MiB에서 .1 하나로 회전한다.

활성화 검증

./scripts/run-development-cron.sh run checkMatch
tail -n 100 .runtime/development-cron.log
pm2 logs coupler-api --lines 100 --nostream

다음을 모두 확인한다.

  • token 없는 외부 요청은 CRON_DEVELOPMENT_ACCESS_DENIED로 handler 전에 거부된다.
  • 수동 checkMatch는 loopback·token 경로로 PASS를 기록한다.
  • 매분 dispatcher run이 KST due job을 manifest 순서대로 실행하고 PASS/SKIP/FAIL을 구조화 JSON으로 남긴다. 동일 DB 행의 상태 전이 경쟁을 피하기 위해 due job끼리 병렬 실행하지 않는다.
  • 로그에 token, FCM token, 회원정보가 없다.
  • cron 대상 내부 알람과 상태 전이는 생성되지만 Firebase 전송 성공 로그는 없다.
  • DEV_CRON_DESTRUCTIVE_ENABLED=false에서 두 삭제성 endpoint가 CRON_DEVELOPMENT_DESTRUCTIVE_DISABLED로 거부된다.
  • 같은 minute run이 겹치면 두 번째 dispatcher가 flock으로 실행되지 않는다.
  • 다른 cron handler나 dev-data CLI가 전역 DB lock을 보유하면 x-dev-cron-result: maintenance를 반환하고 dispatcher는 SKIP으로 기록한다.
  • 합성 member root가 존재하면 정상 개발 target을 포함한 handler 전체를 시작하지 않고 maintenance SKIP한다.
  • 합성 root가 없으면 정상 개발 데이터를 기존 도메인 규칙대로 처리하고 handler promise가 끝날 때까지 lock을 유지한다.
  • handler의 status/header/body/end 호출은 lock 해제 성공 전까지 지연한다. 공통 exact parser가 lock의 number 0|1, root count의 0 이상 number safe integer 외 값을 거부한다. 결과가 malformed이거나 RELEASE_LOCK이 number 1이 아니면 이미 성공 응답을 보낸 것으로 처리하지 않고 fence 오류로 실패한다.
  • GET_LOCK query 실패·malformed 결과는 lock 소유 여부 불명으로 보고 connection을 파기한다. 정확한 0일 때만 미소유 connection을 pool에 반환한다. 정확한 해제 뒤 pool 반환이 실패해도 connection을 파기하고 실패한다.
  • handler가 undefined를 포함한 어떤 값으로 reject해도 실패로 기록하며 지연한 성공 응답은 재생하지 않는다.
  • DB 연결, 합성 root 확인 또는 lock 해제가 실패하면 handler를 시작하지 않거나 성공으로 기록하지 않고 CRON_DEV_DATA_FENCE_UNAVAILABLE로 실패한다.
  • dispatcher 요청은 최대 45초에 중단되지만 서버 handler가 계속 실행 중이면 advisory lock은 실제 promise가 끝날 때까지 유지되어 다른 cron과 dev-data CLI의 DB 작업을 차단한다.

삭제성 작업 일회성 검증

상시 활성화하지 않는다. 대상 건수, 개발 DB snapshot 또는 복구 기준, 작업 시간을 검토한 일회성 작업에서만 수행한다.

  1. scheduler를 잠시 제거한다.
  2. 환경 파일의 DEV_CRON_DESTRUCTIVE_ENABLED=true를 설정하고 API를 --update-env로 재시작한다.
  3. 대상 job 하나만 run <job-id>로 실행한다.
  4. 삭제 건수와 잔존 불변식을 확인한다.
  5. 즉시 값을 false로 되돌리고 API를 재시작한 뒤 scheduler를 다시 설치한다.

중지와 rollback

./scripts/install-development-cron.sh remove
crontab -l
  1. 환경 파일의 DEV_CRON_ENABLED=false를 설정한다.
  2. set -a; . /etc/coupler-api/dev-cron.env; set +apm2 restart coupler-api --update-env를 실행한다.
  3. .runtime/development-cron.log에서 마지막 run과 중지 시각을 확인한다.
  4. DB 변경은 자동 rollback하지 않는다. 잘못된 상태 전이·환불·삭제는 대상 도메인 정책의 data repair 절차로 분리한다.

공유 개발 데이터와의 관계

  • 평상시에는 개발 cron을 실행한다.
  • cron handler와 dev-data plan/verify/apply/reset은 전역 DB advisory lock 하나로 직렬화한다.
  • 합성 member root가 존재하는 생성 완료·화면 검증·유지 기간에는 개발 cron 전체가 maintenance SKIP이다.
  • reset transaction과 asset cleanup이 끝나 합성 root가 0건이면 다음 dispatcher부터 정상 개발 데이터를 처리한다.
  • 화면 검증 중 정상 개발 row의 cron 처리도 필요하면 합성 dataset을 먼저 reset한다. 합성 target만 선택적으로 제외하는 ownership graph를 추가하지 않는다.

lock 실패 복구

  • maintenance SKIP이 지속되면 실행 중인 dev-data CLI와 cron process가 있는지 확인한다.
  • DB 연결 또는 lock 해제 실패 응답이면 API DB pool과 DB session 상태를 확인하고, 해제 실패 connection이 파기됐는지 로그로 확인한다.
  • fence 실패 때 cron handler의 성공 body가 관측되었다고 간주하지 않는다. 응답 write는 lock 해제 뒤에만 발생한다.
  • lock table, lease file, registry mutex를 별도로 만들거나 advisory lock을 수동 해제하지 않는다.
  • 원인을 해소한 뒤 합성 root 유무에 따라 pnpm data-feed verify --namespace <namespace> 또는 수동 cron job 하나를 실행하고 scheduler를 재검증한다.

관련 문서