개발계 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한다.
설치 전 확인¶
- 개발 API 배포 경로와 PM2 process
coupler-api가 현재 개발 DB를 사용하는지 확인한다. - 배포 SHA에 development cron access guard, 외부 전송 억제 context, destructive guard, dispatcher가 포함됐는지 확인한다.
command -v pnpm,/usr/bin/flock, loopback API 응답을 확인한다.- 기존 user/root crontab과
/etc/crontab,/etc/cron.d를 백업·검색해/admin/cron/직접 호출과 기존 dispatcher가 없는지 확인한다. installer도 현재 user crontab의 legacy·unmanaged entry를 발견하면 변경 없이 중단한다. - 개발 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 CRONmarker 구간만 원자적으로 교체한다. ops/cron/development.crontab의 단일 1분 dispatcher와flock만 설치한다.- endpoint별 cron 표현식은
lib/development-cron-schedule.ts가 소유한다. - installer는
.runtimedirectory를 mode700, log와flockfile을 mode600으로 만들고 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이 number1이 아니면 이미 성공 응답을 보낸 것으로 처리하지 않고 fence 오류로 실패한다. GET_LOCKquery 실패·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 또는 복구 기준, 작업 시간을 검토한 일회성 작업에서만 수행한다.
- scheduler를 잠시 제거한다.
- 환경 파일의
DEV_CRON_DESTRUCTIVE_ENABLED=true를 설정하고 API를--update-env로 재시작한다. - 대상 job 하나만
run <job-id>로 실행한다. - 삭제 건수와 잔존 불변식을 확인한다.
- 즉시 값을
false로 되돌리고 API를 재시작한 뒤 scheduler를 다시 설치한다.
중지와 rollback¶
- 환경 파일의
DEV_CRON_ENABLED=false를 설정한다. set -a; . /etc/coupler-api/dev-cron.env; set +a후pm2 restart coupler-api --update-env를 실행한다..runtime/development-cron.log에서 마지막 run과 중지 시각을 확인한다.- 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를 재검증한다.