flock으로 Bash 스크립트 동시 실행 방지하기

cron 배치가 예상보다 오래 걸려 다음 스케줄과 겹치면, 두 인스턴스가 같은 파일에 동시에 써서 데이터가 꼬이는 사고가 난다. PID 파일로 막는 방식은 프로세스가 비정상 종료되면 파일만 남아 다음 실행을 영영 막아버리는 문제가 있는데, flock은 파일 디스크립터에 커널 레벨 락을 걸어 프로세스가 죽으면 락도 자동으로 풀린다. 이 글에서는 flock으로 중복 실행을 막는 관용구를 실제로 재현하고, non-blocking과 대기(timeout) 모드의 차이를 확인한다.

핵심 개념

옵션동작
-n, --nb락을 바로 못 얻으면 대기하지 않고 즉시 실패
-w <초>지정한 시간만큼만 락을 기다리다 실패
-x (기본값)배타적(exclusive) 락 — 한 번에 하나의 프로세스만 보유
-s공유(shared) 락 — 여러 프로세스가 동시에 보유 가능(읽기 전용 용도)

표준 관용구는 exec 200>lockfile로 fd 200번을 락 파일에 연결하고 flock -n 200으로 그 fd에 락을 시도하는 것이다 — 파일 내용은 안 쓰고 커널이 관리하는 락 상태만 이용한다.

실전: 중복 실행 방지 재현

먼저 락을 못 얻으면 바로 종료하는 non-blocking 버전이다.

#!/bin/bash
LOCKFILE=/tmp/blog_demo_job.lock

exec 200>"$LOCKFILE"
if ! flock -n 200; then
    echo "이미 다른 인스턴스가 실행 중이라 종료한다 (PID $$)"
    exit 1
fi

echo "락 획득 성공, 작업 시작 (PID $$)"
sleep 3
echo "작업 완료 (PID $$)"

이 스크립트를 백그라운드로 하나 띄운 직후, 같은 스크립트를 한 번 더 실행한다.

$ ./job.sh &
$ sleep 0.5 && ./job.sh
락 획득 성공, 작업 시작 (PID 2827)
이미 다른 인스턴스가 실행 중이라 종료한다 (PID 2831)
작업 완료 (PID 2827)

-n 때문에 PID 2831은 기다리지 않고 바로 종료했다. 다음은 실패 대신 최대 5초까지 기다리는 -w 버전이다.

#!/bin/bash
LOCKFILE=/tmp/blog_demo_job2.lock
exec 200>"$LOCKFILE"

echo "락 대기 시작 (PID $$)"
if flock -w 5 200; then
    echo "락 획득 (PID $$), 작업 시작"
    sleep 1
    echo "작업 완료 (PID $$)"
else
    echo "5초 안에 락을 얻지 못해 종료 (PID $$)"
fi
$ ./job_wait.sh &
$ sleep 0.3 && time ./job_wait.sh
락 대기 시작 (PID 3029)
락 획득 (PID 3029), 작업 시작
락 대기 시작 (PID 3033)
작업 완료 (PID 3029)
락 획득 (PID 3033), 작업 시작
작업 완료 (PID 3033)

real    0m1.704s

PID 3033은 PID 3029가 락을 반납할 때까지(약 1.7초) 기다렸다가 바로 실행됐다. 스케줄을 놓치면 안 되는 배치는 -w, 겹쳐도 되는 잡은 -n이 맞다.

주의사항

  • 락은 락 파일의 inode에 걸린다 — 파일을 rm 후 재생성하면 새 inode에 락이 걸려 기존 대기 프로세스와 어긋나므로 락 파일은 지우지 않는다.
  • flock [옵션] 락파일 명령... 형태(command form)는 명령이 끝나면 락이 자동으로 풀리지만, 위처럼 exec로 fd를 열어두는 방식은 스크립트 종료나 fd를 닫을 때 풀린다.
  • 서브셸이나 파이프라인 안에서 flock을 걸면 그 서브셸 종료 시 락도 풀릴 수 있다 — 스크립트 전체 수명과 맞추려면 최상위 셸에서 exec로 fd를 열어야 한다.
  • NFS 등 일부 네트워크 파일시스템에서는 flock이 기대대로 동작하지 않을 수 있다 — 로컬 파일시스템에서 쓰는 걸 권장한다.

마무리

exec 200>lockfile; flock -n 200 세 토큰이면 프로세스가 죽든 정상 종료하든 커널이 락을 자동으로 정리해줘서, PID 파일 방식보다 견고하게 중복 실행을 막을 수 있다.

참고

답글 남기기