# Qwen-Image-Layered로 포스터 자동 레이어 분해 (10/10): 프로덕션 운영 가이드 ## 시리즈 완결: 여정의 회고 10개 파트에 걸쳐 AI 논문에서 실제 서비스까지의 전체 과정을 다뤘다: 1. **프로젝트 개요** - 왜 필요한가? 2. **기술 스택** - Python, FastAPI, Vertex AI 선택 3. **모델 이해** - Qwen-Image-Layered 작동 원리 4. **로컬 실행** - 첫 추론 성공 5. **백엔드** - REST API, 작업 큐 6. **AI 통합** - Gemini Vision으로 설명 생성 7. **프론트엔드** - 웹 인터페이스 8. **품질 개선** - Alpha matting, 후처리 9. **배포** - Docker, Kubernetes, 모니터링 10. **[현재글] 운영 가이드** ## 프로덕션 체크리스트 ### 런칭 전 필수 사항 ```markdown Infrastructure: - [ ] GPU 인스턴스 확보 (최소 RTX 3090 급) - [ ] Redis 클러스터 구성 (Sentinel HA) - [ ] S3 또는 NFS 스토리지 - [ ] Nginx SSL 인증서 (Let's Encrypt) - [ ] 도메인 DNS 설정 Security: - [ ] API Rate Limiting (업로드: 5req/min) - [ ] 파일 크기 제한 (10MB) - [ ] 허용 확장자 검증 (jpg, png, webp만) - [ ] CORS 설정 (화이트리스트) - [ ] 환경 변수 암호화 (.env 파일 보호) Monitoring: - [ ] Prometheus + Grafana 대시보드 - [ ] Sentry 에러 트래킹 - [ ] 로그 수집 (ELK or CloudWatch) - [ ] Uptime 모니터링 (UptimeRobot) - [ ] Slack 알림 설정 Performance: - [ ] 모델 FP16 사용 - [ ] Redis 캐싱 활성화 - [ ] CDN 설정 (결과 파일) - [ ] Gzip 압축 - [ ] 이미지 lazy loading Backup: - [ ] Redis 스냅샷 (일 1회) - [ ] 결과 파일 S3 동기화 - [ ] 설정 파일 버전 관리 (Git) ``` ## 일반적인 장애 대응 ### 문제 1: GPU Out of Memory **증상**: ``` RuntimeError: CUDA out of memory Worker 프로세스 종료 ``` **원인**: - 동시 작업 과다 - 메모리 누수 - 고해상도 이미지 (2048px+) **해결**: ```bash # 1. 즉시 조치: Worker 재시작 docker restart poster-decomposer-worker # 2. 큐 길이 확인 redis-cli LLEN job_queue # 3. 동시 작업 제한 조정 # .env 파일: MAX_CONCURRENT_JOBS=1 # 2 → 1로 감소 # 4. 메모리 누수 확인 nvidia-smi -l 1 # 1초마다 모니터링 # 5. 강제 메모리 정리 python -c "import torch; torch.cuda.empty_cache()" ``` **예방**: ```python # worker.py에 메모리 모니터링 추가 async def process_job(job_id, job_data): try: # 작업 시작 전 메모리 체크 available = torch.cuda.get_device_properties(0).total_memory - \ torch.cuda.memory_allocated() if available < 4 * 1024**3: # 4GB 미만 raise RuntimeError("Insufficient GPU memory") # ... 처리 ... finally: torch.cuda.empty_cache() gc.collect() ``` ### 문제 2: 작업이 큐에서 진행되지 않음 **증상**: - 사용자가 업로드했지만 진행률 0% - 큐 길이는 증가하지만 처리 안 됨 **원인**: - Worker 프로세스 죽음 - Redis 연결 끊김 - 모델 로딩 실패 **디버깅**: ```bash # Worker 상태 확인 docker logs poster-decomposer-worker --tail 100 # Redis 연결 확인 redis-cli ping # 응답: PONG # 큐 내용 확인 redis-cli LRANGE job_queue 0 -1 # Worker 프로세스 확인 ps aux | grep worker.py # GPU 사용 확인 nvidia-smi ``` **해결**: ```bash # Worker 재시작 docker-compose restart worker # 또는 스케일 조정 docker-compose up -d --scale worker=2 ``` ### 문제 3: 레이어 품질 저하 **증상**: - 사용자 불만: "레이어가 뭉개져요" - 품질 점수 < 70 **원인**: - 후처리 비활성화 - FP16 → FP32 변환 이슈 - 원본 이미지 품질 낮음 **해결**: ```python # 1. Deep Matting 활성화 (특정 사용자) processor = LayerPostProcessor() layers = processor.process( layers, enable_deep_matting=True # False → True ) # 2. Guided Filter 파라미터 조정 refined = refine_alpha_channel( layer, radius=10, # 5 → 10 (더 부드럽게) eps=1e-4 # 1e-6 → 1e-4 ) # 3. 원본 이미지 전처리 from PIL import ImageEnhance # 샤프닝 enhancer = ImageEnhance.Sharpness(image) image = enhancer.enhance(1.5) ``` ### 문제 4: 서버 응답 느림 **증상**: - 업로드 타임아웃 - API 응답 > 30초 **디버깅**: ```bash # Nginx 로그 확인 tail -f /var/log/nginx/access.log # API 응답 시간 측정 time curl -X GET https://poster-decomposer.example.com/api/status/uuid # CPU/메모리 사용률 htop # 네트워크 I/O iftop ``` **해결**: ```python # 1. API 엔드포인트 최적화 @app.get("/api/status/{job_id}") async def get_job_status(job_id: str): # Redis에서 빠르게 조회 queue = JobQueue() # 캐시 추가 @functools.lru_cache(maxsize=1000) def get_cached_job(job_id): return queue.get_job(job_id) job_data = get_cached_job(job_id) return JobResponse(**job_data) # 2. Nginx 캐싱 # nginx.conf: location /api/status/ { proxy_cache api_cache; proxy_cache_valid 200 1s; # 1초 캐시 proxy_pass http://api_backend; } ``` ## 성능 모니터링 ### 주요 메트릭 ```python # metrics.py from prometheus_client import Summary, Counter, Gauge # 1. 작업 처리 시간 job_duration = Summary('job_duration_seconds', 'Job processing time') with job_duration.time(): process_job(job_id, job_data) # 2. 성공/실패 비율 jobs_total = Counter('jobs_total', 'Total jobs', ['status']) jobs_total.labels(status='completed').inc() jobs_total.labels(status='failed').inc() # 3. 현재 활성 작업 active_jobs = Gauge('active_jobs', 'Currently processing jobs') active_jobs.set(2) # 4. GPU 온도 gpu_temp = Gauge('gpu_temperature_celsius', 'GPU temperature') import pynvml pynvml.nvmlInit() handle = pynvml.nvmlDeviceGetHandleByIndex(0) temp = pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU) gpu_temp.set(temp) ``` ### Grafana 알림 규칙 ```yaml # grafana-alerts.yaml groups: - name: poster_decomposer interval: 1m rules: - alert: HighJobFailureRate expr: rate(jobs_total{status="failed"}[5m]) / rate(jobs_total[5m]) > 0.1 for: 5m labels: severity: warning annotations: summary: "Job failure rate > 10%" - alert: GPUMemoryHigh expr: gpu_memory_bytes / gpu_memory_total_bytes > 0.95 for: 2m labels: severity: critical annotations: summary: "GPU memory usage > 95%" - alert: QueueBacklog expr: queue_length > 50 for: 10m labels: severity: warning annotations: summary: "Job queue has {{ $value }} items" ``` ## 사용자 피드백 수집 ### 품질 평가 버튼 ```html
결과에 만족하시나요?