You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 2 Next »











1. Pekko(Akka)란?

Pekko(Akka의 Apache 포크)Actor 모델에 기반한 고성능 비동기 메시지 기반 시스템을 개발하기 위한 툴킷입니다.

주로 동시성, 분산 처리, 장애 복원성이 필요한 시스템에서 사용됩니다.

전통적인 Java/Kotlin 쓰레드 기반 프로그래밍은 공유 상태와 동기화 문제로 인해 복잡해지기 쉽습니다.

Pekko는 이를 해결하기 위해 다음과 같은 방식을 취합니다:

  • Actor 단위로 상태와 동작을 캡슐화하고, 오직 비동기 메시지로만 통신합니다. 이로 인해 경합 조건(Race Condition) 없이 안전한 상태 관리를 할 수 있습니다.

  • Actor는 경량 프로세스처럼 동작하며 수천, 수만 개도 가볍게 생성할 수 있습니다.

  • Typed API를 통해 메시지 타입을 컴파일 타임에 고정할 수 있어 안정성이 높아지고, 가독성도 향상됩니다.

  • Supervisor 전략을 통해 각 Actor의 오류를 상위 Actor가 감지하고, 재시작 또는 중단 등의 정책으로 시스템 전체를 무너지지 않게 보호합니다.

예를 들어, “은행 계좌”를 하나의 Actor로 보고, 입출금 요청을 메시지로 전달하는 방식으로 동작을 구현할 수 있습니다. 이런 방식은 이벤트 기반 시스템, 채팅 서버, 실시간 알림, IoT 메시지 처리 시스템 등에 효과적으로 적용됩니다.

☑️ Pekko는 Akka의 공식 포크이며, 현재는 Apache 재단에서 관리되고 있습니다.


2. 데이터 처리 로직 최적화 전략

2-A. “100 건 or 3 초” Buffer Flush

트리거

구현 위치

설명

버퍼 크기 ≥ 100

enqueueOrFlushOnThreshold

FLUSH_BUFFER_SIZE = 100일 때,
즉시 Flush 실행

3 초 무입력

enqueueWithTimerTimerScheduler.startSingleTimer

100건이 안 돼도,
3 초 동안 새 로그가 없으면 자동 Flush

이득

  • Redshift 커밋 횟수↓, 네트워크 RTT↓

  • 실시간성 + Bulk 처리


2-B. 비동기 + Fail-Soft 구조

handler.process(batch)          // R2DBC Batch INSERT
  .subscribeOn(Schedulers.boundedElastic()) // JDBC 블로킹 격리
  .timeout(Duration.ofSeconds(5))           // 지연 차단
  .onErrorResume { err -> delegateBatchToChildActors(err, batch) }
  .subscribe()


  • BoundedElastic : Reactor I/O 스레드를 막지 않고 별도 풀에서 DB 호출 수행.

  • 5 초 Timeout : 미응답 배치를 에러로 간주해 Child Actor에게 작업을 위임하여 처리 재시도.
    (응답이 늦어지는 것을 방지)

  • onErrorResume : 예외(에러) 발생시 Child Actor에게 동일 배치를 절반씩 위임해 문제 레코드를 격리/재시도. (최종적으로 문제 레코드 파악 가능)


2-C. Child Worker 2-계층 Retry

단계

Worker 내부 로직

결과

타임아웃·DB 장애

TimeoutException·DataAccessResourceFailureException → 단순 로깅 후 조용히 무시

시스템 폭주 방지 (트래픽 백프레셔 역할)

일반 예외

데이터 n > 1분할 / 재시도(splitAndRetry),
n = 1 → 실패 로그를 남긴 후 종료

원인 레코드 정확히 추적, 최대 log₂ N 회 재귀


2-D. SQL Batch 최적화

  • place-holder 자동 생성


val allValuesPlaceholders = requests.joinToString(", ") {
    (1..columnNames.size).joinToString(" , ", "(", ")") { "\$${placeholderIndex++}" }
}
  • N 행 × 15 컬럼 SQL을 한 줄로 생성 → 서버 ↔ DB Round Trip 1회로 단축 .

  • R2DBC bind : 파라미터 바인딩으로 SQL Injection 방어 및 타입 안전성을 확보.


2-E. 장애 전파 ≠ 스레드 차단

  • Manager-Actor에서 Mono.subscribe() 후 결과를 Fire-and-Forget → 호출 스레드(HTTP) 즉시 반환.

  • 실패해도 Worker가 독립 처리하므로 컨트롤러 타임아웃에 영향을 주지 않고 느슨한 연결을 유지.


2-F 예외·타임아웃 대응 전략 정리

단계

로직

설명

Manager 오류

onErrorResume에서 배치 분할 후,
두 개의 Child Actor (Worker)로 위임

대량 오류 → 폭발 가능성 최소화

Worker 타임아웃

TimeoutException이면 재시도 X, 단순 로깅

과부하 폭주 방지

Worker 일반 오류

절반으로 Split + 재전송 (재귀)

이분 탐색으로 오염 레코드 격리


3. 핵심 클래스 구조 & 동작 개요

Akka (Pekko) 경험이 없는 독자용 요약


계층

클래스로 보는 역할

설명

Application

EventTracerController → Service

HTTP 요청을 Actor 메시지로 변환해 비동기 파이프라인 시작

Actor Root

ActorManager

“스프링 빈 대신 ActorRef” 를 동적 생성해주는 ActorManager. CreateChildActor 메시지를 수신하면 자식 Actorcontext.spawnreplyTo 로 돌려준다

Wiring

AkkaConfiguration

ActorSystem 부트스트랩
② Spring Bean bulkManagerActor 생성 시 Manager에게 Ask-Pattern(미래)로 요청 → ActorRef<BulkManagerActor>를 주입

Domain Actor

BulkManagerActor

버퍼·타이머·에러 위임 담당 상태 머신

  • idle()active() 전이

  • 타이머/버퍼 기준 Flush

  • 예외 시 자식 Worker Actor로 분배

Worker Actor

BulkWorkerActor

실제 DB 호출 + 분할 재시도 담당

  • pipeToSelf로 Mono -> Actor 메시지 변환

  • 이분할 재귀 + 타임아웃 무재시도 정책

💡 Actor 동작 흐름 (텍스트 버전)

  1. ControllerBulkManagerActor.ReceiveData(Data)

  2. Manager : 버퍼 적재 → (Timer or Threshold) → Flush

  3. Flush 성공 : 로그 카운트 기록 후 Idle

  4. Flush 실패 : worker1, worker2에게 절반씩 ProcessBatch

  5. Worker : Mono 결과를 Success or Failure자기 자신에게 전송 → 이분할 재시도 혹은 로그 후 종료

deciduous tree Actor 클래스 구조

ActorSystem
 └─ ActorManager
     └─ BulkManagerActor (부모)
         ├─ BulkWorkerActor#1 (자식)
         └─ BulkWorkerActor#2 (자식)


4. 데이터 처리 순서
























  • No labels