Flink의 "정확히 한 번"은 실제로 무엇을 보장하는가?

결론부터 말하면, Flink의 EXACTLY_ONCE는 연산자 상태의 일관된 복원을 보장할 뿐 레코드가 파이프라인을 물리적으로 한 번만 통과한다는 뜻은 아니다. 외부 시스템까지 정확히 한 번을 만들려면 소스의 재생 가능성과 싱크의 트랜잭션·멱등성이 별도로 필요하다. 2026년 기준으로도 이 경계를 혼동한 운영 설계가 가장 흔한 실패 지점이다.

핵심 정리를 먼저 보면:

  • EXACTLY_ONCE는 "내부 상태 일관성" 보장이지 "외부 시스템 중복 제거" 보장이 아니다.
  • end-to-end exactly-once는 Kafka류 재생 가능 소스 + 트랜잭션/멱등 싱크 조합에서만 성립한다.
  • 체크포인트 완료 주기가 레이크하우스 신선도를 직접 결정한다 — Iceberg는 체크포인트 완료 시점에 스냅샷을 커밋한다.
  • 입력 정렬 비용 때문에 재분배가 있는 파이프라인에서는 꼬리 지연이 늘 수 있고, 초저지연이 최우선이면 AT_LEAST_ONCE를 검토할 여지가 있다.
  • 백프레셔가 있는 환경에서는 unaligned checkpoint를 별도로 평가해야 하지만, 아직 실험적 기능 꼬리표가 붙어 있다.

두 선택지를 나란히 놓고 보면 질문이 단순해진다. EXACTLY_ONCE는 배리어 정렬을 거쳐 체크포인트 시점의 상태를 엄격하게 고정하는 방식이고, AT_LEAST_ONCE는 정렬을 생략해 지연을 줄이는 대신 재시작 시 일부 레코드가 중복 처리될 여지를 남긴다. 이 둘은 상호 배타적인 "더 나은" 옵션이 아니라, 정합성과 지연을 어느 축에 더 할당할지를 묻는 설정값에 가깝다. 출처

내부 상태 보장과 외부 시스템 보장은 왜 다른 질문인가?

다르다. Flink 공식 문서는 EXACTLY_ONCE 모드에서 장애 복구 시 연산자와 사용자 함수의 상태가 마지막 완료 체크포인트를 기준으로 일관되게 복원된다고 명시하지만, 이는 Flink 내부 상태에 한정된 보장이다. 출처

반면 JDBC 싱크나 일반 HTTP 호출처럼 트랜잭션을 지원하지 않는 외부 시스템에 쓰는 경우, Flink 내부가 아무리 EXACTLY_ONCE라도 재시작 이후 동일 레코드가 다시 전송되면서 중복 쓰기나 부분 쓰기가 발생할 수 있다. CheckpointingMode API 문서 역시 이 조건을 명확히 구분해 기술한다. 출처 즉 "Flink 설정에서 EXACTLY_ONCE를 켰다"는 문장과 "이 파이프라인은 end-to-end exactly-once다"는 문장은 서로 다른 주장이며, 후자를 검증하려면 소스·싱크 커넥터 각각의 보장 범위를 따로 확인해야 한다.

소스·싱크 조건이 바뀌면 보장 수준이 어떻게 갈라지는가?

갈라진다. Kafka처럼 오프셋 기반으로 재생 가능한 소스와, 2단계 커밋 또는 멱등 쓰기를 지원하는 싱크가 결합될 때만 end-to-end exactly-once가 성립한다. 이 조합이 아니면 Flink 내부 상태는 정확히 한 번이어도 전체 파이프라인은 그렇지 않다.

예를 들어 Kafka 소스 + Kafka 싱크(트랜잭션 지원) 조합은 end-to-end exactly-once를 구성할 수 있는 전형적인 사례지만, 같은 Kafka 소스라도 싱크를 일반 REST API나 트랜잭션 미지원 JDBC로 바꾸면 보장은 "Flink 내부 상태만 정확히 한 번"으로 축소된다. 이 차이는 설정값 하나가 아니라 커넥터 선택의 문제이므로, 파이프라인 설계 단계에서 소스·싱크 조합표를 따로 점검하는 편이 체크포인트 모드 자체를 튜닝하는 것보다 실패 가능성을 더 많이 줄인다. 출처

체크포인트 간격은 레이크하우스 신선도를 어떻게 가르는가?

직접 가른다. Iceberg용 Flink 싱크는 체크포인트가 완료되는 시점에 새 스냅샷을 커밋하므로, 체크포인트 간격이 사실상 데이터 가시성 지연을 결정하는 제어값이 된다. 1분 간격 설정은 대략 분 단위 신선도를 만들고, 간격을 더 좁히면 스냅샷 수가 늘고 작은 파일 문제가 커진다. 출처

이는 배치·스트리밍 경계에서 흔히 생기는 트레이드오프와 같은 구조다. 신선도를 올리려고 체크포인트 간격을 좁히면 커밋 빈도와 함께 메타데이터 오버헤드, 컴팩션 비용이 따라 늘어난다. 반대로 간격을 넓히면 신선도는 떨어지지만 스냅샷·파일 수는 안정적으로 유지된다. 레이크하우스 경로를 설계할 때는 "체크포인트 간격을 얼마로 할 것인가"를 지연 튜닝 문제가 아니라 신선도-운영비용 트레이드오프 문제로 다루는 편이 맞다.

정합성 비용은 지연에 어떤 형태로 나타나는가?

주로 입력 정렬 대기시간으로 나타난다. EXACTLY_ONCE 모드는 체크포인트 배리어가 모든 입력 채널에 도착할 때까지 기다리는 정렬 과정을 거치며, 이 때문에 지연이 늘 수 있다. 단순한 비재분배 파이프라인에서는 증가폭이 무시할 수준일 수 있지만, 재분배(shuffle)가 끼어 있는 파이프라인에서는 특히 꼬리 지연이 커질 수 있다. 출처

지속적으로 수 밀리초 수준의 초저지연이 요구되는 애플리케이션에서는 공식 문서도 AT_LEAST_ONCE가 적절할 수 있다고 설명한다. 다만 이는 중복 허용 또는 별도의 멱등성 설계를 전제로 한 선택이며, "지연이 낮아서 더 낫다"는 식의 일반화는 성립하지 않는다. 백프레셔가 심한 환경이라면 unaligned checkpoint를 별도로 검토할 수 있는데, Flink 2.0 preview 문서는 이 기능이 백프레셔 상황에서 체크포인트 시간을 크게 줄일 수 있다고 설명하면서도 여전히 실험적 기능으로 표시한다. 운영 적용 전 버전과 안정성 검증이 필요하다. 출처

두 모드를 무엇으로 비교해야 하는가?

체크포인트 간격 하나만 보면 비교가 반쪽짜리가 된다. 아래는 공식 문서가 명시한 보장 범위를 기준으로 정리한 대조표다. 여기 실린 항목은 수치가 아니라 설계 시 확인해야 할 조건이며, 실측 수치는 재현 조건(버전, 상태 크기, 병렬도, 스토리지, 장애 주입 방식 등)이 공개된 벤치마크에서만 별도로 확보해야 한다.

항목 EXACTLY_ONCE AT_LEAST_ONCE
내부 상태 복원 마지막 완료 체크포인트 기준 일관 복원 복원되지만 중복 재처리 가능
입력 배리어 정렬 필요 (재분배 시 꼬리 지연 증가 가능) 불필요
end-to-end 보장 조건 재생 가능 소스 + 트랜잭션/멱등 싱크 필수 별도 멱등성 설계로 보완 필요
적합한 상황 일반적인 운영 기본값 수 ms 수준 초저지연 요구 시
레이크하우스 신선도 체크포인트 간격이 스냅샷 커밋 주기를 결정 동일 구조지만 중복 레코드가 스냅샷에 섞일 수 있음

실제 측정에서는 이 표에 더해 체크포인트 지속시간, 완료율, 실패율, 복구시간, 재처리 레코드 수, 싱크 커밋 지연, Iceberg 스냅샷 수와 파일 크기를 함께 기록해야 비교가 의미를 가진다. 이 항목들이 빠진 "체크포인트 몇 초 완료" 류 수치는 하드웨어·상태 크기·부하 조건이 불명확하면 그대로 인용하기 어렵다. 실제로 일부 공개 기술 자료는 상태 크기와 무관하게 수 초 내 체크포인트 완료나 수십 배 복구 가속을 주장하지만, 해당 수치는 구체적인 재현 조건이 충분히 제시되지 않아 벤치마크 근거로 단정하기보다 "공개 자료의 주장" 수준으로 제한해 두는 편이 안전하다.

모드를 전환할 때 실제로 드는 비용은 무엇인가?

적지 않다. AT_LEAST_ONCE에서 EXACTLY_ONCE로 전환할 때는 체크포인트 모드 설정 변경만으로 끝나지 않는다. 먼저 싱크 커넥터가 트랜잭션 또는 멱등 쓰기를 지원하는지 확인해야 하고, 지원하지 않는다면 커넥터 교체나 멱등성 레이어(고유 키 기반 upsert 등)를 추가로 설계해야 한다.

체크포인트 간격도 다시 잡아야 한다. 정렬 비용이 추가되므로 기존 간격을 그대로 두면 체크포인트 지속시간이 늘고, 이는 다시 Iceberg 같은 레이크하우스 싱크의 스냅샷 커밋 주기와 작은 파일 문제에 영향을 준다. 백프레셔가 있는 파이프라인이라면 unaligned checkpoint 적용 여부도 함께 판단해야 하는데, 이 기능이 아직 실험적 상태이므로 운영 반영 전 별도 테스트 환경에서 버전별 안정성을 검증하는 작업이 추가된다. 반대 방향, 즉 EXACTLY_ONCE에서 AT_LEAST_ONCE로 내려가는 전환은 설정 자체는 가볍지만, 다운스트림에서 중복 레코드를 걸러낼 멱등성 로직을 새로 심어야 하므로 비용이 소스 측에서 싱크·소비자 측으로 옮겨갈 뿐 사라지지 않는다.

어느 쪽을 선택해야 하는가, 기준은 무엇인가?

정답은 모드 자체가 아니라 조건에 있다. 소스가 재생 불가능하거나 싱크가 트랜잭션·멱등성을 지원하지 않는다면 EXACTLY_ONCE를 켜도 end-to-end 보장은 만들어지지 않으므로, 먼저 커넥터 조합부터 점검해야 한다.

그 조건이 충족된 상태에서는 지연 민감도와 신선도 요구를 함께 본다. 재분배가 있는 파이프라인에서 수 밀리초 단위 지연이 업무 요구사항이라면 AT_LEAST_ONCE와 별도 멱등성 설계를 묶는 조합을 검토할 여지가 있다. 반대로 레이크하우스 적재처럼 분 단위 신선도로도 충분하고 정합성이 우선이라면 EXACTLY_ONCE가 기본값이 되어야 한다. 백프레셔가 자주 발생하는 환경이라면 unaligned checkpoint를 실험 단계에서 별도로 검증한 뒤 반영 여부를 결정하는 것이 순서다. 어떤 선택이든 체크포인트 지속시간, 완료율, 복구시간, 재처리 레코드 수를 함께 로깅해 두지 않으면, 이후 재현도 비교도 할 수 없다.

핵심 정리

  • EXACTLY_ONCE는 Flink 내부 상태의 일관된 복원을 보장할 뿐, 외부 시스템까지 자동으로 정확히 한 번을 만들지 않는다.
  • end-to-end exactly-once는 재생 가능한 소스와 트랜잭션/멱등 싱크 조합에서만 성립한다.
  • 체크포인트 완료 주기는 Iceberg 스냅샷 커밋 주기를 결정하므로 레이크하우스 신선도를 조정하는 1차 제어값이다.
  • 재분배가 있는 파이프라인에서는 배리어 정렬 비용이 꼬리 지연으로 나타날 수 있고, 초저지연 요구 시 AT_LEAST_ONCE를 별도 멱등성 설계와 묶어 검토할 수 있다.
  • 공개 자료의 체크포인트 소요시간·복구 가속 수치는 재현 조건이 확인되기 전까지 설계 근거로 쓰지 않는 편이 안전하다.

자주 묻는 질문

Kafka 소스 + Kafka 싱크 조합이면 추가 설정 없이 end-to-end exactly-once가 보장되나?

조합 자체는 전제 조건을 충족하지만, 싱크 쪽 트랜잭션 기능이 활성화되어 있어야 하고 체크포인트 타임아웃·동시 체크포인트 수 같은 세부 설정이 어긋나면 커밋 지연이나 트랜잭션 중단이 생길 수 있다. 커넥터 버전별 트랜잭션 지원 범위도 함께 확인해야 한다.

체크포인트 간격을 좁히면 신선도만 좋아지고 다른 비용은 없나?

없지 않다. 간격을 좁히면 Iceberg 스냅샷 수와 작은 파일 수가 함께 늘어나고, 이는 이후 컴팩션 작업 부담으로 이어진다. 신선도 개선분과 이 운영 비용을 함께 측정해야 간격 조정이 타당한지 판단할 수 있다.

unaligned checkpoint는 언제 EXACTLY_ONCE 대신 고려하나?

EXACTLY_ONCE를 대체하는 것이 아니라 그 안에서 체크포인트 소요시간을 줄이는 보조 기능이다. 백프레셔가 지속적으로 관측되는 파이프라인에서 체크포인트 지연이 운영에 영향을 줄 때 별도 환경에서 먼저 검증해 보는 것이 순서이며, 실험적 기능 꼬리표가 붙어 있는 동안은 운영 전면 적용을 보류하는 편이 안전하다.

AT_LEAST_ONCE로 내려가면 멱등성 설계는 어디에 둬야 하나?

보통 싱크 직전이나 다운스트림 소비자 쪽에 고유 키 기반 중복 제거 로직을 둔다. Flink 내부에서 중복을 막는 대신, 외부 시스템이 같은 레코드를 두 번 받아도 결과가 같도록 upsert 키나 멱등 쓰기 방식을 설계하는 쪽이 일반적인 접근이다.

더 알아보기

데이터 파이프라인 아키텍처: 배치·스트리밍·신선도의 설계 축 — 이 주제의 종합 가이드