수집 대상을 여덟에서 스물로 늘리기로 했다. 지금 장비로 되는지 물었을 때 답을 못 했다.
Table of contents
Open Table of contents
사전 준비 — 대상당 자원을 쟀다
늘리기 전에 지금 도는 것부터 쟀다.
$ ps -o pid,rss,pcpu,args -C collector
PID RSS %CPU ARGS
2841 184320 6.2 collector --target=a
2847 179104 5.8 collector --target=b
...
대상 하나에 collector 프로세스 하나가 뜨고 RSS 가 180MB쯤 %CPU 가 6쯤이다.
8 대상 → 메모리 1.4GB, CPU 48%
20 대상 → 메모리 3.6GB, CPU 120%
코어가 둘이라 상한이 200%이니 20 대상 의 120%는 아직 여유가 있어 보인다.
다만 이 숫자는 %CPU 의 평균이라 평균으로 계산하면 충분해 보이는 것이 실제로는 모자라는 경우가 있다. 그래서 몰리는 때를 따로 봐야 했다.
몰리는 때를 봤다
수집이 언제 도는지부터 봤다.
* * * * * /usr/local/bin/collect-all.sh
collect-all.sh 하나에 여덟 대상이 전부 매분 0초에 함께 시작한다.
$ sar -u 1 60 | sort -k8 -n | head -3
14:22:00 all 91.20 0.00 6.10 1.90 0.00 0.80
sar 로 보니 시작하는 몇 초 동안 프로세서가 구십 퍼센트 넘게 올라간다.
평균 절반 수준과 순간 구십 퍼센트대의 차이가 이 구조의 성격이고 대상이 스물이 되면 그 몇 초가 다음 주기까지 밀린다.
장비가 견뎌야 하는 값은 평균이 아니라 이 순간의 합계이고 나머지 시간에는 자원이 대부분 놀고 있었다.
조치 — 시작 시각 흩기
전부 같은 초에 시작할 이유가 없었다.
#!/bin/sh
i=0
for t in $TARGETS; do
( sleep $(( i * 2 )); /usr/local/bin/collector --target="$t" ) &
i=$(( i + 1 ))
done
sleep 으로 2초씩 밀어 시작하니 스물이면 40초에 걸쳐 흩어진다.
같은 일을 같은 만큼 하는데 최대치만 내려갔고 장비를 안 늘리고 얻은 여유라 값이 쌌다.
다만 이 방법은 수집 주기가 1분일 때만 성립한다. 주기가 짧아지면 흩을 자리 자체가 없고 40초를 흩으면 다음 주기와 겹친다.
제약 — 대상 수가 곧 자원인 구조
sleep 으로 시작을 흩는 것은 최대치만 낮추고 총량은 그대로 둔다.
collector 하나가 대상 하나를 맡으면 대상 수가 곧 자원이다. 이 구조로는 늘릴 때마다 장비를 늘려야 한다.
프로세스가 실제로 무엇을 하는지 봤다.
- 대상에 접속해 값을 읽는다 (대기가 대부분)
- 파싱한다 (짧다)
- 저장소에 넣는다 (대기)
파싱은 짧고 도는 시간의 대부분이 응답을 기다리는 시간이었다.
이런 일은 collector 하나가 여럿을 맡아도 된다.
for (;;) {
int n = poll(fds, ntargets, 1000);
for (int i = 0; i < ntargets; i++) {
if (fds[i].revents & POLLIN) { handle(i); }
}
}
poll 로 여러 대상을 한 프로세스가 돌면 메모리가 대상 수에 비례하지 않는다. 매뉴얼대로 poll 은 파일 서술자 집합 중 하나가 입출력 준비될 때까지 기다리는 호출이라 대기가 대부분인 일에 맞는다.
선택지 — 격리와 자원
대신 내주는 것이 있었다.
[대상마다 프로세스] 하나가 죽어도 나머지가 산다. 자원이 대상 수에 비례한다
[하나가 전부] 자원이 적게 든다. 하나가 죽으면 전부 멈춘다
앞은 격리를 얻고 자원을 쓰며 뒤는 자원을 아끼고 격리를 내주는 쪽이다.
그래서 중간을 골랐다. 이유는 어느 한쪽을 전부 내주고 싶지 않아서다. 대상을 넷씩 묶어 collector 하나가 맡게 했다.
20 대상 → 프로세스 5개
RSS 합이 3.6GB에서 1GB 아래로 내려갔고 하나가 죽어도 네 대상만 멈춘다.
묶는 크기는 설정으로 뺐는데 지금 넷이 맞는지는 돌려 봐야 알고 나중에 다시 정할 여지를 남겨 두는 편이 낫다.
대응 — 대상당 자원을 계속 기록
다음에도 같은 질문이 올 것이다.
* * * * * ps -o rss= -C collector | awk '{s+=$1} END {print strftime("%s"), s, NR}' >> /var/log/collector-usage
rss 합과 프로세스 수를 collector-usage 에 매분 남기고 대상 수로 나누면 대상당 값이 나온다.
물었을 때 재기 시작하면 며칠이 걸리고 그동안 답을 못 하는데 이미 재 둔 것이 있으면 그 자리에서 답한다.
이번에 여덟에서 스물 을 두고 답을 못 한 것도 재 둔 것이 없었기 때문이다. 늘릴지 말지를 정하는 자리에서 필요한 것은 정확한 예측이 아니라 근거가 되는 숫자였다.
정리
- 늘리기 전에 대상 하나가 무엇을 얼마나 쓰는지 잰다
- 평균이 아니라 몰리는 때를 본다
- 같은 시각에 시작하면 그 순간이 상한을 정한다
- 시작을 흩는 것만으로 최대치가 내려간다
- 총량은 그대로이고 최대치만 내려간다
- 주기가 짧으면 흩을 자리가 없다
- 대상마다 프로세스면 대상 수가 곧 자원이다
- 대기가 대부분인 일은 하나가 여럿을 맡을 수 있다
- 격리와 자원 중 무엇을 내줄지 정한다
- 묶는 크기를 설정으로 두면 나중에 다시 정할 수 있다
- 대상당 자원을 계속 기록해 두면 물었을 때 바로 답한다