관련 글
- Esper 초보 시리즈 1 - 퀵스타트
- Esper 초보 시리즈 2 - EPL 기초
- Esper 초보 시리즈 3 - Output을 이용한 출력 제어
- Esper 초보 시리즈 5 - 패턴
- Esper 초보 시리즈 6 - 컨텍스트
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 된다.
외부 조인을 사용할 수도 있다. 다음은 외부 조인의 사용예이다. 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 사용 예에서의 결과와 동일하지만, 조인의 경우보다 이해하기가 더 쉬운 것을 알 수 있다.)