장치 수십 대가 1초마다 값을 보냈다. 몇 주 지나니 MySQL 저장 공간이 찼고 값을 덜 재면 놓치는 것이 생긴다.
Table of contents
Open Table of contents
재는 것과 남기는 것을 나눴다
read_temp 는 1초마다 부르되 그 값을 다 보내지는 않기로 했다.
static float sum = 0;
static float min_v = FLT_MAX, max_v = -FLT_MAX;
static int cnt = 0;
void on_tick(void) {
float t;
if (read_temp(&t) != 0) return;
sum += t;
if (t < min_v) min_v = t;
if (t > max_v) max_v = t;
cnt++;
if (cnt >= REPORT_INTERVAL) { /* 60초 */
send_report(sum / cnt, min_v, max_v, cnt);
sum = 0; min_v = FLT_MAX; max_v = -FLT_MAX; cnt = 0;
}
}
REPORT_INTERVAL 마다 한 번 보내고 평균과 최소와 최대와 몇 번 쟀는지를 같이 보낸다. 전송량이 60분의 1이 됐다.
자주 재는 것과 잰 것을 전부 저장하는 것은 다른 얘기다. 계속 자주 재되 보내는 것만 묶으면 짧은 변화를 놓치지 않으면서 저장량은 크게 준다.
평균만으로는 부족했다
sum / cnt 만 보내면 그 안의 변화가 사라진다. 30초 동안 60도였다가 30초 동안 0도여도 평균은 30도다.
최소와 최대를 같이 보내니 그 구간에 무슨 일이 있었는지가 보인다.
avg 24.5 min 24.1 max 24.9 n 60 → 안정
avg 30.0 min 0.2 max 59.8 n 60 → 뭔가 있었음
두 번째면 그 구간을 자세히 봐야 한다.
n 60 을 보내는 것도 중요했다. 1분에 60번 재야 하는데 40번이면 뭔가 놓친 것이다.
avg 24.5 min 24.1 max 24.9 n 40 ← 20번 실패
평균값만 보면 정상으로 보이는데 cnt 가 있으니 read_temp 실패가 드러난다. 서버에서 이 값을 감시해서 50 미만이면 알린다.
급변하면 즉시 보냈다
주기를 기다리는 동안 중요한 변화를 놓칠 수 있다.
if (fabsf(t - last_reported) > ALERT_DELTA) {
send_alert(t);
last_reported = t;
}
ALERT_DELTA 를 넘으면 주기와 무관하게 보낸다. 정기 보고와 사건 보고를 나눈 것이고 정기는 추세를 보고 사건은 놓치면 안 되는 것을 본다.
저장 기간의 단계
보내는 양을 줄여도 temp_1min 에 계속 쌓이므로 오래된 것을 줄였다.
최근 7일 1분 단위 그대로
7~30일 10분 단위로 묶음 (평균/최소/최대)
30일 이후 1시간 단위로 묶음
1년 이후 삭제
최근 것은 자세히 보고 오래된 것은 성기게 본다. 묶는 작업은 crontab 으로 새벽에 돌게 했다.
INSERT INTO temp_10min (device_id, ts, avg_v, min_v, max_v, cnt)
SELECT device_id,
FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(ts) / 600) * 600),
AVG(avg_v), MIN(min_v), MAX(max_v), SUM(cnt)
FROM temp_1min
WHERE ts < DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY device_id, FLOOR(UNIX_TIMESTAMP(ts) / 600);
최소와 최대는 묶어도 MIN 과 MAX 로 유지된다. 평균은 개수로 가중해야 정확한데 개수가 같으면 단순 평균으로 충분했다.
검증 — 저장량 계산과 빈 구간
바꾸기 전에 계산했다.
장치 40대 × 1초 × 1행 30바이트 = 하루 약 103MB
장치 40대 × 60초 × 1행 40바이트 = 하루 약 2.3MB
REPORT_INTERVAL 을 60으로 두면 45분의 1이고 1년치가 800메가 남짓이면 감당할 수 있었다. 계산을 먼저 하면 어느 주기가 맞는지 정해지고 감으로 정하면 나중에 또 바꾼다.
장치가 꺼져 있으면 그 구간에 자료가 없는데 Chart 화면에서는 선이 이어져 보인다.
if (next.ts - prev.ts > REPORT_INTERVAL * 2) {
/* 선을 잇지 않는다 */
}
앞뒤 시각 차이가 주기보다 크면 끊어 그렸다. 이어진 선과 끊긴 선은 다른 뜻이고 자료가 없는 구간을 없는 것으로 보여 준다.
정리
- 자주 재는 것과 다 저장하는 것은 별개다
- 장치 안에서 자주 재고 묶어서 보낸다
- 평균만 보내면 그 안에서 튄 값이 사라진다
- 최소와 최대를 같이 보내야 구간의 변화가 보인다
- 표본 개수를 같이 보내면 읽기 실패가 드러난다
- 정기 보고와 사건 보고를 나누고 급변은 주기를 안 기다린다
- 오래된 자료는 단계적으로 성기게 묶는다
- 묶어도
MIN과MAX는 유지되고 평균은 개수를 봐야 한다 - 주기를 정하기 전에 저장량을 계산한다
- 자료가 없는 구간은 선을 끊어 보인다