예약 작업이 지금 ‘어느 브랜치’ 코드를 돌리는지 확인해본 적 있나요?

과외 자료를 자동으로 정리하는 스크립트를 만들다 보니, 저는 그걸 매일 정해진 시각에 스스로 돌게 예약 작업으로 걸어뒀어요. 그런데 문득 이런 질문이 떠올랐습니다. “이 예약 작업은 지금 내 코드의 어느 버전을 돌리고 있는 거지?” 답을 바로 못 하겠더라고요. 이건 사고가 나서 쓴 글이 아니라, 사고가 나기 전에 막은 이야기입니다. 그런데 왜 미리 막아야 했는지, 구조를 보면 납득이 되실 거예요.

예약 작업은 ‘커밋한 버전’이 아니라 ‘지금 폴더 상태’를 돌린다

먼저 용어부터 풀게요. 예약 작업(scheduled task): 정해진 시각에 사람이 없어도 알아서 실행되는 작업이에요. 저는 상태 집계 스크립트를 매일 밤 도는 예약 작업으로 걸어뒀습니다.

여기서 많은 사람이 착각하는 게 있어요. 예약 작업이 “내가 어제 완성해서 저장(커밋)한 그 버전”을 돌린다고 생각하기 쉬운데, 아니에요. 예약 작업은 그 폴더의 바로 지금 디스크 상태를 그대로 실행합니다. 무슨 뜻이냐면요.

  • 브랜치(branch): 코드의 평행 세계 같은 거예요. 안정된 본선을 main이라고 하고, 새 기능을 실험할 땐 feature라는 곁가지를 따로 만들어 거기서 작업해요.
  • 체크아웃(checkout): 폴더를 특정 브랜치 상태로 “갈아 끼우는” 것. feature 브랜치를 체크아웃하면 그 폴더의 파일들이 실험 중인 코드로 바뀝니다.

문제가 보이시나요? 제가 밤에 새 기능을 실험하려고 feature 브랜치를 체크아웃해둔 채 잠들면, 그 폴더는 지금 실험 코드 상태예요. 그리고 아침에 예약 작업이 깨어나면, 그 실험 중인 코드를 그대로 프로덕션(실제 운영)으로 돌립니다. 제가 “이건 아직 테스트 중인데”라고 생각하는 코드가, 사람 없는 새벽에 진짜로 실행돼버리는 거죠.

왜 미리 막아야 했나 — 터지고 나서 알기엔 비싼 위험

솔직하게 밝힐게요. 이것 때문에 실제로 사고가 난 적은 없어요. 저는 관측된 오작동을 보고 고친 게 아니라, 구조를 보고 “이건 언젠가 반드시 터진다”고 판단해서 미리 막았습니다.

그 판단의 근거는 이야기가 아니라 구조예요. 예약 작업이 디스크의 현재 상태를 실행한다는 사실은 변하지 않아요. 그리고 그 폴더에서 개발도 하는 사람(1인 개발자라면 거의 전부죠)은 언젠가 반드시 feature 브랜치를 체크아웃해둔 채 밤을 넘깁니다. 두 개가 겹치는 날, 예약 작업은 조용히 엉뚱한 코드를 돌려요.

이 위험이 고약한 이유: 티가 안 나요. 예약 작업은 사람이 없을 때 도니까, 잘못된 코드가 돌아도 그 자리에서 아무도 못 봐요. 나중에 결과가 이상해진 뒤에야 “어, 왜 이러지?” 하고 거슬러 올라가게 되죠. 터지고 나서 원인을 찾는 비용이 큰 종류라, 터지기 전에 막는 게 남는 장사입니다.

가드의 형태 — ‘무인일 때만’ 막는 플래그

그래서 브랜치 가드를 붙였어요. 핵심 아이디어는 “예약 작업이 실행되는 순간 현재 브랜치가 main이 아니면, 그냥 실행하지 말고 멈춰라”입니다.

그런데 여기서 제일 중요한 설계 결정이 있어요. 이 가드를 무조건 켜지 않았다는 것. 만약 “항상 main이 아니면 무조건 중단”으로 만들면, 제가 feature 브랜치에서 손으로 직접 스크립트를 테스트할 때도 막혀버려요. 그건 개발을 방해하죠.

그래서 가드를 플래그(flag, 실행할 때 붙이는 옵션 스위치) 뒤에 숨겼어요. 예약 작업이 부르는 무인 진입점에서만 그 플래그(--require-main)를 붙이게 했습니다. 정리하면 이래요.

  • 예약 작업(무인 실행): 플래그가 붙음 → main이 아니면 중단. 엉뚱한 코드가 새벽에 도는 걸 막음.
  • 내가 손으로 실행(대화형 개발): 플래그 없음 → 가드가 발동 안 함. feature 브랜치에서 자유롭게 테스트.

즉 이건 항상 main만 실행하는 게 아니라, 무인 진입점에서만 main을 강제하는 것이에요. 사람이 개입하는 자리와 개입하지 않는 자리를 구분한 게 이 설계의 핵심입니다.

그리고 하나 더. 가드가 걸려서 멈출 때 조용히 죽지 않게 했어요. 명시적인 종료 코드(exit code, 프로그램이 끝날 때 성공·실패를 알리는 숫자)로 중단해서, 나중에 로그를 보면 “아, 브랜치가 main이 아니라서 안 돌았구나”를 알 수 있게요. 조용히 넘어가면 안 돌아간 이유조차 못 찾거든요.

검증 — ‘발동할 때’와 ‘발동하지 말아야 할 때’ 둘 다 확인

가드는 만들었다고 끝이 아니에요. 두 방향을 다 확인해야 진짜 검증입니다. 한쪽만 보면 반쪽짜리예요.

첫째, 발동해야 할 때 발동하는가. feature 브랜치로 갈아 끼운 상태에서 무인 플래그를 붙여 실행해봤어요. 결과는 곧바로 중단(종료 코드 2), 평소 같으면 나올 실행 로그도 없고 네트워크 호출도 없었습니다. 일이 시작되기 전에 딱 끊긴 거죠. 가드가 제대로 문을 막았다는 뜻이에요.

둘째, 그리고 이게 자주 빠뜨리는 부분인데, 발동하지 말아야 할 때 얌전히 있는가. 플래그 없이(손으로 개발하듯) 실행하면 가드가 발동하지 않아야 해요. 안 그러면 개발을 막으니까요. 이것도 확인했습니다 — 플래그가 없으면 가드는 조용히 비켜줬어요.

이 “양쪽 검증”은 습관으로 만들면 좋아요. 안전장치를 넣을 땐 막아야 할 걸 막는지뿐 아니라 막지 말아야 할 걸 안 막는지까지 봐야, 안전장치가 오히려 일을 방해하는 사태를 피할 수 있거든요. 실제로 이 브랜치 문제는 비슷한 예약 스크립트들에서도 똑같이 생길 수 있어서, 같은 가드를 그쪽에도 옮겨 붙이며 양쪽 경로를 다시 확인했어요.

정리 — 자동화에 물어볼 질문

점검 질문왜 중요한가액션
예약 작업이 어느 브랜치를 도는가커밋 버전이 아니라 ‘지금 디스크’를 실행함🔴 지금: main일 때만 무인 실행하게 가드
가드가 손 개발까지 막지 않나무조건 강제는 개발을 방해함🔴 지금: 플래그로 무인 진입점만 강제
멈출 때 티가 나는가조용히 죽으면 원인을 못 찾음🟡 백로그: 명시적 종료 코드로 중단
검증을 양쪽 다 했나발동/미발동 둘 다 봐야 반쪽이 아님🟢 계속: 안전장치는 양방향 테스트

한마디로, 자동화에게 “지금 어느 코드를 돌리고 있니?”라고 물어보라는 거예요. 예약 작업은 내가 커밋한 안정 버전이 아니라 폴더의 현재 상태를 돌리기 때문에, 개발과 자동화가 같은 폴더를 쓰면 언젠가 부딪힙니다. 사고가 난 뒤에 알기엔 비싼 위험이라, 구조를 이해했다면 터지기 전에 문 하나 달아두는 게 낫습니다.


댓글 남기기