|
← 포트폴리오 목록← Portfolio

Hybrid OrderBook System

On + Off Chain 기반 탈중앙화 하이브리드 오더북 시스템Decentralized hybrid orderbook engine based on On + Off Chain


1. 프로젝트 개요1. Project Overview

현재 개발중인 프로젝트로, 공개 가능한 범위 내에서 제가 담당한 핵심 모듈인 Hybrid OrderBook System에 대한 내용을 다룹니다.This is a confidential project currently under development. While the project name and specific details cannot be disclosed, this portfolio covers the Hybrid OrderBook System, the core module I am responsible for, within the scope that can be shared.

Off-Chain에서 고속 매칭을 수행하고, On-Chain에서 정산의 투명성과 최종성을 보장하는 2-Layer 하이브리드 구조입니다.It is a hybrid 2-layer architecture that performs high-speed matching off-chain while guaranteeing settlement transparency and finality on-chain.

항목Item 내용Details
프로젝트명Project Hybrid OrderBook System
분류Category On + Off Chain 기반 탈중앙화 하이브리드 오더북 시스템Decentralized hybrid orderbook engine based on On + Off Chain
개발 기간Period 2025.10 ~ (개발 진행 중)Oct 2025 ~ (In Development)
역할Role Backend DeveloperBackend Developer (System Design, OrderBook Engine, Concurrency Control, Infrastructure)

2. 프로젝트 배경과 기술 스택2. Background and Tech Stack

순수 On-Chain 오더북은 가스비 부담과 블록 컨펌 지연으로 인해 거래소 수준의 매칭 속도를 달성할 수 없었다. 이를 해결하기 위해 Off-Chain에서 고속 매칭을 수행하고, 정산만 On-Chain에서 처리하는 2-Layer 하이브리드 구조를 설계했다.Pure on-chain orderbooks couldn't achieve exchange-level matching speed due to gas cost burden and block confirmation delays. To solve this, a hybrid 2-layer architecture was designed to perform high-speed matching off-chain while processing only settlement on-chain.

분류Category 기술Technologies
Language / Framework Python, FastAPI
Database MongoDB Atlas (Event Store + View + Outbox)
Infrastructure AWS EKS / Karpenter & HPA / Docker + ECR / GitHub Actions / SQS FIFO / API Gateway
Messaging Transactional Outbox Pattern / SQS FIFO (Relay + Worker)
Architecture DDD + Hexagonal Architecture / Event Sourcing / CQRS / Transactional Outbox
Observability Prometheus / Fluent Bit / Grafana / AWS OpenSearch

기술 선택과 근거Technology Choices and Rationale

기술Technology선택 근거Rationale
SQS FIFO마켓별 주문 순서 보장이 필수. AWS 완전 관리형으로 운영 복잡도를 최소화하고, 금융 시스템 특성상 단순함과 안정성을 중시Market-level order sequencing essential. AWS fully managed service minimizes operational complexity; financial system prioritizes simplicity and stability
MongoDB (Event Store)Append-Only 이벤트 저장에 Document 모델이 적합. RDBMS 대비 스키마 유연성이 높아 이벤트 타입별 다양한 payload 구조 대응 용이Document model suited for Append-Only event storage. Higher schema flexibility than PostgreSQL for diverse payload structures per event type
Transactional Outbox온체인과 오프체인 간 2-Phase 기록 방식은 데이터 정합성을 보장하기 어려워, 온체인 트랜잭션 발행과 DB 쓰기의 원자성을 Outbox 패턴으로 해결2-Phase recording between on-chain and off-chain makes data consistency difficult to guarantee, so Outbox pattern ensures atomicity of on-chain transaction publishing and DB writes
Roll-Forward금융 원장의 Append-Only 원칙 유지. Rollback은 기존 레코드를 수정하여 감사 추적성을 해치므로, Roll-Forward의 보상 이벤트 추가 방식 채택Preserves financial ledger Append-Only principle. Rollback modifies existing records breaking audit trail; adopted Roll-Forward compensating event approach
Event Sourcing + CQRS모든 상태 변경을 Append-Only 이벤트로 기록하여 완전한 감사 추적성(Audit Trail) 확보. 조회 전용 데이터는 View Store로 Projection하여 Command / Query 부하를 분리, 각각 독립적으로 스케일링Records all state changes as Append-Only events for complete audit trail. Read-optimized data projected to View Store, separating Command / Query loads for independent scaling
DDD + Hexagonal금융 서비스 특성상 시스템 안정성과 도메인 순수성이 최우선. Domain(Port 포함) / Service / Entrypoints / Adaptors 계층 분리로 Smart Contract 교체, DB 변경 등 외부 서비스 등의 변경이 실제 비즈니스 도메인 로직에 영향을 주지 않는 유연한 구조 확보System stability and domain purity are paramount for financial services. Layered separation (Domain / Service / Entrypoints / Adaptors) ensures infrastructure-level changes like Smart Contract replacement or DB migration do not affect domain logic

3. 핵심 기술 구현3. Core Technical Implementation

3-1. Hybrid 2-Layer 구조 & 3-Queue 메시지 아키텍처3-1. Hybrid 2-Layer Structure & 3-Queue Message Architecture

서비스의 핵심인 주문 매칭 엔진을 Hybrid OrderBook 구조로 설계했다. Off-Chain에서 주문 매칭과 원장 관리를 수행하고 On-Chain에서 최종 정산(Settlement)을 확정하는 2-Layer 구조이다.The order matching engine was designed as a Hybrid OrderBook — a 2-Layer structure where Off-Chain handles order matching and ledger management while On-Chain confirms final settlement.

Hybrid OrderBook System Architecture

온체인 트랜잭션은 2-of-2 서명(사용자 서명 + 오퍼레이터 지갑 서명)이 필요하며, 오퍼레이터별 논스(Nonce) 충돌 방지를 위해 역할별로 큐를 분리하고 논스를 직접 관리한다.On-chain transactions require 2-of-2 signing (user signature + operator wallet signature), and queues are separated by role with direct nonce management to prevent per-operator nonce conflicts.

Queue유형Type역할Role
Market Queue오프체인 전용Off-chain only마켓별 주문 순차 처리 (SQS FIFO market:{market_id} GroupId)Per-market order sequential processing (SQS FIFO market:{market_id} GroupId)
Admin Operator Queue온체인 전용On-chain onlyMarket 생성 등 시스템 관리자 기능 및 이벤트 제어System admin functions (market creation) and event control
Settlement Operator Queue온체인 전용On-chain only매칭 체결 → 온체인 정산 Task 처리Matched trade → on-chain settlement task processing

3-2. 주문 처리 파이프라인3-2. Order Processing Pipeline

주문 처리의 각 단계(Placed, Matched, Settlement, Cancelled)에서 발생하는 모든 DB 변경은 Unit of Work 패턴을 적용하여 MongoDB Transaction으로 원자적 커밋한다. 비즈니스 데이터 변경과 Outbox Event 기록을 하나의 트랜잭션으로 묶어, 중간 실패 시 전체 롤백되므로 부분 커밋에 의한 데이터 불일치를 원천 방지한다.All DB changes across each stage (Placed, Matched, Settlement, Cancelled) are atomically committed via MongoDB Transaction using the Unit of Work pattern. Business data changes and Outbox Event records are bundled in a single transaction — on mid-failure, full rollback prevents data inconsistency from partial commits.

Placed (주문 등록)Placed (Order Registration)

유효성 검증(잔액, 가격 / 수량 범위, 마켓 상태) 후 주문 금액만큼 availablepending으로 Fund Lock(MongoDB Unique Index 활용)을 수행한다.After validation (balance, price / quantity range, market status), Fund Lock (using MongoDB Unique Index) moves order amount from availablepending.

Matched (매칭)Matched

가격-시간 우선 원칙(Price-Time Priority)에 따라 매칭한다. BUY는 높은 가격 우선, SELL은 낮은 가격 우선이며, 동일 가격 시 먼저 등록된 주문이 우선한다.Matching follows Price-Time Priority. BUY prioritizes highest price, SELL prioritizes lowest price, with earliest order first at same price.

Settlement (정산)Settlement

매칭 확정 후 Off-Chain 원장과 On-Chain 정산을 동기화하는 핵심 파이프라인이다. 3-Queue 메시지 아키텍처(섹션 3-1)의 Settlement Operator Queue를 통해 온체인 정산을 처리한다.The core pipeline synchronizing Off-Chain ledger and On-Chain settlement after matching. On-chain settlement is processed via the Settlement Operator Queue from the 3-Queue message architecture (Section 3-1).

Settlement Sequence Diagram
  1. Market Queue에서 매칭 엔진 실행 → 체결 결과를 in-memory에 보관Market Queue executes matching engine → holds matched results in-memory
  2. Outbox Event Message를 DB에 저장Saves Outbox Event Message to DB
  3. Outbox Relay가 감지 → Settlement Operator Queue에 온체인 정산 Task 발행Outbox Relay detects → publishes on-chain settlement Task to Settlement Operator Queue
  4. 매칭 엔진은 동시에 DB 커밋할 정보를 메모리에서 정리Matching engine simultaneously organizes commit data in memory
  5. DB 커밋 직전에 Outbox Event의 온체인 결과를 폴링 (BSC 기준 최대 30초 타임아웃)Polls on-chain result of Outbox Event before DB commit (BSC-based max 30-second timeout)
  6. 온체인 성공 확인 후 → 오프체인 매칭 체결 DB 커밋 진행. 30초 이상 폴링 실패 시 DB 커밋 없이 종료하여 매칭 취소 처리After on-chain success confirmation → proceeds with off-chain matching DB commit. If polling fails beyond 30 seconds, terminates without DB commit → match cancelled

Failure Recovery — Roll-Forward 전략Failure Recovery — Roll-Forward Strategy

금융 시스템에서 가장 중요한 원칙인 원장 불변성(Append-Only)을 유지하면서 장애를 복구하기 위해, Rollback이 아닌 Roll-Forward 방식을 채택했다.To recover from failures while preserving the most critical principle in financial systems — ledger immutability (Append-Only) — a Roll-Forward approach was adopted instead of Rollback.

Rollback 방식 (미채택):Rollback approach (not adopted):

100(잔액) → 20(출금) → 80(잔액) → 실패 → [기존 레코드 수정] → 100(잔액)

Roll-Forward 방식 (채택):Roll-Forward approach (adopted):

100(잔액) → 20(출금) → 80(잔액) → 실패 → [20(출금 취소 & 입금) 신규 이벤트 추가] → 100(잔액)

기존 원장을 절대 변경하지 않고, 신규 보상 이벤트를 추가하여 실패를 처리한다. 이를 통해 모든 거래 이력의 감사 추적성(Audit Trail)을 완벽하게 보존한다.The existing ledger is never modified. Failures are handled by appending compensating events, perfectly preserving the Audit Trail of all transaction history.

유형Type 처리 방식Handling
일시적 실패Transient failure Exponential Backoff 재시도 (최대 N회)Exponential Backoff retry (up to N times)
영구적 실패Permanent failure 최대 N회 초과 시 → 거래 중지 → 개발팀 알림 전송 & 수기 대응Exceeds max N retries → halt trading → alert dev team & manual intervention

Cancelled (주문 취소)Cancelled (Order Cancellation)

미체결 또는 부분 체결 주문에 대해 오더북 제거 → Fund Lock 해제(pendingavailable) → Outbox를 통해 취소 이벤트 발행의 순서로 처리한다.For unfilled or partially filled orders: remove from orderbook → release Fund Lock (pendingavailable) → publish cancel event via Outbox.

3-3. 동시성 제어 설계3-3. Concurrency Control Design

금융 서비스에서 동시성 문제는 자산 이중 차감, 중복 체결 등 치명적인 장애로 직결된다. SQS FIFO와 MongoDB를 활용하여 User-Level과 Market-Level 양쪽에서 동시성을 제어했다.In financial services, concurrency issues directly lead to critical failures such as double-deduction of assets and duplicate fills. Concurrency is controlled at both User-Level and Market-Level using SQS FIFO and MongoDB.

User-Level 동시성 제어User-Level Concurrency Control

기법Technique 구현Implementation 목적Purpose
멱등성 검증Idempotency MongoDB Unique Index (idempotency_key) 동일 요청의 중복 실행 방지 (네트워크 재시도 등)Prevent duplicate execution of identical requests (e.g. network retries)
Distributed Lock MongoDB Unique Index (lock:<USER_ID>) + TTL 동일 사용자의 동시 주문 요청 직렬화Serialize concurrent order requests from the same user

Market-Level 동시성 제어Market-Level Concurrency Control

하나의 마켓에 동시에 들어오는 다수의 주문이 매칭 순서를 엄격하게 보장해야 한다. 이를 위해 SQS FIFO QueueMessageGroupId를 활용했다.Multiple orders arriving simultaneously in a single market must strictly guarantee matching order. SQS FIFO Queue's MessageGroupId was leveraged for this.

3-4. Transactional Outbox 패턴3-4. Transactional Outbox Pattern

도입 배경Background

분산 시스템에서 "DB에 저장 → 메시지 브로커에 발행"을 두 단계로 처리하면, 중간에 장애가 발생할 경우 DB는 커밋되었으나 메시지는 유실되는 불일치가 생긴다.
또한 이러한 2-Phase Commit은 동기 순차 실행으로 성능 저하가 크고 브로커가 지원하지 않는 경우도 많다.
Transactional Outbox 패턴을 도입하여, DB 쓰기와 이벤트 발행의 원자성을 보장했다.
In distributed systems, handling "save to DB → publish to broker" in two steps can cause inconsistency where DB is committed but the message is lost if a failure occurs in between. 2PC (2-Phase Commit) has significant performance overhead and is often unsupported by brokers. The Transactional Outbox pattern was introduced to guarantee atomicity between DB writes and event publishing.

각 사이드별 역할Role of Each Side

Market Queue Worker Side

매칭 엔진이 체결 결과를 in-memory에 보관한 채, Outbox Event만 먼저 DB에 저장한다. 온체인 정산 결과가 확인된 후에 비로소 비즈니스 데이터를 DB에 커밋한다. 브로커를 직접 호출하지 않기 때문에 브로커 장애에 영향을 받지 않는다.The matching engine holds matched results in memory, saving only the Outbox Event to DB first. Business data is committed to DB only after on-chain settlement is confirmed. Since the broker is never called directly, broker failures have no impact.

# Pseudocode — Market Queue Worker
async def process_matched_order(matched_result):
    # 1. 체결 결과를 in-memory에 보관
    commit_data = matched_result.prepare_commit()

    # 2. Outbox Event만 먼저 DB에 저장
    outbox_event = OutboxEvent(
        aggregate_id=matched_result.order_id,
        event_type="ORDER_MATCHED",
        payload=matched_result.to_settlement_payload(),
        idempotency_key=matched_result.idempotency_key,
        published_at=None,  # 미발행 상태
    )
    await outbox_col.insert_one(outbox_event)

    # 3. 온체인 Task 수행 동안, DB 커밋에 필요한 데이터를 미리 정리
    commit_data.prepare_ledger_entries()
    commit_data.prepare_balance_updates()

    # 4. Relay가 온체인 정산 발행 → 온체인 결과 폴링 (최대 30초)
    on_chain_result = await poll_on_chain_result(
        outbox_event.id, timeout=30
    )

    # 5. 온체인 성공 확인 후 비즈니스 데이터 커밋
    if on_chain_result.success:
        async with db.transaction() as session:
            await orders_col.update_one(commit_data, session=session)
            await ledger_col.insert_one(commit_data.ledger, session=session)
    else:
        # 폴링 실패 → 매칭 취소 처리
        raise SettlementTimeoutError()

Relay Side (OutboxRelay)

별도 프로세스로 동작하며, published_atNone인 미발행 이벤트를 주기적으로 폴링하여 SQS FIFO에 Publish한 뒤 발행 완료를 마킹한다.Runs as a separate process, periodically polling unpublished events (published_at == None), publishing them to SQS FIFO, and marking them as published.

# Pseudocode — Relay Side
async def relay_loop():
    while True:
        events = await outbox_col.find(
            {"published_at": None},
            sort=[("created_at", 1)],
            limit=BATCH_SIZE,
        )

        for event in events:
            await sqs.send_message(
                queue_url=FIFO_QUEUE_URL,
                message_body=event.payload,
                message_group_id=event.aggregate_id,
                message_deduplication_id=str(event.id),
            )
            await outbox_col.update_one(
                {"_id": event.id},
                {"$set": {"published_at": utcnow()}},
            )

        await asyncio.sleep(POLLING_INTERVAL)

Worker Side (Consumer)

SQS FIFO에서 메시지를 수신하여 후속 처리를 실행한다. Relay가 미발행 이벤트(published_at: null)를 주기적으로 재폴링하여 at-least-once 전달을 구현하므로, 중복 메시지 수신 가능성에 대비하여 processed_events Collection의 Unique Index로 멱등성 체크를 수행한다.Receives messages from SQS FIFO for downstream processing. Since Relay implements at-least-once delivery by periodically re-polling unpublished events (published_at: null), idempotency checks are performed via processed_events Collection Unique Index to handle potential duplicates.

# Pseudocode — Worker Side
async def process_message(message):
    event = parse(message.body)

    # 1. 멱등성 체크
    try:
        await processed_col.insert_one(
            {"event_id": event.id, "processed_at": utcnow()}
        )  # Unique Index on event_id
    except DuplicateKeyError:
        return  # 이미 처리된 이벤트 → Skip

    # 2. 후속 처리 (Settlement, Notification 등)
    match event.event_type:
        case "ORDER_MATCHED":
            await settlement_service.execute(event)
        case "ORDER_CANCELLED":
            await notification_service.notify(event)
        case "SETTLEMENT_COMPLETED":
            await balance_service.confirm(event)

3-5. Event Sourcing + CQRS3-5. Event Sourcing + CQRS

Event Store + View ProjectionEvent Store + View Projection

모든 주문 / 매칭 / 정산 이벤트를 Append-Only Event Store에 기록하고, 조회 전용 데이터는 별도의 View Store로 Projection하는 CQRS 패턴을 적용했다.All order / matching / settlement events are recorded in an Append-Only Event Store, and read-only data is projected to a separate View Store using the CQRS pattern.

Event Store:

View Store:

적용 예시:Example:

4. 성과 요약4. Results Summary

영역Area 성과Achievement
매칭 속도Matching Speed Full On-Chain 대비 평균 체결 시간 10~15초 → 3~5초로 단축 (약 3배 개선)Average matching time reduced from 10–15s → 3–5s compared to full on-chain (approx. 3× improvement)
가스비 절감Gas Cost Reduction 주문 등록·매칭·취소의 온체인 처리를 제거하고 최종 정산만 온체인 기록 → 트랜잭션당 가스비 약 1/3 수준으로 절감Eliminated on-chain processing for order placement, matching, and cancellation — only final settlement recorded on-chain → gas cost reduced to approx. 1/3 per transaction
OrderBook Engine Off-Chain 매칭 + On-Chain 정산 Hybrid 구조로, 매칭 속도와 정산 신뢰성 동시 확보Hybrid Off-Chain matching + On-Chain settlement — achieving both matching speed and settlement reliability
이벤트 신뢰성Event Reliability Transactional Outbox 패턴으로 DB 쓰기와 이벤트 발행의 원자성 보장, 메시지 유실 원천 차단Transactional Outbox pattern guarantees atomicity of DB writes and event publishing, eliminating message loss
동시성 제어Concurrency SQS FIFO 마켓별 순서 보장 + MongoDB Unique Index 기반 멱등성 / Distributed LockSQS FIFO per-market ordering + MongoDB Unique Index-based idempotency / Distributed Lock
장애 복구Failure Recovery Append-Only 원장 + Roll-Forward 전략으로 데이터 무결성 보존Append-Only ledger + Roll-Forward strategy preserving data integrity
정산 안전성Settlement Safety BSC 온체인 컨펌 30초 타임아웃, 실패 시 DB 커밋 없이 매칭 취소로 자산 안전 보장BSC on-chain confirm 30-second timeout; on failure, match cancelled without DB commit ensuring asset safety
논스 복구Nonce Recovery 제로 서명(빈 서명) 메커니즘으로 펜딩 논스 강제 소비, 오퍼레이터 큐 정상화Zero signature (empty signature) mechanism for force-consuming pending nonces, normalizing operator queues
아키텍처Architecture DDD + Hexagonal Architecture로 도메인 순수성 확보, 인프라 변경에 대한 유연성DDD + Hexagonal Architecture for domain purity and infrastructure flexibility
데이터 설계Data Design Event Sourcing + CQRS로 완전한 감사 추적성 및 Command / Query 독립 스케일링Event Sourcing + CQRS for complete audit trail and independent Command / Query scaling

← 포트폴리오 목록← Portfolio