Skip to content
isdnetworks
Go back

파일 하나에 프로세스 하나를 띄우고 있었다

배포 후에 파일 권한을 맞추는 스크립트가 있다. 예전부터 있던 것인데 언제부턴가 40분씩 걸린다. 작업 자체는 단순해서 파일은 644 디렉터리는 755로 맞추는 것이 전부다.

Table of contents

Open Table of contents

스크립트가 이랬다

find /var/www/game -type f -exec chmod 644 {} \;
find /var/www/game -type d -exec chmod 755 {} \;

문제없어 보인다. 실제로 파일이 몇천 개일 때는 몇 초에 끝났다.

지금 세어 봤다.

$ find /var/www/game -type f | wc -l
83412

wc -l 로 세니 8만 3천 개다. 리소스 파일인 이미지와 사운드가 계속 늘었다.

원인 — 세미콜론과 플러스의 차이

-exec ... \; 는 파일 하나마다 명령을 한 번씩 실행한다. 8만 3천 번 chmod 프로세스를 띄운다는 뜻이다. -exec ... + 는 여러 파일을 모아 한 번에 넘긴다.

# \; 는 이렇게 실행된다
chmod 644 /var/www/game/a.png
chmod 644 /var/www/game/b.png
chmod 644 /var/www/game/c.png
...8만 3천

# + 는 이렇게 실행된다
chmod 644 /var/www/game/a.png /var/www/game/b.png ... (수천 )
chmod 644 ... (나머지)

인자 길이 제한 때문에 몇 번으로는 나뉘지만 8만 번이 수십 번이 된다. xargs 로 파이프해도 같다.

chmod 자체는 순식간이다. 오래 걸리는 것은 프로세스를 만들고 없애는 일 곧 forkexec 이고 그것이 8만 번 반복됐다.

바꿔서 재 봤다

$ time find /var/www/game -type f -exec chmod 644 {} \;
real    38m41.203s

$ time find /var/www/game -type f -exec chmod 644 {} +
real    0m11.907s

38분이 12초가 됐다. 코드는 문자 하나 차이다.

대상을 2,000개로 줄여 따로 재 보면 차이가 더 또렷하다. \; 는 3.9초이고 + 는 0.02초로 한 번에 200배 가까이 갈린다.

이미 맞는 것은 대상에서 뺀다

한 발 더 갔다. 8만 개 중 대부분은 이미 644고 새로 올라온 것만 다르다.

find 에 권한 조건을 넣을 수 있다.

find /var/www/game -type f -not -perm 644 -exec chmod 644 {} +
find /var/www/game -type d -not -perm 755 -exec chmod 755 {} +

-not -perm 644 는 권한이 644가 아닌 것만 남기므로 대상이 8만 개에서 몇백 개로 줄었다.

$ time find /var/www/game -type f -not -perm 644 -exec chmod 644 {} +
real    0m3.114s

3초다. 파일 목록을 훑는 시간이 대부분이다.

다른 자리의 같은 패턴

같은 구조를 셸 스크립트에서 여러 번 봤다. grep -rn 으로 찾으니 for 문 안에서 외부 명령을 부르는 자리가 여럿 나왔다.

# 파일마다 프로세스 두 개(cat, grep)를 띄운다
for f in $(ls logs/*.log); do
    cat $f | grep ERROR
done

grep 은 원래 파일을 여러 개 받는다.

grep ERROR logs/*.log

cat 도 필요 없고 반복도 필요 없다. 프로세스가 파일 수 × 2 에서 1이 된다.

# 파일마다 stat 을 부른다
for f in *; do
    size=$(stat -c %s "$f")
    ...
done

statfinddu 로 한 번에 얻을 수 있는 값이다. 루프 안에 외부 명령이 있으면 그 명령이 파일 수만큼 실행되는데 이것이 셸 스크립트가 느려지는 가장 흔한 이유다.

이 스크립트는 배포될 때부터 이랬고 코드는 안 바뀌었으며 데이터만 늘었다. 앞서 DB 에서 겪은 것과 같은 구조로 인덱스 없는 조회가 몇천 건일 때는 안 보이다가 몇십만 건에서 터진 것과 같다.

둘 다 건당 비용 × 건수인데 건수만 늘어난 경우다. 건당 비용이 작아 보여서 넘어갔던 것이 건수가 커지면서 전체를 지배한다. 그래서 이런 것을 볼 때 건당 무엇을 하는지와 건수가 늘어날 수 있는지 둘을 본다. 건당 비용이 0에 가까우면 건수가 늘어도 괜찮은데 프로세스 생성은 0이 아니다.

정리


Share this post on:

Previous Post
만드는 쪽과 처리하는 쪽이 떨어져 있었다
Next Post
포트 번호를 매번 찾고 있었다