MySQL binlog 보존 정책: expire_logs_days와 binlog_expire_logs_seconds
MySQL binary log의 자동 삭제 원리와 보존 기간 산정, 구식 변수 전환, 복제·PITR 관점의 운영 기준을 정리한다.
MySQL binary log는 복제의 전달 기록이면서 Point-in-Time Recovery(PITR)의 변경 이력이다. 그러나 보존 기간을 무조건 길게 잡으면 저장 공간이 고갈되고, 너무 짧게 잡으면 지연된 replica가 재동기화되지 못하거나 복구 목표 시점까지 도달할 수 없다. 따라서 binlog 보존 정책은 단순한 로그 정리 옵션이 아니라 복제 복구 능력, 백업 체계, 장애 대응 시간, 저장 공간 예산을 하나로 연결하는 운영 정책이다.
MySQL 8.0 이상에서는 초 단위 변수인 binlog_expire_logs_seconds를 기준으로 관리해야 한다. 일 단위의 expire_logs_days는 호환성을 위해 남아 있는 deprecated 변수이므로 신규 구성에 사용하지 않는 것이 원칙이다. 이 글은 두 변수의 관계, 자동 삭제가 실제로 일어나는 시점, 보존 기간 계산법, 수동 정리 시 주의점, Aurora MySQL의 차이를 순서대로 설명한다.
1. binary log 보존이 보호하는 세 가지 시간축
보존 기간을 결정하기 전에 binlog를 누가 소비하는지 구분해야 한다.
- 복제 시간축: source에서 replica가 아직 가져가지 못한 이벤트를 보호한다.
- 복구 시간축: 전체·증분 백업 이후 목표 시점까지 재생할 변경 이력을 보호한다.
- 감사·분석 시간축: 장애 원인 분석이나 변경 추적에 필요한 기간을 보호한다.
세 시간축 가운데 가장 긴 요구사항보다 보존 기간이 짧으면 언젠가는 복구 공백이 생긴다. 반대로 감사를 위해 장기간 보관해야 한다면 운영 서버 로컬 디스크에만 binlog를 쌓는 방식보다 외부 저장소로 안전하게 반출하는 편이 낫다. 로컬 보존은 빠른 복제와 복구를 위한 짧은 작업 구간으로 두고, 장기 보존은 별도의 수명 주기 정책으로 관리하는 것이 일반적이다.
flowchart LR
A[트랜잭션 COMMIT] --> B[binary log 기록]
B --> C[replica 전송·적용]
B --> D[백업 이후 PITR 재생]
B --> E[장애 분석·감사]
C --> F{모든 소비 요구 충족?}
D --> F
E --> F
F -- 아니오 --> G[보존 유지]
F -- 예 --> H[만료 후 purge 후보]
H --> I[서버 시작 또는 binlog flush 시 삭제]
이 흐름에서 중요한 점은 “만료 시간이 지났다”와 “파일이 즉시 삭제되었다”가 같은 뜻이 아니라는 사실이다. 만료된 파일은 purge가 수행되는 시점에 삭제 대상이 된다.
2. 자동 만료의 내부 동작
2.1 활성 파일이 아니라 닫힌 파일이 대상이다
MySQL은 binary log를 여러 파일로 회전시키고 index 파일로 목록을 관리한다. 현재 쓰는 active binlog는 단순히 보존 시간이 지났다는 이유로 삭제되지 않는다. 오래되어 만료된 이전 파일이 자동 정리 대상이다. 파일 시스템에서 binlog 파일만 직접 삭제하면 index와 서버 상태가 어긋날 수 있으므로 운영자가 rm으로 정리해서는 안 된다.
자동 purge 가능 시점은 대표적으로 다음과 같다.
- MySQL 서버 시작 시
- binary log가 flush 또는 rotate될 때
FLUSH BINARY LOGS등으로 새 파일을 열 때
따라서 보존 시간을 줄인 직후에도 디스크 사용량이 즉시 감소하지 않을 수 있다. 정책 변경과 실제 파일 삭제 사이에는 다음 회전 시점까지 간격이 생길 수 있다. 긴급 공간 회수가 필요하다면 복제와 복구 요구를 확인한 뒤 PURGE BINARY LOGS를 계획적으로 실행해야 한다.
2.2 기준은 파일의 이벤트 시간이 아니라 만료 정책이다
binlog_expire_logs_seconds는 binary log 만료 기간을 초 단위로 지정한다. MySQL 8.0의 기본값은 2592000, 즉 30일이다. 값이 0이면 기간 기반 자동 purge를 중지하는 의미가 된다. MySQL 8.0.29 이상에서는 binlog_expire_logs_auto_purge=OFF도 자동 purge를 비활성화하며, 이 설정은 만료 초 값보다 우선한다.
자동 purge를 끄는 것은 “로그를 안전하게 영구 보존한다”는 뜻이 아니다. 저장 공간 상한과 외부 반출 절차가 없다면 디스크 고갈 시 MySQL 전체 가용성을 해칠 수 있다. 비활성화는 백업 제품이나 외부 수명 주기 관리자가 삭제를 완전히 통제하는 경우에만 선택해야 한다.
3. 두 변수의 관계와 전환 원칙
3.1 binlog_expire_logs_seconds: MySQL 8.0의 기준 변수
binlog_expire_logs_seconds는 MySQL 8.0.1에서 추가되었고 초 단위로 세밀하게 지정할 수 있다. 예를 들어 7일은 604800, 14일은 1209600초다. Global 범위의 dynamic 변수이므로 실행 중 변경할 수 있지만, 재시작 후에도 유지하려면 구성 파일이나 SET PERSIST 같은 영속화 방법이 필요하다.
3.2 expire_logs_days: 신규 구성에서 제외할 deprecated 변수
expire_logs_days는 일 단위 정수만 받는 과거 변수다. MySQL 8.0에서 deprecated 상태이므로 다음 업그레이드에서 제거될 가능성을 전제로 해야 한다. 기존 구성에서 발견하면 동등한 초 값을 계산하여 binlog_expire_logs_seconds로 이전한다.
두 변수에 동시에 0이 아닌 시작 값을 주면 binlog_expire_logs_seconds가 우선하고 expire_logs_days는 경고와 함께 무시된다. 실행 중에도 한 변수가 0이 아닌 상태에서 다른 변수를 0이 아닌 값으로 설정할 수 없다. 이 상호 배타 규칙 때문에 마이그레이션 시 두 값을 함께 남겨 두는 “안전한 이중 설정”은 오히려 잘못된 접근이다.
| 구분 | expire_logs_days |
binlog_expire_logs_seconds |
|---|---|---|
| 단위 | 일 | 초 |
| MySQL 8.0 상태 | deprecated | 권장 |
| 정밀도 | 1일 단위 | 1초 단위 |
| 신규 구성 | 사용하지 않음 | 사용 |
| 기본 운영 원칙 | 제거 계획 수립 | 단일 기준 변수로 관리 |
현재 서버의 관련 상태는 performance_schema.global_variables에서 한 번에 확인할 수 있다. log_bin=OFF라면 보존 변수 값이 있더라도 해당 서버가 binary log를 생성하지 않는 상태다.
SELECT VERSION() AS mysql_version;
SELECT VARIABLE_NAME, VARIABLE_VALUE
FROM performance_schema.global_variables
WHERE VARIABLE_NAME IN (
'log_bin',
'binlog_expire_logs_seconds',
'binlog_expire_logs_auto_purge',
'expire_logs_days'
)
ORDER BY VARIABLE_NAME;
실행 결과(MySQL 8.0.x):
mysql> SELECT VERSION() AS mysql_version;
+---------------+
| mysql_version |
+---------------+
| 8.0.46 |
+---------------+
1 row in set (0.00 sec)
mysql> SELECT VARIABLE_NAME, VARIABLE_VALUE
-> FROM performance_schema.global_variables
-> WHERE VARIABLE_NAME IN (
-> 'log_bin',
-> 'binlog_expire_logs_seconds',
-> 'binlog_expire_logs_auto_purge',
-> 'expire_logs_days'
-> )
-> ORDER BY VARIABLE_NAME;
+-------------------------------+----------------+
| VARIABLE_NAME | VARIABLE_VALUE |
+-------------------------------+----------------+
| binlog_expire_logs_auto_purge | ON |
| binlog_expire_logs_seconds | 2592000 |
| expire_logs_days | 0 |
| log_bin | OFF |
+-------------------------------+----------------+
4 rows in set (0.00 sec)
검사할 때는 다음 조합을 특히 주의한다.
log_bin=ON,binlog_expire_logs_auto_purge=ON, 초 값이 양수: 일반적인 자동 보존 정책log_bin=ON, 자동 purge가OFF: 별도 정리 주체와 저장 공간 경보가 반드시 필요- 두 만료 변수가 모두 0: 기간 기반 자동 정리가 멈춘 상태
expire_logs_days가 양수: 업그레이드 전에 전환해야 할 기술 부채
4. 보존 기간은 어떻게 계산해야 하는가
“기본값 30일이면 충분한가”라는 질문에는 트래픽보다 먼저 RPO·RTO와 replica 운영 방식을 대입해야 한다. 다음 요소를 시간 단위로 수집한다.
- 계획·비계획 replica 중단의 최대 예상 시간
- 중단 후 backlog를 따라잡는 데 필요한 시간
- 유효한 base backup 사이의 최대 간격
- 백업 복원 및 검증에 걸리는 시간
- 장애 인지와 담당자 의사결정에 필요한 시간
- 주말·연휴·변경 동결을 흡수할 안전 여유
실무적인 하한은 다음과 같이 잡을 수 있다.
복제 요구 시간 = 최대 replica 중단 시간 + 최대 catch-up 시간
PITR 요구 시간 = base backup 간격 + 복원·검증 시간
최소 보존 시간 = MAX(복제 요구 시간, PITR 요구 시간) + 운영 안전 여유
예를 들어 replica가 최장 36시간 중단될 수 있고 재추적에 8시간, 매일 백업의 복원 검증에 4시간, 운영 안전 여유가 12시간이라면 계산상 최소값은 56시간이다.
SET @replica_outage_hours = 36;
SET @replica_catchup_hours = 8;
SET @backup_interval_hours = 24;
SET @restore_validation_hours = 4;
SET @safety_margin_hours = 12;
SELECT
@replica_outage_hours + @replica_catchup_hours
AS replication_requirement_hours,
@backup_interval_hours + @restore_validation_hours
AS pitr_requirement_hours,
GREATEST(
@replica_outage_hours + @replica_catchup_hours,
@backup_interval_hours + @restore_validation_hours
) + @safety_margin_hours AS minimum_retention_hours,
3600 * (
GREATEST(
@replica_outage_hours + @replica_catchup_hours,
@backup_interval_hours + @restore_validation_hours
) + @safety_margin_hours
) AS binlog_expire_logs_seconds;
실행 결과(MySQL 8.0.x):
mysql> SET @replica_outage_hours = 36;
Query OK, 0 rows affected (0.00 sec)
mysql> SET @replica_catchup_hours = 8;
Query OK, 0 rows affected (0.00 sec)
mysql> SET @backup_interval_hours = 24;
Query OK, 0 rows affected (0.00 sec)
mysql> SET @restore_validation_hours = 4;
Query OK, 0 rows affected (0.00 sec)
mysql> SET @safety_margin_hours = 12;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT
-> @replica_outage_hours + @replica_catchup_hours
-> AS replication_requirement_hours,
-> @backup_interval_hours + @restore_validation_hours
-> AS pitr_requirement_hours,
-> GREATEST(
-> @replica_outage_hours + @replica_catchup_hours,
-> @backup_interval_hours + @restore_validation_hours
-> ) + @safety_margin_hours AS minimum_retention_hours,
-> 3600 * (
-> GREATEST(
-> @replica_outage_hours + @replica_catchup_hours,
-> @backup_interval_hours + @restore_validation_hours
-> ) + @safety_margin_hours
-> ) AS binlog_expire_logs_seconds;
+-------------------------------+------------------------+-------------------------+----------------------------+
| replication_requirement_hours | pitr_requirement_hours | minimum_retention_hours | binlog_expire_logs_seconds |
+-------------------------------+------------------------+-------------------------+----------------------------+
| 44 | 28 | 56 | 201600 |
+-------------------------------+------------------------+-------------------------+----------------------------+
1 row in set (0.00 sec)
이 계산은 정책의 출발점이지 용량 검증을 대신하지 않는다. 최종 설정 전에는 다음 용량 식도 함께 계산해야 한다.
예상 binlog 필요 용량
= 시간당 최대 binlog 생성량 × 보존 시간 × 변동 여유 계수
평균 생성량만 사용하면 대량 배치, DDL, 데이터 마이그레이션, 장애 후 재처리 구간을 놓칠 수 있다. 시간당 p95 또는 관측된 최대 생성량을 기준으로 잡고, 파일 시스템의 다른 소비자와 운영 예비 공간을 별도로 남긴다.
5. 설정 적용과 검증
5.1 Community MySQL
7일 보존을 구성 파일에 반영하는 예시는 다음과 같다.
[mysqld]
binlog_expire_logs_seconds = 604800
실행 중 변경할 때는 먼저 현재 정책과 replica 상태를 확인하고 다음과 같이 적용한다.
SET @previous_binlog_expire_seconds = @@GLOBAL.binlog_expire_logs_seconds;
SET GLOBAL binlog_expire_logs_seconds = 604800;
SELECT @@GLOBAL.binlog_expire_logs_seconds AS active_retention_seconds;
SET GLOBAL binlog_expire_logs_seconds = @previous_binlog_expire_seconds;
SELECT @@GLOBAL.binlog_expire_logs_seconds AS restored_retention_seconds;
실행 결과(MySQL 8.0.x):
mysql> SET @previous_binlog_expire_seconds = @@GLOBAL.binlog_expire_logs_seconds;
Query OK, 0 rows affected (0.00 sec)
mysql> SET GLOBAL binlog_expire_logs_seconds = 604800;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @@GLOBAL.binlog_expire_logs_seconds AS active_retention_seconds;
+--------------------------+
| active_retention_seconds |
+--------------------------+
| 604800 |
+--------------------------+
1 row in set (0.00 sec)
mysql> SET GLOBAL binlog_expire_logs_seconds = @previous_binlog_expire_seconds;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @@GLOBAL.binlog_expire_logs_seconds AS restored_retention_seconds;
+----------------------------+
| restored_retention_seconds |
+----------------------------+
| 2592000 |
+----------------------------+
1 row in set (0.00 sec)
위 예제는 dynamic 변경과 원복을 검증하기 위한 것이다. 실제 운영에서는 재시작 후 원래 값으로 돌아가지 않도록 구성 관리 시스템이나 다음과 같은 영속화 방식을 검토한다.
SET PERSIST binlog_expire_logs_seconds = 604800;
SET PERSIST는 서버의 영속 변수 파일을 변경하므로 조직의 구성 관리 도구와 충돌하지 않는지 먼저 확인해야 한다. 선언형 구성 파일을 source of truth로 삼는 환경에서는 파일 배포와 계획된 재시작 또는 별도 runtime 적용 절차를 사용하는 편이 일관적이다.
5.2 정책 변경 후 확인할 항목
정책 값을 읽는 것만으로 검증이 끝나지 않는다. 다음을 함께 확인한다.
SHOW BINARY LOGS로 가장 오래된 파일과 파일별 크기를 확인한다.- replica가 읽고 있거나 적용 중인 source log 위치를 확인한다.
- 최근 base backup이 실제 복원 가능한지 확인한다.
- 다음 rotate 이후 오래된 파일이 정책대로 줄어드는지 확인한다.
- binlog 전체 크기와 증가율에 경보를 둔다.
- MySQL error log에서 만료 변수 충돌이나 purge 관련 경고를 확인한다.
아래 명령은 자동 만료를 기다리지 않고 관리자가 특정 파일 이전을 정리할 때 쓰는 형태다. 실행 전 모든 replica와 PITR 체인이 해당 파일을 더 이상 필요로 하지 않는지 확인해야 하므로 예시 구조로만 제시한다.
PURGE BINARY LOGS TO 'mysql-bin.000250';
PURGE BINARY LOGS BEFORE '2026-09-01 00:00:00';
PURGE BINARY LOGS TO는 지정 파일 자체는 남기고 그 이전 파일을 제거한다. 날짜 방식은 서버 시간대와 파일 경계를 오판하기 쉬우므로 파일명 기반 절차가 더 명확한 경우가 많다. 어떤 방식이든 파일 시스템의 직접 삭제보다 SQL 문을 사용해야 index가 일관되게 갱신된다.
6. replica와 PITR 관점의 실패 모드
6.1 지연 replica가 필요한 source binlog를 잃는 경우
보존 기간보다 오래 중단된 replica가 재접속하면 source가 필요한 파일을 이미 삭제했을 수 있다. 이 경우 단순 재시작으로 따라잡을 수 없으며 새 base copy에서 replica를 다시 구축해야 한다. GTID를 사용하더라도 실제 트랜잭션 이벤트가 사라지면 그 공백을 마법처럼 복원하지는 못한다.
따라서 보존 정책은 정상 시점의 Seconds_Behind_Source만 보고 결정하면 안 된다. 네트워크 단절, 유지보수, 대형 트랜잭션, replica 재시작, 병렬 적용 효율 저하를 포함한 최악의 합리적 중단 시간을 반영해야 한다.
6.2 백업은 있지만 연속 binlog가 없는 경우
PITR은 base backup만으로 완성되지 않는다. 백업이 끝난 지점부터 목표 시점까지 binary log가 연속적으로 존재해야 한다. 중간 파일 하나라도 빠지면 그 이후 시점으로 논리적 재생을 이어 갈 수 없다. 보존 기간 변경 전에는 가장 오래된 유효 백업의 binlog 좌표 또는 GTID 집합과 현재 보존 파일의 시작점을 대조해야 한다.
6.3 디스크 압박 때문에 성급하게 purge하는 경우
디스크 사용률이 임계치에 도달한 뒤 처음으로 purge를 고민하면 안전과 가용성이 충돌한다. 급하게 오래된 파일을 지우면 복구 능력을 잃고, 지우지 않으면 MySQL 쓰기가 중단될 수 있다. 이를 피하려면 다음 두 경보를 별도로 둔다.
- 시간 경보: 가장 오래된 binlog의 보존 가능 시간이 요구 하한에 접근함
- 용량 경보: 현재 생성률을 기준으로 파일 시스템 고갈까지 남은 시간이 운영 대응 시간보다 짧아짐
정책은 일수 하나가 아니라 “요구 보존 시간, 실제 보존 시간, 증가율, 잔여 용량” 네 지표로 운영해야 한다.
7. Aurora MySQL에서는 무엇이 다른가
Aurora MySQL에서는 호스트 파일 시스템과 binary log 파일 수명 주기를 Community MySQL처럼 직접 관리하지 않는다. 외부 MySQL 복제나 binlog 소비자를 위해 보존 시간이 필요하면 AWS가 제공하는 저장 프로시저 기반 설정을 사용한다.
CALL mysql.rds_set_configuration('binlog retention hours', 24);
CALL mysql.rds_show_configuration;
Aurora MySQL의 binlog retention hours 기본값 NULL은 binary log가 일정 기간 지연 정리될 수 있음을 뜻하며, 명시적인 복제 보존 SLA로 해석해서는 안 된다. Aurora MySQL version 2.11.0 이상과 version 3에서는 문서상 최대 2160시간(90일)까지 설정할 수 있지만, 엔진 버전과 리전 지원 범위는 적용 전에 확인해야 한다.
또한 Aurora의 continuous backup과 point-in-time restore는 분산 스토리지 계층의 관리형 백업 기능이다. 이것을 외부 binlog replica가 요구하는 binary log 보존과 혼동하면 안 된다. 다음 요구를 분리해서 설계한다.
- Aurora 관리형 PITR의 복원 가능 기간
- 외부 replica 또는 CDC가 소비할 binary log의 보존 시간
- 장기 감사 목적의 외부 아카이빙 기간
- cluster parameter group에서 binary logging을 활성화한 방식과 재부팅 영향
즉, Aurora에서는 binlog_expire_logs_seconds를 로컬 디스크 정리 변수처럼 조정하기보다 서비스가 제공하는 binlog retention hours와 관리형 백업 정책을 각각의 목적에 맞게 운영해야 한다.
8. 흔한 오해와 주의점
오해 1: “30일 기본값이면 모든 환경에서 안전하다”
기본값은 업무의 RPO, 최대 replica 중단 시간, 백업 주기를 알지 못한다. 30일이 과도할 수도 있고 부족할 수도 있다. 요구시간과 생성량으로 산정해야 한다.
오해 2: “값을 줄이면 오래된 파일이 즉시 사라진다”
만료 파일 삭제는 서버 시작과 binlog flush·rotate 같은 purge 기회에 일어난다. 설정 변경 직후의 디스크 사용량만 보고 실패로 판단하지 않는다.
오해 3: “GTID를 쓰면 source의 오래된 binlog가 없어도 된다”
GTID는 트랜잭션 식별과 위치 관리를 단순화하지만 삭제된 이벤트 내용을 대체하지 않는다. 필요한 GTID가 source 어디에도 없으면 clone이나 새 백업으로 재구축해야 한다.
오해 4: “자동 purge를 끄면 데이터가 더 안전하다”
외부 보존 체계 없이 자동 purge만 끄면 디스크 고갈 위험이 커진다. 안전성은 삭제 금지가 아니라 백업 검증, 외부 보존, 용량 경보, 복구 훈련의 조합에서 나온다.
오해 5: “오래된 파일은 운영체제에서 지워도 된다”
binary log index와 서버가 관리하는 상태를 건너뛰게 된다. 수동 정리는 PURGE BINARY LOGS를 사용하고, 전체 초기화 계열 문장을 일반적인 공간 정리 수단으로 사용하지 않는다.
9. 운영 변경 절차
보존 기간을 바꿀 때는 다음 순서가 안전하다.
- 소비자 식별: replica, CDC, 데이터 파이프라인, PITR, 감사 작업을 목록화한다.
- 요구 시간 계산: 최대 중단·추적 시간, 백업 간격, 복원 검증, 안전 여유를 합산한다.
- 용량 검증: 최대 binlog 생성률로 예상 필요 용량과 파일 시스템 여유를 계산한다.
- 현재 상태 기록: 관련 변수, 가장 오래된·최신 binlog, replica 위치, 백업 좌표를 보관한다.
- 구식 변수 제거:
expire_logs_days와 새 변수를 동시에 유지하지 않는다. - 동적 적용: 필요한 경우
SET GLOBAL로 먼저 적용하고 경고와 동작을 관찰한다. - 영속화: 구성 파일·parameter 관리 체계에 동일한 값을 반영한다.
- 회전 후 확인: 다음 flush·rotate에서 만료 파일이 예상대로 정리되는지 확인한다.
- 복구 검증: backup과 남은 binlog로 목표 시점 복구가 가능한지 정기적으로 시험한다.
- 경보 운영: 보존 시간 부족과 디스크 고갈 예측을 함께 감시한다.
10. DBA 점검표
- MySQL 8.0 이상에서
expire_logs_days대신binlog_expire_logs_seconds -
binlog_expire_logs_auto_purge
맺음말
binlog_expire_logs_seconds는 숫자 하나로 보이지만, 그 값은 replica가 견딜 수 있는 중단 시간과 PITR의 연속성, 저장 공간 고갈 위험을 동시에 결정한다. MySQL 8.0에서는 deprecated된 expire_logs_days를 제거하고 초 단위 변수 하나를 기준으로 삼되, 자동 purge 시점과 용량 증가율까지 함께 관찰해야 한다.
다음 단계에서는 binary log 파일의 생성·회전 조건과 max_binlog_size, 대형 트랜잭션이 파일 경계와 디스크 사용량에 미치는 영향을 연결해서 살펴볼 수 있다.