---
title: "MySQL 메모리 구조 개요: 글로벌 메모리와 세션 메모리의 차이"
description: "MySQL 서버의 글로벌 메모리와 세션 메모리 구조를 구분하고, 운영자가 용량 산정과 장애 진단에 활용할 기준을 정리한다."
tags: [ MySQL, 아키텍처, 운영, DBA ]
image: "mysql-report-bg.png"
published: "2026-05-15"
updated: "2026-05-15"
author: "MySQL 기술 노트"
source_url: ""
---

## 1. 왜 MySQL 메모리 구조를 먼저 이해해야 하는가

MySQL 운영에서 메모리 문제는 단순히 “메모리가 부족하다”는 한 문장으로 끝나지 않는다. 같은 메모리 부족이라도 원인은 `InnoDB Buffer Pool`이 너무 작아 디스크 I/O가 증가한 것일 수 있고, 반대로 너무 크게 잡아 운영체제 파일 캐시와 프로세스 여유 공간을 압박한 것일 수도 있다. 또는 평소에는 안정적이지만 특정 시간대에 접속 수가 늘면서 정렬, 조인, 임시 테이블, 네트워크 버퍼 같은 세션 단위 메모리가 동시에 증가해 OOM Killer가 MySQL 프로세스를 종료할 수도 있다.

MySQL 메모리 구조를 볼 때 가장 중요한 구분은 **글로벌 메모리(global memory)**와 **세션 메모리(session memory)**다. 글로벌 메모리는 MySQL 서버 프로세스 전체가 공유하는 장기 상주 메모리이고, 세션 메모리는 클라이언트 연결과 쿼리 실행 과정에서 연결별·작업별로 할당되는 메모리다. 이 차이를 모르면 다음과 같은 운영 오류가 자주 발생한다.

- `innodb_buffer_pool_size`만 보고 전체 메모리 사용량을 예측한다.
- `sort_buffer_size`, `join_buffer_size`, `tmp_table_size` 같은 세션 관련 변수를 크게 올려도 “한 번만 잡히는 값”으로 오해한다.
- `max_connections`를 높이면 동시 처리량이 늘어난다고 생각하지만, 실제로는 최악 시나리오의 메모리 폭발 가능성을 키운다.
- Performance Schema, 바이너리 로그 캐시, 연결 버퍼, 복제 스레드, 플러그인 메모리 같은 부수적인 사용량을 고려하지 않는다.
- Aurora MySQL에서 스토리지 계층이 다르다는 이유로 메모리 산정 원칙까지 완전히 달라진다고 오해한다.

이 글은 MySQL 서버 메모리를 구성하는 주요 영역을 글로벌 메모리와 세션 메모리 관점에서 정리하고, DBA와 운영자가 실제 용량 산정·장애 진단·파라미터 변경 검토에 사용할 수 있는 기준을 제시한다.

## 2. MySQL 프로세스 관점의 메모리 큰 그림

MySQL 서버는 일반적으로 하나의 `mysqld` 프로세스 안에서 여러 스레드가 동작하는 구조다. 클라이언트 연결을 처리하는 foreground thread, InnoDB의 page cleaner와 purge thread, redo log writer, 복제 SQL/I/O thread, 이벤트 스케줄러, Performance Schema 내부 구조 등이 같은 프로세스 주소 공간을 공유한다.

운영체제에서 `mysqld`의 메모리를 보면 보통 다음 요소가 섞여 보인다.

| 구분 | 설명 | 운영상 의미 |
|---|---|---|
| InnoDB Buffer Pool | 데이터/인덱스 페이지를 캐시하는 핵심 글로벌 메모리 | 너무 작으면 I/O 증가, 너무 크면 OS 여유 메모리 부족 |
| InnoDB 내부 메모리 | Change Buffer, Adaptive Hash Index, lock 구조, data dictionary 등 | 워크로드와 객체 수에 따라 증가 |
| 로그/캐시 메모리 | Redo log buffer, binary log cache, table open cache 등 | 트랜잭션 크기와 테이블 수에 영향 |
| Performance Schema | 계측 데이터를 저장하는 메모리 | 설정에 따라 수백 MB 이상 가능 |
| 연결/스레드 메모리 | thread stack, net buffer, connection control 구조 | 연결 수에 비례 |
| 쿼리 실행 메모리 | sort/join/read buffer, 임시 테이블 등 | 동시 실행 쿼리 수와 쿼리 형태에 비례 |
| allocator/fragmentation | glibc 또는 jemalloc 등 메모리 할당자의 보유 영역과 단편화 | SQL 변수로 직접 설명되지 않는 RSS 증가 원인 |

MySQL 변수 하나하나를 더한다고 실제 RSS가 정확히 계산되지는 않는다. 하지만 글로벌 메모리와 세션 메모리를 나누면 “고정적으로 상주하는 기반 비용”과 “부하가 몰릴 때 증가하는 가변 비용”을 분리해서 볼 수 있다. 이 구분이 용량 산정의 출발점이다.

## 3. 글로벌 메모리: 서버 전체가 공유하는 장기 상주 영역

글로벌 메모리는 MySQL 서버가 기동되면서 할당하거나, 서버 전체 상태를 유지하기 위해 장기간 보유하는 메모리다. 모든 연결이 함께 사용하는 영역이므로 연결 수가 증가한다고 단순히 선형으로 늘지는 않는다. 대신 설정값, 데이터셋 크기, 테이블 수, 계측 설정, InnoDB 내부 상태에 영향을 받는다.

### 3.1 InnoDB Buffer Pool

`InnoDB Buffer Pool`은 MySQL 메모리 구조에서 가장 큰 비중을 차지하는 경우가 많다. InnoDB는 테이블과 인덱스 페이지를 디스크에서 읽어 16KB 단위 페이지로 Buffer Pool에 올려두고, 이후 동일 페이지 접근을 메모리에서 처리한다. 읽기 성능뿐 아니라 변경된 페이지(dirty page)를 모아 쓰는 write-back 동작에도 관여한다.

대표 변수는 다음과 같다.

```sql
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
SHOW VARIABLES LIKE 'innodb_buffer_pool_instances';
```

실행 결과(MySQL 8.0.46):

```text
+-------------------------+----------+
| Variable_name           | Value    |
+-------------------------+----------+
| innodb_buffer_pool_size | 67108864 |
+-------------------------+----------+
+------------------------------+-------+
| Variable_name                | Value |
+------------------------------+-------+
| innodb_buffer_pool_instances | 1     |
+------------------------------+-------+
```

MySQL 8.0에서는 `innodb_buffer_pool_size`를 동적으로 변경할 수 있지만, 운영 중 변경은 즉시 모든 문제가 해결되는 스위치가 아니다. Buffer Pool을 늘리면 새 메모리가 할당되고 워크로드에 따라 점진적으로 warm-up된다. 줄이면 dirty page flush, 내부 조정, 일시적 성능 흔들림이 발생할 수 있다.

운영 관점에서 Buffer Pool은 “클수록 무조건 좋다”가 아니라 다음 균형을 봐야 한다.

- 실제 working set이 Buffer Pool에 얼마나 들어가는가
- 운영체제와 MySQL 프로세스의 기타 메모리에 충분한 여유가 남는가
- 백업, 로그 압축, 모니터링 에이전트, 배치 도구가 같은 호스트에서 추가 메모리를 쓰는가
- swap 사용이 발생하지 않는가
- dirty page flush가 스토리지 처리량과 균형을 이루는가

Buffer Pool 적중률은 유용하지만, 단일 지표만으로 판단해서는 안 된다. 적중률이 높아도 특정 쿼리의 랜덤 I/O가 병목일 수 있고, 적중률이 낮아도 대량 스캔성 배치라면 업무 특성상 자연스러운 결과일 수 있다.

### 3.2 Redo Log Buffer와 로그 관련 메모리

`innodb_log_buffer_size`는 트랜잭션 변경 내용을 redo log로 쓰기 전에 임시로 보관하는 글로벌 메모리다. 큰 트랜잭션이 많거나 BLOB/TEXT 변경이 많은 환경에서는 너무 작은 log buffer가 flush 빈도를 높일 수 있다.

```sql
SHOW VARIABLES LIKE 'innodb_log_buffer_size';
SHOW GLOBAL STATUS LIKE 'Innodb_log_waits';
```

실행 결과(MySQL 8.0.46):

```text
+------------------------+----------+
| Variable_name          | Value    |
+------------------------+----------+
| innodb_log_buffer_size | 16777216 |
+------------------------+----------+
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| Innodb_log_waits | 0     |
+------------------+-------+
```

`Innodb_log_waits`가 증가한다면 log buffer가 부족해 트랜잭션이 redo log flush를 기다린 신호일 수 있다. 다만 redo log 파일 크기, 스토리지 지연, `innodb_flush_log_at_trx_commit` 설정과 함께 해석해야 한다.

Binary log를 사용하는 환경에서는 트랜잭션별 binary log cache도 고려해야 한다. `binlog_cache_size`는 전역으로 한 번만 잡히는 메모리가 아니라, binary logging이 필요한 세션에서 트랜잭션 처리 중 사용될 수 있는 캐시다. 따라서 엄밀히는 “로그 관련 변수”이지만 운영 위험은 세션 메모리와 유사하게 동시성에 영향을 받는다.

```sql
SHOW VARIABLES LIKE 'binlog_cache_size';
SHOW GLOBAL STATUS LIKE 'Binlog_cache%';
```

실행 결과(MySQL 8.0.46):

```text
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| binlog_cache_size | 32768 |
+-------------------+-------+
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Binlog_cache_disk_use | 0     |
| Binlog_cache_use      | 0     |
+-----------------------+-------+
```

### 3.3 Table Open Cache와 Dictionary 관련 메모리

MySQL은 열린 테이블 객체, 테이블 정의, metadata lock 구조, data dictionary 정보를 메모리에 유지한다. 관련 변수는 다음과 같다.

```sql
SHOW VARIABLES LIKE 'table_open_cache';
SHOW VARIABLES LIKE 'table_definition_cache';
SHOW GLOBAL STATUS LIKE 'Open_tables';
SHOW GLOBAL STATUS LIKE 'Opened_tables';
```

실행 결과(MySQL 8.0.46):

```text
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| table_open_cache | 4000  |
+------------------+-------+
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| table_definition_cache | 2000  |
+------------------------+-------+
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Open_tables   | 77    |
+---------------+-------+
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Opened_tables | 158   |
+---------------+-------+
```

테이블 수가 많고 샤딩 비슷한 다중 테넌트 스키마를 운영하는 경우, 테이블 캐시와 dictionary 관련 메모리가 무시하기 어려운 수준으로 커질 수 있다. `Opened_tables`가 지속적으로 빠르게 증가하면 table open cache가 부족하거나 테이블 접근 패턴이 캐시 친화적이지 않다는 신호다. 반대로 캐시를 과도하게 키우면 메모리와 file descriptor 사용량이 늘어난다.

### 3.4 Performance Schema 메모리

Performance Schema는 MySQL 내부 대기 이벤트, statement, transaction, memory, thread 계측 정보를 제공한다. 운영 진단에는 매우 유용하지만, 계측 범위와 history 크기에 따라 메모리 사용량이 증가한다.

```sql
SELECT event_name, current_alloc, high_alloc
FROM sys.memory_global_by_current_bytes
ORDER BY current_alloc DESC
LIMIT 20;
```

`sys.memory_global_by_current_bytes`는 Performance Schema memory instrumentation이 활성화되어 있을 때 글로벌 메모리 사용 경향을 파악하는 데 도움이 된다. 모든 메모리 할당을 완벽하게 포착하지는 못하지만, MySQL 내부에서 어떤 계측 영역이 큰지 확인하는 출발점으로 적합하다.

운영 환경에서 Performance Schema를 완전히 끄는 것은 진단 능력을 크게 낮출 수 있다. 대신 필요한 consumer와 history 크기를 조정하고, 메모리 증가가 의심될 때 어떤 instrument가 큰지 확인하는 방식이 좋다.

## 4. 세션 메모리: 연결과 쿼리 실행에 따라 증가하는 영역

세션 메모리는 클라이언트 연결 또는 특정 쿼리 실행 단계에서 할당되는 메모리다. 핵심은 **동시성 곱셈 효과**다. `sort_buffer_size = 16MB`라는 설정은 서버 전체에서 16MB만 사용한다는 뜻이 아니다. 정렬이 필요한 여러 세션이 동시에 실행되면 세션별로 할당될 수 있다.

### 4.1 연결당 기본 메모리

클라이언트가 MySQL에 접속하면 연결 관리 구조, thread stack, 네트워크 버퍼 등이 필요하다. 관련 변수는 다음과 같다.

```sql
SHOW VARIABLES WHERE Variable_name IN (
  'max_connections',
  'thread_stack',
  'net_buffer_length',
  'max_allowed_packet'
);
```

실행 결과(MySQL 8.0.46):

```text
+--------------------+----------+
| Variable_name      | Value    |
+--------------------+----------+
| max_allowed_packet | 67108864 |
| max_connections    | 30       |
| net_buffer_length  | 16384    |
| thread_stack       | 1048576  |
+--------------------+----------+
```

`max_allowed_packet`은 연결마다 항상 최대 크기로 할당된다고 단순화하면 안 되지만, 큰 패킷을 다루는 세션에서 네트워크 버퍼가 확장될 수 있다. 대량 INSERT, 큰 JSON 문서, BLOB/TEXT 전송이 많은 시스템에서는 패킷 크기와 동시 실행 수를 함께 봐야 한다.

`max_connections`는 메모리 산정에서 위험한 변수다. 실제 동시 접속이 낮더라도, 장애 상황에서 애플리케이션 커넥션 풀이 폭증하거나 재시도 폭풍이 발생하면 순간적으로 연결이 크게 증가할 수 있다. 따라서 `max_connections`는 “최대로 허용할 수 있는 논리 연결 수”일 뿐 아니라 “최악 시나리오에서 세션 메모리를 얼마나 허용할 것인가”라는 운영 정책이기도 하다.

### 4.2 정렬, 조인, 읽기 버퍼

대표적인 세션 실행 메모리는 다음과 같다.

```sql
SHOW VARIABLES WHERE Variable_name IN (
  'sort_buffer_size',
  'join_buffer_size',
  'read_buffer_size',
  'read_rnd_buffer_size'
);
```

실행 결과(MySQL 8.0.46):

```text
+----------------------+--------+
| Variable_name        | Value  |
+----------------------+--------+
| join_buffer_size     | 262144 |
| read_buffer_size     | 131072 |
| read_rnd_buffer_size | 262144 |
| sort_buffer_size     | 262144 |
+----------------------+--------+
```

이 변수들은 이름 때문에 성능 튜닝의 쉬운 레버처럼 보이지만, 무작정 키우면 위험하다.

- `sort_buffer_size`: `ORDER BY`, `GROUP BY` 등에서 메모리 정렬이 필요할 때 사용될 수 있다.
- `join_buffer_size`: 인덱스를 제대로 사용하지 못하는 조인, block nested loop 계열 처리에서 사용될 수 있다.
- `read_buffer_size`: 순차 테이블 스캔에서 사용될 수 있다.
- `read_rnd_buffer_size`: 정렬 후 row를 읽는 과정 등에 사용될 수 있다.

이 값들은 쿼리마다 항상 할당되는 것이 아니라 필요할 때 할당되는 경향이 있다. 그러나 문제 쿼리가 동시에 많이 실행될 때는 여러 버퍼가 한 세션 안에서도 함께 사용될 수 있고, 세션 수만큼 반복된다. 운영자가 흔히 저지르는 실수는 “정렬이 느리니 `sort_buffer_size`를 크게 올린다”는 방식이다. 정렬이 느린 원인은 인덱스 설계, 실행 계획, 결과 집합 크기, 임시 테이블 사용, 디스크 스필일 수 있다. 버퍼 증설은 마지막 보조 수단이어야 한다.

### 4.3 내부 임시 테이블 메모리

MySQL은 일부 `GROUP BY`, `ORDER BY`, `DISTINCT`, 윈도우 함수, 파생 테이블, CTE, UNION 처리에서 내부 임시 테이블을 사용한다. MySQL 8.0에서는 내부 임시 테이블 엔진으로 TempTable이 사용될 수 있으며, 메모리 한계를 넘으면 디스크 기반 임시 테이블로 전환된다.

관련 변수는 버전에 따라 다르지만 주로 다음을 확인한다.

```sql
SHOW VARIABLES WHERE Variable_name IN (
  'tmp_table_size',
  'max_heap_table_size',
  'temptable_max_ram',
  'internal_tmp_mem_storage_engine'
);

SHOW GLOBAL STATUS WHERE Variable_name IN (
  'Created_tmp_tables',
  'Created_tmp_disk_tables',
  'Created_tmp_files'
);
```

실행 결과(MySQL 8.0.46):

```text
+---------------------------------+------------+
| Variable_name                   | Value      |
+---------------------------------+------------+
| internal_tmp_mem_storage_engine | TempTable  |
| max_heap_table_size             | 16777216   |
| temptable_max_ram               | 1073741824 |
| tmp_table_size                  | 16777216   |
+---------------------------------+------------+
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 0     |
| Created_tmp_files       | 5     |
| Created_tmp_tables      | 14    |
+-------------------------+-------+
```

`tmp_table_size`와 `max_heap_table_size`를 크게 올리면 디스크 임시 테이블을 줄일 수 있다고 기대할 수 있다. 하지만 동시 쿼리 수가 많으면 내부 임시 테이블 메모리도 동시에 증가한다. 특히 리포트성 쿼리, 관리자 화면의 복잡한 검색, 배치 집계가 OLTP 트래픽과 같은 인스턴스에서 실행될 때 위험하다.

MySQL 8.0의 TempTable 관련 변수는 버전별 동작 차이가 있으므로, 운영 중인 정확한 버전의 문서를 기준으로 해석해야 한다. 중요한 원칙은 동일하다. 내부 임시 테이블은 “전역으로 한 번 잡히는 캐시”가 아니라 “쿼리 실행 중 필요에 따라 증가하는 작업 메모리”다.

### 4.4 Prepared Statement, Cursor, 사용자 변수

애플리케이션이 prepared statement를 많이 열어 두거나 서버 사이드 cursor를 사용하면 세션에 상태가 남는다. 사용자 변수, 세션 임시 테이블, 트랜잭션 컨텍스트도 연결이 유지되는 동안 메모리를 차지할 수 있다.

```sql
SHOW GLOBAL STATUS LIKE 'Prepared_stmt_count';
SHOW VARIABLES LIKE 'max_prepared_stmt_count';
```

실행 결과(MySQL 8.0.46):

```text
+---------------------+-------+
| Variable_name       | Value |
+---------------------+-------+
| Prepared_stmt_count | 0     |
+---------------------+-------+
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| max_prepared_stmt_count | 16382 |
+-------------------------+-------+
```

커넥션 풀을 사용하는 애플리케이션에서는 연결이 오래 유지되므로 세션 상태 누수가 더 오래 남을 수 있다. 애플리케이션이 statement를 닫지 않거나, 세션 임시 테이블을 만들고 정리하지 않는 패턴은 DB 서버 메모리 증가로 이어질 수 있다.

## 5. 메모리 산정의 기본 공식과 한계

운영자가 자주 사용하는 단순 산정식은 다음 형태다.

```text
예상 MySQL 메모리 사용량
≈ 글로벌 메모리
 + (활성 세션 수 × 평균 세션 메모리)
 + (동시 무거운 쿼리 수 × 작업 메모리)
 + Performance Schema/복제/플러그인/할당자 여유분
```

더 보수적인 최악 산정은 다음처럼 잡을 수 있다.

```text
보수적 상한
≈ innodb_buffer_pool_size
 + innodb_log_buffer_size
 + Performance Schema 및 캐시성 메모리
 + max_connections × (thread_stack + net buffer + 쿼리 버퍼 후보)
 + 운영체제/에이전트/단편화 여유분
```

하지만 이 공식은 어디까지나 위험을 감지하기 위한 도구다. 모든 세션이 동시에 `sort_buffer_size`, `join_buffer_size`, `read_buffer_size`, 내부 임시 테이블을 최대치로 쓰는 경우는 흔하지 않다. 반대로 실제 장애는 평균이 아니라 특정 시간대의 집중 부하, 장애 재시도, 느린 쿼리 누적, 커넥션 풀 폭증에서 발생한다. 따라서 다음 세 가지 값을 분리해서 보는 것이 실용적이다.

1. **상시 상주 메모리**: MySQL 기동 후 안정 상태에서 유지되는 RSS와 Buffer Pool 중심 메모리
2. **정상 피크 메모리**: 일일 최대 트래픽, 배치, 백업, 리포트 작업을 포함한 정상 범위의 피크
3. **장애 피크 메모리**: 애플리케이션 장애, 락 대기 누적, 쿼리 지연, 재시도 폭풍이 발생했을 때의 피크

DB 서버는 정상 피크뿐 아니라 장애 피크에서도 즉시 죽지 않아야 한다. 메모리 여유분은 낭비가 아니라 장애 격리를 위한 완충재다.

## 6. 실무 진단 쿼리와 운영 명령어

### 6.1 현재 메모리 관련 변수 확인

```sql
SHOW VARIABLES WHERE Variable_name IN (
  'innodb_buffer_pool_size',
  'innodb_log_buffer_size',
  'max_connections',
  'thread_stack',
  'sort_buffer_size',
  'join_buffer_size',
  'read_buffer_size',
  'read_rnd_buffer_size',
  'tmp_table_size',
  'max_heap_table_size',
  'binlog_cache_size',
  'table_open_cache',
  'table_definition_cache'
);
```

실행 결과(MySQL 8.0.46):

```text
+-------------------------+----------+
| Variable_name           | Value    |
+-------------------------+----------+
| binlog_cache_size       | 32768    |
| innodb_buffer_pool_size | 67108864 |
| innodb_log_buffer_size  | 16777216 |
| join_buffer_size        | 262144   |
| max_connections         | 30       |
| max_heap_table_size     | 16777216 |
| read_buffer_size        | 131072   |
| read_rnd_buffer_size    | 262144   |
| sort_buffer_size        | 262144   |
| table_definition_cache  | 2000     |
| table_open_cache        | 4000     |
| thread_stack            | 1048576  |
| tmp_table_size          | 16777216 |
+-------------------------+----------+
```

이 결과를 변경 이력과 함께 보관하면 장애 시점에 “언제 어떤 값이 커졌는지”를 추적하기 쉽다.

### 6.2 연결 수와 실행 상태 확인

```sql
SHOW GLOBAL STATUS WHERE Variable_name IN (
  'Threads_connected',
  'Threads_running',
  'Max_used_connections',
  'Connection_errors_max_connections'
);

SELECT user, host, db, command, state, COUNT(*) AS sessions
FROM information_schema.processlist
GROUP BY user, host, db, command, state
ORDER BY sessions DESC;
```

실행 결과(MySQL 8.0.46):

```text
+-----------------------------------+-------+
| Variable_name                     | Value |
+-----------------------------------+-------+
| Connection_errors_max_connections | 0     |
| Max_used_connections              | 1     |
| Threads_connected                 | 1     |
| Threads_running                   | 2     |
+-----------------------------------+-------+
+-----------------+-----------+-----------------+---------+------------------------+----------+
| user            | host      | db              | command | state                  | sessions |
+-----------------+-----------+-----------------+---------+------------------------+----------+
| event_scheduler | localhost | NULL            | Daemon  | Waiting on empty queue |        1 |
| root            | localhost | mysql_tech_note | Query   | executing              |        1 |
+-----------------+-----------+-----------------+---------+------------------------+----------+
```

`Threads_connected`는 연결 수, `Threads_running`은 실제 실행 중인 스레드 수에 가깝다. 연결 수가 높아도 대부분 Sleep이면 세션 기본 메모리와 커넥션 관리 비용이 문제일 수 있고, `Threads_running`이 높으면 CPU/I/O/락 경합과 함께 작업 메모리 증가 가능성을 봐야 한다.

### 6.3 임시 테이블과 정렬 상태 확인

```sql
SHOW GLOBAL STATUS WHERE Variable_name IN (
  'Created_tmp_tables',
  'Created_tmp_disk_tables',
  'Created_tmp_files',
  'Sort_merge_passes',
  'Sort_scan',
  'Sort_range'
);
```

실행 결과(MySQL 8.0.46):

```text
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 1     |
| Created_tmp_files       | 5     |
| Created_tmp_tables      | 21    |
| Sort_merge_passes       | 0     |
| Sort_range              | 0     |
| Sort_scan               | 2     |
+-------------------------+-------+
```

`Created_tmp_disk_tables`가 많다고 해서 즉시 `tmp_table_size`를 크게 올리는 것은 위험하다. 먼저 어떤 쿼리가 임시 테이블을 만드는지, 인덱스나 쿼리 구조로 줄일 수 있는지 확인해야 한다.

```sql
SELECT DIGEST_TEXT,
       COUNT_STAR,
       SUM_CREATED_TMP_TABLES,
       SUM_CREATED_TMP_DISK_TABLES,
       SUM_SORT_ROWS,
       SUM_ROWS_EXAMINED
FROM performance_schema.events_statements_summary_by_digest
WHERE SUM_CREATED_TMP_TABLES > 0
   OR SUM_CREATED_TMP_DISK_TABLES > 0
ORDER BY SUM_CREATED_TMP_DISK_TABLES DESC, SUM_CREATED_TMP_TABLES DESC
LIMIT 20;
```

Performance Schema statement summary가 활성화되어 있다면, 임시 테이블과 정렬을 많이 유발하는 쿼리 패턴을 빠르게 찾을 수 있다.

### 6.4 MySQL 내부 메모리 계측 확인

```sql
SELECT event_name,
       current_alloc,
       high_alloc
FROM sys.memory_global_by_current_bytes
ORDER BY current_alloc DESC
LIMIT 30;
```

세션별 메모리도 확인할 수 있다.

```sql
SELECT thread_id,
       user,
       current_allocated,
       total_allocated
FROM sys.memory_by_thread_by_current_bytes
ORDER BY current_allocated DESC
LIMIT 20;
```

이 뷰가 비어 있거나 기대보다 정보가 적다면 Performance Schema memory instrument 설정을 확인해야 한다.

```sql
SELECT NAME, ENABLED
FROM performance_schema.setup_instruments
WHERE NAME LIKE 'memory/%'
ORDER BY NAME
LIMIT 20;
```

실행 결과(MySQL 8.0.46):

```text
+------------------------------------------------------+---------+
| NAME                                                 | ENABLED |
+------------------------------------------------------+---------+
| memory/archive/FRM                                   | YES     |
| memory/archive/record_buffer                         | YES     |
| memory/blackhole/blackhole_share                     | YES     |
| memory/client/MYSQL                                  | YES     |
| memory/client/MYSQL_DATA                             | YES     |
| memory/client/MYSQL_HANDSHAKE                        | YES     |
| memory/client/mysql_options                          | YES     |
| memory/client/MYSQL_RES                              | YES     |
| memory/client/MYSQL_ROW                              | YES     |
| memory/client/MYSQL_SSL_session                      | YES     |
| memory/client/MYSQL_STATE_CHANGE_INFO                | YES     |
| memory/component_sys_vars/component_system_variables | YES     |
| memory/csv/blobroot                                  | YES     |
| memory/csv/row                                       | YES     |
| memory/csv/tina_set                                  | YES
... <truncated 407 chars>
```

### 6.5 운영체제에서 RSS와 swap 확인

Linux 호스트에서는 MySQL 내부 지표와 운영체제 지표를 함께 봐야 한다.

```bash
pid=$(pidof mysqld)
ps -o pid,rss,vsz,comm -p "$pid"
grep -E 'VmRSS|VmSize|VmSwap|Threads' /proc/$pid/status
free -h
vmstat 1 5
```

컨테이너 환경에서는 cgroup 제한과 컨테이너 메모리 사용량도 확인한다.

```bash
docker stats --no-stream <mysql-container-name>
```

RSS가 증가했지만 MySQL 내부 memory summary에 충분히 나타나지 않는다면 메모리 할당자 단편화, 계측되지 않는 플러그인/라이브러리, 큰 세션 버퍼의 일시적 사용, 운영체제 관점의 shared/private memory 해석 차이를 함께 검토해야 한다.

## 7. Aurora MySQL에서의 해석 차이

Aurora MySQL은 스토리지 계층이 일반 MySQL과 다르다. 데이터는 분산 스토리지에 저장되고, redo 처리와 복제 방식도 Aurora 아키텍처에 맞게 다르다. 그러나 DB 인스턴스의 계산 노드에서 쿼리를 실행하고 Buffer Pool을 사용하는 기본 원칙은 여전히 중요하다.

Aurora 운영에서 특히 주의할 점은 다음과 같다.

- **Buffer Pool은 여전히 중요하다.** 스토리지가 분산되어 있어도 자주 읽는 페이지를 메모리에 보관하지 못하면 읽기 지연과 스토리지 I/O 비용이 증가할 수 있다.
- **Failover 후 캐시 온도 문제가 있다.** 새 writer 또는 reader가 승격되면 Buffer Pool 상태가 이전 writer와 같지 않을 수 있다. 승격 직후 지연 증가를 캐시 warm-up 관점에서 해석해야 한다.
- **파라미터 그룹 변경은 적용 시점이 다르다.** 일부 변수는 동적이지만, 일부는 재시작이 필요하다. 클러스터 파라미터 그룹과 인스턴스 파라미터 그룹의 범위도 구분해야 한다.
- **Performance Insights와 CloudWatch를 함께 본다.** MySQL 내부 지표만으로는 인스턴스 메모리 압박, swap, freeable memory, wait event 흐름을 모두 설명하기 어렵다.
- **인스턴스 클래스가 메모리 상한이다.** `max_connections`와 세션 버퍼를 크게 잡아도 인스턴스 메모리 한계를 넘으면 안정성이 떨어진다.

Aurora에서는 `FreeableMemory`, `DatabaseConnections`, `BufferCacheHitRatio`, Performance Insights의 wait event, SQL digest를 함께 보면서 MySQL 내부 진단 쿼리와 교차 검증하는 방식이 좋다.

## 8. 장애 모드와 흔한 오해

### 8.1 “Buffer Pool을 크게 잡으면 모든 메모리 문제가 해결된다”는 오해

Buffer Pool은 읽기 성능의 핵심이지만, 전체 메모리를 거의 모두 차지하도록 설정하면 운영체제와 세션 메모리 여유가 줄어든다. 특히 동시 접속이 많은 OLTP 환경에서 Buffer Pool을 과도하게 크게 잡으면 정상 상태는 빠르지만 장애 상황에서 OOM 위험이 커질 수 있다.

### 8.2 “세션 버퍼를 크게 하면 쿼리가 항상 빨라진다”는 오해

`sort_buffer_size`나 `join_buffer_size`를 크게 해도 인덱스가 없는 조인, 과도한 정렬, 큰 중간 결과 자체가 사라지는 것은 아니다. 오히려 동시 실행 시 메모리 압박을 키울 수 있다. 쿼리 성능은 먼저 실행 계획, 인덱스, 조건 선택도, 결과 집합 크기, 임시 테이블 발생 여부를 봐야 한다.

### 8.3 연결 수 증가는 처리량 증가가 아니라 장애 증폭일 수 있다

`max_connections`를 크게 늘리면 “Too many connections” 오류는 줄어들 수 있다. 하지만 병목이 CPU, I/O, 락, 느린 쿼리라면 더 많은 연결은 대기열을 DB 내부로 밀어 넣을 뿐이다. 연결마다 메모리와 스케줄링 비용이 발생하므로, 애플리케이션 커넥션 풀과 DB의 동시 처리 한계를 함께 조정해야 한다.

### 8.4 임시 테이블 디스크 전환은 항상 나쁜 것이 아니다

디스크 임시 테이블은 느릴 수 있지만, 모든 내부 임시 테이블을 메모리에 유지하는 것이 항상 더 안전한 것은 아니다. 대량 집계나 리포트성 쿼리가 많은 환경에서 메모리 임시 테이블 한계를 크게 올리면 DB 전체 안정성을 해칠 수 있다. 임시 테이블 발생 원인을 줄이는 것이 우선이고, 변수 조정은 동시성까지 고려해 제한적으로 수행해야 한다.

### 8.5 RSS가 줄지 않는 현상

큰 쿼리나 피크 부하 이후 RSS가 즉시 줄지 않는다고 해서 반드시 메모리 누수는 아니다. 메모리 할당자가 반환하지 않고 프로세스 내부에서 재사용하려고 보유할 수 있다. 다만 high watermark가 계속 상승하고, 같은 워크로드 반복 후에도 안정 구간이 높아진다면 누수 또는 세션 상태 누적 가능성을 검토해야 한다.

## 9. 파라미터 변경 시 의사결정 기준

메모리 관련 파라미터를 바꿀 때는 다음 순서가 안전하다.

1. **문제 정의**: OOM인지, swap인지, 쿼리 지연인지, 디스크 임시 테이블 증가인지, Buffer Pool miss인지 구분한다.
2. **영향 범위 확인**: 글로벌 변수인지, 세션별 변수인지, 동적 변경 가능한지, 재시작이 필요한지 확인한다.
3. **동시성 반영**: 평균 연결 수가 아니라 피크 연결 수와 피크 쿼리 유형을 기준으로 계산한다.
4. **쿼리 개선 우선 검토**: 세션 버퍼 증설 전 실행 계획과 인덱스를 확인한다.
5. **작게 변경**: 한 번에 큰 폭으로 올리지 않고 관측 가능한 범위에서 단계적으로 조정한다.
6. **관측 지표 사전 정의**: 변경 후 볼 지표를 미리 정한다. 예: `Created_tmp_disk_tables`, `Sort_merge_passes`, RSS, swap, wait event, p95 latency.
7. **롤백 기준 설정**: 메모리 압박, swap 증가, OOM 위험이 감지되면 되돌릴 기준을 정한다.

예를 들어 `tmp_table_size`를 조정하려면 다음 질문에 답해야 한다.

- 어떤 SQL digest가 디스크 임시 테이블을 많이 만드는가?
- 해당 쿼리는 인덱스나 쿼리 구조로 개선 가능한가?
- 동시에 몇 개까지 실행되는가?
- 메모리 임시 테이블 한계를 올렸을 때 최악의 추가 메모리는 어느 정도인가?
- 변경 후 `Created_tmp_disk_tables`와 전체 지연 시간이 실제로 개선되는가?

## 10. 운영 점검표

- [ ] `innodb_buffer_pool_size`가 인스턴스 메모리, 세션 메모리, OS 여유분을 고려해 설정되어 있는가?
- [ ] `max_connections`가 애플리케이션 커넥션 풀 총합과 장애 시 재시도 패턴을 반영하는가?
- [ ] `sort_buffer_size`, `join_buffer_size`, `tmp_table_size`를 전역 성능 향상 목적으로 과도하게 키우지 않았는가?
- [ ] `Threads_connected`, `Threads_running`, `Max_used_connections`의 일일 피크를 알고 있는가?
- [ ] `Created_tmp_disk_tables`, `Sort_merge_passes`, `Binlog_cache_disk_use` 같은 지표를 변경 전후로 비교하는가?
- [ ] Performance Schema memory summary 또는 `sys` 스키마로 큰 메모리 소비 영역을 확인할 수 있는가?
- [ ] Linux 또는 컨테이너 수준에서 RSS, swap, cgroup memory limit를 함께 모니터링하는가?
- [ ] Aurora MySQL에서는 `FreeableMemory`, Performance Insights wait event, failover 후 cache warm-up을 함께 해석하는가?
- [ ] 메모리 파라미터 변경에 대한 롤백 기준과 적용 시간이 문서화되어 있는가?
- [ ] 장애 상황에서 DB가 연결 폭증을 무제한 받아들이지 않도록 애플리케이션 풀, 프록시, 제한 정책이 함께 설계되어 있는가?

## 11. 결론

MySQL 메모리 구조는 Buffer Pool 하나로 설명되지 않는다. 글로벌 메모리는 서버 전체 성능의 기반을 만들고, 세션 메모리는 동시성과 쿼리 형태에 따라 장애 시점의 위험을 결정한다. 따라서 안정적인 MySQL 운영은 “큰 캐시를 잡는 일”이 아니라, 상시 상주 메모리와 피크 작업 메모리, 연결 수, 쿼리 실행 방식, 운영체제 여유분을 함께 설계하는 일이다.

다음 단계의 학습에서는 이 큰 그림을 바탕으로 `InnoDB Buffer Pool`의 내부 구조, dirty page flush, LRU 관리, page cleaner 동작을 더 세밀하게 살펴볼 수 있다. 메모리 구조의 전체 지도를 먼저 이해하면 이후 InnoDB 성능 튜닝과 장애 분석을 훨씬 일관된 기준으로 해석할 수 있다.
