# Phase 8 엔터프라이즈 기능 구현 계획 ## 📋 개요 Phase 8은 **멀티테넌트 지원**, **실시간 업데이트**, **감사 로그**, **비용 관리**를 추가하여 온톨로지 플랫폼을 엔터프라이즈급 시스템으로 전환합니다. --- ## 🎯 Phase 8의 목표 | 목표 | 설명 | 우선순위 | |------|------|---------| | 멀티테넌트 | 여러 조직 동시 지원 + 데이터 격리 | P0 | | WebSocket | 실시간 그래프 업데이트 | P1 | | 감사 로그 | 모든 작업 변경 이력 추적 | P1 | | 비용 관리 | API 호출당 요금 계산 | P2 | | 고급 분석 | 사용자별 통계 대시보드 | P2 | --- ## 📁 구현 파일 구조 ``` ontology_platform/ └─ ont_platform/ ├─ api/ │ ├─ phase7_app.py (기존) │ └─ phase8_app.py ✨ NEW (멀티테넌트 + WebSocket) │ ├─ auth/ ✨ NEW │ ├─ __init__.py │ ├─ models.py (Organization, User, APIKey) │ ├─ auth.py (JWT, API 키 검증) │ └─ rbac.py (역할 기반 액세스) │ ├─ audit/ ✨ NEW │ ├─ __init__.py │ ├─ models.py (AuditLog, Change) │ └─ logger.py (감사 로그 기록) │ ├─ billing/ ✨ NEW │ ├─ __init__.py │ ├─ models.py (Usage, Subscription) │ └─ calculator.py (비용 계산) │ └─ realtime/ ✨ NEW ├─ __init__.py ├─ websocket.py (WebSocket 관리) └─ broadcaster.py (이벤트 브로드캐스트) tests/ ├─ test_phase8_multitenant.py ✨ NEW ├─ test_phase8_websocket.py ✨ NEW ├─ test_phase8_audit.py ✨ NEW └─ test_phase8_billing.py ✨ NEW docs/ └─ PHASE_8_ENTERPRISE_GUIDE.md ✨ NEW ``` --- ## 🏗️ Phase 8 아키텍처 ### 1. 멀티테넌트 아키텍처 ``` ┌─────────────────────────────────────┐ │ API Gateway (인증/인가) │ ├─────────────────────────────────────┤ │ JWT 토큰 | API 키 | 역할 확인 │ ├─────────────────────────────────────┤ │ Organization A │ Organization B│ │ ├─ Users (5) │ ├─ Users (3) │ │ ├─ API Keys │ ├─ API Keys │ │ └─ Neo4j DB │ └─ Neo4j DB │ │ (격리됨) │ (격리됨) │ └─────────────────────────────────────┘ ``` **데이터 격리 전략**: - `org_id` 필드를 모든 쿼리에 포함 - Neo4j 라벨: `:Organization`, `:User`, `:Subscription` - 각 요청에서 org_id 검증 ### 2. 실시간 업데이트 (WebSocket) ``` 클라이언트 A 클라이언트 B │ │ └──→ WebSocket ←───────┘ Connection Pool ┌────────────────┐ │ Broadcaster │ │ (이벤트 큐) │ └────────────────┘ ↑ Neo4j 변경 이벤트 ``` **이벤트 타입**: - `entity.created`, `entity.updated`, `entity.deleted` - `relation.created`, `relation.deleted` - `graph.analyzed` (분석 완료) ### 3. 감사 로그 ``` 모든 API 작업 ↓ 감사 미들웨어 ├─ User ID ├─ Organization ID ├─ 작업 타입 (CREATE, UPDATE, DELETE, QUERY) ├─ 대상 엔티티 ├─ 변경 사항 └─ 타임스탐프 ↓ AuditLog (Neo4j) ├─ 쿼리 가능 ├─ 변경 이력 추적 └─ 감시 경고 ``` ### 4. 비용 관리 ``` API 호출 ↓ 작업 분류 (Query, LLM, Stream 등) ↓ 토큰/시간 계산 ├─ LLM 호출: 토큰 기반 ├─ 그래프 쿼리: 노드 수 기반 ├─ 스트리밍: 시간 기반 └─ 저장소: GB 기반 ↓ Usage 기록 └─ Subscription 확인 (할당량) ``` --- ## 🔐 1단계: 멀티테넌트 인증 시스템 ### 파일: `ont_platform/auth/models.py` ```python from sqlalchemy import Column, String, DateTime, Boolean, Integer from datetime import datetime class Organization(Base): """조직""" __tablename__ = "organizations" id: str # UUID name: str # 조직명 created_at: datetime is_active: bool subscription_tier: str # "free", "pro", "enterprise" class User(Base): """사용자""" __tablename__ = "users" id: str org_id: str (FK → Organization) email: str hashed_password: str role: str # "admin", "editor", "viewer" is_active: bool created_at: datetime class APIKey(Base): """API 키""" __tablename__ = "api_keys" id: str org_id: str (FK → Organization) key_hash: str name: str last_used: datetime is_active: bool created_at: datetime ``` ### 파일: `ont_platform/auth/auth.py` ```python class JWTAuth: """JWT 기반 인증""" async def create_token(self, user_id: str, org_id: str) -> str: """JWT 토큰 생성""" payload = { "user_id": user_id, "org_id": org_id, "exp": datetime.utcnow() + timedelta(hours=24), } return jwt.encode(payload, SECRET_KEY) async def verify_token(self, token: str) -> Dict: """JWT 토큰 검증""" try: payload = jwt.decode(token, SECRET_KEY) return payload except: raise HTTPException(status_code=401, detail="Invalid token") class APIKeyAuth: """API 키 기반 인증""" async def create_key(self, org_id: str, name: str) -> str: """새 API 키 생성""" key = secrets.token_urlsafe(32) key_hash = hashlib.sha256(key.encode()).hexdigest() # DB에 저장 await db.create_api_key(org_id, key_hash, name) return key # 한 번만 보여줌 async def verify_key(self, api_key: str) -> str: """API 키 검증 → org_id 반환""" key_hash = hashlib.sha256(api_key.encode()).hexdigest() org_id = await db.get_org_by_api_key(key_hash) if not org_id: raise HTTPException(status_code=401, detail="Invalid API key") return org_id ``` ### 파일: `ont_platform/auth/rbac.py` ```python class RBAC: """역할 기반 액세스 제어""" PERMISSIONS = { "admin": ["read", "write", "delete", "manage_users", "view_audit"], "editor": ["read", "write", "delete"], "viewer": ["read"], } async def check_permission( self, user_id: str, action: str ) -> bool: """사용자가 작업을 수행할 수 있는지 확인""" user = await db.get_user(user_id) permissions = self.PERMISSIONS.get(user.role, []) return action in permissions ``` --- ## 📊 2단계: 감시 및 감사 로그 ### 파일: `ont_platform/audit/models.py` ```python class AuditLog(Base): """감시 로그""" __tablename__ = "audit_logs" id: str org_id: str user_id: str timestamp: datetime action: str # "CREATE", "READ", "UPDATE", "DELETE" resource_type: str # "Entity", "Relation", "Graph" resource_id: str changes: Dict # {"before": {...}, "after": {...}} ip_address: str status: str # "success", "failed" error_message: Optional[str] ``` ### 파일: `ont_platform/audit/logger.py` ```python class AuditLogger: """감시 로그 기록""" async def log_action( self, org_id: str, user_id: str, action: str, resource_type: str, resource_id: str, changes: Dict = None, ip_address: str = None, ) -> None: """작업 로그 기록""" log_entry = AuditLog( org_id=org_id, user_id=user_id, timestamp=datetime.utcnow(), action=action, resource_type=resource_type, resource_id=resource_id, changes=changes, ip_address=ip_address, status="success", ) await db.create_audit_log(log_entry) async def get_audit_trail( self, org_id: str, resource_id: str, limit: int = 100, ) -> List[AuditLog]: """리소스의 변경 이력 조회""" return await db.query_audit_logs( org_id=org_id, resource_id=resource_id, limit=limit, ) ``` --- ## 🔄 3단계: 실시간 업데이트 (WebSocket) ### 파일: `ont_platform/realtime/websocket.py` ```python class ConnectionManager: """WebSocket 연결 관리""" def __init__(self): self.active_connections: Dict[str, Set[WebSocket]] = {} # org_id → {WebSocket 객체들} async def connect(self, org_id: str, websocket: WebSocket): """클라이언트 연결""" await websocket.accept() if org_id not in self.active_connections: self.active_connections[org_id] = set() self.active_connections[org_id].add(websocket) async def disconnect(self, org_id: str, websocket: WebSocket): """클라이언트 연결 해제""" self.active_connections[org_id].remove(websocket) async def broadcast(self, org_id: str, message: Dict): """조직의 모든 클라이언트에게 메시지 브로드캐스트""" if org_id not in self.active_connections: return disconnected = set() for connection in self.active_connections[org_id]: try: await connection.send_json(message) except: disconnected.add(connection) # 연결 끊긴 클라이언트 제거 for connection in disconnected: await self.disconnect(org_id, connection) ``` ### 파일: `ont_platform/realtime/broadcaster.py` ```python class EventBroadcaster: """Neo4j 변경 이벤트 브로드캐스트""" def __init__(self, connection_manager: ConnectionManager): self.manager = connection_manager async def broadcast_entity_created( self, org_id: str, entity: Dict, ): """엔티티 생성 이벤트""" message = { "type": "entity.created", "timestamp": datetime.utcnow().isoformat(), "entity": entity, } await self.manager.broadcast(org_id, message) async def broadcast_entity_updated( self, org_id: str, entity_id: str, changes: Dict, ): """엔티티 업데이트 이벤트""" message = { "type": "entity.updated", "timestamp": datetime.utcnow().isoformat(), "entity_id": entity_id, "changes": changes, } await self.manager.broadcast(org_id, message) async def broadcast_graph_analyzed( self, org_id: str, analysis_results: Dict, ): """그래프 분석 완료 이벤트""" message = { "type": "graph.analyzed", "timestamp": datetime.utcnow().isoformat(), "results": analysis_results, } await self.manager.broadcast(org_id, message) ``` --- ## 💰 4단계: 비용 관리 ### 파일: `ont_platform/billing/models.py` ```python class Usage(Base): """사용량 기록""" __tablename__ = "usages" id: str org_id: str user_id: str timestamp: datetime operation_type: str # "llm_call", "graph_query", "streaming", "storage" quantity: float # 토큰, 노드 수, 시간 등 cost: float # USD metadata: Dict # 추가 정보 class Subscription(Base): """구독 정보""" __tablename__ = "subscriptions" org_id: str tier: str # "free", "pro", "enterprise" monthly_limit: float # USD current_month_cost: float overages_allowed: bool created_at: datetime ``` ### 파일: `ont_platform/billing/calculator.py` ```python class CostCalculator: """비용 계산""" PRICING = { "llm_call": 0.01, # 토큰당 $0.01 "graph_query": 0.001, # 노드당 $0.001 "streaming": 0.1, # 분당 $0.1 "storage": 10.0, # GB당 $10/월 } async def calculate_operation_cost( self, operation_type: str, quantity: float, ) -> float: """작업 비용 계산""" price_per_unit = self.PRICING.get(operation_type, 0) return quantity * price_per_unit async def check_quota( self, org_id: str, estimated_cost: float, ) -> bool: """할당량 확인""" subscription = await db.get_subscription(org_id) remaining = subscription.monthly_limit - subscription.current_month_cost return estimated_cost <= remaining ``` --- ## 🌐 Phase 8 FastAPI 앱 구조 ### 파일: `ont_platform/api/phase8_app.py` ``` phase8_app.py ├─ FastAPI 앱 생성 ├─ 미들웨어 │ ├─ 인증 (JWT/API 키) │ ├─ 감시 로깅 │ ├─ 비용 추적 │ └─ 에러 처리 ├─ 엔드포인트 │ ├─ /auth/* (로그인, 토큰, API 키) │ ├─ /org/* (조직 관리) │ ├─ /users/* (사용자 관리) │ ├─ /ws (WebSocket) │ ├─ /audit/* (감시 로그) │ ├─ /billing/* (사용량, 비용) │ └─ /api/v1/* (기존 엔드포인트 + 멀티테넌트) └─ 전역 인스턴스 ├─ connection_manager ├─ broadcaster ├─ audit_logger └─ cost_calculator ``` --- ## 🧪 테스트 계획 ### `test_phase8_multitenant.py` ``` ✓ 조직 생성 ✓ 사용자 추가 ✓ API 키 생성 ✓ 데이터 격리 확인 (org_id 검증) ✓ 역할 기반 권한 확인 ✓ JWT 토큰 검증 ✓ API 키 검증 ``` ### `test_phase8_websocket.py` ``` ✓ 클라이언트 연결 ✓ 메시지 브로드캐스트 ✓ 조직별 격리 (org_id 기반) ✓ 연결 해제 ✓ 오류 처리 ``` ### `test_phase8_audit.py` ``` ✓ 작업 로그 기록 ✓ 감시 로그 조회 ✓ 변경 이력 추적 ✓ IP 주소 기록 ``` ### `test_phase8_billing.py` ``` ✓ 비용 계산 ✓ 할당량 확인 ✓ 사용량 기록 ✓ 월간 리셋 ``` --- ## 📅 구현 일정 | 단계 | 작업 | 예상 시간 | 우선순위 | |------|------|---------|---------| | 1 | 멀티테넌트 인증 | 2-3시간 | P0 | | 2 | 감시 로그 | 2시간 | P1 | | 3 | WebSocket 실시간 | 2-3시간 | P1 | | 4 | 비용 관리 | 2시간 | P2 | | 5 | 통합 테스트 | 2시간 | P1 | | 6 | 문서화 | 1-2시간 | P1 | **총 예상 시간**: 11-15시간 --- ## 🔑 핵심 설계 결정 ### 1. 데이터 격리 - **방식**: 논리적 격리 (같은 DB, org_id로 필터링) - **이점**: 간단한 구현, 비용 효율적 - **주의**: 모든 쿼리에 org_id 포함 필수 ### 2. 실시간 업데이트 - **방식**: WebSocket + 메모리 브로드캐스트 - **이점**: 낮은 레이턴시, 간단한 구현 - **확장성**: Redis Pub/Sub으로 나중에 개선 가능 ### 3. 감시 로그 - **저장소**: Neo4j (기존 DB 활용) - **구조**: 모든 변경을 트리플 저장 - **쿼리**: Cypher로 변경 이력 검색 ### 4. 비용 모델 - **기반**: 작업 단위 (토큰, 노드, 시간) - **구독 계층**: Free, Pro, Enterprise - **특징**: 초과 사용량 추적 및 경고 --- ## 📊 예상 영향 ### 성능 - 멀티테넌트 오버헤드: < 5% - WebSocket 레이턴시: < 100ms - 감시 로깅 오버헤드: < 2% ### 보안 - JWT + API 키 이중 인증 - 조직별 데이터 격리 - 감시 로그로 완전한 감사 추적 ### 확장성 - 다중 테넌트: 수십 개 조직 지원 - 동시 WebSocket: 1000+ 연결 - 감시 로그: 월 백만 건 이상 기록 가능 --- ## 🚀 다음 단계 (Phase 9+) ``` Phase 9: 고급 분석 및 모니터링 ├─ 사용자별 대시보드 ├─ 성능 메트릭 ├─ 비용 예측 └─ 알림 및 경고 Phase 10: 엔터프라이즈 추가 기능 ├─ SSO (Single Sign-On) ├─ SAML/OAuth ├─ 세밀한 권한 관리 └─ 감사 보고서 자동 생성 ``` --- ## 📚 문서 - **PHASE_8_ENTERPRISE_GUIDE.md**: API 레퍼런스 - **코드 내 주석**: 함수 및 클래스 설명 - **테스트**: 사용 예제 --- **Phase 8로 온톨로지 플랫폼이 엔터프라이즈급 시스템으로 완성됩니다!** 🏢