펜타시스템/입사 후 첫 프로젝트 공부 내용

첫 과제 진행 : NIFI 구축 사항

dev-lee 2026. 9. 15. 13:12

========================================
NiFi 플로우 설계 정리 - CUBRID CDC 테스트베드
(최종 확정본, 2026-09-15 기준)
========================================

전체 그림:
  test_dmy_table (CUBRID) --트리거--> test_trigger_table (CUBRID)
                                            |
                              NiFi 메인 루프(10초) 폴링/JOIN/PutHDFS
                                            |
                                     HDFS Staging
                                            |
                    (실패분) NiFi 재처리 루프(5분)가 별도로 재시도


========================================
[0] Controller Services (공용)
========================================

--- DBCPConnectionPool ---
모든 ExecuteSQL/PutSQL 프로세서가 공유
  - Database Connection URL: jdbc:CUBRID:<host>:<port>:<dbname>:::
  - Database Driver Class Name: cubrid.jdbc.driver.CUBRIDDriver
  - Database Driver Location(s): CUBRID JDBC 드라이버 jar 경로
  - Database User: NCSC_INTEG (또는 admin - 테이블 소유 계정과 일치해야 함)
  - Database Password: (실제 값)

--- Parameter Context ---
hadoop_config_resource 파라미터 정의 후,
PutHDFS가 속한 Process Group에 반드시 "할당"까지 해야 함
(Process Group 우클릭 -> Configure -> Process Group Parameter Context 드롭다운)
참조 문법: #{hadoop_config_resource}  (${...}는 EL이라 안 먹힘, #{...}가 파라미터 문법)


========================================
[1] 메인 폴링 루프 (10초 주기)
========================================

1) GenerateFlowFile
   - Scheduling: Timer driven, Run Schedule = 10 sec
   - Properties: File Size = 0B

2) UpdateAttribute ("Set Job UUID")
   - current_flow_id = ${uuid}
   - filename        = ${uuid}.avro
   (두 속성 다 이 시점의 "원본" 값 기준으로 평가되므로 순서 걱정 없음.
    UpdateAttribute는 모든 속성을 원본 스냅샷 기준으로 한 번에 계산 후
    일괄 적용하는 구조이기 때문)

3) ExecuteSQL ("클레임")
   - Database Connection Pooling Service: DBCPConnectionPool
   - SQL Query:
     UPDATE test_trigger_table
     SET job_prcs_stts_cd = '1', job_id = '${current_flow_id}'
     WHERE job_prcs_stts_cd = '0'
     LIMIT 200
   (CUBRID의 UPDATE...LIMIT 문법 실제 동작 확인됨 - 2026-09-15)

4) ExecuteSQL ("get pk + JOIN")
   - SQL Query:
     SELECT d.*
     FROM test_trigger_table t
     JOIN test_dmy_table d ON t.pk_vl = d.mng_no
     WHERE t.job_id = '${current_flow_id}' AND t.job_prcs_stts_cd = '1'
   - 결과가 자동으로 Avro 형식 FlowFile로 출력됨 (별도 변환 불필요)
   - INNER JOIN(기본 JOIN)이 맞는 선택: pk_vl은 항상 test_dmy_table에서
     복사된 값이라 지금 스코프(INSERT/UPDATE)에선 무조건 매칭됨.
     DELETE가 스코프에 들어오면 이 JOIN이 자동으로 매칭 실패하는 걸
     이용해 "삭제된 건 여기서 자동 제외"하는 용도로 쓰이게 됨
     (DELETE 이벤트는 이 JOIN과 별개의 경로로 처리 필요, 현재 스코프 제외 상태)

5) RouteOnAttribute ("Has Data?")
   - matched = ${executesql.row.count:gt(0)}
   - matched만 다음 단계로, 나머지는 auto-terminate
   (주의: 1번 ExecuteSQL 바로 뒤가 아니라 2번 뒤에 이 체크를 둬야 함 -
    UPDATE류 DML은 executesql.row.count가 항상 0으로 고정되어 나오므로
    거기서 체크하면 항상 "없음"으로 오판됨)

6) PutHDFS
   - Hadoop Configuration Resources: #{hadoop_config_resource}
   - Kerberos User Service: 비워둠 (클러스터가 Kerberos 안 씀, optional)
   - Remote Owner / Remote Group: 비워둠 (optional, 실패해도 flowfile 안 죽음)
   - Writing Strategy: 기본값 유지 (Write and Rename - 임시 dot파일에 다 쓴 뒤
     원자적 rename하는 방식이라 최종 경로엔 완전한 파일 아니면 아예 안 보임,
     atomic 보장됨)
   - Directory: (확정된 실제 경로)
   - Conflict Resolution Strategy: fail (파일명이 매 사이클 uuid 기반이라 유니크함)
   - 재시도(Retry) 설정 없음 -- 아래 [설계 변경 이력] 참고, 메인 루프에서는
     실패 시 즉시 종결하고 별도 재처리 루프가 담당하도록 재설계함
   - success -> 7)로
   - failure -> 8)로 (즉시, 재시도 없음)

7) ExecuteSQL ("Mark Success") - PutHDFS success에서 연결
   - SQL Query:
     UPDATE test_trigger_table
     SET job_prcs_stts_cd = '2', job_proc_dt = SYSDATETIME
     WHERE job_id = '${current_flow_id}' AND job_prcs_stts_cd = '1'
   - success 관계 -> Auto-terminate

8) ExecuteSQL ("Mark Failure") - PutHDFS failure에서 연결
   - SQL Query:
     UPDATE test_trigger_table
     SET job_prcs_stts_cd = '9', job_proc_dt = SYSDATETIME, retry_cnt = retry_cnt + 1
     WHERE job_id = '${current_flow_id}' AND job_prcs_stts_cd = '1'
   - success 관계 -> Auto-terminate (재처리 루프가 별도로 이어받음, 여기서 루프백 금지)
   - failure 관계(이 UPDATE 자체가 실패하는 극단적 경우) -> Auto-terminate
     (좀비 행 되는데, 이건 수동 대응 정책 범위)


========================================
[2] 재처리 루프 (5분 주기, 메인 루프와 완전히 독립된 별도 플로우)
========================================

설계 배경: PutHDFS 실패 시 메인 루프 안에서 바로 재시도하면, 유입/처리
속도가 애매하게 비슷한 상황에서 백프레셔 계산이 불가능해질 위험이 있다는
피드백에 따라, 재시도를 메인 루프에서 완전히 분리함.

1) GenerateFlowFile
   - Scheduling: Timer driven, Run Schedule = 5 min

2) UpdateAttribute
   - retry_flow_id = ${uuid}

3) ExecuteSQL ("재클레임")
   - SQL Query:
     UPDATE test_trigger_table
     SET job_prcs_stts_cd = '1', job_id = '${retry_flow_id}'
     WHERE job_prcs_stts_cd = '9' AND retry_cnt < 3
     LIMIT 200

4) ExecuteSQL ("get pk + JOIN") - 메인 루프와 동일 구조
   - SQL Query:
     SELECT d.*
     FROM test_trigger_table t
     JOIN test_dmy_table d ON t.pk_vl = d.mng_no
     WHERE t.job_id = '${retry_flow_id}' AND t.job_prcs_stts_cd = '1'

5) RouteOnAttribute - 메인 루프와 동일 (row.count > 0)

6) PutHDFS - 메인 루프와 동일 설정 (재시도 없음)
   - success -> 7)로
   - failure -> 8)로

7) ExecuteSQL ("Mark Success")
   - job_prcs_stts_cd = '2', job_proc_dt = SYSDATETIME
   - WHERE job_id = '${retry_flow_id}' AND job_prcs_stts_cd = '1'
   - Auto-terminate

8) ExecuteSQL ("Mark Failure")
   - job_prcs_stts_cd = '9', job_proc_dt = SYSDATETIME, retry_cnt = retry_cnt + 1
   - WHERE job_id = '${retry_flow_id}' AND job_prcs_stts_cd = '1'
   - Auto-terminate (다음 5분 주기에 자연스럽게 다시 후보가 됨, 루프백 없음)

영구 실패 판정: 별도 상태코드 없이 retry_cnt로 구분.
  job_prcs_stts_cd='9' AND retry_cnt<3   -> 재처리 대기 중
  job_prcs_stts_cd='9' AND retry_cnt>=3  -> 3회 다 실패, 재처리 루프의
                                             클레임 조건에서 자동 제외되어
                                             영구히 재시도 안 됨 (수동 확인 필요)


========================================
[3] 확인된 FlowFile 동작 원리 (트러블슈팅 중 소스로 검증)
========================================

- filename, uuid는 GenerateFlowFile 생성 시 "동일한" 랜덤값으로 세팅됨.
- ExecuteSQL은 filename을 절대 안 건드리고 그대로 물려줌.
- uuid는 ExecuteSQL이 실제 결과셋(SELECT)을 반환할 때만 session.create()로
  자식 FlowFile을 만들면서 "새로 생성"됨 (UPDATE류 DML은 원본 FlowFile을
  그대로 재사용하므로 uuid 안 바뀜). 그래서 GenerateFlowFile 직후
  UpdateAttribute로 current_flow_id를 미리 커스텀 속성에 스냅샷 떠둬야 함
  - uuid 자체는 이후 단계에서 바뀌므로 신뢰 불가.
- PutHDFS는 FlowFile의 filename 속성을 그대로 읽어 Directory와 합쳐서
  최종 저장 경로를 만듦. 별도로 파일명 지정하는 프로퍼티는 없음.
- Kerberos User Service, Remote Owner/Remote Group은 모두 optional이며,
  안 쓰면 그냥 스킵되고 실패해도(권한 없어도) FlowFile에 영향 없음.
- NiFi Parameter Context 참조 문법은 #{name} (EL인 ${name}과 다름).
  파라미터를 정의하는 것과, 그걸 Process Group에 "할당"하는 것은 별개 단계 -
  둘 다 해야 함.


========================================
[4] 설계 변경 이력 (피드백 반영)
========================================

- (원래 설계) PutHDFS 실패 시 메인 루프 안에서 3회, Penalize 방식 지수
  백오프로 자동 재시도.
- (변경 사유) 정체현상(유입속도>처리속도)이 발생하지 않는다는 가정 하의
  설계였다는 피드백. 유입/처리 속도가 애매하게 비슷한 상황에서 재시도로
  인한 지연이 추가되면 백프레셔 계산이 불가능해질 수 있어, 실패 시 즉시
  로그로 빼고 흘려보내는 설계가 더 안전하다는 지적.
- (변경 후) 메인 루프는 재시도 없이 1회 시도 후 즉시 실패 확정(9),
  retry_cnt 컬럼 신설, 완전히 독립된 별도 재처리 루프(5분 주기)가 재시도
  담당. 3회(retry_cnt<3) 초과 시 자동으로 영구 실패 상태에 머무름.