Files
jisangs 03eea2098e 운영 서버에서 DB index 적용 작업 진행
- 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>
2026-09-15 23:30:42 +09:00

225 lines
10 KiB
Markdown

# 운영 서버 DB 성능 개선 (260915-improve-production)
> 스테이징에서 성공한 인덱스 추가를 운영 서버에 적용합니다.
---
## 📋 개요
| 항목 | 내용 |
|---|---|
| **목표** | 복합 인덱스 추가로 랭킹·기록 조회 쿼리의 검사 행 수와 실행 시간 감소 |
| **대상 DB** | chocomae.jinaju.com (AWS EC2 Docker, MariaDB 10.11.13) |
| **작업 방식** | 온라인 DDL (ALGORITHM=INPLACE, LOCK=NONE) |
| **예상 시간** | 약 1시간 (백업·측정·검증 포함, 인덱스 생성은 여유 있게 30분) |
| **위험도** | 인덱스 추가: 낮음 (스테이징 검증 완료) / 중복 정리: 중간 (운영 데이터 삭제, 백업 후 진행) |
| **영향 범위** | 인덱스: 쿼리 응답 시간만 개선 (서비스 무중단) / 중복 정리: `app_highest_record`의 중복 행 삭제 |
---
## 📏 운영 사전 측정 결과 (01단계, 2026-09-15)
| 항목 | 값 | 비고 |
|---|---|---|
| best_record | 약 115만 행, data 72.5MB / index 72.1MB | 월별 합계 1,210,482행 |
| typing_exam_record | 약 13만 행, data 10.3MB / index 10.9MB | |
| 월 적재량 | 최근 3개월(6~8월) 평균 57,968행/월 | 전년 같은 기간(27,049행)의 약 2.1배 |
| 일간 랭킹 EXPLAIN | `index_merge` (MaestroID ∩ AppID), rows **10,100** | 전체 스캔(ALL)이 아님 |
| app_highest_record 중복 | **4,823조합, 삭제 대상 8,795행** (약 3.8%) | 03-0에서 점수 기준으로 정리 |
| typing_exam_highest_record 중복 | 0건 | |
| jisangs 계정 권한 | `SELECT, ALTER` + 임시 부여(SUPER, 최고기록 테이블 2개 INSERT·DELETE, `mysql.slow_log` SELECT) | 05단계에서 회수 |
| Slow query log | ON, long_query_time=3, log_output=FILE | 05단계 복원 기준값 |
> 운영 DB에는 `MaestroID`, `AppID`, `PlayerID` 단일 인덱스가 이미 있어 스테이징(type=ALL)과 기준값이 다릅니다. 개선율은 스테이징 수치가 아니라 **운영에서 측정한 02(베이스라인)와 04(검증) 결과**로 계산합니다.
상세: [result/premeasure_summary.txt](result/premeasure_summary.txt)
---
## 🎯 기대 효과
| 항목 | 스테이징 결과 | 운영 예상 |
|---|---|---|
| **EXPLAIN cost** | 8.63 → 0.004 (**2,158배 ⬇️**) | MariaDB 10.11 EXPLAIN JSON에는 cost가 없어 rows·실행 시간으로 비교 |
| **EXPLAIN rows** | 5,804 → 1 (**5,800배 ⬇️**) | 10,100 → 해당 시간·날짜의 실제 기록 수 수준 (범위 조건 쿼리) |
| **접근 방식** | ALL → range | index_merge → range |
| **실행 시간** | ~2초 (충분) | 02·04에서 측정 |
| **느린 쿼리 제거** | 0.5초 이상 → 없음 | 04-5에서 확인 |
---
## 📊 스테이징 결과 요약
### ✅ 완료한 작업
1. 베이스라인 측정: 전체 스캔 (type=ALL)
2. 중복 데이터 정리: app_highest_record, typing_exam_highest_record
3. 인덱스 5개 + UNIQUE 키 2개 추가 (총 7개)
4. **쿼리 조건 수정**: 7개 파일, 8+ 함수 (DATE()/HOUR() → 범위 조건)
5. 개선 효과 검증: 2,000배 이상 향상 확인 (인덱스 + 쿼리 수정)
6. Slow query log: 개선 쿼리 0.5초 이하
### 📈 성능 개선 상세 (Stage 검증)
- **인덱스만 적용:** cost 8.63 → 1.0 (8배), rows 5,804 → 200 (29배)
- **인덱스 + 쿼리 수정:** cost 8.63 → 0.004 (2,158배), rows 5,804 → 1 (5,800배)
### 📁 저장된 파일
```
260915-improve-stage/result/
├── baseline_query1_explain.json
├── baseline_query2_explain.json
├── baseline_results.txt
├── after_query1_explain.json
├── after_query2_improved_explain.json
├── verify_results.txt
└── add_indexes.sql
```
---
## 🔐 운영 DB 접속 정보
```
# 운영 DB (AWS EC2 Docker)
Host: chocomae.jinaju.com
Port: 3306
Database: chocomae
User: jisangs (임시 계정 — 2026-09-15 작업 후 삭제)
Password: 문서에 적지 않음 (별도로 전달받은 값 사용)
출발지 IP: 182.217.174.221
# 부여된 권한 (2026-09-15 SHOW GRANTS로 확인)
GRANT SELECT, ALTER ON chocomae.* TO jisangs@'182.217.174.221'
# 작업 기간 임시 권한 (2026-09-15 부여 → 같은 날 회수 완료, result/grants_after_revoke.txt)
GRANT SUPER ON *.* TO jisangs@'182.217.174.221'
GRANT INSERT, DELETE ON chocomae.app_highest_record TO jisangs@'182.217.174.221'
GRANT INSERT, DELETE ON chocomae.typing_exam_highest_record TO jisangs@'182.217.174.221'
GRANT SELECT ON mysql.slow_log TO jisangs@'182.217.174.221'
```
> ✅ 임시 계정 `jisangs`는 작업 후 삭제했습니다 (2026-09-15).
>
> ⚠️ 같은 비밀번호가 예전 커밋 기록과 메일(Gmail SMTP)·Python 배치(DB root) 소스 코드에도 평문으로 남아 있습니다. 계정 삭제와 별개로 그 비밀번호들은 바꿔야 합니다.
### 단계별 필요 권한
| 단계 | 작업 | 필요 권한 | jisangs 계정으로 가능? |
|---|---|---|---|
| 01, 02, 04 | 조회, EXPLAIN | SELECT | ✅ |
| 03-0 | 삭제 대상 행 백업 (`mariadb-dump`) | SELECT | ✅ |
| 03-0 | 중복 행 삭제 | `app_highest_record` DELETE | ✅ 임시 부여 |
| 03-2 | 인덱스·UNIQUE 키 추가 | ALTER | ✅ |
| 02-1, 05 | Slow query log 켜기·복원 (`SET GLOBAL`) | SUPER | ✅ 임시 부여 |
| 04-5, 07 | `mysql.slow_log` 조회 | `mysql.slow_log` SELECT | ✅ 임시 부여 |
| 06 | 삭제 행 복원 | 최고기록 테이블 INSERT | ✅ 임시 부여 |
| 06 | `ANALYZE TABLE best_record`, 복제 상태 확인 | `best_record` INSERT, REPLICA MONITOR 등 | ❌ 관리자 계정 |
2026-09-15에 관리자 계정으로 아래 권한을 임시 부여했습니다 (확인: `result/premeasure_grants_after.txt`). ❌ 작업은 문제가 생겼을 때만 필요하며 관리자 계정으로 실행합니다. 임시 권한은 05단계 사후 작업에서 회수합니다.
```sql
-- 관리자 계정으로 실행함 (2026-09-15)
GRANT DELETE, INSERT ON chocomae.app_highest_record TO 'jisangs'@'182.217.174.221';
GRANT DELETE, INSERT ON chocomae.typing_exam_highest_record TO 'jisangs'@'182.217.174.221';
GRANT SELECT ON mysql.slow_log TO 'jisangs'@'182.217.174.221';
GRANT SUPER ON *.* TO 'jisangs'@'182.217.174.221';
```
> `SUPER`는 서버 전체 권한(다른 연결의 쿼리 중단, 모든 전역 설정 변경, read_only 우회)이므로 작업이 끝나면 바로 회수하세요.
---
## ⚠️ 주의사항
1. **운영 DB 백업 필수**
- EC2 EBS 스냅샷 생성 (MariaDB 데이터 볼륨 포함)
- 또는 서버에서 `mariadb-dump`로 전체 DB 백업
2. **온라인 DDL 사용**
- ALGORITHM=INPLACE (테이블 잠금 최소화)
- LOCK=NONE (동시 읽기/쓰기 가능)
3. **중복 정리는 데이터 삭제**
- 03-0에서 삭제 대상 행을 먼저 파일로 백업한 뒤 삭제
- 인덱스와 달리 DROP INDEX로 되돌릴 수 없음 → 06-rollback.md의 "중복 정리 되돌리기" 참고
4. **모니터링 필수**
- 인덱스 추가 중 CPU/메모리 모니터링
- SHOW PROCESSLIST로 진행 상황 확인
5. **Slow query log 부하 주의**
- `log_output``TABLE`이 있으면 `mysql.slow_log`에 계속 쌓이고 자동으로 비워지지 않음
- `log_queries_not_using_indexes`는 켜지 않음 (인덱스를 안 쓰는 모든 쿼리가 기록되어 로그가 급증)
- 작업 후 01단계 1-4에서 저장한 원래 값으로 복원
6. **롤백 계획**
- DROP INDEX 명령어 준비 (06-rollback.md)
- 문제 발생 시 즉시 실행 가능
---
## 📅 작업 일정
01단계 사전 측정은 2026-09-15에 완료했습니다.
### 권장: 새벽 2시~3시 (트래픽 최소 시간)
| 시간 | 작업 | 예상 소요 |
|---|---|---|
| 02:00 | DB 백업 (EBS 스냅샷 또는 전체 덤프) | 5분 |
| 02:05 | 성능 기준선 측정 (02-baseline.md) | 10분 |
| 02:15 | 중복 정리 + 인덱스 추가 (03-add-indexes.md) | 30분 |
| 02:45 | 인덱스 생성 확인 | 5분 |
| 02:50 | 성능 개선 검증 (04-verify.md) | 10분 |
| 03:00 | 최종 체크리스트, slow log 복원, 임시 권한 회수 (05-checklist.md) | 10분 |
---
## 📂 디렉토리 구조
```
260915-improve-production/
├── 00-overview.md ← 이 파일
├── 01-premeasure.md ← 운영 DB 현황 측정
├── 02-baseline.md ← 성능 기준선 측정
├── 03-add-indexes.md ← 중복 정리 + 인덱스 추가
├── 04-verify.md ← 개선 효과 검증
├── 05-checklist.md ← 최종 체크리스트
├── 06-rollback.md ← 롤백 계획
├── 07-post-analysis.md ← 사후 분석
└── result/ ← 결과 파일 저장
├── premeasure_*.txt, premeasure_*.json ← 01 사전 측정
├── premeasure_summary.txt ← 01 요약
├── params.sql, queries/*.sql ← 02·04 공통 측정 쿼리
├── run_measure.sh, summarize_explain.py ← 02·04 공통 측정 도구
├── baseline_explain_*.json, baseline_perf.txt ← 02
├── backup_app_highest_dup_rows.sql ← 03-0 삭제 행 백업
├── add_indexes.sql, add_indexes_result.txt ← 03
├── after_explain_*.json, after_perf.txt ← 04
└── comparison.txt ← 04 비교
```
---
## 🚀 다음 단계
**01-premeasure.md** — 모든 측정 완료 (2026-09-15).
필요한 권한은 jisangs 계정에 임시 부여했습니다 (2026-09-15). **02-baseline.md**로 이동합니다.
---
## 📞 문제 발생 시
| 상황 | 대응 |
|---|---|
| **인덱스 추가 실패 (`Duplicate entry`)** | 3-0 다시 실행 후 `add_indexes.sql` 재실행 (이미 만든 인덱스는 건너뜀) |
| **인덱스 추가 실패 (기타)** | 에러 확인 후 필요하면 DROP INDEX (06-rollback.md) |
| **서비스 영향** | KILL QUERY + 인덱스 삭제 (06-rollback.md) |
| **최고기록 값 이상** | 삭제 행 백업으로 되돌리기 (06-rollback.md) |
| **성능 악화** | 인덱스 삭제 → 그래도 안 되면 백업에서 복원 |
| **기타 문제** | 07-post-analysis.md 참고 |
---
**01단계 완료, 작업 권한 준비 완료. 02단계부터 진행합니다.**