MySQL Errcode: 28 - No space left on device 트러블 슈팅

노력을 즐겼던 사람·2023년 3월 28일
0
post-thumbnail
post-custom-banner

문제 발생

잘 동작하던 서버가 갑자기 동작하지 않게 되었다. 동작을 멈춘 후 4일이나 지나서야 알아챘다. 로그를 살펴봤는데 어째서인지 에러 메세지가 잘 남지 않았다. 그래서 에러 해결 국룰 재배포를 시도했고 이런 에러를 발견했다.

원인 탐색 과정

재배포 시도

재배포 과정에서 발생한 에러는 아래와 같다.

No space left on device: '/home/ubuntu/.prov2_tmp_file_20230328085837908950_31b31419', Exception :[Errno 28] No space left on device: ''/home/ubuntu/.prov2_tmp_file_20230328085837908950_31b31419'

에러 메세지를 보니 왠지 디스크 용량이 부족해서 발생한 에러인 것 같아서 에러에 표시된 위치에서 .prov2_tmp_file_20230328085837908950_31b31419 파일을 찾아보았다. 그런데 파일이 없었다. 그래서 호스트 OS의 파일 시스템의 여유 공간을 살펴봤다.

디스크 공간 과다 사용 근원지 확인

$df -h 커맨드로 여유 공간을 살펴보니 100%로 가득 차 있었다. 도대체 어디서 80GB를 점유하고 있는지 확인해봤다.

sudo du -h / | grep '[0-9\.]\+G' # 1GB 이상 디렉토리 출력

/ 하위의 있는 디렉토리들 중 1GB 이상의 디렉토리들을 쭉 탐색해보니 mysql/data 하위의 폴더가 약 76GB를 점유하고 있는 것을 확인했다.

테이블 사이즈 확인

mysql/data 하위의 데이터이므로 당연히 db에 row가 비정상적으로 쌓였을거라고 생각하고 DB에 다음과 같이 쿼리를 날렸다.

SELECT
  TABLE_NAME AS `Table`,
  ROUND((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024) AS `Size (MB)`
FROM
  information_schema.TABLES
WHERE
  TABLE_SCHEMA = "SCHEMA NAME"
ORDER BY
  (DATA_LENGTH + INDEX_LENGTH)
DESC;

그랬더니 가장 큰 테이블들의 사이즈를 합해보니 100MB 남짓이었다. 그렇다면 도대체 어떤 파일의 사이즈가 그렇게 큰걸까?

binlog 파일

mysql/data 폴더에 들어가보니 mysql-bin 을 prefix로 가지는 바이너리 파일이 굉장히 많았다. 파일들의 크기는 1.1GB 였고, 약 82개에 달하는 파일들이 있었다.

이 파일들은 왜 생기는걸까? mysql-bin 파일은 데이터 복구와 복제를 위한 백업 데이터이다. 따라서 데이터 변경 작업들을 로깅한다. mysql-bin 파일은 mysqlbinlog 툴로 손쉽게 decode해서 볼 수 있다. decode 해보니 실행되었던 DML들이 로깅되어 있는 것을 눈으로 확인해볼 수 있었다.

어쨌든 이 mysql-bin 파일에 의해서 디스크가 가득 찼고, 가득 찬 상태에서 새로운 쿼리를 실행하게 되면 바이너리 로그를 추가해야 하는데 추가할 디스크가 없어서 에러가 발생한 것 이었다.

문제 해결

너무 단순하다. 로그 파일을 지워버리면 된다. 그런데 로그 파일을 호스트 OS에서 지우는 것보다 MySQL을 통해서 제거하는 것이 좋다.

PURGE BINARY LOGS TO/BEFORE

MySQL은 바이너리 로그를 지우기 위해 두 가지 방법을 제공한다.

PURGE BINARY LOGS TO 'mysql-bin.000001';
PURGE BINARY LOGS BEFORE '2023-03-01 00:00:00';

TO 문법을 사용하면 mysql-bin 파일이름의 패턴을 사용하는 방식이다. 만약 PURGE BINARY LOGS TO 'mysql-bin.000010'; 을 입력한다면 000001 ~ 000009 까지 삭제된다. SQL을 그대로 해석해보아도 고개가 끄덕여지는 동작이다.

반면에 BEFORE 문법을 사용하면 로그 파일이 쓰여진 시간을 기준으로 이전의 파일들이 모두 삭제된다.

이렇게 삭제하면 되는데 한 가지 이슈가 있다. 위의 쿼리들도 데이터 변경이기 때문에 쿼리 수행 시 바이너리 로그가 추가된다. 그러니까 디스크가 가득차서 쿼리 수행이 안된다는 뜻이다. 디스크를 확보한 후 쿼리르 수행하자

재발 방지

로그 파일을 지우면 해결은 되지만 시간이 흘러 로그가 또 쌓이면 장애가 재발한다. 이를 방지하기 위해서 바이너리 파일에 대한 변수를 조절하여 로그 파일의 총 크기가 디스크 용량을 넘지 않도록 해야 한다.

mysql 설정 파일에 아래와 같이 추가하자.

[mysqld]
...
...
max_binlog_files=50 # mysql-bin 파일의 총 개수
max_binlog_size=100M # mysql-bin 파일의 사이즈
expire_logs_days=7   # mysql-bin 파일의 보관 날짜

위 세 개의 변수만 잘 설정하면 재발 방지로서 충분하다. 50 * 100M 면 5GB 밖에 안되니 디스크 총 용량인 80GB에 한참 모자른다. 주의할점이 있다. 만약 백업이 매우 중요한 서버라면 더 많은 파일과 큰 사이즈를 설정하는 것을 추천한다.

아참, 설정 파일을 수정했다면 MySQL 엔진을 재시작하는 것을 잊지 말자.

profile
노력하는 자는 즐기는 자를 이길 수 없다 를 알면서도 게으름에 지는 중
post-custom-banner

0개의 댓글