PRO DBDATA PRO DBDATA

MySQL binlog 용량 관리: 보관 기간 설정과 안전한 PURGE 실습

읽는 시간 약 20분
MySQL binlog 용량 관리

MySQL 서버의 디스크 사용량이 늘어날 때 테이블 크기만 확인하면 원인을 놓칠 수 있습니다. 데이터 변경을 기록하는 Binary Log(binlog)는 테이블과 별개로 공간을 차지합니다. 보관 정책이 제대로 동작하지 않으면 눈에 띄지 않게 계속 쌓입니다.

그렇다고 오래된 파일부터 지우면 복제나 시점 복구(PITR)에 필요한 로그까지 잃을 수 있습니다. 이 글에서는 MySQL 8.4.11에서 보관 기간을 30일에서 7일로 바꾸고, PURGE 명령으로 binlog를 수동 삭제해 약 3.00GiB를 1.96MiB로 줄인 과정을 정리했습니다. 그 과정에서 binlog 목록의 파일 크기만 보고도 알 수 있는 정보가 생각보다 많다는 점도 함께 다룹니다.

복제 지연을 실제로 재현한 실험은 아닙니다. 복제와 마이그레이션 부분은 운영에서 삭제 전에 확인해야 할 기준으로 설명합니다.

실습 전 서버의 binlog 설정 확인

먼저 버전과 binlog 관련 설정을 한 번에 조회했습니다.

sql

SELECT
    VERSION() AS mysql_version,
    @@GLOBAL.log_bin AS log_bin,
    @@GLOBAL.binlog_format AS binlog_format,
    @@GLOBAL.binlog_expire_logs_seconds AS retention_seconds,
    @@GLOBAL.binlog_expire_logs_auto_purge AS auto_purge;
MySQL 8.4.11에서 binlog가 활성화되어 있으며 기록 형식은 ROW입니다. 보관 기간은 2592000초이고 자동 purge는 false로 표시됩니다.

조회 결과는 다음과 같습니다.

  • 버전: MySQL 8.4.11
  • binlog: 켜져 있음 (log_bin=true)
  • 기록 형식: ROW
  • 보관 기간: 2,592,000초 (30일)
  • 자동 삭제(auto_purge): false

자동 삭제가 false인 것은 수동 삭제 결과를 구분하려고 실습 준비 단계에서 일부러 끈 것입니다. MySQL 8.4의 기본값은 ON입니다.

운영 서버에서 이 조합이 보인다면 의미가 다릅니다. 보관 기간은 30일로 적혀 있지만 실제로는 아무 파일도 지워지지 않는 상태입니다. 보관 기간 숫자만 보고 “30일이 지나면 정리된다”고 판단하면 안 되는 이유입니다.

보관 기간과 자동 삭제는 별개의 설정입니다

MySQL 8.4에서 binlog 자동 정리는 두 변수의 조합으로 결정됩니다.

설정역할이번 실습
binlog_expire_logs_seconds자동 삭제 기준이 되는 보관 기간2,592,000초 → 604,800초
binlog_expire_logs_auto_purge자동 삭제 동작 여부OFF 유지

auto_purge가 OFF이면 보관 기간을 아무리 줄여도 자동 삭제는 일어나지 않습니다. 반대로 auto_purge가 ON이어도 binlog_expire_logs_seconds가 0이면 만료 삭제가 꺼집니다. 두 값을 함께 봐야 합니다.

자동 삭제는 백그라운드에서 매초 도는 작업도 아닙니다. MySQL은 서버가 시작될 때와 binlog가 다음 파일로 넘어갈 때만 만료된 파일을 검사합니다. 다음 파일로 넘어가는 경우는 FLUSH BINARY LOGS를 실행했을 때와 파일이 최대 크기에 닿았을 때입니다. 그래서 쓰기가 거의 없는 서버에서는 보관 기간이 지난 파일이 한동안 남아 있을 수 있습니다.

예전 버전에서 쓰던 expire_logs_days는 8.0에서 사용 비권장이 되었고, 8.4에서는 아예 제거되었습니다. 업그레이드한 서버의 설정 파일에 이 변수가 남아 있다면 binlog_expire_logs_seconds로 바꿔야 합니다. 7일이라면 604800입니다.

테스트 데이터 1,000행 준비

실습 테이블은 binlog_test입니다. 1,000행의 payload 컬럼에 각각 ASCII 문자 1,000개를 넣었습니다.

sql

SELECT
    COUNT(*) AS row_count,
    SUM(OCTET_LENGTH(payload)) AS payload_bytes
FROM binlog_retention_lab_20260922.binlog_test;
테스트 데이터는 1,000행이며 payload의 합계는 1,000,000바이트입니다. 이 수치는 실제 테이블 파일 크기나 binlog 크기를 의미하지 않습니다.

1,000행, payload 합계 1,000,000바이트입니다. 이 숫자는 뒤에서 binlog 파일 크기와 비교하는 기준이 됩니다.

이후 로그 파일을 구분하기 위해 변경 작업 앞뒤로 FLUSH BINARY LOGS를 실행하고, 전체 행을 변경했습니다.

sql

FLUSH BINARY LOGS;

START TRANSACTION;

UPDATE binlog_retention_lab_20260922.binlog_test
SET batch_no = 2,
    payload = REPEAT('B', 1000),
    updated_at = NOW(6);

COMMIT;

FLUSH BINARY LOGS;

SHOW BINARY LOGS;
YUNSIK-bin.000001부터 YUNSIK-bin.000010까지 10개 파일이 조회됩니다. 기존에 누적된 로그와 이번 실습 과정에서 기록된 로그가 함께 포함된 목록입니다.

YUNSIK-bin.000001부터 000010까지 10개 파일이 조회되었습니다. File_size 합계는 3,218,969,858바이트(약 3.00GiB)입니다.

이 목록에는 이번 실습 전부터 쌓여 있던 로그가 포함되어 있습니다. 따라서 3GiB 전체가 1,000행 실습에서 생긴 것은 아닙니다. 또 이 값은 MySQL이 보고한 파일 크기이며, 파일시스템 여유 공간을 직접 잰 값은 아닙니다.

1GiB 제한인데 1GiB를 넘은 파일

목록에서 000002는 1,073,798,627바이트, 000003은 1,073,803,440바이트입니다. binlog 파일 하나의 최대 크기를 정하는 max_binlog_size의 기본값은 1GiB(1,073,741,824바이트)입니다. 두 파일 모두 이 값을 각각 약 56KB, 61KB 넘었습니다.

설정 오류가 아닙니다. MySQL은 트랜잭션 하나를 두 binlog 파일로 나눠 쓰지 않습니다. 파일이 1GiB에 닿아도 기록 중인 트랜잭션을 끝까지 쓴 다음에 새 파일로 넘어갑니다. 그래서 대량 변경 트랜잭션이 있으면 파일이 그 트랜잭션 크기만큼 max_binlog_size를 넘을 수 있습니다. 트랜잭션 하나가 수 GB라면 binlog 파일 하나도 수 GB가 됩니다.

디스크 용량을 계획할 때 “파일 하나는 최대 1GiB”라고 가정하면 안 되는 이유입니다.

1MB 데이터 변경이 2MB binlog가 된 이유

목록 끝부분의 작은 파일들은 실습 내용과 직접 연결해 볼 수 있습니다.

파일File_size해석
0000071,029,220payload 1,000,000바이트의 약 1.03배
0000082,058,553payload의 약 2.06배
0000092,058,553000008과 바이트 단위까지 동일
000010158FLUSH 직후 새로 열린 빈 파일

000008과 000009는 크기가 바이트 단위까지 같습니다. 같은 형태의 1,000행 변경이 두 파일에 한 번씩 기록되었다는 뜻으로 보입니다. 변경한 데이터는 1MB인데, 각 파일의 binlog는 약 2.06MB입니다.

이유는 ROW 형식의 기록 방식에 있습니다. 기본 설정인 binlog_row_image=FULL에서는 UPDATE 하나를 기록할 때 변경 전 행 전체와 변경 후 행 전체를 모두 남깁니다. 1,000바이트 payload가 두 번 들어가니 약 2배가 됩니다.

반면 000007은 payload의 약 1.03배로, 000008의 정확히 절반 수준입니다. INSERT는 변경 후 이미지만 기록하므로, 000007이 초기 적재 INSERT였다면 크기가 정확히 맞아떨어집니다. 다만 각 파일에 어떤 문장이 들어 있는지는 이번 캡처만으로 확정할 수 없습니다. mysqlbinlog로 파일 내용을 열어 보면 확인할 수 있습니다.

000010의 158바이트는 데이터 변경 없이 파일 헤더(형식 설명 이벤트 등)만 들어 있는 크기입니다. FLUSH BINARY LOGS를 실행하면 이런 빈 파일이 하나씩 생깁니다.

운영에서는 이렇게 활용할 수 있습니다. ROW 형식, FULL 이미지 설정에서 넓은 행을 자주 UPDATE하는 테이블은 변경한 데이터 크기의 약 2배가 binlog로 쌓입니다. binlog 증가 속도를 추정할 때는 하루 변경 데이터량에 이 배수를 곱해야 합니다.

보관 기간을 7일로 줄여도 파일이 그대로인 이유

보관 기간을 604,800초(7일)로 바꾸고 변경 전후 값을 확인했습니다.

sql

SET GLOBAL binlog_expire_logs_seconds = 604800;

SELECT
    @original_expire_seconds AS previous_seconds,
    @@GLOBAL.binlog_expire_logs_seconds AS current_seconds,
    @@GLOBAL.binlog_expire_logs_seconds / 86400 AS retention_days,
    @@GLOBAL.binlog_expire_logs_auto_purge AS auto_purge;

SHOW BINARY LOGS;

@original_expire_seconds는 준비 단계에서 원래 값을 저장해 둔 세션 변수입니다. 새로 접속하면 이 변수가 비어 있으므로, 직접 재현할 때는 설정을 바꾸기 전에 원래 값을 따로 기록해 두어야 합니다.

previous_seconds는 2592000, current_seconds는 604800, retention_days는 7.0000으로 조회됩니다. 자동 purge는 계속 false인 상태입니다.

이전 값 2,592,000초가 604,800초(7.0000일)로 바뀌었고, auto_purge는 여전히 false입니다.

보관 기간을 변경한 뒤에도 10개 파일과 파일별 크기가 그대로 유지됩니다. 이번 실습은 자동 purge가 꺼진 상태이므로 자동 만료 삭제를 검증한 결과는 아닙니다.

설정을 바꾼 뒤에도 binlog 목록은 10개 파일, 같은 크기 그대로입니다. 자동 삭제가 꺼져 있으니 당연한 결과입니다.

그런데 auto_purge가 ON인 운영 서버에서도 같은 장면을 볼 수 있습니다. 앞에서 설명했듯이 만료 검사는 파일이 넘어가는 시점에만 일어나므로, SET GLOBAL 직후에는 아무 일도 일어나지 않을 수 있습니다. 설정을 바꿨는데 파일이 줄지 않는다고 바로 rm으로 지우면 안 됩니다. 먼저 auto_purge 값과 다음 로그 전환 시점을 확인해야 합니다.

이번 실습에서는 각 파일이 만들어진 시각을 기록하지 않았습니다. 그래서 auto_purge를 켰다면 어느 파일까지 지워졌을지는 이 자료로 판단할 수 없습니다.

재시작 후에도 보관 정책을 유지하려면

SET GLOBAL은 실행 중인 서버에만 적용됩니다. 서버를 재시작하면 설정 파일의 값으로 돌아갑니다. 7일 보관과 자동 삭제를 운영 정책으로 확정했다면 설정 파일에 반영합니다.

ini

[mysqld]
binlog_expire_logs_seconds=604800
binlog_expire_logs_auto_purge=ON

SET PERSIST binlog_expire_logs_seconds = 604800;로 저장하는 방법도 있습니다. SET PERSIST 값은 데이터 디렉터리의 mysqld-auto.cnf에 기록되고, 설정 파일보다 나중에 적용됩니다. 그래서 두 곳에 다른 값이 있으면 SET PERSIST 쪽 값이 최종 적용됩니다.

설정은 한곳에서만 관리하고, 재시작 후 실제 실행값을 다시 조회해 확인하는 것이 안전합니다. 이번 실습에서는 재시작 후 적용 여부까지는 검증하지 않았습니다.

PURGE BINARY LOGS TO 실행 결과

이번 실습에서는 YUNSIK-bin.000009를 남길 첫 파일로 지정했습니다.

sql

PURGE BINARY LOGS TO 'YUNSIK-bin.000009';

SHOW WARNINGS;

SHOW BINARY LOGS;
수동 purge 이후 YUNSIK-bin.000009와 YUNSIK-bin.000010만 남았습니다. 경계 파일 자체는 보존되고 이전 8개 파일이 목록에서 제거된 결과입니다.

000009와 000010 두 파일만 남았습니다. PURGE BINARY LOGS TO는 지정한 파일을 지우는 명령이 아닙니다. 지정한 파일 앞의 파일들을 지우는 명령입니다. 이름이 헷갈리기 쉬우니 “TO 뒤에 쓴 파일부터 남긴다”고 기억하면 안전합니다.

항목삭제 전삭제 후
파일 수10개2개
File_size 합계3,218,969,858바이트2,058,711바이트
환산 용량약 3.00GiB약 1.96MiB

목록에서 사라진 8개 파일의 합계는 3,216,911,147바이트입니다. 이 중 99.9%가 크기가 큰 5개 파일(000002~000006)에서 나왔습니다. binlog 용량 문제는 대개 이런 소수의 대량 변경 구간에서 생깁니다.

이번 결과에서 확인할 수 있는 동작은 세 가지입니다.

  • auto_purge가 OFF여도 수동 PURGE는 실행됩니다.
  • 보관 기간 7일 설정은 수동 PURGE를 막지 않습니다. 1분 전에 만든 파일도 지정하면 지워집니다.
  • 삭제한 binlog는 설정을 되돌리거나 ROLLBACK을 해도 돌아오지 않습니다.

날짜 기준으로 지우고 싶다면 PURGE BINARY LOGS BEFORE '2026-09-15 00:00:00';처럼 BEFORE를 쓸 수 있습니다.

캡처에는 SHOW WARNINGS를 실행한 화면은 있지만 결과 탭은 없습니다. 그래서 경고가 있었는지는 이번 자료로 확인하지 못했습니다.

파일을 운영체제의 rm 명령으로 직접 지우면 안 됩니다. MySQL은 binlog 목록을 인덱스 파일(.index)로 따로 관리합니다. 파일만 사라지면 인덱스와 실제 파일이 어긋나, 다음 PURGE나 복제 과정에서 오류가 날 수 있습니다.

삭제 경계는 가장 느린 소비자가 정합니다

이번 실습은 “마지막 두 파일을 남긴다”는 기준으로 지웠지만, 운영에서는 이렇게 정하면 안 됩니다. binlog를 읽는 모든 소비자 중에서, 아직 필요로 하는 파일이 가장 오래된 소비자를 기준으로 삭제 경계를 정해야 합니다.

먼저 지금 binlog를 읽고 있는 연결을 확인합니다.

sql

SHOW REPLICAS;

SELECT id, user, host, command, time, state
FROM information_schema.processlist
WHERE command LIKE 'Binlog Dump%';

그리고 각 Replica에서 SHOW REPLICA STATUS\G로 다음 항목을 확인합니다.

확인 항목주요 필드
소스에서 받아 온 위치Source_Log_File, Read_Source_Log_Pos
실제로 적용한 위치Relay_Source_Log_File, Exec_Source_Log_Pos
스레드 상태Replica_IO_Running, Replica_SQL_Running
지연과 오류Seconds_Behind_Source, Last_IO_Error, Last_SQL_Error

소스 서버에서 지워도 되는지는 “받아 온 위치(Source_Log_File)”를 기준으로 판단합니다. 이미 relay log로 받아 두었다면, 적용이 늦더라도 소스의 binlog는 더 이상 필요하지 않습니다.

반대로 IO 스레드가 멈춰 있으면 Seconds_Behind_Source가 NULL이나 0으로 보여도 아직 받지 못한 binlog가 있을 수 있습니다. Seconds_Behind_Source 하나만 보고 삭제를 결정하면 안 되는 이유입니다.

MySQL은 연결된 Replica가 지금 읽고 있는 파일은 PURGE되지 않도록 막아 줍니다. 하지만 연결이 끊긴 Replica까지 보호하지는 않습니다. 이런 Replica가 필요로 하던 로그를 지우면, 재접속할 때 소스에 해당 로그가 없다는 오류(1236)로 복제가 멈추고 Replica를 다시 구축해야 합니다.

GTID 복제를 쓰고 있다면 PURGE 후 @@GLOBAL.gtid_purged의 범위가 늘어납니다. Replica가 아직 받지 못한 GTID가 이 범위에 들어가면 같은 문제가 생깁니다.

binlog 소비자는 Replica만이 아닙니다. 소비자마다 필요한 위치를 정리하면 경계가 분명해집니다. 아래는 가상의 예시입니다.

소비자 또는 복구 요건필요한 가장 오래된 파일
Replica AYUNSIK-bin.000009
중단 후 재개를 기다리는 마이그레이션 작업YUNSIK-bin.000007
로컬에만 있는 PITR용 로그(마지막 백업 이후)YUNSIK-bin.000005

이 경우 이번 실습처럼 000009를 기준으로 지우면, 마이그레이션은 처음부터 다시 해야 하고 시점 복구도 할 수 없게 됩니다. 다른 조건이 없다면 PURGE BINARY LOGS TO 'YUNSIK-bin.000005';가 안전한 경계입니다.

Debezium 같은 CDC 도구의 체크포인트도 같은 방식으로 확인합니다. binlog를 외부 저장소에 따로 보관하고 있다면, 업로드가 성공했는지만이 아니라 실제로 그 파일로 복구할 수 있는지까지 확인해 두어야 삭제 경계를 앞당길 수 있습니다.

binlog 크기를 줄이는 설정도 함께 검토합니다

보관 기간을 줄이는 것만이 방법은 아닙니다. 앞에서 본 “UPDATE는 변경 데이터의 약 2배가 기록된다”는 구조 자체를 줄이는 설정도 있습니다. 이번 실습에서 직접 측정하지는 않았으므로, 도입 전에 테스트해 볼 후보로 소개합니다.

  • binlog_row_image=MINIMAL: 변경 전 이미지에는 기본 키만, 변경 후 이미지에는 바뀐 컬럼만 기록합니다. 넓은 행에서 일부 컬럼만 자주 바꾸는 테이블에서 효과가 큽니다. 다만 CDC 도구가 전체 행 이미지를 요구하는 경우가 있으므로 소비자 호환성을 먼저 확인해야 합니다.
  • binlog_transaction_compression=ON: 트랜잭션 단위로 binlog를 압축합니다(8.0.20부터 지원). 이번 실습의 payload처럼 같은 문자가 반복되는 데이터는 크게 줄어듭니다. 대신 CPU를 추가로 사용합니다.

보관 기간은 복구 가능한 시간에서 거꾸로 계산합니다

이번 실습의 7일은 테스트용 값입니다. 운영에서는 일수를 먼저 정하지 말고, binlog가 있어야만 대응할 수 있는 상황이 얼마나 길어질 수 있는지부터 따져야 합니다.

  • Replica가 끊긴 것을 알아채고 복구하기까지 걸릴 수 있는 최대 시간
  • 마이그레이션이나 CDC 작업이 중단된 뒤 재개하기까지의 여유 시간
  • 마지막 전체 백업부터 복구 목표 시점까지의 간격

예를 들어 전체 백업을 주 1회 하는데 binlog를 3일만 보관한다고 가정해 보겠습니다. 백업 후 4일째 이후에 장애가 나면 시점 복구를 할 수 없습니다. 이 경우 보관 기간은 최소한 백업 주기보다 길어야 합니다.

기간이 정해지면 필요한 용량은 생성 속도로 계산합니다. 생성 속도는 하루 한 번 SHOW BINARY LOGS의 File_size 합계와 마지막 파일 번호를 기록해 두면 측정할 수 있습니다. 예를 들어 하루 40GiB가 생기는 서버라면 다음과 같습니다.

text

하루 생성량 40GiB × 보관 7일 = 280GiB
대량 배치일 증가분과 여유 30% 반영 = 약 364GiB

평균값만으로 계산하면 안 됩니다. 앞에서 본 것처럼 큰 트랜잭션 하나가 1GiB를 넘는 파일을 만들 수 있고, 월말 배치처럼 특정한 날에 생성량이 몇 배로 뛰는 경우가 많습니다. 측정 기간에 이런 날이 포함되었는지 확인해야 합니다.

실습 설정 원복

실습이 끝나면 준비 단계에서 기록한 원래 값으로 되돌립니다. 캡처 1의 auto_purge=false는 실습을 위해 이미 바꿔 둔 값이므로, 이 값을 원래 값으로 착각하지 않아야 합니다. 이번 서버의 원래 설정이 기본값(30일, 자동 삭제 ON)이었다면 다음과 같이 복구합니다.

sql

SET GLOBAL binlog_expire_logs_seconds = 2592000;
SET GLOBAL binlog_expire_logs_auto_purge = ON;

SELECT
    @@GLOBAL.binlog_expire_logs_seconds AS retention_seconds,
    @@GLOBAL.binlog_expire_logs_auto_purge AS auto_purge;

설정을 되돌려도 이미 삭제한 binlog 파일은 돌아오지 않습니다. 이번 실습처럼 PURGE까지 진행했다면, 그 전에 받아 둔 백업 시점 이후로는 이 서버에서 시점 복구를 할 수 없다는 점을 기록해 두어야 합니다.

PRODB
MySQL·Aurora 운영 DBA. 운영에서 겪은 문제를 MySQL 8.4에서 직접 재현해 기록합니다.

함께 보면 좋은 글

댓글 0

첫 댓글을 남겨보세요.

광고 차단 알림

광고 클릭 제한을 초과하여 광고가 차단되었습니다.

단시간에 반복적인 광고 클릭은 시스템에 의해 감지되며, IP가 수집되어 사이트 관리자가 확인 가능합니다.