Perf: BE 성능 최적화 - #4
Conversation
There was a problem hiding this comment.
Code Review
This pull request optimizes performance by converting multiple asynchronous endpoints to synchronous ones, resolving N+1 query issues using SQLAlchemy's joinedload, and introducing parallel Proxmox node querying via a global thread pool. It also configures connection pooling for PostgreSQL and adds a composite index on notifications. The review feedback highlights a potential thread pool exhaustion risk in services/proxmox_client.py if Proxmox nodes are unresponsive, and suggests initializing default values for VM metrics in api/routes/vmcontrol.py to avoid missing keys in the API response if a node query fails.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| # 여러 노드(서버)의 Proxmox 상태를 동시에 조회하기 위한 공용 스레드풀. | ||
| # 각 서버는 서로 다른 Proxmox 연결(requests.Session)을 쓰므로 "서버 단위" 병렬은 안전하다. | ||
| # (동일 서버 내 호출은 세션을 공유하므로 각 작업 함수 내부에서는 순차 처리한다.) | ||
| _node_executor = ThreadPoolExecutor(max_workers=12, thread_name_prefix="pmox-node") |
There was a problem hiding this comment.
위험 (High): _node_executor는 전역적으로 공유되는 고정 크기(max_workers=12)의 스레드풀입니다. 만약 특정 Proxmox 노드가 다운되거나 네트워크 장애로 인해 응답하지 않는 경우, get_proxmox_for_server에 설정된 180초(3분)의 긴 타임아웃 동안 해당 스레드가 완전히 블로킹됩니다.
여러 노드가 동시에 장애 상태이거나 동시 요청이 몰릴 경우, 12개의 스레드가 모두 타임아웃 대기로 고갈(Exhaustion)되어 정상 작동 중인 다른 노드의 상태 조회 요청까지 모두 무한 대기하게 되는 장애(Cascading Failure)가 발생할 수 있습니다.
권장 조치:
get_proxmox_for_server내부의ProxmoxAPI타임아웃 설정을 웹 API 환경에 맞게 대폭 줄여야 합니다 (예: 5~10초). (현재 diff 범위 외이므로 별도 수정 필요)- 스레드풀의
max_workers크기를 노드 수 및 예상 동시 요청 수에 맞춰 더 여유 있게 늘려두는 것이 안전합니다.
| _node_executor = ThreadPoolExecutor(max_workers=12, thread_name_prefix="pmox-node") | |
| _node_executor = ThreadPoolExecutor(max_workers=20, thread_name_prefix="pmox-node") |
| info_by_server.setdefault(vm.server_id, []).append({ | ||
| "vmid": vm.hypervisor_vmid, | ||
| "name": vm.display_name or vm.name, | ||
| "node": None, # 아래에서 서버명으로 채움 | ||
| "status": "unknown", | ||
| "owner_email": vm.owner.email if vm.owner else None, | ||
| "internal_ip": vm.internal_ip, | ||
| "created_at": str(vm.created_at) if vm.created_at else None, | ||
| "expires_at": str(vm.expires_at) if vm.expires_at else None, | ||
| }) |
There was a problem hiding this comment.
개선 사항 (Medium): Proxmox API 호출이 실패하거나 특정 노드가 오프라인 상태일 때, fetch 함수 내의 try-except 블록에서 예외가 무시되어 cpu_usage, maxmem, mem_usage, maxdisk, uptime 등의 키가 딕셔너리에 추가되지 않은 채 반환될 수 있습니다.
이로 인해 API 응답 스키마가 일관되지 않게 되어 프론트엔드에서 해당 필드에 접근할 때 KeyError나 undefined 관련 런타임 에러가 발생할 위험이 있습니다.
따라서 기본 딕셔너리를 생성할 때 Proxmox 관련 실시간 메트릭 필드들을 기본값(0)으로 미리 초기화해 두는 것이 안전합니다.
| info_by_server.setdefault(vm.server_id, []).append({ | |
| "vmid": vm.hypervisor_vmid, | |
| "name": vm.display_name or vm.name, | |
| "node": None, # 아래에서 서버명으로 채움 | |
| "status": "unknown", | |
| "owner_email": vm.owner.email if vm.owner else None, | |
| "internal_ip": vm.internal_ip, | |
| "created_at": str(vm.created_at) if vm.created_at else None, | |
| "expires_at": str(vm.expires_at) if vm.expires_at else None, | |
| }) | |
| info_by_server.setdefault(vm.server_id, []).append({ | |
| "vmid": vm.hypervisor_vmid, | |
| "name": vm.display_name or vm.name, | |
| "node": None, # 아래에서 서버명으로 채움 | |
| "status": "unknown", | |
| "cpu_usage": 0, | |
| "maxmem": 0, | |
| "mem_usage": 0, | |
| "maxdisk": 0, | |
| "uptime": 0, | |
| "owner_email": vm.owner.email if vm.owner else None, | |
| "internal_ip": vm.internal_ip, | |
| "created_at": str(vm.created_at) if vm.created_at else None, | |
| "expires_at": str(vm.expires_at) if vm.expires_at else None, | |
| }) |
No description provided.