Files
AI/PHASE_8_ENTERPRISE_PLAN.md
lasta 47a710a8b9 Fix datetime deprecation warnings in Phase 8 modules
- Update all datetime.utcnow() to datetime.now(UTC) for Python 3.12+ compatibility
- Update all datetime.utcfromtimestamp() to datetime.fromtimestamp(..., UTC)
- Fix dataclass default_factory to use lambda: datetime.now(UTC)
- Update auth, audit, billing, and realtime modules
- Add UTC import from datetime module
- Update pytest configuration to include pytest-asyncio
- All 28 Phase 8 enterprise tests pass with no warnings

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-14 11:50:23 +09:00

16 KiB

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

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

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

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

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

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

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

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

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

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로 온톨로지 플랫폼이 엔터프라이즈급 시스템으로 완성됩니다! 🏢