Observability/Prometheus

EP14 [Part 5: 애플리케이션 레벨 모니터링] 데이터베이스 모니터링 (MySQL, PostgreSQL)

ygtoken 2025. 3. 24. 23:42
728x90

이번 글에서는 쿠버네티스 환경에서 데이터베이스 모니터링의 중요성과 구체적인 방법을 알아보겠습니다. 특히 가장 널리 사용되는 관계형 데이터베이스인 MySQL과 PostgreSQL을 중심으로, 성능 지표 수집부터 분석, 알림 설정까지 체계적인 모니터링 시스템 구축 방법을 다루겠습니다. Prometheus와 Grafana를 활용한 실시간 모니터링으로 데이터베이스 성능 병목 현상을 조기에 발견하고, 서비스 안정성을 높이는 데 필요한 실질적인 지식을 제공하겠습니다.


📌 데이터베이스 모니터링의 중요성

데이터베이스는 대부분의 애플리케이션에서 가장 중요한 구성 요소 중 하나이며, 시스템 성능과 안정성에 직접적인 영향을 미칩니다.

✅ 데이터베이스 장애가 미치는 영향

데이터베이스 성능 저하나 장애는 전체 애플리케이션에 심각한 영향을 줄 수 있습니다.

  1. 비즈니스 영향
    • 트랜잭션 처리 지연 및 실패로 인한 수익 손실
    • 사용자 경험 저하 및 고객 이탈
    • 데이터 무결성 문제로 인한 신뢰도 하락
  2. 기술적 영향
    • 연쇄적인 시스템 장애 유발
    • 백로그 증가와 자원 소모
    • 데이터 복구 비용 및 시간 발생

▶️ 실제 사례: 한 전자상거래 회사는 데이터베이스 연결 풀 고갈로 인해 블랙 프라이데이 세일 기간 중 2시간 동안 서비스 장애를 경험했고, 이로 인해 약 150만 달러의 손실이 발생했습니다. 적절한 모니터링이 있었다면 이 문제를 사전에 감지하고 예방할 수 있었습니다.

✅ 쿠버네티스 환경에서의 특수성

쿠버네티스 환경에서 데이터베이스를 운영할 때 고려해야 할 특별한 요소들이 있습니다.

  1. 스테이트풀 워크로드 관리
    • 상태 보존을 위한 퍼시스턴트 볼륨(PV) 관리
    • 노드 간 마이그레이션 시 데이터 일관성 유지
    • 스테이트풀셋(StatefulSet)의 올바른 설정과 관리
  2. 리소스 경합과 제한
    • 컨테이너화된 환경에서의 리소스 제한 관리
    • 노드 간 리소스 불균형 모니터링
    • 쿠버네티스 스케줄러와 데이터베이스 성능 요구 사항 조율
  3. 네트워킹 복잡성
    • 클러스터 내 서비스 디스커버리와 통신
    • 노드 간 네트워크 지연 시간 관리
    • 쿠버네티스 서비스와 엔드포인트 모니터링
# StatefulSet으로 배포된 데이터베이스 예시
# 이 예시는 MySQL을 StatefulSet으로 배포하는 기본 구성을 보여줍니다
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: database
spec:
  selector:
    matchLabels:
      app: mysql        # StatefulSet이 관리할 Pod 선택 레이블
  serviceName: mysql    # 헤드리스 서비스 이름 (각 Pod에 고유 DNS 제공)
  replicas: 3           # 복제본 수 (Primary 1개, Replica 2개)
  template:
    metadata:
      labels:
        app: mysql      # Pod 레이블
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
          name: mysql           # 포트 이름 지정
        resources:              # 리소스 요청 및 제한 설정
          requests:             # 최소 필요 리소스
            memory: "1Gi"
            cpu: "500m"
          limits:               # 최대 사용 가능 리소스
            memory: "2Gi"       # 메모리 제한 - OOM 발생 가능성 고려 필요
            cpu: "1000m"        # CPU 제한 - 1 코어
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql  # 데이터 저장 경로
        env:                    # 환경 변수 설정
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:       # 보안을 위해 Secret에서 패스워드 참조
              name: mysql-secrets
              key: root-password
      affinity:                 # 노드 친화성 설정 (분산 배치)
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - mysql
              topologyKey: kubernetes.io/hostname
  volumeClaimTemplates:         # 각 Pod의 PVC 템플릿
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "ssd-storage"  # 고성능 스토리지 클래스
      resources:
        requests:
          storage: 50Gi         # 각 Pod당 스토리지 요청 크기

📌 MySQL 모니터링 설정

MySQL은 가장 널리 사용되는 오픈 소스 관계형 데이터베이스 중 하나로, 효과적인 모니터링이 중요합니다.

✅ MySQL Exporter 설치 및 구성

Prometheus MySQL Exporter를 설치하여 MySQL 메트릭을 수집하는 방법을 알아보겠습니다.

  1. MySQL 사용자 및 권한 설정
    • 모니터링 전용 사용자 생성
    • 필요한 최소 권한만 부여
    • 접속 범위 제한
-- MySQL Exporter를 위한 사용자 및 권한 설정
-- 모니터링 전용 사용자를 생성하고 필요한 최소 권한만 부여합니다

-- 모니터링 전용 사용자 생성
CREATE USER 'exporter'@'%' IDENTIFIED BY 'strong_password_here';

-- 필요한 권한 부여
-- PROCESS: 프로세스 목록 조회 권한
-- REPLICATION CLIENT: 복제 상태 확인 권한
-- SELECT: 시스템 테이블 조회 권한
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'%';

-- 권한 적용
FLUSH PRIVILEGES;

-- 확인
SHOW GRANTS FOR 'exporter'@'%';
  1. MySQL Exporter 배포
    • 쿠버네티스 Deployment로 배포
    • 적절한 환경 변수 설정
    • 서비스 및 서비스모니터 구성
# MySQL Exporter 배포 매니페스트
# 이 설정은 MySQL Exporter를 배포하고 Prometheus가 메트릭을 수집할 수 있도록 합니다
apiVersion: v1
kind: Secret
metadata:
  name: mysql-exporter-secrets
  namespace: monitoring
type: Opaque
data:
  # 'exporter:password' 형식의 데이터소스 문자열을 base64로 인코딩
  data-source-name: ZXhwb3J0ZXI6c3Ryb25nX3Bhc3N3b3JkX2hlcmVAbXlzcWwtc2VydmljZTozMzA2  # base64 인코딩된 값
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-exporter
  namespace: monitoring
  labels:
    app: mysql-exporter
spec:
  replicas: 1              # 일반적으로 단일 익스포터로 충분
  selector:
    matchLabels:
      app: mysql-exporter
  template:
    metadata:
      labels:
        app: mysql-exporter
      annotations:
        prometheus.io/scrape: "true"     # Prometheus 자동 검색 활성화
        prometheus.io/port: "9104"       # 익스포터 포트
    spec:
      containers:
      - name: mysql-exporter
        image: prom/mysqld-exporter:v0.13.0   # 익스포터 이미지 및 버전
        ports:
        - containerPort: 9104
          name: metrics                  # 포트 이름
        env:
        - name: DATA_SOURCE_NAME         # MySQL 연결 문자열
          valueFrom:
            secretKeyRef:
              name: mysql-exporter-secrets
              key: data-source-name
        args:
        - --collect.info_schema.tables   # 테이블 통계 수집
        - --collect.info_schema.innodb_metrics  # InnoDB 메트릭 수집
        - --collect.global_status        # 글로벌 상태 변수 수집
        - --collect.global_variables     # 글로벌 시스템 변수 수집
        - --collect.slave_status         # 복제 상태 수집
        - --collect.info_schema.processlist  # 프로세스 목록 수집
        resources:
          limits:
            cpu: 200m                    # CPU 한도
            memory: 256Mi                # 메모리 한도
          requests:
            cpu: 100m                    # CPU 요청량
            memory: 128Mi                # 메모리 요청량
        livenessProbe:                   # 컨테이너 건강 상태 확인
          httpGet:
            path: /
            port: 9104
          initialDelaySeconds: 30
          timeoutSeconds: 5
        readinessProbe:                  # 컨테이너 준비 상태 확인
          httpGet:
            path: /
            port: 9104
          initialDelaySeconds: 10
          timeoutSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: mysql-exporter
  namespace: monitoring
  labels:
    app: mysql-exporter
spec:
  selector:
    app: mysql-exporter
  ports:
  - port: 9104
    targetPort: 9104
    name: metrics
  1. Prometheus ServiceMonitor 설정
    • Prometheus Operator 사용 시 설정
    • 스크래핑 간격 및 타임아웃 조정
    • 메트릭 레이블 설정
# MySQL Exporter ServiceMonitor 설정
# Prometheus Operator를 사용할 경우 이 설정으로 메트릭 수집을 자동화합니다
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: mysql-exporter
  namespace: monitoring
  labels:
    release: prometheus    # Prometheus Operator 설치시 지정한 레이블과 일치해야 함
spec:
  selector:
    matchLabels:
      app: mysql-exporter  # 모니터링할 서비스 선택기
  endpoints:
  - port: metrics          # Service의 port 이름과 일치
    interval: 30s          # 스크래핑 간격 - 서버 부하를 고려하여 설정
    scrapeTimeout: 10s     # 스크래핑 타임아웃
    honorLabels: true      # 원본 메트릭의 레이블 유지
    relabelings:           # 메트릭 레이블 재정의
    - sourceLabels: [__meta_kubernetes_namespace]
      targetLabel: namespace
    - sourceLabels: [__meta_kubernetes_service_name]
      targetLabel: service
    metricRelabelings:     # 메트릭 필터링 및 변환
    - sourceLabels: [__name__]
      regex: 'mysql_info_schema_tables_rows'
      action: drop         # 용량이 큰 테이블 행 수 메트릭 제외 (선택적)

✅ 핵심 MySQL 메트릭

MySQL 성능 모니터링에 필수적인 핵심 메트릭을 살펴보겠습니다.

  1. 연결 및 스레드 메트릭
    • 활성 연결 수
    • 연결 오류 및 중단
    • 스레드 상태 및 사용량
  2. 쿼리 성능 메트릭
    • 초당 쿼리 실행 수
    • 느린 쿼리 수
    • 쿼리 캐시 효율성
  3. InnoDB 메트릭
    • 버퍼 풀 효율성
    • 데이터 및 로그 I/O
    • 트랜잭션 및 잠금 상태
  4. 복제 메트릭
    • 복제 지연 시간
    • I/O 및 SQL 스레드 상태
    • 복제 오류
# 중요 MySQL 메트릭을 위한 PromQL 쿼리 예시
# 이 쿼리들은 MySQL 데이터베이스의 핵심 성능 지표를 모니터링합니다

# 1. 활성 연결 수 vs 최대 연결 수 (사용률 %)
# 연결 풀이 고갈되는 것을 방지하기 위해 모니터링
mysql_global_status_threads_connected / mysql_global_variables_max_connections * 100

# 2. 초당 쿼리 수 (QPS) - 전체 시스템 부하 측정
# rate(): 지정된 시간 범위 동안의 변화율 계산
rate(mysql_global_status_queries[5m])

# 3. 초당 느린 쿼리 수 - 성능 문제 조기 감지
# irate(): 순간 변화율 계산 (더 민감한 변화 감지)
irate(mysql_global_status_slow_queries[5m])

# 4. InnoDB 버퍼 풀 적중률 (%) - 메모리 효율성 측정
# 낮은 적중률은 메모리 부족 또는 비효율적인 쿼리 패턴 의미
(1 - (
  rate(mysql_global_status_innodb_buffer_pool_reads[5m]) /
  rate(mysql_global_status_innodb_buffer_pool_read_requests[5m])
)) * 100

# 5. InnoDB 데이터 읽기/쓰기 비율 - I/O 패턴 분석
# 읽기/쓰기 비율이 높으면 읽기 집약적, 낮으면 쓰기 집약적
rate(mysql_global_status_innodb_data_reads[5m]) /
rate(mysql_global_status_innodb_data_writes[5m])

# 6. 테이블 잠금 대기 비율 - 경합 문제 식별
rate(mysql_global_status_table_locks_waited[5m]) /
rate(mysql_global_status_table_locks_immediate[5m]) * 100

# 7. 복제 지연 시간 (초) - 복제 상태 모니터링
mysql_slave_status_seconds_behind_master

# 8. 연결 실패 비율 - 연결 관련 문제 감지
rate(mysql_global_status_aborted_connects[5m]) /
rate(mysql_global_status_connections[5m]) * 100

✅ MySQL 대시보드 구성

효과적인 MySQL 모니터링 대시보드를 구성하는 방법을 알아보겠습니다.

  1. 개요 패널
    • 인스턴스 상태 요약
    • 주요 성능 지표 (QPS, 연결, 지연 시간)
    • 알림 상태
  2. 연결 및 쿼리 패널
    • 시간에 따른 연결 및 쿼리 추세
    • 쿼리 유형별 분포
    • 상위 데이터베이스 및 테이블
  3. 리소스 사용량 패널
    • 메모리 사용량 및 버퍼 풀 상태
    • 디스크 I/O 및 임시 테이블 사용
    • 테이블 캐시 및 파일 핸들 상태
  4. 복제 및 백업 패널
    • 복제 상태 및 지연 시간
    • 바이너리 로그 크기 및 성장률
    • 백업 수행 시간 및 크기

MySQL 대시보드 구성 예시

 

 

▶️ Grafana 대시보드 활용: 많은 사전 구성된 대시보드 템플릿이 Grafana 커뮤니티에서 제공됩니다. MySQL Overview(ID: 7362) 또는 MySQL InnoDB Metrics(ID: 7991)와 같은 템플릿을 가져와서 시작점으로 사용할 수 있습니다.


📌 PostgreSQL 모니터링 설정

PostgreSQL은 고급 기능과 확장성으로 인해 많은 기업에서 선택하는 강력한 관계형 데이터베이스입니다.

✅ PostgreSQL Exporter 설치 및 구성

Prometheus PostgreSQL Exporter를 설치하여 PostgreSQL 메트릭을 수집하는 방법을 알아보겠습니다.

  1. PostgreSQL 사용자 및 권한 설정
    • 모니터링 전용 사용자 생성
    • 필요한 권한 부여
    • 접근 제어 설정
-- PostgreSQL Exporter를 위한 사용자 및 권한 설정
-- 모니터링 전용 사용자를 생성하고 필요한 최소 권한만 부여합니다

-- 모니터링 전용 사용자 생성 (비밀번호 변경 필요)
CREATE USER postgres_exporter WITH PASSWORD 'strong_password_here' CONNECTION LIMIT 3;

-- 모니터링에 필요한 기본 권한 부여
GRANT pg_monitor TO postgres_exporter;  -- PostgreSQL 10 이상에서는 pg_monitor 역할 사용

-- 특정 데이터베이스에 대한 추가 권한 (선택적)
-- 이 부분은 모니터링하려는 각 데이터베이스에 대해 실행해야 합니다
\c your_database_name
GRANT CONNECT ON DATABASE your_database_name TO postgres_exporter;
GRANT SELECT ON pg_stat_database TO postgres_exporter;

-- 사용자 정의 쿼리를 실행할 경우 추가 권한이 필요할 수 있음
-- 예: 테이블 크기 모니터링을 위한 권한
GRANT SELECT ON ALL TABLES IN SCHEMA public TO postgres_exporter;
  1. PostgreSQL Exporter 배포
    • 쿠버네티스 배포 구성
    • 커스텀 쿼리 설정
    • 서비스 및 엔드포인트 설정

```yaml
# PostgreSQL Exporter 배포 매니페스트
# 이 설정은 PostgreSQL Exporter를 배포하고 Prometheus가 메트릭을 수집할 수 있도록 합니다
# PostgreSQL Exporter는 데이터베이스의 성능 지표를 수집하여 Prometheus 포맷으로 변환합니다

apiVersion: v1
kind: ConfigMap
metadata:
  name: postgres-exporter-config    # ConfigMap의 이름
  namespace: monitoring             # 리소스가 배포될 네임스페이스
data:
  # 추가 쿼리 설정 파일 - 기본 메트릭 외에 사용자 정의 쿼리를 통해 추가 메트릭을 수집
  queries.yaml: |
    # 복제 상태 모니터링 쿼리 - 프라이머리/리플리카 구성에서 복제 상태를 추적
    pg_replication:
      query: "SELECT pid, usename, application_name, client_addr, state, sync_state, sync_priority FROM pg_stat_replication;"
      metrics:
        - pid:
            usage: "LABEL"           # LABEL은 메트릭을 식별하는 데 사용되는 차원
            description: "Process ID of the replication process"
        - usename:
            usage: "LABEL"
            description: "Username used for replication"
        - application_name:
            usage: "LABEL"
            description: "Name of the application"
        - client_addr:
            usage: "LABEL"
            description: "Client IP address"
        - state:
            usage: "LABEL"           # 복제 상태(streaming, catchup 등)
            description: "Current state of replication"
        - sync_state:
            usage: "LABEL"           # 동기화 상태(async, sync, potential 등)
            description: "Synchronization state"
        - sync_priority:
            usage: "GAUGE"           # GAUGE는 증가/감소할 수 있는 값(특정 시점의 측정값)
            description: "Priority of synchronous replication"
    
    # 테이블 크기 모니터링 쿼리 - 각 테이블의 크기를 바이트 단위로 추적
    pg_table_size:
      query: |
        SELECT 
            schemaname,              # 스키마 이름
            tablename,               # 테이블 이름
            pg_table_size(schemaname || '.' || tablename) as size_bytes  # 테이블 크기(바이트)
        FROM pg_tables
        WHERE schemaname NOT IN ('pg_catalog', 'information_schema')  # 시스템 테이블 제외
      metrics:
        - schemaname:
            usage: "LABEL"
            description: "Schema name"
        - tablename:
            usage: "LABEL"
            description: "Table name"
        - size_bytes:
            usage: "GAUGE"           # 테이블 크기는 시간에 따라 변하므로 GAUGE 타입
            description: "Size of the table in bytes"
---
# 데이터베이스 접속 정보를 안전하게 저장하기 위한 Secret 리소스
apiVersion: v1
kind: Secret
metadata:
  name: postgres-exporter-secrets   # Secret 이름
  namespace: monitoring             # 네임스페이스
type: Opaque                        # 일반적인 비밀 데이터 타입
data:
  # PostgreSQL 연결 문자열을 base64로 인코딩
  # 형식: 'postgresql://username:password@hostname:port/dbname?sslmode=disable'
  # 실제 환경에서는 강력한 비밀번호를 사용하고 필요시 SSL 활성화 필요
  data-source-name: cG9zdGdyZXNxbDovL3Bvc3RncmVzX2V4cG9ydGVyOnN0cm9uZ19wYXNzd29yZF9oZXJlQHBvc3RncmVzLXNlcnZpY2U6NTQzMi9wb3N0Z3Jlcz9zc2xtb2RlPWRpc2FibGU=
---
# PostgreSQL Exporter를 배포하기 위한 Deployment 리소스
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres-exporter            # Deployment 이름
  namespace: monitoring              # 네임스페이스
  labels:
    app: postgres-exporter           # 리소스 식별용 레이블
spec:
  replicas: 1                        # 익스포터 Pod 수 - 대개 1개로 충분
  selector:
    matchLabels:
      app: postgres-exporter         # 관리할 Pod 선택 기준
  template:
    metadata:
      labels:
        app: postgres-exporter       # Pod 레이블
      annotations:
        prometheus.io/scrape: "true" # Prometheus가 자동으로 이 Pod를 스크래핑하도록 설정
        prometheus.io/port: "9187"   # 스크래핑할 포트 지정
    spec:
      containers:
      - name: postgres-exporter
        image: prometheuscommunity/postgres-exporter:v0.10.0  # 커뮤니티에서 관리하는 공식 이미지
        ports:
        - containerPort: 9187        # 익스포터가 메트릭을 노출하는 포트
          name: metrics              # 포트 이름 (Service에서 참조)
        env:
        - name: DATA_SOURCE_NAME     # PostgreSQL 연결 정보
          valueFrom:
            secretKeyRef:            # Secret에서 연결 문자열 참조
              name: postgres-exporter-secrets
              key: data-source-name
        - name: PG_EXPORTER_EXTEND_QUERY_PATH  # 사용자 정의 쿼리 파일 경로
          value: "/etc/postgres_exporter/queries.yaml"
        volumeMounts:
        - name: config-volume        # 쿼리 설정 파일이 있는 볼륨
          mountPath: /etc/postgres_exporter
        resources:                   # 리소스 요청 및 제한 설정
          limits:
            cpu: 200m                # 최대 0.2 CPU 코어 사용
            memory: 256Mi            # 최대 256MB 메모리 사용
          requests:
            cpu: 100m                # 최소 0.1 CPU 코어 요청
            memory: 128Mi            # 최소 128MB 메모리 요청
        livenessProbe:               # Pod가 살아있는지 확인하는 프로브
          httpGet:
            path: /                  # 상태 확인 경로
            port: 9187               # 상태 확인 포트
          initialDelaySeconds: 30    # 컨테이너 시작 후 프로브 시작 전 대기 시간
          timeoutSeconds: 5          # 프로브 타임아웃 시간
        readinessProbe:              # Pod가 요청을 처리할 준비가 되었는지 확인하는 프로브
          httpGet:
            path: /
            port: 9187
          initialDelaySeconds: 10    # 컨테이너 시작 후 준비 확인 전 대기 시간
          timeoutSeconds: 5          # 준비 확인 타임아웃
      volumes:
      - name: config-volume          # 쿼리 설정 파일을 위한 볼륨
        configMap:
          name: postgres-exporter-config  # 사용할 ConfigMap 이름
---
# PostgreSQL Exporter를 위한 Service 리소스
# Service는 익스포터 Pod를 안정적인 네트워크 엔드포인트로 노출
apiVersion: v1
kind: Service
metadata:
  name: postgres-exporter            # Service 이름
  namespace: monitoring              # 네임스페이스
  labels:
    app: postgres-exporter           # 서비스 식별 레이블
spec:
  selector:
    app: postgres-exporter           # 트래픽을 전달할 Pod 선택기
  ports:
  - port: 9187                       # 서비스가 수신하는 포트
    targetPort: 9187                 # 트래픽을 전달할 컨테이너 포트
    name: metrics                    # 포트 이름 (ServiceMonitor에서 참조)
```
  1. ServiceMonitor 설정
    • Prometheus Operator 사용 시 설정
    • 스크래핑 간격 및 타임아웃 조정
    • 레이블 및 필터링 설정
# PostgreSQL Exporter ServiceMonitor 설정
# Prometheus Operator를 사용할 경우 이 설정으로 메트릭 수집을 자동화합니다
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: postgres-exporter
  namespace: monitoring
  labels:
    release: prometheus    # Prometheus Operator 설치시 지정한 레이블과 일치해야 함
spec:
  selector:
    matchLabels:
      app: postgres-exporter  # 모니터링할 서비스 선택기
  endpoints:
  - port: metrics          # Service의 port 이름과 일치
    interval: 30s          # 스크래핑 간격 - 서버 부하를 고려하여 설정
    scrapeTimeout: 10s     # 스크래핑 타임아웃
    honorLabels: true      # 원본 메트릭의 레이블 유지
    relabelings:           # 메트릭 레이블 재정의
    - sourceLabels: [__meta_kubernetes_namespace]
      targetLabel: namespace
    - sourceLabels: [__meta_kubernetes_service_name]
      targetLabel: service
    metricRelabelings:     # 메트릭 필터링 및 변환 (선택적)
    - sourceLabels: [__name__]
      regex: 'pg_database_size_bytes'
      action: keep         # 데이터베이스 크기 메트릭만 유지 (예시)

✅ 핵심 PostgreSQL 메트릭

PostgreSQL 성능 모니터링에 필수적인 핵심 메트릭을 살펴보겠습니다.

  1. 데이터베이스 활동 메트릭
    • 활성 연결 및 세션
    • 트랜잭션 및 쿼리 활동
    • 커밋 및 롤백 비율
  2. 성능 메트릭
    • 쿼리 지연 시간
    • 인덱스 사용 및 스캔 효율성
    • 캐시 적중률
  3. 테이블 및 인덱스 메트릭
    • 테이블 및 인덱스 크기
    • 자동 분석 및 배큠 활동
    • 테이블 및 인덱스 스캔 비율
  4. WAL 및 복제 메트릭
    • WAL 생성 속도
    • 복제 지연 및 상태
    • 동기화 상태
# 중요 PostgreSQL 메트릭을 위한 PromQL 쿼리 예시
# 이 쿼리들은 PostgreSQL 데이터베이스의 핵심 성능 지표를 모니터링합니다

# 1. 활성 연결 수 vs 최대 연결 수 (사용률 %)
# 연결 풀 사용 상태를 모니터링하여 연결 고갈 문제를 사전에 감지
100 * pg_stat_activity_count / pg_settings_max_connections

# 2. 데이터베이스별 트랜잭션 처리율 (초당)
# 처리량과 워크로드 패턴을 분석하는데 중요한 지표
rate(pg_stat_database_xact_commit{datname="your_database"}[5m]) + 
rate(pg_stat_database_xact_rollback{datname="your_database"}[5m])

# 3. 캐시 적중률 (%)
# 높을수록 디스크 I/O가 적고 성능이 좋음을 의미 (일반적으로 90% 이상이 바람직)
100 * (1 - (
  sum(rate(pg_stat_database_blks_read[5m])) / 
  (sum(rate(pg_stat_database_blks_read[5m])) + sum(rate(pg_stat_database_blks_hit[5m])))
))

# 4. 가장 오래 실행 중인 쿼리 시간 (초)
# 장기 실행 쿼리는 성능 문제와 잠금 경합을 유발할 수 있음
max(pg_stat_activity_max_tx_duration)

# 5. 디스크 사용량 증가율 (시간당 바이트)
# 스토리지 공간 계획 및 이상 증가 감지에 중요
rate(pg_database_size_bytes{datname="your_database"}[1h]) * 3600

# 6. 인덱스 사용 비율 (% - 높을수록 좋음)
# 인덱스를 효과적으로 사용하는지 확인하는 지표
100 * sum(pg_stat_user_tables_idx_scan) / 
(sum(pg_stat_user_tables_idx_scan) + sum(pg_stat_user_tables_seq_scan) + 1)

# 7. 복제 지연 시간 (바이트)
# 프라이머리와 리플리카 간의 지연을 측정
pg_stat_replication_pg_wal_lsn_diff

# 8. 버퍼 사용률 (%)
# 공유 버퍼의 사용 현황을 모니터링
100 * (pg_stat_bgwriter_buffers_alloc - pg_stat_bgwriter_buffers_clean) / 
pg_settings_shared_buffers

✅ PostgreSQL 대시보드 구성

효과적인 PostgreSQL 모니터링 대시보드를 구성하는 방법을 알아보겠습니다.

  1. 개요 패널
    • 인스턴스 상태 및 버전 정보
    • 연결 상태 및 활성 쿼리
    • 주요 성능 지표 요약
  2. 데이터베이스 성능 패널
    • 트랜잭션 처리율
    • 캐시 적중률
    • 테이블 및 인덱스 스캔
  3. 리소스 및 저장소 패널
    • 데이터베이스 크기 및 성장률
    • 테이블스페이스 사용량
    • WAL 생성 및 처리
  4. 쿼리 성능 패널
    • 슬로우 쿼리 통계
    • 쿼리 실행 시간 분포
    • 정렬 및 임시 파일 사용

 

▶️ Grafana 대시보드 활용: Grafana 커뮤니티에서 제공하는 PostgreSQL Overview(ID: 9628) 또는 PostgreSQL Database(ID: 455)와 같은 템플릿을 활용하여 빠르게 시작할 수 있습니다.


📌 데이터베이스 알림 설정

적절한 알림 구성은 문제가 발생하기 전에 미리 감지하고 대응할 수 있도록 도와줍니다.

✅ 핵심 알림 규칙

데이터베이스 모니터링에서 필수적인 알림 규칙을 알아보겠습니다.

  1. 연결 관련 알림
    • 높은 연결 사용률
    • 연결 실패 급증
    • 유휴 연결 과다
  2. 성능 관련 알림
    • 장기 실행 쿼리
    • 높은 CPU/메모리 사용률
    • 낮은 캐시 적중률
  3. 저장소 관련 알림
    • 디스크 공간 부족
    • 높은 WAL 생성률
    • 테이블스페이스 성장 이상
  4. 가용성 관련 알림
    • 인스턴스 다운
    • 복제 지연 또는 중단
    • 백업 실패
# Prometheus AlertManager를 위한 알림 규칙 예시
# 이 규칙들은 데이터베이스의 핵심 문제를 조기에 감지하기 위한 설정입니다
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: database-alerts
  namespace: monitoring
  labels:
    app: kube-prometheus-stack
    release: prometheus  # Prometheus Operator 설치시 지정한 릴리스 이름
spec:
  groups:
  - name: database.rules  # 규칙 그룹 이름
    rules:
    # MySQL 연결 사용률 임계값 경고
    - alert: MySQLConnectionUsageHigh
      expr: mysql_global_status_threads_connected / mysql_global_variables_max_connections * 100 > 80
      for: 5m  # 5분 동안 지속될 경우 알림 발생
      labels:
        severity: warning  # 알림 심각도
        database_type: mysql  # 데이터베이스 유형 레이블
      annotations:
        summary: "MySQL 연결 사용률 높음 ({{ $value }}%)"
        description: "인스턴스 {{ $labels.instance }}의 연결 사용률이 80%를 초과했습니다. 현재 값: {{ $value }}%"
        runbook_url: "https://your-runbooks/mysql-connection-usage"  # 문제 해결 가이드 링크

    # MySQL 느린 쿼리 증가 경고
    - alert: MySQLSlowQueriesIncreasing
      expr: rate(mysql_global_status_slow_queries[5m]) > 5
      for: 10m
      labels:
        severity: warning
        database_type: mysql
      annotations:
        summary: "MySQL 느린 쿼리 증가 감지"
        description: "인스턴스 {{ $labels.instance }}에서 분당 느린 쿼리가 5개 이상 발생하고 있습니다. 현재 값: {{ $value }}"
        runbook_url: "https://your-runbooks/mysql-slow-queries"

    # PostgreSQL 연결 사용률 임계값 경고
    - alert: PostgreSQLConnectionUsageHigh
      expr: 100 * pg_stat_activity_count / pg_settings_max_connections > 80
      for: 5m
      labels:
        severity: warning
        database_type: postgresql
      annotations:
        summary: "PostgreSQL 연결 사용률 높음 ({{ $value }}%)"
        description: "인스턴스 {{ $labels.instance }}의 연결 사용률이 80%를 초과했습니다. 현재 값: {{ $value }}%"
        runbook_url: "https://your-runbooks/postgresql-connection-usage"

    # PostgreSQL 복제 지연 경고
    - alert: PostgreSQLReplicationLag
      expr: pg_stat_replication_pg_wal_lsn_diff > 52428800  # 50MB 이상 지연
      for: 10m
      labels:
        severity: warning
        database_type: postgresql
      annotations:
        summary: "PostgreSQL 복제 지연 감지"
        description: "인스턴스 {{ $labels.instance }}의 복제 지연이 50MB를 초과했습니다. 현재 값: {{ $value | humanize }}B"
        runbook_url: "https://your-runbooks/postgresql-replication-lag"

    # 데이터베이스 백업 실패 경고 (커스텀 메트릭 필요)
    - alert: DatabaseBackupFailed
      expr: database_backup_success_timestamp < (time() - 86400)  # 마지막 성공 백업이 24시간 초과
      for: 1h
      labels:
        severity: critical
        database_type: all
      annotations:
        summary: "데이터베이스 백업 실패"
        description: "데이터베이스 {{ $labels.database }}의 마지막 성공 백업이 24시간 이상 경과했습니다."
        runbook_url: "https://your-runbooks/database-backup-failure"

    # 디스크 공간 부족 경고
    - alert: DatabaseDiskSpaceCritical
      expr: |
        (
          node_filesystem_avail_bytes{mountpoint="/var/lib/mysql"} / 
          node_filesystem_size_bytes{mountpoint="/var/lib/mysql"}
        ) * 100 < 10
      for: 5m
      labels:
        severity: critical
        database_type: mysql
      annotations:
        summary: "MySQL 디스크 공간 부족 ({{ $value }}% 남음)"
        description: "MySQL 데이터 디렉토리의 디스크 공간이 10% 미만으로 남았습니다. 현재 남은 공간: {{ $value }}%"
        runbook_url: "https://your-runbooks/mysql-disk-space"

✅ 알림 우선순위 및 임계값 설정

효과적인 알림 관리를 위한 우선순위와 임계값 설정 방법을 알아보겠습니다.

  1. 심각도 레벨 정의
    • Critical: 즉각적인 대응 필요
    • Warning: 잠재적인 문제
    • Info: 참고 정보
  2. 알림 임계값 설정 원칙
    • 과거 데이터 분석 기반 설정
    • 점진적 임계값 적용
    • 비즈니스 요구사항 고려
  3. 알림 피로 방지 전략
    • 유사한 알림 그룹화
    • 알림 발생 전 지속 시간 설정
    • 조건부 알림 발송

▶️ 운영 사례: 한 금융 기관은 임계값 기반 알림으로 인한 '알림 피로'를 줄이기 위해 이상 징후 감지 기반 알림 시스템을 구현했습니다. 과거 데이터를 바탕으로 정상 범위를 학습하고, 그 범위를 벗어나는 경우에만 알림을 발생시켜 알림 수를 75% 줄이면서도 중요한 문제 감지율은 유지할 수 있었습니다.


📌 데이터베이스 성능 최적화를 위한 모니터링 활용

수집된 메트릭을 활용하여 데이터베이스 성능을 분석하고 최적화하는 방법을 알아보겠습니다.

✅ 주요 성능 문제 진단

데이터베이스에서 자주 발생하는 성능 문제와 해당 메트릭을 통한 진단 방법을 살펴보겠습니다.

  1. 느린 쿼리 분석
    • 메트릭: 쿼리 실행 시간, 쿼리 유형별 통계
    • 진단: 지속적으로 높은 실행 시간 패턴 확인
    • 해결: 쿼리 재작성, 인덱스 추가/개선
  2. 연결 풀 고갈
    • 메트릭: 활성 연결 수, 연결 대기 시간
    • 진단: 연결 사용률의 지속적인 증가 및 피크
    • 해결: 연결 풀 설정 조정, 연결 관리 개선
  3. 리소스 경합
    • 메트릭: CPU/메모리 사용률, I/O 대기 시간
    • 진단: 리소스 사용률 급증 또는 지속적인 높은 사용률
    • 해결: 리소스 할당 증가, 워크로드 분산
  4. 캐시 비효율성
    • 메트릭: 캐시 적중률, 버퍼 풀 상태
    • 진단: 낮은 적중률, 높은 디스크 I/O
    • 해결: 캐시 크기 조정, 쿼리 패턴 최적화
-- MySQL 성능 스키마를 활용한 느린 쿼리 분석 예시
-- 이 쿼리는 성능 문제를 일으키는 느린 쿼리를 식별하는 데 도움이 됩니다

-- 실행 시간이 가장 긴 쿼리 상위 10개 조회
SELECT 
    DIGEST_TEXT AS query_pattern,                      -- 정규화된 쿼리 패턴
    COUNT_STAR AS execution_count,                     -- 실행 횟수
    SUM_TIMER_WAIT / 1000000000000 AS total_exec_time_sec,  -- 총 실행 시간(초)
    AVG_TIMER_WAIT / 1000000000 AS avg_exec_time_ms,   -- 평균 실행 시간(밀리초)
    MAX_TIMER_WAIT / 1000000000 AS max_exec_time_ms,   -- 최대 실행 시간(밀리초)
    SUM_ROWS_EXAMINED AS rows_examined,                -- 조회된 행 수
    SUM_ROWS_SENT AS rows_sent                         -- 반환된 행 수
FROM 
    performance_schema.events_statements_summary_by_digest
ORDER BY 
    avg_exec_time_ms DESC                             -- 평균 실행 시간으로 정렬
LIMIT 10;                                             -- 상위 10개 쿼리만 표시

-- 결과 해석:
-- 1. avg_exec_time_ms가 높은 쿼리들이 성능 병목의 주요 원인일 수 있음
-- 2. rows_examined와 rows_sent의 비율이 높으면 인덱스가 비효율적이거나 누락된 것일 수 있음
-- 3. execution_count가 높고 avg_exec_time_ms도 높은 쿼리는 최적화 우선순위가 높음
-- PostgreSQL에서 성능 문제를 식별하기 위한 쿼리 예시
-- 이 쿼리들은 성능 병목 현상을 진단하는 데 도움이 됩니다

-- 1. 현재 실행 중인 장기 쿼리 확인
SELECT 
    pid,                                        -- 프로세스 ID
    usename,                                    -- 사용자 이름
    application_name,                           -- 애플리케이션 이름
    client_addr,                                -- 클라이언트 IP 주소
    state,                                      -- 현재 상태
    now() - query_start AS duration,            -- 쿼리 실행 시간(간격)
    wait_event_type,                            -- 대기 이벤트 유형
    wait_event,                                 -- 대기 이벤트
    query                                       -- 실행 중인 쿼리
FROM 
    pg_stat_activity
WHERE 
    state != 'idle'                           -- 유휴 상태가 아닌 쿼리만
    AND now() - query_start > interval '30 seconds'  -- 30초 이상 실행 중인 쿼리
ORDER BY 
    duration DESC;                            -- 실행 시간 내림차순 정렬

-- 2. 테이블별 캐시 적중률 확인
SELECT 
    relname AS table_name,                     -- 테이블 이름
    heap_blks_read AS disk_reads,              -- 디스크에서 읽은 블록 수
    heap_blks_hit AS cache_hits,               -- 캐시에서 읽은 블록 수
    CASE WHEN heap_blks_read + heap_blks_hit > 0
         THEN 100 * heap_blks_hit / (heap_blks_read + heap_blks_hit)
         ELSE 0
    END AS cache_hit_ratio                     -- 캐시 적중률(%)
FROM 
    pg_statio_user_tables
ORDER BY 
    heap_blks_read DESC                         -- 디스크 읽기가 많은 테이블 순으로
LIMIT 20;                                       -- 상위 20개 테이블만 표시

-- 3. 데이터베이스별 블로킹/잠금 세션 확인
SELECT 
    blocked_locks.pid AS blocked_pid,           -- 블로킹된 프로세스 ID
    blocked_activity.usename AS blocked_user,   -- 블로킹된 사용자
    blocking_locks.pid AS blocking_pid,         -- 블로킹 중인 프로세스 ID
    blocking_activity.usename AS blocking_user, -- 블로킹 중인 사용자
    blocked_activity.query AS blocked_query,    -- 블로킹된 쿼리
    blocking_activity.query AS blocking_query,  -- 블로킹 중인 쿼리
    now() - blocking_activity.query_start AS blocking_duration  -- 블로킹 지속 시간
FROM 
    pg_catalog.pg_locks blocked_locks
JOIN 
    pg_catalog.pg_stat_activity blocked_activity ON blocked_locks.pid = blocked_activity.pid
JOIN 
    pg_catalog.pg_locks blocking_locks ON blocked_locks.transactionid = blocking_locks.transactionid 
    AND blocked_locks.pid != blocking_locks.pid
JOIN 
    pg_catalog.pg_stat_activity blocking_activity ON blocking_locks.pid = blocking_activity.pid
WHERE 
    NOT blocked_locks.granted;                  -- 승인되지 않은 잠금(대기 중인 잠금)만

✅ 정기적인 성능 검토 프로세스

모니터링 데이터를 활용한 정기적인 성능 검토 프로세스를 수립하는 방법을 알아보겠습니다.

  1. 일일 검토
    • 알림 발생 패턴 분석
    • 핵심 메트릭 추세 확인
    • 임박한 용량 이슈 식별
  2. 주간 검토
    • 성능 추세 분석
    • 자원 사용 패턴 확인
    • 최적화 대상 식별
  3. 월간 검토
    • 장기 추세 및 성장률 분석
    • 용량 계획 수립
    • 구성 및 인덱스 최적화

▶️ 데이터 기반 최적화 사례: 한 전자상거래 플랫폼은 모니터링 데이터를 분석하여 일일 판매 이벤트 중 특정 쿼리의 실행 시간이 크게 증가하는 패턴을 발견했습니다. 이를 바탕으로 해당 쿼리에 대한 인덱스를 최적화하고 실행 계획을 개선하여, 피크 시간대 데이터베이스 응답 시간을 60% 단축했습니다.


📌 Summary

이번 포스트에서는 쿠버네티스 환경에서 MySQL과 PostgreSQL 데이터베이스를 효과적으로 모니터링하는 방법에 대해 알아보았습니다. 다음과 같은 내용을 다루었습니다:

  • 데이터베이스 모니터링의 중요성: 데이터베이스 성능 문제가 전체 애플리케이션에 미치는 영향과 쿠버네티스 환경에서의 특수성을 이해하는 것이 모니터링의 첫 단계입니다.
  • MySQL 및 PostgreSQL Exporter 설정: 각 데이터베이스 유형에 맞는 익스포터를 설치하고 구성하여 메트릭을 수집하는 방법을 상세히 살펴보았습니다. 사용자 권한 설정부터 쿠버네티스 매니페스트까지 실전적인 설정 방법을 제공했습니다.
  • 핵심 성능 메트릭 이해: 각 데이터베이스 시스템에서 중요하게 모니터링해야 할 메트릭과 이를 분석하기 위한 PromQL 쿼리를 살펴보았습니다. 연결, 쿼리 성능, 캐시 효율성, 복제 상태 등 다양한 측면의 메트릭을 포괄적으로 다루었습니다.
  • 효과적인 대시보드 구성: 수집된 메트릭을 의미 있게 시각화하기 위한 Grafana 대시보드 구성 방법과 베스트 프랙티스를 소개했습니다. 대시보드 계층화와 목적별 패널 구성을 통해 직관적인 모니터링 환경을 구축할 수 있습니다.
  • 알림 규칙 설정: 문제가 발생하기 전에 선제적으로 감지할 수 있는 알림 규칙 설정 방법과 알림 피로를 줄이기 위한 전략을 알아보았습니다. 심각도 수준과 시간 기반 알림 설정으로 효과적인 알림 시스템을 구축할 수 있습니다.
  • 성능 문제 진단 및 최적화: 수집된 메트릭을 활용하여 일반적인 데이터베이스 성능 문제를 진단하고 해결하는 방법을 구체적인 SQL 쿼리와 함께 살펴보았습니다. 정기적인 성능 검토 프로세스를 통해 지속적인 최적화가 가능합니다.

데이터베이스 모니터링은 단순한 메트릭 수집을 넘어, 애플리케이션 성능과 안정성을 보장하기 위한 필수적인 프로세스입니다. 이번 글에서 소개한 방법론과 도구를 활용하여 여러분의 쿠버네티스 환경에서 데이터베이스를 효과적으로 모니터링하고 최적화하시기 바랍니다.

 

728x90