Post

운영 DB 메모리가 계속 늘어난다는 리포트를 받았을 때 - 버퍼풀 vs 진짜 누수 구분하기

운영 DB 메모리가 계속 늘어난다는 리포트를 받았을 때 - 버퍼풀 vs 진짜 누수 구분하기

Kafka 메시지를 소비해서 DB에 적재하는 컨슈머 애플리케이션을 운영 중인데, “DB 메모리가 계속 증가한다”는 리포트를 받았다. 결론부터 말하면 아직 원인을 100% 확정하지 못했고, 지금은 재기동 후 추이를 지켜보는 중이다. 완결된 해결기가 아니라 진행 중인 진단 과정을 그대로 남긴다.

리포트 자체가 불안정했다

가장 먼저 한 일은 “얼마나, 어떤 주기로 늘어나는지”를 명확히 하는 것이었다. 처음 전달받은 내용은

“하루에 3%씩 증가한다”

였는데, 확인 과정에서

“하루가 아니라 주 단위로 2~3%씩 증가한다”

로 정정됐다. 숫자 하나 확정하는 데도 몇 번을 되물어야 했다. 모니터링 주체가 다르면(운영팀 vs 개발자) 같은 현상도 다르게 요약되어 전달될 수 있다는 걸 새삼 느꼈다. 원인 분석에 들어가기 전에 “그래서 정확히 뭐가, 얼마나, 언제”를 먼저 고정해야 그다음 숫자들이 의미를 가진다.

의심 1순위: 최근에 추가한 중복 방지 로직

시점상 가장 최근에 건드린 코드가 있었다 — 같은 요청이 중복으로 들어오는 걸 막는 로직을 애플리케이션에 새로 추가했었다. “내가 최근에 뭘 바꿨나”가 항상 1순위 용의자가 되는 건 자연스럽다. 다만 이번 리포트는 애플리케이션(JVM) 메모리가 아니라 DB 자체의 메모리였다. 둘은 서로 다른 프로세스, 다른 메모리 공간이라 애플리케이션 코드 수정만으로 DB 메모리 증가를 설명하려면 “그 코드가 DB에 어떤 부하/락/연결을 만드는가”를 봐야지, 코드 자체의 메모리 누수를 보는 건 방향이 다르다는 걸 짚고 넘어갔다.

DB 메모리를 늘릴 수 있는 후보들을 체크리스트로 훑기

DB 프로세스 메모리가 늘어날 수 있는 경로를 하나씩 소거법으로 확인했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 1. InnoDB 버퍼풀 설정값과 실사용량 비교 (가장 먼저 봐야 할 지표)
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
SHOW STATUS LIKE 'Innodb_buffer_pool_bytes_data';

-- 2. 열려 있는(오래 안 끝난) 트랜잭션
SELECT * FROM information_schema.INNODB_TRX;

-- 3. History List Length - 언두 로그가 쌓이고 있는지
SHOW ENGINE INNODB STATUS\G

-- 4. 커넥션/스레드 재사용률
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Threads_created';
SHOW STATUS LIKE 'Connections';
SHOW STATUS LIKE 'Aborted_clients';

-- 5. MyISAM 키 캐시 / 쿼리 캐시 / performance_schema
SHOW VARIABLES LIKE 'key_buffer_size';
SHOW VARIABLES LIKE 'query_cache_size';
SHOW VARIABLES LIKE 'performance_schema';

하나씩 지워나간 결과:

  • 열린 트랜잭션 없음, 락 대기 없음, 최근 데드락 없음 — 잠긴 채로 안 끝난 트랜잭션이 메모리를 붙들고 있는 상황은 아니었다.
  • History List Length가 매우 작음 — 언두 로그가 쌓여서 부풀어 오르는 전형적인 패턴도 아니었다.
  • 커넥션 수 / 스레드 생성-재사용 비율 / Aborted_clients 비율 모두 정상 범위 — 커넥션 누수로 스레드가 계속 새로 생기며 메모리를 먹는 패턴도 아니었다.
  • key_buffer_size(MyISAM), query_cache_size, performance_schema 모두 사실상 꺼져 있거나 무의미한 값 — 이쪽도 용의선상에서 제외.

남은 건 InnoDB 버퍼풀이었다. 확인해보니 innodb_buffer_pool_size가 물리 메모리의 상당 비중을 차지하도록 크게 설정돼 있었고, 실제 사용량이 그 설정값의 거의 100%에 가깝게 채워져 있었다. 버퍼풀은 설정된 한도까지 차오르는 게 정상 동작이라, 지금까지 관측된 “DB 메모리 사용량”의 상당 부분은 이 설정값 자체로 설명이 됐다.

그런데 이게 전부는 아닐 수도 있다

문제는 운영팀의 관찰이 “지금 이 순간에도 계속 늘고 있다”는 것이었다. 버퍼풀이 이미 설정 상한 근처에서 꽉 찬 상태라면, 거기서 더 늘어나는 부분은 단순 warm-up으로는 설명이 안 된다. 즉 “버퍼풀 설정값이 원래 큰 것”과 “실제로 뭔가가 계속 새고 있는 것”이 동시에 존재할 가능성을 배제할 수 없는 상태로 남았다.

여기서 얻은 교훈: 하나의 그럴듯한 설명을 찾았다고 바로 “해결”로 선언하면 안 된다. 그 설명이 관측된 현상 전체(특히 “여전히 진행 중”이라는 시간축 정보)를 다 덮는지 별도로 검증해야 한다.

지금 상태와 다음 단계

  • DB 자체 지표(트랜잭션/락/History List/커넥션)에서는 이상 신호를 찾지 못했다.
  • 버퍼풀 설정값(전체 메모리 대비 큰 비중)이 지금까지의 사용량 대부분을 설명한다고 잠정 판단했다.
  • 다만 “여전히 증가 중”이라는 보고를 완전히 설명하지는 못해서, 원인 분석은 아직 미완료로 남겨뒀다.
  • DB를 재기동하고, 재기동 전후의 증가율을 비교해서 “설정값 때문이었는지 / 실제로 새는 게 있었는지”를 가리기로 했다. 재기동 전에는 컨슈머 애플리케이션을 정상 종료 절차로 먼저 내려서 처리 중이던 메시지가 유실되지 않게 했다.

결론이 나면 후속 글로 이어서 정리할 예정이다.

This post is licensed under CC BY 4.0 by the author.