top of page

[FFT 시리즈 두번째 #2 ] 실시간 고속 푸리에 변환과 알람 시스템 구축

최종 수정일: 7월 19일

마크베이스 실시간 데이터 연구소


처음 이 글에 오신 분이라면, 아래의 이전 자료를 먼저 읽으시길 권고드린다.


들어가며

이전 글에서는 "시계열 데이터베이스"(Time Series DBMS)가 비교가 불가능할 정도의 성능으로 가장 강력하게 활용될 수 있는 고속 푸리에 변환을 활용하는 방법에 대해 기술해 보았다. 그러나, 실제 응용 단계에서는 단순히 FFT 연산 결과를 보는 것과 더불어서 다양한 조건에 대해 실시간 혹은 배치 형태로 이상을 탐지하고, 이를 여러 경로를 통해 담당자에게 이벤트를 알리는 것이 최종 목적이기도 하다. 아래는 상상할 수 있는 여러 알람 상황이다.


  1. 특정 센서에서 허용 가능한 Peak를 넘어서는 경우 (최대 값)

  2. 특정 센서에서 허용 가능한 Trough를 넘어서는 경우 (최소값)

  3. 특정 센서에서 허용 가능한 RMS 값을 넘어서는 경우 (진동 강도)

  4. FFT 결과에서 특정 주파수 범위의 진폭의 값이 허용 값을 넘어서는 경우 (특정 장애 패턴 감지)


위 말고도, 더 고도화된 경우가 있을 수 있지만, 본 문서에서는 그 중에서 1번의 케이스에 대해 두가지 알람 발생 방식을 구현하고, 테스트해 보겠다.


  1. 로그 테이블에 알람의 시간과 실제 값을 추가하는 방식

  2. 특정 Rest API로 구성된 웹훅(web hook)에 알람 정보를 실시간으로 전송하는 방식


위의 1번 같은 경우 예전 블로그 마크베이스 네오 기반의 시스템 모니터링 서비스 구축하기 에서 다룬바 있기 때문에 생소하지는 않을 것으로 생각된다.


그렇지만, 2번과 같은 실시간 알람의 경우에는 실제 필드에서 정말로 발생하지 않아야 하는 응급한 종류의 알람이거나, 매우 위험하거나 중요한 이벤트에 대해 설정을 함으로써 장기적인 큰 위험을 방지하는 상황에 활용하면 유용할 것이다.


주의 사항


이 블로그는 2024년 5월 20일 기준으로 마크베이스 네오 8.0.18-rc3a 이상에서 테스트되고, 작성되었다. 그 이유는 이 버전부터 TQL의 챠트 결과를 바로 대시보드로 출력하는 기능이 추가되어, 별도의 html 화일 및 javascript를 쓰지 않아도 TQL의 차트를 바로 시각화할 수 있기 때문이다. 비록 정식버전은 아니지만, 이후 8.0.19 이후로는 이 기능이 정착될 것이라고 생각된다.

혹시나 여러가지 이유로 이전 버전(8.0.18-rc2 이하)만을 사용해야 하는 경우를 위해 별도의 html 화일을 작성해 놓았으니, 참고하기 바란다.


알람 구축을 위해 실습해 보기


1. 맘 급하신 분들을 위한 영상

아래의 영상은 구축된 데모 영상을 캡쳐한 것이다. 실제로 이전 블로그와 달라진 여러가지 화면과 내용을 확인하시면 될 것 이다.



2. 네오 설치 및 neo-apps 소스코드 받기

이 설치관련 내용은 이전의 블로그 내용과 완전히 동일하다. 실제 설치 방법은 이전 블로그의 링크에서 1번(네오 설치)과 2번(neo-apps 다운로드)만을 참조하고, 설치될 마크베이스 네오 버전은 가능하면 8.0.18-rc3a이후의 최신으로 받도록 하자. 그리고, 여기로 다시 돌아오면 된다. (3번부터는 아래의 내용을 다시 따라하면 된다)


3. realtime-fft에서 추가된 화일들 설명

이 블로그에서는 알람 데이터 발생과 감지가 핵심이기 때문에 데이터 입력 및 관련 TQL 및 대시보드가 별도로 아래와 같이 추가되었다. 대부분의 prefix는 alarm- 이다.


튜토리얼 워크시트

  • alarm-data.wrk


데이터 입력

  • alarm-insert-noise-x.tql

  • alarm-insert-noise-y.tql

  • alarm-insert-noise-z.tql


개선된 FFT 차트

  • alarm-fft-2d.tql : 파라메터를 통해 X,Y,Z 축의 2D FFT 차트 생성

  • alarm-fft-3d.tql : 파라메터를 통해 X,Y,Z 축의 3D FFT 차트 생성


알람 설정 및 프로세싱 TQL

  • alarm-peak-publish-post.tql : 알람시 웹혹을 통해 메시지 전송

  • alarm-peak-insert.tql : 알람시 로그 테이블에 데이터 입력

  • alarm-data-stat.tql : 통계 차트 생성


대시보드

  • alarm-data.dsh : 통계 대시보드 버젼 (8.0.18-rc3a 이상)

  • alarm-fft.dsh : 실시간 FFT 대시보드 (8.0.18-rc3a 이상)

  • alarm-data.html : 통계 대시보드 HTML 버젼 (버전 상관 없음)

  • alarm-peak-history.dsh : 실시간 알람 차트 (버전 상관 없음)


4. 스키마 생성 및 데이터 입력하기


테이블 FFT 센서 구성

이 데모를 위해 실제 진동 센서 데이터를 시뮬레이션 하도록 하였으며, 데모의 효과를 극대화하기 위해 다양한 패턴을 그릴 수 있도록 조작하였다. X, Y, Z 축을 가진 1개의 진동 센서를 가정하였으며, 각각 초당 1000개의 데이터를 생성하도록 하였다. 이 생성은 마크베이스 네오의 입력 TQL을 준비하고, 내부 타이머를 이용하여 1초 각각 1회씩 호출하여, 각각 초당 1000건의 데이터를 입력하여, 데이터베이스 입장에서는 초당 3000건의 센서 데이터가 고속으로 입력된다.

  • alarm-insert-noise-x.tql (X 축 진동 데이터, 150Hz의 진폭 2.0 + 300Hz의 진폭 50)

  • alarm-insert-noise-y.tql (Y 축 진동 데이터, 100Hz의 진폭 1.0 + 0.1Hz의 진폭 10)

  • alarm-insert-noise-z.tql (X 축 진동 데이터, 0.5Hz의 진폭 20 + 15Hz의 진폭 10 + 35Hz의 진폭 3 + 220Hz의 진폭 2 + 400Hz의 진폭 2)


노이즈 생성

아래는 위 세개의 TQL 중에 하나를 보인 것이다. 임의로 노이즈를 섞기 위해 1/1000 의 확률로 데이터에 100을 곱해서 매우 큰 수를 입력하도록 하였다. 코드 2번째 라인을 확인하면 된다.

alarm-insert-noise-x.tql

FAKE( oscillator( freq(150, 2.0), freq(300, 50), range("now", "1s", "1ms") ))
MAPVALUE(1, (random() < 0.0001) ? value(1) * 100: value(1))
PUSHVALUE(0, "vib-x2", "name")
APPEND(table("fft"))

FFT 테이블 및 타이머 생성

아래와 같이 워크시트  alarm-fft.wrk를 통해 생성하자. 그리고, 추가된 내용은 alarm_history라고 하는 로그테이블인데 이곳에 알람의 내역이 입력된다. 또한, retention 기능을 활용해서 일정기간(한달치)만 데이터를 저장하도록 개선하였다. Retention에 대한 자세한 내용은 여기 관련매뉴얼을 참조한다.

또한, 타이머를 생성하여, 노이즈가 포함된 데이터를 자동으로 입력하자. (타이머 만드는 스크립트는 이전 블로그에서 했던 것처럼 동일하게 수행한다)



5. 실시간 FFT 결과 대시보드로 확인하기 (alarm-fft.dsh)

이전과 다른 점이라면, 다수의 FFT 차트를 한 화면에 보기 위해서 더이상 HTML 화일을 별도로 만들 필요가 없다는 것이다. 8.0.18-rc3a에서 추가된 대시보드에서의 TQL 차트 기능을 이용한다.

위 차트를 로딩하면, 아래와 같이 3축에 대해 각각 2D및 3D의 FFT 결과가 실시간으로 출력되며, 부드럽게 데이터가 출력되는 것을 볼 수 있다.


화면

위와 같이 5초에 한번씩 부드럽게 갱신되는 것을 볼 수 있다.

또한, 3D FFT의 경우에는 CHART_BAR3D()가 아닌 CHART()를 직접 써서 공간 배치 등 다양한 차트옵션을 활용했고,아래 소스코드를 참조하자. 또한, 차트 생성시 GET 요청에 대해 key=value 형태로 다양한 파라메터를 넘겨 매우 편리해졌다.

alarm-fft-3d.tql

SQL_SELECT(
    'time', 'value',
    from('fft', param('axis') ?? 'vib-y2'),
    between('last-' + (param('range') ?? '10s'), 'last')
)
MAPKEY( roundTime(value(0), '500ms') )
GROUPBYKEY()
FFT(minHz(0), maxHz(1000))
FLATTEN()
PUSHKEY('fft')
PUSHVALUE(0, list(value(0), value(1), value(2)))
POPVALUE(1, 2, 3)

CHART(
    plugins("gl"),
    chartOption(strSprintf(`{
        "xAxis3D": {
            "name": "time",
            "type": "time",
            "show": true,
        },
        "yAxis3D": {
            "name": "Hz",
            "type": "value",
            "show": true
        },
        "zAxis3D": {
            "name": "Amp",
            "type": "value",
            "show": true
        },
        "grid3D": {
            "boxWidth": 70,
            "boxHeight":70,
            "boxDepth": 70,
            "left":  "10%%",
            
            "viewControl": {
                "projection": "orthographic",
                "autoRotate": false,
                "speed": 0
            }
        },
        "title": {
            "text": "%s",
            "left": 'center'

        },
        "visualMap": [{
            "type": "continuous",
            "calculable": true,
            "min": 0,
            "max": 10,
            "inRange": {
                "color": ["#313695", "#4575b4", "#74add1", "#abd9e9", "#e0f3f8", "#ffffbf", "#fee090", "#fdae61", "#f46d43", "#d73027", "#a50026"]
            }
        }],
        "tooltip": {
            "show": true,
            "trigger": "axis"
        },
        "series": [{
            "type": "bar3D",
            "coordinateSystem": "cartesian3D",
            "data":column(0),
            "shading": "lambert",
            "itemStyle": {
                "opacity": 1
            }
        }]  
    }`, param('title') ?? "X axis FFT in 3D"))
)

차트 생성시 파라메터 지정 화면

아래와 같이 차트 타입을 TQL로 하면 화일 지정 및 파라메터를 넘길 수 있게 되었다. 이 파라메터는 TQL 내부에서 param('키') 형태로 값을 받아 동작한다.


6. 진동 데이터 통계 지표 대시보드 (alarm-data.dsh)

이 통계 지표 별도의 HTML 없이 역시 대시보드에서 모두 설정가능하다.

첫번째줄 3개의 차트는 통계 수치들이고, 두번째줄 차트 3개(X, Y, Z Alarm)는 주어진 범위를 벗어난 알람을 화면에 나타낸 것이다. 이 대시보드에서는 3초에 한번씩 alarm-data-stat.tql과 alarm-peak-insert.tql, alarm-peak-publish-post.tql을 주기적으로 실행한다.

이 알람차트는 정상의 경우에는 0으로 출력이 되고, 알람 상황의 경우에는 해당 값으로 출력된다. 확률적으로 대략 5~10초에 1번씩 알람이 발생할 것이다. 이 데모에서는 Peak의 값에 대해서 알람을 발생시키도록 하였으며, 각각의 축에 대한 알람에 대한 상세 내역은 다음과 같다.

동작 정보

X 축

Y 축

Z 축

데이터 생성 TQL

alarm-insert-noise-x.tql

alarm-insert-noise-y.tql

alarm-insert-noise-z.tql

알람 전달 방식

alarm_history 테이블에 알람 내역 입력

alarm_history 테이블에 알람 내역 입력

Web Hook으로 알람 이벤트 전송

알람 조건 (차트 생성시 파라메터로 입력)

X 축의 Peak 값이 200보다 큰 경우

Y축의 Peak 값이 300보다 큰 경우

Z축의 Peak 값이 100보다 큰 경우

알람 감지 TQL

alarm-peak-insert.tql

alarm-peak-insert.tql

alarm-peak-publish-post.tql

아래는 해당 대시보드 동작화면이며, Y 축의 peak 알람이 발생한 순간을 확인할 수 있다.


7. 알람이 발생하면?

위에서 언급한 바와 같이 실제로 알람이 발생하면, X축 Y축의 경우 alarm_history 테이블에 입력하고, Z 축은 웹훅으로 이벤트를 전송한다고 했었다. 아래와 같은 코드를 통해 이것이 수행되는데, 다음과 같다.


alarm-peak-insert.tql (X 혹은 Y 축)

아래의 코드에서 GROUP() 함수를 통해 1초 범위내에서 최대 값을 구한 후에 param('peak')으로 받은 파라메터 값보다 클 경우 WHEN() + INSERT() 구문을 통해 로그 테이블에 해당 데이터를 입력한다.

SQL_SELECT(
    'time', 'value',
    from('fft', param('axis') ?? 'vib-y2'),
    between(strSprintf('now-%s', param('range') ?? '20s') , 'now')
)
GROUP(
    by( value(0),
        timewindow(time(strSprintf('now-%s', param('range') ?? '20s')), time('now'), period(param('precision') ?? "1s")),
        "TIME"
    ),
    max( value(1),
          "PEAK ALARM"
    )
)
SET(PEAK, parseFloat(param('peak') ?? 100))
MAPVALUE(0, strTime(value(0), 'DEFAULT', tz('Local')))

WHEN(value(1) > parseFloat(param('peak') ?? 100),
      do( value(0), param('axis'), value(1), {
        ARGS()
        MAPVALUE(3, strSprintf("%s peak alarm!..: more than %.2f", param('axis'), parseFloat(param('peak') ?? 100)))
        INSERT("time", "name", "value", "desc", table("alarm_history"))
        }
      )
    )

MAPVALUE(1, value(1) > $PEAK ? value(1) : 0)

CHART(
..생략 
)

alarm-peak-publish-post.tql (Z 축)

아래 코드는 Z축에 알람이 발생했을 때, 구글 스페이스로 (XXX 키는 보안 이슈로 이 블로그에서 생략했다) 알람 메시지를 POST로 전송하는 코드이다.

SQL_SELECT(
    'time', 'value',
    from('fft', param('axis') ?? 'vib-z2'),
    between(strSprintf('now-%s', param('range') ?? '20s') , 'now')
)
GROUP(
    by( value(0),
        timewindow(time(strSprintf('now-%s', param('range') ?? '20s')), time('now'), period(param('precision') ?? "1s")),
        "TIME"
    ),
    max( value(1),
          "PEAK ALARM"
    )
)
MAPVALUE(0, strTime(value(0), 'DEFAULT', tz('Local')))

WHEN(value(1) > parseFloat(param('peak') ?? 100),
      doHttp("POST", 
      "https://chat.googleapis.com/v1/spaces/AAAA6WEdswk/messages?key=XXXX",
        strSprintf(`{"text": "%s peak alarm (%.2f)!..: more than %.2f"}`, param('axis') ?? "no tag", value(1), parseFloat(param('peak') ?? 100)),
        "Content-Type: application/json",
        "X-Custom-Header: notification" )
    )

MAPVALUE(1, value(1) > parseFloat(param('peak') ?? 100) ? value(1) : 0)

CHART(
   .. 생략 
)

실제 전송된 화면은 아래와 같다.


8. 알람 발생 내역 시각화 (alarm-peak-history.dsh)

이 대시보드는 scatter 차트를 활용하여, 현재 alarm-history 테이블에 저장되고 있는 알람 내역을 실시간으로 화면에 출력한다. Z 축의 경우 내역을 로그 테이블에 남기지 않기 때문에 이 대시보드에서는 존재하지 않는다.


마치면서

이번 블로그에서는 고속 푸리에 분석 시리즈 두번째로서 실시간 알람을 설정하고, 이를 처리하는 데모를 만들어보았다. 이 글의 목적상 특정 축의 Peak 통계 수치에 대해서만 알람을 설정하고, 처리하였지만, 다른 통계 값인 RMS 혹은 기타 통계 값에 대해서도 동일한 방법으로 얼마든지 추가하고, 확장할 수 있다는 것을 말씀드린다.


마지막으로 이와 관련되어 더 궁금한 점이나 필요한 것들에 대해서는 아래의 링크를 통해 마크베이스로 연락주시면, 친절하게 대답드릴 것을 약속드리며 FFT 분석 및 알람 처리에 관한 글을 마치도록 하겠다.











조회수 61회

최근 게시물

전체 보기
bottom of page