Apache Flink — 스트림을 엔진으로 돌린다 · 패턴 인식 (MATCH_RECOGNIZE) · 이론
MATCH_RECOGNIZE — 행 여러 개에 걸친 패턴을 SQL 로 찾는다
한 줄 요약
MATCH_RECOGNIZE 는 키별로 이벤트 시간 순서대로 줄 세운 행들에 정규식 같은 패턴을 대어 보고, 맞은 구간마다 요약 행 하나를 내는 절이다. 무엇이 한 매치가 되는지는 패턴 자체보다 수량자의 탐욕 여부, AFTER MATCH SKIP 전략, WITHIN 시간 제한이 정한다.
왜 이게 필요했나
"같은 사용자가 로그인에 세 번 넘게 연달아 실패한 뒤 성공했다" 를 찾는다고 해 보자. GROUP BY 로는 실패 횟수는 세도 "연달아" 와 "그 뒤에 바로" 를 표현할 수 없다. 창 집계는 시간 칸을 잘라 버려서 칸 경계에 걸친 시도를 놓친다. 자기 조인을 여러 번 걸면 표현은 되지만, 스트림에서는 양쪽을 영원히 기억해야 하고(5모듈) 쿼리도 읽을 수 없게 된다.
필요한 것은 "행의 순서에 대한 조건" 이다. Flink 는 복합 이벤트 처리(CEP) 라이브러리를 이미 가지고 있었고, SQL 표준(ISO/IEC TR 19075-5:2016)에 들어온 행 패턴 인식을 그 위에 올려 MATCH_RECOGNIZE 로 내놓았다(공식 문서의 Pattern Recognition).
어떻게 동작하나
뼈대는 이렇다.
SELECT * FROM events MATCH_RECOGNIZE ( PARTITION BY user_id ORDER BY ts -- 키별로, 이벤트 시간 순서로 MEASURES FIRST(F.ts) AS first_fail, COUNT(F.ts) AS fails, S.ts AS ok_ts ONE ROW PER MATCH AFTER MATCH SKIP PAST LAST ROW PATTERN (F{3,} S) WITHIN INTERVAL '30' SECOND DEFINE F AS F.kind = 'FAIL', S AS S.kind = 'OK') AS T;- PARTITION BY · ORDER BY — 키별로 따로 찾는다. 문서는 나누지 않으면 전역 순서를 지키려고 병렬도 1 인 연산자가 된다고 경고한다.
ORDER BY의 첫 열은 오름차순 시간 속성이어야 한다. 이벤트 시간이면 행을 먼저 정렬해서 패턴 기계에 넣으므로, 도착 순서가 흐트러져도 결과는 이벤트 시간 순서를 따른다 — 단 워터마크보다 늦게 온 행은 정렬에 끼지 못하고 버려진다. - PATTERN · DEFINE — 패턴 변수와 수량자(
*+?{n}{n,}{n,m})로 정규식처럼 쓰고, 변수마다 조건을 준다. 이어 쓴 변수 사이에는 다른 행이 끼면 안 된다(엄격한 연속). 빈 매치가 가능한 패턴(A*)과 마지막 변수에 탐욕 수량자(A B*)는 허용되지 않는다. - MEASURES — 매치 하나가 내는 열.
FIRST·LAST로 변수에 걸린 행을 앞뒤에서 가리키고,COUNT·SUM같은 집계도 쓴다. 출력은PARTITION BY열 + MEASURES 열이다. 지금은ONE ROW PER MATCH만 지원한다.
탐욕과 비탐욕. 수량자는 기본이 탐욕(되도록 많이)이고 뒤에 ? 를 붙이면 비탐욕(되도록 적게)이다. 두 변수의 조건이 겹칠 때만 차이가 난다. 이 실습의 카드 시험 패턴 S T+ B 에서 T 는 "20 미만 결제", B 는 "10 이상 결제" 라 10–19 가 둘 다에 걸린다. 탐욕 T+ 는 20 미만 결제를 끝까지 먹고 그다음 행이 B 여야 하고, 비탐욕 T+? 는 하나를 먹은 뒤 B 가 될 수 있는 첫 행에서 끝낸다. 이 파일에서는 매치 수는 같은데 끝나는 행과 큰 결제 금액이 달라졌다.
AFTER MATCH SKIP. 매치 하나를 찾은 뒤 어디서 다시 찾을지다. SKIP PAST LAST ROW 는 매치의 마지막 행 다음에서 — 한 행은 많아야 한 매치에만 든다. SKIP TO NEXT ROW 는 매치의 시작 행 다음에서 — 실패가 다섯 번 이어진 뒤 성공하면 시작 행만 다른 매치가 세 개 나온다. SKIP TO LAST 변수·SKIP TO FIRST 변수 도 있다.
WITHIN. 첫 행과 마지막 행의 간격 제한이다(표준 밖의 Flink 확장). 넘은 후보는 버려지고, 문서 말대로 상태를 비울 근거가 되므로 스트림에서는 거의 늘 붙인다. 이 파드에서 재 보니 간격이 정확히 30초인 후보도 WITHIN INTERVAL '30' SECOND 에서 떨어졌다 — 경계는 "미만" 으로 생각하는 것이 안전하다. 또 하나 — MATCH_RECOGNIZE 는 table.exec.state.ttl 을 따르지 않는다. 상태를 줄이는 손잡이는 WITHIN 이다.
현장에서 만나는 모습
탐지 규칙은 거의 늘 "몇 번 이상 · 얼마 안에 · 그 뒤에" 모양이라 MATCH_RECOGNIZE 가 잘 맞는다. 대신 경보 개수가 규칙보다 AFTER MATCH 전략에 좌우된다는 것을 모르면 곤란해진다. 같은 패턴이 PAST LAST ROW 로는 경보 1건, TO NEXT ROW 로는 3건을 낸다. 경보 시스템이 "사건 수" 를 세는지 "매치 수" 를 세는지부터 맞춰야 한다.
두 번째는 늦은 행이다. 패턴은 이벤트 시간으로 정렬한 뒤 찾으므로 입력이 흐트러져도 괜찮아 보이지만, 정렬은 워터마크까지만 기다린다. 워터마크 지연을 0으로 두고 흐트러진 입력을 넣으면 늦은 행이 조용히 빠지고 매치가 줄어든다. 오류도 경고도 없다. 이 실습에서 같은 행을 순서만 흐트러뜨린 파일로 지연 10초와 0초를 비교한다.
세 번째는 끝나지 않는 패턴. 조건 없는 변수에 상한 없는 수량자를 걸면 모든 행이 그 변수에 걸려 매치가 끝나지 않고 상태만 쌓인다. 문서의 처방은 뒤 변수의 조건을 부정해 넣거나 비탐욕으로 바꾸거나 WITHIN 을 거는 것이다.
다음 실습에서 할 것
로그인·결제 사건 표를 만들어 종류별 건수로 확인한 뒤, 무차별 대입 패턴을 PAST LAST ROW · TO NEXT ROW · WITHIN 30초로 바꿔 가며 돌린다. 카드 시험 패턴을 탐욕·비탐욕으로 돌려 끝나는 행이 달라지는 것을 보고, 도착 순서를 흐트러뜨린 파일을 워터마크 지연 10초와 0초로 읽어 매치 수를 비교한 뒤, 모든 숫자를 보고서로 모은다. 채점기는 같은 규칙을 파이썬으로 다시 돌려 한 행씩 대조한다.