RT-O5: 실시간 재고 동기화 (Kafka + InventoryHub)

🎯 다채널 재고 동기화란?Real-time Inventory Sync · Event-Driven · ATP Consistency

다채널(쇼핑몰·마켓·POS) 재고 운영은 본질적으로 race condition이다. WMS에서 1개 차감되는 동시에 POS가 1개를 팔고, 마켓플레이스가 1개를 reserve하면 동일 SKU가 동시에 3번 commit되어 Oversell(초과 판매)이 발생한다. 폴링(N분 주기 스냅샷) 방식으로는 이 race를 막을 수 없다.

RT-O5는 이 문제를 해결하기 위해 4개 Kafka topic으로 모든 mutation을 단일 스트림으로 모은 뒤, 중앙 InventoryHub ATP(Available To Promise)를 계산해 모든 채널에 broadcast한다. Idempotent consumer + exactly-once semantics + per-SKU partitioning으로 순서를 보존하고, 가상 재고 풀(Virtual Inventory Pool, VIP)로 거점·채널별 가용 재고를 단일 풀로 추상화한다. 3월 4주차 기준 Oversell 0건, p95 sync latency 38ms, 채널 재고 불일치 0.04%를 달성했다.

🧭 InventoryHub 동기화 파이프라인5 stages · channel mutation → broadcast
STEP 1
event_emit

WMS / POS / OMS / 반품 시스템에서 mutation 이벤트 발행 (각자 topic). 발행자는 InventoryHub 존재를 알 필요 없음.

STEP 2
kafka_route

SKU 기반 partitioning · ordering 보장 · idempotent producer. 동일 SKU는 항상 같은 partition.

STEP 3
inventory_hub

Flink/Faust streaming consumer · ATP 계산 · 가상 재고 풀(VIP) 갱신. exactly-once 처리.

STEP 4
state_store

RocksDB / Redis 상태 저장 · 30일 TTL · compaction · key=SKU, value=ATP snapshot.

STEP 5
broadcast

모든 채널 SDK / API에 실시간 가용 재고 푸시 (WebSocket / Webhook). p95 38ms.

🐍 Kafka Topic 정의 (Python)
# Kafka 기반 이벤트 드리븐 재고 동기화
TOPICS = [
    "inventory.wms.updated",     # WMS 재고 변동
    "inventory.pos.sold",        # POS 판매
    "inventory.order.reserved",  # OMS 예약
    "inventory.order.released",  # 주문 취소/반품
]
# 모든 채널 재고 이벤트를 중앙 InventoryHub로 집계
# InventoryHub -> 전 채널에 최신 가용 재고 broadcast

def compute_atp(sku):
    snap = state_store.get(sku)
    return snap.on_hand - snap.reserved - snap.allocated + snap.in_transit

4개 topic은 모두 partition 16(SKU hash 기반)으로 운영하며, idempotent producer(enable.idempotence=true) + transactional consumer로 exactly-once를 보장한다. broker는 3대 replication factor 3으로 durability 확보.

➗ ATP 공식 & 가중치Available To Promise · Oversell risk
ATP_t = OnHandReservedAllocated + InTransit
Oversell_risk = max(0, Σ_channel committedATP)
# ATP 일관성이 깨지면 Oversell_risk > 0 → 모든 채널 broadcast로 즉시 보정
wms 0.30inventory.wms.updated · WMS의 입고/출고/이동/실사 결과로 OnHand 갱신. 가장 큰 변동량 차지
pos 0.30inventory.pos.sold · 매장 POS 판매로 OnHand 즉시 차감. 초당 수백 건 burst 발생
oms 0.30inventory.order.reserved · 온라인 주문 reserve로 Reserved 증가. 결제 완료 시 Allocated 전환
release 0.10inventory.order.released · 취소/반품으로 Reserved 해제. 가장 빈도 낮음

예) SKU-A의 OnHand=10, Reserved=3, Allocated=2, InTransit=5라면 ATP = 10 − 3 − 2 + 5 = 10이다. 이 시점 5개 채널이 동시에 11개를 commit하려 하면 Oversell_risk = 11 − 10 = 1로 1건 reject되고 InventoryHub가 즉시 모든 채널에 ATP=0으로 broadcast한다.

📡 채널별 mutation rate · sync latency (3월 4주차)N=4채널 합산 평균 870 ops/s · 7일 측정
채널평균 mutation ratesync p95재고 정확도주요 이벤트
WMS230 ops/s22ms99.99%입고/출고/이동/실사
POS180 ops/s31ms99.97%매장 판매 즉시 차감
마켓플레이스480 ops/s38ms99.96%쿠팡·네이버·11번가 reserve burst
B2B EDI80 ops/s64ms99.94%대량 주문(EDI 850) batch

마켓플레이스가 가장 큰 mutation rate(480 ops/s)를 차지하며 burst가 잦아 partition 추가가 중요하다. B2B EDI는 batch성이라 latency가 64ms로 다소 높으나 정확도는 99.94%로 안정. WMS는 가장 빠른 22ms로 동작하며 재고 정확도 99.99%로 baseline 역할을 한다.

참고문헌
[1] 김지훈, "실시간 재고 동기화 (Kafka + InventoryHub)", IntraLogis 사내 보고서, 2026.03
[2] Apache Kafka, Apache Kafka 공식 문서, 2025
[3] Confluent, "Streaming ATP Patterns", Confluent Blog, 2024
[4] Apache Flink, Apache Flink Stateful Streaming Documentation, 2025
[5] Kleppmann, M. "Designing Data-Intensive Applications", O'Reilly, 2017
[6] Facebook, RocksDB 공식 문서, 2025
[7] 박소연 · 김지훈, WMS-OMS 통합 협력 보고서, IntraLogis 사내, 2026.02