여는 글

안녕하세요. 오늘은 실무에서 겪은 아찔한 장애 경험을 공유하려 합니다. DB가 다운되지도 않았고, 네트워크 문제도 없었으며, 애플리케이션의 쿼리문도 정상이었지만 데이터베이스가 정상 동작하지 않았던 날의 이야기입니다. 원인은 바로 MariaDB의 SQL 로그 파일이 300GB 서버를 가득 채워버린 것이었습니다.

사건의 발단 - 갑작스러운 장애 발생

평소와 다름없던 어느 월요일 오전, 갑자기 전산 시스템에서 이상 신호가 감지되었습니다.

장애 증상:

  • INSERT, UPDATE 쿼리가 전혀 실행되지 않음
  • SELECT 쿼리는 정상 동작하지만 가끔 Lock에 걸리는 현상 발생
  • DB 서버는 정상 구동 중
  • 애플리케이션 로그에는 특별한 에러 없음
  • 네트워크 연결 상태 양호

가장 당황스러운 점은 명확한 에러 메시지가 없었다는 것입니다. 그리고 평소에 애플리케이션의 log.txt 파일의 용량이 작아 로그 용량관리에 대해 유의미하지 않다고 방심했기 때문에, DB 로그가 문제일 거라는 생각은 전혀 하지 못했습니다. 단순히 INSERT/UPDATE가 “조용히” 실패하고 Lock이 간헐적으로 걸리는 상황이었습니다.

초기 트러블슈팅 과정

※ 참고사항: 아래 트러블슈팅 과정은 실제 제가 겪었던 경로를 Claude.ai의 도움을 받아 재구성한 것입니다. 실제로는 더 많은 우회와 시행착오가 있었고, 같은 문제를 겪으시는 분들의 해결 경로가 100% 일치하지 않을 수 있습니다. 특히 저는 평소 애플리케이션의 log.txt 파일의 용량이 작아 로그 용량관리에 대해 유의미하지 않다고 방심했기 때문에, DB 로그가 원인일 거라는 생각을 하는 데 상당한 시간이 걸렸습니다.

1단계: 기본적인 확인

# DB 서버 상태 확인
sudo systemctl status mariadb
● mariadb.service - MariaDB 10.6.7 database server
   Loaded: loaded
   Active: active (running) since Mon 2024-03-25 08:00:00 KST
   
# 프로세스 확인
ps aux | grep mysql
mysql     1234  0.5  2.1 1234567 123456 ?   Sl   08:00   0:05 /usr/sbin/mysqld

# 네트워크 연결 확인
telnet db-server 3306
Trying 192.168.1.100...
Connected to db-server.

모든 기본 상태는 정상이었습니다.

2단계: MariaDB 접속 및 상태 확인

-- DB 접속은 정상
mysql -u root -p

-- 기본 상태 확인
SHOW STATUS LIKE 'Connections';
SHOW PROCESSLIST;
SHOW ENGINE INNODB STATUS;

여기서도 특별한 이상은 발견되지 않았습니다. 다만 간헐적으로 쿼리들이 Lock 상태에 걸려있는 것이 보였는데, 당시에는 이것이 근본 원인이라기보다는 증상 중 하나라고 생각했습니다. 그리고 평소 애플리케이션 로그들이 용량을 차지하는 것에 익숙해져 있던 터라, “설마 DB 로그가 문제일까?” 하는 생각은 쉽게 들지 않았습니다.

3단계: 시스템 리소스 확인

# 메모리 사용량 확인
free -h
              total        used        free      shared  buff/cache   available
Mem:           16Gi        4.2Gi       2.1Gi       234Mi        9.8Gi        11Gi

# CPU 사용량 확인  
top
# CPU 사용률 정상

# 디스크 사용량 확인 - 여기서 문제 발견!
df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       300G  300G     0 100%  /

범인을 찾았습니다! 디스크 사용량이 100%였습니다. 그제서야 “아, 이래서 Lock이 걸리고 쓰기 작업이 안 됐구나” 하고 깨달았습니다.

범인 찾기: 무엇이 300GB를 채웠는가?

큰 파일들 찾기

# 큰 파일들 찾기
find / -type f -size +1G 2>/dev/null | head -20

# /var/lib/mysql 디렉토리 확인
du -sh /var/lib/mysql/*
128M    /var/lib/mysql/payment_db
256M    /var/lib/mysql/mysql
285G    /var/lib/mysql/mysql-general.log  # 범인 발견!

285GB짜리 mysql-general.log 파일이 범인이었습니다! 이때까지도 “설마 DB 로그가 이렇게 클까?” 하는 의심을 했었는데, 실제로 확인해보니 정말 이 파일이 거의 모든 용량을 차지하고 있었습니다.

MariaDB General Log 확인

-- General Log 설정 확인
SHOW VARIABLES LIKE 'general_log';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| general_log   | ON    |
+---------------+-------+

SHOW VARIABLES LIKE 'general_log_file';
+------------------+----------------------------------+
| Variable_name    | Value                            |
+------------------+----------------------------------+
| general_log_file | /var/lib/mysql/mysql-general.log |
+------------------+----------------------------------+

-- 로그 로테이션 설정 확인
SHOW VARIABLES LIKE 'log_output';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_output    | FILE  |
+---------------+-------+

문제의 핵심을 파악했습니다:

  • General Log가 활성화되어 있음
  • 모든 SQL 쿼리가 하나의 파일에 계속 기록됨
  • 로그 로테이션이 설정되어 있지 않음
  • 일별 백업/삭제 프로세스 없음

응급 처치: 서비스 복구

1단계: 즉시 디스크 공간 확보

# 우선 General Log 백업 (중요한 정보가 있을 수 있음)
sudo cp /var/lib/mysql/mysql-general.log /backup/mysql-general-$(date +%Y%m%d).log

# General Log 파일 비우기 (삭제하면 MariaDB에서 문제가 될 수 있음)
sudo truncate -s 0 /var/lib/mysql/mysql-general.log

# 디스크 공간 확인
df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       300G   15G  285G   5%  /

2단계: General Log 임시 비활성화

-- 즉시 General Log 비활성화
SET GLOBAL general_log = 'OFF';

-- 설정 확인
SHOW VARIABLES LIKE 'general_log';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| general_log   | OFF   |
+---------------+-------+

3단계: 서비스 정상성 확인

-- INSERT 테스트
INSERT INTO test_table (message, created_at) VALUES ('test', NOW());
Query OK, 1 row affected (0.01 sec)

-- UPDATE 테스트  
UPDATE test_table SET message = 'updated' WHERE id = 1;
Query OK, 1 row affected (0.01 sec)

서비스가 정상 복구되었습니다!

근본 원인 분석

MariaDB General Log란?

General Log는 MariaDB/MySQL에서 제공하는 로깅 기능으로, 서버에서 실행되는 모든 SQL 문과 연결 정보를 기록합니다.

-- General Log에 기록되는 내용들
/*
2024-03-25T09:15:23.123456Z	  123 Connect	user@localhost on payment_db
2024-03-25T09:15:23.234567Z	  123 Query	SELECT * FROM transactions WHERE id = 12345
2024-03-25T09:15:23.345678Z	  123 Query	UPDATE transactions SET status = 'COMPLETED' WHERE id = 12345
2024-03-25T09:15:23.456789Z	  123 Quit	
*/

문제점:

  • 모든 쿼리가 기록되어 용량이 빠르게 증가
  • 로그 로테이션이 기본적으로 설정되어 있지 않음
  • 성능에도 약간의 영향을 미침

왜 디스크가 가득 차면 INSERT/UPDATE가 안 될까?

# 디스크 공간이 없을 때 발생하는 일들
1. MariaDB가 새로운 데이터를 디스크에 쓸 수 없음
2. 트랜잭션 로그를 기록할 수 없음  
3. 임시 파일을 생성할 수 없음
4. 테이블 Lock이 걸리면서 대기 상태 발생
5. 결과적으로 쓰기 작업이 조용히 실패

실제로 이 과정에서 SELECT는 되지만 간헐적으로 Lock에 걸리던 현상의 원인도 명확해졌습니다. 디스크 공간 부족으로 인해 트랜잭션 처리가 제대로 되지 않으면서 테이블 Lock이 발생했던 것이었습니다.

영구적 해결책 구현

1. General Log 로테이션 설정

# logrotate 설정 파일 생성
sudo vi /etc/logrotate.d/mysql-general

# 설정 내용
/var/lib/mysql/mysql-general.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    create 644 mysql mysql
    postrotate
        if test -x /usr/bin/mysqladmin && \
           /usr/bin/mysqladmin ping &>/dev/null
        then
           /usr/bin/mysqladmin flush-logs
        fi
    endscript
}

2. 모니터링 스크립트 구현

#!/bin/bash
# disk_monitor.sh

DISK_USAGE=$(df / | grep -vE '^Filesystem|tmpfs|cdrom' | awk '{print $5}' | sed 's/%//g')
THRESHOLD=80

if [ $DISK_USAGE -gt $THRESHOLD ]; then
    echo "Warning: Disk usage is ${DISK_USAGE}%"
    # Slack 알림 등 추가 가능
    
    # General Log 크기 확인
    LOG_SIZE=$(du -h /var/lib/mysql/mysql-general.log | cut -f1)
    echo "General Log size: $LOG_SIZE"
fi

3. MariaDB 설정 최적화

# /etc/mysql/mariadb.conf.d/50-server.cnf

[mysqld]
# General Log 설정
general_log = 0  # 기본값을 OFF로 설정
general_log_file = /var/lib/mysql/mysql-general.log

# Slow Query Log (필요한 경우만)
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/mysql-slow.log
long_query_time = 2

# Binary Log (복제나 백업용)
log-bin = mysql-bin
expire_logs_days = 7
max_binlog_size = 100M

다른 로그 타입들과의 비교

로그 타입별 특징

로그 타입 용도 크기 증가 속도 성능 영향 운영 필요성
General Log 모든 쿼리 기록 매우 빠름 높음 디버깅 시에만
Slow Query Log 느린 쿼리 기록 보통 낮음 권장
Error Log 에러/경고 기록 느림 없음 필수
Binary Log 복제/백업용 빠름 보통 복제 시 필수

권장 설정

-- 개발 환경
SET GLOBAL general_log = 'ON';  -- 디버깅 시에만 일시적으로

-- 운영 환경  
SET GLOBAL general_log = 'OFF';          -- 기본 OFF
SET GLOBAL slow_query_log = 'ON';        -- 성능 모니터링용
SET GLOBAL long_query_time = 2;          -- 2초 이상 쿼리만 기록

예방 대책

1. 정기적인 디스크 모니터링

# crontab 등록
# 매시간 디스크 사용률 체크
0 * * * * /home/user/scripts/disk_monitor.sh

# 매일 General Log 크기 체크
0 6 * * * du -h /var/lib/mysql/mysql-general.log | mail -s "Daily General Log Size" admin@company.com

2. 알림 시스템 구축

#!/bin/bash
# alert_system.sh

DISK_USAGE=$(df / | grep -vE '^Filesystem|tmpfs|cdrom' | awk '{print $5}' | sed 's/%//g')

if [ $DISK_USAGE -gt 85 ]; then
    # Slack webhook으로 알림
    curl -X POST -H 'Content-type: application/json' \
    --data "{\"text\":\"⚠️ DB Server disk usage: ${DISK_USAGE}%\"}" \
    YOUR_SLACK_WEBHOOK_URL
fi

3. 자동화된 로그 관리

-- 매일 자동으로 General Log를 관리하는 이벤트 스케줄러
DELIMITER $$
CREATE EVENT daily_log_cleanup
ON SCHEDULE EVERY 1 DAY
STARTS '2024-03-26 02:00:00'
DO
BEGIN
    -- General Log가 1GB 이상이면 비활성화
    SET @log_size = (SELECT ROUND(((data_length + index_length) / 1024 / 1024 / 1024), 2) 
                     FROM information_schema.tables 
                     WHERE table_schema = 'mysql' AND table_name = 'general_log');
    
    IF @log_size > 1 THEN
        SET GLOBAL general_log = 'OFF';
    END IF;
END$$
DELIMITER ;

맺는글

이번 장애를 통해 배운 교훈들:

  1. 로그 관리의 중요성: 애플리케이션 로그뿐만 아니라 DB 로그도 적절한 로테이션과 관리가 필요합니다
  2. 디스크 모니터링 필수: 디스크 사용률 모니터링은 선택이 아닌 필수입니다
  3. General Log는 신중하게: 개발 환경에서만 일시적으로 사용하는 것이 바람직합니다
  4. 증상보다 원인: DB가 정상이어도 시스템 레벨의 문제가 있을 수 있습니다
  5. 예방이 최선: 사전 모니터링과 알림 시스템이 장애를 예방하는 최선의 방법입니다
  6. 선입견의 위험: 평소 경험에 의존해서 문제의 원인을 미리 단정짓지 말자

300GB를 가득 채운 하나의 로그 파일로 인해 전체 서비스가 마비되는 경험을 했습니다. 특히 평소 애플리케이션 로그의 용량이 작아서 로그 관리에 대해 방심하고 있던 터라 DB 로그는 전혀 의심하지 못했던 것이 아쉬웠습니다. 다행히 빠른 대응으로 서비스를 복구할 수 있었지만, 이런 기본적인 실수를 반복하지 않기 위해 철저한 예방 시스템을 구축했습니다.

실제 트러블슈팅 과정은 이 포스팅에서 보여드린 것보다 훨씬 더 우회가 많았고 시간도 오래 걸렸지만, 비슷한 상황에 처한 개발자들에게 이 경험이 조금이라도 도움이 되길 바랍니다.