Skip to content

[HSC-424] user-log admin dispatch outbox 저장 및 재처리 경로 구성#280

Merged
tkv00 merged 2 commits into
mainfrom
perf/HSC-424
Apr 1, 2026
Merged

[HSC-424] user-log admin dispatch outbox 저장 및 재처리 경로 구성#280
tkv00 merged 2 commits into
mainfrom
perf/HSC-424

Conversation

@tkv00
Copy link
Copy Markdown
Contributor

@tkv00 tkv00 commented Apr 1, 2026

📝작업 내용

Burst 부하 구간에서 AbortPolicy 이후 드러난 rejected 건을 유실로 두지 않기 위한 저장 및 재처리 구조 구성.

문제 상황

  • AbortPolicy 적용 이후 user-log request path는 보호됐지만 admin-log-feature executor 포화 시 enqueue reject가 지속적으로 발생
  • reject 건은 metric으로만 확인 가능했고, 어떤 이벤트가 실패했는지 추적하거나 재처리할 수 있는 저장 구조 부재
  • 결과적으로 admin dispatch 실패가 최종 feature 반영 유실로 바로 이어지는 구조 유지

해결 과정

단계 내용 의도
실패건 영속화 admin dispatch 대상을 outbox row로 저장 reject와 HTTP 실패를 복구 가능한 상태로 전환
상태 관리 READY / PROCESSING / ACKED / RETRY / DEAD 정의 재처리 흐름과 종료 상태 명확화
재전달 분리 scheduler가 READY, RETRY를 batch claim 후 재전달 request path와 retry path 분리
결과 반영 admin HTTP 결과에 따라 ACKED, RETRY, DEAD 갱신 성공/실패 후속 처리 일관화
계측 보강 dispatch, outbox lifecycle metric 추가 저장, 재시도, 종료 상태 관측 가능화
구분 내용 목적
실패건 저장 user_log_admin_dispatch_outbox 도입 reject 및 dispatch 실패건 영속화
상태 전이 READY / PROCESSING / ACKED / RETRY / DEAD 구성 재처리 대상 구분
재전달 경로 scheduler 기반 batch dispatch request path와 retry path 분리
결과 판별 admin HTTP 결과 객체 반환 ACK / RETRY 결정 기준 명확화
계측 보강 dispatch / outbox metric 추가 enqueue, 저장, 재시도, 종료 상태 관측
설정 추가 batch size, retry delay, max attempts 외부화 운영 환경별 조정 여지 확보

구조도

flowchart LR
    A["UserLogService"] --> B["Kafka publish"]
    A --> C["Outbox 저장"]

    D["UserLogAdminDispatchScheduler"] --> E["READY / RETRY 조회"]
    E --> F["AdminLogFeatureDispatchService"]
    F --> G["AdminLogFeaturesClient"]
    G --> H{"dispatch 결과"}

    H -->|success| I["ACKED"]
    H -->|failure| J["RETRY / DEAD"]

    style C fill:#eef8ef,stroke:#5a9c67
    style D fill:#eef4ff,stroke:#5b80b7
    style I fill:#eef8ef,stroke:#5a9c67
    style J fill:#fff6ea,stroke:#c58f48
Loading

👀변경 사항

구현 항목

항목 파일 내용
outbox 상태 enum src/main/java/site/holliverse/customer/persistence/entity/UserLogDispatchStatus.java dispatch 상태 정의
outbox entity src/main/java/site/holliverse/customer/persistence/entity/UserLogAdminDispatchOutbox.java 실패건 저장 모델
outbox repository src/main/java/site/holliverse/customer/persistence/repository/UserLogAdminDispatchOutboxRepository.java FOR UPDATE SKIP LOCKED 조회
outbox 저장/제출 src/main/java/site/holliverse/customer/application/usecase/log/UserLogAdminDispatchOutboxService.java 저장, enqueue, reject 처리
outbox 상태 갱신 src/main/java/site/holliverse/customer/application/usecase/log/UserLogAdminDispatchOutboxStateService.java ACK / RETRY / DEAD 갱신
scheduler src/main/java/site/holliverse/customer/application/usecase/log/UserLogAdminDispatchScheduler.java 주기적 재전달
dispatch 연동 src/main/java/site/holliverse/customer/application/usecase/log/AdminLogFeatureDispatchService.java outbox row 기반 dispatch
user log 연동 src/main/java/site/holliverse/customer/application/usecase/log/UserLogService.java direct HTTP 제거, outbox 저장 전환
HTTP 결과 반환 src/main/java/site/holliverse/customer/integration/external/AdminLogFeaturesClient.java success / failure 결과 객체 반환
metric src/main/java/site/holliverse/shared/monitoring/CustomerMetrics.java outbox lifecycle metric 추가
runtime 설정 src/main/java/site/holliverse/shared/config/runtime/CustomerRuntimeInfraConfiguration.java scheduling 활성화
customer 설정 src/main/resources/application-customer.yml dispatch retry 설정 추가
migration src/main/resources/db/migration/V33__create_log_dispatch_outbox.sql outbox table 및 index 정의

🎫 Jira Ticket

  • Jira Ticket: HSC-424

#️⃣관련 이슈


@tkv00 tkv00 added 🗂️ area: BE 백엔드 영역 🔥 priority: P0 즉시 처리 필요(서비스/데모 블로커) ⚡ perf 성능 개선(쿼리/캐시/병목 제거 등) 🏷️ release 릴리즈 준비/버전 태깅/릴리즈 노트/릴리즈 브랜치 작업 release:minor 버전 minor bump: X.Y.0 deploy:api-server 배포 대상: customer-api Customer Team labels Apr 1, 2026
@github-actions github-actions Bot changed the title user-log admin dispatch outbox 저장 및 재처리 경로 구성 [HSC-424] user-log admin dispatch outbox 저장 및 재처리 경로 구성 Apr 1, 2026
@tkv00 tkv00 merged commit 8c6d92a into main Apr 1, 2026
11 checks passed
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 1, 2026

🧪 Test Coverage Report (JaCoCo)

overall__line overall__branch changed__line changed__branch

기준(soft, workflow는 실패 안 함): Overall line 70% / branch 50%, Changed line 80% / branch 60% · Generated: 2026-04-01 03:21 UTC

Scope Line Branch Line Graph Branch Graph Verdict
Overall 38.8% 28.8% ████████░░░░░░░░░░░░ ██████░░░░░░░░░░░░░░ ⚠️⚠️
Changed 2.5% 0.0% ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░ ⚠️⚠️

Package line spark (sample): ▃▂▁▂▂▄▄·▇█▁▆▆▆·▆▁▁▆█▇▃▁▁▁██▁▁▃
Package branch spark (sample): ▃▁·▁▁·▁·▇··▅▆▅·▅▁▁▅▇▇▂▁·▁·█▁▁▁

📦 Package coverage (worst 10)

Rank Package Line Branch Lines Line Graph Branch Graph
1 site.holliverse.customer.application.usecase.log 0.0% 0.0% 146 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
2 site.holliverse.shared.monitoring 0.0% N/A 89 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
3 site.holliverse.customer.coupon.application 0.0% 0.0% 61 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
4 site.holliverse.infra.kafka.consumer 0.0% 0.0% 44 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
5 site.holliverse.customer.integration.external 0.0% 0.0% 30 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
6 site.holliverse.customer.integration.fastapi 0.0% 0.0% 28 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
7 site.holliverse.customer.application.usecase.counsel 0.0% 0.0% 14 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
8 site.holliverse.customer.coupon.web 0.0% 0.0% 12 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
9 site.holliverse.shared.config.runtime 0.0% N/A 11 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
10 site.holliverse.customer.web.util 0.0% 0.0% 8 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░

🧨 Lowest coverage classes (worst 10)

Rank Class Line Branch Lines Line Graph Branch Graph
1 site.holliverse.shared.monitoring.CustomerMetrics 0.0% N/A 62 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
2 site.holliverse.customer.application.usecase.log.UserLogService 0.0% 0.0% 55 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
3 site.holliverse.infra.kafka.consumer.RecommendationKafkaConsumer 0.0% 0.0% 44 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
4 site.holliverse.customer.application.usecase.recommendation.RecommendationKafkaConsumeUseCase 0.0% 0.0% 42 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
5 site.holliverse.admin.application.usecase.CalculateLogChurnScoreService 0.0% 0.0% 38 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
6 site.holliverse.admin.application.usecase.CalculateChurnScoreService 0.0% 0.0% 37 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
7 site.holliverse.customer.coupon.application.UseMemberCouponUseCase 0.0% 0.0% 34 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
8 site.holliverse.customer.application.usecase.log.UserLogAdminDispatchOutboxService 0.0% 0.0% 33 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
9 site.holliverse.admin.application.usecase.ChurnRiskReason$ReasonCode 0.0% 0.0% 31 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
10 site.holliverse.customer.integration.external.AdminLogFeaturesClient 0.0% 0.0% 30 ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
🧩 Changed files coverage breakdown (11 files)
File Line Branch Lines Verdict Line Graph Branch Graph
src/main/java/site/holliverse/customer/application/usecase/log/AdminLogFeatureDispatchService.java 0.0% 0.0% 16 ⚠️⚠️ ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
src/main/java/site/holliverse/customer/application/usecase/log/UserLogAdminDispatchOutboxService.java 0.0% 0.0% 33 ⚠️⚠️ ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
src/main/java/site/holliverse/customer/application/usecase/log/UserLogAdminDispatchOutboxStateService.java 0.0% 0.0% 23 ⚠️⚠️ ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
src/main/java/site/holliverse/customer/application/usecase/log/UserLogAdminDispatchScheduler.java 0.0% N/A 2 ⚠️ ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
src/main/java/site/holliverse/customer/application/usecase/log/UserLogService.java 0.0% 0.0% 55 ⚠️⚠️ ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
src/main/java/site/holliverse/customer/integration/external/AdminLogFeaturesClient.java 0.0% 0.0% 30 ⚠️⚠️ ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
src/main/java/site/holliverse/customer/persistence/entity/UserLogAdminDispatchOutbox.java 0.0% N/A 17 ⚠️ ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
src/main/java/site/holliverse/shared/monitoring/CustomerMetrics.java 0.0% N/A 62 ⚠️ ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
src/main/java/site/holliverse/customer/persistence/entity/UserLogDispatchStatus.java 100.0% N/A 6 ✅✅ ████████████████████ ░░░░░░░░░░░░░░░░░░░░
src/main/java/site/holliverse/customer/persistence/repository/UserLogAdminDispatchOutboxRepository.java N/A N/A 0 ✅✅ ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░
src/main/java/site/holliverse/shared/config/runtime/CustomerRuntimeInfraConfiguration.java N/A N/A 0 ✅✅ ░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░

🔎 HTML 리포트: Actions → Artifacts → jacoco-html · XML: jacoco-xml

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이 PR은 Admin 로그 발송에 아웃박스 패턴을 도입하여 시스템의 안정성과 재시도 메커니즘을 강화했습니다. 주요 변경 사항으로 아웃박스 엔티티 및 테이블 생성, FOR UPDATE SKIP LOCKED를 활용한 배치 스케줄러 구현, 그리고 기존 UserLogService의 직접 발송 로직을 큐잉 방식으로 전환하는 작업이 포함되었습니다. 리뷰 결과, 아웃박스 테이블의 무한 증식을 방지하기 위한 데이터 정리(Cleanup) 로직 추가, 예외 발생 시 디버깅을 위한 로그 보강, 그리고 배치 조회 시 DB 라운드트립을 줄이기 위한 성능 최적화가 필요합니다.

Comment on lines +9 to +24
CREATE TABLE user_log_admin_dispatch_outbox (
event_id BIGINT PRIMARY KEY,
member_id BIGINT NOT NULL,
event_name VARCHAR(200) NOT NULL,
event_type VARCHAR(100) NOT NULL,
event_timestamp TIMESTAMPTZ NOT NULL,
payload JSONB NOT NULL,

status user_log_dispatch_status NOT NULL DEFAULT 'READY',
attempt_count INTEGER NOT NULL DEFAULT 0,
next_retry_at TIMESTAMPTZ,
last_error TEXT,

created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

아웃박스 테이블(user_log_admin_dispatch_outbox)에 처리 완료(ACKED)되거나 재시도 횟수를 초과한(DEAD) 데이터에 대한 삭제 또는 아카이빙 로직이 부재합니다. 데이터가 무한히 쌓일 경우 스케줄러의 조회 성능이 점진적으로 저하될 수 있으므로, 주기적인 정리(Cleanup) 로직 도입이 필요합니다.

Comment on lines +57 to +59
} catch (Exception e) {
customerMetrics.recordAdminLogFeatureOutbox("store_error");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

모든 예외(Exception)를 catch하면서 메트릭만 기록하고 에러 로그를 남기지 않고 있습니다. DB 연결 오류나 직렬화 실패 등 예기치 못한 장애 발생 시 원인 파악이 매우 어려울 수 있습니다. 최소한 에러 메시지와 스택 트레이스를 로깅하도록 개선이 필요합니다.

        } catch (Exception e) {
            log.error("[UserLog][Outbox] Failed to enqueue admin dispatch outbox eventId={}", eventId, e);
            customerMetrics.recordAdminLogFeatureOutbox("store_error");
        }

Comment on lines +33 to +40
List<Long> ids = repository.findReadyEventIdsForUpdate(batchSize);
if (ids.isEmpty()) {
return ids;
}

List<UserLogAdminDispatchOutbox> rows = repository.findAllById(ids);
rows.forEach(UserLogAdminDispatchOutbox::markProcessing);
repository.saveAll(rows);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

현재 claimReadyBatch 로직은 ID 조회 후 다시 전체 엔티티를 조회(findAllById)하고 상태를 변경하여 저장하는 3단계 DB 라운드트립이 발생합니다. 성능 최적화를 위해 네이티브 쿼리에서 엔티티 리스트를 직접 반환받거나, 벌크 업데이트 쿼리를 사용하는 방안을 권장합니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🗂️ area: BE 백엔드 영역 Customer Team deploy:api-server 배포 대상: customer-api ⚡ perf 성능 개선(쿼리/캐시/병목 제거 등) 🔥 priority: P0 즉시 처리 필요(서비스/데모 블로커) release:minor 버전 minor bump: X.Y.0 🏷️ release 릴리즈 준비/버전 태깅/릴리즈 노트/릴리즈 브랜치 작업

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant