- 기술
- 문제해결
충전기는 자기 상태를 점으로 알려 준다. "지금 충전중", "지금 대기중". 하지만 알고 싶은 건 선이다. 충전이 언제 시작해서 언제 끝났고 얼마나 걸렸나. 이 글은 LogStash의 Aggregate 필터로 점을 선으로 바꾼 방법과, 그 대가로 받아들인 제약을 적는다.
LogStash를 이 일 때문에 들인 건 아니다. 이미 RDB에 Raw 데이터가 쌓이고 있었고, LogStash가 주기적으로 그걸 ElasticSearch로 옮기고 있었다. 옮기는 길목에서 같이 처리하기로 했다.
실제 테이블을 단순화한 것이다. 행 하나는 어떤 충전기가 어느 시각에 어떤 상태였는지만 말한다. 충전 작업이라는 정보는 어디에도 없다.
충전중이 아닌 상태에서 충전중으로 바뀌는 지점충전중에서 충전중이 아닌 상태로 바뀌는 지점끝이 충전대기가 아니면 비정상 종료로 태그를 달아 따로 모았다. 버리지 않은 건 오류 자체도 분석 대상이기 때문이다. 이 정의로 충전기C의 작업은 #10에서 #12까지이고 비정상 종료다.
Aggregate 필터는 이벤트를 task_id로 묶고, 묶음마다 map이라는 Ruby Hash를 유지한다. 이벤트는 하나씩 지나가지만 map은 남아 있어서, 앞 이벤트에서 적어 둔 값을 뒤 이벤트에서 읽을 수 있다. 상태를 기억하는 필터인 셈이다.
충전기 ID를 task_id로 쓰면 충전기마다 map이 하나씩 생긴다. 규칙은 둘이다.
map이 없으면 새 작업을 시작하고 시작 시각을 적는다. 있으면 진행 중인 작업에 이어 붙인다.map이 있으면 작업을 끝내고 걸린 시간을 계산한 뒤 map을 지운다. 끝난 이유가 충전대기인지 아닌지는 태그로 남긴다.충전이 끝났는데 충전대기 신호가 누락되면 다음 충전까지 하나로 이어져 충전 시간이 비정상적으로 길어진다. 이런 값은 평균을 망가뜨린다. 방법은 두 가지가 있다.
inactivity_timeout은 마지막 이벤트 이후 일정 시간이 지나면 map을 만료시킨다. 다만 충전기A처럼 상태가 바뀔 때만 알리는 기기에서는 정상적인 긴 충전도 만료시켜 버린다. 또 기준 시각이 기본적으로 시스템 시각이라, 쌓여 있던 과거 데이터를 한꺼번에 읽을 때는 timeout_timestamp_field로 이벤트의 시각을 기준으로 삼아야 한다.이 방식은 이벤트가 발생한 순서대로 들어온다는 전제 위에 서 있다. 순서가 섞이면 시작과 끝을 잘못 짝짓는다. 그래서 두 가지를 지켜야 한다.
ORDER BY updated_at을 넣었다.-w 1로 실행하거나 pipeline.workers: 1로 설정한다. 공식 문서가 이 필터를 쓸 때 반드시 지키라고 적어 둔 조건이다.즉 이 파이프라인은 수평으로 늘릴 수 없다. 상태를 기억하는 대가다. 파이프라인이 여럿 필요하다면 집계가 필요한 이벤트만 한 노드로 모아야 한다.
점으로 된 로그에서 선을 얻으려면 누군가는 앞의 점을 기억해야 한다. Aggregate 필터는 그 기억을 LogStash 안에 두는 방법이고, 덕분에 데이터를 옮기는 길목에서 시스템을 더 들이지 않고 작업을 식별했다. 대신 순서 보장과 단일 워커라는 제약을 받아들였다.