Joe Armstrong 저서에서 인용

"메시지는 유실될 수 있다. Erlang은 실패 가능성을 숨기지 않는다.
그러나 그것은 신뢰할 수 있는 실패이다. 왜냐하면 숨기지 않기 때문이다."

Joe Armstrong, Programming Erlang

💡 "거짓없는(Unreliable but Honest)" 철학

"Erlang은 메시지를 신중하게 보내며, 실패할 가능성을 숨기지 않는다.
메시지가 유실될 수 있음을 정직하게 인정하고,
시스템 설계자가 그에 맞게 복원력을 고려하도록 한다."


언랭에서 출발한 위와같은 개념은 오늘날의 메시지 전송 수준 개념에 영향을 주며 확장 되었습니다.

언랭의 거짓없는 메시지 전송을 살펴보고 메시지 전송 수준에 대한 개념과 함께

Kafka/Akka에서 이용할수 있는 장치도 함께 살펴보겠습니다.

🧩 Erlang의 메시지 전송: "Unreliable but Honest"

🟡 핵심 정의

  • Erlang은 메시지를 “거짓없이(honestly)” 보낸다고 보장하되, “항상 도착하는 것”은 보장하지 않음.

  • 이는 즉, "보낸다고 했으면 진짜 보냈다"는 것을 보장하지만, 네트워크나 프로세스 문제로 인해 수신자가 받지 못할 수 있음.


✅ 의미 정리

요소설명
보낸다는 약속Sender ! Msg를 실행하면 Erlang은 메시지를 보내려고 시도했음을 보장
수신자 보장 없음수신자가 다운되었거나, 네트워크 분리, 큐가 손상된 경우 메시지는 유실될 수 있음
확인(ACK)기본적으로 ACK가 없음. 메시지 수신 여부는 확인할 방법이 없음
신뢰성 책임개발자가 직접 retry, ACK, 중복제거를 구현해야 함
기본 전송 모델At-Most-Once에 가까운 구조 (한 번 보내고 끝)



🧠 Erlang 프로그래머의 현실 대응법


상황대응 방식
메시지 유실 방지gen_server:call 처럼 동기 호출 사용 (내부 ACK)
중복 방지메시지에 UUID 붙이고 중복 수신 필터링
상태 복구supervisor + process monitoring 활용
메시지 순서 보장기본적으로 없음 → 순서를 명시적으로 관리해야 함

예시요약

% 메시지 보내기 (ACK 없음, 실패 감지 불가)
ReceiverPid ! {from, self(), hello}.

% 신뢰 보장 원한다면 call 사용
Response = gen_server:call(MyServer, {do_work, Payload}).

🎯 요약 비교


항목Erlang 기본 메시징Kafka / Akka 등 현대 메시지 플랫폼
전달 보장❌ (at-most-once 유사)✅ (옵션에 따라 보장)
중복 방지❌ 직접 구현✅ 트랜잭션 / idempotency 지원
유실 허용✅ (허용됨, 정직함)❌ 최소 at-least-once 보장됨
ACK / Retry❌ 기본 미제공✅ 명시적 제공


🧠 핵심 요약

단계

시스템

메시지 전달 수준

특징

Erlang (1986)

기본: At-Most-Once

메시지 유실 가능, ack 없음, 재전송은 직접 구현

Akka (2010~)

기본: At-Most-Once + At-Least-Once

at-least-onceAck 직접 구현, DurableMailbox

Akka Persistence

At-Least-Once + Idempotent ActorExactly-Once에 근접

이벤트 소싱, 재처리 시 동일 효과 보장

Kafka (v0.11~)

명시적 지원: At-Most, At-Least, Exactly Once

트랜잭션 API + Idempotent Producer 제공


Message Delivery Semantics

메시지 보장수준은 전략에따라 채택할수 있으며 모든기능에 보장수준을 최고수준으로 하는 오버엔지니어링을 하기보다는

인프라및 설계비용을 고려 전략적 채택이 중요할수 있습니다.

✅ 1. At Most Once (최대 1번 전송)

  • 의미: 메시지가 0번 또는 1번 전송됨.
    메시지 손실이 발생할 수 있지만 중복은 없음.

  • 보장 수준: 전송 실패 시 재시도 없음

  • 사용 예시: 로그 데이터, 비중요 알림

  • 장점: 빠르고 가볍다. 인프라 부담 거의 없음

  • 비용 관점:

    • 최저 비용

    • 메시지 저장 없음, 재전송 로직 없음

    • 브로커도 상태를 유지할 필요 거의 없음


✅ 2. At Least Once (적어도 1번 전송)

  • 의미: 메시지가 1번 이상 전송될 수 있음.
    중복 가능성이 있으나 손실은 없음

  • 보장 수준: 수신 확인이 없으면 재전송

  • 사용 예시: 결제 요청, 주문 이벤트

  • 장점: 신뢰성 확보 (중복 제거는 소비자가 책임짐)

  • 비용 관점:

    • 중간 수준 비용

    • 메시지 재시도를 위한 큐 유지, 상태 저장 필요

    • 클라이언트 or 브로커의 중복 제어 로직 필요

    • 디스크 I/O 비용, 네트워크 재전송 비용 발생


✅ 3. Exactly Once (정확히 1번 전송)

  • 의미: 메시지가 정확히 1번만 전달됨.
    중복도 손실도 없음

  • 보장 수준: 중복 제거 + 재전송 보장

  • 사용 예시: 금융거래, 은행 이체, 재고 차감

  • 장점: 최상의 데이터 무결성

  • 비용 관점:

    • 가장 높은 비용

    • 트랜잭션, idempotent 처리를 위한 상태 관리,
      브로커 + 소비자 모두 상태 추적

    • Kafka의 경우, idempotent producer + transactional consumer 필요

    • 성능 하락, 지연 발생 가능성 증가


💡 선택 기준 요약표

전송 수준

중복

손실

처리 비용

사용 예시

At Most Once

💲 (낮음)

로그, 알림

At Least Once

💲💲 (중간)

주문, 결제

Exactly Once

💲💲💲 (높음)

금융, 정산


🔍 인프라 비용 고려 팁

  • 로그나 비동기 알림At Most Once로 충분 → 비용 절감

  • 결제, 주문처리At Least Once + 중복제거 로직 추가로 타협 가능

  • 회계, 정산Exactly Once 필수지만, 서비스 경계를 좁히면 필요한 범위를 최소화할 수 있음


Kafka를 활용한 Exactly Once전략

Kafka를 단순하게 이용한다고 ExactlyOnce의 목표가 달성되는것이 아니며 성능을 위해 At MostOnce전략을 채택할수도 있으며

Exactly달성을 위해서는 Kafka의 특성을 이해하고 설계자가 활용해야하며  Kafka는 메시지 보장수준에따른 다양한 전략적 기능을 제공해줍니다.


Kafka에서 **Exactly Once Semantics (EOS)**를 구현하기 위해서는 일반적인 at-least-once 또는 at-most-once보다 복잡한 설정과 추가 기술이 필요합니다. 다음은 Kafka에서 Exactly Once 메시지 전송을 보장하기 위해 반드시 이해하고 활용해야 하는 기술 요소들을 요약한 것입니다:


✅ 1. Transactional Producer

  • 기능: 하나 이상의 메시지를 하나의 트랜잭션으로 묶어서 처리.

  • 설정:

    • enable.idempotence=true (필수)

    • transactional.id 지정 (트랜잭션을 식별하는 고유 ID)

  • 역할:

    • 중복 전송 방지 (idempotent producer)

    • 브로커에 정확히 한 번만 기록되도록 보장


✅ 2. Idempotent Producer

  • 기능: 동일한 메시지를 여러 번 전송해도 중복 저장되지 않도록 보장

  • 설정:

    • Kafka 0.11+ 이상 기본 지원

    • enable.idempotence=true 설정 시 활성화됨


✅ 3. Transaction Coordinator

  • Kafka 내부에서 트랜잭션을 관리하는 역할

  • 트랜잭션 범위 내의 쓰기 작업을 commit 또는 abort로 원자적 처리


✅ 4. Transactional Consumer (정확히 Once 소비 보장)

  • 소비자 측에서도 정확히 한 번 처리를 위해 read-process-write 패턴을 트랜잭션과 묶어야 함

  • 일반적으로 consume → process → produce 전 과정을 하나의 Kafka 트랜잭션 내에서 실행


트랜잭션 시작 → 메시지 소비 → 처리 → 결과 produce → offset 커밋 → 트랜잭션 commit
  • Kafka에서는 offset 커밋도 트랜잭션에 포함할 수 있도록 KafkaConsumer를 트랜잭션과 연동함


✅ 5. Exactly Once 설정 요약


설정 항목
enable.idempotencetrue (Producer 필수)
acksall
transactional.id고유한 문자열 지정
isolation.levelread_committed (Consumer 측)
offset 커밋 방식

트랜잭션 내부에서 처리 필요


✅ 6. 주의점 / 제약사항

  • 성능 저하: 트랜잭션 overhead로 인한 처리 속도 감소

  • 브로커 최소 요구사항: Kafka 0.11 이상, 3개 이상의 브로커 권장 (quorum을 통한 안정성)

  • 트랜잭션 시간 제한: 기본 transaction.timeout.ms는 15분

  • 상호작용하는 시스템도 정확히 한번 처리 보장해야 전체 E2E가 Exactly Once가 됨


💡 실무에서 함께 고려해야 할 것

  • Kafka 외부 시스템과 연동 시 (DB, API 등) 정확히 한 번 처리 보장 로직은 별도로 구현 필요

  • EOS는 전체 처리 파이프라인이 트랜잭션을 지원할 때만 실질적인 의미를 가짐


AKKA에서의 Exactly Once 전략

✅ 1. Persistence (Event Sourcing) + At-Least-Once + Idempotency 조합

  • Akka의 기본 메시지 전송은 at-most-once입니다.

  • 하지만 EventSourcedBehavior 또는 DurableStateBehavior 를 이용해 재시도중복 방지(Idempotency key logic)를 구현하면 사실상 Exactly Once에 근접할 수 있습니다.


✅ 2. Deduplication (중복 제거 로직)

  • 메시지마다 고유 ID (UUID or domain sequence) 부여

  • 처리 완료된 ID는 캐시 (예: Redis, LRU Cache)나 저장소에 기록

  • 동일 ID 수신 시 중복 처리 방지


✅ 3. Transactional Outbox 패턴

  • 메시지 처리와 외부 시스템 전송을 단일 트랜잭션으로 묶음

  • DB에 Outbox 테이블을 만들고, 메시지를 먼저 저장

  • 별도 프로세스가 Outbox 테이블을 읽고 메시지 전송 후 처리 상태 업데이트

  • Kafka 등 외부 시스템과 연계 시 유용


✅ 4. Exactly Once Kafka Sink (Kafka + Akka Streams)

  • Kafka의 Transactional Producer + Akka Streams Alpakka Kafka Connector

  • Transactional.sink() 사용

  • 오직 한 번만 메시지가 Kafka에 기록되도록 보장

Transactional
  .source(...)
  .map { msg -> ProducerMessage.single(..., msg.partitionOffset) }
  .via(Transactional.flow(producerSettings, transactionSettings))
  .runWith(Sink.ignore(), materializer)


✅ 5. Pekko DurableStateBehavior with External Store

  • Durable State로 상태를 최신값만 유지

  • DB와의 upsert 트랜잭션으로 메시지 처리 중복 방지

  • 메시지를 처리한 후에만 상태 반영 → 중복 처리 회피


✅ 6. Reliable Delivery (Akka Typed) (Experimental / Pekko 개선 중)

  • Akka Typed의 ReliableDelivery 모듈은 ack 기반의 메시지 재전송 및 순서 보장을 제공

  • 정확히 한 번은 아니지만 적어도 한 번에 기반한 구현을 쉽게 구성 가능


🔐 보조 도구

  • Akka Cluster Sharding + Persistence: 상태 기반 메시지 라우팅과 저장을 결합

  • Timers / Schedulers: 메시지 처리 timeout & 재시도 트리거


결론

Akka 자체는 정확히 "Exactly Once"를 자동으로 보장하지 않습니다. 하지만 다음의 조합을 통해 이를 현실적으로 달성할 수 있습니다:

At-Least-Once delivery + Idempotent business logic + Persistence or transactional write





  • No labels