Dify 성능 최적화 SKILL
개요
본 SKILL은 Dify 워크플로와 플러그인의 포괄적인 성능 최적화 가이드를 제공하며, 그래프 구조 최적화, LLM 비용 제어, Worker 풀 튜닝, 변수 풀 최적화, 병렬 처리 및 성능 모니터링과 같은 핵심 주제를 다룹니다.
적용 시나리오
다음과 같은 상황에서 이 SKILL을 사용해야 합니다:
- ⏱️ 워크플로 실행 시간이 너무 길어 성능 최적화가 필요할 때
- 💰 LLM API 비용이 너무 높아 토큰 소모를 줄여야 할 때
- 🚀 동시 처리 능력이 부족하여 처리량을 늘려야 할 때
- 📊 시스템 리소스 활용률이 낮아 구성을 최적화해야 할 때
- 📈 대규모 데이터 처리 성능 병목 현상이 발생할 때
- 🔍 성능 진단 및 모니터링이 필요할 때
핵심 가치
- 정량화 가능한 최적화 효과: 병렬 브랜치 실행으로 40-60% 시간 단축, LLM 캐시로 30-50% 호출 감소
- 실전 사례 기반: 최적화 전후 비교가 포함된 5가지 실제 최적화 사례
- 포괄적인 최적화 전략: 워크플로, LLM, 플러그인, 모니터링 등 모든 계층을 포괄
- 신속한 진단 도구: 5가지 차원의 성능 진단 체크리스트
빠른 진단 체크리스트
최적화를 시작하기 전에 다음 체크리스트를 사용하여 성능 문제를 신속히 찾아내십시오:
📊 그래프 구조 진단
- 직렬 종속성이 너무 많지 않은가요?
- 병렬 브랜치를 사용할 수 있나요? (v0.8.0+)
- 불필요한 노드와 연결이 있나요?
- 노드 수가 50개를 초과하나요?
- 적절한 디자인 패턴을 사용하고 있나요?
🤖 LLM 사용 진단
- 모델 선택이 적절한가요? (가벼운 작업에는 가벼운 모델)
- 프롬프트가 너무 길지 않나요? (간결하게 할 수 있나요?)
- LLM 응답 캐시를 활성화했나요?
- 구조화된 출력(JSON Schema)을 사용하고 있나요?
- 토큰 소모가 예산 내에 있나요?
⚙️ Worker 풀 진단
- Worker 수 구성이 합리적인가요? (2-8 스레드)
- 큐 깊이가 너무 크지 않나요?
- Worker 유휴 상태로 낭비되는 부분이 있나요?
- 확장/축소 전략이 합리적인가요?
💾 변수 풀 진단
- 변수 풀에 큰 객체를 저장하고 있나요?
- 변수를 적시에 정리하고 있나요?
- 복사 대신 참조를 사용하고 있나요?
- 변수 범위가 합리적인가요?
🔄 동시 처리 진단
- 반복자에
parallel_nums를 구성했나요? - 스트리밍 응답을 사용하고 있나요?
- 비동기 작업이 적절히 구성되었나요?
- 블로킹 작업이 있나요?
제1부: 그래프 구조 최적화
1.1 직렬 종속성 줄이기
문제: 너무 많은 직렬 종속성으로 인해 워크플로 실행 시간이 선형적으로 증가합니다.
최적화 전략:
- 병렬화 가능한 노드 식별: 노드 간 종속 관계를 분석하여 병렬로 실행할 수 있는 노드를 찾습니다.
- 병렬 브랜치 사용: Dify v0.8.0+는 병렬 브랜치 기능을 지원합니다.
- 워크플로 재구성: 직렬 흐름을 병렬 + 집계 패턴으로 재구성합니다.
예시:
# 최적화 전: 직렬 실행 (총 시간 = 3 + 2 + 4 = 9초)
Start → API호출1(3s) → API호출2(2s) → API호출3(4s) → End
# 최적화 후: 병렬 실행 (총 시간 = max(3,2,4) = 4초)
Start → [API호출1(3s), API호출2(2s), API호출3(4s)] → 변수 집계 → End
최적화 효과: 실행 시간 40-60% 감소
1.2 병렬 브랜치 기능 (v0.8.0+)
Dify v0.8.0은 여러 브랜치를 동시에 실행할 수 있는 병렬 브랜치 기능을 도입했습니다.
사용 방법:
- 워크플로 편집기에서 여러 브랜치를 생성합니다.
- 브랜치 간에 데이터 종속성이 없는지 확인합니다.
- 변수 수집기를 사용하여 결과를 수집합니다.
적용 시나리오:
- 여러 HTTP Request 노드 병렬 호출
- 여러 Tool 노드 병렬 실행
- 지식 베이스 검색 + API 호출 병렬 처리
- 다중 모델 병렬 추론 (모델 비교 시나리오)
구성 예시:
# 병렬 브랜치 구성
parallel_branches:
- branch_1:
nodes: [http_request_1, code_1]
- branch_2:
nodes: [http_request_2, code_2]
- branch_3:
nodes: [tool_1, llm_1]
1.3 불필요한 노드 방지
일반적인 문제:
- 중복된 데이터 변환 노드
- 불필요한 중간 변수 노드
- 과도한 조건 판단 노드
최적화 제안:
- 유사한 처리 로직을 병합합니다.
- 여러 단순 노드 대신 코드 노드를 사용합니다.
- 조건 판단 로직을 단순화합니다.
1.4 그래프 구조 디자인 패턴
작업 유형에 따라 적절한 디자인 패턴을 선택하십시오:
- 순차 파이프라인: Start → LLM → End (간단한 작업)
- 조건 분기: Start → LLM → If-Else → 다중 경로 (의사 결정 작업)
- 반복 처리: Start → Code → Iteration → LLM (배치 작업)
- 지식 검색: Start → Knowledge-Retrieval → LLM (RAG 애플리케이션)
- 다중 소스 집계: 여러 Tool/HTTP → Variable-Aggregator → LLM (데이터 집계)
상세 문서: workflow-optimization/graph-structure.md 참조
제2부: LLM 최적화 전략
2.1 모델 선택 전략
적절한 모델을 선택하는 것이 비용 최적화의 핵심입니다.
모델 선택 매트릭스:
| 작업 유형 | 권장 모델 | 토큰 비용 | 적용 시나리오 |
|---|---|---|---|
| 간단한 분류 | Qwen-Turbo, Gemini Flash | 낮음 | 텍스트 분류, 감정 분석 |
| 텍스트 생성 | GPT-3.5-turbo, Qwen-Plus | 중간 | 콘텐츠 생성, 요약 |
| 복잡한 추론 | GPT-4, Claude Opus | 높음 | 복잡한 분석, 의사 결정 |
| 로컬 배포 | Ollama (Llama 3, Qwen) | API 비용 0 | 개인정보 민감 시나리오 |
비용 비교:
- GPT-4: $0.03/1K 토큰 (입력) + $0.06/1K 토큰 (출력)
- GPT-3.5-turbo: $0.0015/1K 토큰 (입력) + $0.002/1K 토큰 (출력)
- Qwen-Turbo: ¥0.0008/1K 토큰 (약 20배 비용 차이)
최적화 제안:
- 가벼운 작업에는 가벼운 모델 사용 (Qwen/Gemini)
- 복잡한 작업에는 강력한 모델 사용 (GPT-4/Claude)
- 비용 절감을 위해 로컬 모델(Ollama) 고려
2.2 프롬프트 최적화 팁
프롬프트 간소화:
# 최적화 전 (150 토큰)
"""
당신은 전문 고객 지원 담당자입니다. 다음 고객 질문을 주의 깊게 읽고, 제품 지식 베이스를 기반으로
상세하고 정확하며 친절한 답변을 제공하십시오. 답변 시 다음 사항에 유의하십시오:
1. 전문적이고 예의 바른 어조 유지
2. 구체적인 해결 방안 제공
3. 확실하지 않은 경우 설명
4. 관련 제품 문서 인용
고객 질문: {query}
"""
# 최적화 후 (50 토큰)
"""
고객 지원 담당자로서 지식 베이스를 바탕으로 고객 질문에 답변하되, 전문적이고 예의 바르게 하십시오.
질문: {query}
"""
토큰 절약: 66% (150 → 50 토큰)
기타 최적화 팁:
- 중복된 설명과 예제 제거
- 간결한 명령어 사용
- 반복되는 컨텍스트 방지
- 구조화된 출력을 사용하여 파싱 비용 절감
2.3 캐시 전략
LLM 응답 캐시:
Redis를 사용하여 LLM 응답을 캐시하고 중복 호출을 방지합니다.
구현 방안:
import hashlib
import redis
# Redis 연결
r = redis.Redis(host='localhost', port=6379, db=0)
def get_llm_response_with_cache(prompt, model="gpt-3.5-turbo"):
# 캐시 키 생성
cache_key = f"llm:{model}:{hashlib.md5(prompt.encode()).hexdigest()}"
# 캐시 확인
cached = r.get(cache_key)
if cached:
return cached.decode('utf-8')
# LLM 호출
response = call_llm_api(prompt, model)
# 캐시 저장 (TTL: 1시간)
r.setex(cache_key, 3600, response)
return response
캐시 전략:
- LLM 응답 캐시: 동일한 프롬프트에 대해 캐시된 결과 반환
- 지식 베이스 검색 캐시: 동일한 쿼리에 대해 캐시된 벡터 반환
- 노드 결과 캐시: 동일한 노드의 중복 실행 방지
최적화 효과: API 호출 30-50% 감소
2.4 구조화된 출력
JSON Schema를 사용하여 LLM 출력 안정성을 보장하고 파싱 오류 및 재시도를 줄입니다.
구성 예시:
{
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": ["기술", "영업", "사후"]
},
"priority": {
"type": "integer",
"minimum": 1,
"maximum": 5
},
"summary": {
"type": "string",
"maxLength": 100
}
},
"required": ["category", "priority", "summary"]
}
최적화 효과:
- 출력 토큰 수 감소
- 파싱 오류 및 재시도 방지
- 다운스트림 노드 처리 효율성 향상
상세 문서: llm-optimization/ 참조
제3부: Worker 풀 튜닝
3.1 동적 작업자 스레드 풀
Dify는 동적 Worker 풀을 사용하여 노드를 병렬로 실행하며, 기본 구성은 2-8개의 작업 스레드입니다.
Worker 풀 아키텍처:
GraphEngine
├── ReadyQueue (준비된 노드 큐)
├── EventQueue (이벤트 큐)
├── Dispatcher (이벤트 디스패처)
└── WorkerPool (2-8개 작업 스레드)
├── Worker 1
├── Worker 2
├── ...
└── Worker N
3.2 구성 파라미터
주요 구성:
| 파라미터 | 기본값 | 설명 | 최적화 제안 |
|---|---|---|---|
| min_workers | 2 | 최소 Worker 수 | 최소 동시 요구 사항에 따라 설정 |
| max_workers | 8 | 최대 Worker 수 | CPU 코어 수에 따라 설정 |
| queue_depth_threshold | 10 | 확장 트리거 임계값 | 평균 큐 깊이에 따라 조정 |
| idle_timeout | 60s | Worker 유휴 시간 초과 | 작업 빈도에 따라 조정 |
확장/축소 전략:
# 확장 조건
if queue_depth > queue_depth_threshold and workers < max_workers:
add_worker()
# 축소 조건
if worker_idle_time > idle_timeout and workers > min_workers:
remove_worker()
3.3 성능 기준
테스트 시나리오: 100개 노드의 워크플로
| Worker 수 | 실행 시간 | 처리량 | CPU 사용률 |
|---|---|---|---|
| 2 | 45초 | 2.2 노드/초 | 40% |
| 4 | 25초 | 4.0 노드/초 | 70% |
| 8 | 15초 | 6.7 노드/초 | 95% |
최적화 효과: Worker 수를 2에서 8로 늘리면 처리량이 3배 향상
3.4 최적화 제안
- CPU 코어 수에 따라
max_workers설정: 일반적으로 CPU 코어 수의 1-2배로 설정합니다. - 큐 깊이 모니터링: 큐 깊이가 지속적으로 높으면
max_workers를 늘립니다. - 과도한 확장 방지: Worker가 너무 많으면 컨텍스트 전환 오버헤드가 발생합니다.
- 비동기 I/O 사용: I/O 집약적인 작업의 경우 비동기 처리를 사용하여 효율성을 높입니다.
상세 문서: workflow-optimization/parallel-execution.md 참조
제4부: 변수 풀 최적화
4.1 큰 객체 저장 방지
문제: 변수 풀에 큰 객체(예: 큰 파일, 긴 텍스트)를 저장하면 많은 메모리를 사용합니다.
최적화 전략:
# ❌ 비권장: 큰 객체 저장
variables = {
"large_file": open("large_file.pdf", "rb").read(), # 10MB
"long_text": "..." * 100000 # 1MB
}
# ✅ 권장: 참조 저장
variables = {
"file_path": "/path/to/large_file.pdf",
"text_summary": summarize(long_text) # 요약만 저장
}
4.2 복사 대신 참조 사용
문제: 변수 복사는 메모리 사용량과 처리 시간을 증가시킵니다.
최적화 전략:
# ❌ 비권장: 변수 복사
result = process_data(data.copy())
# ✅ 권장: 참조 사용
result = process_data(data) # 읽기 전용 작업은 참조 사용
4.3 변수 적시 정리
문제: 더 이상 사용되지 않는 변수가 메모리를 차지합니다.
최적화 전략:
# 코드 노드에서 더 이상 사용하지 않는 변수 정리
def main(args):
# 데이터 처리
large_data = process_large_data(args["input"])
result = extract_result(large_data)
# 큰 객체 정리
del large_data
return {"result": result}
4.4 변수 범위 관리
변수 유형:
- 시스템 변수:
sys.query,sys.files,sys.conversation_id - 노드 변수:
{{#node_id.output#}} - 환경 변수: 구성 및 키 관리
- 반복 변수: 루프의 현재 항목 및 인덱스
최적화 제안:
- 최소 범위 사용
- 전역 변수 오염 방지
- 임시 변수 적시 해제
상세 문서: workflow-optimization/variable-pool.md 참조
제5부: 병렬 처리 및 스트리밍 응답
5.1 반복자 병렬 처리
반복자의 parallel_nums 매개변수를 사용하여 배열 데이터를 병렬로 처리합니다.
구성 예시:
iteration_node:
type: iteration
inputs:
items: "{{#code_node.output.items#}}"
config:
parallel_nums: 5 # 5개 항목 병렬 처리
성능 비교:
| 데이터 양 | 직렬 처리 | 병렬 처리 (5) | 향상 |
|---|---|---|---|
| 10개 항목 | 20초 | 5초 | 4배 |
| 50개 항목 | 100초 | 25초 | 4배 |
| 100개 항목 | 200초 | 50초 | 4배 |
5.2 스트리밍 응답
스트리밍 응답을 사용하여 사용자 경험을 개선하고 첫 바이트까지의 시간(TTFB)을 줄입니다.
구현 방식:
# API 호출에서 스트리밍 응답 활성화
response = requests.post(
"https://api.dify.ai/v1/workflows/run",
headers={"Authorization": f"Bearer {api_key}"},
json={"inputs": {...}, "response_mode": "streaming"},
stream=True
)
for line in response.iter_lines():
if line:
event = json.loads(line)
print(event["data"])
최적화 효과:
- TTFB 80% 감소
- 사용자 인지 응답 시간 50% 감소
5.3 비동기 작업 처리
Celery를 사용하여 장기 실행 작업을 비동기적으로 처리합니다.
아키텍처:
API 서버 → Celery 큐 → Worker 풀 → 작업 실행
적용 시나리오:
- 대용량 파일 처리
- 일괄 데이터 가져오기
- 장시간 API 호출
- 예약된 작업
5.4 이벤트 기반 아키텍처 최적화
Dify는 이벤트 기반 아키텍처를 채택하여 노드 실행 시 이벤트 스트림을 생성합니다.
최적화 전략:
- 비차단 이벤트 처리 사용
- 이벤트 큐 일괄 처리
- 이벤트 디스패치 로직 최적화
상세 문서: workflow-optimization/parallel-execution.md 참조
제6부: 성능 최적화 사례
사례 1: 데이터베이스 쿼리 최적화 (N+1 문제)
적용 시나리오:
- 워크플로에서 여러 관련 데이터를 쿼리해야 하는 경우
- 데이터베이스에 대한 반복 쿼리가 있는 경우
- 실행 시간이 데이터 양에 따라 선형적으로 증가하는 경우
문제 특징:
- 쿼리 횟수 = 1 + N (N은 데이터 레코드 수)
- 데이터베이스 연결이 빈번하게 생성 및 종료됨
- 실행 시간이 너무 김
최적화 전 상태:
# 사용자 목록 조회
users = query("SELECT * FROM users LIMIT 100")
# 각 사용자의 주문 조회 (N+1 문제)
for user in users:
orders = query(f"SELECT * FROM orders WHERE user_id = {user.id}")
user.orders = orders
- 쿼리 횟수: 101회 (1 + 100)
- 실행 시간: 10.5초
- 데이터베이스 부하: 높음
최적화 방안:
# 모든 데이터를 한 번에 조회
users = query("SELECT * FROM users LIMIT 100")
user_ids = [user.id for user in users]
# 주문 일괄 조회
orders = query(f"SELECT * FROM orders WHERE user_id IN ({','.join(map(str, user_ids))})")
# 메모리에서 데이터 연결
orders_by_user = {}
for order in orders:
if order.user_id not in orders_by_user:
orders_by_user[order.user_id] = []
orders_by_user[order.user_id].append(order)
for user in users:
user.orders = orders_by_user.get(user.id, [])
최적화 후 효과:
- 쿼리 횟수: 2회 (98% 감소)
- 실행 시간: 2.1초 (80% 감소)
- 데이터베이스 부하: 낮음
예상 이점:
- 단기: 실행 시간 80% 감소, 데이터베이스 부하 감소
- 장기: 더 큰 데이터 규모 지원, 시스템 확장성 향상
- ROI: 개발 비용 2시간, 월간 서버 비용 30% 절감
사례 2: 대규모 노드 렌더링 최적화 (100+ 노드)
적용 시나리오:
- 워크플로에 100개 이상의 노드 포함
- 프론트엔드 렌더링 끊김
- 편집기 응답 느림
문제 특징:
- 페이지 로드 시간 > 5초
- 노드 드래그 지연 현상 현저함
- 브라우저 메모리 사용량 높음
최적화 전 상태:
- 노드 수: 150개
- 페이지 로드 시간: 8.5초
- 메모리 사용량: 500MB
- 사용자 경험: 심각한 끊김
최적화 방안:
- 가상 스크롤: 보이는 영역의 노드만 렌더링
- 노드 지연 로딩: 필요에 따라 노드 세부 정보 로드
- Canvas 최적화: DOM 렌더링 대신 Canvas 사용
- 상태 관리 최적화: 불필요한 재렌더링 감소
최적화 후 효과:
- 페이지 로드 시간: 2.1초 (75% 감소)
- 메모리 사용량: 150MB (70% 감소)
- 사용자 경험: 원활함
예상 이점:
- 단기: 사용자 경험 현저히 향상
- 장기: 더 복잡한 워크플로 설계 지원
- ROI: 개발 비용 1주, 사용자 만족도 40% 향상
사례 3: 워크플로 로그 삭제 성능 최적화
적용 시나리오:
- 정기적인 이전 로그 정리 필요
- 로그 데이터 많음 (수백만 건)
- 삭제 작업 시간이 오래 걸림
문제 특징:
- 삭제 작업이 데이터베이스를 차단
- 정상 업무에 영향
- 실행 시간 예측 불가
최적화 전 상태:
- 로그 수: 500만 건
- 삭제 시간: 45분
- 데이터베이스 잠금: 장시간 테이블 잠금
- 업무 영향: 심각
최적화 방안:
# 최적화 전: 일괄 삭제
DELETE FROM workflow_logs WHERE created_at < '2024-01-01';
# 최적화 후: 분할 삭제
batch_size = 10000
while True:
deleted = DELETE FROM workflow_logs
WHERE created_at < '2024-01-01'
LIMIT batch_size;
if deleted < batch_size:
break
# 짧은 대기, 데이터베이스 잠금 해제
sleep(0.1)
최적화 후 효과:
- 삭제 시간: 50분 (약간 증가했지만 업무 차단 없음)
- 데이터베이스 잠금: 짧은 시간 잠금 (0.1초)
- 업무 영향: 없음
예상 이점:
- 단기: 업무에 영향 없음
- 장기: 데이터베이스 안정성 향상
- ROI: 개발 비용 4시간, 업무 중단 손실 방지
사례 4: TiDB 분산 스토리지 통합
적용 시나리오:
- 지식 베이스 데이터 큼 (TB 단위)
- 단일 PostgreSQL 성능 병목
- 수평 확장 능력 필요
문제 특징:
- 쿼리 응답 시간 > 5초
- 데이터베이스 CPU 사용률 > 90%
- 더 이상 확장 불가
최적화 전 상태:
- 데이터베이스: PostgreSQL 단일
- 데이터 양: 2TB
- 쿼리 응답 시간: 8.5초
- 확장 능력: 제한적
최적화 방안:
- TiDB로 마이그레이션: 분산 SQL 데이터베이스
- 벡터 인덱스 최적화: TiDB Vector를 사용하여 검색 가속화
- 읽기/쓰기 분리: 읽기 요청을 여러 복제본에 분산
- 파티션 테이블: 시간별 파티셔닝으로 쿼리 효율성 향상
최적화 후 효과:
- 쿼리 응답 시간: 1.2초 (86% 감소)
- 데이터베이스 CPU: 40% (50% 감소)
- 확장 능력: 10배 데이터 규모 지원
예상 이점:
- 단기: 쿼리 성능 현저히 향상
- 장기: 비즈니스 급속 성장 지원
- ROI: 마이그레이션 비용 2주, 10배 비즈니스 성장 지원
사례 5: Higress AI Gateway 고가용성 배포
적용 시나리오:
- 프로덕션 환경에서 고가용성 필요
- 여러 LLM 모델 부하 분산
- 장애 조치 능력 필요
문제 특징:
- 단일 장애점 위험
- LLM API 호출 불안정
- 트래픽 급증 대응 불가
최적화 전 상태:
- 아키텍처: 단일 인스턴스 Dify
- 가용성: 95%
- 장애 복구 시간: 10분
- 트래픽 피크: 대응 불가
최적화 방안:
# Higress AI Gateway 구성
apiVersion: networking.higress.io/v1
kind: McpBridge
metadata:
name: dify-gateway
spec:
replicas: 3 # 3개 인스턴스
loadBalancer:
algorithm: round-robin
healthCheck:
enabled: true
interval: 10s
failover:
enabled: true
maxRetries: 3
아키텍처:
사용자 요청 → Higress Gateway → [Dify 인스턴스 1, Dify 인스턴스 2, Dify 인스턴스 3]
↓
부하 분산 + 장애 조치
최적화 후 효과:
- 가용성: 99.9%
- 장애 복구 시간: < 1초 (자동 전환)
- 트래픽 피크: 10배 트래픽 지원
예상 이점:
- 단기: 시스템 안정성 현저히 향상
- 장기: 비즈니스 핵심 애플리케이션 지원
- ROI: 배포 비용 3일, 업무 중단 손실 방지
제7부: 성능 테스트 및 모니터링
7.1 성능 지표 체계
워크플로 실행 지표:
| 지표 | 목표값 | 설명 |
|---|---|---|
| 평균 실행 시간 | < 2초 | 워크플로 전체 실행 시간 |
| P95 실행 시간 | < 5초 | 95% 요청의 실행 시간 |
| P99 실행 시간 | < 10초 | 99% 요청의 실행 시간 |
| 노드 성공률 | > 99% | 노드 실행 성공 비율 |
| API 호출 성공률 | > 99.5% | 외부 API 호출 성공률 |
| 동시 처리 능력 | > 100 QPS | 초당 처리 요청 수 |
리소스 사용 지표:
| 지표 | 목표값 | 설명 |
|---|---|---|
| CPU 사용률 | < 70% | 평균 CPU 사용률 |
| 메모리 사용률 | < 80% | 평균 메모리 사용률 |
| 데이터베이스 연결 수 | < 80% | 연결 풀 사용률 |
| Redis 메모리 | < 70% | Redis 메모리 사용률 |
비용 지표:
| 지표 | 목표값 | 설명 |
|---|---|---|
| 토큰 사용 비용 | < 예산 | LLM API 비용 |
| 인프라 비용 | < 예산 | 서버, 데이터베이스 비용 |
| 사용자당 비용 | 지속적 감소 | 단일 사용자 비용 |
7.2 모니터링 도구
LLMOps 모니터링:
# Dify 내장 모니터링
from dify import Monitor
monitor = Monitor()
# 워크플로 실행 기록
monitor.log_workflow_execution(
workflow_id="xxx",
execution_time=2.5,
node_count=10,
token_usage=1500,
cost=0.003
)
# 통계 데이터 조회
stats = monitor.get_workflow_stats(
workflow_id="xxx",
time_range="7d"
)
실행 추적:
- 워크플로 실행 이력
- 노드 실행 단계 통계
- LLM 사용 통계
- 오류 로그 및 재시도 기록
리소스 모니터링:
# Prometheus + Grafana
# CPU, 메모리, 네트워크, 디스크 모니터링
# 예시 쿼리
rate(dify_workflow_execution_total[5m]) # 워크플로 실행 속도
histogram_quantile(0.95, dify_workflow_duration_seconds) # P95 실행 시간
7.3 성능 테스트 방법
부하 테스트:
# Locust를 사용한 부하 테스트
from locust import HttpUser, task, between
class DifyUser(HttpUser):
wait_time = between(1, 3)
@task
def run_workflow(self):
self.client.post(
"/v1/workflows/run",
headers={"Authorization": f"Bearer {self.api_key}"},
json={"inputs": {"query": "테스트 질문"}}
)
# 테스트 실행
# locust -f load_test.py --users 100 --spawn-rate 10
스트레스 테스트:
# Apache Bench 사용
ab -n 1000 -c 50 -H "Authorization: Bearer xxx" \
-p request.json \
https://api.dify.ai/v1/workflows/run
벤치마크 테스트:
# 성능 벤치마크 테스트
import time
def benchmark_workflow(workflow_id, iterations=100):
times = []
for i in range(iterations):
start = time.time()
run_workflow(workflow_id)
end = time.time()
times.append(end - start)
return {
"avg": sum(times) / len(times),
"p50": sorted(times)[len(times) // 2],
"p95": sorted(times)[int(len(times) * 0.95)],
"p99": sorted(times)[int(len(times) * 0.99)]
}
7.4 성능 분석 도구
Python Profiler:
import cProfile
...
[번역을 위해 잘렸습니다]