1. NiFi로 구현하는 CUBRID CDC 기반 Kafka Producer
Python producer와 동일한 목적을 순수 NiFi 프로세서 조합으로 재구현함
1.1 배경
메인 HDFS 적재 루프와 거의 동일한 뼈대에서 마지막 목적지만 PutHDFS에서 PublishKafka로 교체하는 작업이다. DB 스키마(test_kafka_trigger_table, 트리거 2개)는 Python producer와 완전히 동일하게 공유해서 사용한다.
2. 플로우 구조
GenerateFlowFile (주기)
→ UpdateAttribute (job_id = ${uuid})
→ ExecuteSQL (클레임: job_prcs_stts_cd 0→1, job_id='${job_id}'
WHERE job_prcs_stts_cd='0')
→ ExecuteSQLRecord (get pk + JOIN, Record Writer = JsonRecordSetWriter)
→ RouteOnAttribute (matched = ${executesql.row.count:gt(0)})
→ PublishKafka (topic: test_dummy_topic)
├─ success → ExecuteSQL (job_prcs_stts_cd=2, job_proc_dt=SYSDATETIME)
└─ failure → ExecuteSQL (job_prcs_stts_cd=9, job_proc_dt=SYSDATETIME,
retry_cnt=retry_cnt+1)
2.1 설계 결정 — ExecuteSQLRecord 단일 프로세서 채택
JSON 변환 방식으로 두 가지 후보를 검토했다.
- 후보 A — ExecuteSQL(기본, Avro 출력) + ConvertRecord(Avro→JSON)
- 후보 B — ExecuteSQLRecord 하나로 Record Writer를 JsonRecordSetWriter로 지정
후보 B로 결정했다. 프로세서 하나가 절약될 뿐 아니라, 예전에 확인했던 실제 운영 파이프라인(실시간+DIRECT_PG)의 ExecuteSQLRecord - select data + SQL_Bulk_JsonRecordSetWriter 패턴과 정확히 동일한 구조라 채택했다.
AttributesToJSON은 이 목적에 맞지 않는다. 이는 FlowFile "속성"을 JSON으로 바꾸는 프로세서이지 실제 조회된 데이터(row)를 바꾸는 것이 아니며, 실 운영에서도 로그 메시지 조립용으로만 쓰였다.
3. 트러블슈팅
구현 과정에서 겪은 버그 7건을 정리하면 다음과 같다.
1) RouteOnAttribute에서 matched가 항상 0으로만 나옴
- 원인 — Properties 탭에 matched 동적 속성 자체를 만들어두지 않은 상태였음. Routing Strategy만 골라두고 실제 조건은 비어있었던 것이 원인이며, 예전 메인 루프에서 겪은 "조건 0개 = 공허하게 항상 통과"와 유사한 계열의 실수이나 이번엔 반대 방향으로 작용해 전부 unmatched로 흘러 matched=0으로 보인 경우임
- 해결 — Properties 탭에 이름 matched, 값 ${executesql.row.count:gt(0)}인 속성을 직접 추가
2) ExecuteSQLRecord로 바꾼 뒤 executesql.row.count 속성 유효성 의문
- 확인 — NiFi 소스 확인 결과 ExecuteSQLRecord도 동일하게 executesql.row.count를 세팅함(record.count도 같은 값으로 추가 세팅되나 실제 값은 동일). 속성 이름을 바꿀 필요는 없었고, 실제 원인은 1번(RouteOnAttribute 조건 미설정)이었음
3) 조건 추가 후에도 matched=0이 재현됨
- 확인 — List Queue > Attributes로 executesql.row.count를 직접 열어본 결과 실제로 0이었음. 즉 RouteOnAttribute 설정 문제가 아니라 JOIN 자체가 0건인 문제로 좁혀짐
4) JOIN이 0건인 이유 진단
- 확인 — LEFT JOIN + COUNT 비교로 pk_vl=mng_no 자체는 전체 테이블 기준 정상 매칭됨(total_rows == matched_rows)을 확인. 데이터 불일치가 아니라 쿼리 WHERE절 문제로 좁혀짐
5) ExecuteSQLRecord SQL Query의 실제 버그 확정
- 원인 — WHERE t.job_id = job_id AND t.job_prcs_stts_cd = '1'에서 job_id 앞뒤에 ${}가 빠져 있었음. NiFi는 ${}로 감싸지 않은 텍스트는 FlowFile 속성으로 치환하지 않고 SQL 텍스트 그대로 전달하므로, CUBRID가 이를 UpdateAttribute가 만든 값이 아니라 그냥 컬럼명(job_id)으로 해석해 의도한 필터링이 전혀 되지 않았음
- 해결 — WHERE t.job_id = '${job_id}' AND t.job_prcs_stts_cd = '1'로 수정 (문자열 비교이므로 작은따옴표도 필수)
6) 클레임용 ExecuteSQL에서도 같은 계열의 버그
- 증상 — SET job_id = ${job_id}(따옴표 없음) 실행 시 CUBRIDException: Syntax: befor '...4feb-b600-...' invalid relational operator 에러 발생
- 원인 — UUID 문자열 안의 하이픈(-)을 CUBRID가 뺄셈 연산자로 오해해 SQL 문법이 깨짐. 따옴표 없이 값이 그대로 SQL에 꽂히면서 생긴 문제
- 해결 — SET job_id = '${job_id}'로 작은따옴표 감싸기
결론적으로 이 플로우 전체에서 ${job_id}를 쓴 모든 지점(클레임, JOIN, 성공/실패 상태 업데이트)에 전부 작은따옴표로 감싸야 했다. 처음 한 곳에서 발견된 실수가 플로우 전체에 반복되어 있었던 경우다.
7) PublishKafka success 흐름인데 Kafbat UI에 메시지가 안 보이는 것처럼 보였던 순간
- 결과 — 최종적으로 토픽 이름/클러스터 확인 후 test_dummy_topic에 메시지가 정상 적재된 것을 확인함
4. 결과
test_dmy_table 변경 → 트리거 → test_kafka_trigger_table 적재 → NiFi(클레임 → JOIN → JSON → PublishKafka → 상태 업데이트) 전 과정을 확인했고, Kafbat UI에서 test_dummy_topic에 메시지가 실제 적재된 것을 확인 완료했다. Python 버전과 NiFi 버전 둘 다 같은 DB 스키마를 공유하며 독립적으로 정상 작동한다.
5. 공통 교훈
Python 구현과 NiFi 구현 양쪽에서 반복된 패턴을 정리하면 다음과 같다.
| 구분 | Python | NiFi |
| 증상 | 성공으로 표시되는데 실제로는 아무 일도 안 일어남 | 성공으로 표시되는데 실제로는 아무 일도 안 일어남 |
| 원인 | errors 리스트가 비어서 성공으로 오판 | RouteOnAttribute 조건 미설정으로 공허하게 통과 |
조건/카운트 기반 판단 로직을 짤 때는 "진짜 처리된 게 있는지"를 직접 찍어서 확인하는 습관이 중요하다.
NiFi Expression Language(${}) 누락은 두 가지 양상으로 나타났다.
- 조용히 잘못된 동작으로 이어지는 경우 — ${} 없는 텍스트가 컬럼명으로 오인되어 필터링이 무효화됨(에러 없음)
- 문법 에러로 바로 드러나는 경우 — UUID의 하이픈이 연산자로 오인되어 즉시 SQL 문법 에러 발생
후자가 오히려 디버깅하기 쉬웠다. 에러 없이 조용히 틀리는 쪽이 훨씬 위험하다.
요약
| 항목 | 내용 |
| 목표 | 순수 NiFi 프로세서 조합으로 CUBRID CDC 기반 Kafka 발행 구현 |
| 핵심 설계 | ExecuteSQLRecord 단일 프로세서로 JOIN+JSON 변환 일원화 |
| 주요 버그 | RouteOnAttribute 조건 미설정, ${} 누락으로 인한 컬럼명 오인, UUID 하이픈의 연산자 오인 |
| 검증 결과 | 트리거 적재 → NiFi 클레임/JOIN/발행/상태 업데이트 전 과정, Kafbat UI에서 메시지 적재 확인 완료 |
${}를 빼먹었을 때 에러 없이 조용히 틀리는 경우와 문법 에러로 바로 터지는 경우가 갈린다는 것 — 전자를 잡아내려면 값을 직접 찍어보는 습관이 결국 답이었다.
'펜타시스템 > 입사 후 첫 프로젝트 공부 내용' 카테고리의 다른 글
| Cubrid의 url 디팽처리 후 AES 암호화 : NIFI 파이프라인 구축 (0) | 2026.09.21 |
|---|---|
| 두번째 과제 완료 / 폐쇄망 Trino 연동 기록 (0) | 2026.09.21 |
| 두번째 과제 진행 : By Python Code (0) | 2026.09.18 |
| 두 번째 과제 개념 : Kafka의 메시지 포맷에 대해 (0) | 2026.09.16 |
| 두 번째 과제 시작 : Kafka (0) | 2026.09.16 |