LLM WikiAccess-protected knowledge portal
← 스터디 홈
6편 · 약 24분

용량 계획: CPU, memory, IOPS, connection, disk growth 예측

장애가 나기 전에 알아야 한다

디스크가 꽉 차서 PostgreSQL이 새 WAL을 쓰지 못한 순간, 서비스는 쓰기 불가 상태가 된다. max_connections에 도달해 "Too many connections" 에러가 쏟아지는 순간, 앱 서버는 DB에 붙지 못한다. 이 두 가지 장애의 공통점은 미리 알 수 있었다는 것이다.

용량 계획(capacity planning)은 현재 사용량을 측정하고, 성장 추세를 모델링해, 자원이 임계치에 도달하기 전에 대응하는 활동이다. 장애 대응보다 훨씬 싸다. 그리고 대부분의 경우, 다음 6개월치 성장은 지난 6개월 데이터에 이미 들어 있다.


네 가지 자원 축

CPU 쿼리 실행, 정렬, 해시
집계, 연결 처리
포화 시: 쿼리 지연 증가
CPU wait ↑, load avg ↑
목표: 피크 70% 이하
Memory Buffer pool / shared_buffers
정렬·해시 버퍼
연결당 스택·힙
포화 시: 캐시 미스 급증
IOPS 폭발, 스왑 사용
목표: 스왑 0MB
IOPS / Storage 읽기: 캐시 미스 시 발생
쓰기: 커밋, WAL/binlog
체크포인트, 플러시
포화 시: I/O wait 급증
쿼리 지연 수 배 증가
목표: 피크 70% 이하
Connections 각 연결: 스레드 + 메모리
max_connections 한도
커넥션 풀로 다중화
포화 시: 즉시 연결 거부
앱 전체 장애로 직결
목표: 피크 70% 이하
데이터베이스 용량 계획의 네 가지 자원 축

각 축은 독립적으로 포화될 수 있다. CPU는 여유롭지만 IOPS가 터지는 경우, 메모리는 충분한데 connection이 가득 차는 경우 모두 가능하다. 따라서 네 축을 개별적으로 추적하고 예측해야 한다.


CPU 용량 계획

무엇을 측정하는가

지표도구의미
CPU 사용률 (%)top, vmstat, Prometheus node_cpu_seconds_total전체 부하 수준
CPU I/O wait (%)iostat, vmstat wa스토리지 병목 여부
DB process CPUtop -p <pid>, pidstatDB 프로세스 점유율
쿼리별 CPUMySQL: performance_schema, PG: pg_stat_statements상위 소비 쿼리

성장 모델링

CPU 사용률이 매달 3%p씩 증가한다면 12개월 후 현재보다 36%p 높다. 피크 기준 50%에서 출발한다면 12개월 후 86%로, 여유가 14%p밖에 없다.

-- PostgreSQL: 상위 CPU 소비 쿼리 (pg_stat_statements)
SELECT query,
       calls,
       total_exec_time / calls AS avg_ms,
       rows / calls            AS avg_rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

CPU가 선형으로 증가한다면 추가 쿼리 최적화 여지가 있는지 먼저 본다. 인덱스 누락, 풀스캔이 CPU를 많이 먹는 경우가 많다.


Memory 용량 계획

Buffer Pool / shared_buffers

MySQL과 PostgreSQL 모두 핵심 메모리 캐시가 있다.

엔진파라미터권장값역할
MySQL InnoDBinnodb_buffer_pool_size전체 RAM의 70~80%데이터·인덱스 캐시
PostgreSQLshared_buffers전체 RAM의 25~30%공유 버퍼 (OS 캐시도 활용)
PostgreSQLeffective_cache_size전체 RAM의 70~75%플래너 힌트 (실제 할당 아님)

Buffer pool hit ratio가 낮아지기 시작하면 메모리 부족 신호다.

-- MySQL: buffer pool hit ratio
SELECT (1 - Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests) * 100
    AS hit_ratio_pct
FROM (
    SELECT variable_value AS Innodb_buffer_pool_reads
    FROM performance_schema.global_status
    WHERE variable_name = 'Innodb_buffer_pool_reads'
) r,
(
    SELECT variable_value AS Innodb_buffer_pool_read_requests
    FROM performance_schema.global_status
    WHERE variable_name = 'Innodb_buffer_pool_read_requests'
) rr;
-- 목표: 99% 이상. 95% 아래면 buffer pool 증설 검토.

-- PostgreSQL: shared buffer hit ratio
SELECT blks_hit::float / (blks_hit + blks_read) * 100 AS hit_ratio_pct
FROM pg_stat_database
WHERE datname = current_database();

연결당 메모리

MySQL은 연결당 평균 4~10 MB를 사용한다. PostgreSQL은 연결당 5~10 MB 정도다.

총 연결 메모리 = max_connections × 연결당 메모리
예: 500 × 8 MB = 4 GB → 이 외에 buffer pool/shared_buffers 메모리가 필요

메모리 총량에서 OS 예비(10%), buffer pool, 그 외 용도를 빼고 남은 것이 연결용 메모리다.


IOPS 용량 계획

IOPS가 중요한 이유

데이터가 메모리 캐시에 없으면 디스크에서 읽어야 한다. 쓰기는 WAL/binlog 커밋마다, 체크포인트마다 발생한다. IOPS 포화는 쿼리 지연을 수 배~수십 배까지 올린다.

IOPS 예산 분배 (예시, NVMe SSD 10,000 IOPS 기준)

읽기 IOPS (캐시 미스)      : ~3,000
쓰기 IOPS (WAL/binlog)     : ~2,000
체크포인트 / 백그라운드 플러시: ~2,000
예비 헤드룸                : ~3,000
                          ─────────
                            10,000

측정 지표

# 시스템 레벨
iostat -x 1      # r/s, w/s, %iowait, await(ms)
iotop            # 프로세스별 I/O

# MySQL
SHOW GLOBAL STATUS LIKE 'Innodb_data_reads';
SHOW GLOBAL STATUS LIKE 'Innodb_data_writes';
SHOW GLOBAL STATUS LIKE 'Innodb_os_log_written';

# PostgreSQL
SELECT * FROM pg_stat_bgwriter;  -- buffers_clean, buffers_checkpoint
SELECT * FROM pg_stat_io;        -- 16+: 엔진 내 I/O 상세

innodb_io_capacity 설정

# MySQL: 스토리지 속도에 맞춰야 백그라운드 플러시가 제때 동작
innodb_io_capacity     = 4000   # SATA SSD
innodb_io_capacity     = 8000   # NVMe
innodb_io_capacity_max = 20000  # 피크 허용 상한

Connection 용량 계획

Connection 포화는 가장 즉각적인 장애다. 연결 시도가 max_connections 초과하는 순간, 이후 모든 연결 시도는 에러를 받는다.

올바른 max_connections 산정

안전한 동시 연결 수 = 가용 RAM (연결용) / 연결당 메모리
예: 4 GB / 8 MB = 500 연결

단, MySQL은 동시 64~128개 연결에서 처리량이 피크에 달하고
그 이상에서는 컨텍스트 스위칭 비용으로 처리량이 하락한다.
max_connections은 1,000~2,000 이하로 유지하는 것이 실용적이다.
-- MySQL: 현재 연결 사용률
SELECT variable_value AS max_connections
FROM performance_schema.global_variables
WHERE variable_name = 'max_connections';

SHOW GLOBAL STATUS LIKE 'Max_used_connections';   -- 역대 피크
SHOW GLOBAL STATUS LIKE 'Threads_connected';      -- 현재 연결 수

-- PostgreSQL
SHOW max_connections;
SELECT count(*) FROM pg_stat_activity;
SELECT count(*) FROM pg_stat_activity WHERE state = 'active';

커넥션 풀로 다중화

앱 서버가 늘어날수록 DB 연결 수가 선형으로 증가한다. 커넥션 풀러(PgBouncer, ProxySQL)는 수백 개의 앱 연결을 수십 개의 DB 연결로 다중화한다.

풀 없음: 앱 서버 증가 → DB 연결 폭증
앱 서버 1
10 conn
앱 서버 2
10 conn
앱 서버 N
10 conn
DB
N×10 conn
(포화 위험)
풀 있음: DB 연결 수 제어 가능
앱 서버 1
10 conn
앱 서버 2
10 conn
앱 서버 N
10 conn
PgBouncer
ProxySQL
(다중화)
DB
50~100 conn
(안정)
커넥션 풀 없는 경우 vs 있는 경우

Disk Growth 예측

디스크는 성장이 가장 예측 가능하고, 포화 시 장애가 가장 치명적인 자원이다.

성장률 측정

-- MySQL: 데이터베이스별 크기 추이 (information_schema)
SELECT table_schema,
       ROUND(SUM(data_length + index_length) / 1024 / 1024 / 1024, 2) AS size_gb
FROM information_schema.tables
GROUP BY table_schema
ORDER BY size_gb DESC;

-- PostgreSQL: 데이터베이스 크기
SELECT pg_database.datname,
       pg_size_pretty(pg_database_size(pg_database.datname)) AS size
FROM pg_database
ORDER BY pg_database_size(pg_database.datname) DESC;

-- PostgreSQL: 테이블별 크기 상위 10
SELECT schemaname, tablename,
       pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS total_size
FROM pg_tables
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC
LIMIT 10;

Prometheus predict_linear로 디스크 소진 예측

-- 현재 추세가 이어지면 디스크 언제 꽉 차는가
predict_linear(node_filesystem_free_bytes{mountpoint="/var/lib/mysql"}[7d], 3600 * 24 * 30)

-- 30일 후 남은 공간 예측 (GB)
predict_linear(node_filesystem_free_bytes{mountpoint="/var/lib/mysql"}[7d], 86400 * 30)
  / (1024^3)

-- 알림: 7일 추세로 24시간 내 1GB 이하 예상 시 경보
predict_linear(node_filesystem_free_bytes[7d], 3600 * 24) / (1024^3) < 1

성장 모델 선택

선형 성장 (Linear) 매월 고정량 증가
예: +20 GB/월
성숙한 서비스, 사용자 수 안정
예측: 현재 + (성장률 × 기간)
12개월 후 = 현재 + 240 GB
지수 성장 (Exponential) 매월 일정 비율 증가
예: +5%/월
성장 중 서비스, 사용자 급증
예측: 현재 × (1 + 성장률)^기간
12개월 후 = 현재 × 1.05^12 ≈ ×1.8
계획 기준 두 모델 중 더 큰 값으로
헤드룸: 피크 기준 30% 이상
목표 사용률: ≤ 70%
불확실성 버퍼 = 지수-선형 차이
리드 타임(증설 소요 시간) 고려
클라우드: 몇 분 / 온프레미스: 몇 주
선형 vs 지수 성장 모델 비교

용량 알림 임계값 설계

경보는 문제가 생기기 전에 울려야 한다. 이미 100%에서 울리면 늦다.

자원경고 임계값위험 임계값비고
CPU 사용률70% (5분 평균)90% (1분 평균)쿼리 최적화 or 스케일업 검토
Memory 스왑 사용1 MB 이상100 MB 이상스왑 자체가 경고 신호
Buffer pool hit ratio< 98%< 95%메모리 증설 또는 쿼리 최적화
IOPS 사용률70%90%캐시 튜닝 or 스토리지 업그레이드
디스크 사용률70%85%용량 증설 리드타임 고려
연결 사용률70%85%풀러 도입 또는 max_connections 조정
복제 지연10초 이상60초 이상피크 IOPS, long trx 원인 분석

용량 계획 주기와 루틴

월간 용량 리뷰 체크리스트

□ 지난 30일 각 자원(CPU/메모리/IOPS/연결/디스크)의 피크 추이 확인
□ 6개월 선형·지수 성장 예측 업데이트
□ 예측 소진 시점 계산: 모든 축에서 70% 도달 예상일
□ 증설 리드타임(클라우드 분~시간 / 온프레미스 주~월) 대비 여유 있는가
□ 최근 스키마 변경, 신규 배치 등 성장률 변화 요인 없는가
□ 이벤트(대규모 캠페인, 배치 작업) 앞두고 추가 준비 필요한가

기억할 문장

용량 계획의 목표는 70% 이하를 유지하는 것이다. 자원이 포화되면 장애는 순식간에 오고, 회복하는 데 예상보다 오래 걸린다. "곧 증설할 거니까 괜찮다"는 함정이다. 리드타임 전에 경보가 울리도록 임계값을 미리 낮게 잡아야 한다.

다음 장에서는 스키마 변경, 인덱스, 파라미터 변경이 얼마나 위험한지, 그리고 안전하게 적용하고 롤백하는 방법을 다룬다.

References

  • JusDB Blog, Database Capacity Planning: From Metrics to Growth Projections — https://www.jusdb.com/blog/database-capacity-planning-metrics-growth
  • DBTA, Database Capacity Planning (DBA Corner) — https://www.dbta.com/Columns/DBA-Corner/Database-Capacity-Planning-156629.aspx
  • ManageEngine, Database monitoring for capacity planning — https://www.manageengine.com/products/applications_manager/blog/database-monitoring-for-capacity-planning.html
  • MinervaDB, Forecast MySQL IOPS — https://minervadb.xyz/forecast-mysql-iops/
  • Crunchy Data Blog, Understanding Postgres IOPS — https://www.crunchydata.com/blog/understanding-postgres-iops
  • MySQL Documentation, Connection Handling and Scaling — https://dev.mysql.com/blog-archive/mysql-connection-handling-and-scaling/
  • JusDB Blog, How to Tune max_connections for Your MySQL Workload — https://oneuptime.com/blog/post/2026-03-31-mysql-how-to-tune-maxconnections-for-your-mysql-workload/view
  • Sysdig Blog, Top metrics in PostgreSQL monitoring with Prometheus — https://www.sysdig.com/blog/postgresql-monitoring
  • Oracle OCI, Database Capacity Planning — https://docs.oracle.com/en-us/iaas/operations-insights/doc/database-capacity-planning.html
  • Alibaba Cloud, Cloud Capacity Planning Principles & Best Practices — https://www.alibabacloud.com/help/en/well-architected/latest/capacity-planning