---
title: "MySQL 8.0 Error Log Filtering과 JSON 출력 운영"
description: "MySQL 8.0 오류 로그의 component pipeline, 규칙 기반 필터링, JSON 출력과 수집·복구 절차를 운영 관점에서 정리한다."
tags: [ MySQL, 운영, DBA, 아키텍처 ]
image: "mysql-report-bg.png"
published: "2026-08-30"
updated: "2026-08-30"
author: "MySQL 기술 노트"
source_url: ""
---

MySQL 오류 로그는 단순한 텍스트 파일이 아니다. MySQL 8.0에서는 서버가 만든 **log event**가 filter component를 거쳐 sink component에 기록되는 pipeline으로 동작한다. 따라서 운영자는 “로그 레벨을 몇으로 둘 것인가”뿐 아니라, 어떤 이벤트를 어느 출력으로 보내고 어떤 필드를 남길 것인지까지 설계할 수 있다.

이 기능은 관측 가능성을 높이지만 설정 순서가 잘못되면 더 위험해질 수 있다. filter 뒤에 sink가 없으면 설정이 거부되고, sink를 filter 앞에 두면 의도한 필터가 적용되지 않는다. 과도한 drop 규칙은 장애 징후를 지우며, JSON 전환만 하고 수집기 parser를 바꾸지 않으면 중앙 로그 시스템에서 로그가 사라진 것처럼 보인다. 이 글에서는 MySQL 8.0의 오류 로그 pipeline을 먼저 이해하고, 단계적으로 JSON 출력과 규칙 기반 filtering을 도입하는 방법을 설명한다.

## 1. 오류 로그를 component pipeline으로 이해하기

MySQL 8.0 오류 로그의 핵심 구성요소는 두 종류다.

- **filter component**: 이벤트를 통과시키거나 제거하고, field를 수정하거나 제거한다.
- **sink component**: 전달받은 이벤트를 traditional text, JSON, system log 등의 형식과 대상으로 기록한다.

기본 구성은 일반적으로 다음과 같다.

```text
log_filter_internal; log_sink_internal
```

`log_error_services`의 순서는 선언 목록이 아니라 **실행 순서**다. 앞 component의 결과가 뒤 component로 전달된다.

```mermaid
flowchart LR
    A[mysqld 내부에서 log event 생성] --> B{filter component}
    B -->|유지 또는 변환| C[log_sink_internal]
    B -->|유지 또는 변환| D[log_sink_json]
    B -->|drop| X[기록하지 않음]
    C --> E[traditional error log]
    D --> F[JSON error log]
    F --> G[수집기·SIEM·검색 플랫폼]
```

예를 들어 다음 배치는 서로 다른 의미를 가진다.

```text
log_filter_internal; log_sink_internal; log_sink_json
```

두 sink 모두 `log_filter_internal`의 영향을 받는다. 반면 다음 구성에서는 첫 번째 JSON sink가 filter보다 앞에 있으므로 필터링되지 않은 이벤트를 받고, 뒤의 JSON sink만 필터링 결과를 받는다.

```text
log_sink_json; log_filter_internal; log_sink_json
```

이 기능은 원본 보존과 축약본 분리를 가능하게 하지만, 같은 `log_error` 파일명을 기준으로 `.00.json`, `.01.json`처럼 별도 파일이 만들어질 수 있으므로 수집 경로와 보존 정책을 함께 설계해야 한다.

## 2. 현재 설정과 관측 인터페이스 확인

변경 전에는 서버 버전, component 순서, 기본 오류 로그 대상, timestamp 기준을 먼저 기록한다. 다음 조회는 MySQL 8.0 계열의 변경 전 점검에 사용할 수 있다.

```sql
SELECT VERSION() AS mysql_version,
       @@GLOBAL.log_error AS log_error,
       @@GLOBAL.log_error_services AS log_error_services,
       @@GLOBAL.log_error_verbosity AS log_error_verbosity,
       @@GLOBAL.log_timestamps AS log_timestamps;

SELECT COMPONENT_URN
FROM mysql.component
WHERE COMPONENT_URN IN (
  'file://component_log_filter_dragnet',
  'file://component_log_sink_json'
)
ORDER BY COMPONENT_URN;

SELECT TABLE_SCHEMA, TABLE_NAME
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'performance_schema'
  AND TABLE_NAME = 'error_log';
```

실행 결과(MySQL 8.0.x):

```text
mysql> SELECT VERSION() AS mysql_version,
    ->        @@GLOBAL.log_error AS log_error,
    ->        @@GLOBAL.log_error_services AS log_error_services,
    ->        @@GLOBAL.log_error_verbosity AS log_error_verbosity,
    ->        @@GLOBAL.log_timestamps AS log_timestamps;

+---------------+-----------+----------------------------------------+---------------------+----------------+
| mysql_version | log_error | log_error_services                     | log_error_verbosity | log_timestamps |
+---------------+-----------+----------------------------------------+---------------------+----------------+
| 8.0.46        | stderr    | log_filter_internal; log_sink_internal |                   2 | UTC            |
+---------------+-----------+----------------------------------------+---------------------+----------------+
1 row in set (0.00 sec)

mysql> SELECT COMPONENT_URN
    -> FROM mysql.component
    -> WHERE COMPONENT_URN IN (
    ->   'file://component_log_filter_dragnet',
    ->   'file://component_log_sink_json'
    -> )
    -> ORDER BY COMPONENT_URN;

Empty set (0.00 sec)

mysql> SELECT TABLE_SCHEMA, TABLE_NAME
    -> FROM information_schema.TABLES
    -> WHERE TABLE_SCHEMA = 'performance_schema'
    ->   AND TABLE_NAME = 'error_log';

+--------------------+------------+
| TABLE_SCHEMA       | TABLE_NAME |
+--------------------+------------+
| performance_schema | error_log  |
+--------------------+------------+
1 row in set (0.01 sec)
```

`mysql.component` 조회 결과가 비어 있으면 loadable component가 아직 영구 등록되지 않았다는 의미다. 이것이 곧 오류 로그 기능이 없다는 뜻은 아니다. `log_filter_internal`과 `log_sink_internal`은 built-in component이며 `mysql.component` 등록 여부와 구분해서 해석해야 한다.

`performance_schema.error_log`는 MySQL 8.0.22 이상에서 최근 오류 이벤트를 SQL로 조회할 수 있게 한다. 다만 활성 sink와 버전, 구성에 따라 `DATA`의 표현이 traditional text 또는 JSON이 될 수 있으므로 parser는 형식을 확인한 뒤 적용해야 한다.

## 3. 우선순위 기반 filtering: log_filter_internal

기본 filter인 `log_filter_internal`은 `log_error_verbosity`를 사용한다.

| 값 | 기록 대상 | 운영 해석 |
|---:|---|---|
| 1 | Error | 정상 시에도 정보가 지나치게 줄어 원인 분석이 어려울 수 있다. |
| 2 | Error, Warning | 비교적 보수적인 운영 기준이지만 Note가 필요한 변경 관측에는 부족할 수 있다. |
| 3 | Error, Warning, Note | 기본 조사 정보가 풍부한 대신 반복 메시지 양을 관리해야 한다. |

우선순위 숫자는 “심각도가 3”이라는 일반적인 직관과 다르게 해석될 수 있으므로 값의 의미를 암기하기보다 실제 허용 범위를 확인해야 한다. 특히 장애 중에 로그량을 줄이려고 갑자기 `1`로 내리면 장애 직전의 Note·Warning 문맥을 잃을 수 있다.

운영 변경은 즉시 적용과 재시작 후 지속 여부를 구분한다.

```sql
SET GLOBAL log_error_verbosity = 3;
SET PERSIST log_error_verbosity = 3;

SELECT @@GLOBAL.log_error_verbosity AS active_value,
       VARIABLE_VALUE AS persisted_value
FROM performance_schema.persisted_variables
WHERE VARIABLE_NAME = 'log_error_verbosity';
```

실행 결과(MySQL 8.0.x):

```text
mysql> SET GLOBAL log_error_verbosity = 3;

Query OK, 0 rows affected (0.00 sec)

mysql> SET PERSIST log_error_verbosity = 3;

Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@GLOBAL.log_error_verbosity AS active_value,
    ->        VARIABLE_VALUE AS persisted_value
    -> FROM performance_schema.persisted_variables
    -> WHERE VARIABLE_NAME = 'log_error_verbosity';

+--------------+-----------------+
| active_value | persisted_value |
+--------------+-----------------+
|            3 | 3               |
+--------------+-----------------+
1 row in set (0.00 sec)
```

첫 문장은 running value를 바꾸고, 두 번째 문장은 running value와 `mysqld-auto.cnf`의 persisted value를 갱신한다. 실제 운영에서는 같은 값에 대해 둘을 연속 실행할 필요는 없다. 예시는 `GLOBAL`과 `PERSIST`의 차이를 확인하기 위한 것이며, 최종 정책에는 보통 `SET PERSIST` 하나를 사용한다.

`SET PERSIST`는 서버 재시작 후에도 적용되므로 변경 관리 대상이다. 설정 변경 전후의 값, 승인 번호, 되돌릴 값, 적용 시각을 기록해야 한다. 구성 관리 도구가 option file을 관리하는 환경에서는 `mysqld-auto.cnf`와 선언형 설정 사이의 이중 관리도 피해야 한다.

## 4. JSON sink를 안전하게 도입하기

`log_sink_json`은 오류 event를 구조화된 JSON object로 기록한다. 사람이 읽는 텍스트보다 장황할 수 있지만, 수집 시스템이 timestamp, priority, error code, subsystem, message를 안정적으로 분리할 수 있다는 장점이 있다.

### 4.1 병행 출력으로 먼저 검증한다

기존 traditional sink를 즉시 제거하지 말고 JSON sink를 병행하는 방식이 안전하다.

```sql
INSTALL COMPONENT 'file://component_log_sink_json';

SET PERSIST log_error_services =
  'log_filter_internal; log_sink_internal; log_sink_json';

SELECT @@GLOBAL.log_error_services AS active_services;

SELECT COMPONENT_URN
FROM mysql.component
WHERE COMPONENT_URN = 'file://component_log_sink_json';
```

실행 결과(MySQL 8.0.x):

```text
mysql> INSTALL COMPONENT 'file://component_log_sink_json';

Query OK, 0 rows affected (0.00 sec)

mysql> SET PERSIST log_error_services =
    ->   'log_filter_internal; log_sink_internal; log_sink_json';

Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@GLOBAL.log_error_services AS active_services;

+-------------------------------------------------------+
| active_services                                       |
+-------------------------------------------------------+
| log_filter_internal; log_sink_internal; log_sink_json |
+-------------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT COMPONENT_URN
    -> FROM mysql.component
    -> WHERE COMPONENT_URN = 'file://component_log_sink_json';

+--------------------------------+
| COMPONENT_URN                  |
+--------------------------------+
| file://component_log_sink_json |
+--------------------------------+
1 row in set (0.00 sec)
```

이 구성에서는 동일한 filter 결과가 traditional sink와 JSON sink에 전달된다. `log_error`가 파일명을 가리키면 JSON sink는 해당 이름을 기준으로 `.00.json` suffix가 붙은 파일을 사용한다. JSON sink를 여러 번 선언하면 `.01.json`처럼 번호가 증가한다. `log_error=stderr`인 container 환경에서는 JSON이 console로 출력될 수 있으므로 sidecar나 container runtime의 수집 방식과 맞춰야 한다.

운영 적용 후에는 다음을 확인한다.

1. JSON 파일 또는 stdout에 새 이벤트가 실제 도착하는가.
2. 수집기가 JSON 한 줄을 하나의 event로 인식하는가.
3. timestamp와 timezone이 중앙 플랫폼의 기준과 일치하는가.
4. traditional·JSON 병행으로 저장량과 전송량이 예상보다 증가하지 않는가.
5. rotate·retention 정책이 `.00.json` 파일을 포함하는가.

### 4.2 JSON field를 운영 키로 사용한다

JSON 출력의 장점은 message 문자열을 정규식으로만 해석하지 않아도 된다는 점이다. 대표적으로 다음 속성을 운영 키로 사용할 수 있다.

- `time`: event 발생 시각
- `thread`: event를 기록한 mysqld thread 식별자
- `prio`: 숫자 priority
- `label`: `Error`, `Warning`, `Note` 등 사람이 읽는 priority
- `err_code`: `MY-...` 형식 오류 코드
- `subsystem`: `Server`, `InnoDB` 등 발생 영역
- `msg`: 실제 메시지

minor version과 event 종류에 따라 optional field가 달라질 수 있다. 따라서 모든 event에 `source_file`, `source_line` 같은 field가 존재한다고 가정해서는 안 된다. 중앙 수집 schema에서는 필수 키와 선택 키를 구분하고, 새로운 field가 추가되어도 event 전체를 버리지 않도록 설계한다.

## 5. 규칙 기반 filtering: log_filter_dragnet

`log_filter_internal`은 priority 기준에는 간단하고 안정적이지만, 특정 오류 코드·subsystem·반복 빈도에 따른 세밀한 정책은 표현하기 어렵다. `log_filter_dragnet`은 `IF ... THEN ... .` 형식의 규칙 언어를 제공한다.

다음 예시는 Information event를 버리고, 남은 event에서 `source_line`이 있을 때 해당 field를 제거한다.

```sql
INSTALL COMPONENT 'file://component_log_filter_dragnet';

SET PERSIST dragnet.log_error_filter_rules = '
  IF prio>=INFORMATION THEN drop.
  IF EXISTS source_line THEN unset source_line.
';

SET PERSIST log_error_services =
  'log_filter_dragnet; log_sink_internal; log_sink_json';

SELECT @@GLOBAL.log_error_services AS active_services,
       @@GLOBAL.dragnet.log_error_filter_rules AS active_rules;
```

실행 결과(MySQL 8.0.x):

```text
mysql> INSTALL COMPONENT 'file://component_log_filter_dragnet';

Query OK, 0 rows affected (0.00 sec)

mysql> SET PERSIST dragnet.log_error_filter_rules = '
    ->   IF prio>=INFORMATION THEN drop.
    ->   IF EXISTS source_line THEN unset source_line.
    -> ';

Query OK, 0 rows affected (0.00 sec)

mysql> SET PERSIST log_error_services =
    ->   'log_filter_dragnet; log_sink_internal; log_sink_json';

Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@GLOBAL.log_error_services AS active_services,
    ->        @@GLOBAL.dragnet.log_error_filter_rules AS active_rules;

+------------------------------------------------------+-------------------------------------------------------------------------------------+
| active_services                                      | active_rules                                                                        |
+------------------------------------------------------+-------------------------------------------------------------------------------------+
| log_filter_dragnet; log_sink_internal; log_sink_json | 
  IF prio>=INFORMATION THEN drop.
  IF EXISTS source_line THEN unset source_line.
 |
+------------------------------------------------------+-------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
```

각 규칙 끝의 마침표는 선택 사항이 아니라 문법의 일부다. 잘못된 symbolic error name이나 문법은 `SET` 시점에 오류가 발생할 수 있으므로, 운영 서버에 적용하기 전에 같은 MySQL minor version에서 검증해야 한다.

반복되는 Information event를 완전히 지우기보다 속도를 제한하려면 throttle을 사용할 수 있다.

```sql
SET PERSIST dragnet.log_error_filter_rules =
  'IF prio>=INFORMATION THEN throttle 1/60.';

SELECT @@GLOBAL.dragnet.log_error_filter_rules AS active_rules;
```

실행 결과(MySQL 8.0.x):

```text
mysql> SET PERSIST dragnet.log_error_filter_rules =
    ->   'IF prio>=INFORMATION THEN throttle 1/60.';

Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@GLOBAL.dragnet.log_error_filter_rules AS active_rules;

+------------------------------------------+
| active_rules                             |
+------------------------------------------+
| IF prio>=INFORMATION THEN throttle 1/60. |
+------------------------------------------+
1 row in set (0.00 sec)
```

이 규칙은 해당 조건의 event를 60초당 1개로 제한하는 방향으로 동작한다. 반복 폭주를 줄이면서 “그 현상이 계속 발생한다”는 신호는 남길 수 있다. 그러나 서로 다른 원인의 event를 넓은 조건 하나로 묶으면 중요한 최초 증상까지 숨길 수 있다. 실무에서는 priority만 보지 말고 error code, subsystem, message 특성, 발생 빈도를 함께 검토한다.

> `log_filter_dragnet`을 사용할 때는 `log_error_suppression_list`가 무시될 수 있다. 기존 suppression 정책과 dragnet 규칙이 동시에 적용된다고 가정하지 말고, 전환 전에 현재 suppression 목록을 규칙으로 옮길지 결정해야 한다.

## 6. performance_schema.error_log로 최근 이벤트 조사하기

파일 접근 권한이 제한된 환경에서도 `performance_schema.error_log`가 제공되면 SQL로 최근 event를 확인할 수 있다.

```sql
SELECT LOGGED,
       PRIO,
       ERROR_CODE,
       SUBSYSTEM,
       LEFT(DATA, 160) AS data_excerpt
FROM performance_schema.error_log
ORDER BY LOGGED DESC
LIMIT 10;
```

실행 결과(MySQL 8.0.x):

검증 결과에서 대표적인 3개 행을 발췌했다. `DATA`는 원래 쿼리의 `LEFT(..., 160)` 결과다.

```text
mysql> SELECT LOGGED, PRIO, ERROR_CODE, SUBSYSTEM,
    ->        LEFT(DATA, 160) AS data_excerpt
    -> FROM performance_schema.error_log
    -> ORDER BY LOGGED DESC
    -> LIMIT 10;

+----------------------------+---------+------------+-----------+------------------------------------------------------------+
| LOGGED                     | PRIO    | ERROR_CODE | SUBSYSTEM | data_excerpt                                               |
+----------------------------+---------+------------+-----------+------------------------------------------------------------+
| 2026-08-30 00:02:56.078852 | System  | MY-010931  | Server    | /usr/sbin/mysqld: ready for connections. Version: '8.0.46' |
| 2026-08-30 00:02:56.063300 | Warning | MY-011810  | Server    | Insecure configuration for --pid-file: Location ...        |
| 2026-08-30 00:02:55.935531 | System  | MY-013577  | InnoDB    | InnoDB initialization has ended.                           |
+----------------------------+---------+------------+-----------+------------------------------------------------------------+
```

이 테이블은 무한한 장기 보관소가 아니다. 최근 사건 조사와 상태 확인에 유용하지만 중앙 로그 저장소, object storage, backup을 대신하지 않는다. 또한 `DATA` 형식은 event를 공급한 sink에 따라 달라질 수 있다.

JSON 형식 행만 골라 주요 field를 추출하려면 다음처럼 조회할 수 있다.

```sql
SELECT LOGGED,
       PRIO,
       ERROR_CODE,
       JSON_UNQUOTE(JSON_EXTRACT(DATA, '$.subsystem')) AS json_subsystem,
       JSON_UNQUOTE(JSON_EXTRACT(DATA, '$.msg')) AS json_message
FROM performance_schema.error_log
WHERE JSON_VALID(DATA) = 1
ORDER BY LOGGED DESC
LIMIT 10;
```

실행 결과(MySQL 8.0.x):

```text
mysql> SELECT LOGGED,
    ->        PRIO,
    ->        ERROR_CODE,
    ->        JSON_UNQUOTE(JSON_EXTRACT(DATA, '$.subsystem')) AS json_subsystem,
    ->        JSON_UNQUOTE(JSON_EXTRACT(DATA, '$.msg')) AS json_message
    -> FROM performance_schema.error_log
    -> WHERE JSON_VALID(DATA) = 1
    -> ORDER BY LOGGED DESC
    -> LIMIT 10;

Empty set (0.00 sec)
```

결과가 `Empty set`이어도 쿼리 실패를 뜻하지 않는다. JSON sink 전환 이후 아직 새 event가 없거나, 현재 `error_log` table에 traditional 형식 행만 남아 있을 수 있다. 반대로 `JSON_VALID(DATA)=1`이라고 해서 모든 optional field가 존재한다는 뜻도 아니다. `JSON_EXTRACT()` 결과가 `NULL`이면 field 부재와 JSON literal `null`을 구분해서 수집기 로직을 설계해야 한다.

## 7. 필터 규칙을 설계하는 원칙

### 7.1 drop보다 관측과 throttle을 먼저 사용한다

로그가 많다는 이유만으로 넓은 drop 규칙을 먼저 적용하면 다음 정보를 잃기 쉽다.

- 장애가 시작되기 전 최초 Warning
- 재시작·복구 과정의 상태 전이
- replication 또는 storage subsystem의 사전 징후
- 동일 error code라도 message context가 다른 사건

권장 순서는 **수집 → 분류 → 빈도 측정 → throttle 후보 선정 → 제한된 drop 검토**다. 규칙 변경 전후에는 error code별 건수와 전체 byte 증가율을 비교한다.

### 7.2 sink 앞에 filter를 배치한다

filter는 자신보다 뒤에 있는 component에만 영향을 준다. 다음 구성은 JSON 원본과 filtering된 traditional log를 동시에 남기는 의도라면 타당하다.

```text
log_sink_json; log_filter_dragnet; log_sink_internal
```

그러나 “두 출력 모두 filtering한다”는 목적이라면 틀린 구성이다. 그 목적에는 다음 순서가 맞다.

```text
log_filter_dragnet; log_sink_internal; log_sink_json
```

구성 문자열을 review할 때는 component 존재 여부만 보지 말고 데이터 흐름을 왼쪽에서 오른쪽으로 읽어야 한다.

### 7.3 수집기 전환과 서버 전환을 하나의 배포로 관리한다

JSON sink 활성화는 서버 설정 변경이면서 동시에 observability pipeline 변경이다. 다음 요소를 한 배포 단위로 묶는다.

- mysqld component 및 `log_error_services`
- 파일 glob 또는 stdout 수집 경로
- JSON parser와 field mapping
- error code·subsystem 기반 routing
- retention·압축·전송량 한도
- dashboard와 alert query
- rollback 절차

서버는 정상인데 parser 오류로 중앙 로그만 비는 상황을 방지하려면, 배포 직후 로컬 출력과 중앙 수집 건수를 동시에 확인해야 한다.

## 8. 흔한 실패 모드와 복구

### 8.1 sink 없는 구성

filter만 남기면 출력 대상이 없어 설정이 거부된다. 항상 최소 하나의 sink를 포함한다.

```text
log_filter_internal; log_sink_internal
```

### 8.2 규칙이 너무 넓어 필요한 이벤트까지 제거됨

가장 빠른 복구는 검증된 단순 구성으로 돌아가는 것이다.

```sql
SET PERSIST log_error_services =
  'log_filter_internal; log_sink_internal';

SET PERSIST log_error_verbosity = 3;

SELECT @@GLOBAL.log_error_services AS active_services,
       @@GLOBAL.log_error_verbosity AS active_verbosity;
```

실행 결과(MySQL 8.0.x):

```text
mysql> SET PERSIST log_error_services =
    ->   'log_filter_internal; log_sink_internal';

Query OK, 0 rows affected (0.00 sec)

mysql> SET PERSIST log_error_verbosity = 3;

Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@GLOBAL.log_error_services AS active_services,
    ->        @@GLOBAL.log_error_verbosity AS active_verbosity;

+----------------------------------------+------------------+
| active_services                        | active_verbosity |
+----------------------------------------+------------------+
| log_filter_internal; log_sink_internal |                3 |
+----------------------------------------+------------------+
1 row in set (0.00 sec)
```

component를 제거하려면 먼저 `log_error_services`에서 해당 component를 빼야 한다. 사용 중인 component를 무작정 `UNINSTALL COMPONENT`하는 절차를 자동화해서는 안 된다.

### 8.3 JSON 파일이 생성되지만 수집되지 않음

다음 순서로 조사한다.

1. `@@GLOBAL.log_error`와 `@@GLOBAL.log_error_services`를 확인한다.
2. `log_error` 기준 `.00.json` 파일 또는 container stdout을 확인한다.
3. mysqld 실행 사용자와 수집 agent의 읽기 권한을 확인한다.
4. 수집 glob이 suffix 파일을 포함하는지 확인한다.
5. JSON parser 오류와 multiline 설정을 확인한다.
6. 중앙 플랫폼의 ingest quota, drop rule, timestamp parsing을 확인한다.

### 8.4 재시작 후 설정이 달라짐

`SET GLOBAL`만 사용하면 재시작 후 사라진다. `SET PERSIST`, option file, configuration management 가운데 어느 것이 source of truth인지 정하고 중복 정의를 피한다. `performance_schema.persisted_variables`와 실제 global value를 비교하면 drift를 발견하는 데 도움이 된다.

### 8.5 너무 공격적인 규칙 때문에 원인 분석 불가

필요하면 filter 앞의 별도 sink에 원본을 제한된 기간 보존할 수 있다. 다만 원본에는 개인정보나 인증 관련 context가 더 많이 남을 수 있으므로 access control, encryption, retention을 더 엄격하게 적용해야 한다.

## 9. Aurora MySQL에서의 운영 해석

Aurora MySQL도 MySQL 호환 오류 로그와 DB parameter를 제공하지만, self-managed MySQL처럼 host filesystem의 파일 경로와 component 설치 절차를 자유롭게 제어한다고 가정하면 안 된다.

- 변경 가능한 parameter와 적용 유형(dynamic/static)은 Aurora MySQL engine version과 parameter group에서 확인한다.
- 오류 로그 export는 Amazon CloudWatch Logs 연동을 중심으로 설계하는 경우가 많다.
- DB instance 내부 파일 suffix를 직접 수집하기보다 RDS API, console, CloudWatch Logs의 log group과 subscription filter가 운영 경계가 된다.
- Aurora failover 후 writer instance가 바뀌므로 instance 단위 stream과 cluster 사건을 correlation해야 한다.
- MySQL Community에서 검증한 `INSTALL COMPONENT`, `SET PERSIST`, filesystem 경로 절차를 Aurora에 그대로 적용하지 않는다.

따라서 Aurora에서는 “JSON sink를 설치했는가”보다 **엔진이 제공하는 log format·export 기능, parameter group, CloudWatch field parsing, failover 후 stream 연속성**을 우선 확인한다. Community MySQL과 동일한 개념을 적용하되 제어면은 AWS 관리 경계를 따른다.

## 10. 변경 전후 점검표

### 변경 전

- [ ] 대상 MySQL exact version과 지원 component를 확인했다.
- [ ] 현재 `log_error`, `log_error_services`, `log_error_verbosity`, `log_timestamps`를 기록했다.
- [ ] 기존 suppression·filter 정책과 중앙 수집 규칙을 확인했다.
- [ ] JSON 파일 또는 stdout의 예상 목적지와 권한을 확인했다.
- [ ] 저장량·전송량 증가와 retention 영향을 계산했다.
- [ ] 되돌릴 `log_error_services` 문자열을 준비했다.

### 변경 직후

- [ ] `@@GLOBAL.log_error_services`가 의도한 순서와 일치한다.
- [ ] traditional·JSON 출력 중 의도한 sink에 새 event가 기록된다.
- [ ] 중앙 수집기의 JSON parse error가 증가하지 않는다.
- [ ] timestamp, priority, error code, subsystem, message가 올바른 field로 들어온다.
- [ ] alert query가 새 schema에서도 동작한다.
- [ ] `performance_schema.error_log`의 최근 event를 확인했다.

### 안정화 후

- [ ] error code·subsystem별 유입량을 기준선과 비교했다.
- [ ] throttle 또는 drop 규칙이 실제 장애 신호를 숨기지 않는지 review했다.
- [ ] duplicate sink에 따른 저장량 증가를 확인했다.
- [ ] restart 또는 failover 뒤에도 persisted 설정과 수집이 유지되는지 검증했다.
- [ ] runbook에 적용·검증·rollback 명령을 반영했다.

## 11. 결론

MySQL 8.0 오류 로그 운영의 핵심은 format 선택보다 **event가 filter와 sink를 어떤 순서로 통과하는지** 이해하는 데 있다. `log_filter_internal`은 단순한 priority 정책에 적합하고, `log_filter_dragnet`은 error code·field·빈도를 고려한 정교한 제어에 적합하다. `log_sink_json`은 중앙 수집과 검색을 구조화하지만, parser·보존·권한·rollback까지 함께 준비해야 비로소 운영 개선이 된다.

안전한 도입 순서는 현재 상태 기록, traditional·JSON 병행 출력, 중앙 수집 검증, 제한적인 filtering 적용, 재시작·failover 검증이다. 다음 기술노트에서는 오류 로그뿐 아니라 slow query log와 Performance Schema를 연결해, 단일 장애 시점의 server event와 query workload를 함께 추적하는 관측 절차를 다룬다.
