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