수집 서버가 한 대였다. 고쳐야 할 것이 있는데 못 고치고 있었다.
Table of contents
Open Table of contents
멈출 수 없었다
장비 스무 대가 그 서버로 HTTP 로 보낸다. 서버를 내리면 그동안 자료가 안 들어온다.
장비가 큐에 쌓아 두므로 잃지는 않는데 오래 내리면 그 큐가 넘친다. 큐 상한이 하루치라 정지 가능 시간이 그보다 짧아야 했다.
큐 상한 하루치
서버 정지 가능 시간 그보다 짧아야 한다
계산상 몇 시간은 되는데 실제로는 아무도 collect-01 을 몇 시간 내리자고 하지 않았다. 자료를 놓칠 위험을 지고 고치자는 말을 꺼내기가 어려웠다. InfluxDB 판 하나 올리자고 하루치 자료를 걸 수는 없었다.
미룬 것이 쌓였다
미룬 목록이 이렇게 됐다.
시계열 저장소 판 올리기 미룸
디스크 늘리기 미룸
커널 갱신 미룸
수집 코드 구조 개선 미룸
넉 달째 미루고 있었다. InfluxDB 판 올리기부터 커널 갱신까지 한 번에 해야 할 것이 계속 늘었다.
한 대뿐이라 못 고치고 못 고치니 더 손대기 어려워지는 상태였다. 미루는 것이 미루는 이유를 만들고 있었고 그 고리를 어디선가 끊어야 했다.
조치 — 고치려고 늘린 한 대
그래서 한 대를 더 놓기로 했다. 부하 때문이 아니라 고치기 위해 늘리는 것이었다.
서버 A 지금 것
서버 B 새로 놓는다
장비가 collect-01 과 collect-02 중 하나로 보내게 하면 하나를 내려도 다른 쪽이 받는다. 멈춰도 되는 상태를 먼저 만들고 고치는 순서다.
늘리는 이유를 분명히 안 해 두면 CPU 도 남는데 왜 늘리느냐는 말이 나온다. 처리량이 아니라 정지 가능 시간을 사는 것이라고 적어 뒀다. 같은 장비를 두 대 놓는데 목적이 다르면 뒤에 붙일 것도 달라진다.
어떻게 나눌지 정했다
앞에 분산기를 두는 방법도 있었는데 장비 쪽에서 고르는 방식으로 했다.
static const char *servers[] = { "collect-01", "collect-02" };
int send_row(row_t *r) {
for (int i = 0; i < 2; i++) {
int idx = (cur_server + i) % 2;
if (post_to(servers[idx], r) == 0) { cur_server = idx; return 0; }
}
return -1;
}
cur_server 에 기억해 둔 쪽을 먼저 부르고 post_to 가 실패하면 다른 쪽으로 넘어간다. 성공한 쪽을 다시 기억하므로 다음부터는 살아 있는 쪽으로 바로 간다.
분산기를 두면 그것이 다시 하나뿐인 고장 지점이 된다. 하나를 없애려고 늘리면서 새 하나를 만드는 셈이라 안 뒀다.
제약 — 두 곳에 나뉘는 자료
나눠 받으면 자료가 두 곳에 있게 된다. 조회할 때 둘을 다 봐야 한다.
서버 A ─┐
├─→ 저장소
서버 B ─┘
그래서 두 서버가 같은 InfluxDB 에 쓰게 했다. 저장소가 다시 하나가 되지만 그쪽은 갱신이 잦지 않았다.
수집 서버는 코드를 자주 고치므로 그쪽을 늘린 것이었다. 무엇을 늘려야 하는지는 무엇을 자주 손대는지가 정했다.
전부를 둘로 만들면 비용도 두 배가 되고 맞춰야 할 것도 늘어난다. 자주 손대는 계층만 고른 것이 이번 판단의 핵심이었다.
미룬 것을 하나씩 처리했다
한 대를 내릴 수 있게 되니 미룬 것을 꺼냈다.
1. B 를 새 판으로 세운다
2. 장비 몇 대를 B 로 보내 본다
3. 값이 맞는지 확인한다
4. 전부 B 로 보낸다
5. A 를 새 판으로 올린다
6. 다시 나눈다
한 번에 하나씩 했다. 넉 달 미룬 것을 3주에 걸쳐 다 처리했다.
하나씩 하니 문제가 나도 어느 단계 때문인지 curl 한 번으로 갈렸다. 한꺼번에 했으면 커널 갱신 탓인지 InfluxDB 판 탓인지 구분이 안 됐을 것이다.
검증 — 두 대가 같은 결과를 내는가
두 서버가 같은 결과를 내는지 확인이 필요했다.
$ for h in collect-01 collect-02; do
echo -n "$h "
curl -s "http://$h:8086/query?db=sensor&q=SELECT+count(*)+FROM+reading+WHERE+time>now()-1h"
done
같은 InfluxDB 를 보므로 count(*) 값이 같아야 하고 다르면 한쪽이 못 쓰고 있는 것이다.
판을 올린 뒤에는 이 count(*) 를 며칠 지켜봤다. 올린 직후에 맞는 것과 며칠 뒤에도 맞는 것이 다른 확인이었다.
결과 — 따라온 것과 새로 생긴 것
원래 목적은 고칠 수 있게 하는 것이었는데 다른 것도 따라왔다. 한 대가 죽어도 수집이 안 멈춘다.
실제로 한 번 디스크 오류로 collect-01 이 죽었는데 아무도 몰랐다. collect-02 가 다 받고 있었기 때문이다.
그런데 그것이 새 문제이기도 했다. 죽은 것을 모르는 상태가 며칠 갔다.
# 두 대 다 살아 있는지 확인
for h in $SERVERS; do
curl -sf "http://$h:8086/ping" > /dev/null || echo "DOWN: $h"
done
curl -sf 로 /ping 이 안 되면 호스트 이름을 찍게 했다. 한 대가 죽어도 서비스가 되니 알림이 오히려 더 필요해졌다.
전에는 죽으면 수집이 멈춰서 그 자체가 알림이었는데 collect-02 가 받아 주면서 그 신호를 잃었다.
정리
- 한 대뿐이면 못 고치고 못 고치니 미루게 된다
- 미루는 것이 미루는 이유를 만든다
- 부하가 아니라 고치기 위해 늘리는 경우가 있다
- 늘리는 이유를 적어 두지 않으면 왜 늘리느냐는 말이 나온다
- 분산기를 두면 그것이 다시 하나뿐인 고장 지점이 된다
- 보내는 쪽이 우리 것이면
cur_server로 살아 있는 쪽을 고르게 한다 - 무엇을 늘릴지는 무엇을 자주 손대는지가 정한다
- 늘린 뒤에는 미룬 것을 한 번에 하나씩 처리한다
- 두 대가 같은 값을 내는지 확인하는 방법을 만든다
- 한 대가 죽어도 되면 멈춤을 알려 주던 신호를 잃는다