now() 는 현재 시각을 Date 로 돌려준다. 여기서 원하는 형태로 가공한다.
| 필요한 값 | 식 |
|---|---|
| 유닉스 시각(밀리초) | ${now():toNumber()} |
| 유닉스 시각(초) | ${now():toNumber():divide(1000)} |
yyyy-MM-dd |
${now():format('yyyy-MM-dd')} |
| 한국시간 기준 날짜 | ${now():format('yyyyMMdd', 'Asia/Seoul')} |
PostgreSQL timestamp |
${now():format('yyyy-MM-dd HH:mm:ss.SSS')} |
PostgreSQL timestamptz |
${now():format("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")} |
| 문자열을 Date 로 | ${my.time:toDate('yyyy-MM-dd HH:mm:ss')} |
| 하루 전 | ${now():toNumber():minus(86400000):format('yyyyMMdd')} |
format 의 두 번째 인자로 타임존을 준다. 주지 않으면 JVM 기본 타임존을 쓴다. 서버가 UTC 인데 업무 기준이 한국시간이면 날짜가 어긋나므로, 날짜를 만들 때는 타임존을 명시하는 편이 안전하다.
plus · minus 는 숫자 함수다. 날짜에 직접 기간을 더하는 함수는 없으므로 toNumber() 로 밀리초를 얻은 뒤 계산하고 다시 format 한다. ${now():plus(1h)} 같은 표기는 동작하지 않는다.
InvokeHTTP 에서 헤더나 쿼리 문자열에 시각을 넣을 때도 같은 식을 쓴다. 속성 이름을 헤더 이름으로 쓰는 동적 속성을 추가하고 값에 EL 을 넣는다.
X-Timestamp = ${now():format("yyyy-MM-dd'T'HH:mm:ss'Z'", 'GMT')}
UpdateAttribute 로 속성을 추가·수정하고, Delete Attributes Expression 으로 지운다. 이 속성은 정규식이며 이름이 맞는 속성을 모두 지운다.
Delete Attributes Expression = ^(tmp\..*|my\.work\..*)$
uuid · filename · path 처럼 NiFi 가 관리하는 핵심 속성까지 지우면 뒤 프로세서가 동작하지 않으므로, 지우는 대상은 접두사로 구분해 두는 것이 좋다.
속성은 FlowFile 하나마다 전부 힙에 상주하고 FlowFile 저장소에도 기록된다. 큰 값을 속성에 담으면 큐가 길어질수록 힙을 그대로 잡아먹는다. 본문은 내용(content)에 두고 속성에는 키와 짧은 메타데이터만 담는다. NiFi 자체에 속성 길이 상한이 정해져 있지는 않으며, 자주 인용되는 65535 는 프로비넌스 인덱싱이나 외부 저장소 쪽 제약으로 보인다 (확인 필요).
어떤 속성이 붙어 있는지 확인할 때 쓴다.
| 속성 | 값 |
|---|---|
| Log Level | info |
| Log Payload | 내용까지 보려면 true |
| Attributes to Log | 비워 두면 전부. 정규식은 Attributes to Log by Regular Expression |
| Log prefix | 로그에서 찾기 쉬운 접두사 |
출력은 nifi-app.log 로 간다. 별도 파일로 빼려면 logback.xml 에 appender 와 로거를 추가한다.
<appender name="ATTRLOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${org.apache.nifi.bootstrap.config.log.dir}/nifi-attr.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${org.apache.nifi.bootstrap.config.log.dir}/nifi-attr_%d.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date %level [%thread] %msg%n</pattern>
</encoder>
</appender>
<logger name="org.apache.nifi.processors.standard.LogAttribute" level="INFO" additivity="false">
<appender-ref ref="ATTRLOG"/>
</logger>
logback.xml 은 주기적으로 다시 읽히므로 재기동 없이 반영된다. 다만 잘못 쓰면 로그가 통째로 멈추므로 바꾸기 전에 원본을 복사해 둔다.
내용을 파일로 떨구고 싶으면 LogAttribute 가 아니라 PutFile 을 쓴다. LogAttribute 는 로그 프레임워크로만 출력한다.
Cannot get a connection, pool error Timeout waiting for idle object,
borrowMaxWaitDuration=PT0.5S
풀에서 유휴 커넥션을 빌리지 못한 채 대기 시간이 끝났다는 뜻이다. PT0.5S 는 Max Wait Time 이 0.5 초로 설정돼 있음을 말한다. 원인은 셋 중 하나다.
첫째, 동시 사용량이 풀 크기를 넘었다. Max Total Connections 를 올리거나 해당 프로세서의 Concurrent Tasks 를 줄인다. 클러스터에서는 노드마다 풀이 따로 있으므로 DB 쪽 최대 접속 수와 대조해 계산한다.
둘째, 쿼리가 오래 걸려 커넥션이 반납되지 않는다. 쿼리 자체를 개선하고 ExecuteSQL 의 Max Wait Time 으로 상한을 둔다.
셋째, 예외 경로에서 커넥션이 반납되지 않고 새는 경우다. 직접 만든 Groovy 스크립트에서 커넥션을 쓰면 try { } finally { conn.close() } 로 반드시 닫는다.
Max Wait Time 을 0.5 초로 두면 순간적인 경합에도 바로 실패한다. 처리량이 일정하지 않은 흐름에서는 몇 초 정도로 늘린다.