저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의

관련 글


컨텍스트


주식 종목 별로 최근 10분 동안의 평균 가격을 구하려면 다음과 같이 group by 사용햇다.


select code, avg(cost) as avg from StockTick.win:time(10 min) group by code


컨텍스트를 만들면 group by를 사용하지 않고 이벤트를 분류해서 분류된 파티션 별로 결과를 만들어 낼 수 있다. 컨텍스트는 이벤트를 컨텍스트 파티션으로 분류해 주며, EPL에 컨텍스트를 적용하면, 한 개의 EPL을 컨텍스트 파티션 별로 적용할 수 있다. 따라서, 주식 코드 별로 컨텍스트 파티션을 생성하고, 이 컨텍스트를 이용해서 EPL을 실행하면 group by 등의 쿼리를 사용하지 않아도 종목 별로 평균이나 추이 등을 분석할 수 있다.


컨텍스트 만들고 사용하기


컨텍스트는 다음과 같은 문장을 이용해서 생성한다.


epService.getEPAdministrator().createEPL(

       "create context CodeSegment partition by code from StockTick");


위 코드는 StockTick 이벤트를 code 값으로 분류하는 CodeSegment라는 컨텍스트를 생성한다. 값으로 파티션을 생성하는 방식과 해시값으로 파티션을 생성하는 방식 등 몇 가지 종류의 컨텍스트를 제공하는데, 이에 대한 내용은 뒤에서 다시 정리해 본다.


파티션을 생성했다면, EPL에서 다음과 같이 context 절을 이용해서 컨텍스트를 적용할 수 있다.


EPStatement eps = epService.getEPAdministrator().createEPL(

        "context CodeSegment " +

        "select code, avg(cost) as avg from StockTick.win:time(3 sec) ");


위 코드는 CodeSegment 컨텍스트를 사용하는데, 이 경우 각 EPL은 code 값으로 분류된 파티션 별로 적용된다. 따라서, 위 EPL의 select는 각 code 별로 최근 3초 시간 윈도우의 평균 값을 생성한다.


컨텍스트 종류

  • 키 기반 컨텍스트
  • 해시 키반 컨텍스트
  • 카테고리 컨텍스트
  • 논오버래핑 컨텍스트
  • 오버래핑 컨텍스트


컨텍스트 종류: 키 기반 컨텍스트


키 기반 컨텍스트는 이벤트의 특정 프로퍼티를 이용해서 이벤트를 파티션으로 분류한다. partition by 를 이용해서 파티션 키를 지정한다.


create context CodeSegment partition by code from StockTick


두 개 이상의 키를 사용할 수도 있다.


create context AccessLogSegment partition by domain and sesessionId from AccessLog


여러 이벤트를 이용해서 컨텍스트를 생성할 수도 있다.


create context CodeSegment2 partition by 

compCode from Announcement, code from StockTick


위 컨텍스트는 StockTick 이벤트와 Announcement 이벤트를 이용해서 컨텍스트를 생성한다. 각 이벤트에서 지정한 프로퍼티 개수와 타입은 동일해야 한다. 위 컨텍스트 내에서 실행되는 EPL은 코드는 같은 회사 코드를 갖는 주가와 공시 이벤트를 묶어서 처리할 수 있게 된다. 예를 들어, 아래 EPL에서 StockTick 이벤트와 Announcement 이벤트는 이미 동일한 회사 코드를 갖고 있으므로, code와 compCode가 같은지 여부를 비교할 필요가 없다.


context CodeSegment

select s from Announcement.win:time(10 min) a, StockTick t

where t.rate > 10


컨텍스트 종류: 해시 기반 컨텍스트


해시 기반 컨텍스트는 지정한 프로퍼티의 해시 값을 이용해서 이벤트를 분류한다. 해시 기반 컨텍스트를 사용하려면 다음과 같이 coalesce by와 해시 함수를 함께 사용하면 된다.


create context CodeSegment coalesce by

consistent_hash_crc32(code) from StockTick granularity 32

preallocate


consistent_hash_crc32는 CRC 32 알고리즘을 이용해서 해시 코드를 생성한다. hash_code를 사용하면 자바의 해시 코드를 이용한다. granularity는 파티션의 최대 개수를 지정하며, preallocate를 사용하면 미리 파티션을 생성해 놓는다. 키 기반 컨텍스트와 마찬가지로 여러 이벤트를 이용해서 정의할 수 있다.


위 코드를 이용해서 생성한 해시 기반 컨텍스트는 하나의 파티션에 한 개 이상의 code가 존재하게 됨에 유의하자.


컨텍스트 종류: 카테고리 컨텍스트


카테고리 컨텍스트는 프로퍼티를 이용해서 카테고리를 생성하고 이를 기준으로 분류한다. 다음은 카테고리 컨텍스트의 생성 예를 보여주고 있다. "group 표현식 as 카테고리이름"을 이용해서 카테고리를 정의한다.


create context AccessLogCategory

group responseTime <= 1000 as normal,

group responseTime > 1000 and responseTime <= 2000 as slow,

group responseTime > 2000 as tooslow

from AccessLog


카테고리 기반 컨텍스트는 이벤트를 몇 개의 카테고리로 나눈다. 위 코드는 3개의 파티션을 생성하는데, 이벤트는 응답 시간에 따라 normal 파티션, slow 파티션, tooslow 파티션에 속하게 된다.


이 컨텍스트를 사용하는 EPL은 다음과 같이 context.label을 이용해서 카테고리 이름을 구할 수 있다.


context AccessLogCategory

select context.label from AccessLog


컨텍스트 종류: 논오버래핑 컨텍스트


논오버래핑 컨텍스트는 시작과 끝 조건에 따라 컨텍스트가 시작되거나 끝나는 컨텍스트이다. 컨텍스트가 시작되면 끝 조건 전까지 컨텍스트 파티션은 1개만 존재한다. 끝 조건을 충족해서 컨텍스트가 끝나면 파티션은 0개가 된다.


논오버래핑 컨텍스트는 start와 end를 이용해서 시작 조건과 끝 조건을 지정한다. 시작 조건과 끝 조건에는 이벤트, 패턴, 그론탭, 시간 간격 등이 올 수 있다. 다음은 레퍼런스 문서에 있는 몇 가지 예이다.

  • create context NineToFive start (0, 9, *, *, *) end (0, 17, *, *, *)
    크론탭 표현식을 이용한 시작/종료 시점을 지정한다. 9시에 컨텍스트를 시작하고 17시에 끝낸다.
  • create context PowerOutage start PowerOutageEvent end pattern [PowerOnEvent -> timer:interval(5 sec)]
    PowerOutageEvent가 발생하면 컨텍스트를 시작하고, PowerOnEvent 발생 후 5 초가 지나면 끝낸다.
  • create context Every15minutes start @now end after 15 min
    @now는 지금을 의미하는 어노테이션으로, 컨텍스트는 지금 시작해서 15분 후에 끝낸다. 컨텍스트가 종료되면 @now에 의해 다시 컨텍스트가 바로 시작.
시작 조건은 @now 또는 크론탭이나 이벤트 필터 등의 표현식이 온다.

EPL에서는 context.startTime과 context.endTime을 이용해서 컨텍스트의 시작 시작과 끝 시간을 구할 수 있다.

컨텍스트 종류: 오버래핑 컨텍스트


오버래핑 컨텍스트는 시작과 종료 조건을 지정하는 건 논오버래핑 컨텍스트와 같다. 차이점이 있다면, 오버래핑 컨텍스트는 시작 조건을 충족할 때 마다 새로운 컨텍스트 파티션을 생성한다는 점이다. initiated와 terminated를 이용해서 컨텍스트 시작과 종료 조건을 지정한다. 다음은 레퍼런스 문서에 있는 몇 가지 예이다.

  • create context CtxTrainEnter initiated TrainEventEnter as te terminated after 5 min
    TrainEventEnter 이벤트가 들어올 때 마다 새로운 컨텍스트를 시작하고, 5분이 지나면 해당 컨텍스트를 종료한다.
  • create context CtxEachMinute initiated @now and pattern [every timer:interval(1 min)] terminated after 1 min
    "@now and"는 컨텍스트를 즉각 시작하고, 시작 조건이 충족되면 새로운 컨텍스트를 시작한다. 위 EPL은 컨텍스트를 즉각 시작하고, (1분이 지나면 조건을 충족하므로) 매 1분이 지날 때 마다 컨텍스트를 시작한다. 각 컨텍스트는 1분 이후에 종료된다.
  • create context OrderContext initiated distinct(orderId) NewOrderEvent as newOrder terminated CloseOrderEvent(closeOrderid = newOrder.orderId)
    NewOrderEvent가 들어오면 컨텍스트를 시작한다. 단, orderId를 기준으로 컨텍스트가 이미 존재하면 새로운 컨텍스트는 시작되지 않는다.

컨텍스트 종료시 결과 생성하기


output을 사용하면 컨텍스트가 종료될 때 결과를 생성할 수 있다. 다음처럼 output - when terminated 구문을 사용하면 된다.


context CtxEachMinute select avg(temp) from sensorEvent output snapshot when terminiated



컨텍스트를 사용할 때의 장점


레퍼런스 문서에 따르면 컨텍스트 사용시 다음과 같은 장점이 있다고 한다.

  • 하나의 컨텍스트를 여러 EPL에 적용할 수 있으므로, 그룹핑하기 위한 중복 부분을 제거할 수 있다.
  • EPL을 더 읽기 쉽게 만들어준다.
  • 두 개 이상의 컨텍스트를 조합(중첩)할 수 있다.
  • 파티션이 시간 상 겹칠 수 있다.
  • 엔진이 컨텍스트 단위로 락을 관리하기 때문에 동시성을 높일 수 있다.


Posted by 최범균 madvirus

댓글을 달아 주세요

관련 글


Insert into를 이용한 이벤트 발생

EPL은 insert into 를 사용해서 새로운 이벤트를 발생시킬 수 있다. insert into의 사용 방법은 다음과 같이 단순하다.

insert into SlowResponse
select url, responseTime as respTime from AccessLog where responseTime > 500

위 코드는 AccessLog 이벤트의 responseTime 프로퍼티가 500보다 크면 url과 responseTime 프로퍼티를 선택하고, 선택된 프로퍼티를 이용해서 새로운 SlowResponse 이벤트를 생성한다.

insert into를 이용해서 생성하는 이벤트는 select에서 선택한 프로퍼티와 동일한 프로퍼티를 갖고 있어야 한다. 예를 들어, 위 코드의 경우 SlowResponse 이벤트는 url과 respTime 프로퍼티를 갖고 있어야 한다. (SlowResponse 이벤트를 자바 클래스로 정의했다면 setUrl()과 setRespTime() 메서드를 필요로 한다.)

insert into를 이용해서 생성한 이벤트는 일반적인 다른 이벤트와 마찬가지로 EPL을 이용해서 사용할 수 있다. 예를 들어, 위 코드로 생성한 SlowResponse 이벤트는 다음의 EPL에서 사용할 수 있게 된다.

select count(*) as count from SlowResponse.win:time(2 sec) having count(*) > 3


SlowResponse 클래스가 다음의 생성자를 갖고 있다고 하자.


public class SlowResponse {

    private String url;

    private long respTime;


    public SlowResponse(String url, long respTime) {

        this.url = url;

        this.respTime = respTime;

    }


이 경우, inserto into에서는 생성자를 이용할 수 있다.


insert into SlowResponse(url, responseTime)

select url, responseTime from AccessLog where responseTime > 500


조인(Join)


Esper는 스트림 간의 조인을 제공한다. RDMBS의 조인과 유사하며, 내부 조인과 외부 조인을 함께 지원하고 있다. RDBMS와의 차이가 있다면, EPL에서의 조인은 제한된 뷰에서의 조인을 지원한다는 점이다. (win:time 이나 win:length 등이 뷰에 해당하며, Esper는 앞서 Esper 초보 시리즈 2 - EPL 기초 에서 봤던 시간/길이 윈도우 외에 다양한 뷰를 제공하고 있다.)


아래 코드는 두 스트림의 조인을 이용한 코드의 예이다.


EPStatement eps = epService.getEPAdministrator().createEPL(

  "select v, o from ProductView.win:time(2 sec) as v, ProductOrder.win:time(2 sec) as o "

);

eps.addListener(new UpdateListener() {

    @Override

    public void update(EventBean[] newEvents, EventBean[] oldEvents) {

        for (EventBean eb : newEvents) {

            ProductView pv = (ProductView) eb.get("v");

            ProductOrder po = (ProductOrder) eb.get("o");

        }

    }

});


위 코드는 1분 동안 발생한 ProductView와 ProductOrder 이벤트를 조인한 결과를 select 하고 있다.



위 그림에서 ProductView 이벤트가 1초, 1.5초, 2.5초 시점에 발생하고, ProductOrder 이벤트는 2초와 3.3초 시점에 발생했다. 앞서 EPL에서 두 이벤트에 대해 동일하게 2초의 시간 윈도우를 지정했는데, 이 경우 각 시점에 select로 조회되는 조인 결과는 다음과 같다.

  • 주황색(2초 시점): O1 이벤트가 발생하고, 그 결과로 (V1, O1), (V2, O2) 조인 결과가 select 된다.
  • 녹색(2.5초 시점): V3 이벤트가 발생하고, 그 결과로 (V3, O1) 조인 결과가 select 된다.
  • 빨강색(3.3초 시점): O2 이벤트가 발생하고, 그 결과로 (V2, O2), (V3, O2) 조인 결과가 select 된다.
where 절을 이용해서 조인 조건을 정의할 수도 있다.

select v, o from ProductView.win:time(2 sec) as v, ProductOrder.win:time(2 sec) as o
where v.productId = o.productId and v.userId = o.userId

외부 조인을 사용할 수도 있다. 다음은 외부 조인의 사용예이다. SQL과 동일하게 on 절을 이용해서 조건을 지정한다.


select v, o 

from ProductView.win:time(2 sec) as v 

left outer join ProductOrder.win:time(2 sec) as o 

on v.productId = o.productId and v.userId = o.userId 


leff 외에 right, full 외부 조인을 지원한다.


아래 그림은 다음의 쿼리를 실행했을 때 결과를 정리한 것이다.

  • select v, o from ProductView.win:time(2 sec) as v 
    left outer join ProductOrder.win:time(2 sec) as o 


이 그림에서 회색 상자는 특정 시점에 select 결과로 전달받은 데이터를 표시한 것이다. V1 이벤트가 발생한 시점을 기준으로 아직 ProductOrder 이벤트가 존재하지 않으므로 select 결과는 (V1, null)이 된다. 비슷하게 V2 이벤트 발생 시점에도 (V2, null)을 결과로 받는다. O1 이벤트가 발생하는 시점이 되면, EPL에서 지정한 시간 윈도우 범위 안에 V1, V2 이벤트가 조인할 수 있는 O1 이벤트가 생기게 되고, 따라서 select 결과로 (V1, O1), (V2, O1)을 받게 된다.


외부 조인을 사용하면 좀 더 재미난 쿼리를 만들 수 있다. 아래 코드를 보자.


select rstream v

from ProductView.win:time(1 min) as v

left outer join ProductOrder.win:time(1 min) as o

on v.productId = o.productId and v.userId = o.userId

where o is null


위 EPL은 select 절에 rstream을 사용했는데, 이렇게 하면 데이터가 윈도우를 벗어날 때 쿼리를 실행하게 된다. 따라서, 위 코드는 ProductView 이벤트가 발생한 뒤로 1분 안에 동일 제품/동일 사용자의 ProductOrder 이벤트가 발생하지 않으면 해당 ProductView 이벤트를 select 결과로 받게 된다. 이 때, UpdateListener는 아래 코드와 같이 oldEvents 파라미터를 이용해서 결과를 얻을 수 있다.


EPStatement eps = epService.getEPAdministrator().createEPL(

        "select rstream v "+

                "from ProductView.win:time(2 sec) as v "+

                "left outer join ProductOrder.win:time(2 sec) as o " +

                "on v.productId = o.productId and v.userId = o.userId "+

                "where o is null"

);


eps.addListener(new UpdateListener() {

    @Override

    public void update(EventBean[] newEvents, EventBean[] oldEvents) {

        for (EventBean eb : newEvents) {

            ProductView pv = (ProductView) eb.get("v");

            ...

        }

    }

});


조인이 유용하지만, 좀 더 상황이 복잡할 경우에는 패턴을 사용해서 풀어낼 수 있다. 패턴 대한 내용은 이후에 따로 정리할 예정이다.


서브 쿼리


Esper는 서브 쿼리도 지원한다. 서브 쿼리는 select, where, having, 필터 등에서 사용할 수 있다. 다음은 서브 쿼리의 예다.


select count(*) as count, (select max(value) from Tps.win:time(5 sec)) as maxTps

from SlowResponse.win:time(3 sec) s having count(*) > 3


다음은 where 절에서 서브 쿼리를 사용하는 예이다.


select * from SlowResponse.win:time(4 sec) s

where s.responseTime > (select avg(responseTime) from SlowResponse.win:time(2 sec))


where 절에서 exists, in, not in, any, all 등을 사용할 수도 있다. 다음은 exists를 사용한 예이다.


select rstream v from ProductView.win:time(10 min) as v 

where not exists 

        (select * from ProductOrder.win:time(10 min) as o 

         where v.productId = o.productId and v.userId = o.userId)


위 코드는 ProductView 이벤트의 시간 윈도우에서 벗어나는 이벤트에 대해 그 이벤트와 매칭되는 ProductOrder 이벤트가 존재하지 않는지 확인한다. 즉, 상품을 본 후 10초 이내에 주문을 하지 않은 ProductView 이벤트를 구한다. (이 결과는 앞에서 left outer join 사용 예에서의 결과와 동일하지만, 조인의 경우보다 이해하기가 더 쉬운 것을 알 수 있다.)



Posted by 최범균 madvirus

댓글을 달아 주세요

Esper를 잘 사용하려면 EPL을 잘 사용해야 할 것 같다. EPL 레퍼런스가 워낙 양이 많아 다 읽어보지 못했지만, Esper를 학습하는 진입 지점으로 사용할 수 있도록 몇 가지 기초적인 내용을 좀 추려보았다.


관련 시리즈:


EPL 기본 구조


EPL의 기본 구조 및 EPL이 선택한 데이터를 UpdateLister에서 접근할 때 사용하는 코드는 아래와 같다.


// 모든 이벤트를 선택

select * from StockTick


// UpdateListener의 구현 코드

EPStatement eps = epService.getEPAdministrator().createEPL("select * from StockTick");

eps.addListener(new UpdateListener() {

    @Override

    public void update(EventBean[] newEvents, EventBean[] oldEvents) {

        StockTick pe = (StockTick) newEvents[0].getUnderlying();

        ...

    }

});


select에서 선택한 이벤트는 UpdateListener.update() 메서드의 newEvents 파라미터로 전달된다. 위 코드의 EPL의 경우 한 번에 한 개의 이벤트만 선택되기 때문에, newEvents에는 길이가 0인 배열에 전달된다.


EventBean은 선택한 이벤트에 접근할 수 있는 몇 가지 메서드를 제공하고 있는데, 위 EPL과 같이 select 절에서 "*"을 사용한 경우 getUnderlying() 메서드를 이용해서 데이터에 접근할 수 있다.


다음은 이벤트에서 특정 프로퍼티만 사용하는 경우의 코드이다.


// 모든 이벤트의 특정 프로퍼티를 선택

select name, code, cost from StockTick


// UpdateListener.update 메서드

public void update(EventBean[] newEvents, EventBean[] oldEvents) {

    String name = (String) newEvents[0].get("name");

    String code = (String) newEvents[0].get("code");

    int cost = (Integer) newEvents[0].get("cost");

    ...

}


where 절을 이용해서 특정 조건을 충족하는 이벤트를 선택할 수 있다.


// 특정 조건을 충족하는 이벤트를 선택

select * from StockTick where rate > 10 and cost > 5000


다음처럼 from 절의 이벤트나 select 절에서 별칭을 사용할 수 있다.


// 별칭 사용

select s.name, s.code, s.cost as cost from StockTick s where s.rate > 10 


// UpdateListener.update 메서드

public void update(EventBean[] newEvents, EventBean[] oldEvents) {

    String name = (String) newEvents[0].get("s.name");

    String code = (String) newEvents[0].get("s.code");

    int cost = (Integer) newEvents[0].get("cost");

}


위 코드에서 눈여겨 볼 점은 select 절에서 "별칭.프로퍼티" 이름으로 프로퍼티의 경우 EventBean.get() 메서드에서도 그대로 "별칭.프로퍼티이름"을 사용한다는 것이다. 이 형식을 사용하기 싫으면 s.cost의 경우처럼 as 를 이용해서 이름을 변경하면 된다.


윈도우


최근 10분간 발생한 모든 주문의 합을 실시간으로 추적하고 힢다면 어떻게 해야 할까? 또는 가장 최근에 발생한 20개의 이벤트 중 가장 큰 값을 구하려면 어떻게 해야 할까? 이럴 때 사용할 수 있는 게 윈도우이다. 윈도우는 일정 시간 동안 또는 일정 개수의 이벤트를 메모리에 보관하고, EPL에서는 이 윈도우에 보관된 이벤트 목록를 대상으로 결과를 뽑아낼 수 있다.


주요 윈도우는 다음의 네 가지가 있다.

  • 시간 윈도우
  • 시간 배치 윈도우
  • 길이 윈도우
  • 길이 배치 윈도우


시간(time) 윈도우


시간 윈도우는 이벤트가 발생한 기준으로 지정한 시간 동안만 이벤트를 보관한다. 예를 들어, 지정 시간이 1분이라면, 마지막에 발생한 이벤트를 기준으로 최근 1분 사이에 발생한 이벤트만 윈도우에 보관된다. 지정한 시간이 지나면 이전 이벤트는 윈도우에서 제거된다. 아래 그림은 시간 윈도우의 동작 방식을 그림으로 부여주고 있다.


* 출처: http://esper.codehaus.org/


위 그림에서 윈도우 크기를 4초로 잡았다고 가정하자. W1 이벤트는 t+4초 시점에 발생했다. 이 시점에서 윈도우에는 W1 이벤트만 보관되어 있다. 위 그림에서 둥근 사각형이 윈도우를 의미한다. t+5초 시점에 W2 이벤트가 발생했고, 이 시점에 윈도우에는 W2, W1 이벤트가 보관된다. t+8초 시점에 되면 W1은 윈도우에서 제거된다.


[참고]

윈도우에서 제거된 이벤트는 UpdateListener를 통해서 받을 수 있는데, 그러기 위해서는 select 구문에서 rstream 키워드를 사용해야 한다. 이에 대한 내용이 궁금하다면, EPL 레퍼런스 문서를 확인해보도록 하자.


시간 윈도우를 정하는 방식은 아래와 같다.


select * from StockTick.win:time(5 seconds) s where s.cost > 100


보통은 위와 같이 단순히 select를 하기 보다는 그룹 함수와 함께 사용되는 경우가 많다. 예를 들어, 아래 코드는 최근 30초간 발생한 StockTick 이벤트를 code로 그룹핑 한 뒤, 각 code별로 최대 cost 및 StockTick을 구한다.


select s as tick, max(cost) as maxCost from StockTick.win:time(30 seconds) s group by s.code


시간 배치 윈도우


시간 배치 윈도우는 일정 간격으로 이벤트를 모아서 처리할 때 사용한다. 시간 배치 윈도우의 동작 방식은 아래 그림과 같다.


* 출처: http://esper.codehaus.org/


시간 배치 윈도우는 지정한 시간동안 들어온 이벤트를 윈도우에 보관한다. 시간 배치가 종료되는 시점에 select 결과를 전달하고, 다시 새로운 시간 배치를 시작한다.


시간 배치를 지정할 때에는 아래 코드처럼 win:time() 뷰를 사용한다.


select s as tick, max(cost) as maxCost from StockTick.win:time_batch(30 seconds)

group by s.code


시간 배치 윈도우를 사용할 때 유의할 점은 타임 배치 내에 이벤트가 들어오지 않으면 null을 발생하고, 그 다음 타임 배치에서도 이벤트가 들어오지 않으면 null을 발생하지 않는다는 점이다. 예를 들어, 3초 타임 배치를 사용하는 "select max(cost) from StockTick.win:time_batch(3 sec)" EPL을 사용하고, 다음의 시간 순으로 이벤트가 들어왔다고 하자.

  • 1초(100) -> 4초(200) -> 13초(500) -> 이후 안 들어옴
이 경우 3초 타임 배치에 의해 생성되는 결과는 다음과 같다.
  • 3초(100) -> 6초(200) -> 9초(null) -> 15초(500) -> 18초(null)


길이 윈도우


시간 윈도우가 지정한 시간만큼 윈도우에 보관된다면, 길이 윈도우는 지정한 개수 만큼의 이벤트만 윈도우에 보관한다. 동작 방식은 아래 그림과 같다.


* 출처: http://esper.codehaus.org/


위 그림은 길이가 5인 길이 윈도우의 경우를 보여주고 있는데, 이 경우 윈도우에는 최대 5개의 이벤트만 보관된다. 이미 5개의 이벤트가 윈도우에 보관된 상태에서 새로운 이벤트가 추가되면, 가장 오래된 이벤트가 윈도우에서 제거된다.


길이 윈도우를 사용하는 방법은 다음과 같다.


select s as tick, max(cost) as maxCost from StockTick.win:length(5) s group by code


길이 배치 윈도우


시간 배치 윈도우와 비슷하게 길이 배치 윈도우는 지정한 길이 만큼 이벤트가 차면, select 결과를 발생시키고 새로운 배치를 시작한다. 길이 배치는 win:length_batch()를 사용해서 지정한다.


select s as tick, max(cost) as maxCost from StockTick.win:length_batch(5) s group by code


필터


where 절 외에 이벤트에 조건을 거는 또 다른 방법으로 필터가 있다. 필터를 사용하면 다음과 같이 이벤트의 조건을 지정할 수 있다. 필터를 사용할 때에는 아래 코드처럼 이벤트 이름 뒤에 괄호를 사용해서 지정한다.


select * from StockTick(cost > 2000).win:length(3) 


where 절과 필터가 이벤트의 조건을 확인하는 건 동일하지만, 다음의 중요한 차이점이 있다.

  • 필터 조건을 충족하지 않는 이벤트는 윈도우에 포함되지 않는다.
  • where 조건을 충족하지 않는 이벤트는 일단 윈도우에 포함되고 그 다음에 select 과정에서 where 조건을 검사한다.
예를 들어, 길이가 3인 윈도우를 만들고 위 코드와 같은 필터를 사용한 상태에서 다음의 순서로 이벤트를 발생시켰다고 해 보자.
  • cost=3000, cost=1000, cost=2000, cost=4000, cost=5000, cost=6000 (왼쪽부터 이벤트가 들어온다고 가정)
이 경우, cost가 1000이거나 2000인 이벤트는 애초에 윈도우에 들어가지 않는다. 따라서, cost가 5000인 이벤트가 들어온 시점에 길이 윈도우에는 다음과 같은 순서로 이벤트가 들어가게 된다.
  • [cost=5000, cost=4000, cost=3000] (왼쪽이 최신)
반면에 필터를 사용하지 않고 "select * from StockTick.win:length(3) where cost > 2000" 코드를 실행했다면, cost가 5000인 이벤트가 들어온 시점에 윈도우에는 다음의 이벤트가 남아 있게 된다.
  • [cost=5000, cost=4000, cost=2000] (왼쪽이 최신)


그룹핑과 집합 연산


EPL도 SQL과 유사하게 count() 등의 집합 관련 함수와 group by/having를 이용한 그룹핑을 지원하고 있다. Esper가 제공하는 함수 목록은 레퍼런스 문서를 찾아보도록 하고, 여기서는 기본적인 동작 방식 위주로 설명할 것이다.


아래 코드는 집합 함수의 사용 예이다. 지금까지 들어온 StockTick 이벤트에 대해서 최대 cost와 평균 cost를 구한다.


select max(cost) as max, avg(cost) as avg from StockTick


다음처럼 시간 윈도우를 이용해서 최근 3초간 최대 cost와 평균 cost를 구할 수도 있다.


select max(cost) as max, avg(cost) as avg from StockTick.win:time(3 sec)


group by는 SQL과 마찬가지로 이벤트의 프로퍼티를 이용해서 그룹핑을 할 때 사용된다. 예를 들어, 종목 코드 별로 현재까지 최대/평균을 알고 싶다면 다음과 같이 group by를 사용하면 된다.


select code, max(cost) as max, avg(cost) as avg from StockTick group by code


having을 사용하면 그룹 함수에 대한 선택 조건을 지정할 수 있다. (avg(), sum() 등의 그룹 함수는 where 절에서 제약할 수 없다.)


select code, avg(cost) as avg from StockTick group by code having avg(cost) > 2000


그룹핑과 윈도우 이동에 따른 결과 이벤트 발생 시점


아래 EPL을 보자.


select code, avg(cost) as avg from StockTick.win:time(3 sec) group by code


이 EPL은 code를 기준으로 그룹을 구성하고, 3초 시간 윈도우를 지정하였다.이 경우 윈도우 새롭게 이벤트가 들어오면 위 select 결과가 UpdateListener에 전달된다. 아래 그림은 이벤트 발생 시점과 UpdateListener과 호출되는 시점을 표시한 것이다. 이 그림을 보면 이벤트가 발생하자 마자 거의 바로 리스너가 호출되는 것을 확인할 수 있다. 이벤트가 들어오면 윈도우에 포함된 이벤트들의 cost 평균이 바뀌므로 리스너에 새로운 평균을 전달한다.



위 그림에서 주황색 상자는 5.5초 정도에서 3초 구간을 표시한 것인데, 5.5초 정도에는 새롭게 윈도우에 들어온 이벤트가 없음에도 리스너가 호출되는 것을 알 수 있다. 여기서 리스너가 호출된 이유는 윈도우에서 이벤트가 벗어났기 때문이다. 윈도우에서 이벤트가 벗어나면 윈도우의 평균값이 바뀌게 되고, 따라서 바뀐 평균을 리스너에 전달하는 것이다.


그룹핑과 그룹 함수에 따른 select 출력 개수


기본적으로 아래 코드는 1개 이벤트 당 1개의 출력을 생성한다.


select * from StockTick


그런데, group by 대상과 select 대상을 어떻게 잡느냐에 따라서 select로 발생하는 결과 개수가 달라진다. 다음표는 레퍼런스를 문서를 참고해서 몇 가지 상황 별로 이를 정리한 것이다. 배치가 아닌 시간 윈도우나 길이 윈도우를 사용할 경우 매 이벤트마다 결과가 발생되므로, 이해를 돕기 위해 시간 배치 윈도우를 기준으로 설명하였다.


조건 

예제 쿼리 

출력 개수 

그룹핑 없음, 그룹 함수 없음

select * from StockTick 

이벤트 당 1개 

그룹핑 없음

그룹 함수만 사용

select avg(cost) 

from StockTick.win:time_batch(3 sec)

배치 시간 당 1개 

그룹핑 없음,

그룹 함수와 프로퍼티 함께 사용

select code, avg(cost)

from StockTick.win:time_batch(3 sec)

이벤트 당 1개 

그룹핑 존재,

그룹 함수와 그룹핑 대상 프로퍼티만 사용 

select code, avg(cost)

from StockTick.win:time_batch(3 sec)

group by code

그룹 당 1개 

그룹핑 존재

그룹 함수/그룹핑 대상 프로퍼티 및 다른 프로퍼티 함께 사용

select name, code, avg(cost)

from StockTick.win:time_batch(3 sec)

group by code

이벤트 당 1개




Posted by 최범균 madvirus

댓글을 달아 주세요