RouteOnAttribute 는 동적 속성으로 조건을 여러 개 만들고, Routing Strategy 에 따라 관계를 만든다.
| 전략 | 동작 |
|---|---|
| Route to Property name | 조건 이름마다 관계가 생긴다. 가장 흔히 쓴다 |
| Route to 'matched' if all match | 모든 조건이 참일 때 matched |
| Route to 'matched' if any matches | 하나라도 참이면 matched |
어느 조건에도 걸리지 않은 FlowFile 은 unmatched 로 간다. 이 관계를 auto-terminate 해 두면 조용히 사라지므로, 운영 흐름에서는 로그로 남기는 편이 낫다.
Route to Property name 을 쓰면 통과한 조건의 이름이 속성 RouteOnAttribute.Route 에 담긴다. 뒤에서 그 값을 그대로 쓸 수 있다. 예를 들어 조건 이름을 스키마 이름과 같게 지어 두면 ConvertRecord 로 넘길 스키마 이름을 따로 계산하지 않아도 된다.
RouteOnAttribute
조건 이름 order_master : ${filename:startsWith('aaa')}
조건 이름 order_detail : ${filename:startsWith('bbb')}
UpdateAttribute
schema.name = ${RouteOnAttribute.Route}
이후 ConvertRecord 의 Record Reader 에서 Schema Access Strategy 를 Use 'Schema Name' Property 로 두고 Schema Name 에 ${schema.name} 을 넣으면 Avro Schema Registry 에서 이름으로 스키마를 찾는다.
문자열 비교는 대소문자를 구분한다. 구분하지 않으려면 equalsIgnoreCase 를 쓴다.
${category:equalsIgnoreCase('include')}
여러 값 중 하나인지 보려면 or 를 이어 붙이거나 in 을 쓴다. in 이 훨씬 읽기 쉽다.
${category:equalsIgnoreCase('include'):or(${category:equalsIgnoreCase('included')})}
${category:in('include', 'included', 'INCLUDE')}
in 은 대소문자를 구분하므로, 구분 없이 비교하려면 ${category:toLower():in('include','included')} 처럼 한쪽을 맞춰 준다.
여러 조건을 모두 만족해야 하면 and 로 묶는다.
${filename:contains('POH'):and(${filename:contains(${now():format('yyyyMMdd')})})}
| 함수 | 동작 |
|---|---|
matches |
문자열 전체 가 정규식과 맞아야 참 |
find |
문자열 일부에 정규식이 있으면 참 |
contains |
단순 부분 문자열 포함 |
startsWith · endsWith |
접두·접미 비교 |
^G.*.HGT$ 는 의도한 뜻과 다르게 동작한다. . 은 임의의 한 글자이므로 .HGT 는 "아무 글자 하나 + HGT" 가 된다. 확장자 앞의 점을 뜻하려면 이스케이프한다.
${filename:matches('^G.*\.HGT$')}
matches 는 전체 일치라 앞뒤의 ^ · $ 가 없어도 된다. find 를 쓸 때만 앵커가 의미를 갖는다.
*.LED.POH.* 처럼 셸 와일드카드로 쓰던 패턴을 정규식으로 옮기면 다음이 된다.
${filename:matches('.*\.LED\.POH\..*')}
여러 이름 토큰 중 하나이면서 오늘 날짜를 포함하는 조건은 이렇게 쓴다.
${filename:find('(A|B|C|D|E)'):and(${filename:contains(${now():format('yyyyMMdd', 'Asia/Seoul')})})}
ListS3 의 Prefix 는 S3 API 가 그대로 받는 값이라 와일드카드를 쓸 수 없다. bbbb/ccc/*/20260920/ 같은 값은 그 문자열로 시작하는 키만 찾게 되어 아무것도 나오지 않는다.
공통 접두사까지만 Prefix 에 주고 나머지는 뒤에서 거른다.
Prefix = bbbb/ccc/
RouteOnAttribute : ${s3.key:find('^bbbb/ccc/[^/]+/20260920/')}
ListS3 는 s3.bucket · s3.key · filename · s3.lastModified 등을 속성으로 붙인다. filename 은 키의 마지막 요소이므로 경로까지 보려면 s3.key 를 봐야 한다.
목록이 매우 크면 Prefix 를 최대한 좁히는 것이 비용과 시간 모두에 유리하다. 날짜가 경로 앞쪽에 오도록 설계된 버킷이면 Prefix 에 날짜까지 넣을 수 있다.
로컬 디렉터리를 다루는 ListFile 은 다르다. Path Filter 가 정규식이며, 하위 디렉터리를 포함하려면 Recurse Subdirectories 를 true 로 둔다.
Input Directory = /data/in
Recurse Subdirectories = true
Path Filter = asdf/asdf
File Filter = .*\.csv
Path Filter 는 입력 디렉터리 기준 상대 경로 에 대한 정규식이다.
EvaluateJsonPath 로 JSON 안의 값을 속성에 담는다.
Destination = flowfile-attribute
Return Type = auto-detect
planid = $.PLANID
객체가 여러 개인 배열이면 EvaluateJsonPath 하나로 각 항목을 개별 속성에 담을 수 없다. 두 가지 방법이 있다.
첫째, SplitJson 으로 $ 또는 $.items 를 잘라 FlowFile 을 항목 수만큼 나눈 뒤 EvaluateJsonPath 를 적용한다. 건수가 많으면 FlowFile 이 폭증하므로 뒤에서 MergeContent 로 다시 묶는다.
둘째, 레코드 기반으로 처리한다. QueryRecord 에 JsonTreeReader 를 붙이고 SQL 로 거르면 FlowFile 을 쪼개지 않고도 분기할 수 있다.
SELECT * FROM FLOWFILE WHERE key_01 = 'value2'
레코드 방식이 성능과 메모리 면에서 유리하므로, 건수가 많으면 SplitJson 대신 이쪽을 쓴다.
프로세서가 실패하면 대체로 failure 관계로 나가면서 오류 메시지를 속성에 담아 준다. 속성 이름은 프로세서마다 다르며 <prefix>.error.message 형태가 많다. 어떤 속성이 실제로 붙는지는 LogAttribute 로 확인한다.
오류 내용을 테이블에 적재하려면 PutSQL 의 파라미터 속성 규약을 쓴다.
sql.args.1.type = 12
sql.args.1.value = ${error.message}
type 은 java.sql.Types 의 정수값이다. 12 는 VARCHAR, 93 은 TIMESTAMP, 4 는 INTEGER 다. ReplaceText 로 INSERT 문을 직접 만들 때는 작은따옴표 이스케이프 문제가 생기므로 파라미터 방식을 권한다.