Files
trex-core/docs/batches/bat-039_20260612_paper-seq-redis-persistence/design.md
T
genichinandClaude Opus 4.8 9871008bb6 fix(paper): BAT-039 — paper order seq Redis 영속화 (재시작 멱등 키 충돌 방지, REQ-011/issue #4)
REQ-011 change-req(CHG-20260612-01)→구현→confirm-req-implemented(CHG-20260612-02)
전체 delivery. paper 한정 결정성 버그(프로덕션 무관).

- seq_store.py 신규: SeqStore 포트 + RedisSeqStore(원자 INCR, 재시작 단조·영속)
  + InMemorySeqStore(폴백/테스트). REQ-016 token_cache 패턴 재사용.
- paper.py: optional seq_store 주입. 미주입 시 기존 in-memory 동작 byte-for-byte 유지.
- config.py/daemon.py: TREX_PAPER_REDIS_URL → RedisSeqStore wiring
  (key=trex:broker:paper:{instance_key}:order-seq).
- 런타임 Redis 오류는 전파(silent in-memory 폴백 금지 — 버그 재현 방지).
- tests: 재시작 단조성/correlation_id 비충돌/in-memory 폴백/fake redis INCR (5).
  434 passed (+5), ruff clean.

REQ-011 Approved→Implemented, BAT-039 release-candidate. issue #4 close.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 18:16:11 +09:00

3.5 KiB

BAT-039 Design

  • Status: confirmed
  • Batch: BAT-039

Architecture Summary

  • paper 어댑터의 order seq 발번을 optional SeqStore 위로 옮긴다. 주입되면 영속 단조 카운터(Redis INCR)를, 미주입이면 기존 in-memory _seq 를 사용한다(byte-for-byte 기존 동작 유지).
  • SeqStore 는 adapters/broker 레이어의 작은 포트로, REQ-016 token_cache.py(Redis best-effort)와 같은 패턴·같은 레이어에 둔다. 도메인은 의존하지 않는다(NFR-1 불변).
  • 데몬은 TREX_PAPER_REDIS_URL 이 설정되면 RedisSeqStore 를 만들어 paper 브로커에 주입한다.

Changed Areas

  • Modules:
    • trex_core/adapters/broker/seq_store.py(신규):
      • SeqStore Protocol — next_seq() -> int
      • RedisSeqStore(redis_url, *, key, redis_module=None) — INCR key 로 원자·영속 단조 정수 반환. from_url(socket_connect_timeout=2)(token_cache 정합).
      • InMemorySeqStore — thread-safe 카운터(테스트/명시 폴백)
    • trex_core/adapters/broker/paper.py — __init__(..., seq_store: Optional[SeqStore] = None); submit_order 에서 seq = self._seq_store.next_seq() (주입 시) / self._seq += 1 (미주입)
    • trex_core/runtime/config.py — PaperBrokerConfig.redis_url: str = "" + from_config TREX_PAPER_REDIS_URL
    • trex_core/runtime/daemon.py — _build_broker: paper + config.paper.redis_url → RedisSeqStore(url, key=f"trex:broker:paper:{config.instance_key}:order-seq") 주입
  • Tests:
    • tests/adapters/broker/test_paper_seq_store.py(신규)
  • Docs: 없음(runtime-flows 흐름 불변 — 발번 영속화는 내부 결정성, 외부 흐름 동일)

Key Decisions

  1. SeqStore.next_seq() -> int 단일 메서드: paper 가 필요로 하는 것은 단조 정수뿐. order_id 포맷(PAPER-{seq:06d})은 paper 책임으로 유지. 포트는 최소.
  2. Redis INCR(원자·영속): GET/SET race 없이 단일 명령으로 단조 보장. 키 부재 시 0→1 로 시작해 첫 order_id 가 PAPER-000001(기존과 동일 시작값).
  3. 키 스코프 = instance_key: trex:broker:paper:{instance_key}:order-seq. 인스턴스별 독립 카운터(다중 인스턴스 교차 방지). 단일 인스턴스(default)는 단일 키.
  4. 폴백 의미 = 설정 부재만: TREX_PAPER_REDIS_URL 미설정 → seq store 미주입 → in-memory _seq(기존 동작). 런타임 Redis 오류는 전파(silent in-memory 폴백 금지 — 그 폴백이 곧 버그 재현). paper 는 테스트 하네스이므로 오류 전파가 안전. (R-1)
  5. token_cache best-effort 와의 차이: 토큰 캐시는 miss/error → 재발급(무해)이라 best-effort 폴백. seq 는 error → 폴백이 곧 멱등 키 충돌이므로 best-effort 폴백을 적용하지 않는다.

Edge Cases

  • seq store 미주입: 기존 self._seq += 1, PAPER-000001부터. AC-1~3 불변.
  • INCR 결과 > 999999: f"{seq:06d}" 는 최소 패딩 → PAPER-1000000 정상 표기, 고유성 유지.
  • 재시작 모사(같은 key 공유 RedisSeqStore/InMemorySeqStore 인스턴스): 두 번째 PaperBroker 가 첫 인스턴스 이후 seq 로 시작 → 매도 order_id ≠ 직전 매수 → correlation_id 비충돌.
  • 동시 submit(스레드): INCR 원자 / InMemorySeqStore 는 lock 으로 보호.

Architecture Impact

  • none (runtime-flows 흐름 불변. 발번 경로 내부 결정성 보강만, 외부 계약/흐름 동일)

Reference Doc Update Plan

  • project-structure.md: none (adapters/broker 에 파일 1개 추가 — 구조 패턴 불변, token_cache 와 동급)
  • runtime-flows.md: none