2026년 5월 25일 | 개발 깨알 상식_Tips
timeout을 검증 명령 앞에 붙일 때 내가 자주 놓쳤던 부분은 “몇 분 뒤에 끊는다”가 전부가 아니라는 점이다. 오래 도는 테스트나 변환 작업은 제한 시간을 넘겼을 때 깔끔하게 끝나지 않을 수 있다. 자식 프로세스가 남거나, 종료 신호를 무시하거나, 로그는 멈췄는데 CI 작업은 계속 붙잡혀 있는 식이다. 그래서 나는 단순한 시간 제한보다 -k, 정확히는 --kill-after를 같이 보는 편이 더 안전하다고 느꼈다.
시간 제한과 강제 종료는 다른 층
timeout 10m command는 지정한 시간이 지나면 기본적으로 대상 명령에 TERM 신호를 보낸다. 여기까지는 “그만 끝내라”에 가깝다. 대부분의 평범한 프로세스는 이 신호를 받고 종료하지만, 항상 그런 것은 아니다. 테스트 러너가 자식 프로세스를 여럿 띄웠거나, cleanup handler 안에서 오래 걸리거나, 어떤 프로세스가 신호를 무시하면 작업이 애매하게 남는다.
-k 30s는 이 애매한 시간을 한 번 더 잘라 준다. 처음 제한 시간이 지나 TERM을 보낸 뒤에도 30초 동안 살아 있으면, 그때는 KILL로 밀어붙인다. 나는 이걸 친절한 종료와 강제 종료 사이의 완충 구간으로 본다. 테스트가 정상적으로 정리할 시간은 주되, 정리 자체가 멈춘 경우에는 CI 슬롯을 끝없이 잡아먹지 않게 막는 장치다.
timeout -k 30s 10m pytest tests/integration
timeout -k 1m 20m python scripts/build_report.py
124라는 종료 코드를 따로 읽기
GNU coreutils 기준으로 timeout은 제한 시간 때문에 명령을 끝내면 보통 124를 반환한다. 이 값은 내부 테스트 실패와 구분해서 읽어야 한다. 예를 들어 테스트 assertion이 깨진 것과, 테스트가 끝나지 않아서 끊긴 것은 다음 행동이 다르다. 전자는 실패 케이스를 열어 봐야 하고, 후자는 deadlock, 네트워크 대기, 너무 넓은 fixture, 무한 retry 같은 쪽을 먼저 의심해야 한다.
그래서 자동화 로그에는 가능하면 “명령 실패”라고만 남기지 않는다. 제한 시간 초과인지, 명령 자체의 실패인지를 분리해서 적어 둔다. 나는 특히 긴 검증을 여러 개 이어 붙일 때 이 구분이 중요하다고 느꼈다. 앞 단계가 timeout으로 끊겼는데 뒤에서 그냥 “검증 실패”로 합쳐 버리면, 다음에 보는 사람은 실패 원인을 다시 재현해야 한다.
--preserve-status는 조심해서 쓰기
--preserve-status는 이름만 보면 좋아 보인다. 대상 명령의 종료 상태를 보존한다는 뜻이라, wrapper가 원래 실패 코드를 덮지 않게 만들 수 있다. 다만 timeout 여부 자체를 124로 보고 싶은 자동화에서는 오히려 정보가 흐려질 수 있다. “이 명령이 원래 실패했는가”와 “시간 제한 때문에 잘렸는가”를 한 줄 상태 코드로만 판단하려는 경우라면 기본 동작이 더 읽기 쉽다.
내 기준은 단순하다. timeout을 상한선 감시 장치로 쓰는 CI나 배치 스크립트에서는 124를 남기는 기본 동작을 선호한다. 반대로 이미 별도 로그나 wrapper에서 timeout 이벤트를 확실히 기록하고 있고, 하위 명령의 종료 코드를 그대로 전달해야 하는 상황이면 --preserve-status를 검토한다. 옵션 이름이 좋아 보인다고 항상 붙일 문제는 아니었다.
너무 짧은 제한은 디버깅을 망친다
시간 제한을 붙이면 마음이 편해지지만, 값을 너무 공격적으로 잡으면 다른 문제가 생긴다. 원래 6분 걸리는 통합 테스트에 5분 제한을 걸면, 회귀가 아니라 평소 변동폭을 계속 timeout으로 착각하게 된다. 특히 네트워크, 캐시 준비, 첫 실행 컴파일이 끼는 작업은 1회차와 2회차 시간이 다를 수 있다. 제한 시간은 평균 시간이 아니라 정상 실행의 느린 쪽 꼬리를 보고 잡는 편이 낫다.
나는 처음부터 “3분 안에 끝나야 함”처럼 세게 자르기보다, 실제 로그에서 평소 시간을 몇 번 본 뒤 상한을 둔다. 그리고 -k 값은 cleanup이 끝날 정도로 짧게 둔다. 예를 들어 본 제한은 10분, kill-after는 30초나 1분처럼 둔다. 이렇게 나누면 긴 작업을 무한히 기다리지는 않으면서도, 정상적인 종료 정리는 어느 정도 허용할 수 있다.
어디까지 감쌀지 먼저 정하기
timeout을 붙일 때 또 하나 조심하는 건 감싸는 범위다. 명령 하나만 감쌀지, 여러 단계를 묶은 스크립트 전체를 감쌀지에 따라 원인 추적 난이도가 달라진다. 너무 바깥을 감싸면 어느 단계에서 멈췄는지 흐려지고, 너무 안쪽만 감싸면 바깥 wrapper가 여전히 끝나지 않을 수 있다. 긴 파이프라인이라면 먼저 단계별 로그를 남긴 뒤, 실제로 오래 붙잡는 구간만 좁게 감싸는 편이 낫다.
예를 들어 의존성 설치, 데이터 준비, 테스트 실행이 한 덩어리로 묶여 있다면 처음부터 전체에 30분 timeout을 거는 것보다, 테스트 실행 구간에 10분 제한을 따로 두는 쪽이 읽기 쉽다. 설치가 느린 건 캐시 문제일 수 있고, 테스트가 멈춘 건 코드나 fixture 문제일 수 있다. 시간 제한의 위치가 원인 분류의 위치가 되기 때문에, wrapper를 어디에 씌우는지도 작은 설계 문제로 봐야 한다.
로그에는 끊은 이유를 남기기
마지막으로 중요한 건 명령 앞에 timeout을 붙였다는 사실보다, 끊긴 뒤에 무엇을 볼지 정해 두는 일이다. timeout이 났다면 직전 로그 몇 줄, 실행 시간, 대상 테스트 이름, 남은 프로세스 여부 정도는 같이 남기는 게 좋다. 그래야 다음 실행에서 바로 “시간을 늘릴 문제인지, 멈춤 원인을 볼 문제인지”를 나눌 수 있다.
작은 팀 스크립트라면 이 규칙을 더 노골적으로 적어 두는 편이 좋다. “10분 제한, 30초 후 강제 종료, 124는 timeout으로 분류”처럼 한 줄만 남겨도 충분하다. 옵션을 모르는 사람이 봐도 이 명령이 성능 최적화가 아니라 멈춤을 판별하기 위한 경계선이라는 점을 바로 이해할 수 있기 때문이다. 특히 야간 배치나 긴 평가처럼 사람이 바로 보지 않는 작업에서는 이 한 줄이 다음 아침의 첫 판단을 꽤 줄여 준다. 실패 원인 분류도 덜 흔들린다.
내가 좋아하는 형태는 간단하다. 오래 걸릴 수 있는 명령에는 먼저 넉넉한 상한을 걸고, 종료가 안 되면 -k로 강제 종료선을 하나 더 둔다. 그리고 124가 보이면 일반 실패와 섞지 않고 timeout으로 표시한다. timeout -k는 화려한 옵션은 아니지만, 멈춘 검증을 끝까지 붙잡지 않게 해 주는 작은 안전핀에 가깝다. 실패를 고치는 속도보다 먼저, 실패가 어디서 멈췄는지 알아볼 수 있게 만드는 쪽에 더 값이 있었다.
'[개발 깨알 상식_Tips]' 카테고리의 다른 글
| 복사 전에 바뀔 파일만 먼저 확인하기 (0) | 2026.05.29 |
|---|---|
| 실행 로그를 화면과 파일에 동시에 남기기 (0) | 2026.05.27 |
| 공백 들어간 파일명 목록 안전하게 넘기기 (0) | 2026.05.21 |
| 큰 JSON을 통째로 열지 않고 훑어보기 (0) | 2026.05.20 |
| ClawSweeper: AI 이슈 정리 봇은 review와 apply를 분리해 high-confidence 제안만 닫기 (0) | 2026.04.27 |