- 통합 캐싱 시스템 구축: * utils/cache.js: Redis + 메모리 캐시 하이브리드 시스템 * Redis 연결 실패 시 자동 메모리 캐시 fallback * 캐시 키 생성, TTL 관리, 패턴 기반 무효화 * 캐시 미들웨어 및 무효화 헬퍼 함수 - 데이터베이스 쿼리 최적화: * utils/queryOptimizer.js: 쿼리 성능 분석 및 최적화 * 페이지네이션 헬퍼 (최대 100개 제한) * 인덱스 최적화 제안 시스템 * 배치 삽입 최적화 (100개 단위) * 최적화된 쿼리 템플릿 (작업자, 프로젝트, 작업보고서) - 응답 압축 및 최적화: * gzip 압축 미들웨어 (1KB 이상, 레벨 6) * 압축 제외 헤더 지원 (x-no-compression) * 성능 모니터링 시스템 - 성능 모니터링 API: * /api/performance/* 엔드포인트 추가 * 캐시 통계 및 관리 (조회, 초기화) * DB 성능 통계 (연결 수, 슬로우 쿼리) * 인덱스 분석 및 최적화 제안 * 쿼리 실행 계획 분석 (EXPLAIN) * 시스템 리소스 모니터링 - 실제 적용 사례: * workerController.js에 캐싱 및 페이지네이션 적용 * 캐시 히트/미스 로깅 * 캐시 무효화 자동 처리 - 보안 및 권한: * 성능 관련 API는 관리자 권한 필요 * 쿼리 분석은 시스템/관리자만 접근 가능 * 캐시 초기화는 관리자 전용 - Swagger 문서화: * 모든 성능 API 완전 문서화 * 요청/응답 스키마 및 예시 포함
389 lines
12 KiB
JavaScript
389 lines
12 KiB
JavaScript
/**
|
|
* @swagger
|
|
* tags:
|
|
* name: Performance
|
|
* description: 성능 모니터링 및 최적화 API
|
|
*/
|
|
|
|
const express = require('express');
|
|
const router = express.Router();
|
|
const { asyncHandler } = require('../utils/errorHandler');
|
|
const cache = require('../utils/cache');
|
|
const { getPerformanceStats, suggestIndexes, analyzeQuery } = require('../utils/queryOptimizer');
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/performance/cache/stats:
|
|
* get:
|
|
* tags: [Performance]
|
|
* summary: 캐시 통계 조회
|
|
* description: 현재 캐시 시스템의 상태와 통계를 조회합니다.
|
|
* security:
|
|
* - bearerAuth: []
|
|
* responses:
|
|
* 200:
|
|
* description: 캐시 통계 조회 성공
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* success:
|
|
* type: boolean
|
|
* example: true
|
|
* message:
|
|
* type: string
|
|
* example: "캐시 통계 조회 성공"
|
|
* data:
|
|
* type: object
|
|
* properties:
|
|
* type:
|
|
* type: string
|
|
* example: "memory"
|
|
* connected:
|
|
* type: boolean
|
|
* example: true
|
|
* keys:
|
|
* type: integer
|
|
* example: 42
|
|
* hits:
|
|
* type: integer
|
|
* example: 150
|
|
* misses:
|
|
* type: integer
|
|
* example: 25
|
|
* hitRate:
|
|
* type: number
|
|
* example: 0.857
|
|
* 401:
|
|
* description: 인증 필요
|
|
* 500:
|
|
* description: 서버 오류
|
|
*/
|
|
router.get('/cache/stats', asyncHandler(async (req, res) => {
|
|
const stats = cache.getStats();
|
|
res.success(stats, '캐시 통계 조회 성공');
|
|
}));
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/performance/cache/flush:
|
|
* post:
|
|
* tags: [Performance]
|
|
* summary: 캐시 초기화
|
|
* description: 모든 캐시 데이터를 삭제합니다. (관리자 전용)
|
|
* security:
|
|
* - bearerAuth: []
|
|
* responses:
|
|
* 200:
|
|
* description: 캐시 초기화 성공
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* success:
|
|
* type: boolean
|
|
* example: true
|
|
* message:
|
|
* type: string
|
|
* example: "캐시가 성공적으로 초기화되었습니다."
|
|
* 401:
|
|
* description: 인증 필요
|
|
* 403:
|
|
* description: 권한 부족
|
|
* 500:
|
|
* description: 서버 오류
|
|
*/
|
|
router.post('/cache/flush', asyncHandler(async (req, res) => {
|
|
// 관리자 권한 확인
|
|
if (req.user?.access_level !== 'admin' && req.user?.access_level !== 'system') {
|
|
return res.status(403).json({
|
|
success: false,
|
|
error: '캐시 초기화 권한이 없습니다.'
|
|
});
|
|
}
|
|
|
|
await cache.flush();
|
|
res.success(null, '캐시가 성공적으로 초기화되었습니다.');
|
|
}));
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/performance/database/stats:
|
|
* get:
|
|
* tags: [Performance]
|
|
* summary: 데이터베이스 성능 통계
|
|
* description: 데이터베이스 연결 상태와 성능 지표를 조회합니다.
|
|
* security:
|
|
* - bearerAuth: []
|
|
* responses:
|
|
* 200:
|
|
* description: DB 성능 통계 조회 성공
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* success:
|
|
* type: boolean
|
|
* example: true
|
|
* message:
|
|
* type: string
|
|
* example: "DB 성능 통계 조회 성공"
|
|
* data:
|
|
* type: object
|
|
* properties:
|
|
* connections:
|
|
* type: object
|
|
* properties:
|
|
* current:
|
|
* type: integer
|
|
* example: 5
|
|
* max:
|
|
* type: integer
|
|
* example: 151
|
|
* slowQueries:
|
|
* type: integer
|
|
* example: 0
|
|
* timestamp:
|
|
* type: string
|
|
* format: date-time
|
|
* 401:
|
|
* description: 인증 필요
|
|
* 500:
|
|
* description: 서버 오류
|
|
*/
|
|
router.get('/database/stats', asyncHandler(async (req, res) => {
|
|
const stats = await getPerformanceStats();
|
|
res.success(stats, 'DB 성능 통계 조회 성공');
|
|
}));
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/performance/database/indexes/{tableName}:
|
|
* get:
|
|
* tags: [Performance]
|
|
* summary: 인덱스 최적화 제안
|
|
* description: 특정 테이블의 인덱스 상태를 분석하고 최적화 제안을 제공합니다.
|
|
* security:
|
|
* - bearerAuth: []
|
|
* parameters:
|
|
* - in: path
|
|
* name: tableName
|
|
* required: true
|
|
* schema:
|
|
* type: string
|
|
* description: 분석할 테이블명
|
|
* example: "workers"
|
|
* responses:
|
|
* 200:
|
|
* description: 인덱스 분석 성공
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* success:
|
|
* type: boolean
|
|
* example: true
|
|
* message:
|
|
* type: string
|
|
* example: "인덱스 분석 완료"
|
|
* data:
|
|
* type: object
|
|
* properties:
|
|
* tableName:
|
|
* type: string
|
|
* example: "workers"
|
|
* currentIndexes:
|
|
* type: array
|
|
* items:
|
|
* type: object
|
|
* properties:
|
|
* name:
|
|
* type: string
|
|
* column:
|
|
* type: string
|
|
* unique:
|
|
* type: boolean
|
|
* suggestions:
|
|
* type: array
|
|
* items:
|
|
* type: object
|
|
* properties:
|
|
* type:
|
|
* type: string
|
|
* column:
|
|
* type: string
|
|
* reason:
|
|
* type: string
|
|
* sql:
|
|
* type: string
|
|
* 401:
|
|
* description: 인증 필요
|
|
* 500:
|
|
* description: 서버 오류
|
|
*/
|
|
router.get('/database/indexes/:tableName', asyncHandler(async (req, res) => {
|
|
const { tableName } = req.params;
|
|
const analysis = await suggestIndexes(tableName);
|
|
res.success(analysis, '인덱스 분석 완료');
|
|
}));
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/performance/query/analyze:
|
|
* post:
|
|
* tags: [Performance]
|
|
* summary: 쿼리 성능 분석
|
|
* description: SQL 쿼리의 실행 계획과 성능을 분석합니다. (관리자 전용)
|
|
* security:
|
|
* - bearerAuth: []
|
|
* requestBody:
|
|
* required: true
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* required:
|
|
* - query
|
|
* properties:
|
|
* query:
|
|
* type: string
|
|
* example: "SELECT * FROM workers WHERE department = ?"
|
|
* params:
|
|
* type: array
|
|
* items:
|
|
* type: string
|
|
* example: ["생산부"]
|
|
* responses:
|
|
* 200:
|
|
* description: 쿼리 분석 성공
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* success:
|
|
* type: boolean
|
|
* example: true
|
|
* message:
|
|
* type: string
|
|
* example: "쿼리 분석 완료"
|
|
* data:
|
|
* type: object
|
|
* properties:
|
|
* executionTime:
|
|
* type: integer
|
|
* example: 15
|
|
* explainResult:
|
|
* type: array
|
|
* items:
|
|
* type: object
|
|
* recommendations:
|
|
* type: array
|
|
* items:
|
|
* type: object
|
|
* properties:
|
|
* type:
|
|
* type: string
|
|
* message:
|
|
* type: string
|
|
* suggestion:
|
|
* type: string
|
|
* 401:
|
|
* description: 인증 필요
|
|
* 403:
|
|
* description: 권한 부족
|
|
* 500:
|
|
* description: 서버 오류
|
|
*/
|
|
router.post('/query/analyze', asyncHandler(async (req, res) => {
|
|
// 관리자 권한 확인
|
|
if (req.user?.access_level !== 'admin' && req.user?.access_level !== 'system') {
|
|
return res.status(403).json({
|
|
success: false,
|
|
error: '쿼리 분석 권한이 없습니다.'
|
|
});
|
|
}
|
|
|
|
const { query, params = [] } = req.body;
|
|
|
|
if (!query) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: '분석할 쿼리가 필요합니다.'
|
|
});
|
|
}
|
|
|
|
const analysis = await analyzeQuery(query, params);
|
|
res.success(analysis, '쿼리 분석 완료');
|
|
}));
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/performance/system/info:
|
|
* get:
|
|
* tags: [Performance]
|
|
* summary: 시스템 정보 조회
|
|
* description: 서버의 메모리, CPU, 업타임 등 시스템 정보를 조회합니다.
|
|
* security:
|
|
* - bearerAuth: []
|
|
* responses:
|
|
* 200:
|
|
* description: 시스템 정보 조회 성공
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* success:
|
|
* type: boolean
|
|
* example: true
|
|
* message:
|
|
* type: string
|
|
* example: "시스템 정보 조회 성공"
|
|
* data:
|
|
* type: object
|
|
* properties:
|
|
* uptime:
|
|
* type: number
|
|
* example: 3600.5
|
|
* memory:
|
|
* type: object
|
|
* properties:
|
|
* rss:
|
|
* type: integer
|
|
* heapTotal:
|
|
* type: integer
|
|
* heapUsed:
|
|
* type: integer
|
|
* external:
|
|
* type: integer
|
|
* nodeVersion:
|
|
* type: string
|
|
* example: "v18.17.0"
|
|
* platform:
|
|
* type: string
|
|
* example: "linux"
|
|
* 401:
|
|
* description: 인증 필요
|
|
* 500:
|
|
* description: 서버 오류
|
|
*/
|
|
router.get('/system/info', asyncHandler(async (req, res) => {
|
|
const systemInfo = {
|
|
uptime: process.uptime(),
|
|
memory: process.memoryUsage(),
|
|
nodeVersion: process.version,
|
|
platform: process.platform,
|
|
cpuUsage: process.cpuUsage(),
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
res.success(systemInfo, '시스템 정보 조회 성공');
|
|
}));
|
|
|
|
module.exports = router;
|