03eea2098e
- 01~07 작업 문서를 운영 측정값 기준으로 정리 (문서의 DB 비밀번호 삭제) - app_highest_record 중복 8,795행 점수 기준 정리, 인덱스 5개 + UNIQUE 키 2개 추가 - 02·04 공통 측정 스크립트와 측정 결과, comparison.txt 추가 - 서버 처리 시간 18~292배 단축, 결과 행 수 동일 - 운영 데이터 백업과 권한(비밀번호 해시) 파일은 커밋에서 제외 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5.6 KiB
5.6 KiB
07단계: 사후 분석 및 최적화
운영 서버 적용 후 실제 성능을 분석하고 추가 최적화를 검토합니다.
📊 실제 성능 측정 결과
운영 DB에서 측정한 개선율
comparison.txt(04단계)의 값을 옮겨 적었습니다 (2026-09-15). 6개 쿼리 전체 값은 result/comparison.txt를 참고하세요.
| 항목 | 베이스라인 (02) | 개선 후 (04) | 개선율 | 상태 |
|---|---|---|---|---|
| EXPLAIN rows (q2 시간별·범위) | 10,103 | 22 | 459배 ⬇️ | [x] 확인 |
| EXPLAIN rows (q4 일간·범위) | 10,103 | 99 | 102배 ⬇️ | [x] 확인 |
| EXPLAIN rows (q3 일간·함수) | 10,103 | 5,851 | 1.7배 ⬇️ | [x] 확인 |
| 서버 처리 시간 (q4, ANALYZE) | 75.2ms | 0.47ms | 161배 ⬇️ | [x] 확인 |
| 서버 처리 시간 (q3 일간·함수, ANALYZE) | 75.3ms | 2.44ms | 31배 ⬇️ | [x] 확인 |
| 느린 쿼리 수 (0.5초 이상) | 측정 안 함 (당시 기준 3초·파일 로그) | 앱 쿼리 0건 (작업 후 1시간, 작업 SQL 5건만) | - | [ ] 24시간 후 재확인 |
참고: 01단계 사전 측정의 일간 랭킹(함수형)은
index_merge, rows 10,100이었습니다.MariaDB 10.11의
EXPLAIN FORMAT=JSON에는 cost 필드가 없어 스테이징의 cost 수치와 직접 비교할 수 없습니다. rows와 실행 시간으로 비교하고, 실제 실행 통계가 필요하면ANALYZE FORMAT=JSON <쿼리>의r_rows,r_total_time_ms를 사용하세요 (쿼리를 실제로 실행함).
저장된 파일
result/
├── premeasure_*.txt, premeasure_*.json ← 01 사전 측정
├── baseline_explain_q*.json ← 02 EXPLAIN
├── baseline_perf.txt ← 02 실행 시간
├── backup_app_highest_dup_rows.sql ← 03 삭제 행 백업
├── add_indexes_result.txt ← 03 인덱스 추가 결과
├── after_explain_q*.json ← 04 EXPLAIN
├── after_perf.txt ← 04 실행 시간
├── explain_summary.txt, timing_summary.txt
└── comparison.txt
🔍 성능 분석
쿼리 응답 시간 추이 (24시간 모니터링)
시간대별 성능:
- 평상시: _____ ms
- 피크 시간: _____ ms
- 최고 응답 시간: _____ ms
느린 쿼리 로그:
- 0.5초 이상: _____ 개 (개선 전: _____)
- 1초 이상: _____ 개 (개선 전: _____)
- 5초 이상: _____ 개 (개선 전: _____)
📈 비즈니스 영향
| 지표 | 개선 전 | 개선 후 | 변화 |
|---|---|---|---|
| 동시 사용자 | _____ | _____ | _____ |
| 평균 응답 시간 | _____ ms | _____ ms | _____ ms |
| 실패율 | _____ % | _____ % | _____ % |
| CPU 사용률 | _____ % | _____ % | _____ % |
| 메모리 사용률 | _____ % | _____ % | _____ % |
✅ 성공 여부 판단
성공 기준
- 범위형 쿼리(q2·q4·q6)의 rows가 베이스라인 대비 대폭 감소 (목표: 조회 기간의 실제 기록 수 수준)
- 느린 쿼리 대폭 감소
- 결과 행 수 변화 없음, 최고기록 화면 정상
- 서비스 가용성 100% 유지
- 사용자 불만 없음
결론
[ ] 성공: 모든 기준 달성, 운영 정상화
[ ] 부분 성공: 대부분 달성, 모니터링 계속
[ ] 실패: 목표 미달, 추가 최적화 필요
🔧 추가 최적화 제안
단계별 추가 개선
1단계: 쿼리 최적화 (안 1)
- 함수 제거 쿼리 적용 (03단계 3-4, Stage에서 수정 완료한 코드 배포)
- 예상 효과: 04단계의 함수형↔범위형 비교값(q1↔q2, q3↔q4, q5↔q6) 참고
2단계: 집계 테이블 도입 (안 2)
- daily_best_record, daily_typing_exam_record 생성
- 예상 효과: 히스토리/랭킹 조회 극적 개선
3단계: 아카이빙 (안 3)
- 13개월 이전 기록 아카이빙
- 예상 효과: 테이블 크기 감소, 백업 시간 단축
- 참고: 월 적재량이 전년 대비 약 2.1배로 늘고 있음 (01 사전 측정)
4단계: 애플리케이션 최적화 (안 5)
- N+1 쿼리 제거
- SQL Injection 보안 강화
- 최고기록 저장을
INSERT ... ON DUPLICATE KEY UPDATE로 변경 (UNIQUE 키 활용, 동시 저장 시 더 좋은 기록 유지) - 예상 효과: 동시 사용자 처리량 2~3배 향상
📋 모니터링 계획
지속적 모니터링 (24시간 이후)
mysql.slow_log SELECT 권한이 필요하고, log_output에 TABLE이 있어야 합니다. 조회가 끝나면 05단계 사후 작업대로 slow query log 설정을 원래 값으로 복원하세요.
# 일일 성능 리포트
mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e "
SELECT
DATE_FORMAT(start_time, '%Y-%m-%d %H:00:00') AS hour,
COUNT(*) AS slow_query_count,
AVG(query_time) AS avg_time,
MAX(query_time) AS max_time
FROM mysql.slow_log
WHERE start_time >= CURDATE()
GROUP BY hour
ORDER BY hour;
"
📝 최종 보고서 항목
-
실행 요약
- 작업 목표 및 결과
- 성과 지표
-
기술 분석
- EXPLAIN 개선 결과
- 실행 시간 비교
- 중복 정리 결과 (삭제 행 수, 백업 파일)
-
비즈니스 임팩트
- 사용자 경험 개선
- 서버 비용 절감 효과
-
추가 제안
- 다음 단계 최적화 계획
- 예상 추가 효과
🎯 다음 단계
- 스테이징에서 안 2 (집계 테이블) 테스트
- 운영 DB 추가 최적화 검토
- 팀 회의에서 결과 공유
- 성능 모니터링 대시보드 구축
운영 서버 성능 개선 프로젝트 완료! 🎉