diff --git a/doc/plan/db/260915-improve-production/00-overview.md b/doc/plan/db/260915-improve-production/00-overview.md new file mode 100644 index 0000000..1864771 --- /dev/null +++ b/doc/plan/db/260915-improve-production/00-overview.md @@ -0,0 +1,224 @@ +# 운영 서버 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단계부터 진행합니다.** diff --git a/doc/plan/db/260915-improve-production/01-premeasure.md b/doc/plan/db/260915-improve-production/01-premeasure.md new file mode 100644 index 0000000..cecfa29 --- /dev/null +++ b/doc/plan/db/260915-improve-production/01-premeasure.md @@ -0,0 +1,245 @@ +# 01단계: 운영 DB 현황 측정 (사전준비) + +> 운영 서버에 인덱스를 추가하기 전 현재 상태를 정확히 파악합니다. +> +> **진행 상태 (2026-09-15):** 모든 항목 측정 완료. 결과 요약은 [result/premeasure_summary.txt](result/premeasure_summary.txt)에 있습니다. + +모든 명령은 `result/` 디렉터리에서 실행합니다. + +```bash +cd doc/plan/db/260915-improve-production/result +``` + +--- + +## 1-0. 계정 권한 확인 + +03단계 중복 삭제(DELETE), 02단계 slow query log 켜기(`SET GLOBAL`), 04단계 `mysql.slow_log` 조회에는 `SELECT, ALTER` 외의 권한이 필요합니다. 실제 권한을 먼저 확인하세요. + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e "SHOW GRANTS;" > premeasure_grants.txt + +cat premeasure_grants.txt +``` + +**확인할 점:** +- [00-overview.md의 단계별 필요 권한](00-overview.md#단계별-필요-권한) 표와 비교합니다. +- 권한이 없는 작업은 관리자 계정으로 실행할지, 작업 시간 동안만 권한을 받을지 정합니다. + +**결과 (2026-09-15):** `GRANT SELECT, ALTER ON chocomae.*`만 있습니다. 중복 삭제(03-0-3), `SET GLOBAL`(02-1, 05), `mysql.slow_log` 조회(04-5, 07)는 관리자 계정으로 실행해야 합니다. + +**임시 권한 부여 후 (2026-09-15, `premeasure_grants_after.txt`):** SUPER, 최고기록 테이블 2개 INSERT·DELETE, `mysql.slow_log` SELECT 추가. 05단계에서 회수합니다. + +--- + +## 1-1. 테이블·인덱스 크기 + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e " +SELECT TABLE_NAME, + TABLE_ROWS AS approx_rows, + ROUND(DATA_LENGTH / 1024 / 1024, 1) AS data_mb, + ROUND(INDEX_LENGTH / 1024 / 1024, 1) AS index_mb +FROM information_schema.TABLES +WHERE TABLE_SCHEMA = 'chocomae' +ORDER BY DATA_LENGTH DESC;" > premeasure_table_sizes.txt + +cat premeasure_table_sizes.txt +``` + +**저장할 수치:** +- `best_record` data_mb, index_mb +- `typing_exam_record` data_mb, index_mb + +> `TABLE_ROWS`는 InnoDB 추정치라 실제 행 수와 몇 % 차이가 날 수 있습니다. + +--- + +## 1-2. 월별 적재량 (증가 속도) + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e " +SELECT DATE_FORMAT(RecordDateTime, '%Y-%m') AS ym, COUNT(*) AS cnt +FROM best_record +GROUP BY ym +ORDER BY ym;" > premeasure_monthly_load.txt + +cat premeasure_monthly_load.txt +``` + +**확인할 점:** +- 최근 3개월 증가 추이 +- 월 평균 증가량 +- 마지막 달은 측정일까지의 값이므로 평균에서 제외 + +--- + +## 1-3. 대표 쿼리 기준값 (EXPLAIN) + +### 1-3-1. 기록이 많은 선생님·앱 찾기 + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e " +SELECT MaestroID, AppID, COUNT(*) AS cnt +FROM best_record +WHERE RecordDateTime >= NOW() - INTERVAL 30 DAY +GROUP BY MaestroID, AppID +ORDER BY cnt DESC +LIMIT 5;" > premeasure_top_maestro_app.txt + +cat premeasure_top_maestro_app.txt +``` + +**결과 (2026-09-15):** +``` +MaestroID AppID cnt +181 21 1089 +181 1 591 +389 103 562 +... +``` + +→ 다음 단계에서 MaestroID=181, AppID=21을 사용합니다. + +--- + +### 1-3-2. 일간 랭킹 쿼리 EXPLAIN + +위에서 나온 MaestroID, AppID를 사용하세요: + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -N -r -e " +EXPLAIN FORMAT=JSON +SELECT BR.PlayerID, U.Name, MAX(BR.BestRecord) AS HighScore +FROM best_record BR, player U +WHERE BR.MaestroID = 181 + AND BR.PlayerID = U.PlayerID + AND YEAR(BR.RecordDateTime) = YEAR(NOW()) + AND MONTH(BR.RecordDateTime) = MONTH(NOW()) + AND DAYOFMONTH(BR.RecordDateTime) = DAYOFMONTH(NOW()) + AND BR.AppID = 21 +GROUP BY BR.PlayerID +ORDER BY MAX(BR.BestRecord) DESC;" > premeasure_explain_daily_ranking.json + +cat premeasure_explain_daily_ranking.json +``` + +> `-N`(열 이름 생략)과 `-r`(이스케이프 없이 출력)이 없으면 파일 첫 줄에 `EXPLAIN` 헤더가 붙고 줄바꿈이 `\n` 글자로 저장되어 올바른 JSON이 되지 않습니다. + +**확인할 점:** MariaDB JSON에서는 `type`이 아니라 `access_type` 필드를 봅니다. + +| 필드 | 의미 | 나쁜 값 | 운영 측정값 (2026-09-15) | +|---|---|---|---| +| `access_type` | 접근 방식 | `ALL` (전체 스캔) | `index_merge` | +| `key` 또는 `index_merge` | 사용한 인덱스 | 없음 | `MaestroID` ∩ `AppID` | +| `rows` | 검사 예상 행 수 | 전체 행 수에 가까움 | 10,100 | + +> 운영에는 `MaestroID`, `AppID` 단일 인덱스가 있어 전체 스캔은 아닙니다. 하지만 날짜 조건은 인덱스로 거르지 못해 10,100행을 하나씩 확인합니다. 복합 인덱스 `(MaestroID, AppID, RecordDateTime)`로 이 수를 해당 날짜의 실제 기록 수 수준으로 줄이는 것이 목표입니다. + +--- + +## 1-4. Slow Query Log 현재 설정 저장 + +> 설정 변경(`SET GLOBAL`)은 SUPER 권한이 필요하고 운영에 영향을 주므로, 여기서는 **현재 값만 저장**합니다. 켜는 작업은 02단계 2-1에서, 원래 값 복원은 05단계에서 이 파일을 보고 합니다. + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e " +SHOW GLOBAL VARIABLES WHERE Variable_name IN + ('slow_query_log', 'long_query_time', 'log_output', + 'log_queries_not_using_indexes', 'slow_query_log_file');" > premeasure_slow_log_settings.txt + +cat premeasure_slow_log_settings.txt +``` + +> 이전 버전 문서의 1-4(`SET GLOBAL ...`)를 이미 성공적으로 실행했다면 저장된 값은 원래 값이 아닙니다. MariaDB 기본값은 `slow_query_log=OFF`, `long_query_time=10`, `log_output=FILE`입니다. 서버 설정 파일의 값을 확인해 05단계 복원에 사용하세요. + +**결과 (2026-09-15):** `slow_query_log=ON`, `long_query_time=3`, `log_output=FILE`, `log_queries_not_using_indexes=OFF`, 로그 파일 `/var/log/mysql/mariadb-slow.log` + +> 이전 버전 1-4의 `SET GLOBAL`은 적용되지 않았습니다 (SUPER 권한 없음. 적용됐다면 `long_query_time=1`, `log_output=TABLE`). 따라서 위 값이 05단계에서 복원할 원래 값입니다. + +--- + +## 1-5. 최고기록 테이블 중복 점검 + +### 1-5-1. 중복 상위 10개 (완료) + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e " +-- app_highest_record 중복 확인 +SELECT MaestroID, PlayerID, AppID, COUNT(*) AS cnt +FROM app_highest_record +GROUP BY MaestroID, PlayerID, AppID +HAVING cnt > 1 +ORDER BY cnt DESC +LIMIT 10;" > premeasure_app_highest_duplicates.txt + +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e " +-- typing_exam_highest_record 중복 확인 +SELECT MaestroID, PlayerID, WritingID, COUNT(*) AS cnt +FROM typing_exam_highest_record +GROUP BY MaestroID, PlayerID, WritingID +HAVING cnt > 1 +ORDER BY cnt DESC +LIMIT 10;" > premeasure_exam_highest_duplicates.txt + +echo "=== app_highest_record 중복 ===" && cat premeasure_app_highest_duplicates.txt +echo "=== typing_exam_highest_record 중복 ===" && cat premeasure_exam_highest_duplicates.txt +``` + +> 결과가 없으면 `-e` 모드는 열 이름도 출력하지 않아 **빈 파일**이 됩니다. 에러는 파일이 아니라 터미널에 표시되므로, 에러 없이 빈 파일이면 중복 0건입니다. + +**결과 (2026-09-15):** app_highest_record는 상위 10개 조합이 각 17~33행, typing_exam_highest_record는 0건. + +### 1-5-2. 전체 중복 개수 + +`LIMIT 10`이라 위 결과만으로는 삭제할 행 수를 알 수 없습니다. + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e " +SELECT COUNT(*) AS dup_groups, SUM(cnt - 1) AS rows_to_delete +FROM (SELECT COUNT(*) AS cnt FROM app_highest_record + GROUP BY MaestroID, PlayerID, AppID HAVING cnt > 1) t;" > premeasure_app_highest_dup_count.txt + +cat premeasure_app_highest_dup_count.txt +``` + +**확인할 점:** +- `dup_groups`: 중복이 있는 조합 수 +- `rows_to_delete`: 조합마다 1행만 남길 때 지워지는 행 수 → 03단계 3-0에서 실제 삭제 행 수와 비교 + +**결과 (2026-09-15):** `dup_groups` 4,823 / `rows_to_delete` 8,795 (app_highest_record 약 23만 행의 약 3.8%) + +**중복이 있으면:** +- 03-add-indexes.md 3-0에서 UNIQUE 키 추가 전에 **점수 기준으로** 정리합니다. + +--- + +## 1-6. 측정 결과 기록 + +모든 측정값을 [result/premeasure_summary.txt](result/premeasure_summary.txt)에 정리했습니다. + +> 이전 버전의 `cat > premeasure_summary.txt << 'EOF'` 명령은 채워 둔 요약을 빈 양식으로 덮어쓰므로 삭제했습니다. + +--- + +## 다음 단계 + +✅ 사전 측정 완료 → **02-baseline.md로 이동** + +(사전 측정 수치는 모두 `result/`의 txt/json 파일에 저장되어 있습니다) diff --git a/doc/plan/db/260915-improve-production/02-baseline.md b/doc/plan/db/260915-improve-production/02-baseline.md new file mode 100644 index 0000000..e965167 --- /dev/null +++ b/doc/plan/db/260915-improve-production/02-baseline.md @@ -0,0 +1,377 @@ +# 02단계: 개선 전 성능 측정 (베이스라인) + +> 인덱스 추가 전 EXPLAIN과 실행 시간을 측정해 개선 효과의 기준점을 만듭니다. +> 04단계에서 **같은 스크립트, 같은 쿼리, 같은 시각**으로 다시 측정해 비교합니다. + +모든 명령은 `result/` 디렉터리에서 실행합니다. + +```bash +cd doc/plan/db/260915-improve-production/result +``` + +--- + +## 측정 방식 + +| 원칙 | 이유 | +|---|---| +| 02와 04가 같은 쿼리 파일(`queries/*.sql`)을 실행 | 쿼리가 다르면 실행 시간을 비교할 수 없음 | +| `NOW()` 대신 **지난달의 시각을 고정** (`params.sql`) | 새벽 작업 시간에는 현재 시각의 기록이 거의 없음. 지난달 데이터는 02와 04 사이에 바뀌지 않아 결과 행 수까지 비교 가능 | +| 같은 조건을 **함수형**과 **범위형** 두 가지로 측정 | 인덱스만의 효과(함수형)와 인덱스 + 쿼리 수정 효과(범위형)를 나눠 확인 | +| 쿼리 묶음을 2회 실행해 **2회차** 값 사용 | 1회차에는 디스크에서 페이지를 읽는 시간이 섞임 | +| `SQL_NO_CACHE` | 쿼리 캐시 결과로 시간이 왜곡되지 않게 함 | + +| 쿼리 파일 | 조건 | 형태 | +|---|---|---| +| `q1_hour_func.sql` | 특정 1시간 기록 | `DATE()`, `HOUR()` 함수 (수정 전 코드 형태) | +| `q2_hour_range.sql` | 특정 1시간 기록 | 범위 조건 (Stage에서 수정한 코드 형태) | +| `q3_day_func.sql` | 일간 랭킹 | `YEAR()`, `MONTH()`, `DAYOFMONTH()` 함수 | +| `q4_day_range.sql` | 일간 랭킹 | 범위 조건 | +| `q5_month_func.sql` | 월간 랭킹 | `YEAR()`, `MONTH()` 함수 | +| `q6_month_range.sql` | 월간 랭킹 | 범위 조건 | + +--- + +## 2-0. 측정 기준 시각 정하기 + +MaestroID 181 · AppID 21의 **지난달** 기록 중 가장 많은 시간대를 찾습니다. + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e " +SELECT DATE(RecordDateTime) AS day, HOUR(RecordDateTime) AS hour, COUNT(*) AS cnt +FROM best_record +WHERE MaestroID = 181 AND AppID = 21 + AND RecordDateTime >= CAST(DATE_FORMAT(NOW() - INTERVAL 1 MONTH, '%Y-%m-01') AS DATETIME) + AND RecordDateTime < CAST(DATE_FORMAT(NOW(), '%Y-%m-01') AS DATETIME) +GROUP BY day, hour +ORDER BY cnt DESC +LIMIT 5;" > baseline_pick_time.txt + +cat baseline_pick_time.txt +``` + +첫 번째 행의 `day`와 `hour`로 `params.sql`을 만듭니다. **아래 값은 예시이니 반드시 바꾸세요.** + +```bash +cat > params.sql << 'EOF' +SET @maestro = 181, @app = 21, @day = '2026-08-20', @hour = 14; +EOF + +cat params.sql +``` + +> 04단계가 끝날 때까지 `params.sql`을 바꾸지 마세요. + +--- + +## 2-1. Slow Query Log 켜기 (SUPER 권한) + +> `SET GLOBAL`은 SUPER 권한이 필요합니다. 2026-09-15에 `jisangs` 계정에 임시 부여했으므로 jisangs로 실행하면 됩니다 (`mariadb -h chocomae.jinaju.com -u jisangs -p chocomae`로 접속 후 아래 SQL 실행). 운영의 원래 값은 `slow_query_log=ON`, `long_query_time=3`, `log_output=FILE`이며(01 1-4), 05단계에서 이 값으로 복원합니다. + +```sql +SET GLOBAL log_output = 'FILE,TABLE'; +SET GLOBAL long_query_time = 0.5; +SET GLOBAL slow_query_log = 'ON'; + +SHOW GLOBAL VARIABLES WHERE Variable_name IN + ('slow_query_log', 'long_query_time', 'log_output', 'log_queries_not_using_indexes'); +``` + +- `log_output = 'FILE,TABLE'`: 기존 파일 로그(`/var/log/mysql/mariadb-slow.log`)는 유지하면서 04단계에서 `mysql.slow_log` 테이블로 조회할 수 있게 합니다. +- `long_query_time`은 **새 연결부터** 적용됩니다. PHP는 요청마다 새로 연결하므로 바로 반영됩니다. +- ⚠️ `log_queries_not_using_indexes`는 켜지 마세요. 인덱스를 쓰지 않는 모든 쿼리가 실행 시간과 상관없이 기록되어 `mysql.slow_log`가 빠르게 커집니다. + +--- + +## 2-2. 측정 파일 만들기 + +### 측정 쿼리 6개 + +> 쿼리 파일은 반드시 `SELECT`로 시작해야 합니다. 스크립트가 앞에 `EXPLAIN FORMAT=JSON `을 붙여 실행하므로 주석을 넣지 마세요. + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +mkdir -p queries + +cat > queries/q1_hour_func.sql << 'EOF' +SELECT SQL_NO_CACHE PlayerID, BestRecord, RecordDateTime +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND DATE(RecordDateTime) = @day + AND HOUR(RecordDateTime) = @hour +ORDER BY RecordDateTime, PlayerID; +EOF + +cat > queries/q2_hour_range.sql << 'EOF' +SELECT SQL_NO_CACHE PlayerID, BestRecord, RecordDateTime +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(@day AS DATETIME) + INTERVAL @hour HOUR + AND RecordDateTime < CAST(@day AS DATETIME) + INTERVAL (@hour + 1) HOUR +ORDER BY RecordDateTime, PlayerID; +EOF + +cat > queries/q3_day_func.sql << 'EOF' +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND YEAR(RecordDateTime) = YEAR(@day) + AND MONTH(RecordDateTime) = MONTH(@day) + AND DAYOFMONTH(RecordDateTime) = DAYOFMONTH(@day) +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID; +EOF + +cat > queries/q4_day_range.sql << 'EOF' +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(@day AS DATETIME) + AND RecordDateTime < CAST(@day AS DATETIME) + INTERVAL 1 DAY +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID; +EOF + +cat > queries/q5_month_func.sql << 'EOF' +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND YEAR(RecordDateTime) = YEAR(@day) + AND MONTH(RecordDateTime) = MONTH(@day) +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID; +EOF + +cat > queries/q6_month_range.sql << 'EOF' +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(DATE_FORMAT(@day, '%Y-%m-01') AS DATETIME) + AND RecordDateTime < CAST(DATE_FORMAT(@day, '%Y-%m-01') AS DATETIME) + INTERVAL 1 MONTH +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID; +EOF + +ls queries/ +``` + +### 측정 스크립트 + +비밀번호를 한 번만 입력받아 EXPLAIN 6개와 실행 시간을 모두 측정합니다. 비밀번호는 파일이나 명령 인자로 남지 않습니다. + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +cat > run_measure.sh << 'EOF' +#!/bin/zsh +# 02·04단계 공통 측정 스크립트 +# 사용법: zsh run_measure.sh baseline (02단계, 인덱스 추가 전) +# zsh run_measure.sh after (04단계, 인덱스 추가 후) + +PREFIX=$1 +if [[ $PREFIX != baseline && $PREFIX != after ]]; then + echo "사용법: zsh run_measure.sh baseline|after" >&2; exit 1 +fi +cd "${0:A:h}" || exit 1 + +printf "Enter password: "; read -rs DBPW; echo + +db() { + mariadb --defaults-extra-file=<(printf '[client]\npassword="%s"\n' "$DBPW") \ + -h chocomae.jinaju.com -u jisangs chocomae "$@" +} + +# 1) EXPLAIN: 쿼리마다 JSON 파일 1개 +for q in queries/*.sql; do + out="${PREFIX}_explain_${q:t:r}.json" + { cat params.sql; printf 'EXPLAIN FORMAT=JSON '; cat "$q"; } | db -N -r > "$out" \ + || { echo "EXPLAIN 실패: $q" >&2; exit 1; } + echo "저장: $out" +done + +# 2) 실행 시간: 같은 쿼리 묶음을 2회 실행 (2회차 값 사용) +for round in 1 2; do + { cat params.sql + for q in queries/*.sql; do + echo "SELECT '=== round ${round}: ${q:t:r} ===' AS msg;" + cat "$q" + done + } | db -vvv || { echo "실행 시간 측정 실패" >&2; exit 1; } +done > "${PREFIX}_perf.txt" +echo "저장: ${PREFIX}_perf.txt" + +# 3) 요약: 쿼리 이름 / 결과 행 수 / 실행 시간 (2회차) +echo +grep -E '^\| === |rows? in set|Empty set' "${PREFIX}_perf.txt" | paste - - - | grep 'round 2' \ + | sed -E 's/\| === round 2: (.*) === \|/\1/; s/\t1 row in set \([0-9.]+ sec\)//' +EOF +``` + +### EXPLAIN 요약 스크립트 + +EXPLAIN JSON에서 `access_type`, 사용 인덱스, `rows`만 뽑아 한 줄씩 보여줍니다. + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +cat > summarize_explain.py << 'EOF' +# 사용법: python3 summarize_explain.py baseline [after] +import glob +import json +import sys + + +def find_table(node): + if isinstance(node, dict): + if isinstance(node.get("table"), dict): + return node["table"] + children = node.values() + elif isinstance(node, list): + children = node + else: + return None + for child in children: + found = find_table(child) + if found: + return found + return None + + +def keys_of(node): + if isinstance(node, dict): + for k, v in node.items(): + if k == "key": + yield v + else: + yield from keys_of(v) + elif isinstance(node, list): + for v in node: + yield from keys_of(v) + + +for prefix in sys.argv[1:]: + for path in sorted(glob.glob(f"{prefix}_explain_*.json")): + with open(path, encoding="utf-8") as f: + table = find_table(json.load(f)) + key = table.get("key") or " ∩ ".join(keys_of(table.get("index_merge", {}))) or "-" + print(f"{path:40} access_type={str(table.get('access_type')):12} key={key:28} rows={table.get('rows')}") +EOF +``` + +--- + +## 2-3. 베이스라인 측정 실행 + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +zsh run_measure.sh baseline +``` + +**출력 예 (숫자는 예시):** +``` +Enter password: +저장: baseline_explain_q1_hour_func.json +... +저장: baseline_explain_q6_month_range.json +저장: baseline_perf.txt + +q1_hour_func 38 rows in set (0.052 sec) +q2_hour_range 38 rows in set (0.049 sec) +q3_day_func 21 rows in set (0.061 sec) +q4_day_range 21 rows in set (0.058 sec) +q5_month_func 96 rows in set (0.410 sec) +q6_month_range 96 rows in set (0.395 sec) +``` + +**확인할 점:** +- 함수형과 범위형 짝(q1↔q2, q3↔q4, q5↔q6)의 **결과 행 수가 같아야** 합니다. 다르면 범위 조건이 잘못된 것이니 03단계로 가지 말고 원인을 확인하세요. +- `Empty set`이면 그 시각에 기록이 없는 것이니 2-0에서 다른 시각을 고르세요. +- 비밀번호를 틀리면 `Access denied`와 함께 멈춥니다. 다시 실행하세요. + +--- + +## 2-4. EXPLAIN 요약 + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +python3 summarize_explain.py baseline | tee baseline_explain_summary.txt +``` + +**운영 예상:** 01단계 사전 측정처럼 `index_merge`(MaestroID ∩ AppID)나 단일 인덱스 접근이 나오고, `rows`는 수천~1만 행 수준입니다. 전체 스캔(`ALL`)이 아닐 수 있으니 나온 값을 그대로 기준값으로 기록하세요. + +--- + +## 2-4-1. 서버 측 실행 시간 (ANALYZE) + +2-3의 실행 시간은 Mac ↔ AWS 네트워크 왕복이 포함된 클라이언트 시간입니다. 서버가 쿼리를 처리한 시간(`r_total_time_ms`)을 따로 기록해 두면 네트워크 영향 없이 비교할 수 있습니다. + +> 운영 측정 (2026-09-15): 클라이언트 시간 약 90ms 중 서버 처리 시간이 약 75ms였습니다. 6개 쿼리 모두 `index_merge`로 1만여 행을 읽기 때문입니다. `ANALYZE`는 쿼리를 실제로 실행하지만 조회(SELECT)만 하므로 데이터는 바뀌지 않습니다. + +**요약 스크립트 만들기:** + +```bash +cat > summarize_analyze.py << 'EOF' +# 사용법: python3 summarize_analyze.py baseline [after] +import json +import sys + +decoder = json.JSONDecoder() +for prefix in sys.argv[1:]: + text = open(f"{prefix}_analyze.txt", encoding="utf-8").read() + names = [line.split("=== ")[1].split(" ===")[0] for line in text.splitlines() if line.startswith("=== ")] + docs, pos = [], 0 + while True: + start = text.find("{", pos) + if start < 0: + break + doc, pos = decoder.raw_decode(text, start) + docs.append(doc) + for name, doc in zip(names, docs): + block = doc["query_block"] + print(f"{prefix:8} {name:16} server_time_ms={block.get('r_total_time_ms')}") +EOF +``` + +**측정 (비밀번호 한 번 입력):** + +```bash +{ cat params.sql + for q in queries/*.sql; do + echo "SELECT '=== ${q:t:r} ===';" + printf 'ANALYZE FORMAT=JSON ' + cat "$q" + done +} | mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -N -r > baseline_analyze.txt + +python3 summarize_analyze.py baseline | tee baseline_analyze_summary.txt +``` + +--- + +## 2-5. 결과 기록 + +**파일로 저장된 결과:** +``` +params.sql, queries/*.sql ← 04단계에서 그대로 재사용 (수정 금지) +run_measure.sh, summarize_explain.py ← 04단계에서 그대로 재사용 +baseline_pick_time.txt ← 측정 기준 시각 선정 근거 +baseline_explain_q*.json (6개) ← EXPLAIN 결과 +baseline_explain_summary.txt ← EXPLAIN 요약 +baseline_perf.txt ← 실행 시간 (-vvv 전체 출력, 네트워크 포함) +summarize_analyze.py ← 04단계에서 그대로 재사용 +baseline_analyze.txt ← ANALYZE 결과 (서버 측 실행 통계) +baseline_analyze_summary.txt ← 서버 측 실행 시간 요약 +``` + +2-3 요약과 2-4 결과는 04단계 4-4의 `comparison.txt`에 옮겨 적습니다. + +--- + +## 다음 단계 + +✅ 베이스라인 측정 완료 → **03-add-indexes.md로 이동** diff --git a/doc/plan/db/260915-improve-production/03-add-indexes.md b/doc/plan/db/260915-improve-production/03-add-indexes.md new file mode 100644 index 0000000..965fcdd --- /dev/null +++ b/doc/plan/db/260915-improve-production/03-add-indexes.md @@ -0,0 +1,302 @@ +# 03단계: 중복 정리 + 인덱스 추가 + +> 온라인 DDL로 서비스 중단 없이 인덱스를 추가합니다. UNIQUE 키를 넣기 전에 최고기록 테이블의 중복을 정리합니다. + +모든 명령은 `result/` 디렉터리에서 실행합니다. + +--- + +## 3-0. 중복 데이터 정리 (필수) + +> UNIQUE 키 추가 전에 `app_highest_record`의 중복을 정리합니다. `typing_exam_highest_record`는 01단계에서 중복 0건이라 확인만 합니다. + +### 왜 점수 기준으로 남기나 + +- 중복은 같은 학생의 결과 저장 요청이 동시에 들어올 때 생깁니다. `src/web/server/record/update_result_record.php`는 "조회 → 없으면 INSERT"와 "DELETE → INSERT"를 트랜잭션 없이 실행하므로 두 요청이 모두 INSERT할 수 있습니다. +- 그래서 **가장 최근 행이 최고 점수라는 보장이 없습니다.** 이전 버전 문서처럼 `MAX(ID)`를 남기면 최고기록이 낮아질 수 있습니다. +- 좋은 기록의 기준은 [util_app.php](../../../../src/web/server/lib/util_app.php)의 `is_highest_record_prefer_app()`을 따릅니다. **AppID 105는 낮은 점수**, 나머지 앱은 **높은 점수**가 좋은 기록입니다. 점수가 같으면 최근 행(ID가 큰 행)을 남깁니다. +- `typing_exam_highest_record`는 높은 점수가 좋은 기록입니다 (`update_typing_exam_record.php`). + +### UNIQUE 키 추가 후 앱 동작 + +- 같은 조합의 INSERT가 또 들어오면 `ERROR 1062 Duplicate entry`로 거부되어 중복이 더 생기지 않습니다. +- PHP 7.3 mysqli는 기본 설정에서 예외를 던지지 않고 `execute()`가 false만 반환하므로 **화면이 멈추지 않습니다.** 먼저 저장된 행이 남습니다. +- 드물게 동시에 저장한 두 기록 중 덜 좋은 기록이 남을 수 있습니다 (지금은 같은 상황에서 중복 행이 생깁니다). + +### 3-0-1. 중복 규모 확인 + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e " +SELECT COUNT(*) AS dup_groups, SUM(cnt - 1) AS rows_to_delete +FROM (SELECT COUNT(*) AS cnt FROM app_highest_record + GROUP BY MaestroID, PlayerID, AppID HAVING cnt > 1) t;" > dedupe_before_count.txt + +cat dedupe_before_count.txt +``` + +`rows_to_delete` 값을 메모합니다. 3-0-2 백업 행 수, 3-0-3 삭제 행 수와 같아야 합니다. (01단계 측정: 4,823조합 / 8,795행. 작업 당일에는 조금 늘어날 수 있습니다) + +### 3-0-2. 삭제 대상 행 백업 + +삭제할 행만 INSERT 문으로 저장합니다. 되돌릴 때 06단계에서 이 파일을 실행합니다. (SELECT 권한만 필요) + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +mariadb-dump -h chocomae.jinaju.com -u jisangs -p \ + --single-transaction --no-create-info --skip-triggers --no-tablespaces --skip-add-locks \ + --where="AppHighestRecordID IN (SELECT AppHighestRecordID FROM (SELECT AppHighestRecordID, ROW_NUMBER() OVER (PARTITION BY MaestroID, PlayerID, AppID ORDER BY CASE WHEN AppID = 105 THEN HighestRecord ELSE -HighestRecord END, AppHighestRecordID DESC) AS rn FROM app_highest_record) ranked WHERE rn > 1)" \ + chocomae app_highest_record > backup_app_highest_dup_rows.sql + +echo "백업한 행 수: $(grep -c '^(' backup_app_highest_dup_rows.sql)" +``` + +**확인할 점:** 백업한 행 수 = 3-0-1의 `rows_to_delete` + +### 3-0-3. 중복 삭제 (DELETE 권한 — jisangs에 임시 부여됨) + +> 3-2 인덱스 추가 **직전**에 실행하세요. 삭제 후 UNIQUE 키가 생기기 전까지 새 중복이 생길 수 있습니다. + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -vvv << 'EOF' | tee dedupe_result.txt +DELETE AHR FROM app_highest_record AHR +JOIN ( + SELECT AppHighestRecordID FROM ( + SELECT AppHighestRecordID, + ROW_NUMBER() OVER ( + PARTITION BY MaestroID, PlayerID, AppID + ORDER BY CASE WHEN AppID = 105 THEN HighestRecord ELSE -HighestRecord END, + AppHighestRecordID DESC + ) AS rn + FROM app_highest_record + ) ranked + WHERE rn > 1 +) dup ON dup.AppHighestRecordID = AHR.AppHighestRecordID; + +SELECT 'app_highest_record' AS table_name, + COUNT(*) AS total_rows, + COUNT(DISTINCT MaestroID, PlayerID, AppID) AS unique_combos +FROM app_highest_record; + +SELECT 'typing_exam_highest_record' AS table_name, + COUNT(*) AS total_rows, + COUNT(DISTINCT MaestroID, PlayerID, WritingID) AS unique_combos +FROM typing_exam_highest_record; +EOF +``` + +**확인할 점:** +- DELETE 결과 `Query OK, N rows affected`의 N = 3-0-1의 `rows_to_delete` +- 두 테이블 모두 `total_rows` = `unique_combos` (중복 없음) + +> `typing_exam_highest_record`에서 `total_rows` ≠ `unique_combos`이면, 3-0-2와 같은 방식으로 백업한 뒤 아래 SQL로 정리합니다 (높은 점수, 같으면 최근 행을 남김). +> +> ```sql +> DELETE TEHR FROM typing_exam_highest_record TEHR +> JOIN ( +> SELECT TypingExamHighestRecordID FROM ( +> SELECT TypingExamHighestRecordID, +> ROW_NUMBER() OVER ( +> PARTITION BY MaestroID, PlayerID, WritingID +> ORDER BY HighestRecord DESC, TypingExamHighestRecordID DESC +> ) AS rn +> FROM typing_exam_highest_record +> ) ranked +> WHERE rn > 1 +> ) dup ON dup.TypingExamHighestRecordID = TEHR.TypingExamHighestRecordID; +> ``` + +--- + +## 3-1. 인덱스 추가 SQL 준비 + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +cat > add_indexes.sql << 'EOF' +-- best_record: 랭킹용 +ALTER TABLE best_record +ADD INDEX IF NOT EXISTS idx_maestro_app_dt (MaestroID, AppID, RecordDateTime), +ALGORITHM=INPLACE, LOCK=NONE; + +-- best_record: 히스토리/저장용 +ALTER TABLE best_record +ADD INDEX IF NOT EXISTS idx_maestro_player_app_dt (MaestroID, PlayerID, AppID, RecordDateTime), +ALGORITHM=INPLACE, LOCK=NONE; + +-- typing_exam_record +ALTER TABLE typing_exam_record +ADD INDEX IF NOT EXISTS idx_maestro_writing_dt (MaestroID, WritingID, RecordDateTime), +ALGORITHM=INPLACE, LOCK=NONE; + +ALTER TABLE typing_exam_record +ADD INDEX IF NOT EXISTS idx_maestro_player_writing_dt (MaestroID, PlayerID, WritingID, RecordDateTime), +ALGORITHM=INPLACE, LOCK=NONE; + +-- license_score +ALTER TABLE license_score +ADD INDEX IF NOT EXISTS idx_maestro_player_dt (MaestroID, PlayerID, ScoreDateTime), +ALGORITHM=INPLACE, LOCK=NONE; + +-- app_highest_record: UNIQUE 키 +ALTER TABLE app_highest_record +ADD UNIQUE KEY IF NOT EXISTS uk_maestro_player_app (MaestroID, PlayerID, AppID), +ALGORITHM=INPLACE, LOCK=NONE; + +-- typing_exam_highest_record: UNIQUE 키 +ALTER TABLE typing_exam_highest_record +ADD UNIQUE KEY IF NOT EXISTS uk_maestro_player_writing (MaestroID, PlayerID, WritingID), +ALGORITHM=INPLACE, LOCK=NONE; +EOF + +cat add_indexes.sql # 확인 +``` + +> `IF NOT EXISTS`: 중간에 실패해 다시 실행해도 이미 만든 인덱스는 건너뜁니다 (`Note 1061 Duplicate key name`만 표시되고 에러는 아님). + +--- + +## 3-2. 인덱스 추가 실행 + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -vvv < add_indexes.sql | tee add_indexes_result.txt +``` + +**확인할 점:** ALTER 7개가 모두 `Query OK`로 끝남 + +**`Duplicate entry ... for key 'uk_...'` 에러가 나면:** +- 3-0 이후 새 중복이 생긴 것입니다. 에러가 난 지점에서 실행이 멈추고, 뒤의 ALTER는 실행되지 않습니다. +- 3-0-2(백업 파일 이름을 바꿔서)와 3-0-3을 다시 실행한 뒤 이 명령을 다시 실행하세요. 이미 만든 인덱스는 건너뜁니다. + +### 진행 상황 모니터링 (다른 터미널) + +**다른 터미널을 열어서 아래 명령어를 반복 실행하세요:** + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e "SHOW PROCESSLIST;" | grep -E "ALTER|Query|State" +``` + +**자동 반복 실행 (선택사항):** + +비밀번호는 처음에 한 번만 입력합니다. 10초마다 다시 묻지 않습니다. 비밀번호는 파일이나 명령 인자로 남기지 않고 메모리에만 두며, `Ctrl+C`로 멈추면 함께 사라집니다. + +```bash +( + printf "Enter password: "; read -rs DBPW; echo + while true; do + clear + echo "=== $(date) === (종료: Ctrl+C)" + mariadb --defaults-extra-file=<(printf '[client]\npassword="%s"\n' "$DBPW") \ + -h chocomae.jinaju.com -u jisangs chocomae -e "SHOW PROCESSLIST;" \ + | grep -E "ALTER|Query|State" + sleep 10 + done +) +``` + +> `--defaults-extra-file`은 반드시 첫 번째 옵션이어야 합니다. 비밀번호를 틀리면 매번 `Access denied`가 출력되니 `Ctrl+C`로 멈추고 다시 실행하세요. +> +> PROCESS 권한이 없으면 `SHOW PROCESSLIST`에는 내 계정의 연결만 보입니다. ALTER는 같은 `jisangs` 계정으로 실행하므로 보입니다. + +**나타날 메시지 (State 문구는 버전에 따라 다름):** +``` +| 123 | jisangs | ... | chocomae | Query | 45 | ... | ALTER TABLE best_record ADD INDEX IF NOT EXISTS idx_maestro_app_dt ... | +``` + +**완료되면:** ALTER TABLE 줄이 사라짐 ✅ + +--- + +## 3-3. 인덱스 생성 확인 + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e " +SELECT TABLE_NAME, INDEX_NAME, NON_UNIQUE, + GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS cols +FROM information_schema.STATISTICS +WHERE TABLE_SCHEMA = 'chocomae' + AND INDEX_NAME IN ('idx_maestro_app_dt', 'idx_maestro_player_app_dt', + 'idx_maestro_writing_dt', 'idx_maestro_player_writing_dt', + 'idx_maestro_player_dt', 'uk_maestro_player_app', 'uk_maestro_player_writing') +GROUP BY TABLE_NAME, INDEX_NAME, NON_UNIQUE +ORDER BY TABLE_NAME, INDEX_NAME;" > add_indexes_check.txt + +cat add_indexes_check.txt +``` + +**출력 예 (7행이어야 함):** +``` +TABLE_NAME INDEX_NAME NON_UNIQUE cols +app_highest_record uk_maestro_player_app 0 MaestroID,PlayerID,AppID +best_record idx_maestro_app_dt 1 MaestroID,AppID,RecordDateTime +best_record idx_maestro_player_app_dt 1 MaestroID,PlayerID,AppID,RecordDateTime +license_score idx_maestro_player_dt 1 MaestroID,PlayerID,ScoreDateTime +typing_exam_highest_record uk_maestro_player_writing 0 MaestroID,PlayerID,WritingID +typing_exam_record idx_maestro_player_writing_dt 1 MaestroID,PlayerID,WritingID,RecordDateTime +typing_exam_record idx_maestro_writing_dt 1 MaestroID,WritingID,RecordDateTime +``` + +--- + +## 3-4. 쿼리 조건 수정 (선택: 인덱스 최대 활용) + +> **중요:** 인덱스만으로도 성능이 개선되지만, 함수 기반 조건을 범위 조건으로 변경하면 **1,770배** 추가 성능 향상 가능 (스테이징 기준) +> +> 04단계는 함수형(q1·q3·q5)과 범위형(q2·q4·q6)을 모두 측정하므로, 운영에서도 인덱스만 적용했을 때와 쿼리까지 수정했을 때의 차이를 확인할 수 있습니다. + +### 수정 대상 (7개 파일, 8+ 함수) + +**꼭 필요한 파일:** +1. `src/web/server/record/app_ranking.php` (3개 함수) +2. `src/web/server/record/ranking_record_*.php` (3개 파일) +3. `src/web/server/record/history_record.php` +4. `src/web/php/db/typing_exam_collection.php` (8개 함수) + +### 변경 예시 + +```sql +-- 변경 전 (인덱스 일부만 사용) +WHERE DATE(RecordDateTime) = DATE(NOW()) + AND HOUR(RecordDateTime) = HOUR(NOW()) + +-- 변경 후 (인덱스 완전 활용) +WHERE RecordDateTime >= CAST(DATE_FORMAT(NOW(), '%Y-%m-%d %H:00:00') AS DATETIME) + AND RecordDateTime < CAST(DATE_FORMAT(NOW(), '%Y-%m-%d %H:00:00') AS DATETIME) + INTERVAL 1 HOUR +``` + +**자세한 변경 규칙:** [02-option1-index-and-query-rewrite.md section 3-3](../02-option1-index-and-query-rewrite.md) + +### 수정 진행 상황 (Stage에서 완료됨) + +- [x] ✅ Stage 서버에서 7개 파일 모두 수정 완료 +- [x] ✅ 결과 동일성 검증 완료 (EXCEPT로 확인) +- [x] ✅ EXPLAIN 성능 개선 확인 (rows: 5,804 → 1, cost: 8.634 → 0.00488) +- [x] ⚠️ 후속 수정 (2026-09-15): 히스토리 쿼리 2곳(`history_record.php`, `typing_exam_collection.php` `getHistoryRecord()`)의 하한 조건 `>= DATE(?) - INTERVAL 7 DAY` 삭제 → "기록이 있는 최근 7일" 동작 복원. **운영 배포 코드에 반드시 포함** ([상세](../260915-improve-stage/result/FINAL-REPORT.md)) +- [ ] Stage에서 히스토리 재검증 (8일보다 전 기록만 있는 학생으로 일반 앱·긴글의 시작·결과 화면 확인) + +### Production 적용 방법 + +> **실제 적용 (2026-09-15):** 인덱스 추가(3-2) 직후, 04단계 측정 전에 애플리케이션(PHP, 화면) 코드도 운영에 배포했습니다. 04단계 측정 쿼리는 `queries/*.sql`을 직접 실행하므로 코드 배포의 영향을 받지 않습니다. + +**Option A: 단계적 적용 (권장)** +1. 먼저 인덱스만 적용 (이 파일) +2. 1~2일 모니터링 +3. 이후 코드 배포 + +**Option B: 한 번에 적용** +1. 인덱스 추가 + 코드 배포 동시 진행 +2. 더 빠른 성능 향상 + +--- + +## 다음 단계 + +✅ 인덱스 추가 완료 → **04-verify.md로 이동** diff --git a/doc/plan/db/260915-improve-production/04-verify.md b/doc/plan/db/260915-improve-production/04-verify.md new file mode 100644 index 0000000..6b0f4fc --- /dev/null +++ b/doc/plan/db/260915-improve-production/04-verify.md @@ -0,0 +1,151 @@ +# 04단계: 개선 효과 검증 + +> 인덱스 추가 후 02단계와 **같은 스크립트, 같은 쿼리, 같은 시각**으로 다시 측정해 비교합니다. + +모든 명령은 `result/` 디렉터리에서 실행합니다. `params.sql`, `queries/`는 02단계 그대로 두세요. + +```bash +cd doc/plan/db/260915-improve-production/result +``` + +--- + +## 4-1. 측정 실행 + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +zsh run_measure.sh after +``` + +`after_explain_q*.json` 6개와 `after_perf.txt`가 저장되고 요약이 출력됩니다. + +--- + +## 4-2. EXPLAIN 비교 + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +python3 summarize_explain.py baseline after | tee explain_summary.txt +``` + +**확인할 점:** + +| 쿼리 | 기대 결과 | +|---|---| +| q2, q4, q6 (범위형) | `access_type` = `range`, 새 인덱스(`idx_maestro_app_dt` 또는 `idx_maestro_player_app_dt`) 사용, `rows`가 베이스라인보다 크게 감소 | +| q1, q3, q5 (함수형) | 새 인덱스를 써도 날짜 조건은 인덱스로 거르지 못해 `rows` 감소 폭이 작음 → 3-4 쿼리 수정이 필요한 이유 | + +> 옵티마이저가 두 새 인덱스 중 어느 쪽을 고를지는 데이터 분포에 따라 다릅니다. 새 인덱스 중 하나를 쓰고 `rows`가 줄었으면 정상입니다. + +--- + +## 4-3. 결과 행 수 비교 + +인덱스는 조회 결과를 바꾸지 않아야 합니다. + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +rowcount() { + grep -E '^\| === |rows? in set|Empty set' "$1" | paste - - - | grep 'round 2' \ + | sed -E 's/ \([0-9.]+ sec\)//g' +} +diff <(rowcount baseline_perf.txt) <(rowcount after_perf.txt) && echo "결과 행 수 동일 ✅" +``` + +> `@day`가 지난달이라 02와 04 사이에 데이터가 거의 바뀌지 않습니다. 다르게 나오면 그 사이 선생님이 기록을 삭제했는지 확인하세요. (3-0 중복 정리는 `best_record`를 건드리지 않습니다) + +--- + +## 4-4. 실행 시간 비교 + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +timing() { + grep -E '^\| === |rows? in set|Empty set' "$1" | paste - - - | grep 'round 2' \ + | sed -E 's/\| === round 2: (.*) === \|/\1/; s/\t1 row in set \([0-9.]+ sec\)//' +} +paste <(timing baseline_perf.txt) <(timing after_perf.txt | cut -f2) | tee timing_summary.txt +``` + +**출력 형식:** `쿼리 이름` · `베이스라인 행 수 (시간)` · `개선 후 행 수 (시간)` + +4-2와 4-4-1 결과를 비교표에 옮깁니다. + +```bash +cat > comparison.txt << 'EOF' +=== 성능 개선 효과 (운영 DB) === +측정 기준: params.sql (MaestroID 181, AppID 21, @day ____, @hour ____) + +| 쿼리 | access_type (전→후) | rows (전→후) | 서버 시간 ms (전→후, 4-4-1) | +|--------------------|---------------------|----------------|-------------------| +| q1 시간별 · 함수 | ____ → ____ | ____ → ____ | ____ → ____ | +| q2 시간별 · 범위 | ____ → ____ | ____ → ____ | ____ → ____ | +| q3 일간 · 함수 | ____ → ____ | ____ → ____ | ____ → ____ | +| q4 일간 · 범위 | ____ → ____ | ____ → ____ | ____ → ____ | +| q5 월간 · 함수 | ____ → ____ | ____ → ____ | ____ → ____ | +| q6 월간 · 범위 | ____ → ____ | ____ → ____ | ____ → ____ | + +참고 (스테이징): rows 5,804 → 1, cost 8.63 → 0.004 +참고 (운영 01 사전 측정): 일간 랭킹 index_merge, rows 10,100 + +결과 행 수 동일 (4-3): [ ] 예 [ ] 아니오 + +결론: +- 인덱스만의 효과 (q1·q3·q5): ____ +- 인덱스 + 쿼리 수정 효과 (q2·q4·q6): ____ +EOF + +cat comparison.txt +``` + +--- + +## 4-4-1. 서버 측 실행 시간 비교 (ANALYZE) + +4-4의 시간에는 네트워크 왕복(운영 측정 기준 약 15ms)이 포함됩니다. 02단계 2-4-1과 같은 방법으로 서버 처리 시간을 측정해 비교합니다. (베이스라인: 6개 모두 약 75~80ms) + +```bash +{ cat params.sql + for q in queries/*.sql; do + echo "SELECT '=== ${q:t:r} ===';" + printf 'ANALYZE FORMAT=JSON ' + cat "$q" + done +} | mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -N -r > after_analyze.txt + +python3 summarize_analyze.py after | tee after_analyze_summary.txt +``` + +```bash +python3 summarize_analyze.py baseline after | tee analyze_summary.txt +``` + +--- + +## 4-5. Slow Query Log 확인 (`mysql.slow_log` SELECT 권한 필요) + +**아래 명령어를 터미널에 복사-붙여넣기해서 실행하세요:** + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e " +SELECT start_time, query_time, rows_examined, LEFT(sql_text, 120) AS sql_text +FROM mysql.slow_log +WHERE start_time >= NOW() - INTERVAL 1 HOUR +ORDER BY query_time DESC +LIMIT 10;" > after_slow_log.txt + +cat after_slow_log.txt +``` + +- 측정 스크립트의 쿼리도 0.5초를 넘으면 기록됩니다. 운영 앱 쿼리 중 느린 것이 남아 있는지 확인하세요. +- `log_output`에 `TABLE`이 없으면 이 테이블은 비어 있습니다 (02단계 2-1 참고). + +--- + +## 다음 단계 + +✅ 개선 효과 검증 완료 → **05-checklist.md로 이동** diff --git a/doc/plan/db/260915-improve-production/05-checklist.md b/doc/plan/db/260915-improve-production/05-checklist.md new file mode 100644 index 0000000..a284154 --- /dev/null +++ b/doc/plan/db/260915-improve-production/05-checklist.md @@ -0,0 +1,100 @@ +# 05단계: 최종 체크리스트 + +> 운영 서버 인덱스 적용 전후 모든 단계를 최종 확인합니다. + +--- + +## 준비 단계 + +- [x] DB 백업 (전체 `mariadb-dump` → `backup_chocomae_full_20260915.sql.gz`, 로컬 복원 테스트 통과) +- [x] 운영 DB 현황 측정 (01-premeasure.md, 2026-09-15) +- [x] 계정 권한 확인 (01 1-0: `SELECT, ALTER`만 있음) +- [x] 작업 권한 임시 부여 (SUPER, 최고기록 테이블 2개 INSERT·DELETE, `mysql.slow_log` SELECT — `premeasure_grants_after.txt`) +- [x] Slow query log 원래 설정 저장 (01 1-4: ON, long_query_time=3, log_output=FILE) +- [x] app_highest_record 전체 중복 개수 확인 (01 1-5-2: 4,823조합, 삭제 대상 8,795행) + +--- + +## 베이스라인 측정 (02-baseline.md) + +- [x] 지난달 측정 기준 시각 선정, `params.sql` 작성 +- [x] Slow query log 켜기 (jisangs 계정, `log_queries_not_using_indexes`는 켜지 않음) +- [x] `queries/` 6개, `run_measure.sh`, `summarize_explain.py` 작성 +- [x] `zsh run_measure.sh baseline` 실행 + - `baseline_explain_q*.json` 6개 + - `baseline_perf.txt` +- [x] 확인: 함수형·범위형 짝(q1↔q2, q3↔q4, q5↔q6)의 결과 행 수가 같음 +- [x] 기준값 기록: `baseline_explain_summary.txt` (운영은 ALL이 아니라 index_merge일 수 있음) + +--- + +## 중복 정리·인덱스 추가 (03-add-indexes.md) + +- [x] 3-0-1 중복 규모 확인 (`rows_to_delete` 메모) +- [x] 3-0-2 삭제 대상 행 백업 `backup_app_highest_dup_rows.sql` (백업 행 수 = `rows_to_delete`) +- [x] 3-0-3 중복 삭제 (`rows affected` = `rows_to_delete`) +- [x] 확인: 두 테이블 모두 `total_rows` = `unique_combos` +- [x] `add_indexes.sql` 준비 +- [x] 인덱스 추가 실행 (`add_indexes_result.txt`, ALTER 7개 모두 `Query OK`) +- [x] ~~SHOW PROCESSLIST로 진행 상황 모니터링~~ 생략 (ALTER 7개 합계 약 12초) +- [x] 새 인덱스 7개 확인 (`add_indexes_check.txt`) + +--- + +## 개선 효과 검증 (04-verify.md) + +- [x] `zsh run_measure.sh after` 실행 +- [x] 확인: 범위형 쿼리(q2, q4, q6)에서 `access_type` = `range`, 새 인덱스 사용 +- [x] 확인: `rows`가 베이스라인보다 크게 감소 +- [x] 확인: 결과 행 수가 베이스라인과 같음 (4-3) +- [x] 실행 시간 비교, `comparison.txt` 작성 +- [x] Slow query log에서 느린 쿼리 확인 (`after_slow_log.txt`) + +--- + +## 성공 기준 + +모두 체크되면 **운영 서버 적용 완료:** + +- [x] 인덱스 7개 추가 성공 +- [x] 범위형 쿼리가 새 인덱스를 사용 (`access_type` = `range`) +- [x] 쿼리 실행 시간 개선 확인 +- [x] 스캔 행 수 대폭 감소 +- [x] 결과 행 수 변화 없음 +- [x] 서비스 정상 운영 (게임 결과 저장, 최고기록·랭킹·기록 화면) — 2026-09-15 운영에서 확인: 게임 결과 저장·최고기록, 시간·일·월 랭킹, 최근 7번 기록 화면, 타자 시험 순위와 기록 + +--- + +## 사후 작업 + +- [x] Slow query log 설정을 원래 값으로 복원 (jisangs 계정 — **SUPER 회수 전에** 실행, 2026-09-15 완료: ON / 3 / FILE / OFF) + + ```sql + -- 01 1-4에서 저장한 원래 값 (premeasure_slow_log_settings.txt) + SET GLOBAL slow_query_log = 'ON'; + SET GLOBAL long_query_time = 3; + SET GLOBAL log_output = 'FILE'; + ``` + +- [x] 임시로 부여한 권한 회수 (관리자 계정, slow log 복원 후) — 2026-09-15 완료, `result/grants_after_revoke.txt`: `USAGE` + `SELECT, ALTER ON chocomae.*`만 남음 + + ```sql + REVOKE DELETE, INSERT ON chocomae.app_highest_record FROM 'jisangs'@'182.217.174.221'; + REVOKE DELETE, INSERT ON chocomae.typing_exam_highest_record FROM 'jisangs'@'182.217.174.221'; + REVOKE SELECT ON mysql.slow_log FROM 'jisangs'@'182.217.174.221'; + REVOKE SUPER ON *.* FROM 'jisangs'@'182.217.174.221'; + + -- 기대 결과: GRANT USAGE ... 와 GRANT SELECT, ALTER ON `chocomae`.* 두 줄만 남음 + SHOW GRANTS FOR 'jisangs'@'182.217.174.221'; + ``` + +- [x] 임시 계정 `jisangs`@`182.217.174.221` 삭제 (2026-09-15, root 계정으로 `DROP USER`) +- ~~[ ] 같은 비밀번호를 쓰는 다른 계정 교체 (Gmail SMTP, Python 배치의 DB root 설정)~~ — 취소 (2026-09-15) +- ~~[ ] 팀원 통보 (작업 완료)~~ — 취소 (2026-09-15) +- [ ] 모니터링 (24시간) +- [ ] 문제 발생 시 롤백 준비 완료 (06-rollback.md) +- [ ] 사후 분석 (07-post-analysis.md) + +--- + +**운영 서버 적용 완료!** ✅ diff --git a/doc/plan/db/260915-improve-production/06-rollback.md b/doc/plan/db/260915-improve-production/06-rollback.md new file mode 100644 index 0000000..0043121 --- /dev/null +++ b/doc/plan/db/260915-improve-production/06-rollback.md @@ -0,0 +1,127 @@ +# 06단계: 롤백 계획 + +> 문제 발생 시 인덱스를 삭제하고, 필요하면 중복 정리 전 상태로 되돌립니다. +> +> ⚠️ 작업용 `jisangs` 계정은 2026-09-15에 삭제했습니다. 이 문서의 `-u jisangs` 명령은 관리자 계정으로 바꿔 실행하세요. + +모든 명령은 `result/` 디렉터리에서 실행합니다. + +--- + +## 🚨 긴급 상황 대응 + +### 1. 진행 중인 ALTER 중단 + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e "SHOW PROCESSLIST;" | grep ALTER + +# 첫 번째 열(Id)의 값을 넣어 실행 +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e "KILL QUERY ;" +``` + +> 온라인 DDL을 중단하면 그 인덱스만 만들어지지 않고 테이블 데이터는 그대로입니다. 내 계정이 실행한 쿼리는 별도 권한 없이 중단할 수 있습니다. + +### 2. 인덱스 삭제 + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae << 'EOF' +-- 모든 인덱스 삭제 +ALTER TABLE best_record DROP INDEX IF EXISTS idx_maestro_app_dt; +ALTER TABLE best_record DROP INDEX IF EXISTS idx_maestro_player_app_dt; +ALTER TABLE typing_exam_record DROP INDEX IF EXISTS idx_maestro_writing_dt; +ALTER TABLE typing_exam_record DROP INDEX IF EXISTS idx_maestro_player_writing_dt; +ALTER TABLE license_score DROP INDEX IF EXISTS idx_maestro_player_dt; +ALTER TABLE app_highest_record DROP INDEX IF EXISTS uk_maestro_player_app; +ALTER TABLE typing_exam_highest_record DROP INDEX IF EXISTS uk_maestro_player_writing; +EOF +``` + +> UNIQUE 키 2개는 성능과 무관하므로, 성능 문제로 롤백할 때는 남겨 두어도 됩니다. 남겨 두면 중복이 다시 생기지 않습니다. + +--- + +## 중복 정리 되돌리기 (필요할 때만) + +삭제한 행은 같은 조합의 **덜 좋은 기록**이라 보통 되돌릴 필요가 없습니다. 되돌려야 하면 UNIQUE 키를 먼저 삭제한 뒤 03단계 3-0-2의 백업 파일을 실행합니다. (INSERT 권한 — jisangs에 임시 부여됨) + +```bash +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e " +ALTER TABLE app_highest_record DROP INDEX IF EXISTS uk_maestro_player_app;" + +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae < backup_app_highest_dup_rows.sql + +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e " +SELECT COUNT(*) AS total_rows FROM app_highest_record;" +``` + +> 백업 파일은 원래 ID 그대로 INSERT하므로 기존 행과 충돌하지 않습니다. 복원 후 `total_rows`는 3-0-3 삭제 전 행 수(그 사이 새로 저장된 기록 제외)와 같아야 합니다. + +--- + +## 전체 백업에서 복원 (최후의 수단) + +운영 DB는 AWS RDS가 아니라 **EC2의 Docker MariaDB**입니다. 인덱스 문제는 DROP INDEX로 충분하므로 데이터 손상이 있을 때만 사용하세요. **백업 이후 저장된 게임 기록은 모두 사라집니다.** + +### 방법 1: 덤프 파일 복원 + +```bash +# EC2 서버의 MariaDB 컨테이너 안에서 관리자 계정으로 실행 +mariadb -u <관리자> -p chocomae < backup_2026-09-15.sql +``` + +### 방법 2: EBS 스냅샷 복원 + +1. AWS 콘솔 → EC2 → 스냅샷에서 작업 전 스냅샷 선택 +2. 스냅샷으로 볼륨 생성 (EC2 인스턴스와 같은 가용 영역) +3. MariaDB 컨테이너 중지 → 기존 데이터 볼륨 분리 → 새 볼륨 연결·마운트 +4. 컨테이너 시작 후 접속 확인 + +### 복제 상태 확인 (복제를 사용 중인 경우, 관리자 계정) + +```sql +SHOW MASTER STATUS; +SHOW REPLICA STATUS\G +``` + +### 서비스 정상 확인 +- 웹 서비스 접속 테스트 +- 타이핑 게임 플레이 테스트 (결과 저장, 최고기록 표시) +- 랭킹 조회 테스트 + +--- + +## 문제 원인 분석 + +| 증상 | 원인 | 대응 | +|---|---|---| +| 인덱스 생성 실패 | 디스크 부족 | DROP INDEX + 디스크 정리 | +| `Duplicate entry ... for key 'uk_...'` | 3-0 이후 새 중복 발생 | 3-0-2·3-0-3 다시 실행 후 `add_indexes.sql` 재실행 (이미 만든 인덱스는 건너뜀) | +| 서비스 느려짐 | 옵티마이저가 새 인덱스를 잘못 선택, 통계 부정확 | `ANALYZE TABLE best_record;` (관리자) → 그래도 느리면 해당 인덱스 DROP | +| 최고기록 화면 값 이상 | 중복 정리 결과 | 백업 파일에서 해당 조합 확인, 필요하면 "중복 정리 되돌리기" | +| 복제 지연·오류 | 복제 서버에서 DDL 적용 중 또는 실패 | `SHOW REPLICA STATUS`로 확인 | + +--- + +## 복구 후 확인 + +```bash +# 테이블 상태 확인 +mariadb -h chocomae.jinaju.com -u jisangs -p chocomae -e " +SHOW INDEXES FROM best_record; +SELECT COUNT(*) FROM best_record; +" +``` + +성능 확인은 `zsh run_measure.sh after`로 다시 측정해 `baseline_perf.txt`와 비교합니다. 기존 `after_*` 파일을 덮어쓰므로 먼저 다른 폴더로 옮기세요. + +--- + +## 문제 발생 시 연락 + +- **팀 리더**: [연락처] +- **DBA**: [연락처] +- **운영팀**: [연락처] + +--- + +**롤백은 최후의 수단입니다. 충분한 검토 후 신중하게 진행하세요.** ⚠️ diff --git a/doc/plan/db/260915-improve-production/07-post-analysis.md b/doc/plan/db/260915-improve-production/07-post-analysis.md new file mode 100644 index 0000000..5f0c22c --- /dev/null +++ b/doc/plan/db/260915-improve-production/07-post-analysis.md @@ -0,0 +1,170 @@ +# 07단계: 사후 분석 및 최적화 + +> 운영 서버 적용 후 실제 성능을 분석하고 추가 최적화를 검토합니다. + +--- + +## 📊 실제 성능 측정 결과 + +### 운영 DB에서 측정한 개선율 + +`comparison.txt`(04단계)의 값을 옮겨 적었습니다 (2026-09-15). 6개 쿼리 전체 값은 [result/comparison.txt](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 설정을 원래 값으로 복원하세요. + +```bash +# 일일 성능 리포트 +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; +" +``` + +--- + +## 📝 최종 보고서 항목 + +1. **실행 요약** + - 작업 목표 및 결과 + - 성과 지표 + +2. **기술 분석** + - EXPLAIN 개선 결과 + - 실행 시간 비교 + - 중복 정리 결과 (삭제 행 수, 백업 파일) + +3. **비즈니스 임팩트** + - 사용자 경험 개선 + - 서버 비용 절감 효과 + +4. **추가 제안** + - 다음 단계 최적화 계획 + - 예상 추가 효과 + +--- + +## 🎯 다음 단계 + +- [ ] 스테이징에서 안 2 (집계 테이블) 테스트 +- [ ] 운영 DB 추가 최적화 검토 +- [ ] 팀 회의에서 결과 공유 +- [ ] 성능 모니터링 대시보드 구축 + +--- + +**운영 서버 성능 개선 프로젝트 완료!** 🎉 diff --git a/doc/plan/db/260915-improve-production/README.md b/doc/plan/db/260915-improve-production/README.md new file mode 100644 index 0000000..b7dfb57 --- /dev/null +++ b/doc/plan/db/260915-improve-production/README.md @@ -0,0 +1,166 @@ +# 260915-improve-production: 운영 서버 DB 성능 개선 + +> 스테이징에서 성공한 인덱스 추가를 운영 서버(chocomae.jinaju.com)에 적용합니다. + +--- + +## 📚 파일 가이드 + +### 🎯 먼저 읽기 +- **[00-overview.md](00-overview.md)** — 전체 개요 및 작업 일정 + - 운영 사전 측정 결과 + - 단계별 필요 권한 + - 예상 효과 및 주의사항 + +### 📋 단계별 작업 + +#### **Step 1: 운영 DB 현황 측정** (2026-09-15 측정 완료) +- **[01-premeasure.md](01-premeasure.md)** — 권한, 테이블 크기, 월별 적재량, 기준값, 중복 확인 + - 결과 요약: [result/premeasure_summary.txt](result/premeasure_summary.txt) + - 주요 결과: 계정 권한은 `SELECT, ALTER`만, app_highest_record 중복 삭제 대상 8,795행 + +#### **Step 2: 성능 기준선 측정** +- **[02-baseline.md](02-baseline.md)** — 인덱스 추가 전 성능 측정 + - 지난달 측정 기준 시각 선정 (`params.sql`) + - Slow query log 켜기 + - 공통 측정 스크립트로 EXPLAIN 6개 + 실행 시간 측정 + +#### **Step 3: 중복 정리 + 인덱스 추가** +- **[03-add-indexes.md](03-add-indexes.md)** — 온라인 DDL로 인덱스 추가 + - app_highest_record 삭제 대상 백업 후 점수 기준으로 중복 정리 + - 인덱스 5개 + UNIQUE 키 2개 추가 + - 진행 상황 모니터링 + - **옵션:** 쿼리 조건 수정 (DATE()/HOUR() → 범위 조건) - 추가 1,770배 성능 향상 (스테이징 기준) + +#### **Step 4: 개선 효과 검증** +- **[04-verify.md](04-verify.md)** — 인덱스 추가 후 성능 검증 + - 02와 같은 스크립트로 재측정 + - EXPLAIN·결과 행 수·실행 시간 비교 + - 성능 개선율 확인 + +#### **Step 5: 최종 체크리스트** +- **[05-checklist.md](05-checklist.md)** — 모든 단계 최종 확인 + - 성공 기준 검증 + - slow query log 복원, 임시 권한 회수 + +### 🔧 추가 문서 + +- **[06-rollback.md](06-rollback.md)** — 문제 발생 시 롤백 계획 + - ALTER 중단, DROP INDEX 명령어 + - 중복 정리 되돌리기 + - 백업 복원 절차 (EC2) + +- **[07-post-analysis.md](07-post-analysis.md)** — 사후 분석 및 최적화 + - 실제 성능 측정 결과 + - 추가 개선 사항 검토 + +--- + +## 🚀 빠른 시작 + +모든 명령은 `result/` 디렉터리에서 실행합니다. + +```bash +cd doc/plan/db/260915-improve-production/result +``` + +1. 01 사전 측정·작업 권한 준비 완료 (2026-09-15) +2. `../02-baseline.md` 실행 → `zsh run_measure.sh baseline` +3. `../03-add-indexes.md` 실행 → 중복 백업·정리 후 `add_indexes.sql` +4. `../04-verify.md` 실행 → `zsh run_measure.sh after` +5. `../05-checklist.md` 체크리스트 완료 + +--- + +## 👤 운영 DB 접속 정보 + +``` +# 임시 계정 +Host: chocomae.jinaju.com +Port: 3306 +User: jisangs (2026-09-15 작업 후 삭제) +Password: 문서에 적지 않음 (별도로 전달받은 값 사용) +Database: chocomae + +# 권한 (2026-09-15 SHOW GRANTS로 확인) +GRANT SELECT, ALTER ON chocomae.* TO jisangs@'182.217.174.221' +# 작업 기간 임시 권한 (05단계에서 회수): SUPER, 최고기록 테이블 2개 INSERT·DELETE, mysql.slow_log SELECT +``` + +> 중복 삭제(DELETE), slow query log 설정(SUPER), `mysql.slow_log` 조회 권한은 작업 기간 동안 임시로 부여했고 05단계에서 회수합니다. [00-overview.md의 단계별 필요 권한](00-overview.md#단계별-필요-권한)을 참고하세요. + +--- + +## 📁 디렉토리 구조 + +``` +260915-improve-production/ +├── README.md ← 이 파일 +├── 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_* ← 01 + ├── params.sql, queries/ ← 02·04 공통 측정 쿼리 + ├── run_measure.sh, summarize_explain.py + ├── baseline_explain_q*.json, baseline_perf.txt + ├── backup_app_highest_dup_rows.sql + ├── add_indexes.sql, add_indexes_result.txt + ├── after_explain_q*.json, after_perf.txt + └── comparison.txt +``` + +--- + +## ✅ 체크리스트 + +운영 서버 적용 전 확인: + +- [ ] **스테이징 결과 검토** (260915-improve-stage 참고) +- [ ] **백업 계획 수립** (EC2 EBS 스냅샷 또는 전체 `mariadb-dump`) +- [x] **01-premeasure.md 실행** (운영 DB 현황 파악, 2026-09-15) +- [x] **계정 권한 확인** (`SELECT, ALTER`만 있음) +- [x] **작업 권한 임시 부여** (SUPER, 최고기록 테이블 2개 INSERT·DELETE, `mysql.slow_log` SELECT) +- [x] **작업 후 임시 권한 회수** (05-checklist.md) +- [ ] **02-baseline.md 실행** (기준선 측정) +- [ ] **새벽 시간 예약** (02:00~03:00 권장) +- [ ] **모니터링 준비** (CPU, 메모리, 프로세스) +- [ ] **롤백 계획 검토** (06-rollback.md) +- [ ] **팀원 통보** (예정된 작업 공지) +- [x] **작업 후 임시 계정 삭제** (2026-09-15) + +--- + +## 📞 도움말 + +| 상황 | 참고 | +|---|---| +| 인덱스 추가 방법 | 03-add-indexes.md | +| 성능 검증 방법 | 04-verify.md | +| 문제 발생 시 | 06-rollback.md | +| 최적화 제안 | 07-post-analysis.md | +| 전체 계획 | 00-overview.md | + +--- + +## 📅 작업 일정 + +**권장: 새벽 2시~3시 (트래픽 최소 시간)** + +| 시간 | 작업 | 소요 | +|---|---|---| +| 02:00 | DB 백업 | 5분 | +| 02:05 | 기준선 측정 (02-baseline) | 10분 | +| 02:15 | 중복 정리 + 인덱스 추가 (03-add-indexes) | 30분 | +| 02:45 | 인덱스 생성 확인 | 5분 | +| 02:50 | 검증 (04-verify) | 10분 | +| 03:00 | 체크리스트, slow log 복원, 권한 회수 (05-checklist) | 10분 | + +--- + +**준비 완료! 시작하려면 [00-overview.md](00-overview.md)부터 읽으세요.** 🚀 diff --git a/doc/plan/db/260915-improve-production/result/add_indexes.sql b/doc/plan/db/260915-improve-production/result/add_indexes.sql new file mode 100644 index 0000000..81bcda9 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/add_indexes.sql @@ -0,0 +1,33 @@ +-- best_record: 랭킹용 +ALTER TABLE best_record +ADD INDEX IF NOT EXISTS idx_maestro_app_dt (MaestroID, AppID, RecordDateTime), +ALGORITHM=INPLACE, LOCK=NONE; + +-- best_record: 히스토리/저장용 +ALTER TABLE best_record +ADD INDEX IF NOT EXISTS idx_maestro_player_app_dt (MaestroID, PlayerID, AppID, RecordDateTime), +ALGORITHM=INPLACE, LOCK=NONE; + +-- typing_exam_record +ALTER TABLE typing_exam_record +ADD INDEX IF NOT EXISTS idx_maestro_writing_dt (MaestroID, WritingID, RecordDateTime), +ALGORITHM=INPLACE, LOCK=NONE; + +ALTER TABLE typing_exam_record +ADD INDEX IF NOT EXISTS idx_maestro_player_writing_dt (MaestroID, PlayerID, WritingID, RecordDateTime), +ALGORITHM=INPLACE, LOCK=NONE; + +-- license_score +ALTER TABLE license_score +ADD INDEX IF NOT EXISTS idx_maestro_player_dt (MaestroID, PlayerID, ScoreDateTime), +ALGORITHM=INPLACE, LOCK=NONE; + +-- app_highest_record: UNIQUE 키 +ALTER TABLE app_highest_record +ADD UNIQUE KEY IF NOT EXISTS uk_maestro_player_app (MaestroID, PlayerID, AppID), +ALGORITHM=INPLACE, LOCK=NONE; + +-- typing_exam_highest_record: UNIQUE 키 +ALTER TABLE typing_exam_highest_record +ADD UNIQUE KEY IF NOT EXISTS uk_maestro_player_writing (MaestroID, PlayerID, WritingID), +ALGORITHM=INPLACE, LOCK=NONE; diff --git a/doc/plan/db/260915-improve-production/result/add_indexes_check.txt b/doc/plan/db/260915-improve-production/result/add_indexes_check.txt new file mode 100644 index 0000000..53a3b04 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/add_indexes_check.txt @@ -0,0 +1,8 @@ +TABLE_NAME INDEX_NAME NON_UNIQUE cols +app_highest_record uk_maestro_player_app 0 MaestroID,PlayerID,AppID +best_record idx_maestro_app_dt 1 MaestroID,AppID,RecordDateTime +best_record idx_maestro_player_app_dt 1 MaestroID,PlayerID,AppID,RecordDateTime +license_score idx_maestro_player_dt 1 MaestroID,PlayerID,ScoreDateTime +typing_exam_highest_record uk_maestro_player_writing 0 MaestroID,PlayerID,WritingID +typing_exam_record idx_maestro_player_writing_dt 1 MaestroID,PlayerID,WritingID,RecordDateTime +typing_exam_record idx_maestro_writing_dt 1 MaestroID,WritingID,RecordDateTime diff --git a/doc/plan/db/260915-improve-production/result/add_indexes_result.txt b/doc/plan/db/260915-improve-production/result/add_indexes_result.txt new file mode 100644 index 0000000..1353c8f --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/add_indexes_result.txt @@ -0,0 +1,64 @@ +-------------- +ALTER TABLE best_record +ADD INDEX IF NOT EXISTS idx_maestro_app_dt (MaestroID, AppID, RecordDateTime), +ALGORITHM=INPLACE, LOCK=NONE +-------------- + +Query OK, 0 rows affected (4.803 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +-------------- +ALTER TABLE best_record +ADD INDEX IF NOT EXISTS idx_maestro_player_app_dt (MaestroID, PlayerID, AppID, RecordDateTime), +ALGORITHM=INPLACE, LOCK=NONE +-------------- + +Query OK, 0 rows affected (4.832 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +-------------- +ALTER TABLE typing_exam_record +ADD INDEX IF NOT EXISTS idx_maestro_writing_dt (MaestroID, WritingID, RecordDateTime), +ALGORITHM=INPLACE, LOCK=NONE +-------------- + +Query OK, 0 rows affected (0.735 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +-------------- +ALTER TABLE typing_exam_record +ADD INDEX IF NOT EXISTS idx_maestro_player_writing_dt (MaestroID, PlayerID, WritingID, RecordDateTime), +ALGORITHM=INPLACE, LOCK=NONE +-------------- + +Query OK, 0 rows affected (0.370 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +-------------- +ALTER TABLE license_score +ADD INDEX IF NOT EXISTS idx_maestro_player_dt (MaestroID, PlayerID, ScoreDateTime), +ALGORITHM=INPLACE, LOCK=NONE +-------------- + +Query OK, 0 rows affected (0.042 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +-------------- +ALTER TABLE app_highest_record +ADD UNIQUE KEY IF NOT EXISTS uk_maestro_player_app (MaestroID, PlayerID, AppID), +ALGORITHM=INPLACE, LOCK=NONE +-------------- + +Query OK, 0 rows affected (1.076 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +-------------- +ALTER TABLE typing_exam_highest_record +ADD UNIQUE KEY IF NOT EXISTS uk_maestro_player_writing (MaestroID, PlayerID, WritingID), +ALGORITHM=INPLACE, LOCK=NONE +-------------- + +Query OK, 0 rows affected (0.156 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +Bye diff --git a/doc/plan/db/260915-improve-production/result/after_analyze.txt b/doc/plan/db/260915-improve-production/result/after_analyze.txt new file mode 100644 index 0000000..42558e7 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/after_analyze.txt @@ -0,0 +1,308 @@ +=== q1_hour_func === +{ + "query_optimization": { + "r_total_time_ms": 0.303570412 + }, + "query_block": { + "select_id": 1, + "r_loops": 1, + "r_total_time_ms": 2.069071756, + "nested_loop": [ + { + "read_sorted_file": { + "r_rows": 22, + "filesort": { + "sort_key": "best_record.RecordDateTime, best_record.PlayerID", + "r_loops": 1, + "r_total_time_ms": 2.050037968, + "r_used_priority_queue": false, + "r_output_rows": 22, + "r_buffer_size": "2047Kb", + "r_sort_mode": "sort_key,addon_fields", + "table": { + "table_name": "best_record", + "access_type": "ref", + "possible_keys": [ + "MaestroID", + "AppID", + "idx_maestro_app_dt", + "idx_maestro_player_app_dt" + ], + "key": "idx_maestro_app_dt", + "key_length": "8", + "used_key_parts": ["MaestroID", "AppID"], + "ref": ["const", "const"], + "r_loops": 1, + "rows": 5851, + "r_rows": 22, + "r_table_time_ms": 2.024712928, + "r_other_time_ms": 0.015343053, + "r_engine_stats": { + "pages_accessed": 77 + }, + "filtered": 100, + "r_filtered": 100, + "index_condition": "cast(best_record.RecordDateTime as date) = @`day` and hour(best_record.RecordDateTime) = 9", + "attached_condition": "best_record.MaestroID <=> @maestro and best_record.AppID <=> @app" + } + } + } + } + ] + } +} +=== q2_hour_range === +{ + "query_optimization": { + "r_total_time_ms": 0.23481673 + }, + "query_block": { + "select_id": 1, + "r_loops": 1, + "r_total_time_ms": 0.259231588, + "nested_loop": [ + { + "read_sorted_file": { + "r_rows": 22, + "filesort": { + "sort_key": "best_record.RecordDateTime, best_record.PlayerID", + "r_loops": 1, + "r_total_time_ms": 0.241578075, + "r_used_priority_queue": false, + "r_output_rows": 22, + "r_buffer_size": "2047Kb", + "r_sort_mode": "sort_key,addon_fields", + "table": { + "table_name": "best_record", + "access_type": "range", + "possible_keys": [ + "MaestroID", + "AppID", + "idx_maestro_app_dt", + "idx_maestro_player_app_dt" + ], + "key": "idx_maestro_app_dt", + "key_length": "13", + "used_key_parts": ["MaestroID", "AppID", "RecordDateTime"], + "r_loops": 1, + "rows": 22, + "r_rows": 22, + "r_table_time_ms": 0.143388535, + "r_other_time_ms": 0.086477209, + "r_engine_stats": { + "pages_accessed": 69 + }, + "filtered": 100, + "r_filtered": 100, + "index_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and best_record.RecordDateTime >= '2026-08-26 09:00:00' and best_record.RecordDateTime < '2026-08-26 10:00:00'" + } + } + } + } + ] + } +} +=== q3_day_func === +{ + "query_optimization": { + "r_total_time_ms": 0.260151772 + }, + "query_block": { + "select_id": 1, + "r_loops": 1, + "r_total_time_ms": 2.440415655, + "filesort": { + "sort_key": "max(best_record.BestRecord) desc, best_record.PlayerID", + "r_loops": 1, + "r_total_time_ms": 0.019813943, + "r_used_priority_queue": false, + "r_output_rows": 61, + "r_buffer_size": "1Kb", + "r_sort_mode": "sort_key,rowid", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "best_record", + "access_type": "ref", + "possible_keys": [ + "MaestroID", + "AppID", + "idx_maestro_app_dt", + "idx_maestro_player_app_dt" + ], + "key": "idx_maestro_app_dt", + "key_length": "8", + "used_key_parts": ["MaestroID", "AppID"], + "ref": ["const", "const"], + "r_loops": 1, + "rows": 5851, + "r_rows": 99, + "r_table_time_ms": 2.331223925, + "r_other_time_ms": 0.071694268, + "r_engine_stats": { + "pages_accessed": 308 + }, + "filtered": 100, + "r_filtered": 100, + "index_condition": "year(best_record.RecordDateTime) = 2026 and month(best_record.RecordDateTime) = 8 and dayofmonth(best_record.RecordDateTime) = 26", + "attached_condition": "best_record.MaestroID <=> @maestro and best_record.AppID <=> @app" + } + } + ] + } + } + } +} +=== q4_day_range === +{ + "query_optimization": { + "r_total_time_ms": 0.335646795 + }, + "query_block": { + "select_id": 1, + "r_loops": 1, + "r_total_time_ms": 0.467062948, + "filesort": { + "sort_key": "max(best_record.BestRecord) desc, best_record.PlayerID", + "r_loops": 1, + "r_total_time_ms": 0.017993581, + "r_used_priority_queue": false, + "r_output_rows": 61, + "r_buffer_size": "1Kb", + "r_sort_mode": "sort_key,rowid", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "best_record", + "access_type": "range", + "possible_keys": [ + "MaestroID", + "AppID", + "idx_maestro_app_dt", + "idx_maestro_player_app_dt" + ], + "key": "idx_maestro_app_dt", + "key_length": "13", + "used_key_parts": ["MaestroID", "AppID", "RecordDateTime"], + "r_loops": 1, + "rows": 99, + "r_rows": 99, + "r_table_time_ms": 0.318203324, + "r_other_time_ms": 0.112702428, + "r_engine_stats": { + "pages_accessed": 300 + }, + "filtered": 100, + "r_filtered": 100, + "index_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and best_record.RecordDateTime >= '2026-08-26 00:00:00' and best_record.RecordDateTime < '2026-08-27 00:00:00'" + } + } + ] + } + } + } +} +=== q5_month_func === +{ + "query_optimization": { + "r_total_time_ms": 0.248649482 + }, + "query_block": { + "select_id": 1, + "r_loops": 1, + "r_total_time_ms": 4.299725667, + "filesort": { + "sort_key": "max(best_record.BestRecord) desc, best_record.PlayerID", + "r_loops": 1, + "r_total_time_ms": 0.068003533, + "r_used_priority_queue": false, + "r_output_rows": 262, + "r_buffer_size": "6Kb", + "r_sort_mode": "sort_key,rowid", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "best_record", + "access_type": "ref", + "possible_keys": [ + "MaestroID", + "AppID", + "idx_maestro_app_dt", + "idx_maestro_player_app_dt" + ], + "key": "idx_maestro_app_dt", + "key_length": "8", + "used_key_parts": ["MaestroID", "AppID"], + "ref": ["const", "const"], + "r_loops": 1, + "rows": 5851, + "r_rows": 709, + "r_table_time_ms": 3.78789381, + "r_other_time_ms": 0.408851363, + "r_engine_stats": { + "pages_accessed": 2138 + }, + "filtered": 100, + "r_filtered": 100, + "index_condition": "year(best_record.RecordDateTime) = 2026 and month(best_record.RecordDateTime) = 8", + "attached_condition": "best_record.MaestroID <=> @maestro and best_record.AppID <=> @app" + } + } + ] + } + } + } +} +=== q6_month_range === +{ + "query_optimization": { + "r_total_time_ms": 0.306621019 + }, + "query_block": { + "select_id": 1, + "r_loops": 1, + "r_total_time_ms": 2.496056728, + "filesort": { + "sort_key": "max(best_record.BestRecord) desc, best_record.PlayerID", + "r_loops": 1, + "r_total_time_ms": 0.065773089, + "r_used_priority_queue": false, + "r_output_rows": 262, + "r_buffer_size": "6Kb", + "r_sort_mode": "sort_key,rowid", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "best_record", + "access_type": "range", + "possible_keys": [ + "MaestroID", + "AppID", + "idx_maestro_app_dt", + "idx_maestro_player_app_dt" + ], + "key": "idx_maestro_app_dt", + "key_length": "13", + "used_key_parts": ["MaestroID", "AppID", "RecordDateTime"], + "r_loops": 1, + "rows": 709, + "r_rows": 709, + "r_table_time_ms": 2.033224622, + "r_other_time_ms": 0.360211684, + "r_engine_stats": { + "pages_accessed": 2131 + }, + "filtered": 0.057728633, + "r_filtered": 100, + "index_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and best_record.RecordDateTime >= '2026-08-01 00:00:00' and best_record.RecordDateTime < '2026-09-01 00:00:00'" + } + } + ] + } + } + } +} diff --git a/doc/plan/db/260915-improve-production/result/after_explain_q1_hour_func.json b/doc/plan/db/260915-improve-production/result/after_explain_q1_hour_func.json new file mode 100644 index 0000000..0e78013 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/after_explain_q1_hour_func.json @@ -0,0 +1,32 @@ +{ + "query_block": { + "select_id": 1, + "nested_loop": [ + { + "read_sorted_file": { + "filesort": { + "sort_key": "best_record.RecordDateTime, best_record.PlayerID", + "table": { + "table_name": "best_record", + "access_type": "ref", + "possible_keys": [ + "MaestroID", + "AppID", + "idx_maestro_app_dt", + "idx_maestro_player_app_dt" + ], + "key": "idx_maestro_app_dt", + "key_length": "8", + "used_key_parts": ["MaestroID", "AppID"], + "ref": ["const", "const"], + "rows": 5851, + "filtered": 100, + "index_condition": "cast(best_record.RecordDateTime as date) = @`day` and hour(best_record.RecordDateTime) = 9", + "attached_condition": "best_record.MaestroID <=> @maestro and best_record.AppID <=> @app" + } + } + } + } + ] + } +} diff --git a/doc/plan/db/260915-improve-production/result/after_explain_q2_hour_range.json b/doc/plan/db/260915-improve-production/result/after_explain_q2_hour_range.json new file mode 100644 index 0000000..1611a32 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/after_explain_q2_hour_range.json @@ -0,0 +1,30 @@ +{ + "query_block": { + "select_id": 1, + "nested_loop": [ + { + "read_sorted_file": { + "filesort": { + "sort_key": "best_record.RecordDateTime, best_record.PlayerID", + "table": { + "table_name": "best_record", + "access_type": "range", + "possible_keys": [ + "MaestroID", + "AppID", + "idx_maestro_app_dt", + "idx_maestro_player_app_dt" + ], + "key": "idx_maestro_app_dt", + "key_length": "13", + "used_key_parts": ["MaestroID", "AppID", "RecordDateTime"], + "rows": 22, + "filtered": 100, + "index_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and best_record.RecordDateTime >= (cast(@`day` as datetime) + interval @`hour` hour) and best_record.RecordDateTime < (cast(@`day` as datetime) + interval @`hour` + 1 hour)" + } + } + } + } + ] + } +} diff --git a/doc/plan/db/260915-improve-production/result/after_explain_q3_day_func.json b/doc/plan/db/260915-improve-production/result/after_explain_q3_day_func.json new file mode 100644 index 0000000..89dea9d --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/after_explain_q3_day_func.json @@ -0,0 +1,32 @@ +{ + "query_block": { + "select_id": 1, + "filesort": { + "sort_key": "max(best_record.BestRecord) desc, best_record.PlayerID", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "best_record", + "access_type": "ref", + "possible_keys": [ + "MaestroID", + "AppID", + "idx_maestro_app_dt", + "idx_maestro_player_app_dt" + ], + "key": "idx_maestro_app_dt", + "key_length": "8", + "used_key_parts": ["MaestroID", "AppID"], + "ref": ["const", "const"], + "rows": 5851, + "filtered": 100, + "index_condition": "year(best_record.RecordDateTime) = 2026 and month(best_record.RecordDateTime) = 8 and dayofmonth(best_record.RecordDateTime) = 26", + "attached_condition": "best_record.MaestroID <=> @maestro and best_record.AppID <=> @app" + } + } + ] + } + } + } +} diff --git a/doc/plan/db/260915-improve-production/result/after_explain_q4_day_range.json b/doc/plan/db/260915-improve-production/result/after_explain_q4_day_range.json new file mode 100644 index 0000000..1eb5b54 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/after_explain_q4_day_range.json @@ -0,0 +1,30 @@ +{ + "query_block": { + "select_id": 1, + "filesort": { + "sort_key": "max(best_record.BestRecord) desc, best_record.PlayerID", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "best_record", + "access_type": "range", + "possible_keys": [ + "MaestroID", + "AppID", + "idx_maestro_app_dt", + "idx_maestro_player_app_dt" + ], + "key": "idx_maestro_app_dt", + "key_length": "13", + "used_key_parts": ["MaestroID", "AppID", "RecordDateTime"], + "rows": 99, + "filtered": 100, + "index_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and best_record.RecordDateTime >= (cast(@`day` as datetime)) and best_record.RecordDateTime < (cast(@`day` as datetime) + interval 1 day)" + } + } + ] + } + } + } +} diff --git a/doc/plan/db/260915-improve-production/result/after_explain_q5_month_func.json b/doc/plan/db/260915-improve-production/result/after_explain_q5_month_func.json new file mode 100644 index 0000000..ef3dac1 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/after_explain_q5_month_func.json @@ -0,0 +1,32 @@ +{ + "query_block": { + "select_id": 1, + "filesort": { + "sort_key": "max(best_record.BestRecord) desc, best_record.PlayerID", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "best_record", + "access_type": "ref", + "possible_keys": [ + "MaestroID", + "AppID", + "idx_maestro_app_dt", + "idx_maestro_player_app_dt" + ], + "key": "idx_maestro_app_dt", + "key_length": "8", + "used_key_parts": ["MaestroID", "AppID"], + "ref": ["const", "const"], + "rows": 5851, + "filtered": 100, + "index_condition": "year(best_record.RecordDateTime) = 2026 and month(best_record.RecordDateTime) = 8", + "attached_condition": "best_record.MaestroID <=> @maestro and best_record.AppID <=> @app" + } + } + ] + } + } + } +} diff --git a/doc/plan/db/260915-improve-production/result/after_explain_q6_month_range.json b/doc/plan/db/260915-improve-production/result/after_explain_q6_month_range.json new file mode 100644 index 0000000..d91c66c --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/after_explain_q6_month_range.json @@ -0,0 +1,30 @@ +{ + "query_block": { + "select_id": 1, + "filesort": { + "sort_key": "max(best_record.BestRecord) desc, best_record.PlayerID", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "best_record", + "access_type": "range", + "possible_keys": [ + "MaestroID", + "AppID", + "idx_maestro_app_dt", + "idx_maestro_player_app_dt" + ], + "key": "idx_maestro_app_dt", + "key_length": "13", + "used_key_parts": ["MaestroID", "AppID", "RecordDateTime"], + "rows": 709, + "filtered": 0.057728633, + "index_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and best_record.RecordDateTime >= (cast(date_format(@`day`,'%Y-%m-01') as datetime)) and best_record.RecordDateTime < (cast(date_format(@`day`,'%Y-%m-01') as datetime) + interval 1 month)" + } + } + ] + } + } + } +} diff --git a/doc/plan/db/260915-improve-production/result/after_perf.txt b/doc/plan/db/260915-improve-production/result/after_perf.txt new file mode 100644 index 0000000..d18cc52 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/after_perf.txt @@ -0,0 +1,1716 @@ +-------------- +SET @maestro = 181, @app = 21, @day = '2026-08-26', @hour = 9 +-------------- + +Query OK, 0 rows affected (0.012 sec) + +-------------- +SELECT '=== round 1: q1_hour_func ===' AS msg +-------------- + ++-------------------------------+ +| msg | ++-------------------------------+ +| === round 1: q1_hour_func === | ++-------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, BestRecord, RecordDateTime +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND DATE(RecordDateTime) = @day + AND HOUR(RecordDateTime) = @hour +ORDER BY RecordDateTime, PlayerID +-------------- + ++----------+------------+---------------------+ +| PlayerID | BestRecord | RecordDateTime | ++----------+------------+---------------------+ +| 129377 | 141.498 | 2026-08-26 09:12:00 | +| 129368 | 288.908 | 2026-08-26 09:12:05 | +| 129384 | 102.975 | 2026-08-26 09:12:31 | +| 129383 | 281.531 | 2026-08-26 09:13:30 | +| 129375 | 174.494 | 2026-08-26 09:14:10 | +| 129373 | 32.9989 | 2026-08-26 09:14:55 | +| 129376 | 467.553 | 2026-08-26 09:16:58 | +| 129385 | 336.673 | 2026-08-26 09:18:01 | +| 129370 | 153.223 | 2026-08-26 09:18:17 | +| 129371 | 83.1084 | 2026-08-26 09:18:25 | +| 129382 | 18.6974 | 2026-08-26 09:18:53 | +| 129380 | 160.591 | 2026-08-26 09:19:44 | +| 129366 | 82.7434 | 2026-08-26 09:20:10 | +| 129369 | 310.72 | 2026-08-26 09:21:02 | +| 129374 | 425.483 | 2026-08-26 09:21:17 | +| 129387 | 73.3656 | 2026-08-26 09:21:25 | +| 129379 | 548.041 | 2026-08-26 09:23:48 | +| 129378 | 212.914 | 2026-08-26 09:26:35 | +| 129367 | 170.092 | 2026-08-26 09:28:15 | +| 129365 | 772.591 | 2026-08-26 09:29:27 | +| 129386 | 268.392 | 2026-08-26 09:29:58 | +| 129372 | 326.853 | 2026-08-26 09:44:54 | ++----------+------------+---------------------+ +22 rows in set (0.017 sec) + +-------------- +SELECT '=== round 1: q2_hour_range ===' AS msg +-------------- + ++--------------------------------+ +| msg | ++--------------------------------+ +| === round 1: q2_hour_range === | ++--------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, BestRecord, RecordDateTime +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(@day AS DATETIME) + INTERVAL @hour HOUR + AND RecordDateTime < CAST(@day AS DATETIME) + INTERVAL (@hour + 1) HOUR +ORDER BY RecordDateTime, PlayerID +-------------- + ++----------+------------+---------------------+ +| PlayerID | BestRecord | RecordDateTime | ++----------+------------+---------------------+ +| 129377 | 141.498 | 2026-08-26 09:12:00 | +| 129368 | 288.908 | 2026-08-26 09:12:05 | +| 129384 | 102.975 | 2026-08-26 09:12:31 | +| 129383 | 281.531 | 2026-08-26 09:13:30 | +| 129375 | 174.494 | 2026-08-26 09:14:10 | +| 129373 | 32.9989 | 2026-08-26 09:14:55 | +| 129376 | 467.553 | 2026-08-26 09:16:58 | +| 129385 | 336.673 | 2026-08-26 09:18:01 | +| 129370 | 153.223 | 2026-08-26 09:18:17 | +| 129371 | 83.1084 | 2026-08-26 09:18:25 | +| 129382 | 18.6974 | 2026-08-26 09:18:53 | +| 129380 | 160.591 | 2026-08-26 09:19:44 | +| 129366 | 82.7434 | 2026-08-26 09:20:10 | +| 129369 | 310.72 | 2026-08-26 09:21:02 | +| 129374 | 425.483 | 2026-08-26 09:21:17 | +| 129387 | 73.3656 | 2026-08-26 09:21:25 | +| 129379 | 548.041 | 2026-08-26 09:23:48 | +| 129378 | 212.914 | 2026-08-26 09:26:35 | +| 129367 | 170.092 | 2026-08-26 09:28:15 | +| 129365 | 772.591 | 2026-08-26 09:29:27 | +| 129386 | 268.392 | 2026-08-26 09:29:58 | +| 129372 | 326.853 | 2026-08-26 09:44:54 | ++----------+------------+---------------------+ +22 rows in set (0.012 sec) + +-------------- +SELECT '=== round 1: q3_day_func ===' AS msg +-------------- + ++------------------------------+ +| msg | ++------------------------------+ +| === round 1: q3_day_func === | ++------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND YEAR(RecordDateTime) = YEAR(@day) + AND MONTH(RecordDateTime) = MONTH(@day) + AND DAYOFMONTH(RecordDateTime) = DAYOFMONTH(@day) +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID +-------------- + ++----------+-----------+ +| PlayerID | HighScore | ++----------+-----------+ +| 127555 | 930.714 | +| 115731 | 904.198 | +| 129365 | 772.591 | +| 115748 | 690.39 | +| 129485 | 582.42 | +| 129379 | 548.041 | +| 129250 | 515.021 | +| 129376 | 467.553 | +| 128818 | 442.442 | +| 129417 | 433.996 | +| 129374 | 425.483 | +| 129372 | 424.086 | +| 128820 | 358.362 | +| 115727 | 349.82 | +| 129406 | 348.801 | +| 129385 | 336.673 | +| 129369 | 310.72 | +| 128822 | 294.813 | +| 50960 | 291.092 | +| 129368 | 288.908 | +| 115728 | 285.48 | +| 129383 | 281.531 | +| 115737 | 279.923 | +| 129418 | 274.516 | +| 128824 | 270.27 | +| 129386 | 268.392 | +| 129407 | 257.985 | +| 129251 | 255.102 | +| 129413 | 238.507 | +| 92080 | 233.291 | +| 129410 | 220.016 | +| 129397 | 213.707 | +| 129378 | 212.914 | +| 128825 | 192.552 | +| 129375 | 174.494 | +| 129367 | 170.092 | +| 129380 | 160.591 | +| 129412 | 159.737 | +| 129370 | 153.223 | +| 129400 | 148.369 | +| 129403 | 143.564 | +| 129377 | 141.498 | +| 129404 | 140.091 | +| 129415 | 127.432 | +| 129398 | 120.774 | +| 129416 | 110.298 | +| 129384 | 102.975 | +| 129402 | 88.645 | +| 115755 | 84.9017 | +| 129371 | 83.1084 | +| 129366 | 82.7434 | +| 129401 | 81.1104 | +| 129387 | 73.3656 | +| 129396 | 71.4623 | +| 128819 | 68.0787 | +| 129408 | 59.7946 | +| 128795 | 57.5178 | +| 129414 | 54.4765 | +| 129411 | 50.4361 | +| 129373 | 32.9989 | +| 129382 | 18.6974 | ++----------+-----------+ +61 rows in set (0.017 sec) + +-------------- +SELECT '=== round 1: q4_day_range ===' AS msg +-------------- + ++-------------------------------+ +| msg | ++-------------------------------+ +| === round 1: q4_day_range === | ++-------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(@day AS DATETIME) + AND RecordDateTime < CAST(@day AS DATETIME) + INTERVAL 1 DAY +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID +-------------- + ++----------+-----------+ +| PlayerID | HighScore | ++----------+-----------+ +| 127555 | 930.714 | +| 115731 | 904.198 | +| 129365 | 772.591 | +| 115748 | 690.39 | +| 129485 | 582.42 | +| 129379 | 548.041 | +| 129250 | 515.021 | +| 129376 | 467.553 | +| 128818 | 442.442 | +| 129417 | 433.996 | +| 129374 | 425.483 | +| 129372 | 424.086 | +| 128820 | 358.362 | +| 115727 | 349.82 | +| 129406 | 348.801 | +| 129385 | 336.673 | +| 129369 | 310.72 | +| 128822 | 294.813 | +| 50960 | 291.092 | +| 129368 | 288.908 | +| 115728 | 285.48 | +| 129383 | 281.531 | +| 115737 | 279.923 | +| 129418 | 274.516 | +| 128824 | 270.27 | +| 129386 | 268.392 | +| 129407 | 257.985 | +| 129251 | 255.102 | +| 129413 | 238.507 | +| 92080 | 233.291 | +| 129410 | 220.016 | +| 129397 | 213.707 | +| 129378 | 212.914 | +| 128825 | 192.552 | +| 129375 | 174.494 | +| 129367 | 170.092 | +| 129380 | 160.591 | +| 129412 | 159.737 | +| 129370 | 153.223 | +| 129400 | 148.369 | +| 129403 | 143.564 | +| 129377 | 141.498 | +| 129404 | 140.091 | +| 129415 | 127.432 | +| 129398 | 120.774 | +| 129416 | 110.298 | +| 129384 | 102.975 | +| 129402 | 88.645 | +| 115755 | 84.9017 | +| 129371 | 83.1084 | +| 129366 | 82.7434 | +| 129401 | 81.1104 | +| 129387 | 73.3656 | +| 129396 | 71.4623 | +| 128819 | 68.0787 | +| 129408 | 59.7946 | +| 128795 | 57.5178 | +| 129414 | 54.4765 | +| 129411 | 50.4361 | +| 129373 | 32.9989 | +| 129382 | 18.6974 | ++----------+-----------+ +61 rows in set (0.014 sec) + +-------------- +SELECT '=== round 1: q5_month_func ===' AS msg +-------------- + ++--------------------------------+ +| msg | ++--------------------------------+ +| === round 1: q5_month_func === | ++--------------------------------+ +1 row in set (0.013 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND YEAR(RecordDateTime) = YEAR(@day) + AND MONTH(RecordDateTime) = MONTH(@day) +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID +-------------- + ++----------+-----------+ +| PlayerID | HighScore | ++----------+-----------+ +| 34820 | 2001.19 | +| 112638 | 1952.35 | +| 113366 | 1863.35 | +| 112641 | 1763.78 | +| 92104 | 1217.8 | +| 113361 | 1082.21 | +| 124027 | 1059.27 | +| 124029 | 1011.24 | +| 122149 | 1007.8 | +| 127555 | 968.61 | +| 113588 | 950.495 | +| 55094 | 930.788 | +| 115731 | 904.198 | +| 121713 | 893.079 | +| 113298 | 884.146 | +| 113358 | 866.426 | +| 55093 | 862.026 | +| 129613 | 861.538 | +| 129611 | 841.894 | +| 129048 | 812.515 | +| 113624 | 809.535 | +| 124030 | 808.738 | +| 46710 | 804.252 | +| 112635 | 779.046 | +| 56775 | 778.698 | +| 129365 | 772.591 | +| 85486 | 745.011 | +| 113296 | 744.186 | +| 92674 | 728.948 | +| 54456 | 726.969 | +| 113362 | 712.677 | +| 129059 | 702.972 | +| 55083 | 697.975 | +| 115748 | 690.39 | +| 113634 | 685.967 | +| 129102 | 685.564 | +| 129256 | 673.212 | +| 129248 | 658.979 | +| 117340 | 656.891 | +| 82793 | 649.689 | +| 124120 | 641.483 | +| 129617 | 638.026 | +| 34049 | 636.484 | +| 114278 | 634.321 | +| 129056 | 632.356 | +| 97042 | 628.272 | +| 113297 | 615.506 | +| 114280 | 602.806 | +| 128820 | 584.348 | +| 129485 | 582.42 | +| 116203 | 581.357 | +| 117666 | 574.346 | +| 104129 | 573.431 | +| 113633 | 555.923 | +| 129376 | 555.097 | +| 120446 | 554.187 | +| 129244 | 552.741 | +| 129062 | 549.367 | +| 129379 | 548.041 | +| 114283 | 544.218 | +| 50960 | 543.074 | +| 116208 | 540.121 | +| 128818 | 533.672 | +| 129067 | 528.548 | +| 117316 | 523.283 | +| 129261 | 520.043 | +| 129250 | 515.021 | +| 112634 | 512.899 | +| 129873 | 512.747 | +| 55082 | 510.561 | +| 92079 | 504.123 | +| 124511 | 502.403 | +| 115727 | 501.008 | +| 92106 | 499.139 | +| 124509 | 483.871 | +| 92080 | 468.411 | +| 124205 | 466.926 | +| 129622 | 466.744 | +| 113629 | 455.779 | +| 115728 | 454.545 | +| 128825 | 451.191 | +| 117312 | 439.394 | +| 129238 | 438.596 | +| 127557 | 434.389 | +| 129417 | 433.996 | +| 129374 | 425.483 | +| 129372 | 424.086 | +| 128821 | 412.225 | +| 129061 | 405.314 | +| 113638 | 404.267 | +| 113357 | 403.286 | +| 129105 | 400.757 | +| 114291 | 397.76 | +| 92082 | 395.859 | +| 129109 | 384.871 | +| 114282 | 381.985 | +| 115737 | 380.684 | +| 129107 | 373.918 | +| 129114 | 371.353 | +| 124484 | 368.894 | +| 113299 | 349.418 | +| 129406 | 348.801 | +| 108963 | 347.896 | +| 129385 | 336.673 | +| 113355 | 333.83 | +| 110901 | 327.69 | +| 113590 | 324.445 | +| 129150 | 324.356 | +| 113630 | 319.266 | +| 128824 | 314.136 | +| 104131 | 310.795 | +| 129369 | 310.72 | +| 129055 | 300.617 | +| 129101 | 300.518 | +| 128822 | 294.813 | +| 124581 | 291.524 | +| 87632 | 289.71 | +| 129368 | 288.908 | +| 113631 | 287.2 | +| 129383 | 281.531 | +| 114290 | 274.779 | +| 129418 | 274.516 | +| 129386 | 268.392 | +| 127554 | 265.487 | +| 129252 | 258.491 | +| 129407 | 257.985 | +| 117381 | 255.494 | +| 129251 | 255.102 | +| 113675 | 254.118 | +| 113359 | 251.451 | +| 124209 | 248.521 | +| 129626 | 246.643 | +| 129413 | 238.507 | +| 67147 | 236.255 | +| 115984 | 231.784 | +| 129063 | 229.314 | +| 127558 | 225.389 | +| 113637 | 225.336 | +| 129410 | 220.016 | +| 111357 | 216.854 | +| 129397 | 213.707 | +| 129378 | 212.914 | +| 114540 | 209.505 | +| 102619 | 208.709 | +| 129052 | 206.831 | +| 129058 | 202.326 | +| 115985 | 199.018 | +| 129240 | 197.102 | +| 129049 | 196.652 | +| 129051 | 190.747 | +| 127556 | 189.618 | +| 113365 | 187.929 | +| 128362 | 180.535 | +| 129115 | 178.05 | +| 129116 | 175.953 | +| 116213 | 175.926 | +| 129375 | 174.494 | +| 115983 | 174.401 | +| 129367 | 170.092 | +| 67140 | 167.858 | +| 129620 | 161.074 | +| 129380 | 160.591 | +| 129412 | 159.737 | +| 129110 | 159.111 | +| 118814 | 156.493 | +| 124208 | 154.884 | +| 113587 | 154.682 | +| 129616 | 153.533 | +| 129370 | 153.223 | +| 129400 | 148.369 | +| 129053 | 146.551 | +| 129245 | 146.329 | +| 129260 | 145.151 | +| 129403 | 143.564 | +| 129840 | 143.483 | +| 129377 | 141.498 | +| 114285 | 141.314 | +| 129404 | 140.091 | +| 124210 | 137.378 | +| 129615 | 134.145 | +| 113294 | 131.096 | +| 129100 | 128.45 | +| 129060 | 127.547 | +| 129415 | 127.432 | +| 85336 | 126.409 | +| 129237 | 125.256 | +| 129113 | 123.163 | +| 129398 | 120.774 | +| 115741 | 111.975 | +| 129416 | 110.298 | +| 129258 | 109.448 | +| 124206 | 109.087 | +| 129057 | 108.223 | +| 119422 | 108.187 | +| 114537 | 107.263 | +| 124162 | 106.926 | +| 117322 | 105.382 | +| 129096 | 103.318 | +| 129384 | 102.975 | +| 112642 | 100.391 | +| 129108 | 100.196 | +| 129623 | 95.3137 | +| 129050 | 94.8697 | +| 129629 | 92.2779 | +| 129099 | 91.6305 | +| 129111 | 91.0051 | +| 129609 | 89.2835 | +| 129402 | 88.645 | +| 129610 | 88.2107 | +| 129064 | 87.0746 | +| 124207 | 86.1428 | +| 113632 | 85.5615 | +| 115755 | 84.9017 | +| 129371 | 83.1084 | +| 129366 | 82.7434 | +| 127546 | 81.9865 | +| 129401 | 81.1104 | +| 129624 | 80.5582 | +| 129242 | 80.4589 | +| 112636 | 77.6431 | +| 129103 | 75.6127 | +| 129614 | 75.3867 | +| 129106 | 74.9306 | +| 129387 | 73.3656 | +| 113589 | 72.7894 | +| 129396 | 71.4623 | +| 92077 | 70.6436 | +| 129098 | 69.1138 | +| 128819 | 68.0787 | +| 129625 | 66.2291 | +| 129247 | 66.1358 | +| 129255 | 63.9769 | +| 115820 | 61.557 | +| 114289 | 60.2948 | +| 129408 | 59.7946 | +| 128795 | 57.5178 | +| 129621 | 55.2846 | +| 116337 | 55.0093 | +| 129414 | 54.4765 | +| 129065 | 53.9601 | +| 129259 | 52.3702 | +| 124168 | 51.4742 | +| 129627 | 51.1316 | +| 129411 | 50.4361 | +| 129243 | 50.3983 | +| 129097 | 49.6162 | +| 129254 | 48.1486 | +| 129612 | 47.508 | +| 129257 | 40.7716 | +| 124166 | 39.6231 | +| 117668 | 38.2632 | +| 108880 | 38.2148 | +| 124258 | 37.8442 | +| 129054 | 37.1786 | +| 117382 | 33.5436 | +| 129373 | 32.9989 | +| 127537 | 29.4361 | +| 129618 | 26.545 | +| 129253 | 24.0941 | +| 129816 | 20.0367 | +| 129382 | 18.6974 | +| 92984 | 18.466 | ++----------+-----------+ +262 rows in set (0.036 sec) + +-------------- +SELECT '=== round 1: q6_month_range ===' AS msg +-------------- + ++---------------------------------+ +| msg | ++---------------------------------+ +| === round 1: q6_month_range === | ++---------------------------------+ +1 row in set (0.013 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(DATE_FORMAT(@day, '%Y-%m-01') AS DATETIME) + AND RecordDateTime < CAST(DATE_FORMAT(@day, '%Y-%m-01') AS DATETIME) + INTERVAL 1 MONTH +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID +-------------- + ++----------+-----------+ +| PlayerID | HighScore | ++----------+-----------+ +| 34820 | 2001.19 | +| 112638 | 1952.35 | +| 113366 | 1863.35 | +| 112641 | 1763.78 | +| 92104 | 1217.8 | +| 113361 | 1082.21 | +| 124027 | 1059.27 | +| 124029 | 1011.24 | +| 122149 | 1007.8 | +| 127555 | 968.61 | +| 113588 | 950.495 | +| 55094 | 930.788 | +| 115731 | 904.198 | +| 121713 | 893.079 | +| 113298 | 884.146 | +| 113358 | 866.426 | +| 55093 | 862.026 | +| 129613 | 861.538 | +| 129611 | 841.894 | +| 129048 | 812.515 | +| 113624 | 809.535 | +| 124030 | 808.738 | +| 46710 | 804.252 | +| 112635 | 779.046 | +| 56775 | 778.698 | +| 129365 | 772.591 | +| 85486 | 745.011 | +| 113296 | 744.186 | +| 92674 | 728.948 | +| 54456 | 726.969 | +| 113362 | 712.677 | +| 129059 | 702.972 | +| 55083 | 697.975 | +| 115748 | 690.39 | +| 113634 | 685.967 | +| 129102 | 685.564 | +| 129256 | 673.212 | +| 129248 | 658.979 | +| 117340 | 656.891 | +| 82793 | 649.689 | +| 124120 | 641.483 | +| 129617 | 638.026 | +| 34049 | 636.484 | +| 114278 | 634.321 | +| 129056 | 632.356 | +| 97042 | 628.272 | +| 113297 | 615.506 | +| 114280 | 602.806 | +| 128820 | 584.348 | +| 129485 | 582.42 | +| 116203 | 581.357 | +| 117666 | 574.346 | +| 104129 | 573.431 | +| 113633 | 555.923 | +| 129376 | 555.097 | +| 120446 | 554.187 | +| 129244 | 552.741 | +| 129062 | 549.367 | +| 129379 | 548.041 | +| 114283 | 544.218 | +| 50960 | 543.074 | +| 116208 | 540.121 | +| 128818 | 533.672 | +| 129067 | 528.548 | +| 117316 | 523.283 | +| 129261 | 520.043 | +| 129250 | 515.021 | +| 112634 | 512.899 | +| 129873 | 512.747 | +| 55082 | 510.561 | +| 92079 | 504.123 | +| 124511 | 502.403 | +| 115727 | 501.008 | +| 92106 | 499.139 | +| 124509 | 483.871 | +| 92080 | 468.411 | +| 124205 | 466.926 | +| 129622 | 466.744 | +| 113629 | 455.779 | +| 115728 | 454.545 | +| 128825 | 451.191 | +| 117312 | 439.394 | +| 129238 | 438.596 | +| 127557 | 434.389 | +| 129417 | 433.996 | +| 129374 | 425.483 | +| 129372 | 424.086 | +| 128821 | 412.225 | +| 129061 | 405.314 | +| 113638 | 404.267 | +| 113357 | 403.286 | +| 129105 | 400.757 | +| 114291 | 397.76 | +| 92082 | 395.859 | +| 129109 | 384.871 | +| 114282 | 381.985 | +| 115737 | 380.684 | +| 129107 | 373.918 | +| 129114 | 371.353 | +| 124484 | 368.894 | +| 113299 | 349.418 | +| 129406 | 348.801 | +| 108963 | 347.896 | +| 129385 | 336.673 | +| 113355 | 333.83 | +| 110901 | 327.69 | +| 113590 | 324.445 | +| 129150 | 324.356 | +| 113630 | 319.266 | +| 128824 | 314.136 | +| 104131 | 310.795 | +| 129369 | 310.72 | +| 129055 | 300.617 | +| 129101 | 300.518 | +| 128822 | 294.813 | +| 124581 | 291.524 | +| 87632 | 289.71 | +| 129368 | 288.908 | +| 113631 | 287.2 | +| 129383 | 281.531 | +| 114290 | 274.779 | +| 129418 | 274.516 | +| 129386 | 268.392 | +| 127554 | 265.487 | +| 129252 | 258.491 | +| 129407 | 257.985 | +| 117381 | 255.494 | +| 129251 | 255.102 | +| 113675 | 254.118 | +| 113359 | 251.451 | +| 124209 | 248.521 | +| 129626 | 246.643 | +| 129413 | 238.507 | +| 67147 | 236.255 | +| 115984 | 231.784 | +| 129063 | 229.314 | +| 127558 | 225.389 | +| 113637 | 225.336 | +| 129410 | 220.016 | +| 111357 | 216.854 | +| 129397 | 213.707 | +| 129378 | 212.914 | +| 114540 | 209.505 | +| 102619 | 208.709 | +| 129052 | 206.831 | +| 129058 | 202.326 | +| 115985 | 199.018 | +| 129240 | 197.102 | +| 129049 | 196.652 | +| 129051 | 190.747 | +| 127556 | 189.618 | +| 113365 | 187.929 | +| 128362 | 180.535 | +| 129115 | 178.05 | +| 129116 | 175.953 | +| 116213 | 175.926 | +| 129375 | 174.494 | +| 115983 | 174.401 | +| 129367 | 170.092 | +| 67140 | 167.858 | +| 129620 | 161.074 | +| 129380 | 160.591 | +| 129412 | 159.737 | +| 129110 | 159.111 | +| 118814 | 156.493 | +| 124208 | 154.884 | +| 113587 | 154.682 | +| 129616 | 153.533 | +| 129370 | 153.223 | +| 129400 | 148.369 | +| 129053 | 146.551 | +| 129245 | 146.329 | +| 129260 | 145.151 | +| 129403 | 143.564 | +| 129840 | 143.483 | +| 129377 | 141.498 | +| 114285 | 141.314 | +| 129404 | 140.091 | +| 124210 | 137.378 | +| 129615 | 134.145 | +| 113294 | 131.096 | +| 129100 | 128.45 | +| 129060 | 127.547 | +| 129415 | 127.432 | +| 85336 | 126.409 | +| 129237 | 125.256 | +| 129113 | 123.163 | +| 129398 | 120.774 | +| 115741 | 111.975 | +| 129416 | 110.298 | +| 129258 | 109.448 | +| 124206 | 109.087 | +| 129057 | 108.223 | +| 119422 | 108.187 | +| 114537 | 107.263 | +| 124162 | 106.926 | +| 117322 | 105.382 | +| 129096 | 103.318 | +| 129384 | 102.975 | +| 112642 | 100.391 | +| 129108 | 100.196 | +| 129623 | 95.3137 | +| 129050 | 94.8697 | +| 129629 | 92.2779 | +| 129099 | 91.6305 | +| 129111 | 91.0051 | +| 129609 | 89.2835 | +| 129402 | 88.645 | +| 129610 | 88.2107 | +| 129064 | 87.0746 | +| 124207 | 86.1428 | +| 113632 | 85.5615 | +| 115755 | 84.9017 | +| 129371 | 83.1084 | +| 129366 | 82.7434 | +| 127546 | 81.9865 | +| 129401 | 81.1104 | +| 129624 | 80.5582 | +| 129242 | 80.4589 | +| 112636 | 77.6431 | +| 129103 | 75.6127 | +| 129614 | 75.3867 | +| 129106 | 74.9306 | +| 129387 | 73.3656 | +| 113589 | 72.7894 | +| 129396 | 71.4623 | +| 92077 | 70.6436 | +| 129098 | 69.1138 | +| 128819 | 68.0787 | +| 129625 | 66.2291 | +| 129247 | 66.1358 | +| 129255 | 63.9769 | +| 115820 | 61.557 | +| 114289 | 60.2948 | +| 129408 | 59.7946 | +| 128795 | 57.5178 | +| 129621 | 55.2846 | +| 116337 | 55.0093 | +| 129414 | 54.4765 | +| 129065 | 53.9601 | +| 129259 | 52.3702 | +| 124168 | 51.4742 | +| 129627 | 51.1316 | +| 129411 | 50.4361 | +| 129243 | 50.3983 | +| 129097 | 49.6162 | +| 129254 | 48.1486 | +| 129612 | 47.508 | +| 129257 | 40.7716 | +| 124166 | 39.6231 | +| 117668 | 38.2632 | +| 108880 | 38.2148 | +| 124258 | 37.8442 | +| 129054 | 37.1786 | +| 117382 | 33.5436 | +| 129373 | 32.9989 | +| 127537 | 29.4361 | +| 129618 | 26.545 | +| 129253 | 24.0941 | +| 129816 | 20.0367 | +| 129382 | 18.6974 | +| 92984 | 18.466 | ++----------+-----------+ +262 rows in set (0.015 sec) + +Bye +-------------- +SET @maestro = 181, @app = 21, @day = '2026-08-26', @hour = 9 +-------------- + +Query OK, 0 rows affected (0.012 sec) + +-------------- +SELECT '=== round 2: q1_hour_func ===' AS msg +-------------- + ++-------------------------------+ +| msg | ++-------------------------------+ +| === round 2: q1_hour_func === | ++-------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, BestRecord, RecordDateTime +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND DATE(RecordDateTime) = @day + AND HOUR(RecordDateTime) = @hour +ORDER BY RecordDateTime, PlayerID +-------------- + ++----------+------------+---------------------+ +| PlayerID | BestRecord | RecordDateTime | ++----------+------------+---------------------+ +| 129377 | 141.498 | 2026-08-26 09:12:00 | +| 129368 | 288.908 | 2026-08-26 09:12:05 | +| 129384 | 102.975 | 2026-08-26 09:12:31 | +| 129383 | 281.531 | 2026-08-26 09:13:30 | +| 129375 | 174.494 | 2026-08-26 09:14:10 | +| 129373 | 32.9989 | 2026-08-26 09:14:55 | +| 129376 | 467.553 | 2026-08-26 09:16:58 | +| 129385 | 336.673 | 2026-08-26 09:18:01 | +| 129370 | 153.223 | 2026-08-26 09:18:17 | +| 129371 | 83.1084 | 2026-08-26 09:18:25 | +| 129382 | 18.6974 | 2026-08-26 09:18:53 | +| 129380 | 160.591 | 2026-08-26 09:19:44 | +| 129366 | 82.7434 | 2026-08-26 09:20:10 | +| 129369 | 310.72 | 2026-08-26 09:21:02 | +| 129374 | 425.483 | 2026-08-26 09:21:17 | +| 129387 | 73.3656 | 2026-08-26 09:21:25 | +| 129379 | 548.041 | 2026-08-26 09:23:48 | +| 129378 | 212.914 | 2026-08-26 09:26:35 | +| 129367 | 170.092 | 2026-08-26 09:28:15 | +| 129365 | 772.591 | 2026-08-26 09:29:27 | +| 129386 | 268.392 | 2026-08-26 09:29:58 | +| 129372 | 326.853 | 2026-08-26 09:44:54 | ++----------+------------+---------------------+ +22 rows in set (0.015 sec) + +-------------- +SELECT '=== round 2: q2_hour_range ===' AS msg +-------------- + ++--------------------------------+ +| msg | ++--------------------------------+ +| === round 2: q2_hour_range === | ++--------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, BestRecord, RecordDateTime +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(@day AS DATETIME) + INTERVAL @hour HOUR + AND RecordDateTime < CAST(@day AS DATETIME) + INTERVAL (@hour + 1) HOUR +ORDER BY RecordDateTime, PlayerID +-------------- + ++----------+------------+---------------------+ +| PlayerID | BestRecord | RecordDateTime | ++----------+------------+---------------------+ +| 129377 | 141.498 | 2026-08-26 09:12:00 | +| 129368 | 288.908 | 2026-08-26 09:12:05 | +| 129384 | 102.975 | 2026-08-26 09:12:31 | +| 129383 | 281.531 | 2026-08-26 09:13:30 | +| 129375 | 174.494 | 2026-08-26 09:14:10 | +| 129373 | 32.9989 | 2026-08-26 09:14:55 | +| 129376 | 467.553 | 2026-08-26 09:16:58 | +| 129385 | 336.673 | 2026-08-26 09:18:01 | +| 129370 | 153.223 | 2026-08-26 09:18:17 | +| 129371 | 83.1084 | 2026-08-26 09:18:25 | +| 129382 | 18.6974 | 2026-08-26 09:18:53 | +| 129380 | 160.591 | 2026-08-26 09:19:44 | +| 129366 | 82.7434 | 2026-08-26 09:20:10 | +| 129369 | 310.72 | 2026-08-26 09:21:02 | +| 129374 | 425.483 | 2026-08-26 09:21:17 | +| 129387 | 73.3656 | 2026-08-26 09:21:25 | +| 129379 | 548.041 | 2026-08-26 09:23:48 | +| 129378 | 212.914 | 2026-08-26 09:26:35 | +| 129367 | 170.092 | 2026-08-26 09:28:15 | +| 129365 | 772.591 | 2026-08-26 09:29:27 | +| 129386 | 268.392 | 2026-08-26 09:29:58 | +| 129372 | 326.853 | 2026-08-26 09:44:54 | ++----------+------------+---------------------+ +22 rows in set (0.013 sec) + +-------------- +SELECT '=== round 2: q3_day_func ===' AS msg +-------------- + ++------------------------------+ +| msg | ++------------------------------+ +| === round 2: q3_day_func === | ++------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND YEAR(RecordDateTime) = YEAR(@day) + AND MONTH(RecordDateTime) = MONTH(@day) + AND DAYOFMONTH(RecordDateTime) = DAYOFMONTH(@day) +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID +-------------- + ++----------+-----------+ +| PlayerID | HighScore | ++----------+-----------+ +| 127555 | 930.714 | +| 115731 | 904.198 | +| 129365 | 772.591 | +| 115748 | 690.39 | +| 129485 | 582.42 | +| 129379 | 548.041 | +| 129250 | 515.021 | +| 129376 | 467.553 | +| 128818 | 442.442 | +| 129417 | 433.996 | +| 129374 | 425.483 | +| 129372 | 424.086 | +| 128820 | 358.362 | +| 115727 | 349.82 | +| 129406 | 348.801 | +| 129385 | 336.673 | +| 129369 | 310.72 | +| 128822 | 294.813 | +| 50960 | 291.092 | +| 129368 | 288.908 | +| 115728 | 285.48 | +| 129383 | 281.531 | +| 115737 | 279.923 | +| 129418 | 274.516 | +| 128824 | 270.27 | +| 129386 | 268.392 | +| 129407 | 257.985 | +| 129251 | 255.102 | +| 129413 | 238.507 | +| 92080 | 233.291 | +| 129410 | 220.016 | +| 129397 | 213.707 | +| 129378 | 212.914 | +| 128825 | 192.552 | +| 129375 | 174.494 | +| 129367 | 170.092 | +| 129380 | 160.591 | +| 129412 | 159.737 | +| 129370 | 153.223 | +| 129400 | 148.369 | +| 129403 | 143.564 | +| 129377 | 141.498 | +| 129404 | 140.091 | +| 129415 | 127.432 | +| 129398 | 120.774 | +| 129416 | 110.298 | +| 129384 | 102.975 | +| 129402 | 88.645 | +| 115755 | 84.9017 | +| 129371 | 83.1084 | +| 129366 | 82.7434 | +| 129401 | 81.1104 | +| 129387 | 73.3656 | +| 129396 | 71.4623 | +| 128819 | 68.0787 | +| 129408 | 59.7946 | +| 128795 | 57.5178 | +| 129414 | 54.4765 | +| 129411 | 50.4361 | +| 129373 | 32.9989 | +| 129382 | 18.6974 | ++----------+-----------+ +61 rows in set (0.015 sec) + +-------------- +SELECT '=== round 2: q4_day_range ===' AS msg +-------------- + ++-------------------------------+ +| msg | ++-------------------------------+ +| === round 2: q4_day_range === | ++-------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(@day AS DATETIME) + AND RecordDateTime < CAST(@day AS DATETIME) + INTERVAL 1 DAY +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID +-------------- + ++----------+-----------+ +| PlayerID | HighScore | ++----------+-----------+ +| 127555 | 930.714 | +| 115731 | 904.198 | +| 129365 | 772.591 | +| 115748 | 690.39 | +| 129485 | 582.42 | +| 129379 | 548.041 | +| 129250 | 515.021 | +| 129376 | 467.553 | +| 128818 | 442.442 | +| 129417 | 433.996 | +| 129374 | 425.483 | +| 129372 | 424.086 | +| 128820 | 358.362 | +| 115727 | 349.82 | +| 129406 | 348.801 | +| 129385 | 336.673 | +| 129369 | 310.72 | +| 128822 | 294.813 | +| 50960 | 291.092 | +| 129368 | 288.908 | +| 115728 | 285.48 | +| 129383 | 281.531 | +| 115737 | 279.923 | +| 129418 | 274.516 | +| 128824 | 270.27 | +| 129386 | 268.392 | +| 129407 | 257.985 | +| 129251 | 255.102 | +| 129413 | 238.507 | +| 92080 | 233.291 | +| 129410 | 220.016 | +| 129397 | 213.707 | +| 129378 | 212.914 | +| 128825 | 192.552 | +| 129375 | 174.494 | +| 129367 | 170.092 | +| 129380 | 160.591 | +| 129412 | 159.737 | +| 129370 | 153.223 | +| 129400 | 148.369 | +| 129403 | 143.564 | +| 129377 | 141.498 | +| 129404 | 140.091 | +| 129415 | 127.432 | +| 129398 | 120.774 | +| 129416 | 110.298 | +| 129384 | 102.975 | +| 129402 | 88.645 | +| 115755 | 84.9017 | +| 129371 | 83.1084 | +| 129366 | 82.7434 | +| 129401 | 81.1104 | +| 129387 | 73.3656 | +| 129396 | 71.4623 | +| 128819 | 68.0787 | +| 129408 | 59.7946 | +| 128795 | 57.5178 | +| 129414 | 54.4765 | +| 129411 | 50.4361 | +| 129373 | 32.9989 | +| 129382 | 18.6974 | ++----------+-----------+ +61 rows in set (0.014 sec) + +-------------- +SELECT '=== round 2: q5_month_func ===' AS msg +-------------- + ++--------------------------------+ +| msg | ++--------------------------------+ +| === round 2: q5_month_func === | ++--------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND YEAR(RecordDateTime) = YEAR(@day) + AND MONTH(RecordDateTime) = MONTH(@day) +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID +-------------- + ++----------+-----------+ +| PlayerID | HighScore | ++----------+-----------+ +| 34820 | 2001.19 | +| 112638 | 1952.35 | +| 113366 | 1863.35 | +| 112641 | 1763.78 | +| 92104 | 1217.8 | +| 113361 | 1082.21 | +| 124027 | 1059.27 | +| 124029 | 1011.24 | +| 122149 | 1007.8 | +| 127555 | 968.61 | +| 113588 | 950.495 | +| 55094 | 930.788 | +| 115731 | 904.198 | +| 121713 | 893.079 | +| 113298 | 884.146 | +| 113358 | 866.426 | +| 55093 | 862.026 | +| 129613 | 861.538 | +| 129611 | 841.894 | +| 129048 | 812.515 | +| 113624 | 809.535 | +| 124030 | 808.738 | +| 46710 | 804.252 | +| 112635 | 779.046 | +| 56775 | 778.698 | +| 129365 | 772.591 | +| 85486 | 745.011 | +| 113296 | 744.186 | +| 92674 | 728.948 | +| 54456 | 726.969 | +| 113362 | 712.677 | +| 129059 | 702.972 | +| 55083 | 697.975 | +| 115748 | 690.39 | +| 113634 | 685.967 | +| 129102 | 685.564 | +| 129256 | 673.212 | +| 129248 | 658.979 | +| 117340 | 656.891 | +| 82793 | 649.689 | +| 124120 | 641.483 | +| 129617 | 638.026 | +| 34049 | 636.484 | +| 114278 | 634.321 | +| 129056 | 632.356 | +| 97042 | 628.272 | +| 113297 | 615.506 | +| 114280 | 602.806 | +| 128820 | 584.348 | +| 129485 | 582.42 | +| 116203 | 581.357 | +| 117666 | 574.346 | +| 104129 | 573.431 | +| 113633 | 555.923 | +| 129376 | 555.097 | +| 120446 | 554.187 | +| 129244 | 552.741 | +| 129062 | 549.367 | +| 129379 | 548.041 | +| 114283 | 544.218 | +| 50960 | 543.074 | +| 116208 | 540.121 | +| 128818 | 533.672 | +| 129067 | 528.548 | +| 117316 | 523.283 | +| 129261 | 520.043 | +| 129250 | 515.021 | +| 112634 | 512.899 | +| 129873 | 512.747 | +| 55082 | 510.561 | +| 92079 | 504.123 | +| 124511 | 502.403 | +| 115727 | 501.008 | +| 92106 | 499.139 | +| 124509 | 483.871 | +| 92080 | 468.411 | +| 124205 | 466.926 | +| 129622 | 466.744 | +| 113629 | 455.779 | +| 115728 | 454.545 | +| 128825 | 451.191 | +| 117312 | 439.394 | +| 129238 | 438.596 | +| 127557 | 434.389 | +| 129417 | 433.996 | +| 129374 | 425.483 | +| 129372 | 424.086 | +| 128821 | 412.225 | +| 129061 | 405.314 | +| 113638 | 404.267 | +| 113357 | 403.286 | +| 129105 | 400.757 | +| 114291 | 397.76 | +| 92082 | 395.859 | +| 129109 | 384.871 | +| 114282 | 381.985 | +| 115737 | 380.684 | +| 129107 | 373.918 | +| 129114 | 371.353 | +| 124484 | 368.894 | +| 113299 | 349.418 | +| 129406 | 348.801 | +| 108963 | 347.896 | +| 129385 | 336.673 | +| 113355 | 333.83 | +| 110901 | 327.69 | +| 113590 | 324.445 | +| 129150 | 324.356 | +| 113630 | 319.266 | +| 128824 | 314.136 | +| 104131 | 310.795 | +| 129369 | 310.72 | +| 129055 | 300.617 | +| 129101 | 300.518 | +| 128822 | 294.813 | +| 124581 | 291.524 | +| 87632 | 289.71 | +| 129368 | 288.908 | +| 113631 | 287.2 | +| 129383 | 281.531 | +| 114290 | 274.779 | +| 129418 | 274.516 | +| 129386 | 268.392 | +| 127554 | 265.487 | +| 129252 | 258.491 | +| 129407 | 257.985 | +| 117381 | 255.494 | +| 129251 | 255.102 | +| 113675 | 254.118 | +| 113359 | 251.451 | +| 124209 | 248.521 | +| 129626 | 246.643 | +| 129413 | 238.507 | +| 67147 | 236.255 | +| 115984 | 231.784 | +| 129063 | 229.314 | +| 127558 | 225.389 | +| 113637 | 225.336 | +| 129410 | 220.016 | +| 111357 | 216.854 | +| 129397 | 213.707 | +| 129378 | 212.914 | +| 114540 | 209.505 | +| 102619 | 208.709 | +| 129052 | 206.831 | +| 129058 | 202.326 | +| 115985 | 199.018 | +| 129240 | 197.102 | +| 129049 | 196.652 | +| 129051 | 190.747 | +| 127556 | 189.618 | +| 113365 | 187.929 | +| 128362 | 180.535 | +| 129115 | 178.05 | +| 129116 | 175.953 | +| 116213 | 175.926 | +| 129375 | 174.494 | +| 115983 | 174.401 | +| 129367 | 170.092 | +| 67140 | 167.858 | +| 129620 | 161.074 | +| 129380 | 160.591 | +| 129412 | 159.737 | +| 129110 | 159.111 | +| 118814 | 156.493 | +| 124208 | 154.884 | +| 113587 | 154.682 | +| 129616 | 153.533 | +| 129370 | 153.223 | +| 129400 | 148.369 | +| 129053 | 146.551 | +| 129245 | 146.329 | +| 129260 | 145.151 | +| 129403 | 143.564 | +| 129840 | 143.483 | +| 129377 | 141.498 | +| 114285 | 141.314 | +| 129404 | 140.091 | +| 124210 | 137.378 | +| 129615 | 134.145 | +| 113294 | 131.096 | +| 129100 | 128.45 | +| 129060 | 127.547 | +| 129415 | 127.432 | +| 85336 | 126.409 | +| 129237 | 125.256 | +| 129113 | 123.163 | +| 129398 | 120.774 | +| 115741 | 111.975 | +| 129416 | 110.298 | +| 129258 | 109.448 | +| 124206 | 109.087 | +| 129057 | 108.223 | +| 119422 | 108.187 | +| 114537 | 107.263 | +| 124162 | 106.926 | +| 117322 | 105.382 | +| 129096 | 103.318 | +| 129384 | 102.975 | +| 112642 | 100.391 | +| 129108 | 100.196 | +| 129623 | 95.3137 | +| 129050 | 94.8697 | +| 129629 | 92.2779 | +| 129099 | 91.6305 | +| 129111 | 91.0051 | +| 129609 | 89.2835 | +| 129402 | 88.645 | +| 129610 | 88.2107 | +| 129064 | 87.0746 | +| 124207 | 86.1428 | +| 113632 | 85.5615 | +| 115755 | 84.9017 | +| 129371 | 83.1084 | +| 129366 | 82.7434 | +| 127546 | 81.9865 | +| 129401 | 81.1104 | +| 129624 | 80.5582 | +| 129242 | 80.4589 | +| 112636 | 77.6431 | +| 129103 | 75.6127 | +| 129614 | 75.3867 | +| 129106 | 74.9306 | +| 129387 | 73.3656 | +| 113589 | 72.7894 | +| 129396 | 71.4623 | +| 92077 | 70.6436 | +| 129098 | 69.1138 | +| 128819 | 68.0787 | +| 129625 | 66.2291 | +| 129247 | 66.1358 | +| 129255 | 63.9769 | +| 115820 | 61.557 | +| 114289 | 60.2948 | +| 129408 | 59.7946 | +| 128795 | 57.5178 | +| 129621 | 55.2846 | +| 116337 | 55.0093 | +| 129414 | 54.4765 | +| 129065 | 53.9601 | +| 129259 | 52.3702 | +| 124168 | 51.4742 | +| 129627 | 51.1316 | +| 129411 | 50.4361 | +| 129243 | 50.3983 | +| 129097 | 49.6162 | +| 129254 | 48.1486 | +| 129612 | 47.508 | +| 129257 | 40.7716 | +| 124166 | 39.6231 | +| 117668 | 38.2632 | +| 108880 | 38.2148 | +| 124258 | 37.8442 | +| 129054 | 37.1786 | +| 117382 | 33.5436 | +| 129373 | 32.9989 | +| 127537 | 29.4361 | +| 129618 | 26.545 | +| 129253 | 24.0941 | +| 129816 | 20.0367 | +| 129382 | 18.6974 | +| 92984 | 18.466 | ++----------+-----------+ +262 rows in set (0.018 sec) + +-------------- +SELECT '=== round 2: q6_month_range ===' AS msg +-------------- + ++---------------------------------+ +| msg | ++---------------------------------+ +| === round 2: q6_month_range === | ++---------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(DATE_FORMAT(@day, '%Y-%m-01') AS DATETIME) + AND RecordDateTime < CAST(DATE_FORMAT(@day, '%Y-%m-01') AS DATETIME) + INTERVAL 1 MONTH +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID +-------------- + ++----------+-----------+ +| PlayerID | HighScore | ++----------+-----------+ +| 34820 | 2001.19 | +| 112638 | 1952.35 | +| 113366 | 1863.35 | +| 112641 | 1763.78 | +| 92104 | 1217.8 | +| 113361 | 1082.21 | +| 124027 | 1059.27 | +| 124029 | 1011.24 | +| 122149 | 1007.8 | +| 127555 | 968.61 | +| 113588 | 950.495 | +| 55094 | 930.788 | +| 115731 | 904.198 | +| 121713 | 893.079 | +| 113298 | 884.146 | +| 113358 | 866.426 | +| 55093 | 862.026 | +| 129613 | 861.538 | +| 129611 | 841.894 | +| 129048 | 812.515 | +| 113624 | 809.535 | +| 124030 | 808.738 | +| 46710 | 804.252 | +| 112635 | 779.046 | +| 56775 | 778.698 | +| 129365 | 772.591 | +| 85486 | 745.011 | +| 113296 | 744.186 | +| 92674 | 728.948 | +| 54456 | 726.969 | +| 113362 | 712.677 | +| 129059 | 702.972 | +| 55083 | 697.975 | +| 115748 | 690.39 | +| 113634 | 685.967 | +| 129102 | 685.564 | +| 129256 | 673.212 | +| 129248 | 658.979 | +| 117340 | 656.891 | +| 82793 | 649.689 | +| 124120 | 641.483 | +| 129617 | 638.026 | +| 34049 | 636.484 | +| 114278 | 634.321 | +| 129056 | 632.356 | +| 97042 | 628.272 | +| 113297 | 615.506 | +| 114280 | 602.806 | +| 128820 | 584.348 | +| 129485 | 582.42 | +| 116203 | 581.357 | +| 117666 | 574.346 | +| 104129 | 573.431 | +| 113633 | 555.923 | +| 129376 | 555.097 | +| 120446 | 554.187 | +| 129244 | 552.741 | +| 129062 | 549.367 | +| 129379 | 548.041 | +| 114283 | 544.218 | +| 50960 | 543.074 | +| 116208 | 540.121 | +| 128818 | 533.672 | +| 129067 | 528.548 | +| 117316 | 523.283 | +| 129261 | 520.043 | +| 129250 | 515.021 | +| 112634 | 512.899 | +| 129873 | 512.747 | +| 55082 | 510.561 | +| 92079 | 504.123 | +| 124511 | 502.403 | +| 115727 | 501.008 | +| 92106 | 499.139 | +| 124509 | 483.871 | +| 92080 | 468.411 | +| 124205 | 466.926 | +| 129622 | 466.744 | +| 113629 | 455.779 | +| 115728 | 454.545 | +| 128825 | 451.191 | +| 117312 | 439.394 | +| 129238 | 438.596 | +| 127557 | 434.389 | +| 129417 | 433.996 | +| 129374 | 425.483 | +| 129372 | 424.086 | +| 128821 | 412.225 | +| 129061 | 405.314 | +| 113638 | 404.267 | +| 113357 | 403.286 | +| 129105 | 400.757 | +| 114291 | 397.76 | +| 92082 | 395.859 | +| 129109 | 384.871 | +| 114282 | 381.985 | +| 115737 | 380.684 | +| 129107 | 373.918 | +| 129114 | 371.353 | +| 124484 | 368.894 | +| 113299 | 349.418 | +| 129406 | 348.801 | +| 108963 | 347.896 | +| 129385 | 336.673 | +| 113355 | 333.83 | +| 110901 | 327.69 | +| 113590 | 324.445 | +| 129150 | 324.356 | +| 113630 | 319.266 | +| 128824 | 314.136 | +| 104131 | 310.795 | +| 129369 | 310.72 | +| 129055 | 300.617 | +| 129101 | 300.518 | +| 128822 | 294.813 | +| 124581 | 291.524 | +| 87632 | 289.71 | +| 129368 | 288.908 | +| 113631 | 287.2 | +| 129383 | 281.531 | +| 114290 | 274.779 | +| 129418 | 274.516 | +| 129386 | 268.392 | +| 127554 | 265.487 | +| 129252 | 258.491 | +| 129407 | 257.985 | +| 117381 | 255.494 | +| 129251 | 255.102 | +| 113675 | 254.118 | +| 113359 | 251.451 | +| 124209 | 248.521 | +| 129626 | 246.643 | +| 129413 | 238.507 | +| 67147 | 236.255 | +| 115984 | 231.784 | +| 129063 | 229.314 | +| 127558 | 225.389 | +| 113637 | 225.336 | +| 129410 | 220.016 | +| 111357 | 216.854 | +| 129397 | 213.707 | +| 129378 | 212.914 | +| 114540 | 209.505 | +| 102619 | 208.709 | +| 129052 | 206.831 | +| 129058 | 202.326 | +| 115985 | 199.018 | +| 129240 | 197.102 | +| 129049 | 196.652 | +| 129051 | 190.747 | +| 127556 | 189.618 | +| 113365 | 187.929 | +| 128362 | 180.535 | +| 129115 | 178.05 | +| 129116 | 175.953 | +| 116213 | 175.926 | +| 129375 | 174.494 | +| 115983 | 174.401 | +| 129367 | 170.092 | +| 67140 | 167.858 | +| 129620 | 161.074 | +| 129380 | 160.591 | +| 129412 | 159.737 | +| 129110 | 159.111 | +| 118814 | 156.493 | +| 124208 | 154.884 | +| 113587 | 154.682 | +| 129616 | 153.533 | +| 129370 | 153.223 | +| 129400 | 148.369 | +| 129053 | 146.551 | +| 129245 | 146.329 | +| 129260 | 145.151 | +| 129403 | 143.564 | +| 129840 | 143.483 | +| 129377 | 141.498 | +| 114285 | 141.314 | +| 129404 | 140.091 | +| 124210 | 137.378 | +| 129615 | 134.145 | +| 113294 | 131.096 | +| 129100 | 128.45 | +| 129060 | 127.547 | +| 129415 | 127.432 | +| 85336 | 126.409 | +| 129237 | 125.256 | +| 129113 | 123.163 | +| 129398 | 120.774 | +| 115741 | 111.975 | +| 129416 | 110.298 | +| 129258 | 109.448 | +| 124206 | 109.087 | +| 129057 | 108.223 | +| 119422 | 108.187 | +| 114537 | 107.263 | +| 124162 | 106.926 | +| 117322 | 105.382 | +| 129096 | 103.318 | +| 129384 | 102.975 | +| 112642 | 100.391 | +| 129108 | 100.196 | +| 129623 | 95.3137 | +| 129050 | 94.8697 | +| 129629 | 92.2779 | +| 129099 | 91.6305 | +| 129111 | 91.0051 | +| 129609 | 89.2835 | +| 129402 | 88.645 | +| 129610 | 88.2107 | +| 129064 | 87.0746 | +| 124207 | 86.1428 | +| 113632 | 85.5615 | +| 115755 | 84.9017 | +| 129371 | 83.1084 | +| 129366 | 82.7434 | +| 127546 | 81.9865 | +| 129401 | 81.1104 | +| 129624 | 80.5582 | +| 129242 | 80.4589 | +| 112636 | 77.6431 | +| 129103 | 75.6127 | +| 129614 | 75.3867 | +| 129106 | 74.9306 | +| 129387 | 73.3656 | +| 113589 | 72.7894 | +| 129396 | 71.4623 | +| 92077 | 70.6436 | +| 129098 | 69.1138 | +| 128819 | 68.0787 | +| 129625 | 66.2291 | +| 129247 | 66.1358 | +| 129255 | 63.9769 | +| 115820 | 61.557 | +| 114289 | 60.2948 | +| 129408 | 59.7946 | +| 128795 | 57.5178 | +| 129621 | 55.2846 | +| 116337 | 55.0093 | +| 129414 | 54.4765 | +| 129065 | 53.9601 | +| 129259 | 52.3702 | +| 124168 | 51.4742 | +| 129627 | 51.1316 | +| 129411 | 50.4361 | +| 129243 | 50.3983 | +| 129097 | 49.6162 | +| 129254 | 48.1486 | +| 129612 | 47.508 | +| 129257 | 40.7716 | +| 124166 | 39.6231 | +| 117668 | 38.2632 | +| 108880 | 38.2148 | +| 124258 | 37.8442 | +| 129054 | 37.1786 | +| 117382 | 33.5436 | +| 129373 | 32.9989 | +| 127537 | 29.4361 | +| 129618 | 26.545 | +| 129253 | 24.0941 | +| 129816 | 20.0367 | +| 129382 | 18.6974 | +| 92984 | 18.466 | ++----------+-----------+ +262 rows in set (0.016 sec) + +Bye diff --git a/doc/plan/db/260915-improve-production/result/after_slow_log.txt b/doc/plan/db/260915-improve-production/result/after_slow_log.txt new file mode 100644 index 0000000..5d8e85f --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/after_slow_log.txt @@ -0,0 +1,6 @@ +start_time query_time rows_examined sql_text +2026-09-15 23:09:13.487911 00:00:04.819872 0 ALTER TABLE best_record \nADD INDEX IF NOT EXISTS idx_maestro_player_app_dt (MaestroID, PlayerID, AppID, RecordDateTime), +2026-09-15 23:09:08.684811 00:00:04.790929 0 ALTER TABLE best_record \nADD INDEX IF NOT EXISTS idx_maestro_app_dt (MaestroID, AppID, RecordDateTime),\nALGORITHM=INPLAC +2026-09-15 23:09:19.466802 00:00:01.063310 0 ALTER TABLE app_highest_record \nADD UNIQUE KEY IF NOT EXISTS uk_maestro_player_app (MaestroID, PlayerID, AppID),\nALGORIT +2026-09-15 23:08:21.126639 00:00:00.929698 935362 DELETE AHR FROM app_highest_record AHR\nJOIN (\n SELECT AppHighestRecordID FROM (\n SELECT AppHighestRecordID,\n +2026-09-15 23:09:18.319470 00:00:00.723314 0 ALTER TABLE typing_exam_record \nADD INDEX IF NOT EXISTS idx_maestro_writing_dt (MaestroID, WritingID, RecordDateTime),\nA diff --git a/doc/plan/db/260915-improve-production/result/analyze_summary.txt b/doc/plan/db/260915-improve-production/result/analyze_summary.txt new file mode 100644 index 0000000..c64c73e --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/analyze_summary.txt @@ -0,0 +1,12 @@ +baseline q1_hour_func server_time_ms=74.61373851 +baseline q2_hour_range server_time_ms=75.58458171 +baseline q3_day_func server_time_ms=75.28739257 +baseline q4_day_range server_time_ms=75.24298373 +baseline q5_month_func server_time_ms=76.26824776 +baseline q6_month_range server_time_ms=79.70009072 +after q1_hour_func server_time_ms=2.069071756 +after q2_hour_range server_time_ms=0.259231588 +after q3_day_func server_time_ms=2.440415655 +after q4_day_range server_time_ms=0.467062948 +after q5_month_func server_time_ms=4.299725667 +after q6_month_range server_time_ms=2.496056728 diff --git a/doc/plan/db/260915-improve-production/result/baseline_analyze.txt b/doc/plan/db/260915-improve-production/result/baseline_analyze.txt new file mode 100644 index 0000000..08dea9b --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/baseline_analyze.txt @@ -0,0 +1,356 @@ +=== q1_hour_func === +{ + "query_optimization": { + "r_total_time_ms": 0.175154857 + }, + "query_block": { + "select_id": 1, + "r_loops": 1, + "r_total_time_ms": 74.61373851, + "nested_loop": [ + { + "read_sorted_file": { + "r_rows": 22, + "filesort": { + "sort_key": "best_record.RecordDateTime, best_record.PlayerID", + "r_loops": 1, + "r_total_time_ms": 74.59065391, + "r_used_priority_queue": false, + "r_output_rows": 22, + "r_buffer_size": "2047Kb", + "r_sort_mode": "sort_key,addon_fields", + "table": { + "table_name": "best_record", + "access_type": "index_merge", + "possible_keys": ["MaestroID", "AppID"], + "key_length": "4,4", + "index_merge": { + "intersect": [ + { + "range": { + "key": "MaestroID", + "used_key_parts": ["MaestroID"] + } + }, + { + "range": { + "key": "AppID", + "used_key_parts": ["AppID"] + } + } + ] + }, + "r_loops": 1, + "rows": 10103, + "r_rows": 5851, + "r_table_time_ms": 15.56706792, + "r_other_time_ms": 52.50187814, + "r_engine_stats": { + "pages_accessed": 17780 + }, + "filtered": 100, + "r_filtered": 0.376004102, + "attached_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and cast(best_record.RecordDateTime as date) = @`day` and hour(best_record.RecordDateTime) = 9" + } + } + } + } + ] + } +} +=== q2_hour_range === +{ + "query_optimization": { + "r_total_time_ms": 0.183126443 + }, + "query_block": { + "select_id": 1, + "r_loops": 1, + "r_total_time_ms": 75.58458171, + "nested_loop": [ + { + "read_sorted_file": { + "r_rows": 22, + "filesort": { + "sort_key": "best_record.RecordDateTime, best_record.PlayerID", + "r_loops": 1, + "r_total_time_ms": 75.56186719, + "r_used_priority_queue": false, + "r_output_rows": 22, + "r_buffer_size": "2047Kb", + "r_sort_mode": "sort_key,addon_fields", + "table": { + "table_name": "best_record", + "access_type": "index_merge", + "possible_keys": ["MaestroID", "AppID"], + "key_length": "4,4", + "index_merge": { + "intersect": [ + { + "range": { + "key": "MaestroID", + "used_key_parts": ["MaestroID"] + } + }, + { + "range": { + "key": "AppID", + "used_key_parts": ["AppID"] + } + } + ] + }, + "r_loops": 1, + "rows": 10103, + "r_rows": 5851, + "r_table_time_ms": 15.67992038, + "r_other_time_ms": 53.82031051, + "r_engine_stats": { + "pages_accessed": 17780 + }, + "filtered": 100, + "r_filtered": 0.376004102, + "attached_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and best_record.RecordDateTime >= '2026-08-26 09:00:00' and best_record.RecordDateTime < '2026-08-26 10:00:00'" + } + } + } + } + ] + } +} +=== q3_day_func === +{ + "query_optimization": { + "r_total_time_ms": 0.205960987 + }, + "query_block": { + "select_id": 1, + "r_loops": 1, + "r_total_time_ms": 75.28739257, + "filesort": { + "sort_key": "max(best_record.BestRecord) desc, best_record.PlayerID", + "r_loops": 1, + "r_total_time_ms": 0.028745721, + "r_used_priority_queue": false, + "r_output_rows": 61, + "r_buffer_size": "1Kb", + "r_sort_mode": "sort_key,rowid", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "best_record", + "access_type": "index_merge", + "possible_keys": ["MaestroID", "AppID"], + "key_length": "4,4", + "index_merge": { + "intersect": [ + { + "range": { + "key": "MaestroID", + "used_key_parts": ["MaestroID"] + } + }, + { + "range": { + "key": "AppID", + "used_key_parts": ["AppID"] + } + } + ] + }, + "r_loops": 1, + "rows": 10103, + "r_rows": 5851, + "r_table_time_ms": 15.68132066, + "r_other_time_ms": 53.54159505, + "r_engine_stats": { + "pages_accessed": 17780 + }, + "filtered": 100, + "r_filtered": 1.692018458, + "attached_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and year(best_record.RecordDateTime) = 2026 and month(best_record.RecordDateTime) = 8 and dayofmonth(best_record.RecordDateTime) = 26" + } + } + ] + } + } + } +} +=== q4_day_range === +{ + "query_optimization": { + "r_total_time_ms": 0.200909982 + }, + "query_block": { + "select_id": 1, + "r_loops": 1, + "r_total_time_ms": 75.24298373, + "filesort": { + "sort_key": "max(best_record.BestRecord) desc, best_record.PlayerID", + "r_loops": 1, + "r_total_time_ms": 0.023154608, + "r_used_priority_queue": false, + "r_output_rows": 61, + "r_buffer_size": "1Kb", + "r_sort_mode": "sort_key,rowid", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "best_record", + "access_type": "index_merge", + "possible_keys": ["MaestroID", "AppID"], + "key_length": "4,4", + "index_merge": { + "intersect": [ + { + "range": { + "key": "MaestroID", + "used_key_parts": ["MaestroID"] + } + }, + { + "range": { + "key": "AppID", + "used_key_parts": ["AppID"] + } + } + ] + }, + "r_loops": 1, + "rows": 10103, + "r_rows": 5851, + "r_table_time_ms": 15.79226274, + "r_other_time_ms": 52.7619799, + "r_engine_stats": { + "pages_accessed": 17780 + }, + "filtered": 100, + "r_filtered": 1.692018458, + "attached_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and best_record.RecordDateTime >= '2026-08-26 00:00:00' and best_record.RecordDateTime < '2026-08-27 00:00:00'" + } + } + ] + } + } + } +} +=== q5_month_func === +{ + "query_optimization": { + "r_total_time_ms": 0.213092406 + }, + "query_block": { + "select_id": 1, + "r_loops": 1, + "r_total_time_ms": 76.26824776, + "filesort": { + "sort_key": "max(best_record.BestRecord) desc, best_record.PlayerID", + "r_loops": 1, + "r_total_time_ms": 0.069783887, + "r_used_priority_queue": false, + "r_output_rows": 262, + "r_buffer_size": "6Kb", + "r_sort_mode": "sort_key,rowid", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "best_record", + "access_type": "index_merge", + "possible_keys": ["MaestroID", "AppID"], + "key_length": "4,4", + "index_merge": { + "intersect": [ + { + "range": { + "key": "MaestroID", + "used_key_parts": ["MaestroID"] + } + }, + { + "range": { + "key": "AppID", + "used_key_parts": ["AppID"] + } + } + ] + }, + "r_loops": 1, + "rows": 10103, + "r_rows": 5851, + "r_table_time_ms": 16.3543846, + "r_other_time_ms": 53.27444188, + "r_engine_stats": { + "pages_accessed": 17780 + }, + "filtered": 100, + "r_filtered": 12.11758674, + "attached_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and year(best_record.RecordDateTime) = 2026 and month(best_record.RecordDateTime) = 8" + } + } + ] + } + } + } +} +=== q6_month_range === +{ + "query_optimization": { + "r_total_time_ms": 0.215072801 + }, + "query_block": { + "select_id": 1, + "r_loops": 1, + "r_total_time_ms": 79.70009072, + "filesort": { + "sort_key": "max(best_record.BestRecord) desc, best_record.PlayerID", + "r_loops": 1, + "r_total_time_ms": 0.072174363, + "r_used_priority_queue": false, + "r_output_rows": 262, + "r_buffer_size": "6Kb", + "r_sort_mode": "sort_key,rowid", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "best_record", + "access_type": "index_merge", + "possible_keys": ["MaestroID", "AppID"], + "key_length": "4,4", + "index_merge": { + "intersect": [ + { + "range": { + "key": "MaestroID", + "used_key_parts": ["MaestroID"] + } + }, + { + "range": { + "key": "AppID", + "used_key_parts": ["AppID"] + } + } + ] + }, + "r_loops": 1, + "rows": 10103, + "r_rows": 5851, + "r_table_time_ms": 19.07379578, + "r_other_time_ms": 54.36322855, + "r_engine_stats": { + "pages_accessed": 17780 + }, + "filtered": 100, + "r_filtered": 12.11758674, + "attached_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and best_record.RecordDateTime >= '2026-08-01 00:00:00' and best_record.RecordDateTime < '2026-09-01 00:00:00'" + } + } + ] + } + } + } +} diff --git a/doc/plan/db/260915-improve-production/result/baseline_analyze_summary.txt b/doc/plan/db/260915-improve-production/result/baseline_analyze_summary.txt new file mode 100644 index 0000000..d2240a3 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/baseline_analyze_summary.txt @@ -0,0 +1,6 @@ +baseline q1_hour_func server_time_ms=74.61373851 +baseline q2_hour_range server_time_ms=75.58458171 +baseline q3_day_func server_time_ms=75.28739257 +baseline q4_day_range server_time_ms=75.24298373 +baseline q5_month_func server_time_ms=76.26824776 +baseline q6_month_range server_time_ms=79.70009072 diff --git a/doc/plan/db/260915-improve-production/result/baseline_explain_q1_hour_func.json b/doc/plan/db/260915-improve-production/result/baseline_explain_q1_hour_func.json new file mode 100644 index 0000000..feac369 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/baseline_explain_q1_hour_func.json @@ -0,0 +1,39 @@ +{ + "query_block": { + "select_id": 1, + "nested_loop": [ + { + "read_sorted_file": { + "filesort": { + "sort_key": "best_record.RecordDateTime, best_record.PlayerID", + "table": { + "table_name": "best_record", + "access_type": "index_merge", + "possible_keys": ["MaestroID", "AppID"], + "key_length": "4,4", + "index_merge": { + "intersect": [ + { + "range": { + "key": "MaestroID", + "used_key_parts": ["MaestroID"] + } + }, + { + "range": { + "key": "AppID", + "used_key_parts": ["AppID"] + } + } + ] + }, + "rows": 10103, + "filtered": 100, + "attached_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and cast(best_record.RecordDateTime as date) = @`day` and hour(best_record.RecordDateTime) = 9" + } + } + } + } + ] + } +} diff --git a/doc/plan/db/260915-improve-production/result/baseline_explain_q2_hour_range.json b/doc/plan/db/260915-improve-production/result/baseline_explain_q2_hour_range.json new file mode 100644 index 0000000..326c56d --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/baseline_explain_q2_hour_range.json @@ -0,0 +1,39 @@ +{ + "query_block": { + "select_id": 1, + "nested_loop": [ + { + "read_sorted_file": { + "filesort": { + "sort_key": "best_record.RecordDateTime, best_record.PlayerID", + "table": { + "table_name": "best_record", + "access_type": "index_merge", + "possible_keys": ["MaestroID", "AppID"], + "key_length": "4,4", + "index_merge": { + "intersect": [ + { + "range": { + "key": "MaestroID", + "used_key_parts": ["MaestroID"] + } + }, + { + "range": { + "key": "AppID", + "used_key_parts": ["AppID"] + } + } + ] + }, + "rows": 10103, + "filtered": 100, + "attached_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and best_record.RecordDateTime >= (cast(@`day` as datetime) + interval @`hour` hour) and best_record.RecordDateTime < (cast(@`day` as datetime) + interval @`hour` + 1 hour)" + } + } + } + } + ] + } +} diff --git a/doc/plan/db/260915-improve-production/result/baseline_explain_q3_day_func.json b/doc/plan/db/260915-improve-production/result/baseline_explain_q3_day_func.json new file mode 100644 index 0000000..64a31cd --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/baseline_explain_q3_day_func.json @@ -0,0 +1,39 @@ +{ + "query_block": { + "select_id": 1, + "filesort": { + "sort_key": "max(best_record.BestRecord) desc, best_record.PlayerID", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "best_record", + "access_type": "index_merge", + "possible_keys": ["MaestroID", "AppID"], + "key_length": "4,4", + "index_merge": { + "intersect": [ + { + "range": { + "key": "MaestroID", + "used_key_parts": ["MaestroID"] + } + }, + { + "range": { + "key": "AppID", + "used_key_parts": ["AppID"] + } + } + ] + }, + "rows": 10103, + "filtered": 100, + "attached_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and year(best_record.RecordDateTime) = 2026 and month(best_record.RecordDateTime) = 8 and dayofmonth(best_record.RecordDateTime) = 26" + } + } + ] + } + } + } +} diff --git a/doc/plan/db/260915-improve-production/result/baseline_explain_q4_day_range.json b/doc/plan/db/260915-improve-production/result/baseline_explain_q4_day_range.json new file mode 100644 index 0000000..89bced0 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/baseline_explain_q4_day_range.json @@ -0,0 +1,39 @@ +{ + "query_block": { + "select_id": 1, + "filesort": { + "sort_key": "max(best_record.BestRecord) desc, best_record.PlayerID", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "best_record", + "access_type": "index_merge", + "possible_keys": ["MaestroID", "AppID"], + "key_length": "4,4", + "index_merge": { + "intersect": [ + { + "range": { + "key": "MaestroID", + "used_key_parts": ["MaestroID"] + } + }, + { + "range": { + "key": "AppID", + "used_key_parts": ["AppID"] + } + } + ] + }, + "rows": 10103, + "filtered": 100, + "attached_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and best_record.RecordDateTime >= (cast(@`day` as datetime)) and best_record.RecordDateTime < (cast(@`day` as datetime) + interval 1 day)" + } + } + ] + } + } + } +} diff --git a/doc/plan/db/260915-improve-production/result/baseline_explain_q5_month_func.json b/doc/plan/db/260915-improve-production/result/baseline_explain_q5_month_func.json new file mode 100644 index 0000000..a92ffc1 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/baseline_explain_q5_month_func.json @@ -0,0 +1,39 @@ +{ + "query_block": { + "select_id": 1, + "filesort": { + "sort_key": "max(best_record.BestRecord) desc, best_record.PlayerID", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "best_record", + "access_type": "index_merge", + "possible_keys": ["MaestroID", "AppID"], + "key_length": "4,4", + "index_merge": { + "intersect": [ + { + "range": { + "key": "MaestroID", + "used_key_parts": ["MaestroID"] + } + }, + { + "range": { + "key": "AppID", + "used_key_parts": ["AppID"] + } + } + ] + }, + "rows": 10103, + "filtered": 100, + "attached_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and year(best_record.RecordDateTime) = 2026 and month(best_record.RecordDateTime) = 8" + } + } + ] + } + } + } +} diff --git a/doc/plan/db/260915-improve-production/result/baseline_explain_q6_month_range.json b/doc/plan/db/260915-improve-production/result/baseline_explain_q6_month_range.json new file mode 100644 index 0000000..b31b258 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/baseline_explain_q6_month_range.json @@ -0,0 +1,39 @@ +{ + "query_block": { + "select_id": 1, + "filesort": { + "sort_key": "max(best_record.BestRecord) desc, best_record.PlayerID", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "best_record", + "access_type": "index_merge", + "possible_keys": ["MaestroID", "AppID"], + "key_length": "4,4", + "index_merge": { + "intersect": [ + { + "range": { + "key": "MaestroID", + "used_key_parts": ["MaestroID"] + } + }, + { + "range": { + "key": "AppID", + "used_key_parts": ["AppID"] + } + } + ] + }, + "rows": 10103, + "filtered": 100, + "attached_condition": "best_record.MaestroID = @maestro and best_record.AppID = @app and best_record.RecordDateTime >= (cast(date_format(@`day`,'%Y-%m-01') as datetime)) and best_record.RecordDateTime < (cast(date_format(@`day`,'%Y-%m-01') as datetime) + interval 1 month)" + } + } + ] + } + } + } +} diff --git a/doc/plan/db/260915-improve-production/result/baseline_explain_summary.txt b/doc/plan/db/260915-improve-production/result/baseline_explain_summary.txt new file mode 100644 index 0000000..6a5d236 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/baseline_explain_summary.txt @@ -0,0 +1,6 @@ +baseline_explain_q1_hour_func.json access_type=index_merge key=MaestroID ∩ AppID rows=10103 +baseline_explain_q2_hour_range.json access_type=index_merge key=MaestroID ∩ AppID rows=10103 +baseline_explain_q3_day_func.json access_type=index_merge key=MaestroID ∩ AppID rows=10103 +baseline_explain_q4_day_range.json access_type=index_merge key=MaestroID ∩ AppID rows=10103 +baseline_explain_q5_month_func.json access_type=index_merge key=MaestroID ∩ AppID rows=10103 +baseline_explain_q6_month_range.json access_type=index_merge key=MaestroID ∩ AppID rows=10103 diff --git a/doc/plan/db/260915-improve-production/result/baseline_perf.txt b/doc/plan/db/260915-improve-production/result/baseline_perf.txt new file mode 100644 index 0000000..3350f1a --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/baseline_perf.txt @@ -0,0 +1,1716 @@ +-------------- +SET @maestro = 181, @app = 21, @day = '2026-08-26', @hour = 9 +-------------- + +Query OK, 0 rows affected (0.012 sec) + +-------------- +SELECT '=== round 1: q1_hour_func ===' AS msg +-------------- + ++-------------------------------+ +| msg | ++-------------------------------+ +| === round 1: q1_hour_func === | ++-------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, BestRecord, RecordDateTime +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND DATE(RecordDateTime) = @day + AND HOUR(RecordDateTime) = @hour +ORDER BY RecordDateTime, PlayerID +-------------- + ++----------+------------+---------------------+ +| PlayerID | BestRecord | RecordDateTime | ++----------+------------+---------------------+ +| 129377 | 141.498 | 2026-08-26 09:12:00 | +| 129368 | 288.908 | 2026-08-26 09:12:05 | +| 129384 | 102.975 | 2026-08-26 09:12:31 | +| 129383 | 281.531 | 2026-08-26 09:13:30 | +| 129375 | 174.494 | 2026-08-26 09:14:10 | +| 129373 | 32.9989 | 2026-08-26 09:14:55 | +| 129376 | 467.553 | 2026-08-26 09:16:58 | +| 129385 | 336.673 | 2026-08-26 09:18:01 | +| 129370 | 153.223 | 2026-08-26 09:18:17 | +| 129371 | 83.1084 | 2026-08-26 09:18:25 | +| 129382 | 18.6974 | 2026-08-26 09:18:53 | +| 129380 | 160.591 | 2026-08-26 09:19:44 | +| 129366 | 82.7434 | 2026-08-26 09:20:10 | +| 129369 | 310.72 | 2026-08-26 09:21:02 | +| 129374 | 425.483 | 2026-08-26 09:21:17 | +| 129387 | 73.3656 | 2026-08-26 09:21:25 | +| 129379 | 548.041 | 2026-08-26 09:23:48 | +| 129378 | 212.914 | 2026-08-26 09:26:35 | +| 129367 | 170.092 | 2026-08-26 09:28:15 | +| 129365 | 772.591 | 2026-08-26 09:29:27 | +| 129386 | 268.392 | 2026-08-26 09:29:58 | +| 129372 | 326.853 | 2026-08-26 09:44:54 | ++----------+------------+---------------------+ +22 rows in set (0.089 sec) + +-------------- +SELECT '=== round 1: q2_hour_range ===' AS msg +-------------- + ++--------------------------------+ +| msg | ++--------------------------------+ +| === round 1: q2_hour_range === | ++--------------------------------+ +1 row in set (0.011 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, BestRecord, RecordDateTime +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(@day AS DATETIME) + INTERVAL @hour HOUR + AND RecordDateTime < CAST(@day AS DATETIME) + INTERVAL (@hour + 1) HOUR +ORDER BY RecordDateTime, PlayerID +-------------- + ++----------+------------+---------------------+ +| PlayerID | BestRecord | RecordDateTime | ++----------+------------+---------------------+ +| 129377 | 141.498 | 2026-08-26 09:12:00 | +| 129368 | 288.908 | 2026-08-26 09:12:05 | +| 129384 | 102.975 | 2026-08-26 09:12:31 | +| 129383 | 281.531 | 2026-08-26 09:13:30 | +| 129375 | 174.494 | 2026-08-26 09:14:10 | +| 129373 | 32.9989 | 2026-08-26 09:14:55 | +| 129376 | 467.553 | 2026-08-26 09:16:58 | +| 129385 | 336.673 | 2026-08-26 09:18:01 | +| 129370 | 153.223 | 2026-08-26 09:18:17 | +| 129371 | 83.1084 | 2026-08-26 09:18:25 | +| 129382 | 18.6974 | 2026-08-26 09:18:53 | +| 129380 | 160.591 | 2026-08-26 09:19:44 | +| 129366 | 82.7434 | 2026-08-26 09:20:10 | +| 129369 | 310.72 | 2026-08-26 09:21:02 | +| 129374 | 425.483 | 2026-08-26 09:21:17 | +| 129387 | 73.3656 | 2026-08-26 09:21:25 | +| 129379 | 548.041 | 2026-08-26 09:23:48 | +| 129378 | 212.914 | 2026-08-26 09:26:35 | +| 129367 | 170.092 | 2026-08-26 09:28:15 | +| 129365 | 772.591 | 2026-08-26 09:29:27 | +| 129386 | 268.392 | 2026-08-26 09:29:58 | +| 129372 | 326.853 | 2026-08-26 09:44:54 | ++----------+------------+---------------------+ +22 rows in set (0.090 sec) + +-------------- +SELECT '=== round 1: q3_day_func ===' AS msg +-------------- + ++------------------------------+ +| msg | ++------------------------------+ +| === round 1: q3_day_func === | ++------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND YEAR(RecordDateTime) = YEAR(@day) + AND MONTH(RecordDateTime) = MONTH(@day) + AND DAYOFMONTH(RecordDateTime) = DAYOFMONTH(@day) +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID +-------------- + ++----------+-----------+ +| PlayerID | HighScore | ++----------+-----------+ +| 127555 | 930.714 | +| 115731 | 904.198 | +| 129365 | 772.591 | +| 115748 | 690.39 | +| 129485 | 582.42 | +| 129379 | 548.041 | +| 129250 | 515.021 | +| 129376 | 467.553 | +| 128818 | 442.442 | +| 129417 | 433.996 | +| 129374 | 425.483 | +| 129372 | 424.086 | +| 128820 | 358.362 | +| 115727 | 349.82 | +| 129406 | 348.801 | +| 129385 | 336.673 | +| 129369 | 310.72 | +| 128822 | 294.813 | +| 50960 | 291.092 | +| 129368 | 288.908 | +| 115728 | 285.48 | +| 129383 | 281.531 | +| 115737 | 279.923 | +| 129418 | 274.516 | +| 128824 | 270.27 | +| 129386 | 268.392 | +| 129407 | 257.985 | +| 129251 | 255.102 | +| 129413 | 238.507 | +| 92080 | 233.291 | +| 129410 | 220.016 | +| 129397 | 213.707 | +| 129378 | 212.914 | +| 128825 | 192.552 | +| 129375 | 174.494 | +| 129367 | 170.092 | +| 129380 | 160.591 | +| 129412 | 159.737 | +| 129370 | 153.223 | +| 129400 | 148.369 | +| 129403 | 143.564 | +| 129377 | 141.498 | +| 129404 | 140.091 | +| 129415 | 127.432 | +| 129398 | 120.774 | +| 129416 | 110.298 | +| 129384 | 102.975 | +| 129402 | 88.645 | +| 115755 | 84.9017 | +| 129371 | 83.1084 | +| 129366 | 82.7434 | +| 129401 | 81.1104 | +| 129387 | 73.3656 | +| 129396 | 71.4623 | +| 128819 | 68.0787 | +| 129408 | 59.7946 | +| 128795 | 57.5178 | +| 129414 | 54.4765 | +| 129411 | 50.4361 | +| 129373 | 32.9989 | +| 129382 | 18.6974 | ++----------+-----------+ +61 rows in set (0.090 sec) + +-------------- +SELECT '=== round 1: q4_day_range ===' AS msg +-------------- + ++-------------------------------+ +| msg | ++-------------------------------+ +| === round 1: q4_day_range === | ++-------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(@day AS DATETIME) + AND RecordDateTime < CAST(@day AS DATETIME) + INTERVAL 1 DAY +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID +-------------- + ++----------+-----------+ +| PlayerID | HighScore | ++----------+-----------+ +| 127555 | 930.714 | +| 115731 | 904.198 | +| 129365 | 772.591 | +| 115748 | 690.39 | +| 129485 | 582.42 | +| 129379 | 548.041 | +| 129250 | 515.021 | +| 129376 | 467.553 | +| 128818 | 442.442 | +| 129417 | 433.996 | +| 129374 | 425.483 | +| 129372 | 424.086 | +| 128820 | 358.362 | +| 115727 | 349.82 | +| 129406 | 348.801 | +| 129385 | 336.673 | +| 129369 | 310.72 | +| 128822 | 294.813 | +| 50960 | 291.092 | +| 129368 | 288.908 | +| 115728 | 285.48 | +| 129383 | 281.531 | +| 115737 | 279.923 | +| 129418 | 274.516 | +| 128824 | 270.27 | +| 129386 | 268.392 | +| 129407 | 257.985 | +| 129251 | 255.102 | +| 129413 | 238.507 | +| 92080 | 233.291 | +| 129410 | 220.016 | +| 129397 | 213.707 | +| 129378 | 212.914 | +| 128825 | 192.552 | +| 129375 | 174.494 | +| 129367 | 170.092 | +| 129380 | 160.591 | +| 129412 | 159.737 | +| 129370 | 153.223 | +| 129400 | 148.369 | +| 129403 | 143.564 | +| 129377 | 141.498 | +| 129404 | 140.091 | +| 129415 | 127.432 | +| 129398 | 120.774 | +| 129416 | 110.298 | +| 129384 | 102.975 | +| 129402 | 88.645 | +| 115755 | 84.9017 | +| 129371 | 83.1084 | +| 129366 | 82.7434 | +| 129401 | 81.1104 | +| 129387 | 73.3656 | +| 129396 | 71.4623 | +| 128819 | 68.0787 | +| 129408 | 59.7946 | +| 128795 | 57.5178 | +| 129414 | 54.4765 | +| 129411 | 50.4361 | +| 129373 | 32.9989 | +| 129382 | 18.6974 | ++----------+-----------+ +61 rows in set (0.088 sec) + +-------------- +SELECT '=== round 1: q5_month_func ===' AS msg +-------------- + ++--------------------------------+ +| msg | ++--------------------------------+ +| === round 1: q5_month_func === | ++--------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND YEAR(RecordDateTime) = YEAR(@day) + AND MONTH(RecordDateTime) = MONTH(@day) +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID +-------------- + ++----------+-----------+ +| PlayerID | HighScore | ++----------+-----------+ +| 34820 | 2001.19 | +| 112638 | 1952.35 | +| 113366 | 1863.35 | +| 112641 | 1763.78 | +| 92104 | 1217.8 | +| 113361 | 1082.21 | +| 124027 | 1059.27 | +| 124029 | 1011.24 | +| 122149 | 1007.8 | +| 127555 | 968.61 | +| 113588 | 950.495 | +| 55094 | 930.788 | +| 115731 | 904.198 | +| 121713 | 893.079 | +| 113298 | 884.146 | +| 113358 | 866.426 | +| 55093 | 862.026 | +| 129613 | 861.538 | +| 129611 | 841.894 | +| 129048 | 812.515 | +| 113624 | 809.535 | +| 124030 | 808.738 | +| 46710 | 804.252 | +| 112635 | 779.046 | +| 56775 | 778.698 | +| 129365 | 772.591 | +| 85486 | 745.011 | +| 113296 | 744.186 | +| 92674 | 728.948 | +| 54456 | 726.969 | +| 113362 | 712.677 | +| 129059 | 702.972 | +| 55083 | 697.975 | +| 115748 | 690.39 | +| 113634 | 685.967 | +| 129102 | 685.564 | +| 129256 | 673.212 | +| 129248 | 658.979 | +| 117340 | 656.891 | +| 82793 | 649.689 | +| 124120 | 641.483 | +| 129617 | 638.026 | +| 34049 | 636.484 | +| 114278 | 634.321 | +| 129056 | 632.356 | +| 97042 | 628.272 | +| 113297 | 615.506 | +| 114280 | 602.806 | +| 128820 | 584.348 | +| 129485 | 582.42 | +| 116203 | 581.357 | +| 117666 | 574.346 | +| 104129 | 573.431 | +| 113633 | 555.923 | +| 129376 | 555.097 | +| 120446 | 554.187 | +| 129244 | 552.741 | +| 129062 | 549.367 | +| 129379 | 548.041 | +| 114283 | 544.218 | +| 50960 | 543.074 | +| 116208 | 540.121 | +| 128818 | 533.672 | +| 129067 | 528.548 | +| 117316 | 523.283 | +| 129261 | 520.043 | +| 129250 | 515.021 | +| 112634 | 512.899 | +| 129873 | 512.747 | +| 55082 | 510.561 | +| 92079 | 504.123 | +| 124511 | 502.403 | +| 115727 | 501.008 | +| 92106 | 499.139 | +| 124509 | 483.871 | +| 92080 | 468.411 | +| 124205 | 466.926 | +| 129622 | 466.744 | +| 113629 | 455.779 | +| 115728 | 454.545 | +| 128825 | 451.191 | +| 117312 | 439.394 | +| 129238 | 438.596 | +| 127557 | 434.389 | +| 129417 | 433.996 | +| 129374 | 425.483 | +| 129372 | 424.086 | +| 128821 | 412.225 | +| 129061 | 405.314 | +| 113638 | 404.267 | +| 113357 | 403.286 | +| 129105 | 400.757 | +| 114291 | 397.76 | +| 92082 | 395.859 | +| 129109 | 384.871 | +| 114282 | 381.985 | +| 115737 | 380.684 | +| 129107 | 373.918 | +| 129114 | 371.353 | +| 124484 | 368.894 | +| 113299 | 349.418 | +| 129406 | 348.801 | +| 108963 | 347.896 | +| 129385 | 336.673 | +| 113355 | 333.83 | +| 110901 | 327.69 | +| 113590 | 324.445 | +| 129150 | 324.356 | +| 113630 | 319.266 | +| 128824 | 314.136 | +| 104131 | 310.795 | +| 129369 | 310.72 | +| 129055 | 300.617 | +| 129101 | 300.518 | +| 128822 | 294.813 | +| 124581 | 291.524 | +| 87632 | 289.71 | +| 129368 | 288.908 | +| 113631 | 287.2 | +| 129383 | 281.531 | +| 114290 | 274.779 | +| 129418 | 274.516 | +| 129386 | 268.392 | +| 127554 | 265.487 | +| 129252 | 258.491 | +| 129407 | 257.985 | +| 117381 | 255.494 | +| 129251 | 255.102 | +| 113675 | 254.118 | +| 113359 | 251.451 | +| 124209 | 248.521 | +| 129626 | 246.643 | +| 129413 | 238.507 | +| 67147 | 236.255 | +| 115984 | 231.784 | +| 129063 | 229.314 | +| 127558 | 225.389 | +| 113637 | 225.336 | +| 129410 | 220.016 | +| 111357 | 216.854 | +| 129397 | 213.707 | +| 129378 | 212.914 | +| 114540 | 209.505 | +| 102619 | 208.709 | +| 129052 | 206.831 | +| 129058 | 202.326 | +| 115985 | 199.018 | +| 129240 | 197.102 | +| 129049 | 196.652 | +| 129051 | 190.747 | +| 127556 | 189.618 | +| 113365 | 187.929 | +| 128362 | 180.535 | +| 129115 | 178.05 | +| 129116 | 175.953 | +| 116213 | 175.926 | +| 129375 | 174.494 | +| 115983 | 174.401 | +| 129367 | 170.092 | +| 67140 | 167.858 | +| 129620 | 161.074 | +| 129380 | 160.591 | +| 129412 | 159.737 | +| 129110 | 159.111 | +| 118814 | 156.493 | +| 124208 | 154.884 | +| 113587 | 154.682 | +| 129616 | 153.533 | +| 129370 | 153.223 | +| 129400 | 148.369 | +| 129053 | 146.551 | +| 129245 | 146.329 | +| 129260 | 145.151 | +| 129403 | 143.564 | +| 129840 | 143.483 | +| 129377 | 141.498 | +| 114285 | 141.314 | +| 129404 | 140.091 | +| 124210 | 137.378 | +| 129615 | 134.145 | +| 113294 | 131.096 | +| 129100 | 128.45 | +| 129060 | 127.547 | +| 129415 | 127.432 | +| 85336 | 126.409 | +| 129237 | 125.256 | +| 129113 | 123.163 | +| 129398 | 120.774 | +| 115741 | 111.975 | +| 129416 | 110.298 | +| 129258 | 109.448 | +| 124206 | 109.087 | +| 129057 | 108.223 | +| 119422 | 108.187 | +| 114537 | 107.263 | +| 124162 | 106.926 | +| 117322 | 105.382 | +| 129096 | 103.318 | +| 129384 | 102.975 | +| 112642 | 100.391 | +| 129108 | 100.196 | +| 129623 | 95.3137 | +| 129050 | 94.8697 | +| 129629 | 92.2779 | +| 129099 | 91.6305 | +| 129111 | 91.0051 | +| 129609 | 89.2835 | +| 129402 | 88.645 | +| 129610 | 88.2107 | +| 129064 | 87.0746 | +| 124207 | 86.1428 | +| 113632 | 85.5615 | +| 115755 | 84.9017 | +| 129371 | 83.1084 | +| 129366 | 82.7434 | +| 127546 | 81.9865 | +| 129401 | 81.1104 | +| 129624 | 80.5582 | +| 129242 | 80.4589 | +| 112636 | 77.6431 | +| 129103 | 75.6127 | +| 129614 | 75.3867 | +| 129106 | 74.9306 | +| 129387 | 73.3656 | +| 113589 | 72.7894 | +| 129396 | 71.4623 | +| 92077 | 70.6436 | +| 129098 | 69.1138 | +| 128819 | 68.0787 | +| 129625 | 66.2291 | +| 129247 | 66.1358 | +| 129255 | 63.9769 | +| 115820 | 61.557 | +| 114289 | 60.2948 | +| 129408 | 59.7946 | +| 128795 | 57.5178 | +| 129621 | 55.2846 | +| 116337 | 55.0093 | +| 129414 | 54.4765 | +| 129065 | 53.9601 | +| 129259 | 52.3702 | +| 124168 | 51.4742 | +| 129627 | 51.1316 | +| 129411 | 50.4361 | +| 129243 | 50.3983 | +| 129097 | 49.6162 | +| 129254 | 48.1486 | +| 129612 | 47.508 | +| 129257 | 40.7716 | +| 124166 | 39.6231 | +| 117668 | 38.2632 | +| 108880 | 38.2148 | +| 124258 | 37.8442 | +| 129054 | 37.1786 | +| 117382 | 33.5436 | +| 129373 | 32.9989 | +| 127537 | 29.4361 | +| 129618 | 26.545 | +| 129253 | 24.0941 | +| 129816 | 20.0367 | +| 129382 | 18.6974 | +| 92984 | 18.466 | ++----------+-----------+ +262 rows in set (0.087 sec) + +-------------- +SELECT '=== round 1: q6_month_range ===' AS msg +-------------- + ++---------------------------------+ +| msg | ++---------------------------------+ +| === round 1: q6_month_range === | ++---------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(DATE_FORMAT(@day, '%Y-%m-01') AS DATETIME) + AND RecordDateTime < CAST(DATE_FORMAT(@day, '%Y-%m-01') AS DATETIME) + INTERVAL 1 MONTH +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID +-------------- + ++----------+-----------+ +| PlayerID | HighScore | ++----------+-----------+ +| 34820 | 2001.19 | +| 112638 | 1952.35 | +| 113366 | 1863.35 | +| 112641 | 1763.78 | +| 92104 | 1217.8 | +| 113361 | 1082.21 | +| 124027 | 1059.27 | +| 124029 | 1011.24 | +| 122149 | 1007.8 | +| 127555 | 968.61 | +| 113588 | 950.495 | +| 55094 | 930.788 | +| 115731 | 904.198 | +| 121713 | 893.079 | +| 113298 | 884.146 | +| 113358 | 866.426 | +| 55093 | 862.026 | +| 129613 | 861.538 | +| 129611 | 841.894 | +| 129048 | 812.515 | +| 113624 | 809.535 | +| 124030 | 808.738 | +| 46710 | 804.252 | +| 112635 | 779.046 | +| 56775 | 778.698 | +| 129365 | 772.591 | +| 85486 | 745.011 | +| 113296 | 744.186 | +| 92674 | 728.948 | +| 54456 | 726.969 | +| 113362 | 712.677 | +| 129059 | 702.972 | +| 55083 | 697.975 | +| 115748 | 690.39 | +| 113634 | 685.967 | +| 129102 | 685.564 | +| 129256 | 673.212 | +| 129248 | 658.979 | +| 117340 | 656.891 | +| 82793 | 649.689 | +| 124120 | 641.483 | +| 129617 | 638.026 | +| 34049 | 636.484 | +| 114278 | 634.321 | +| 129056 | 632.356 | +| 97042 | 628.272 | +| 113297 | 615.506 | +| 114280 | 602.806 | +| 128820 | 584.348 | +| 129485 | 582.42 | +| 116203 | 581.357 | +| 117666 | 574.346 | +| 104129 | 573.431 | +| 113633 | 555.923 | +| 129376 | 555.097 | +| 120446 | 554.187 | +| 129244 | 552.741 | +| 129062 | 549.367 | +| 129379 | 548.041 | +| 114283 | 544.218 | +| 50960 | 543.074 | +| 116208 | 540.121 | +| 128818 | 533.672 | +| 129067 | 528.548 | +| 117316 | 523.283 | +| 129261 | 520.043 | +| 129250 | 515.021 | +| 112634 | 512.899 | +| 129873 | 512.747 | +| 55082 | 510.561 | +| 92079 | 504.123 | +| 124511 | 502.403 | +| 115727 | 501.008 | +| 92106 | 499.139 | +| 124509 | 483.871 | +| 92080 | 468.411 | +| 124205 | 466.926 | +| 129622 | 466.744 | +| 113629 | 455.779 | +| 115728 | 454.545 | +| 128825 | 451.191 | +| 117312 | 439.394 | +| 129238 | 438.596 | +| 127557 | 434.389 | +| 129417 | 433.996 | +| 129374 | 425.483 | +| 129372 | 424.086 | +| 128821 | 412.225 | +| 129061 | 405.314 | +| 113638 | 404.267 | +| 113357 | 403.286 | +| 129105 | 400.757 | +| 114291 | 397.76 | +| 92082 | 395.859 | +| 129109 | 384.871 | +| 114282 | 381.985 | +| 115737 | 380.684 | +| 129107 | 373.918 | +| 129114 | 371.353 | +| 124484 | 368.894 | +| 113299 | 349.418 | +| 129406 | 348.801 | +| 108963 | 347.896 | +| 129385 | 336.673 | +| 113355 | 333.83 | +| 110901 | 327.69 | +| 113590 | 324.445 | +| 129150 | 324.356 | +| 113630 | 319.266 | +| 128824 | 314.136 | +| 104131 | 310.795 | +| 129369 | 310.72 | +| 129055 | 300.617 | +| 129101 | 300.518 | +| 128822 | 294.813 | +| 124581 | 291.524 | +| 87632 | 289.71 | +| 129368 | 288.908 | +| 113631 | 287.2 | +| 129383 | 281.531 | +| 114290 | 274.779 | +| 129418 | 274.516 | +| 129386 | 268.392 | +| 127554 | 265.487 | +| 129252 | 258.491 | +| 129407 | 257.985 | +| 117381 | 255.494 | +| 129251 | 255.102 | +| 113675 | 254.118 | +| 113359 | 251.451 | +| 124209 | 248.521 | +| 129626 | 246.643 | +| 129413 | 238.507 | +| 67147 | 236.255 | +| 115984 | 231.784 | +| 129063 | 229.314 | +| 127558 | 225.389 | +| 113637 | 225.336 | +| 129410 | 220.016 | +| 111357 | 216.854 | +| 129397 | 213.707 | +| 129378 | 212.914 | +| 114540 | 209.505 | +| 102619 | 208.709 | +| 129052 | 206.831 | +| 129058 | 202.326 | +| 115985 | 199.018 | +| 129240 | 197.102 | +| 129049 | 196.652 | +| 129051 | 190.747 | +| 127556 | 189.618 | +| 113365 | 187.929 | +| 128362 | 180.535 | +| 129115 | 178.05 | +| 129116 | 175.953 | +| 116213 | 175.926 | +| 129375 | 174.494 | +| 115983 | 174.401 | +| 129367 | 170.092 | +| 67140 | 167.858 | +| 129620 | 161.074 | +| 129380 | 160.591 | +| 129412 | 159.737 | +| 129110 | 159.111 | +| 118814 | 156.493 | +| 124208 | 154.884 | +| 113587 | 154.682 | +| 129616 | 153.533 | +| 129370 | 153.223 | +| 129400 | 148.369 | +| 129053 | 146.551 | +| 129245 | 146.329 | +| 129260 | 145.151 | +| 129403 | 143.564 | +| 129840 | 143.483 | +| 129377 | 141.498 | +| 114285 | 141.314 | +| 129404 | 140.091 | +| 124210 | 137.378 | +| 129615 | 134.145 | +| 113294 | 131.096 | +| 129100 | 128.45 | +| 129060 | 127.547 | +| 129415 | 127.432 | +| 85336 | 126.409 | +| 129237 | 125.256 | +| 129113 | 123.163 | +| 129398 | 120.774 | +| 115741 | 111.975 | +| 129416 | 110.298 | +| 129258 | 109.448 | +| 124206 | 109.087 | +| 129057 | 108.223 | +| 119422 | 108.187 | +| 114537 | 107.263 | +| 124162 | 106.926 | +| 117322 | 105.382 | +| 129096 | 103.318 | +| 129384 | 102.975 | +| 112642 | 100.391 | +| 129108 | 100.196 | +| 129623 | 95.3137 | +| 129050 | 94.8697 | +| 129629 | 92.2779 | +| 129099 | 91.6305 | +| 129111 | 91.0051 | +| 129609 | 89.2835 | +| 129402 | 88.645 | +| 129610 | 88.2107 | +| 129064 | 87.0746 | +| 124207 | 86.1428 | +| 113632 | 85.5615 | +| 115755 | 84.9017 | +| 129371 | 83.1084 | +| 129366 | 82.7434 | +| 127546 | 81.9865 | +| 129401 | 81.1104 | +| 129624 | 80.5582 | +| 129242 | 80.4589 | +| 112636 | 77.6431 | +| 129103 | 75.6127 | +| 129614 | 75.3867 | +| 129106 | 74.9306 | +| 129387 | 73.3656 | +| 113589 | 72.7894 | +| 129396 | 71.4623 | +| 92077 | 70.6436 | +| 129098 | 69.1138 | +| 128819 | 68.0787 | +| 129625 | 66.2291 | +| 129247 | 66.1358 | +| 129255 | 63.9769 | +| 115820 | 61.557 | +| 114289 | 60.2948 | +| 129408 | 59.7946 | +| 128795 | 57.5178 | +| 129621 | 55.2846 | +| 116337 | 55.0093 | +| 129414 | 54.4765 | +| 129065 | 53.9601 | +| 129259 | 52.3702 | +| 124168 | 51.4742 | +| 129627 | 51.1316 | +| 129411 | 50.4361 | +| 129243 | 50.3983 | +| 129097 | 49.6162 | +| 129254 | 48.1486 | +| 129612 | 47.508 | +| 129257 | 40.7716 | +| 124166 | 39.6231 | +| 117668 | 38.2632 | +| 108880 | 38.2148 | +| 124258 | 37.8442 | +| 129054 | 37.1786 | +| 117382 | 33.5436 | +| 129373 | 32.9989 | +| 127537 | 29.4361 | +| 129618 | 26.545 | +| 129253 | 24.0941 | +| 129816 | 20.0367 | +| 129382 | 18.6974 | +| 92984 | 18.466 | ++----------+-----------+ +262 rows in set (0.087 sec) + +Bye +-------------- +SET @maestro = 181, @app = 21, @day = '2026-08-26', @hour = 9 +-------------- + +Query OK, 0 rows affected (0.012 sec) + +-------------- +SELECT '=== round 2: q1_hour_func ===' AS msg +-------------- + ++-------------------------------+ +| msg | ++-------------------------------+ +| === round 2: q1_hour_func === | ++-------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, BestRecord, RecordDateTime +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND DATE(RecordDateTime) = @day + AND HOUR(RecordDateTime) = @hour +ORDER BY RecordDateTime, PlayerID +-------------- + ++----------+------------+---------------------+ +| PlayerID | BestRecord | RecordDateTime | ++----------+------------+---------------------+ +| 129377 | 141.498 | 2026-08-26 09:12:00 | +| 129368 | 288.908 | 2026-08-26 09:12:05 | +| 129384 | 102.975 | 2026-08-26 09:12:31 | +| 129383 | 281.531 | 2026-08-26 09:13:30 | +| 129375 | 174.494 | 2026-08-26 09:14:10 | +| 129373 | 32.9989 | 2026-08-26 09:14:55 | +| 129376 | 467.553 | 2026-08-26 09:16:58 | +| 129385 | 336.673 | 2026-08-26 09:18:01 | +| 129370 | 153.223 | 2026-08-26 09:18:17 | +| 129371 | 83.1084 | 2026-08-26 09:18:25 | +| 129382 | 18.6974 | 2026-08-26 09:18:53 | +| 129380 | 160.591 | 2026-08-26 09:19:44 | +| 129366 | 82.7434 | 2026-08-26 09:20:10 | +| 129369 | 310.72 | 2026-08-26 09:21:02 | +| 129374 | 425.483 | 2026-08-26 09:21:17 | +| 129387 | 73.3656 | 2026-08-26 09:21:25 | +| 129379 | 548.041 | 2026-08-26 09:23:48 | +| 129378 | 212.914 | 2026-08-26 09:26:35 | +| 129367 | 170.092 | 2026-08-26 09:28:15 | +| 129365 | 772.591 | 2026-08-26 09:29:27 | +| 129386 | 268.392 | 2026-08-26 09:29:58 | +| 129372 | 326.853 | 2026-08-26 09:44:54 | ++----------+------------+---------------------+ +22 rows in set (0.089 sec) + +-------------- +SELECT '=== round 2: q2_hour_range ===' AS msg +-------------- + ++--------------------------------+ +| msg | ++--------------------------------+ +| === round 2: q2_hour_range === | ++--------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, BestRecord, RecordDateTime +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(@day AS DATETIME) + INTERVAL @hour HOUR + AND RecordDateTime < CAST(@day AS DATETIME) + INTERVAL (@hour + 1) HOUR +ORDER BY RecordDateTime, PlayerID +-------------- + ++----------+------------+---------------------+ +| PlayerID | BestRecord | RecordDateTime | ++----------+------------+---------------------+ +| 129377 | 141.498 | 2026-08-26 09:12:00 | +| 129368 | 288.908 | 2026-08-26 09:12:05 | +| 129384 | 102.975 | 2026-08-26 09:12:31 | +| 129383 | 281.531 | 2026-08-26 09:13:30 | +| 129375 | 174.494 | 2026-08-26 09:14:10 | +| 129373 | 32.9989 | 2026-08-26 09:14:55 | +| 129376 | 467.553 | 2026-08-26 09:16:58 | +| 129385 | 336.673 | 2026-08-26 09:18:01 | +| 129370 | 153.223 | 2026-08-26 09:18:17 | +| 129371 | 83.1084 | 2026-08-26 09:18:25 | +| 129382 | 18.6974 | 2026-08-26 09:18:53 | +| 129380 | 160.591 | 2026-08-26 09:19:44 | +| 129366 | 82.7434 | 2026-08-26 09:20:10 | +| 129369 | 310.72 | 2026-08-26 09:21:02 | +| 129374 | 425.483 | 2026-08-26 09:21:17 | +| 129387 | 73.3656 | 2026-08-26 09:21:25 | +| 129379 | 548.041 | 2026-08-26 09:23:48 | +| 129378 | 212.914 | 2026-08-26 09:26:35 | +| 129367 | 170.092 | 2026-08-26 09:28:15 | +| 129365 | 772.591 | 2026-08-26 09:29:27 | +| 129386 | 268.392 | 2026-08-26 09:29:58 | +| 129372 | 326.853 | 2026-08-26 09:44:54 | ++----------+------------+---------------------+ +22 rows in set (0.088 sec) + +-------------- +SELECT '=== round 2: q3_day_func ===' AS msg +-------------- + ++------------------------------+ +| msg | ++------------------------------+ +| === round 2: q3_day_func === | ++------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND YEAR(RecordDateTime) = YEAR(@day) + AND MONTH(RecordDateTime) = MONTH(@day) + AND DAYOFMONTH(RecordDateTime) = DAYOFMONTH(@day) +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID +-------------- + ++----------+-----------+ +| PlayerID | HighScore | ++----------+-----------+ +| 127555 | 930.714 | +| 115731 | 904.198 | +| 129365 | 772.591 | +| 115748 | 690.39 | +| 129485 | 582.42 | +| 129379 | 548.041 | +| 129250 | 515.021 | +| 129376 | 467.553 | +| 128818 | 442.442 | +| 129417 | 433.996 | +| 129374 | 425.483 | +| 129372 | 424.086 | +| 128820 | 358.362 | +| 115727 | 349.82 | +| 129406 | 348.801 | +| 129385 | 336.673 | +| 129369 | 310.72 | +| 128822 | 294.813 | +| 50960 | 291.092 | +| 129368 | 288.908 | +| 115728 | 285.48 | +| 129383 | 281.531 | +| 115737 | 279.923 | +| 129418 | 274.516 | +| 128824 | 270.27 | +| 129386 | 268.392 | +| 129407 | 257.985 | +| 129251 | 255.102 | +| 129413 | 238.507 | +| 92080 | 233.291 | +| 129410 | 220.016 | +| 129397 | 213.707 | +| 129378 | 212.914 | +| 128825 | 192.552 | +| 129375 | 174.494 | +| 129367 | 170.092 | +| 129380 | 160.591 | +| 129412 | 159.737 | +| 129370 | 153.223 | +| 129400 | 148.369 | +| 129403 | 143.564 | +| 129377 | 141.498 | +| 129404 | 140.091 | +| 129415 | 127.432 | +| 129398 | 120.774 | +| 129416 | 110.298 | +| 129384 | 102.975 | +| 129402 | 88.645 | +| 115755 | 84.9017 | +| 129371 | 83.1084 | +| 129366 | 82.7434 | +| 129401 | 81.1104 | +| 129387 | 73.3656 | +| 129396 | 71.4623 | +| 128819 | 68.0787 | +| 129408 | 59.7946 | +| 128795 | 57.5178 | +| 129414 | 54.4765 | +| 129411 | 50.4361 | +| 129373 | 32.9989 | +| 129382 | 18.6974 | ++----------+-----------+ +61 rows in set (0.091 sec) + +-------------- +SELECT '=== round 2: q4_day_range ===' AS msg +-------------- + ++-------------------------------+ +| msg | ++-------------------------------+ +| === round 2: q4_day_range === | ++-------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(@day AS DATETIME) + AND RecordDateTime < CAST(@day AS DATETIME) + INTERVAL 1 DAY +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID +-------------- + ++----------+-----------+ +| PlayerID | HighScore | ++----------+-----------+ +| 127555 | 930.714 | +| 115731 | 904.198 | +| 129365 | 772.591 | +| 115748 | 690.39 | +| 129485 | 582.42 | +| 129379 | 548.041 | +| 129250 | 515.021 | +| 129376 | 467.553 | +| 128818 | 442.442 | +| 129417 | 433.996 | +| 129374 | 425.483 | +| 129372 | 424.086 | +| 128820 | 358.362 | +| 115727 | 349.82 | +| 129406 | 348.801 | +| 129385 | 336.673 | +| 129369 | 310.72 | +| 128822 | 294.813 | +| 50960 | 291.092 | +| 129368 | 288.908 | +| 115728 | 285.48 | +| 129383 | 281.531 | +| 115737 | 279.923 | +| 129418 | 274.516 | +| 128824 | 270.27 | +| 129386 | 268.392 | +| 129407 | 257.985 | +| 129251 | 255.102 | +| 129413 | 238.507 | +| 92080 | 233.291 | +| 129410 | 220.016 | +| 129397 | 213.707 | +| 129378 | 212.914 | +| 128825 | 192.552 | +| 129375 | 174.494 | +| 129367 | 170.092 | +| 129380 | 160.591 | +| 129412 | 159.737 | +| 129370 | 153.223 | +| 129400 | 148.369 | +| 129403 | 143.564 | +| 129377 | 141.498 | +| 129404 | 140.091 | +| 129415 | 127.432 | +| 129398 | 120.774 | +| 129416 | 110.298 | +| 129384 | 102.975 | +| 129402 | 88.645 | +| 115755 | 84.9017 | +| 129371 | 83.1084 | +| 129366 | 82.7434 | +| 129401 | 81.1104 | +| 129387 | 73.3656 | +| 129396 | 71.4623 | +| 128819 | 68.0787 | +| 129408 | 59.7946 | +| 128795 | 57.5178 | +| 129414 | 54.4765 | +| 129411 | 50.4361 | +| 129373 | 32.9989 | +| 129382 | 18.6974 | ++----------+-----------+ +61 rows in set (0.090 sec) + +-------------- +SELECT '=== round 2: q5_month_func ===' AS msg +-------------- + ++--------------------------------+ +| msg | ++--------------------------------+ +| === round 2: q5_month_func === | ++--------------------------------+ +1 row in set (0.011 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND YEAR(RecordDateTime) = YEAR(@day) + AND MONTH(RecordDateTime) = MONTH(@day) +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID +-------------- + ++----------+-----------+ +| PlayerID | HighScore | ++----------+-----------+ +| 34820 | 2001.19 | +| 112638 | 1952.35 | +| 113366 | 1863.35 | +| 112641 | 1763.78 | +| 92104 | 1217.8 | +| 113361 | 1082.21 | +| 124027 | 1059.27 | +| 124029 | 1011.24 | +| 122149 | 1007.8 | +| 127555 | 968.61 | +| 113588 | 950.495 | +| 55094 | 930.788 | +| 115731 | 904.198 | +| 121713 | 893.079 | +| 113298 | 884.146 | +| 113358 | 866.426 | +| 55093 | 862.026 | +| 129613 | 861.538 | +| 129611 | 841.894 | +| 129048 | 812.515 | +| 113624 | 809.535 | +| 124030 | 808.738 | +| 46710 | 804.252 | +| 112635 | 779.046 | +| 56775 | 778.698 | +| 129365 | 772.591 | +| 85486 | 745.011 | +| 113296 | 744.186 | +| 92674 | 728.948 | +| 54456 | 726.969 | +| 113362 | 712.677 | +| 129059 | 702.972 | +| 55083 | 697.975 | +| 115748 | 690.39 | +| 113634 | 685.967 | +| 129102 | 685.564 | +| 129256 | 673.212 | +| 129248 | 658.979 | +| 117340 | 656.891 | +| 82793 | 649.689 | +| 124120 | 641.483 | +| 129617 | 638.026 | +| 34049 | 636.484 | +| 114278 | 634.321 | +| 129056 | 632.356 | +| 97042 | 628.272 | +| 113297 | 615.506 | +| 114280 | 602.806 | +| 128820 | 584.348 | +| 129485 | 582.42 | +| 116203 | 581.357 | +| 117666 | 574.346 | +| 104129 | 573.431 | +| 113633 | 555.923 | +| 129376 | 555.097 | +| 120446 | 554.187 | +| 129244 | 552.741 | +| 129062 | 549.367 | +| 129379 | 548.041 | +| 114283 | 544.218 | +| 50960 | 543.074 | +| 116208 | 540.121 | +| 128818 | 533.672 | +| 129067 | 528.548 | +| 117316 | 523.283 | +| 129261 | 520.043 | +| 129250 | 515.021 | +| 112634 | 512.899 | +| 129873 | 512.747 | +| 55082 | 510.561 | +| 92079 | 504.123 | +| 124511 | 502.403 | +| 115727 | 501.008 | +| 92106 | 499.139 | +| 124509 | 483.871 | +| 92080 | 468.411 | +| 124205 | 466.926 | +| 129622 | 466.744 | +| 113629 | 455.779 | +| 115728 | 454.545 | +| 128825 | 451.191 | +| 117312 | 439.394 | +| 129238 | 438.596 | +| 127557 | 434.389 | +| 129417 | 433.996 | +| 129374 | 425.483 | +| 129372 | 424.086 | +| 128821 | 412.225 | +| 129061 | 405.314 | +| 113638 | 404.267 | +| 113357 | 403.286 | +| 129105 | 400.757 | +| 114291 | 397.76 | +| 92082 | 395.859 | +| 129109 | 384.871 | +| 114282 | 381.985 | +| 115737 | 380.684 | +| 129107 | 373.918 | +| 129114 | 371.353 | +| 124484 | 368.894 | +| 113299 | 349.418 | +| 129406 | 348.801 | +| 108963 | 347.896 | +| 129385 | 336.673 | +| 113355 | 333.83 | +| 110901 | 327.69 | +| 113590 | 324.445 | +| 129150 | 324.356 | +| 113630 | 319.266 | +| 128824 | 314.136 | +| 104131 | 310.795 | +| 129369 | 310.72 | +| 129055 | 300.617 | +| 129101 | 300.518 | +| 128822 | 294.813 | +| 124581 | 291.524 | +| 87632 | 289.71 | +| 129368 | 288.908 | +| 113631 | 287.2 | +| 129383 | 281.531 | +| 114290 | 274.779 | +| 129418 | 274.516 | +| 129386 | 268.392 | +| 127554 | 265.487 | +| 129252 | 258.491 | +| 129407 | 257.985 | +| 117381 | 255.494 | +| 129251 | 255.102 | +| 113675 | 254.118 | +| 113359 | 251.451 | +| 124209 | 248.521 | +| 129626 | 246.643 | +| 129413 | 238.507 | +| 67147 | 236.255 | +| 115984 | 231.784 | +| 129063 | 229.314 | +| 127558 | 225.389 | +| 113637 | 225.336 | +| 129410 | 220.016 | +| 111357 | 216.854 | +| 129397 | 213.707 | +| 129378 | 212.914 | +| 114540 | 209.505 | +| 102619 | 208.709 | +| 129052 | 206.831 | +| 129058 | 202.326 | +| 115985 | 199.018 | +| 129240 | 197.102 | +| 129049 | 196.652 | +| 129051 | 190.747 | +| 127556 | 189.618 | +| 113365 | 187.929 | +| 128362 | 180.535 | +| 129115 | 178.05 | +| 129116 | 175.953 | +| 116213 | 175.926 | +| 129375 | 174.494 | +| 115983 | 174.401 | +| 129367 | 170.092 | +| 67140 | 167.858 | +| 129620 | 161.074 | +| 129380 | 160.591 | +| 129412 | 159.737 | +| 129110 | 159.111 | +| 118814 | 156.493 | +| 124208 | 154.884 | +| 113587 | 154.682 | +| 129616 | 153.533 | +| 129370 | 153.223 | +| 129400 | 148.369 | +| 129053 | 146.551 | +| 129245 | 146.329 | +| 129260 | 145.151 | +| 129403 | 143.564 | +| 129840 | 143.483 | +| 129377 | 141.498 | +| 114285 | 141.314 | +| 129404 | 140.091 | +| 124210 | 137.378 | +| 129615 | 134.145 | +| 113294 | 131.096 | +| 129100 | 128.45 | +| 129060 | 127.547 | +| 129415 | 127.432 | +| 85336 | 126.409 | +| 129237 | 125.256 | +| 129113 | 123.163 | +| 129398 | 120.774 | +| 115741 | 111.975 | +| 129416 | 110.298 | +| 129258 | 109.448 | +| 124206 | 109.087 | +| 129057 | 108.223 | +| 119422 | 108.187 | +| 114537 | 107.263 | +| 124162 | 106.926 | +| 117322 | 105.382 | +| 129096 | 103.318 | +| 129384 | 102.975 | +| 112642 | 100.391 | +| 129108 | 100.196 | +| 129623 | 95.3137 | +| 129050 | 94.8697 | +| 129629 | 92.2779 | +| 129099 | 91.6305 | +| 129111 | 91.0051 | +| 129609 | 89.2835 | +| 129402 | 88.645 | +| 129610 | 88.2107 | +| 129064 | 87.0746 | +| 124207 | 86.1428 | +| 113632 | 85.5615 | +| 115755 | 84.9017 | +| 129371 | 83.1084 | +| 129366 | 82.7434 | +| 127546 | 81.9865 | +| 129401 | 81.1104 | +| 129624 | 80.5582 | +| 129242 | 80.4589 | +| 112636 | 77.6431 | +| 129103 | 75.6127 | +| 129614 | 75.3867 | +| 129106 | 74.9306 | +| 129387 | 73.3656 | +| 113589 | 72.7894 | +| 129396 | 71.4623 | +| 92077 | 70.6436 | +| 129098 | 69.1138 | +| 128819 | 68.0787 | +| 129625 | 66.2291 | +| 129247 | 66.1358 | +| 129255 | 63.9769 | +| 115820 | 61.557 | +| 114289 | 60.2948 | +| 129408 | 59.7946 | +| 128795 | 57.5178 | +| 129621 | 55.2846 | +| 116337 | 55.0093 | +| 129414 | 54.4765 | +| 129065 | 53.9601 | +| 129259 | 52.3702 | +| 124168 | 51.4742 | +| 129627 | 51.1316 | +| 129411 | 50.4361 | +| 129243 | 50.3983 | +| 129097 | 49.6162 | +| 129254 | 48.1486 | +| 129612 | 47.508 | +| 129257 | 40.7716 | +| 124166 | 39.6231 | +| 117668 | 38.2632 | +| 108880 | 38.2148 | +| 124258 | 37.8442 | +| 129054 | 37.1786 | +| 117382 | 33.5436 | +| 129373 | 32.9989 | +| 127537 | 29.4361 | +| 129618 | 26.545 | +| 129253 | 24.0941 | +| 129816 | 20.0367 | +| 129382 | 18.6974 | +| 92984 | 18.466 | ++----------+-----------+ +262 rows in set (0.092 sec) + +-------------- +SELECT '=== round 2: q6_month_range ===' AS msg +-------------- + ++---------------------------------+ +| msg | ++---------------------------------+ +| === round 2: q6_month_range === | ++---------------------------------+ +1 row in set (0.012 sec) + +-------------- +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(DATE_FORMAT(@day, '%Y-%m-01') AS DATETIME) + AND RecordDateTime < CAST(DATE_FORMAT(@day, '%Y-%m-01') AS DATETIME) + INTERVAL 1 MONTH +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID +-------------- + ++----------+-----------+ +| PlayerID | HighScore | ++----------+-----------+ +| 34820 | 2001.19 | +| 112638 | 1952.35 | +| 113366 | 1863.35 | +| 112641 | 1763.78 | +| 92104 | 1217.8 | +| 113361 | 1082.21 | +| 124027 | 1059.27 | +| 124029 | 1011.24 | +| 122149 | 1007.8 | +| 127555 | 968.61 | +| 113588 | 950.495 | +| 55094 | 930.788 | +| 115731 | 904.198 | +| 121713 | 893.079 | +| 113298 | 884.146 | +| 113358 | 866.426 | +| 55093 | 862.026 | +| 129613 | 861.538 | +| 129611 | 841.894 | +| 129048 | 812.515 | +| 113624 | 809.535 | +| 124030 | 808.738 | +| 46710 | 804.252 | +| 112635 | 779.046 | +| 56775 | 778.698 | +| 129365 | 772.591 | +| 85486 | 745.011 | +| 113296 | 744.186 | +| 92674 | 728.948 | +| 54456 | 726.969 | +| 113362 | 712.677 | +| 129059 | 702.972 | +| 55083 | 697.975 | +| 115748 | 690.39 | +| 113634 | 685.967 | +| 129102 | 685.564 | +| 129256 | 673.212 | +| 129248 | 658.979 | +| 117340 | 656.891 | +| 82793 | 649.689 | +| 124120 | 641.483 | +| 129617 | 638.026 | +| 34049 | 636.484 | +| 114278 | 634.321 | +| 129056 | 632.356 | +| 97042 | 628.272 | +| 113297 | 615.506 | +| 114280 | 602.806 | +| 128820 | 584.348 | +| 129485 | 582.42 | +| 116203 | 581.357 | +| 117666 | 574.346 | +| 104129 | 573.431 | +| 113633 | 555.923 | +| 129376 | 555.097 | +| 120446 | 554.187 | +| 129244 | 552.741 | +| 129062 | 549.367 | +| 129379 | 548.041 | +| 114283 | 544.218 | +| 50960 | 543.074 | +| 116208 | 540.121 | +| 128818 | 533.672 | +| 129067 | 528.548 | +| 117316 | 523.283 | +| 129261 | 520.043 | +| 129250 | 515.021 | +| 112634 | 512.899 | +| 129873 | 512.747 | +| 55082 | 510.561 | +| 92079 | 504.123 | +| 124511 | 502.403 | +| 115727 | 501.008 | +| 92106 | 499.139 | +| 124509 | 483.871 | +| 92080 | 468.411 | +| 124205 | 466.926 | +| 129622 | 466.744 | +| 113629 | 455.779 | +| 115728 | 454.545 | +| 128825 | 451.191 | +| 117312 | 439.394 | +| 129238 | 438.596 | +| 127557 | 434.389 | +| 129417 | 433.996 | +| 129374 | 425.483 | +| 129372 | 424.086 | +| 128821 | 412.225 | +| 129061 | 405.314 | +| 113638 | 404.267 | +| 113357 | 403.286 | +| 129105 | 400.757 | +| 114291 | 397.76 | +| 92082 | 395.859 | +| 129109 | 384.871 | +| 114282 | 381.985 | +| 115737 | 380.684 | +| 129107 | 373.918 | +| 129114 | 371.353 | +| 124484 | 368.894 | +| 113299 | 349.418 | +| 129406 | 348.801 | +| 108963 | 347.896 | +| 129385 | 336.673 | +| 113355 | 333.83 | +| 110901 | 327.69 | +| 113590 | 324.445 | +| 129150 | 324.356 | +| 113630 | 319.266 | +| 128824 | 314.136 | +| 104131 | 310.795 | +| 129369 | 310.72 | +| 129055 | 300.617 | +| 129101 | 300.518 | +| 128822 | 294.813 | +| 124581 | 291.524 | +| 87632 | 289.71 | +| 129368 | 288.908 | +| 113631 | 287.2 | +| 129383 | 281.531 | +| 114290 | 274.779 | +| 129418 | 274.516 | +| 129386 | 268.392 | +| 127554 | 265.487 | +| 129252 | 258.491 | +| 129407 | 257.985 | +| 117381 | 255.494 | +| 129251 | 255.102 | +| 113675 | 254.118 | +| 113359 | 251.451 | +| 124209 | 248.521 | +| 129626 | 246.643 | +| 129413 | 238.507 | +| 67147 | 236.255 | +| 115984 | 231.784 | +| 129063 | 229.314 | +| 127558 | 225.389 | +| 113637 | 225.336 | +| 129410 | 220.016 | +| 111357 | 216.854 | +| 129397 | 213.707 | +| 129378 | 212.914 | +| 114540 | 209.505 | +| 102619 | 208.709 | +| 129052 | 206.831 | +| 129058 | 202.326 | +| 115985 | 199.018 | +| 129240 | 197.102 | +| 129049 | 196.652 | +| 129051 | 190.747 | +| 127556 | 189.618 | +| 113365 | 187.929 | +| 128362 | 180.535 | +| 129115 | 178.05 | +| 129116 | 175.953 | +| 116213 | 175.926 | +| 129375 | 174.494 | +| 115983 | 174.401 | +| 129367 | 170.092 | +| 67140 | 167.858 | +| 129620 | 161.074 | +| 129380 | 160.591 | +| 129412 | 159.737 | +| 129110 | 159.111 | +| 118814 | 156.493 | +| 124208 | 154.884 | +| 113587 | 154.682 | +| 129616 | 153.533 | +| 129370 | 153.223 | +| 129400 | 148.369 | +| 129053 | 146.551 | +| 129245 | 146.329 | +| 129260 | 145.151 | +| 129403 | 143.564 | +| 129840 | 143.483 | +| 129377 | 141.498 | +| 114285 | 141.314 | +| 129404 | 140.091 | +| 124210 | 137.378 | +| 129615 | 134.145 | +| 113294 | 131.096 | +| 129100 | 128.45 | +| 129060 | 127.547 | +| 129415 | 127.432 | +| 85336 | 126.409 | +| 129237 | 125.256 | +| 129113 | 123.163 | +| 129398 | 120.774 | +| 115741 | 111.975 | +| 129416 | 110.298 | +| 129258 | 109.448 | +| 124206 | 109.087 | +| 129057 | 108.223 | +| 119422 | 108.187 | +| 114537 | 107.263 | +| 124162 | 106.926 | +| 117322 | 105.382 | +| 129096 | 103.318 | +| 129384 | 102.975 | +| 112642 | 100.391 | +| 129108 | 100.196 | +| 129623 | 95.3137 | +| 129050 | 94.8697 | +| 129629 | 92.2779 | +| 129099 | 91.6305 | +| 129111 | 91.0051 | +| 129609 | 89.2835 | +| 129402 | 88.645 | +| 129610 | 88.2107 | +| 129064 | 87.0746 | +| 124207 | 86.1428 | +| 113632 | 85.5615 | +| 115755 | 84.9017 | +| 129371 | 83.1084 | +| 129366 | 82.7434 | +| 127546 | 81.9865 | +| 129401 | 81.1104 | +| 129624 | 80.5582 | +| 129242 | 80.4589 | +| 112636 | 77.6431 | +| 129103 | 75.6127 | +| 129614 | 75.3867 | +| 129106 | 74.9306 | +| 129387 | 73.3656 | +| 113589 | 72.7894 | +| 129396 | 71.4623 | +| 92077 | 70.6436 | +| 129098 | 69.1138 | +| 128819 | 68.0787 | +| 129625 | 66.2291 | +| 129247 | 66.1358 | +| 129255 | 63.9769 | +| 115820 | 61.557 | +| 114289 | 60.2948 | +| 129408 | 59.7946 | +| 128795 | 57.5178 | +| 129621 | 55.2846 | +| 116337 | 55.0093 | +| 129414 | 54.4765 | +| 129065 | 53.9601 | +| 129259 | 52.3702 | +| 124168 | 51.4742 | +| 129627 | 51.1316 | +| 129411 | 50.4361 | +| 129243 | 50.3983 | +| 129097 | 49.6162 | +| 129254 | 48.1486 | +| 129612 | 47.508 | +| 129257 | 40.7716 | +| 124166 | 39.6231 | +| 117668 | 38.2632 | +| 108880 | 38.2148 | +| 124258 | 37.8442 | +| 129054 | 37.1786 | +| 117382 | 33.5436 | +| 129373 | 32.9989 | +| 127537 | 29.4361 | +| 129618 | 26.545 | +| 129253 | 24.0941 | +| 129816 | 20.0367 | +| 129382 | 18.6974 | +| 92984 | 18.466 | ++----------+-----------+ +262 rows in set (0.092 sec) + +Bye diff --git a/doc/plan/db/260915-improve-production/result/baseline_pick_time.txt b/doc/plan/db/260915-improve-production/result/baseline_pick_time.txt new file mode 100644 index 0000000..be23a20 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/baseline_pick_time.txt @@ -0,0 +1,6 @@ +day hour cnt +2026-08-26 9 22 +2026-08-28 9 22 +2026-08-26 10 21 +2026-08-31 10 20 +2026-08-25 10 19 diff --git a/doc/plan/db/260915-improve-production/result/comparison.txt b/doc/plan/db/260915-improve-production/result/comparison.txt new file mode 100644 index 0000000..80be77d --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/comparison.txt @@ -0,0 +1,39 @@ +=== 성능 개선 효과 (운영 DB, 2026-09-15) === +측정 기준: params.sql (MaestroID 181, AppID 21, @day 2026-08-26, @hour 9) +측정 시점: 베이스라인 22:5x (인덱스 추가 전) / 개선 후 23:1x (인덱스 7개 추가 후) + +| 쿼리 | access_type (전→후) | rows (전→후) | 서버 시간 ms (전→후, ANALYZE) | 결과 행 수 | +|--------------------|-----------------------|-----------------------|-------------------------------|-----------| +| q1 시간별 · 함수 | index_merge → ref | 10,103 → 5,851 (1.7배) | 74.61 → 2.07 (36배) | 22 = 22 | +| q2 시간별 · 범위 | index_merge → range | 10,103 → 22 (459배) | 75.58 → 0.26 (292배) | 22 = 22 | +| q3 일간 · 함수 | index_merge → ref | 10,103 → 5,851 (1.7배) | 75.29 → 2.44 (31배) | 61 = 61 | +| q4 일간 · 범위 | index_merge → range | 10,103 → 99 (102배) | 75.24 → 0.47 (161배) | 61 = 61 | +| q5 월간 · 함수 | index_merge → ref | 10,103 → 5,851 (1.7배) | 76.27 → 4.30 (18배) | 262 = 262 | +| q6 월간 · 범위 | index_merge → range | 10,103 → 709 (14배) | 79.70 → 2.50 (32배) | 262 = 262 | + +사용 인덱스: 개선 후 6개 모두 idx_maestro_app_dt (MaestroID, AppID, RecordDateTime) +클라이언트 시간 (네트워크 포함, 2회차): 약 88~92ms → 13~18ms + +참고 (스테이징): rows 5,804 → 1, cost 8.63 → 0.004 +참고 (운영 01 사전 측정): 일간 랭킹 index_merge, rows 10,100 + +결과 행 수 동일 (4-3): [x] 예 [ ] 아니오 + +결론: +- 인덱스만의 효과 (q1·q3·q5, 함수형 조건): + rows 추정은 1.7배만 줄었지만 서버 처리 시간은 18~36배 단축. + 기존에는 MaestroID·AppID 단일 인덱스 두 개의 교집합(index_merge)을 구하느라 느렸고, + 복합 인덱스의 (MaestroID, AppID) 앞부분으로 바로 찾게(ref) 되었기 때문. + 날짜 조건은 여전히 인덱스로 거르지 못해 해당 선생님·앱의 기록 약 5,851행을 확인함. +- 인덱스 + 쿼리 수정 효과 (q2·q4·q6, 범위 조건): + 날짜까지 인덱스로 걸러(range) 조회 기간의 실제 기록만 읽음. + 서버 처리 시간 32~292배 단축, 검사 행 수 14~459배 감소. + 같은 조건에서 함수형 대비 추가로 1.7~9배 빠름 (q1→q2 8배, q3→q4 5배, q5→q6 1.7배). + +중복 정리 (03-0): +- app_highest_record 8,795행 삭제 (4,823조합), 220,648행 = 220,648조합 +- UNIQUE 키 2개 추가 완료 → 이후 중복 저장은 DB가 거부 + +Slow query log (04-5, 23:08 이후 1시간): +- 0.5초 이상은 작업 SQL 5건(ALTER 4건, 중복 DELETE 1건)뿐, 애플리케이션 쿼리 0건 +- 새벽이 아닌 밤 시간이고 기간이 짧으므로 24시간 뒤 다시 확인 필요 (07단계) diff --git a/doc/plan/db/260915-improve-production/result/dedupe_before_count.txt b/doc/plan/db/260915-improve-production/result/dedupe_before_count.txt new file mode 100644 index 0000000..de1c7c1 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/dedupe_before_count.txt @@ -0,0 +1,2 @@ +dup_groups rows_to_delete +4823 8795 diff --git a/doc/plan/db/260915-improve-production/result/dedupe_result.txt b/doc/plan/db/260915-improve-production/result/dedupe_result.txt new file mode 100644 index 0000000..9102228 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/dedupe_result.txt @@ -0,0 +1,47 @@ +-------------- +DELETE AHR FROM app_highest_record AHR +JOIN ( + SELECT AppHighestRecordID FROM ( + SELECT AppHighestRecordID, + ROW_NUMBER() OVER ( + PARTITION BY MaestroID, PlayerID, AppID + ORDER BY CASE WHEN AppID = 105 THEN HighestRecord ELSE -HighestRecord END, + AppHighestRecordID DESC + ) AS rn + FROM app_highest_record + ) ranked + WHERE rn > 1 +) dup ON dup.AppHighestRecordID = AHR.AppHighestRecordID +-------------- + +Query OK, 8795 rows affected (0.942 sec) + +-------------- +SELECT 'app_highest_record' AS table_name, + COUNT(*) AS total_rows, + COUNT(DISTINCT MaestroID, PlayerID, AppID) AS unique_combos +FROM app_highest_record +-------------- + ++--------------------+------------+---------------+ +| table_name | total_rows | unique_combos | ++--------------------+------------+---------------+ +| app_highest_record | 220648 | 220648 | ++--------------------+------------+---------------+ +1 row in set (0.190 sec) + +-------------- +SELECT 'typing_exam_highest_record' AS table_name, + COUNT(*) AS total_rows, + COUNT(DISTINCT MaestroID, PlayerID, WritingID) AS unique_combos +FROM typing_exam_highest_record +-------------- + ++----------------------------+------------+---------------+ +| table_name | total_rows | unique_combos | ++----------------------------+------------+---------------+ +| typing_exam_highest_record | 20413 | 20413 | ++----------------------------+------------+---------------+ +1 row in set (0.024 sec) + +Bye diff --git a/doc/plan/db/260915-improve-production/result/explain_summary.txt b/doc/plan/db/260915-improve-production/result/explain_summary.txt new file mode 100644 index 0000000..13301cd --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/explain_summary.txt @@ -0,0 +1,12 @@ +baseline_explain_q1_hour_func.json access_type=index_merge key=MaestroID ∩ AppID rows=10103 +baseline_explain_q2_hour_range.json access_type=index_merge key=MaestroID ∩ AppID rows=10103 +baseline_explain_q3_day_func.json access_type=index_merge key=MaestroID ∩ AppID rows=10103 +baseline_explain_q4_day_range.json access_type=index_merge key=MaestroID ∩ AppID rows=10103 +baseline_explain_q5_month_func.json access_type=index_merge key=MaestroID ∩ AppID rows=10103 +baseline_explain_q6_month_range.json access_type=index_merge key=MaestroID ∩ AppID rows=10103 +after_explain_q1_hour_func.json access_type=ref key=idx_maestro_app_dt rows=5851 +after_explain_q2_hour_range.json access_type=range key=idx_maestro_app_dt rows=22 +after_explain_q3_day_func.json access_type=ref key=idx_maestro_app_dt rows=5851 +after_explain_q4_day_range.json access_type=range key=idx_maestro_app_dt rows=99 +after_explain_q5_month_func.json access_type=ref key=idx_maestro_app_dt rows=5851 +after_explain_q6_month_range.json access_type=range key=idx_maestro_app_dt rows=709 diff --git a/doc/plan/db/260915-improve-production/result/params.sql b/doc/plan/db/260915-improve-production/result/params.sql new file mode 100644 index 0000000..7a4adb2 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/params.sql @@ -0,0 +1 @@ +SET @maestro = 181, @app = 21, @day = '2026-08-26', @hour = 9; diff --git a/doc/plan/db/260915-improve-production/result/premeasure_app_highest_dup_count.txt b/doc/plan/db/260915-improve-production/result/premeasure_app_highest_dup_count.txt new file mode 100644 index 0000000..de1c7c1 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/premeasure_app_highest_dup_count.txt @@ -0,0 +1,2 @@ +dup_groups rows_to_delete +4823 8795 diff --git a/doc/plan/db/260915-improve-production/result/premeasure_app_highest_duplicates.txt b/doc/plan/db/260915-improve-production/result/premeasure_app_highest_duplicates.txt new file mode 100644 index 0000000..865bd0b --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/premeasure_app_highest_duplicates.txt @@ -0,0 +1,11 @@ +MaestroID PlayerID AppID cnt +223 115830 2 33 +306 93738 106 25 +123 86825 106 21 +82 2245 106 21 +278 84908 106 20 +49 114356 106 20 +184 112221 101 19 +127 71176 3 19 +363 105234 106 18 +75 10923 106 17 diff --git a/doc/plan/db/260915-improve-production/result/premeasure_exam_highest_duplicates.txt b/doc/plan/db/260915-improve-production/result/premeasure_exam_highest_duplicates.txt new file mode 100644 index 0000000..e69de29 diff --git a/doc/plan/db/260915-improve-production/result/premeasure_explain_daily_ranking.json b/doc/plan/db/260915-improve-production/result/premeasure_explain_daily_ranking.json new file mode 100644 index 0000000..8fadf97 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/premeasure_explain_daily_ranking.json @@ -0,0 +1,66 @@ +{ + "query_block": { + "select_id": 1, + "filesort": { + "sort_key": "max(BR.BestRecord) desc", + "temporary_table": { + "nested_loop": [ + { + "table": { + "table_name": "BR", + "access_type": "index_merge", + "possible_keys": [ + "MaestroID", + "AppID", + "PlayerID" + ], + "key_length": "4,4", + "index_merge": { + "intersect": [ + { + "range": { + "key": "MaestroID", + "used_key_parts": [ + "MaestroID" + ] + } + }, + { + "range": { + "key": "AppID", + "used_key_parts": [ + "AppID" + ] + } + } + ] + }, + "rows": 10100, + "filtered": 100, + "attached_condition": "BR.MaestroID = 181 and BR.AppID = 21 and year(BR.RecordDateTime) = 2026 and month(BR.RecordDateTime) = 9 and dayofmonth(BR.RecordDateTime) = 15" + } + }, + { + "table": { + "table_name": "U", + "access_type": "eq_ref", + "possible_keys": [ + "PRIMARY" + ], + "key": "PRIMARY", + "key_length": "4", + "used_key_parts": [ + "PlayerID" + ], + "ref": [ + "chocomae.BR.PlayerID" + ], + "rows": 1, + "filtered": 100 + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/doc/plan/db/260915-improve-production/result/premeasure_monthly_load.txt b/doc/plan/db/260915-improve-production/result/premeasure_monthly_load.txt new file mode 100644 index 0000000..0c7b152 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/premeasure_monthly_load.txt @@ -0,0 +1,55 @@ +ym cnt +2022-04 3 +2022-05 3544 +2022-06 7730 +2022-07 7543 +2022-08 4605 +2022-09 4164 +2022-10 4702 +2022-11 4052 +2022-12 2446 +2023-01 3903 +2023-02 1580 +2023-03 18646 +2023-04 22750 +2023-05 19749 +2023-06 18620 +2023-07 16490 +2023-08 13244 +2023-09 17892 +2023-10 19045 +2023-11 23144 +2023-12 17608 +2024-01 15262 +2024-02 6745 +2024-03 19414 +2024-04 27242 +2024-05 25057 +2024-06 21613 +2024-07 23446 +2024-08 17951 +2024-09 21469 +2024-10 22776 +2024-11 22526 +2024-12 19082 +2025-01 12170 +2025-02 8558 +2025-03 26788 +2025-04 30366 +2025-05 29850 +2025-06 32421 +2025-07 28872 +2025-08 19853 +2025-09 33908 +2025-10 22878 +2025-11 28723 +2025-12 28649 +2026-01 16532 +2026-02 8980 +2026-03 66105 +2026-04 70072 +2026-05 58444 +2026-06 65606 +2026-07 56307 +2026-08 51991 +2026-09 39366 diff --git a/doc/plan/db/260915-improve-production/result/premeasure_slow_log_settings.txt b/doc/plan/db/260915-improve-production/result/premeasure_slow_log_settings.txt new file mode 100644 index 0000000..8ce68c5 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/premeasure_slow_log_settings.txt @@ -0,0 +1,6 @@ +Variable_name Value +log_output FILE +log_queries_not_using_indexes OFF +long_query_time 3.000000 +slow_query_log ON +slow_query_log_file /var/log/mysql/mariadb-slow.log diff --git a/doc/plan/db/260915-improve-production/result/premeasure_summary.txt b/doc/plan/db/260915-improve-production/result/premeasure_summary.txt new file mode 100644 index 0000000..e31c7ce --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/premeasure_summary.txt @@ -0,0 +1,67 @@ +=== 운영 DB 사전 측정 결과 (2026-09-15) === + +1. 테이블 크기 (premeasure_table_sizes.txt) + - best_record data_mb: 72.5 + - best_record index_mb: 72.1 + - best_record 행 수: 약 1,152,399 (information_schema 추정치) / 월별 합계 1,210,482 + - typing_exam_record data_mb: 10.3 + - typing_exam_record index_mb: 10.9 + +2. 월별 적재량 (premeasure_monthly_load.txt) + - 최근 1개월 (2026-08): 51,991 행 + - 2026-09 (15일까지): 39,366 행 + - 최근 3개월 평균 (2026-06~08): 57,968 행/월 + - 전년 같은 기간 평균 (2025-06~08): 27,049 행/월 → 약 2.1배 증가 + +3. 테스트용 선생님·앱 (premeasure_top_maestro_app.txt) + - MaestroID: 181 + - AppID: 21 + - 30일 기록 수: 1,089 + +4. 일간 랭킹 EXPLAIN (premeasure_explain_daily_ranking.json) + - access_type: index_merge (예상했던 ALL 아님) + - key: MaestroID ∩ AppID (기존 단일 인덱스 2개의 교집합) + - rows: 10,100 (예상했던 1,206,768 아님) + - 날짜 조건(YEAR/MONTH/DAYOFMONTH)은 인덱스로 거르지 못하고 10,100행을 하나씩 확인 + - 참고: 원본 출력은 헤더와 \n 이스케이프 때문에 JSON이 아니었음 → 내용 변경 없이 JSON으로 복원 + +5. 최고기록 테이블 중복 (premeasure_*_duplicates.txt, premeasure_app_highest_dup_count.txt) + - app_highest_record 중복: 4,823 조합 / 삭제 대상 8,795 행 + · 테이블 약 229,459행(추정치)의 약 3.8% + · 상위 10개 조합은 각 17~33행 + · 03-0-1에서 작업 당일 다시 세어 비교 (그 사이 조금 늘어날 수 있음) + - typing_exam_highest_record 중복: 0 건 (빈 파일 = 조회 결과 없음) + +6. 계정 권한 (premeasure_grants.txt) + - jisangs@182.217.174.221: SELECT, ALTER ON chocomae.* 만 있음 + - 2026-09-15 임시 부여 후 (premeasure_grants_after.txt): + + SUPER ON *.* + + INSERT, DELETE ON chocomae.app_highest_record, chocomae.typing_exam_highest_record + + SELECT ON mysql.slow_log + → 05단계에서 회수 (slow log 복원 후) + - 원래 권한으로 할 수 없던 작업: + · 02-1 slow query log 변경 (SET GLOBAL → SUPER) + · 03-0-3 중복 삭제 (DELETE) + · 04-5, 07 mysql.slow_log 조회 (SELECT ON mysql.slow_log) + · 05 slow query log 복원 (SET GLOBAL) + · 06 삭제 행 복원 (INSERT) → 임시 부여됨 + · 06 ANALYZE TABLE best_record (best_record INSERT 필요, 부여 안 함) → 관리자 계정 + +7. Slow query log 원래 설정 (premeasure_slow_log_settings.txt) — 05단계 복원 기준값 + - slow_query_log: ON + - long_query_time: 3 + - log_output: FILE + - log_queries_not_using_indexes: OFF + - slow_query_log_file: /var/log/mysql/mariadb-slow.log + - 이전 문서의 1-4 SET GLOBAL은 적용되지 않았음 (SUPER 권한 없음, 적용됐다면 long_query_time=1·log_output=TABLE) + +8. 준비 확인 + ☑ 테이블 크기 확인 + ☑ 월별 적재량 확인 + ☑ EXPLAIN 결과 저장 + ☑ 중복 확인 (전체 개수 포함) + ☑ 계정 권한 확인 + ☑ Slow query log 현재 설정 저장 + ☑ 작업 권한 임시 부여 + +다음: 02-baseline.md diff --git a/doc/plan/db/260915-improve-production/result/premeasure_table_sizes.txt b/doc/plan/db/260915-improve-production/result/premeasure_table_sizes.txt new file mode 100644 index 0000000..a5a896e --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/premeasure_table_sizes.txt @@ -0,0 +1,20 @@ +TABLE_NAME approx_rows data_mb index_mb +best_record 1152399 72.5 72.1 +app_highest_record 229459 16.5 15.8 +typing_exam_record 131675 10.3 10.9 +player 22394 3.5 0.4 +active_app 17661 1.5 0.6 +typing_exam_highest_record 21355 1.4 1.2 +maestro_log 1944 0.4 0.0 +maestro 418 0.1 0.0 +license_score 514 0.1 0.0 +license_maestro_password 1024 0.1 0.0 +maestro_extension 450 0.0 0.0 +admin 0 0.0 0.0 +app 48 0.0 0.0 +ads_client 0 0.0 0.0 +license_time 0 0.0 0.0 +writing 20 0.0 0.0 +typing_exam_ads 0 0.0 0.0 +banned_word 22 0.0 0.0 +maestro_upgrade 109 0.0 0.0 diff --git a/doc/plan/db/260915-improve-production/result/premeasure_top_maestro_app.txt b/doc/plan/db/260915-improve-production/result/premeasure_top_maestro_app.txt new file mode 100644 index 0000000..ecfab2e --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/premeasure_top_maestro_app.txt @@ -0,0 +1,6 @@ +MaestroID AppID cnt +181 21 1089 +181 1 591 +389 103 562 +230 21 546 +389 104 541 diff --git a/doc/plan/db/260915-improve-production/result/queries/q1_hour_func.sql b/doc/plan/db/260915-improve-production/result/queries/q1_hour_func.sql new file mode 100644 index 0000000..dbd8b32 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/queries/q1_hour_func.sql @@ -0,0 +1,6 @@ +SELECT SQL_NO_CACHE PlayerID, BestRecord, RecordDateTime +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND DATE(RecordDateTime) = @day + AND HOUR(RecordDateTime) = @hour +ORDER BY RecordDateTime, PlayerID; diff --git a/doc/plan/db/260915-improve-production/result/queries/q2_hour_range.sql b/doc/plan/db/260915-improve-production/result/queries/q2_hour_range.sql new file mode 100644 index 0000000..9030ae1 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/queries/q2_hour_range.sql @@ -0,0 +1,6 @@ +SELECT SQL_NO_CACHE PlayerID, BestRecord, RecordDateTime +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(@day AS DATETIME) + INTERVAL @hour HOUR + AND RecordDateTime < CAST(@day AS DATETIME) + INTERVAL (@hour + 1) HOUR +ORDER BY RecordDateTime, PlayerID; diff --git a/doc/plan/db/260915-improve-production/result/queries/q3_day_func.sql b/doc/plan/db/260915-improve-production/result/queries/q3_day_func.sql new file mode 100644 index 0000000..3a4d29a --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/queries/q3_day_func.sql @@ -0,0 +1,8 @@ +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND YEAR(RecordDateTime) = YEAR(@day) + AND MONTH(RecordDateTime) = MONTH(@day) + AND DAYOFMONTH(RecordDateTime) = DAYOFMONTH(@day) +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID; diff --git a/doc/plan/db/260915-improve-production/result/queries/q4_day_range.sql b/doc/plan/db/260915-improve-production/result/queries/q4_day_range.sql new file mode 100644 index 0000000..3373bfb --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/queries/q4_day_range.sql @@ -0,0 +1,7 @@ +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(@day AS DATETIME) + AND RecordDateTime < CAST(@day AS DATETIME) + INTERVAL 1 DAY +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID; diff --git a/doc/plan/db/260915-improve-production/result/queries/q5_month_func.sql b/doc/plan/db/260915-improve-production/result/queries/q5_month_func.sql new file mode 100644 index 0000000..94320dd --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/queries/q5_month_func.sql @@ -0,0 +1,7 @@ +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND YEAR(RecordDateTime) = YEAR(@day) + AND MONTH(RecordDateTime) = MONTH(@day) +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID; diff --git a/doc/plan/db/260915-improve-production/result/queries/q6_month_range.sql b/doc/plan/db/260915-improve-production/result/queries/q6_month_range.sql new file mode 100644 index 0000000..726dd8f --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/queries/q6_month_range.sql @@ -0,0 +1,7 @@ +SELECT SQL_NO_CACHE PlayerID, MAX(BestRecord) AS HighScore +FROM best_record +WHERE MaestroID = @maestro AND AppID = @app + AND RecordDateTime >= CAST(DATE_FORMAT(@day, '%Y-%m-01') AS DATETIME) + AND RecordDateTime < CAST(DATE_FORMAT(@day, '%Y-%m-01') AS DATETIME) + INTERVAL 1 MONTH +GROUP BY PlayerID +ORDER BY HighScore DESC, PlayerID; diff --git a/doc/plan/db/260915-improve-production/result/run_measure.sh b/doc/plan/db/260915-improve-production/result/run_measure.sh new file mode 100644 index 0000000..5fbed7a --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/run_measure.sh @@ -0,0 +1,41 @@ +#!/bin/zsh +# 02·04단계 공통 측정 스크립트 +# 사용법: zsh run_measure.sh baseline (02단계, 인덱스 추가 전) +# zsh run_measure.sh after (04단계, 인덱스 추가 후) + +PREFIX=$1 +if [[ $PREFIX != baseline && $PREFIX != after ]]; then + echo "사용법: zsh run_measure.sh baseline|after" >&2; exit 1 +fi +cd "${0:A:h}" || exit 1 + +printf "Enter password: "; read -rs DBPW; echo + +db() { + mariadb --defaults-extra-file=<(printf '[client]\npassword="%s"\n' "$DBPW") \ + -h chocomae.jinaju.com -u jisangs chocomae "$@" +} + +# 1) EXPLAIN: 쿼리마다 JSON 파일 1개 +for q in queries/*.sql; do + out="${PREFIX}_explain_${q:t:r}.json" + { cat params.sql; printf 'EXPLAIN FORMAT=JSON '; cat "$q"; } | db -N -r > "$out" \ + || { echo "EXPLAIN 실패: $q" >&2; exit 1; } + echo "저장: $out" +done + +# 2) 실행 시간: 같은 쿼리 묶음을 2회 실행 (2회차 값 사용) +for round in 1 2; do + { cat params.sql + for q in queries/*.sql; do + echo "SELECT '=== round ${round}: ${q:t:r} ===' AS msg;" + cat "$q" + done + } | db -vvv || { echo "실행 시간 측정 실패" >&2; exit 1; } +done > "${PREFIX}_perf.txt" +echo "저장: ${PREFIX}_perf.txt" + +# 3) 요약: 쿼리 이름 / 결과 행 수 / 실행 시간 (2회차) +echo +grep -E '^\| === |rows? in set|Empty set' "${PREFIX}_perf.txt" | paste - - - | grep 'round 2' \ + | sed -E 's/\| === round 2: (.*) === \|/\1/; s/\t1 row in set \([0-9.]+ sec\)//' diff --git a/doc/plan/db/260915-improve-production/result/summarize_analyze.py b/doc/plan/db/260915-improve-production/result/summarize_analyze.py new file mode 100644 index 0000000..fb1af87 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/summarize_analyze.py @@ -0,0 +1,18 @@ +# 사용법: python3 summarize_analyze.py baseline [after] +import json +import sys + +decoder = json.JSONDecoder() +for prefix in sys.argv[1:]: + text = open(f"{prefix}_analyze.txt", encoding="utf-8").read() + names = [line.split("=== ")[1].split(" ===")[0] for line in text.splitlines() if line.startswith("=== ")] + docs, pos = [], 0 + while True: + start = text.find("{", pos) + if start < 0: + break + doc, pos = decoder.raw_decode(text, start) + docs.append(doc) + for name, doc in zip(names, docs): + block = doc["query_block"] + print(f"{prefix:8} {name:16} server_time_ms={block.get('r_total_time_ms')}") diff --git a/doc/plan/db/260915-improve-production/result/summarize_explain.py b/doc/plan/db/260915-improve-production/result/summarize_explain.py new file mode 100644 index 0000000..daae2c7 --- /dev/null +++ b/doc/plan/db/260915-improve-production/result/summarize_explain.py @@ -0,0 +1,40 @@ +# 사용법: python3 summarize_explain.py baseline [after] +import glob +import json +import sys + + +def find_table(node): + if isinstance(node, dict): + if isinstance(node.get("table"), dict): + return node["table"] + children = node.values() + elif isinstance(node, list): + children = node + else: + return None + for child in children: + found = find_table(child) + if found: + return found + return None + + +def keys_of(node): + if isinstance(node, dict): + for k, v in node.items(): + if k == "key": + yield v + else: + yield from keys_of(v) + elif isinstance(node, list): + for v in node: + yield from keys_of(v) + + +for prefix in sys.argv[1:]: + for path in sorted(glob.glob(f"{prefix}_explain_*.json")): + with open(path, encoding="utf-8") as f: + table = find_table(json.load(f)) + key = table.get("key") or " ∩ ".join(keys_of(table.get("index_merge", {}))) or "-" + print(f"{path:40} access_type={str(table.get('access_type')):12} key={key:28} rows={table.get('rows')}")