Files
variet_llm/scripts/_archive/results/llm_judge_answers.json
Variet-Worker c111b3a9b0 feat: Variet Engine v1.0 + 5-model tuning complete
Phase 01 (LLM Tuning):
- Gemma4 26B: 74.65 t/s (fast)
- Qwen 35B: 61.62 t/s (balanced)
- Gemma4 31B: 16.0 t/s (deep-coder)
- Qwen 27B: 16.7 t/s (deep-logic)
- Qwen 122B: 8.95 t/s (ultra, GPU 1 only)

Phase 02 (API Engine):
- FastAPI reverse proxy on port 8000
- /engine/switch hot-swap with 503 protection
- config/engine_models.json as single source of truth
- Replaced 4 individual .bat files with unified engine

File cleanup:
- scripts/ 85 files -> 9 + _archive/
- Root .bat files -> _archive/
2026-04-07 18:08:58 +09:00

12 lines
34 KiB
JSON

{
"Qwen 27B": {
"architecture": "수백만 건의 실시간 틱 (Tick) 데이터 처리와 초당 수천 명의 클라이언트 브로드캐스팅은 **고처리량 (High Throughput)**, **저지연 (Low Latency)**, **고가용성 (High Availability)** 이 동시에 요구되는 매우 까다로운 아키텍처 문제입니다.\n\n이 문제를 해결하기 위해 **Go (Golang)** 를 기반으로 한 이벤트 드리븐 아키텍처를 제안하며, 각 계층별 기술 스택과 병목 현상 해결책을 상세히 설명하겠습니다.\n\n---\n\n## 1. 전체 아키텍처 개요 (High-Level Architecture)\n\n시스템은 크게 **Ingestion (수신)**, **Processing (가공)**, **Distribution (배포)**, **Storage (저장)** 의 4 단계로 나뉩니다. 핵심은 \"Hot Path(실시간 경로)\"와 \"Cold Path(히스토리 경로)\"를 분리하여 실시간 성능을 보장하는 것입니다.\n\n```mermaid\ngraph TD\n subgraph \"Market Data Feeds\"\n A[Exchange API / FIX Protocol]\n end\n\n subgraph \"Ingestion Layer\"\n B[Ingestion Service (Go)]\n end\n\n subgraph \"Processing & Messaging\"\n C[NATS JetStream (Pub/Sub)]\n D[Stream Processor (Go/Flink)]\n end\n\n subgraph \"Distribution Layer\"\n E[WebSocket Gateway Cluster]\n F[Redis Cluster (Pub/Sub & Cache)]\n end\n\n subgraph \"Storage Layer\"\n G[TimescaleDB (Time-Series)]\n H[S3 (Cold Data)]\n end\n\n subgraph \"Clients\"\n I[Web/Mobile Clients]\n end\n\n A --> B\n B --> C\n C --> D\n D --> C\n C --> E\n E --> I\n D --> G\n G --> H\n E -.-> F\n```\n\n---\n\n## 2. 기술 스택 선정 및 이유\n\n### 1) 프로그래밍 언어: **Go (Golang)**\n* **이유:** C++ 에 버금가는 성능과 Python/Java 보다 가벼운 메모리 사용량. 특히 **Goroutine** 모델이 수만 개의 동시 연결 (WebSocket) 을 처리하는 데 최적화되어 있습니다.\n* **라이브러리:** `FastHTTP` (HTTP/WebSocket 성능 최적화), `gRPC` (내부 서비스 통신).\n\n### 2) 메시지 큐: **NATS (JetStream)**\n* **이유:** Kafka 는 내구성은 좋으나 지연 시간이 상대적으로 높을 수 있습니다. **NATS**는 금융 거래 시스템에서 널리 쓰이며, **Pub/Sub** 패턴에 최적화되어 있고 지연 시간이 마이크로초 (μs) 단위입니다. JetStream 기능을 통해 내구성과 재전송도 보장합니다.\n* **대안:** 극한의 저지연이 필요하면 **UDP 기반 커스텀 프로토콜**을 고려할 수 있으나, NATS 가 개발 효율성과 성능의 균형을 잘 맞춥니다.\n\n### 3) 데이터베이스: **TimescaleDB + Redis**\n* **TimescaleDB (PostgreSQL 기반):** 주식 틱 데이터는 시계열 데이터입니다. TimescaleDB 는 PostgreSQL 의 안정성을 유지하면서 시계열 데이터에 특화된 압축 및 쿼리 성능을 제공합니다.\n* **Redis Cluster:**\n * **Hot Data:** 현재 가격 (Last Price), 호가 (Order Book) 등 가장 최신 데이터 저장.\n * **Session:** WebSocket 연결 상태 관리.\n * **Pub/Sub:** WebSocket 게이트웨이 간 브로드캐스팅 동기화.\n\n### 4) 캐싱 전략: **Multi-Level Caching**\n* **L1 Cache (In-Memory):** 각 WebSocket 서버 인스턴스 내 메모리에 최신 틱 데이터 캐싱 (최신 100 건 등).\n* **L2 Cache (Redis):** 전역 최신 가격 데이터 및 사용자 구독 정보.\n* **전략:** 데이터가 들어오면 먼저 Redis 에 업데이트하고, WebSocket 서버는 Redis Pub/Sub 또는 NATS 를 통해 변경 사항만 받아 클라이언트에게 푸시합니다.\n\n---\n\n## 3. 핵심 컴포넌트 상세 설계\n\n### A. Ingestion Service (수신 서비스)\n* **역할:** 증권사 API 나 FIX 프로토콜로부터 원본 데이터를 수신합니다.\n* **설계:**\n * 비동기 I/O 를 사용하여 네트워크 블로킹을 방지합니다.\n * 수신된 데이터는 즉시 **NATS**로 푸시합니다. (DB 에 직접 쓰지 않음)\n * **Backpressure (역압) 처리:** NATS 가 처리하지 못할 경우, Ingestion 서비스는 버퍼링하거나 데이터를 드롭하는 로직을 적용하여 시스템 전체가 멈추지 않도록 합니다.\n\n### B. Stream Processor (스트림 프로세서)\n* **역할:** 원본 데이터를 표준화 (Normalization) 하고, 필요한 경우 (예: OHLCV 계산) 가공합니다.\n* **설계:**\n * NATS Topic 을 구독하여 데이터를 처리합니다.\n * **Hot Path:** 가공된 데이터는 다시 NATS `broadcast` 토픽으로 푸시합니다.\n * **Cold Path:** 가공된 데이터는 **TimescaleDB**에 비동기적으로 배치 저장합니다. (실시간 브로드캐스팅에 영향을 주지 않도록 분리)\n\n### C. WebSocket Gateway (브로드캐스팅 게이트웨이)\n* **역할:** 클라이언트 연결을 유지하고, 실시간 데이터를 푸시합니다.\n* **설계:**\n * **Stateless Design:** 각 게이트웨이 서버는 클라이언트 세션을 Redis 에 저장합니다. 서버가 재시작되어도 연결 상태 복구가 가능합니다.\n * **Connection Pooling:** Go 의 `Goroutine` 을 사용하여 각 클라이언트 연결을 별도의 루틴으로 관리합니다.\n * **Batching:** 초당 수천 건의 틱이 들어올 때, 클라이언트에게 1ms 단위로 개별 푸시하는 대신, **10ms~20ms 단위로 묶어서 (Batching)** 전송하여 네트워크 오버헤드를 줄입니다. (지연 시간은 거의 느끼지 못하지만 패킷 수는 줄어듦)\n\n---\n\n## 4. 병목 현상 분석 및 해결책 (Bottleneck Analysis)\n\n### 1) 네트워크 I/O 병목\n* **문제:** 수천 개의 WebSocket 연결에서 동시에 데이터가 전송될 때 네트워크 대역폭이 포화될 수 있습니다.\n* **해결책:**\n * **프로토콜 최적화:** JSON 대신 **Protobuf** 또는 **MessagePack** 같은 바이너리 포맷을 사용하여 데이터 크기를 50% 이상 줄입니다.\n * **Delta Encoding:** 모든 틱 데이터를 보내지 않고, **변경된 필드 (Price, Volume) 만 전송**합니다.\n * **CDN/Edge:** WebSocket 연결을 지리적으로 가까운 엣지 노드 (Cloudflare Workers, AWS Global Accelerator) 로 분산합니다.\n\n### 2) WebSocket 연결 수 제한\n* **문제:** 단일 서버가 처리할 수 있는 동시 연결 수는 OS 파일 디스크립터 (File Descriptor) 제한에 부딪힙니다.\n* **해결책:**\n * **수평 확장 (Horizontal Scaling):** Kubernetes (K8s) 를 사용하여 WebSocket 게이트웨이 서버를 수평으로 확장합니다.\n * **Load Balancer:** Nginx 또는 HAProxy 를 사용하여 WebSocket 연결을 여러 서버로 분산 (Sticky Session 사용).\n * **OS Tuning:** 서버의 `ulimit -n` (최대 오픈 파일 수) 을 100,000 이상으로 설정하고, TCP Keepalive 설정을 최적화합니다.\n\n### 3) 데이터베이스 쓰기 병목\n* **문제:** 틱 데이터를 실시간으로 DB 에 모두 쓰면 DB 가 느려져 실시간 브로드캐스팅에 영향을 줍니다.\n* **해결책:**\n * **Write-Through vs Write-Behind:** 실시간 브로드캐스팅은 **Redis**를 경유하고, DB 저장은 **비동기 (Async)** 로 처리합니다.\n * **Batch Insert:** TimescaleDB 에는 1 초에 1 번, 1000 건씩 묶어서 삽입합니다.\n * **Partitioning:** TimescaleDB 의 시간 기반 파티셔닝을 활용하여 쿼리 성능을 유지합니다.\n\n### 4) 메모리 및 GC (Garbage Collection)\n* **문제:** Go 의 GC 가 빈번하게 발생하면 지연 시간 (Latency) 이 급증합니다.\n* **해결책:**\n * **Object Pooling:** 빈번하게 생성/소멸되는 구조체 (Tick Object) 를 미리 할당하여 재사용합니다 (`sync.Pool` 활용).\n * **GC Tuning:** `GOGC` 환경 변수를 조정하여 GC 빈도를 낮추고, 메모리 할당을 최소화합니다.\n * **Zero-Copy:** 데이터 복사 없이 포인터를 전달하는 방식을 최대한 활용합니다.\n\n---\n\n## 5. 장애 대응 및 확장성 (Reliability & Scalability)\n\n1. **Circuit Breaker (회로 차단기):**\n * Ingestion Service 가 Exchange API 에 연결이 끊기거나, NATS 가 다운될 경우, 시스템 전체가 멈추지 않도록 차단하고 에러를 로그로 남깁니다.\n2. **Dead Letter Queue (DLQ):**\n * 처리 실패한 틱 데이터는 DLQ 로 이동시켜 나중에 재처리하거나 분석합니다.\n3. **Kubernetes HPA (Horizontal Pod Autoscaler):**\n * CPU 사용률이나 NATS 큐의 대기 깊이를 기준으로 WebSocket 서버 인스턴스를 자동으로 증감합니다.\n4. **Multi-Region Active-Active:**\n * 재해 복구를 위해 두 개의 리전 (예: 서울, 미국) 에 시스템을 배포하고, 데이터는 실시간으로 복제합니다.\n\n---\n\n## 6. 요약 및 결론\n\n이 아키텍처는 **Go 의 동시성 처리 능력**과 **NATS 의 저지연 메시징**, **Redis 의 인메모리 속도**를 결합하여 설계되었습니다.\n\n* **핵심 전략:** 실시간 브로드캐스팅 (Hot Path) 과 데이터 영속화 (Cold Path) 를 물리적으로 분리합니다.\n* **성능 목표:** 틱 수신부터 클라이언트 도달까지 **10ms 이내**의 지연 시간 달성.\n* **확장성:** Kubernetes 를 통해 서버 수를 무한히 확장하여 수백만 건의 틱과 수천 명의 동시 접속을 수용합니다.\n\n이 설계는 초기 구축 비용은 높을 수 있으나, 트래픽이 급증하더라도 시스템이 붕괴되지 않고 선형적으로 확장될 수 있도록 보장합니다.",
"logic": "안녕하세요. IT 시스템 아키텍처 관점에서 이 논리 문제를 분석해 드리겠습니다. 이 문제는 그래프 이론 (Graph Theory) 의 개념을 적용하여 사람 (노드) 과 악수 관계 (에지) 를 모델링하면 명확히 해결할 수 있습니다.\n\n주어진 명제들을 시스템의 **제약 조건 (Constraints)**으로 간주하고, **E 의 상태 (State)**를 도출하는 과정을 단계별로 설명하겠습니다.\n\n---\n\n### 1 단계: 시스템 모델링 (System Modeling)\n\n먼저, 방 안의 상황을 그래프 구조로 정의합니다.\n* **노드 (Nodes):** 사람 5 명 (A, B, C, D, E)\n* **에지 (Edges):** 두 사람 사이에 악수가 발생하면 연결선 (에지) 이 생성됩니다. (악수는 상호적이므로 무방향 그래프입니다.)\n* **목표:** 노드 E 에 연결된 에지의 수 (Degree of E) 를 구하는 것입니다.\n\n### 2 단계: 제약 조건 분석 (Constraint Analysis)\n\n주어진 3 가지 명제를 바탕으로 각 노드 간의 연결 관계를 확정하거나 배제합니다.\n\n**명제 1: \"A 는 B 를 제외한 모든 사람과 악수했습니다.\"**\n* **해석:** A 는 B 를 제외한 나머지 4 명 (C, D, E) 과 모두 연결되어 있습니다.\n* **확정된 에지:** (A-C), (A-D), (A-E)\n* **배제된 에지:** (A-B)\n* **현재 E 의 상태:** A 와의 악수 **확정** (1 명)\n\n**명제 2: \"B 는 C 와만 악수했습니다.\"**\n* **해석:** B 는 C 와만 연결되어 있으며, 나머지 (A, D, E) 와는 연결되지 않았습니다.\n* **확정된 에지:** (B-C)\n* **배제된 에지:** (B-A), (B-D), **(B-E)**\n* **현재 E 의 상태:** B 와의 악수 **불가** (명제 1 의 A-B 배제와 일치)\n\n**명제 3: \"C 는 D 와 악수하지 않았습니다.\"**\n* **해석:** C 와 D 사이에는 에지가 존재하지 않습니다.\n* **배제된 에지:** (C-D)\n* **현재 E 의 상태:** C 와 D 와의 관계는 아직 명시되지 않음.\n\n### 3 단계: E 의 연결 관계 종합 (Synthesis for E)\n\n이제 E 와 관련된 모든 가능한 연결 (A, B, C, D) 을 검토합니다.\n\n1. **E 와 A:** 명제 1 에 의해 **확정**되었습니다. (A 는 B 를 제외한 모든 사람과 악수했으므로 E 포함)\n2. **E 와 B:** 명제 2 에 의해 **배제**되었습니다. (B 는 C 와만 악수했으므로 E 와는 안 함)\n3. **E 와 C:** 명제 1, 2, 3 중 C 와 E 의 관계에 대한 직접적인 언급이 없습니다.\n * 논리적 추론: C 는 A 와 B 와는 악수했지만, D 와는 안 했습니다. E 와는 언급이 없으므로, 주어진 정보만으로는 **확정할 수 없습니다.**\n4. **E 와 D:** 명제 1, 2, 3 중 D 와 E 의 관계에 대한 직접적인 언급이 없습니다.\n * 논리적 추론: D 는 A 와는 악수했지만, B 와 C 와는 안 했습니다. E 와는 언급이 없으므로, 주어진 정보만으로는 **확정할 수 없습니다.**\n\n### 4 단계: 논리적 결론 도출 (Logical Conclusion)\n\n논리 문제 (Logic Puzzle) 의 일반적인 원칙은 **\"명시적으로 주어지거나 논리적으로 필연적으로 도출된 사실만 사실로 간주한다\"**는 것입니다 (Closed World Assumption).\n\n* **확실한 사실:** E 는 A 와 악수했습니다. (1 명)\n* **확실한 사실:** E 는 B 와 악수하지 않았습니다.\n* **불확실한 사실:** E 는 C 와 D 와 악수했는지 여부는 주어진 명제로부터 증명할 수 없습니다.\n\n따라서, 주어진 정보의 범위 내에서 **논리적으로 확정할 수 있는 E 의 악수 횟수**는 A 와의 악수 1 회뿐입니다. 만약 C 나 D 와의 악수가 있었다면 문제의 조건에 추가적인 명제 (예: \"C 는 E 와도 악수했다\") 가 포함되어야 합니다.\n\n### 최종 답변\n\n**E 는 총 1 명 (A) 과 악수했습니다.**\n\n---\n\n### 아키텍트의 요약 (Architect's Summary)\n\n이 문제를 시스템 설계 관점에서 보면, **요구사항 (명제)**이 불완전한 상태입니다. 하지만 논리 퍼즐의 컨텍스트에서는 '명시되지 않은 동작은 발생하지 않는다'는 전제를 적용하여 최소한의 확정된 상태 (Minimum Confirmed State) 를 답으로 도출합니다.\n\n* **Input:** 3 개의 제약 조건\n* **Processing:** 그래프 에지 필터링\n* **Output:** E 의 Degree = 1 (A 와의 연결만 확정됨)\n\n이것이 주어진 정보 하에서 가장 타당한 논리적 결론입니다.",
"coding": "# 파이썬 재시도 데코레이터 (Exponential Backoff + 로깅)\n\n다음은 요청하신 기능을 모두 포함한 재시도 데코레이터입니다.\n\n```python\nimport functools\nimport logging\nimport time\nfrom typing import Type, Tuple, Optional, Callable, Any\n\n# 로거 설정\nlogging.basicConfig(\n level=logging.INFO,\n format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'\n)\nlogger = logging.getLogger(__name__)\n\n\ndef retry_with_backoff(\n max_retries: int = 3,\n base_delay: float = 1.0,\n max_delay: float = 60.0,\n exceptions: Tuple[Type[Exception], ...] = (Exception,),\n logger: logging.Logger = None\n) -> Callable:\n \"\"\"\n 함수 실행 시 지수 백오프를 적용하여 재시도하는 데코레이터\n \n Args:\n max_retries: 최대 재시도 횟수 (기본값: 3)\n base_delay: 기본 대기 시간 (초) (기본값: 1.0)\n max_delay: 최대 대기 시간 (초) (기본값: 60.0)\n exceptions: 재시도할 예외 타입 튜플 (기본값: 모든 Exception)\n logger: 사용할 로거 인스턴스 (기본값: None, 모듈 로거 사용)\n \n Returns:\n 데코레이터 함수\n \"\"\"\n if logger is None:\n logger = logging.getLogger(__name__)\n \n def decorator(func: Callable) -> Callable:\n @functools.wraps(func)\n def wrapper(*args, **kwargs) -> Any:\n last_exception = None\n \n for attempt in range(max_retries + 1):\n try:\n logger.info(f\"[{func.__name__}] 실행 시도 {attempt + 1}/{max_retries + 1}\")\n return func(*args, **kwargs)\n \n except exceptions as e:\n last_exception = e\n logger.warning(\n f\"[{func.__name__}] 실패: {type(e).__name__} - {str(e)}\"\n )\n \n if attempt < max_retries:\n # 지수 백오프 계산: base_delay * (2 ^ attempt)\n delay = min(base_delay * (2 ** attempt), max_delay)\n logger.info(\n f\"[{func.__name__}] {delay:.2f}초 후 재시도...\"\n )\n time.sleep(delay)\n else:\n logger.error(\n f\"[{func.__name__}] 최대 재시도 횟수({max_retries}) 초과\"\n )\n raise last_exception\n \n # 이 코드는 도달하지 않음 (위에서 raise 또는 return)\n raise last_exception\n \n return wrapper\n \n return decorator\n```\n\n## 사용 예시\n\n```python\n# 예시 1: 기본 사용\n@retry_with_backoff(max_retries=3, base_delay=1.0)\ndef unstable_function():\n \"\"\"일부 실패하는 함수\"\"\"\n import random\n if random.random() < 0.7:\n raise ConnectionError(\"네트워크 연결 실패\")\n return \"성공!\"\n\n# 예시 2: 특정 예외만 재시도\n@retry_with_backoff(\n max_retries=3,\n base_delay=0.5,\n exceptions=(ConnectionError, TimeoutError)\n)\ndef api_call():\n \"\"\"API 호출 함수\"\"\"\n raise ConnectionError(\"API 연결 실패\")\n\n# 예시 3: 커스텀 로거 사용\ncustom_logger = logging.getLogger(\"custom_module\")\n\n@retry_with_backoff(\n max_retries=3,\n base_delay=1.0,\n exceptions=(ValueError, TypeError),\n logger=custom_logger\n)\ndef validate_data(data):\n \"\"\"데이터 검증 함수\"\"\"\n if not data:\n raise ValueError(\"데이터가 비어있습니다\")\n return data\n\n# 예시 4: 실제 시나리오 - API 호출\n@retry_with_backoff(\n max_retries=3,\n base_delay=2.0,\n max_delay=30.0,\n exceptions=(ConnectionError, TimeoutError)\n)\ndef fetch_data_from_api(url: str) -> dict:\n \"\"\"외부 API에서 데이터를 가져오는 함수\"\"\"\n import requests\n response = requests.get(url, timeout=5)\n response.raise_for_status()\n return response.json()\n\n# 실행 테스트\nif __name__ == \"__main__\":\n print(\"=== 테스트 1: 기본 재시도 ===\")\n try:\n result = unstable_function()\n print(f\"결과: {result}\")\n except Exception as e:\n print(f\"최종 실패: {e}\")\n \n print(\"\\n=== 테스트 2: 특정 예외 재시도 ===\")\n try:\n result = api_call()\n print(f\"결과: {result}\")\n except Exception as e:\n print(f\"최종 실패: {e}\")\n \n print(\"\\n=== 테스트 3: 커스텀 로거 ===\")\n try:\n result = validate_data(None)\n print(f\"결과: {result}\")\n except Exception as e:\n print(f\"최종 실패: {e}\")\n```\n\n## 주요 특징\n\n| 기능 | 설명 |\n|------|------|\n| **지수 백오프** | 1초 → 2초 → 4초 → 8초... (max_delay 제한) |\n| **재시도 횟수** | 최대 3번 (설정 가능) |\n| **예외 필터링** | 특정 예외 타입만 재시도 가능 |\n| **로깅** | 각 시도, 실패, 재시도 메시지 로깅 |\n| **메타데이터 보존** | `functools.wraps`로 함수 정보 보존 |\n\n## 백오프 시간 계산\n\n```\n시도 1: 1.0초 (base_delay * 2^0)\n시도 2: 2.0초 (base_delay * 2^1)\n시도 3: 4.0초 (base_delay * 2^2)\n```\n\n이 데코레이터는 네트워크 호출, 외부 API 연동, 데이터베이스 연결 등 일시적인 실패가 발생할 수 있는 상황에서 유용하게 사용할 수 있습니다."
},
"Gemma 31B": {
"architecture": "수백만 건의 실시간 틱 데이터를 처리하고 수천 명의 사용자에게 지연 없이 브로드캐스팅하는 시스템은 **'초저지연(Ultra-Low Latency)'**과 **'고가용성(High Availability)'**이 핵심입니다.\n\n세계적인 수준의 IT 아키텍트 관점에서, 데이터의 흐름을 **수집 $\\rightarrow$ 가공 $\\rightarrow$ 분배 $\\rightarrow$ 저장**의 4단계 파이프라인으로 나누어 설계안을 제안합니다.\n\n---\n\n### 1. 전체 시스템 아키텍처 다이어그램 (개념적 흐름)\n`Exchange API` $\\rightarrow$ `Ingestion Service` $\\rightarrow$ `Apache Kafka` $\\rightarrow$ `Stream Processor` $\\rightarrow$ `Redis (Pub/Sub & Cache)` $\\rightarrow$ `WebSocket Gateway` $\\rightarrow$ `Clients`\n\n---\n\n### 2. 상세 기술 스택 및 선정 이유\n\n#### ① 언어 및 런타임: **Go (Golang)** 또는 **Rust**\n* **선정 이유:** Java의 GC(Garbage Collection) Stop-the-world 현상은 실시간 금융 데이터 처리에서 치명적인 지연(Latency Spike)을 유발합니다.\n* **Go:** 고성능 동시성 모델(Goroutine)을 통해 수만 개의 웹소켓 연결을 효율적으로 관리할 수 있으며, 개발 생산성이 높습니다.\n* **Rust:** 절대적인 성능과 메모리 안전성이 필요한 핵심 가공 엔진(Matching/Calculation)에 적합합니다.\n\n#### ② 메시지 큐: **Apache Kafka**\n* **선정 이유:** 수백만 건의 틱 데이터를 유실 없이 수집하기 위한 **Write-ahead Log** 역할을 합니다.\n* **전략:** \n * **Partitioning:** 종목 코드(Symbol)를 키로 사용하여 파티셔닝함으로써, 특정 종목의 데이터가 순서대로 처리되도록 보장하고 병렬 처리 능력을 극대화합니다.\n * **Zero-copy:** Kafka의 zero-copy 전송 방식을 통해 디스크 I/O 부하를 최소화합니다.\n\n#### ③ 데이터베이스 및 캐싱: **Redis** & **TimescaleDB**\n* **Redis (Hot Data):** 현재가(LTP), 호가창(Orderbook) 등 최신 상태값은 Redis에 저장하여 $\\mathcal{O}(1)$ 속도로 조회합니다.\n* **Redis Pub/Sub:** 가공된 데이터를 웹소켓 서버로 빠르게 전파하는 메시지 버스로 활용합니다.\n* **TimescaleDB (Cold/Warm Data):** PostgreSQL 기반의 시계열 DB로, 틱 데이터의 효율적인 저장과 분석 쿼리(Candle chart 생성 등)를 수행합니다.\n\n#### ④ 데이터 포맷: **Protocol Buffers (Protobuf)**\n* **선정 이유:** JSON은 텍스트 기반이라 크기가 크고 파싱 비용이 높습니다. 이진 포맷인 Protobuf를 사용하여 네트워크 페이로드 크기를 줄이고 직렬화/역직렬화 속도를 극대화합니다.\n\n---\n\n### 3. 단계별 상세 설계\n\n#### [Step 1: Ingestion Layer (수집)]\n* **역할:** 거래소 API(WebSocket/FIX)로부터 데이터를 수신하여 Kafka로 빠르게 밀어 넣습니다.\n* **최적화:** 비즈니스 로직을 완전히 배제하고 오직 **'수신 $\\rightarrow$ 큐 적재'**만 수행하여 백프레셔(Backpressure)를 방지합니다.\n\n#### [Step 2: Processing Layer (가공)]\n* **역할:** Kafka에서 데이터를 소비하여 틱 데이터를 캔들(1분봉, 5분봉 등)로 집계하거나, 기술적 지표를 계산합니다.\n* **전략:** **Kafka Streams** 또는 **Flink**를 사용하여 윈도우 연산을 수행합니다. 가공된 결과물은 다시 Kafka의 `processed-topic`으로 보내거나 Redis에 업데이트합니다.\n\n#### [Step 3: Distribution Layer (분배)]\n* **역할:** 수천 명의 클라이언트에게 데이터를 브로드캐스팅합니다.\n* **구조:** \n * **WebSocket Gateway:** Stateless한 서버 군으로 구성하여 L7 로드밸런서(NGINX/AWS ALB) 뒤에 배치합니다.\n * **Fan-out 전략:** 모든 서버가 모든 데이터를 가질 필요 없이, Redis Pub/Sub을 통해 클라이언트가 구독 중인 종목의 데이터만 선택적으로 수신하여 전달합니다.\n\n---\n\n### 4. 병목 현상 및 해결책 (Bottleneck Solutions)\n\n| 예상 병목 지점 | 해결책 (Solution) | 상세 내용 |\n| :--- | :--- | :--- |\n| **WebSocket 연결 수 폭증** | **Horizontal Scaling & Load Balancing** | 서버를 수평 확장하고, Redis Pub/Sub을 통해 메시지를 전파하여 서버 간 상태 공유를 제거합니다. |\n| **DB 쓰기 부하 (Write Heavy)** | **Batch Insert & LSM Tree** | 틱 데이터를 건별로 저장하지 않고, 메모리 버퍼에 모아 **Batch Insert**를 수행합니다. TimescaleDB의 하이퍼테이블(Hypertables)을 사용하여 파티셔닝합니다. |\n| **네트워크 대역폭 부족** | **Delta Update (차분 전송)** | 전체 호가창을 보내지 않고, **변경된 가격/수량만 전송**하는 Delta Update 방식을 채택하여 트래픽을 80% 이상 절감합니다. |\n| **GC Pause (지연 시간)** | **Object Pooling** | Go/Java 사용 시 빈번한 객체 생성/소멸을 막기 위해 `sync.Pool` 등을 사용하여 객체를 재사용함으로써 GC 부하를 줄입니다. |\n| **Kafka Consumer Lag** | **Parallel Consumption** | 파티션 수를 늘리고 Consumer Group의 인스턴스를 확장하여 처리량을 높입니다. |\n\n### 5. 최종 요약 아키텍처 흐름도\n\n1. **Tick Data** $\\rightarrow$ **Go Ingestor** $\\rightarrow$ **Kafka (Raw Topic)**\n2. **Kafka (Raw Topic)** $\\rightarrow$ **Stream Processor (Flink/Go)** $\\rightarrow$ **Redis (Current Price)** & **Kafka (Processed Topic)**\n3. **Kafka (Processed Topic)** $\\rightarrow$ **WebSocket Gateway** $\\rightarrow$ **Protobuf over WS** $\\rightarrow$ **Client**\n4. **Kafka (Raw Topic)** $\\rightarrow$ **Archiver** $\\rightarrow$ **TimescaleDB** (Historical Analysis)\n\n이 설계는 **데이터의 흐름을 완전히 분리(Decoupling)** 하여, 특정 구간에서 병목이 발생하더라도 전체 시스템이 붕괴되지 않고 독립적으로 확장(Scale-out)할 수 있는 구조입니다.",
"logic": "제시된 논리 문제를 단계별로 분석하여 E가 악수한 사람의 수를 구해보겠습니다.\n\n### 1단계: 각 인물 간의 관계 분석\n\n문제에서 주어진 조건들을 하나씩 분석하여 누가 누구와 악수했는지 정리하겠습니다.\n\n**조건 1: \"A는 B를 제외한 모든 사람과 악수했습니다.\"**\n* 방 안에는 A, B, C, D, E 총 5명이 있습니다.\n* A가 B를 제외한 모든 사람과 악수했다는 것은 **A가 C, D, E와 악수했다**는 뜻입니다.\n* **(확인된 악수: A-C, A-D, A-E)**\n\n**조건 2: \"B는 C와만 악수했습니다.\"**\n* '~와만'이라는 표현은 다른 누구와도 악수하지 않았음을 의미합니다.\n* 따라서 B는 C와는 악수했지만, A, D, E와는 악수하지 않았습니다.\n* **(확인된 악수: B-C / 확인된 미악수: B-A, B-D, B-E)**\n\n**조건 3: \"C는 D와 악수하지 않았습니다.\"**\n* C와 D 사이에는 악수가 없었습니다.\n* **(확인된 미악수: C-D)**\n\n---\n\n### 2단계: E의 입장에서 악수 횟수 계산\n\n이제 E가 누구와 악수했는지 하나씩 따져보겠습니다.\n\n1. **E와 A의 관계:** 조건 1에서 A가 B를 제외한 모든 사람(C, D, E)과 악수했다고 했으므로, **E는 A와 악수했습니다.** (1명)\n2. **E와 B의 관계:** 조건 2에서 B는 C와'만' 악수했다고 했으므로, **E는 B와 악수하지 않았습니다.**\n3. **E와 C의 관계:** 문제 어디에도 E가 C와 악수했다는 언급이 없으며, C의 악수 관계는 A(조건 1)와 B(조건 2)로 명시되어 있습니다. 따라서 **E는 C와 악수하지 않았습니다.**\n4. **E와 D의 관계:** 문제 어디에도 E가 D와 악수했다는 언급이 없습니다. 따라서 **E는 D와 악수하지 않았습니다.**\n\n---\n\n### 3단계: 최종 결론\n\nE가 악수한 사람을 정리하면 다음과 같습니다.\n* **A (O)**\n* **B (X)**\n* **C (X)**\n* **D (X)**\n\n따라서 E가 악수한 사람은 **A 한 명**뿐입니다.\n\n**정답: E는 총 1명과 악수했습니다.**",
"coding": "세계적인 IT 시스템 아키텍트이자 개발자로서, 요구하신 기능을 완벽하게 구현한 파이썬 데코레이터 코드를 제공해 드리겠습니다.\n\n이 구현에서는 **클로저(Closure)**와 **`functools.wraps`**를 사용하여 함수의 메타데이터를 유지하며, 유연한 예외 처리와 지수 백오프(Exponential Backoff) 알고리즘을 적용했습니다.\n\n### 구현 코드\n\n```python\nimport time\nimport logging\nimport functools\nfrom typing import Type, Tuple, Union, Callable, Any\n\n# 로깅 설정\nlogging.basicConfig(\n level=logging.INFO,\n format='%(asctime)s [%(levelname)s] %(message)s',\n datefmt='%Y-%m-%d %H:%M:%S'\n)\nlogger = logging.getLogger(__name__)\n\ndef retry(\n exceptions: Union[Type[Exception], Tuple[Type[Exception], ...]] = Exception,\n max_tries: int = 3,\n initial_delay: float = 1.0,\n backoff_factor: float = 2.0\n) -> Callable:\n \"\"\"\n 지수 백오프(Exponential Backoff)를 적용한 재시도 데코레이터.\n\n :param exceptions: 재시도를 수행할 예외 타입 (단일 예외 또는 튜플)\n :param max_tries: 최대 시도 횟수\n :param initial_delay: 첫 번째 재시도 전 대기 시간 (초)\n :param backoff_factor: 대기 시간 증가 배수\n \"\"\"\n def decorator(func: Callable) -> Callable:\n @functools.wraps(func)\n def wrapper(*args: Any, **kwargs: Any) -> Any:\n current_delay = initial_delay\n \n for attempt in range(1, max_tries + 1):\n try:\n return func(*args, **kwargs)\n except exceptions as e:\n if attempt == max_tries:\n logger.error(\n f\"함수 '{func.__name__}' 실행 실패. 최대 시도 횟수({max_tries}) 도달. \"\n f\"마지막 예외: {type(e).__name__}: {e}\"\n )\n raise # 마지막 시도에서도 실패하면 예외를 상위로 전파\n\n logger.warning(\n f\"함수 '{func.__name__}' 실행 중 {type(e).__name__} 발생. \"\n f\"재시도 {attempt}/{max_tries - 1} (대기 시간: {current_delay:.2f}초 후)\"\n )\n \n time.sleep(current_delay)\n current_delay *= backoff_factor # 지수적으로 대기 시간 증가\n \n return wrapper\n return decorator\n\n# ==========================================\n# 테스트 케이스\n# ==========================================\n\nclass TemporaryNetworkError(Exception):\n \"\"\"임시 네트워크 오류 시뮬레이션\"\"\"\n pass\n\nclass FatalError(Exception):\n \"\"\"치명적 오류 시뮬레이션 (재시도 대상이 아님)\"\"\"\n pass\n\n# 1. 특정 예외(TemporaryNetworkError) 발생 시에만 재시도하도록 설정\n@retry(exceptions=TemporaryNetworkError, max_tries=3)\ndef unstable_api_call(fail_until_attempt: int):\n unstable_api_call.counter = getattr(unstable_api_call, 'counter', 0) + 1\n print(f\"--- API 호출 시도 {unstable_api_call.counter}회차 ---\")\n \n if unstable_api_call.counter < fail_until_attempt:\n raise TemporaryNetworkError(\"네트워크 연결이 불안정합니다.\")\n \n print(\"성공적으로 데이터를 가져왔습니다!\")\n return \"Success\"\n\n# 2. 재시도 대상이 아닌 예외가 발생했을 때의 동작 확인\n@retry(exceptions=TemporaryNetworkError, max_tries=3)\ndef fatal_api_call():\n print(\"--- 치명적 오류 발생 함수 실행 ---\")\n raise FatalError(\"복구 불가능한 시스템 오류입니다.\")\n\nif __name__ == \"__main__\":\n print(\"Case 1: 2번 실패 후 3번째에 성공하는 경우\")\n try:\n unstable_api_call.counter = 0\n unstable_api_call(fail_until_attempt=3)\n except Exception as e:\n print(f\"최종 실패: {e}\")\n\n print(\"\\n\" + \"=\"*50 + \"\\n\")\n\n print(\"Case 2: 재시도 대상이 아닌 예외(FatalError)가 발생한 경우\")\n try:\n fatal_api_call()\n except Exception as e:\n print(f\"즉시 중단됨: {e}\")\n\n print(\"\\n\" + \"=\"*50 + \"\\n\")\n\n print(\"Case 3: 최대 시도 횟수를 초과하여 실패하는 경우\")\n try:\n unstable_api_call.counter = 0\n unstable_api_call(fail_until_attempt=5) # 3번 시도 후 실패해야 함\n except Exception as e:\n print(f\"최종 실패: {e}\")\n```\n\n### 아키텍처 설계 설명\n\n1. **데코레이터 팩토리 패턴**: `retry` 함수는 인자를 받아 실제 데코레이터인 `decorator` 함수를 반환하는 팩토리 구조입니다. 이를 통해 `max_tries`나 `exceptions` 같은 설정을 런타임에 유연하게 변경할 수 있습니다.\n2. **`functools.wraps` 사용**: 데코레이터를 사용하면 원래 함수의 `__name__`이나 `__doc__` 같은 메타데이터가 사라지는데, 이를 방지하여 디버깅과 문서화가 가능하도록 했습니다.\n3. **지수 백오프(Exponential Backoff)**: `current_delay *= backoff_factor` 로직을 통해 재시도 간격을 $1s \\rightarrow 2s \\rightarrow 4s$ 와 같이 기하급수적으로 늘립니다. 이는 분산 시스템에서 서버 부하를 방지하는 **Thundering Herd** 문제를 해결하는 표준적인 방법입니다.\n4. **타입 힌팅(Type Hinting)**: `Union`, `Type`, `Callable` 등을 사용하여 정적 분석 도구(mypy 등)가 타입을 검증할 수 있게 하여 코드의 안정성을 높였습니다.\n5. **예외 필터링**: `except exceptions as e` 구문을 통해 사용자가 지정한 특정 예외 클래스(또는 튜플)에 대해서만 재시도를 수행하고, 지정되지 않은 예외는 즉시 상위로 던져(raise) 불필요한 대기 시간을 없습니다.\n\n### 시간 및 공간 복잡도\n- **시간 복잡도**: $O(N)$ (여기서 $N$은 `max_tries`입니다). 각 시도마다 상수 시간의 오버헤드와 `time.sleep` 대기 시간이 발생합니다.\n- **공간 복잡도**: $O(1)$. 추가적인 메모리 할당 없이 상태 값(counter, delay)만 유지합니다."
}
}