현재 서비스중인 t3a.medium 실행 지표 상태 확인

This commit is contained in:
2026-09-18 12:30:12 +09:00
parent 03eea2098e
commit b18612277c
18 changed files with 14858 additions and 68 deletions
@@ -0,0 +1,907 @@
# 운영 서버 부하·용량 확인 방법 (260916)
> 서비스 중인 초코마에 운영 서버(AWS EC2 + MariaDB)에서 아래 3가지를 확인하기 위한 **방법 모음**입니다.
> 각 항목마다 여러 방법을 나열했으니, 권한·설치 가능 여부·측정 기간에 맞춰 골라서 쓰세요.
>
> | 확인 항목 | 문서 위치 |
> |---|---|
> | ① EC2 인스턴스에서 MariaDB에 걸리는 성능 부하 | [A. MariaDB 성능 부하 측정](#a-mariadb-성능-부하-측정) |
> | ② 접속 서비스(chocomae / chocoadmin)별 pool 할당·부하 | [B. 접속 서비스별 연결 pool·부하 측정](#b-접속-서비스별-연결-pool부하-측정) |
> | ③ `best_record` 등 기록 테이블에 데이터가 몇 개까지 들어가는지 | [C. 테이블 최대 저장 가능 행 수](#c-테이블-최대-저장-가능-행-수) |
>
> ⚠️ 이 문서는 **측정 방법만** 다룹니다. 실제 값은 `result/`에 저장하고, 결론은 [90-결과-기록-양식](#90-결과-기록-양식)에 채웁니다.
---
## 0. 사전 준비
### 0-1. 용어 정리
- 질문의 **`best_score` 테이블**은 실제 스키마에 없습니다. 점수/기록을 담는 테이블은 아래 4개입니다. 이 문서에서는 이 4개를 "기록 테이블"로 부릅니다.
| 테이블 | 내용 | PK |
|---|---|---|
| `best_record` | 게임 플레이 기록 (가장 큼, 약 121만 행) | `BestRecordID INT UNSIGNED AUTO_INCREMENT` |
| `app_highest_record` | 앱별 최고 기록 | `AppHighestRecordID INT UNSIGNED AUTO_INCREMENT` |
| `typing_exam_record` | 타자 시험 기록 (약 13만 행) | `TypingExamRecordID INT UNSIGNED AUTO_INCREMENT` |
| `typing_exam_highest_record` | 타자 시험 최고 기록 | `TypingExamHighestRecordID INT UNSIGNED AUTO_INCREMENT` |
### 0-2. 접속 경로 3가지
측정 방법마다 필요한 접속 경로가 다릅니다.
| 경로 | 접속 방법 | 할 수 있는 것 |
|---|---|---|
| **(가) EC2 SSH** | `ssh ubuntu@13.124.33.235` (chocomae.jisangs.com) | OS 지표(CPU/메모리/디스크 I/O), 설정 파일, 로그 파일, 로컬 mariadb 접속 |
| **(나) 원격 DB 클라이언트** | `mariadb -h chocomae.jinaju.com -P 3306 -u <계정> -p chocomae` | SQL로 가능한 모든 측정 (상태 변수, PROCESSLIST, EXPLAIN) |
| **(다) 시놀로지 SSH** | `ssh happyhome@jisangs.synology.me` | chocoadmin 컨테이너 설정·로그, 시놀로지→운영 DB 연결 확인 |
> ⚠️ 2026-09-15 작업에 쓰던 임시 계정 `jisangs`는 작업 후 **삭제**했습니다. 이번 측정에도 조회 전용 계정이 필요하니, 아래 중 하나를 먼저 정하세요.
>
> 1. 관리자(root) 계정으로 직접 측정 (가장 간단, 권한 문제 없음)
> 2. 측정 전용 임시 계정을 다시 만들고 끝나면 삭제
> ```sql
> -- 관리자 계정으로 실행
> CREATE USER 'perfcheck'@'182.217.174.221' IDENTIFIED BY '<임시 비밀번호>';
> GRANT SELECT, PROCESS, REPLICATION CLIENT ON *.* TO 'perfcheck'@'182.217.174.221';
> GRANT SELECT ON mysql.slow_log TO 'perfcheck'@'182.217.174.221';
> -- 측정 종료 후
> -- DROP USER 'perfcheck'@'182.217.174.221';
> ```
> 3. EC2에 SSH로 들어가 `localhost` 소켓으로 접속 (원격 계정을 새로 만들지 않아도 됨)
### 0-3. 측정에 필요한 권한
| 측정 내용 | 필요 권한 |
|---|---|
| `SHOW GLOBAL STATUS`, `SHOW VARIABLES`, `information_schema.TABLES` | 기본 (일반 계정 가능) |
| `SHOW FULL PROCESSLIST` (다른 계정 세션까지 보기), `SHOW ENGINE INNODB STATUS` | `PROCESS` |
| `mysql.slow_log` 조회 | `mysql.slow_log` SELECT |
| `SET GLOBAL ...` (slow log 켜기, `userstat` 켜기) | `SUPER` |
| OS 지표, 설정 파일, 로그 파일 | EC2 SSH (`ubuntu` 계정, 일부는 `sudo`) |
### 0-4. 결과 저장 위치
```bash
cd doc/plan/db/260916-load-and-capacity-check/result
```
> 개인정보·비밀번호 해시가 섞일 수 있는 파일(`*grants*`, 덤프)은 커밋하지 마세요. 2026-09-15 작업의 관례를 따릅니다.
### 0-5. MariaDB가 도커인지 호스트 설치인지 먼저 확인
문서마다 명령어가 달라지므로 **가장 먼저** 확인합니다. (EC2 SSH)
```bash
sudo docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}'
systemctl status mariadb --no-pager | head -5
ls -l /var/lib/mysql | head -5
```
- 도커면: 이후 모든 `mariadb ...` 명령 앞에 `sudo docker exec -i <컨테이너명> ` 을 붙입니다.
- 호스트 설치면: 명령을 그대로 씁니다. (README 기준 설정은 `/etc/mysql`, 로그는 `/var/log/mysql`)
---
## A. MariaDB 성능 부하 측정
> "지금 이 EC2에서 MariaDB가 얼마나 힘들어하는가"를 보는 방법들입니다.
> **OS 레벨(A-1)** 과 **DB 레벨(A-2~A-5)** 을 같이 봐야 원인을 가릴 수 있습니다.
### A-0. 방법 비교
| 방법 | 보는 것 | 설치 필요 | 측정 부하 | 지속 관찰 | 추천도 |
|---|---|---|---|---|---|
| A-1 OS 지표 | CPU·메모리·디스크 I/O·스왑 | 없음(일부 `sysstat`) | 매우 낮음 | 스냅샷~단기 | ★★★ |
| A-2 상태 변수 델타 | QPS, 버퍼 풀 적중률, 임시 테이블, 잠금 | 없음 | 매우 낮음 | 단기 | ★★★ |
| A-3 PROCESSLIST·INNODB STATUS | 지금 실행 중인 쿼리, 대기·잠금 | 없음 | 매우 낮음 | 스냅샷 | ★★★ |
| A-4 slow query log | 느린 쿼리 목록·빈도 | 없음 | 낮음(설정 주의) | 장기 | ★★★ |
| A-5 userstat / performance_schema | 쿼리 다이제스트별·계정별 누적 부하 | 없음(설정 변경) | 낮음~중간 | 장기 | ★★☆ |
| A-6 모니터링 도구 | 시계열 그래프, 알림 | 있음 | 낮음 | 상시 | ★★☆ |
**추천 조합:** 먼저 A-1 + A-2 + A-3으로 현재 상태를 잡고, A-4를 24시간 돌려 실제 서비스 쿼리를 확인합니다. 상시 감시가 필요해지면 A-6.
---
### 방법 A-1. OS 레벨 지표 (EC2 SSH)
**A-1-1. 한 번에 훑기**
```bash
# 인스턴스 사양·부하 평균
nproc; free -h; uptime
# 프로세스별 CPU·메모리 상위 10개 (mysqld가 몇 위인지)
ps -eo pid,comm,pcpu,pmem,rss --sort=-pcpu | head -11
```
**A-1-2. 실시간 관찰**
```bash
top -b -n 3 -d 5 | grep -E 'Cpu|Mem|mysqld' > os_top.txt
cat os_top.txt
```
**A-1-3. CPU·디스크 I/O 시계열 (sysstat 필요)**
```bash
sudo apt-get install -y sysstat # 이미 있으면 생략
# 5초 간격 12회 = 1분치 CPU·메모리·스왑
vmstat 5 12 > os_vmstat.txt
# 5초 간격 12회 디스크 I/O (await, %util 확인)
iostat -dx 5 12 > os_iostat.txt
# mysqld 프로세스만 따로
pidstat -p $(pgrep -o mysqld) 5 12 > os_pidstat_mysqld.txt
```
**판단 기준**
| 지표 | 정상 | 주의 |
|---|---|---|
| `vmstat` `r`(실행 대기) | vCPU 수 이하 | vCPU 수보다 계속 큼 → CPU 포화 |
| `vmstat` `si/so`(스왑) | 0 | 0보다 큼 → 메모리 부족, DB 성능 급락 |
| `iostat` `%util` | 50% 이하 | 90% 이상 지속 → 디스크 I/O 병목 |
| `iostat` `await` | 10ms 이하(EBS gp3) | 수십 ms 이상 → I/O 대기 |
| `free -h` `available` | 여유 있음 | 0에 가까움 → 버퍼 풀 축소 검토 |
**A-1-4. 도커로 돌고 있다면**
```bash
sudo docker stats --no-stream
```
**A-1-5. AWS CloudWatch (SSH 없이 확인)**
EC2 콘솔 → 대상 인스턴스 → **모니터링** 탭에서 `CPUUtilization`, `EBSReadOps`/`EBSWriteOps`, `NetworkIn/Out`을 기간별로 봅니다. 기본 지표는 5분 간격이며, 메모리·디스크 사용률은 CloudWatch Agent를 설치해야 나옵니다. **과거 추이**를 보는 데 가장 편합니다.
---
### 방법 A-2. MariaDB 상태 변수 델타 측정
절대 누적값은 서버 기동 이후 합계라 의미가 약합니다. **일정 시간 동안의 증가량(델타)** 을 봐야 합니다.
**A-2-1. 가장 간단한 방법 — `mariadb-admin`의 상대값 출력**
```bash
# 10초 간격 6회, 이전 값 대비 증가량만 출력
mariadb-admin -h chocomae.jinaju.com -u <계정> -p extended-status -i 10 -c 6 -r \
| grep -vE '\|\s+0\s+\|' > db_status_delta.txt
cat db_status_delta.txt
```
**A-2-2. 요약 한 줄 보기**
```bash
# 5초 간격으로 Queries/초, Threads, Opens, Slow queries 표시 (Ctrl+C로 종료)
mariadb-admin -h chocomae.jinaju.com -u <계정> -p status -i 5
```
**A-2-3. 직접 SQL로 60초 델타 뽑기**
```bash
mariadb -h chocomae.jinaju.com -u <계정> -p chocomae -N -B -e "
SHOW GLOBAL STATUS;" > st1.txt
sleep 60
mariadb -h chocomae.jinaju.com -u <계정> -p chocomae -N -B -e "
SHOW GLOBAL STATUS;" > st2.txt
join st1.txt st2.txt | awk '{d=$3-$2; if (d>0) printf "%-40s %12d\n", $1, d}' \
| sort -k2 -nr | head -40 > db_status_delta_60s.txt
cat db_status_delta_60s.txt
```
**A-2-4. 꼭 봐야 할 값**
| 상태 변수 | 의미 | 해석 |
|---|---|---|
| `Questions`, `Com_select`, `Com_insert` | 처리한 쿼리 수 | 60초 델타 ÷ 60 = QPS |
| `Threads_connected` / `Threads_running` | 연결 수 / 실제 실행 중 | `Threads_running`이 지속적으로 vCPU 수 이상이면 과부하 |
| `Innodb_buffer_pool_read_requests` vs `Innodb_buffer_pool_reads` | 논리 읽기 vs 디스크 읽기 | 적중률 = (1 - reads/read_requests) × 100, **99% 미만이면 버퍼 풀 부족 의심** |
| `Created_tmp_disk_tables` | 디스크 임시 테이블 | 증가하면 `GROUP BY`/`ORDER BY`가 디스크로 넘어감 |
| `Select_scan`, `Select_full_join` | 풀 스캔 쿼리 수 | 증가하면 인덱스 미사용 쿼리 존재 |
| `Slow_queries` | 느린 쿼리 수 | A-4와 함께 확인 |
| `Table_locks_waited`, `Innodb_row_lock_waits`, `Innodb_row_lock_time_avg` | 잠금 대기 | 0에 가까워야 정상 |
| `Aborted_connects`, `Connection_errors_max_connections` | 연결 실패 | 0보다 크면 B 항목에서 원인 추적 |
**A-2-5. 버퍼 풀 적중률·크기 한 번에 계산**
```bash
mariadb -h chocomae.jinaju.com -u <계정> -p -e "
SELECT
(SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_VARIABLES
WHERE VARIABLE_NAME='innodb_buffer_pool_size')/1024/1024 AS buffer_pool_mb,
(SELECT ROUND(SUM(DATA_LENGTH+INDEX_LENGTH)/1024/1024,1) FROM information_schema.TABLES
WHERE TABLE_SCHEMA='chocomae') AS chocomae_total_mb,
ROUND(100 - (
(SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME='Innodb_buffer_pool_reads') /
(SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME='Innodb_buffer_pool_read_requests')
) * 100, 3) AS hit_rate_pct;" > db_buffer_pool.txt
cat db_buffer_pool.txt
```
> `chocomae_total_mb`가 `buffer_pool_mb`보다 작으면 데이터 전체가 메모리에 올라가므로 디스크 I/O 부하는 거의 없습니다. 이 값은 C-3(실용적 상한 계산)에서 다시 씁니다.
---
### 방법 A-3. 실행 중인 쿼리·잠금 관찰
**A-3-1. 지금 무엇이 돌고 있는가**
```bash
mariadb -h chocomae.jinaju.com -u <계정> -p -e "
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, LEFT(INFO,120) AS query
FROM information_schema.PROCESSLIST
WHERE COMMAND <> 'Sleep'
ORDER BY TIME DESC;"
```
**A-3-2. 1초 간격으로 반복 관찰 (부하가 걸리는 시간대에)**
```bash
for i in $(seq 1 60); do
date +%FT%T
mariadb -h chocomae.jinaju.com -u <계정> -p<비밀번호> -N -B -e "
SELECT COUNT(*) AS running FROM information_schema.PROCESSLIST WHERE COMMAND<>'Sleep';"
sleep 1
done > db_running_1min.txt
```
> 비밀번호를 명령줄에 쓰면 셸 히스토리에 남습니다. `~/.my.cnf`(`[client]` 섹션)에 적어두고 `-p`를 빼는 편이 안전합니다.
**A-3-3. InnoDB 내부 상태 (잠금·대기·I/O)**
```bash
mariadb -h chocomae.jinaju.com -u <계정> -p -e "SHOW ENGINE INNODB STATUS\G" > db_innodb_status.txt
grep -A12 'TRANSACTIONS' db_innodb_status.txt
grep -A10 'BUFFER POOL AND MEMORY' db_innodb_status.txt
grep -A8 'LATEST DETECTED DEADLOCK' db_innodb_status.txt
```
**A-3-4. 대기 중인 잠금만 콕 집어 보기**
```sql
-- MariaDB 10.5+ (information_schema.INNODB_TRX + metadata lock 정보)
SELECT trx_id, trx_state, trx_started, trx_wait_started, trx_mysql_thread_id, LEFT(trx_query,100)
FROM information_schema.INNODB_TRX
ORDER BY trx_started;
SELECT * FROM information_schema.METADATA_LOCK_INFO; -- 플러그인 설치 시
```
---
### 방법 A-4. Slow query log 분석
2026-09-15 작업 때 이미 켰던 경로라 재사용이 쉽습니다.
**A-4-1. 현재 설정 확인**
```bash
mariadb -h chocomae.jinaju.com -u <계정> -p -e "
SHOW GLOBAL VARIABLES WHERE Variable_name IN
('slow_query_log','slow_query_log_file','long_query_time','log_output',
'log_queries_not_using_indexes','log_slow_admin_statements','min_examined_row_limit');" > slow_log_settings.txt
cat slow_log_settings.txt
```
**A-4-2. 측정 기간에만 임계값 낮추기 (`SUPER` 필요)**
```sql
-- 원래 값을 먼저 기록해 두고 실행
SET GLOBAL long_query_time = 0.5; -- 0.5초 이상 쿼리를 기록
SET GLOBAL slow_query_log = ON;
-- 측정 종료 후 원래 값으로 복원 (예: 3초)
-- SET GLOBAL long_query_time = 3;
```
> ⚠️ `log_queries_not_using_indexes`는 켜지 마세요. 인덱스를 안 쓰는 모든 쿼리가 기록돼 로그가 폭증합니다. `log_output`에 `TABLE`이 포함돼 있으면 `mysql.slow_log`가 계속 커지고 자동으로 비워지지 않습니다.
**A-4-3. 파일 로그 분석 (EC2 SSH)**
```bash
# 요약: 실행 시간 합계가 큰 순서로 정렬
sudo mariadb-dumpslow -s t -t 20 /var/log/mysql/mariadb-slow.log > slow_top20.txt
cat slow_top20.txt
# 더 정밀한 분석이 필요하면 (percona-toolkit 설치 필요)
sudo apt-get install -y percona-toolkit
sudo pt-query-digest /var/log/mysql/mariadb-slow.log > slow_digest.txt
```
**A-4-4. 테이블 로그 분석 (`log_output`에 `TABLE`이 있을 때)**
```sql
-- 일자별 건수
SELECT DATE(start_time) AS d, COUNT(*) AS cnt, ROUND(MAX(query_time_sec),1) AS max_sec
FROM (SELECT start_time, TIME_TO_SEC(query_time) AS query_time_sec FROM mysql.slow_log) t
GROUP BY d ORDER BY d;
-- 계정·호스트별 느린 쿼리 (B 항목에서 서비스 구분에 활용)
SELECT user_host, COUNT(*) AS cnt, ROUND(AVG(TIME_TO_SEC(query_time)),2) AS avg_sec
FROM mysql.slow_log GROUP BY user_host ORDER BY cnt DESC;
```
> 2026-09-15 측정에서는 slow log에 **애플리케이션 쿼리가 한 건도 없었고**, 매일 04:00 백업 덤프의 `best_record` 전체 SELECT(약 5초)만 기록됐습니다. 이번에도 같은 항목이 계속 잡히니 성능 저하로 오해하지 마세요.
---
### 방법 A-5. 누적 통계 (userstat / performance_schema)
**A-5-1. MariaDB `userstat` (가볍고 설치 불필요, `SUPER` 필요)**
```sql
SET GLOBAL userstat = 1; -- 켠 시점부터 누적 (재시작하면 꺼짐)
-- 잠시 서비스 트래픽을 받은 뒤
SELECT * FROM information_schema.CLIENT_STATISTICS\G -- 접속 호스트별
SELECT * FROM information_schema.USER_STATISTICS\G -- 계정별
SELECT * FROM information_schema.TABLE_STATISTICS
WHERE TABLE_SCHEMA='chocomae' ORDER BY ROWS_READ DESC;
SELECT * FROM information_schema.INDEX_STATISTICS
WHERE TABLE_SCHEMA='chocomae' ORDER BY ROWS_READ DESC;
FLUSH CLIENT_STATISTICS; -- 구간 측정을 하려면 초기화 후 다시 수집
```
- **B 항목(서비스별 부하)에 가장 직접적인 방법**입니다. `CLIENT_STATISTICS`는 접속 호스트별 `CONCURRENT_CONNECTIONS`, `CONNECTED_TIME`, `BUSY_TIME`, `ROWS_READ`, `SELECT_COMMANDS` 등을 보여줍니다.
- `INDEX_STATISTICS`에 안 나오는 인덱스는 **한 번도 안 쓰인 인덱스**입니다. 불필요한 인덱스 정리 판단에 쓰세요.
- 영구 적용하려면 설정 파일에 `userstat=1`을 넣고 재시작해야 합니다.
**A-5-2. performance_schema 다이제스트 (기본 OFF)**
```sql
SHOW VARIABLES LIKE 'performance_schema'; -- OFF면 아래는 못 씀
```
OFF인 경우 `/etc/mysql/my.cnf`(또는 `conf.d/*.cnf`)에 `performance_schema=ON`을 넣고 **재시작**해야 합니다. 서비스 중단이 생기므로 급하지 않으면 A-4/A-5-1로 대체하세요.
```sql
-- 총 실행 시간이 큰 쿼리 유형 순
SELECT SCHEMA_NAME, DIGEST_TEXT, COUNT_STAR,
ROUND(SUM_TIMER_WAIT/1e12,2) AS total_sec,
ROUND(AVG_TIMER_WAIT/1e9,2) AS avg_ms,
SUM_ROWS_EXAMINED, SUM_ROWS_SENT
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME='chocomae'
ORDER BY SUM_TIMER_WAIT DESC LIMIT 20;
-- 계정별 부하 (B 항목에도 사용)
SELECT * FROM performance_schema.events_statements_summary_by_account_by_event_name
WHERE EVENT_NAME='statement/sql/select' ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;
```
---
### 방법 A-6. 상시 모니터링 도구
| 도구 | 설치 위치 | 특징 |
|---|---|---|
| `mytop` / `innotop` | EC2 (`apt-get install mytop percona-toolkit`) | 터미널 실시간 대시보드. QPS·슬로우·연결·실행 쿼리를 한 화면에서 봄. 가장 가볍게 시작 가능 |
| Netdata | EC2 (에이전트 1개) | 설치 5분, MySQL 플러그인 자동 인식, 웹 UI로 시계열 확인 |
| Prometheus + `mysqld_exporter` + Grafana | 수집기는 시놀로지, exporter는 EC2 | 장기 보관·대시보드·알림. 이미 시놀로지 도커가 있으니 구성 부담이 적음 |
| Percona PMM | 도커 1개 + 에이전트 | MySQL 전용 대시보드·쿼리 분석기(QAN)가 강력. 리소스는 좀 더 씀 |
| AWS CloudWatch (+Agent) | EC2 | 인스턴스 지표·알림. DB 내부 지표는 안 보임 |
**mytop 예시**
```bash
sudo apt-get install -y mytop
mytop -h 127.0.0.1 -u root -p --dbname chocomae
```
> **선택 가이드:** 이번처럼 "한 번 확인"이면 A-1~A-5로 충분합니다. "앞으로 계속 보겠다"면 `mysqld_exporter` + 시놀로지 Grafana 조합이 운영 부담 대비 효과가 가장 좋습니다.
---
## B. 접속 서비스별 연결 pool·부하 측정
### B-0. 먼저 알아야 할 구조
| 서비스 | 위치 | 접속 방식 | pool 존재 여부 |
|---|---|---|---|
| **chocomae** (PHP) | 같은 EC2 | `new mysqli($db_host, ...)` — [connect_db.php:17](../../../../src/web/server/setup/connect_db.php#L17), `localhost`(또는 도커 호스트명) | **pool 없음.** PHP 요청마다 새 연결을 맺고 요청이 끝나면 끊습니다. `p:` 접두사(persistent)를 쓰지 않으므로, 동시 연결 수의 상한은 사실상 **Apache의 동시 프로세스 수(MaxRequestWorkers)** 입니다 |
| **chocoadmin** (Next.js/Node) | 시놀로지 도커 | 인터넷 경유로 `chocomae.jinaju.com:3306` | Node의 MySQL 드라이버(`mysql2` 등)를 쓰면 **커넥션 풀 존재**. `connectionLimit` 기본값은 보통 10. 컨테이너/프로세스 수만큼 배수가 됨 |
| **배치/백업** | EC2 cron, 시놀로지 cron | 단발 연결 | 매일 04:00 풀백업이 대표적 |
**따라서 확인할 것은 3가지입니다.**
1. DB가 허용하는 상한 (`max_connections`)과 실제 최대 사용치 (`Max_used_connections`)
2. 출처별 현재·최대 연결 수 (EC2 로컬 vs 시놀로지 공인 IP)
3. 각 애플리케이션 쪽 설정값 (Apache 워커 수, Node pool `connectionLimit`)
### B-0-1. 방법 비교
| 방법 | 보는 것 | 서비스 구분 가능? | 필요 권한 |
|---|---|---|---|
| B-1 연결 상한·사용치 | 전체 연결 수, 한계 근접 여부 | ✗ | 기본 |
| B-2 PROCESSLIST 집계 | 출처 IP·계정별 현재 연결 수 | ○ (IP로) | `PROCESS` |
| B-3 userstat | 출처별 누적 연결 시간·쿼리 수·읽은 행 수 | ◎ (가장 정확) | `SUPER` + 조회 |
| B-4 계정 분리 | 서비스별 완전 분리 + 상한 제한 | ◎ | `CREATE USER`, `GRANT` |
| B-5 애플리케이션 설정 | pool 할당값 자체 | ◎ | 서버 접근 |
| B-6 네트워크 소켓 | TCP 연결 수·상태 | ○ (IP로) | EC2 SSH |
| B-7 주기 샘플링 | 시간대별 추이 | ○ | `PROCESS` + cron |
---
### 방법 B-1. 연결 상한과 실제 사용치
```bash
mariadb -h chocomae.jinaju.com -u <계정> -p -e "
SHOW GLOBAL VARIABLES WHERE Variable_name IN
('max_connections','max_user_connections','thread_handling','thread_pool_size',
'wait_timeout','interactive_timeout','back_log','table_open_cache','open_files_limit');
SHOW GLOBAL STATUS WHERE Variable_name IN
('Threads_connected','Threads_running','Threads_created','Threads_cached',
'Max_used_connections','Max_used_connections_time','Connections',
'Aborted_connects','Aborted_clients','Connection_errors_max_connections');" > conn_limits.txt
cat conn_limits.txt
```
**해석**
| 값 | 의미 |
|---|---|
| `Max_used_connections` / `max_connections` | **연결 여유율.** 80%를 넘으면 상한을 올리거나 pool을 줄여야 합니다 |
| `Max_used_connections_time` | 최고치를 찍은 시각 → 그 시간대에 무슨 작업이 있었는지 추적 |
| `Connection_errors_max_connections` | 0보다 크면 **연결 거부가 실제로 발생**한 것 |
| `Aborted_connects` | 인증 실패·타임아웃. 시놀로지↔AWS 인터넷 구간 문제의 신호일 수 있음 |
| `Threads_created``Connections`의 일부 | 스레드 캐시 부족 (`thread_cache_size` 조정 대상) |
| `wait_timeout` | pool의 유휴 연결이 이 시간을 넘기면 서버가 끊습니다. chocoadmin에서 간헐적 `ECONNRESET`/`Connection lost`가 난다면 1순위 원인 |
---
### 방법 B-2. PROCESSLIST로 출처별 집계
```bash
mariadb -h chocomae.jinaju.com -u <계정> -p -e "
SELECT USER,
SUBSTRING_INDEX(HOST, ':', 1) AS client_ip,
DB,
COUNT(*) AS conns,
SUM(COMMAND <> 'Sleep') AS active,
SUM(COMMAND = 'Sleep') AS idle,
MAX(TIME) AS max_idle_or_run_sec
FROM information_schema.PROCESSLIST
GROUP BY USER, client_ip, DB
ORDER BY conns DESC;" > conn_by_source.txt
cat conn_by_source.txt
```
**읽는 법**
- `client_ip``localhost`/`127.0.0.1`/도커 내부 IP(`172.x`) → **chocomae(PHP)**
- `client_ip`가 시놀로지 공인 IP → **chocoadmin**
- `idle`이 크고 `max_idle_or_run_sec`이 수백 초 → pool이 유휴 연결을 계속 붙잡고 있는 상태 (정상이지만 `max_connections` 여유를 잡아먹음)
- `active`가 계속 큼 → 실제 쿼리 부하
> 시놀로지 공인 IP를 모른다면 시놀로지에서 `curl -s ifconfig.me` 로 확인하세요. README 기준 출발지 IP는 `182.217.174.221`입니다.
---
### 방법 B-3. userstat으로 출처별 누적 부하 (가장 정확)
```sql
SET GLOBAL userstat = 1;
FLUSH CLIENT_STATISTICS; -- 구간 측정 시작점
-- (서비스 트래픽을 받는 동안 대기: 예를 들어 1시간)
SELECT CLIENT,
TOTAL_CONNECTIONS, CONCURRENT_CONNECTIONS,
ROUND(CONNECTED_TIME/60,1) AS connected_min,
ROUND(BUSY_TIME,1) AS busy_sec,
ROUND(CPU_TIME,1) AS cpu_sec,
ROWS_READ, ROWS_SENT, SELECT_COMMANDS, UPDATE_COMMANDS, OTHER_COMMANDS
FROM information_schema.CLIENT_STATISTICS
ORDER BY BUSY_TIME DESC;
```
- `BUSY_TIME`·`CPU_TIME` = **그 출처가 DB를 실제로 점유한 시간**입니다. "chocomae와 chocoadmin 중 어느 쪽이 DB를 더 쓰는가"에 대한 가장 직접적인 답입니다.
- `USER_STATISTICS`는 같은 형식을 **계정 단위**로 보여줍니다. B-4(계정 분리)를 먼저 하면 IP가 아니라 계정만으로 깔끔하게 구분됩니다.
---
### 방법 B-4. 서비스별 DB 계정 분리 + 연결 상한 지정 (구조 개선)
지금은 chocomae와 chocoadmin이 **같은 `chocomae` 계정**을 쓸 가능성이 큽니다(README 기준 계정은 `root`/`chocomae`/`backup` 3개). 계정을 나누면 측정과 제어가 동시에 쉬워집니다.
```sql
-- 관리자 계정으로 실행
CREATE USER 'chocoadmin'@'182.217.174.221' IDENTIFIED BY '<새 비밀번호>';
GRANT SELECT, INSERT, UPDATE, DELETE ON chocomae.* TO 'chocoadmin'@'182.217.174.221';
-- 이 계정 하나가 쓸 수 있는 동시 연결 수 상한 (폭주 시 서비스 보호)
ALTER USER 'chocoadmin'@'182.217.174.221' WITH MAX_USER_CONNECTIONS 20;
FLUSH PRIVILEGES;
SHOW GRANTS FOR 'chocoadmin'@'182.217.174.221';
```
- 계정을 나눈 뒤에는 B-2/B-3의 집계가 `USER` 기준으로 바로 읽힙니다.
- `MAX_USER_CONNECTIONS`를 걸어두면 chocoadmin 쪽 pool 설정 실수로 운영 서비스가 연결을 못 맺는 사고를 막습니다.
- ⚠️ 계정 변경은 chocoadmin 배포와 함께 진행해야 하니, 측정만이 목적이라면 B-2/B-3(IP 기준)으로 충분합니다.
---
### 방법 B-5. 애플리케이션 쪽 설정 확인 (pool 할당값 자체)
**B-5-1. chocomae (PHP / Apache) — EC2 SSH**
```bash
# 동시 처리 가능한 요청 수 = 사실상 최대 동시 DB 연결 수
apache2ctl -V | grep -i 'Server MPM'
grep -RnE 'MaxRequestWorkers|ServerLimit|StartServers|MaxConnectionsPerChild' /etc/apache2/mods-available/mpm_*.conf /etc/apache2/apache2.conf
# php-fpm을 쓴다면
grep -RnE '^(pm|pm\.max_children|pm\.start_servers|pm\.max_spare_servers)' /etc/php/*/fpm/pool.d/*.conf 2>/dev/null
# persistent connection 관련 설정 (현재 코드는 사용 안 함)
php -i | grep -iE 'mysqli.allow_persistent|mysqli.max_persistent|mysqli.max_links|default_socket_timeout'
```
> **판단:** `MaxRequestWorkers`(prefork 기본 150) ≤ `max_connections`여야 합니다. 그렇지 않으면 트래픽 급증 시 PHP가 "Too many connections"를 만납니다.
>
> 현재 코드는 요청마다 연결을 새로 맺습니다(`new mysqli`). 연결 비용이 부담되면 `p:localhost` 형태의 persistent 연결을 검토할 수 있지만, 연결이 프로세스에 남아 `max_connections`를 오래 점유하므로 **먼저 B-1의 여유율을 확인한 뒤** 결정하세요.
**B-5-2. chocoadmin (Next.js / Node) — 시놀로지 SSH**
```bash
cd /volume1/docker/service/jinaju/chocoadmin
# pool 설정값 찾기
grep -RniE 'createPool|connectionLimit|pool|queueLimit|connectTimeout|enableKeepAlive' \
--include='*.ts' --include='*.js' --include='*.mjs' . | grep -v node_modules | head -40
# 환경변수 (비밀번호는 출력하지 말 것)
grep -vE 'PASS|PWD|SECRET|TOKEN' .env.production
# 실행 중인 컨테이너·프로세스 수 (pool이 몇 배로 늘어나는지)
sudo docker ps --filter name=chocoadmin
sudo docker exec chocoadmin sh -c 'ps -eo pid,comm | head'
```
**확인 포인트**
| 항목 | 확인 내용 |
|---|---|
| `connectionLimit` | 명시하지 않았다면 `mysql2` 기본값 10 |
| 컨테이너/워커 수 | pool은 **프로세스마다 하나**입니다. Next.js를 여러 워커로 띄우면 `connectionLimit × 워커 수`가 실제 상한 |
| 모듈 스코프 재사용 | pool을 요청마다 만들면 연결이 계속 늘어납니다. 모듈 최상단에서 한 번만 생성하는지 확인 |
| `connectTimeout`, `enableKeepAlive` | 시놀로지→AWS 인터넷 구간이라 지연·끊김 대비 필요 |
| `wait_timeout`(B-1) 대비 유휴 시간 | 서버가 먼저 끊으면 죽은 연결을 재사용하다 오류가 납니다 |
**B-5-3. chocoadmin 쪽 실제 연결 확인 (시놀로지 SSH)**
```bash
# 컨테이너에서 운영 DB로 맺은 연결 수
sudo docker exec chocoadmin sh -c "ss -tn state established '( dport = :3306 )' 2>/dev/null | wc -l" \
|| sudo netstat -tn | grep ':3306' | wc -l
```
---
### 방법 B-6. 네트워크 소켓 레벨 확인 (EC2 SSH)
DB에 로그인하지 않고도 **누가 3306에 붙어 있는지** 셀 수 있습니다.
```bash
# 출처 IP별 3306 연결 수
sudo ss -tn state established '( sport = :3306 )' \
| awk 'NR>1 {split($5,a,":"); print a[1]}' | sort | uniq -c | sort -nr > conn_by_ip_ss.txt
cat conn_by_ip_ss.txt
# 프로세스까지 같이 보기
sudo ss -tnp state established '( sport = :3306 )' | head -30
# TIME_WAIT 등 상태별 집계 (연결을 자주 맺고 끊는지 확인 — PHP 특성상 많을 수 있음)
sudo ss -tan '( sport = :3306 )' | awk 'NR>1 {print $1}' | sort | uniq -c
```
> `TIME_WAIT`가 수천 개면 "요청마다 연결/해제"의 비용이 눈에 보이는 수준이라는 뜻입니다. persistent 연결이나 pool 도입을 검토할 근거가 됩니다.
---
### 방법 B-7. 시간대별 추이 샘플링 (권장: 24시간)
한 번의 스냅샷으로는 피크를 놓칩니다. cron으로 1분마다 찍어 CSV로 모읍니다.
```bash
# EC2에 저장: /home/ubuntu/conn_sample.sh
cat > /home/ubuntu/conn_sample.sh <<'EOS'
#!/bin/bash
TS=$(date +%FT%T)
mariadb -N -B -e "
SELECT '$TS', USER, SUBSTRING_INDEX(HOST,':',1), COUNT(*), SUM(COMMAND<>'Sleep')
FROM information_schema.PROCESSLIST GROUP BY USER, 3;" >> /home/ubuntu/conn_sample.tsv
mariadb -N -B -e "
SELECT '$TS','__global__','-',
(SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME='Threads_connected'),
(SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME='Threads_running');" \
>> /home/ubuntu/conn_sample.tsv
EOS
chmod +x /home/ubuntu/conn_sample.sh
# 1분마다 실행
( crontab -l 2>/dev/null; echo "* * * * * /home/ubuntu/conn_sample.sh" ) | crontab -
```
수집 후 정리:
```bash
# 시간대별 최대 연결 수
awk -F'\t' '$2=="__global__" {split($1,t,"T"); split(t[2],h,":"); \
key=t[1]" "h[1]"시"; if ($4>m[key]) m[key]=$4} END {for (k in m) print k, m[k]}' \
/home/ubuntu/conn_sample.tsv | sort > conn_hourly_peak.txt
cat conn_hourly_peak.txt
# 측정 끝나면 cron 제거
crontab -l | grep -v conn_sample.sh | crontab -
```
---
## C. 테이블 최대 저장 가능 행 수
> "`best_record`에 데이터가 몇 개까지 들어갈 수 있나"는 상한이 **세 겹**입니다. 셋 중 **가장 먼저 걸리는 것**이 실제 한계입니다.
>
> | 구분 | 상한 요인 | 대략적인 값 |
> |---|---|---|
> | C-1 논리적 상한 | PK `INT UNSIGNED AUTO_INCREMENT` 최대값 | **4,294,967,295 (약 42.9억)** |
> | C-2 물리적 상한 | InnoDB 테이블스페이스 / 디스크 여유 공간 | 디스크 여유 ÷ 행당 바이트 |
> | C-3 실용적 상한 | 버퍼 풀·인덱스 크기 대비 성능이 버티는 지점 | 보통 **위 둘보다 훨씬 먼저** 도달 |
### 방법 C-1. 논리적 상한 — AUTO_INCREMENT 소진까지
기록 테이블 4개 모두 PK가 `INT UNSIGNED AUTO_INCREMENT`이므로 **42.9억**이 상한입니다. 중요한 점은 **행을 삭제해도 AUTO_INCREMENT 값은 되돌아가지 않는다**는 것입니다. 즉 "현재 행 수"가 아니라 "지금까지 발급된 ID 값"이 기준입니다.
```bash
mariadb -h chocomae.jinaju.com -u <계정> -p -e "
SELECT TABLE_NAME,
TABLE_ROWS AS approx_rows,
AUTO_INCREMENT AS next_id,
4294967295 - AUTO_INCREMENT AS remaining_ids,
ROUND(AUTO_INCREMENT / 4294967295 * 100, 4) AS used_pct
FROM information_schema.TABLES
WHERE TABLE_SCHEMA='chocomae' AND AUTO_INCREMENT IS NOT NULL
ORDER BY used_pct DESC;" > capacity_auto_increment.txt
cat capacity_auto_increment.txt
```
**남은 기간 계산**
```bash
mariadb -h chocomae.jinaju.com -u <계정> -p chocomae -e "
SELECT DATE_FORMAT(RecordDateTime,'%Y-%m') AS ym, COUNT(*) AS cnt
FROM best_record
WHERE RecordDateTime >= DATE_SUB(CURDATE(), INTERVAL 13 MONTH)
GROUP BY ym ORDER BY ym;" > capacity_monthly_load.txt
cat capacity_monthly_load.txt
```
> 남은 기간 = `remaining_ids` ÷ (월 적재량 × 12)
>
> **2026-09-15 기준 개략 계산:** 월 평균 약 57,968행 → 연 약 69.6만 행. 42.9억 ÷ 69.6만 ≈ **6,000년 이상**.
> 지금 증가 속도가 100배가 되어도 60년이 넘습니다. → **AUTO_INCREMENT는 실질적인 제약이 아닙니다.**
>
> 단, 아래 경우는 소진이 빨라지니 `used_pct`를 연 1회 확인하세요.
> - `INSERT ... ON DUPLICATE KEY UPDATE` / `REPLACE`를 많이 쓰면 실패한 INSERT도 ID를 소비합니다
> - 대량 삭제 후 재적재를 반복하는 경우
>
> **넘어갈 조짐이 보이면:** `ALTER TABLE best_record MODIFY BestRecordID BIGINT UNSIGNED NOT NULL AUTO_INCREMENT;` (테이블 재구성이 필요해 시간과 디스크 여유가 많이 듭니다. 미리 계획하세요)
### 방법 C-2. 물리적 상한 — 디스크 여유 공간
**C-2-1. 현재 테이블 크기와 행당 바이트**
```bash
mariadb -h chocomae.jinaju.com -u <계정> -p -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,
ROUND((DATA_LENGTH+INDEX_LENGTH)/1024/1024,1) AS total_mb,
ROUND(DATA_FREE/1024/1024,1) AS free_mb,
ROUND((DATA_LENGTH+INDEX_LENGTH)/NULLIF(TABLE_ROWS,0), 1) AS bytes_per_row
FROM information_schema.TABLES
WHERE TABLE_SCHEMA='chocomae'
ORDER BY (DATA_LENGTH+INDEX_LENGTH) DESC;" > capacity_table_sizes.txt
cat capacity_table_sizes.txt
```
> `TABLE_ROWS`는 InnoDB 추정치입니다. 정확한 값이 필요하면 `SELECT COUNT(*) FROM best_record;`를 쓰되, 운영 시간대에는 부하를 고려하세요.
**C-2-2. 디스크 여유 (EC2 SSH)**
```bash
df -h /var/lib/mysql
sudo du -sh /var/lib/mysql
sudo du -sh /var/lib/mysql/chocomae/*.ibd 2>/dev/null | sort -h | tail -10
```
**C-2-3. 계산**
```
추가 가능 행 수 ≈ (디스크 여유 바이트 × 0.8) ÷ bytes_per_row
남은 기간(년) ≈ 추가 가능 행 수 ÷ (월 적재량 × 12)
```
> 0.8은 안전 계수입니다. 백업·바이너리 로그·임시 파일·`ALTER` 시 임시 테이블 공간을 남겨둬야 합니다. 특히 **인덱스 추가나 `BIGINT` 변경 같은 온라인 DDL은 테이블 크기만큼의 여유 공간을 추가로 요구**합니다.
>
> **2026-09-15 기준 개략 계산:** `best_record`는 약 121만 행에 data 72.5MB + index 72.1MB ≈ 145MB → **행당 약 125바이트**. 42.9억 행을 다 채우면 약 537GB가 필요합니다. 즉 **디스크가 AUTO_INCREMENT보다 훨씬 먼저 한계**입니다. 현재 `df -h` 결과로 실제 여유를 확인하세요.
**C-2-4. InnoDB 자체 상한(참고)**
- 테이블스페이스 최대 크기: 페이지 16KB 기준 **64TB** (`SHOW VARIABLES LIKE 'innodb_page_size'`로 확인)
- `innodb_file_per_table`이 ON이면 테이블마다 `.ibd` 파일 → 파일시스템 최대 파일 크기도 확인 대상 (ext4는 16TB)
- 실무에서 여기에 닿는 일은 없습니다. 디스크 여유(C-2-2)가 먼저 걸립니다.
### 방법 C-3. 실용적 상한 — 성능이 버티는 지점 (가장 중요)
디스크가 남아도 **인덱스가 버퍼 풀을 넘어가는 순간** 조회 성능이 급격히 나빠집니다. "몇 개까지 넣을 수 있나"의 현실적인 답은 대개 여기입니다.
```bash
mariadb -h chocomae.jinaju.com -u <계정> -p -e "
SELECT
ROUND((SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_VARIABLES
WHERE VARIABLE_NAME='innodb_buffer_pool_size')/1024/1024, 0) AS buffer_pool_mb,
ROUND((SELECT SUM(DATA_LENGTH+INDEX_LENGTH) FROM information_schema.TABLES
WHERE TABLE_SCHEMA='chocomae')/1024/1024, 1) AS chocomae_total_mb,
ROUND((SELECT SUM(INDEX_LENGTH) FROM information_schema.TABLES
WHERE TABLE_SCHEMA='chocomae')/1024/1024, 1) AS chocomae_index_mb;" \
> capacity_buffer_pool.txt
cat capacity_buffer_pool.txt
```
**판단 기준**
| 상태 | 의미 | 조치 |
|---|---|---|
| `chocomae_total_mb` < `buffer_pool_mb` | 데이터 전체가 메모리에 상주. 디스크 읽기 거의 없음 | 여유 있음 |
| `chocomae_index_mb` < `buffer_pool_mb` < `chocomae_total_mb` | 인덱스는 메모리, 데이터는 일부만 | 정상 범위. 버퍼 풀 적중률(A-2-5) 감시 |
| `buffer_pool_mb` < `chocomae_index_mb` | 인덱스조차 다 못 올림 | **성능 급락 구간.** 버퍼 풀 증설 / 아카이빙 / 파티셔닝 필요 |
**한계 도달 시점 추정**
```
현재 인덱스 크기 ÷ 현재 행 수 = 행당 인덱스 바이트
(버퍼 풀 크기 × 0.8 - 현재 인덱스 크기) ÷ 행당 인덱스 바이트 = 여유 행 수
여유 행 수 ÷ (월 적재량 × 12) = 남은 기간(년)
```
> 인덱스 크기는 2026-09-15 작업에서 인덱스 5개 + UNIQUE 2개를 추가한 뒤의 값으로 다시 재야 정확합니다. 인덱스가 늘면 행당 바이트도 늘어납니다.
**추가 확인: 실제 메모리 여유 (EC2 SSH)**
```bash
free -h
# 버퍼 풀을 키울 때는 (버퍼 풀 + 연결당 버퍼 × max_connections + OS) < 물리 메모리 여야 합니다
mariadb -e "SHOW VARIABLES WHERE Variable_name IN
('innodb_buffer_pool_size','sort_buffer_size','join_buffer_size','read_buffer_size',
'read_rnd_buffer_size','tmp_table_size','max_heap_table_size','max_connections');"
```
### 방법 C-4. 증가 속도 기반 예측 (한 번에 정리)
아래 쿼리 하나로 세 상한을 같이 뽑을 수 있습니다.
```bash
mariadb -h chocomae.jinaju.com -u <계정> -p chocomae -e "
SELECT
t.TABLE_NAME,
t.TABLE_ROWS AS approx_rows,
t.AUTO_INCREMENT AS next_id,
ROUND((t.DATA_LENGTH+t.INDEX_LENGTH)/1024/1024,1) AS total_mb,
ROUND((t.DATA_LENGTH+t.INDEX_LENGTH)/NULLIF(t.TABLE_ROWS,0),1) AS bytes_per_row,
ROUND((4294967295 - t.AUTO_INCREMENT) / (:monthly_rows * 12), 1) AS years_to_autoinc_limit,
ROUND((4294967295 - t.AUTO_INCREMENT)
* (t.DATA_LENGTH+t.INDEX_LENGTH)/NULLIF(t.TABLE_ROWS,0)
/1024/1024/1024, 1) AS gb_needed_for_autoinc_limit
FROM information_schema.TABLES t
WHERE t.TABLE_SCHEMA='chocomae'
AND t.TABLE_NAME IN ('best_record','app_highest_record',
'typing_exam_record','typing_exam_highest_record');" > capacity_projection.txt
cat capacity_projection.txt
```
> `:monthly_rows` 자리에는 C-1에서 구한 **월 평균 적재량**(2026-09 기준 약 57968)을 숫자로 직접 넣으세요. MariaDB CLI에는 바인드 변수가 없습니다.
### 방법 C-5. 상한에 가까워졌을 때의 대응 (기존 문서 연결)
| 대응 | 효과 | 문서 |
|---|---|---|
| 인덱스·쿼리 최적화 | 같은 데이터로 더 오래 버팀 | [02-option1-index-and-query-rewrite.md](../02-option1-index-and-query-rewrite.md) |
| 일별 요약 테이블 | 랭킹 조회가 원본 테이블을 안 읽음 | [03-option2-daily-summary-tables.md](../03-option2-daily-summary-tables.md) |
| 아카이빙 (오래된 기록 분리) | 활성 테이블 크기 감소 → 버퍼 풀 여유 | [04-option3-archiving.md](../04-option3-archiving.md) |
| 파티셔닝 (날짜 기준) | 조회 시 읽는 파티션만 접근 | [05-option4-partitioning.md](../05-option4-partitioning.md) |
| DB 분리 (RDS 등) | 웹/DB 리소스 경합 해소 | [09-aws-db-separation.md](../09-aws-db-separation.md) |
| 시놀로지 복제 | 읽기 부하(chocoadmin) 분산 | [08-synology-replication-setup.md](../08-synology-replication-setup.md) |
> chocoadmin이 DB를 많이 읽는 것으로 B-3에서 확인된다면, **복제본을 두고 chocoadmin만 복제본을 보게 하는 것**이 가장 효과가 큰 조치입니다.
---
## 90. 결과 기록 양식
측정 후 아래 표를 채워 넣으세요.
### A. MariaDB 부하
| 항목 | 측정값 | 측정 방법 | 판단 |
|---|---|---|---|
| vCPU / 메모리 | | A-1-1 | |
| CPU 사용률 (peak) | | A-1-3 / A-1-5 | |
| 스왑 사용 | | A-1-3 | 0이어야 정상 |
| 디스크 %util / await | | A-1-3 | |
| QPS (평균 / peak) | | A-2-1 | |
| `Threads_running` (peak) | | A-2-4 | vCPU 이하면 정상 |
| 버퍼 풀 적중률 | | A-2-5 | 99% 이상 |
| 디스크 임시 테이블 (60초) | | A-2-3 | |
| 잠금 대기 | | A-2-4 / A-3-3 | |
| 느린 쿼리 (24시간) | | A-4 | 백업 SELECT 제외 |
### B. 서비스별 연결
| 항목 | chocomae (EC2) | chocoadmin (시놀로지) | 측정 방법 |
|---|---|---|---|
| 접속 방식 | `new mysqli` (pool 없음) | Node pool | B-0 |
| 설정상 최대 연결 | Apache MaxRequestWorkers = | connectionLimit × 워커 수 = | B-5 |
| 현재 연결 수 | | | B-2 |
| 최대 연결 수 (24h) | | | B-7 |
| 유휴 연결 수 | | | B-2 |
| 누적 BUSY_TIME | | | B-3 |
| 읽은 행 수 | | | B-3 |
| 전체 `max_connections` | | 여유율: | B-1 |
| 연결 거부 발생 | | | B-1 |
### C. 테이블 용량
| 테이블 | 현재 행 수 | next_id | AUTO_INCREMENT 사용률 | total_mb | 행당 바이트 | 디스크 기준 남은 행 | 남은 기간 |
|---|---|---|---|---|---|---|---|
| best_record | | | | | | | |
| app_highest_record | | | | | | | |
| typing_exam_record | | | | | | | |
| typing_exam_highest_record | | | | | | | |
| 항목 | 값 |
|---|---|
| 월 평균 적재량 | |
| `innodb_buffer_pool_size` | |
| chocomae 전체 크기 (data+index) | |
| 디스크 여유 (`/var/lib/mysql`) | |
| **가장 먼저 걸리는 상한** | (AUTO_INCREMENT / 디스크 / 버퍼 풀) |
| **예상 도달 시점** | |
---
## 91. 주의사항
1. **측정 자체가 부하입니다.** `SELECT COUNT(*)``SHOW ENGINE INNODB STATUS`를 반복 실행하지 마세요. 정확한 행 수가 꼭 필요할 때만 한 번 실행합니다.
2. **`SET GLOBAL`로 바꾼 값은 반드시 복원합니다.** 변경 전 값을 파일로 먼저 저장하세요 (`long_query_time`, `slow_query_log`, `userstat`).
3. **`log_queries_not_using_indexes`는 켜지 않습니다.** 로그가 폭증합니다.
4. **cron 샘플링(B-7)은 측정이 끝나면 지웁니다.** 지우지 않으면 TSV가 무한히 커집니다.
5. **매일 04:00 풀백업**이 돌고 있습니다. 이 시간대 측정값은 백업 부하가 섞이니 따로 표시하세요.
6. **비밀번호를 명령줄에 쓰지 마세요.** 셸 히스토리와 `ps` 출력에 남습니다. `~/.my.cnf`(권한 600)를 쓰세요.
```ini
[client]
host=chocomae.jinaju.com
user=perfcheck
password=<비밀번호>
```
7. **임시 계정·임시 권한은 측정 후 회수합니다.** (`DROP USER`, `REVOKE`)
8. **README에 평문으로 남아 있는 DB 비밀번호**(`root`/`chocomae`/`backup`이 모두 같은 값)는 이 작업과 별개로 교체가 필요합니다.