Ubuntu에서 MySQL/MariaDB가 갑자기 느려지면서 “connection이 몰린다”는 느낌이 들 때가 있습니다. 실제로는 커넥션 수 자체보다, 특정 쿼리의 락 대기나 스레드/IO 병목 때문에 커넥션이 쌓이는 경우가 많습니다. 이 글은 측정(쿼리/락) → 병목 → 해결 → 검증 순서로, 운영 중 안전하게 진단하고 개선하는 절차를 정리했습니다.
변경은 빠르게, 되돌리기는 더 쉽게가 목표입니다.
0) 작업 전 안전장치: 백업·복구 플랜을 먼저 고정하기
성능 튜닝은 설정/스키마/쿼리/애플리케이션까지 건드릴 수 있어, 작은 변경도 예상치 못한 부작용이 날 수 있습니다. 측정 전에 복구 가능 상태를 만들어두면 판단이 훨씬 편해집니다.
- 변경 범위 쪼개기: 한 번에 하나(예: max_connections만 / 앱 풀만 / 인덱스만) 변경하고, 변경 기록을 남깁니다.
- 논리 백업: 작은 DB라면 mysqldump, 큰 DB라면 트랜잭션/부하를 고려해 옵션을 조정합니다(예: InnoDB 위주면 --single-transaction).
- 물리 백업: Percona XtraBackup(지원 버전 확인) 또는 스토리지 스냅샷(LVM/클라우드)로 빠른 롤백 수단을 준비합니다.
- 복구 리허설: 최소 1회는 별도 서버/컨테이너에 복원해 “백업 파일이 진짜로 복구되는지” 확인합니다.
- 설정 백업: /etc/mysql/ 및 커스텀 conf.d 파일을 함께 보관합니다.
- 변경 전 상태 캡처: SHOW VARIABLES, SHOW ENGINE INNODB STATUS, 주요 카운터(Threads, Connections, Aborted_connects 등)를 저장해 비교 기준을 만듭니다.
특히 커넥션 이슈는 설정을 만지기 쉽지만, 원인이 쿼리/락이라면 설정만 올려서 증상을 숨길 수 있습니다. 백업·복구 안전장치를 마련한 뒤에 측정을 시작하는 편이 안전합니다.
1) 측정(쿼리/락): “커넥션이 왜 쌓이는지”부터 숫자로 보기
먼저 서버가 바쁜 이유가 락 대기인지, CPU/IO 병목인지, 연결 생성 자체가 느린지를 가릅니다. 아래는 현장에서 자주 쓰는 최소 측정 세트입니다.
- 현재 커넥션/스레드 상태: SHOW PROCESSLIST; 에서 Sleep이 많은지, “Waiting for … lock” 계열이 많은지 봅니다.
- Threads_running: 동시에 실행 중인 스레드가 계속 높으면(예: 코어 수 대비 과도) CPU/락/IO 병목 가능성이 큽니다.
- InnoDB 락/대기: SHOW ENGINE INNODB STATUS\G 로 가장 긴 대기 트랜잭션, 보유 락, 대기 원인을 확인합니다.
- Performance Schema(가능 시): events_statements_summary_by_digest로 상위 쿼리, events_waits_summary_global_by_event_name로 대기 유형을 봅니다.
- connection 실패/재시도: Aborted_connects, connection_errors_* 카운터로 인증 실패/네트워크 문제도 배제합니다.
가능하면 아래처럼 “한 번 찍고 끝”이 아니라, 1~5분 간격으로 3~5회 수집해 추세를 봅니다. 순간치만 보면 원인을 놓치기 쉽습니다.
- Threads_connected / Threads_running / Max_used_connections
- Questions 또는 Queries(초당 처리량), Slow_queries
- Innodb_row_lock_time / Innodb_row_lock_waits
2) 병목 분류: 커넥션 문제처럼 보이는 4가지 패턴
측정 결과를 바탕으로, 아래 중 어디에 가까운지 빠르게 분류합니다. 분류가 되면 해결책도 단순해집니다.
- A. 락 대기형: processlist에 “Waiting for … lock”, InnoDB STATUS에 대기 트랜잭션이 길게 보임. 커넥션은 늘지만 실제 진행이 막힘.
- B. 쿼리 병목형: 실행 중 쿼리가 특정 digest에 몰리고, CPU 또는 IO 사용률이 높음. 커넥션은 요청 처리 대기열처럼 쌓임.
- C. 커넥션 생성/인증 비용형: 짧은 요청이 많은데 매번 새 연결을 만들고 끊음. TLS/역DNS/인증 플러그인 비용이 누적.
- D. 서버 한도/설정형: max_connections, thread_cache_size, table_open_cache 등으로 병목이 생기거나, 메모리 부족으로 스왑/oom이 발생.
실무에서는 A+B가 같이 나타나는 경우가 많습니다. 예를 들어 인덱스가 없어 테이블 스캔이 길어지고, 그 사이에 UPDATE가 락을 잡아 뒤의 SELECT까지 대기하는 식입니다.
3) 해결 1: 락/트랜잭션 대기부터 줄이는 실전 조치
락 대기는 커넥션을 빠르게 증식시키는 대표 원인입니다. 목표는 “대기 시간을 줄이고”, “긴 트랜잭션을 없애고”, “충돌을 피하는 순서로 접근”입니다.
- 긴 트랜잭션 찾기: InnoDB STATUS 또는 performance_schema에서 오래 열린 트랜잭션(특히 idle in transaction)을 우선 제거합니다.
- 자주 충돌하는 테이블/행 식별: 어떤 테이블에 락이 몰리는지(핫스팟)를 먼저 특정합니다.
- 인덱스 점검: UPDATE/DELETE의 WHERE 조건이 인덱스를 타지 않으면 더 많은 레코드를 잠그고, 락 충돌이 폭발합니다.
- 작게 나눠서 처리: 대량 UPDATE/DELETE는 배치 크기를 줄이고(예: PK 범위로 쪼개기) 커밋을 자주 합니다.
- 격리 수준/잠금 범위 점검: 불필요하게 넓은 잠금이 걸리지 않도록 쿼리 패턴을 조정합니다(예: 범위 조건, 정렬/조인 방식).
운영 중에는 “무조건 kill”이 아니라, 영향도를 보고 판단하는 게 중요합니다. 예를 들어 하나의 트랜잭션을 종료하면 대기가 풀리지만, 그 트랜잭션이 중요한 배치/정산일 수도 있습니다. 그래서 원인 트랜잭션(보유자)과 피해 트랜잭션(대기자)를 구분해 보는 습관이 도움이 됩니다.
4) 해결 2: 커넥션 폭증 대응(풀링/설정/자원) — “늘리기”보다 “안 쌓이게”
커넥션은 결과적으로 쌓이는 것이지, 대부분의 경우 진짜 문제는 “처리 시간이 길어져서”입니다. 따라서 아래 우선순위로 접근하는 편이 안전합니다.
- 애플리케이션 커넥션 풀: pool size, idle timeout, max lifetime을 점검합니다. 짧은 요청이 많은 서비스에서 매 요청마다 새 연결을 만드는 패턴은 특히 치명적입니다.
- 서버 측 thread_cache_size: 새 연결 생성/스레드 생성 비용이 큰 경우, 스레드 캐시가 도움이 됩니다(효과는 측정으로 확인).
- max_connections 조정: 임시 완화책으로는 가능하지만, 메모리(특히 per-connection 버퍼) 여유를 계산하지 않고 올리면 더 큰 장애로 번질 수 있습니다.
- wait_timeout 현실화: 유휴 커넥션이 과도하면 정리하되, 앱/풀 설정과 충돌하지 않게 맞춥니다.
- DNS/역DNS 지연 배제: 접속 시 reverse lookup 때문에 느려지는 경우가 있어, 설정(예: skip-name-resolve) 적용 전 영향 범위를 확인합니다.
- 리소스 점검: 커넥션 폭증 시점에 CPU iowait, 디스크 latency, 스왑 사용이 튀면 DB 설정이 아니라 시스템 병목일 수 있습니다.
중요한 포인트는 “처리량이 늘지 않는데 커넥션만 늘리면” 큐가 더 커질 수 있다는 점입니다. 그래서 풀링(요청 수를 통제) + 병목 쿼리 개선(처리 시간 단축) 조합이 안정적입니다.
5) 검증: 개선이 실제로 먹혔는지, 재발을 막을 지표 만들기
조치 후에는 같은 방법으로 다시 측정해 “좋아진 느낌”이 아니라 숫자로 개선을 확인합니다. 아래 체크리스트로 검증하면 재발 방지에도 도움이 됩니다.
- 피크 시간 Threads_running이 낮아졌는가?
- 95p/99p 응답시간이 줄었는가(앱 APM 또는 Nginx/서버 로그 기준)?
- Innodb_row_lock_waits / time가 눈에 띄게 감소했는가?
- Slow_queries 증가 속도가 줄었는가?
- Max_used_connections가 안정 구간으로 들어왔는가?
- 오류 지표(Aborted_connects, Too many connections)가 사라졌는가?
가능하면 “사건 1회 해결”로 끝내지 말고, 간단한 대시보드/알림 기준을 잡아두는 편이 좋습니다. 예를 들어 Threads_running이 특정 값 이상으로 5분 지속, row lock waits 급증, connection 오류 증가 같은 조건을 잡아두면 조기 대응이 됩니다.
마무리
Ubuntu에서 MySQL/MariaDB 연결 문제가 터졌을 때는 커넥션 수만 보지 말고, 락 대기와 상위 쿼리를 먼저 측정해 병목을 분류하는 게 가장 빠른 길입니다. 그리고 해결은 “늘리는 것”보다 “안 쌓이게 만드는 것(쿼리/락/풀링)”에 초점을 맞추는 편이 안전합니다.
마지막으로, 성능 조치는 언제든 되돌릴 수 있어야 합니다. 백업과 복구 리허설까지 포함해 절차를 갖춰두면, 더 과감하게(그리고 더 안전하게) 개선할 수 있습니다.