게임 서버 실시간 모니터링: Prometheus 메트릭 수집과 Grafana CCU·TPS 대시보드 구축 가이드

게임 서버 실시간 모니터링: Prometheus 메트릭 수집과 Grafana CCU·TPS 대시보드 구축 가이드

게임 서버의 동시 접속자 수(CCU)와 초당 트랜잭션 수(TPS)를 실시간 모니터링하기 위한 Prometheus 메트릭 설계, 익스포터 연동 및 Grafana 대시보드 구축 방법을 단계별로 정리합니다.

TL;DR (핵심 요약)

게임 서버 모니터링은 비정상 트래픽, 세션 누수, 틱 레이트 저하를 즉각 감지하기 위한 필수 인프라입니다. Prometheus의 Pull 메트릭 수집 모델과 Gauge(CCU), Counter(패킷/트랜잭션) 타입을 활용해 서버 상태를 노출하고 Grafana에서 PromQL(sum(game_server_ccu), sum(rate(game_server_packets_total[1m])))을 통해 실시간 대시보드를 구축하면 수만 동접 환경에서도 인프라 병목을 정확히 파악할 수 있습니다.

게임 서버에서 실시간 CCU와 TPS 모니터링이 필수적인 이유는 무엇인가?

멀티플레이어 게임 서버는 웹 서비스와 달리 지속적인 상태(Stateful)와 높은 초당 입출력(I/O)을 유지합니다. 수천 명의 유저가 동시에 접속해 패킷을 송수신하는 환경에서는 작은 병목이나 로직 오류가 전체 월드의 틱 레이트(Tick Rate) 저하 또는 서버 다운으로 직결됩니다.

실시간 관측 가능성(Observability)이 확보되지 않으면 다음과 같은 치명적인 문제가 발생합니다.

  1. 스파이크 트래픽 감지 실패: 이벤트 오픈이나 신규 콘텐츠 배포 시 급증하는 트래픽을 즉시 파악하지 못해 오토스케일링 타이밍을 놓칩니다.
  2. 세션 누수(Session Leak): 연결이 끊긴 클라이언트의 세션 객체가 해제되지 않고 남아 메모리를 고갈시키는 현상을 조기에 발견하기 어렵습니다.
  3. 틱 지연 원인 추적 불가: CPU 사용량만으로는 패킷 처리 지연, 데이터베이스 락(Lock), 외부 결제 API 지연 중 어느 것이 틱 드롭을 유발했는지 식별할 수 없습니다.
지표명주요 메트릭 타입측정 목적이상 징후 기준
CCU (동시 접속자 수)Gauge현재 월드/채널/인스턴스별 접속 세션 수급격한 하락(크래시) 또는 비정상 완만한 증가(세션 누수)
TPS / QPS (처리량)Counter / Rate초당 처리된 인게임 패킷 및 DB 트랜잭션 수CCU 대비 급격한 처리량 감소(데드락, 블로킹 I/O)
Tick Duration (틱 소요 시간)Histogram1프레임(Tick) 연산 완료에 소요된 시간목표 틱 주기(50ms50\text{ms} 등)를 초과하는 빈도 증가
Packet Latency (패킷 지연)Histogram패킷 수신부터 처리 완료까지의 소요 시간P99P_{99} 레이턴시 급증(네트워크 버퍼 적체)
flowchart LR
    subgraph Game_Server_Cluster [게임 서버 클러스터]
        GS1[게임 서버 인스턴스 1<br/>:9100 /metrics]
        GS2[게임 서버 인스턴스 2<br/>:9100 /metrics]
        GS3[게임 서버 인스턴스 N<br/>:9100 /metrics]
    end

    subgraph Monitoring_Stack [모니터링 인프라]
        PROM[Prometheus Server<br/>Pull Scrape Interval: 5s]
        GRAF[Grafana Dashboard<br/>실시간 시각화 & Alert]
    end

    PROM -->|HTTP GET Scrape| GS1
    PROM -->|HTTP GET Scrape| GS2
    PROM -->|HTTP GET Scrape| GS3
    GRAF -->|PromQL Query| PROM

게임 서버 메트릭 수집 아키텍처


Prometheus 메트릭 타입과 게임 서버 지표 설계

Prometheus는 크게 4가지 메트릭 타입을 제공합니다. 게임 서버의 모니터링 요구사항에 맞추어 적절한 타입을 선택해야 정확한 집계와 분석이 가능합니다.

  1. Gauge (게이지): 현재 시점의 값을 나타내며 증가하거나 감소할 수 있는 지표입니다. 현재 접속 중인 동시 접속자 수(CCU), 활성 룸(Room) 개수, 메모리 풀 사용량에 사용합니다.
  2. Counter (카운터): 누적 증가만 하는 지표(재시작 시 0으로 리셋)입니다. 처리된 총 패킷 수, 로그인 시도 횟수, DB 쿼리 실행 횟수에 사용하며 rate() 함수와 결합해 TPS를 계산합니다.
  3. Histogram (히스토그램): 관측값을 미리 정의된 버킷(Bucket)별로 나누어 카운트합니다. 패킷 처리 지연시간(P50P_{50}, P95P_{95}, P99P_{99}) 및 DB 응답 속도 분포를 측정할 때 사용합니다.
  4. Summary (서머리): 클라이언트 측에서 직접 백분위수를 계산하여 노출합니다. 오버헤드가 크고 여러 인스턴스 간의 집계가 불가능하므로 분산 게임 서버 환경에서는 Histogram 사용이 권장됩니다.

1단계: 게임 서버에 Prometheus 메트릭 익스포터 연동하기

C# (.NET 기반 게임 서버) 환경에서 prometheus-net 라이브러리를 사용해 HTTP 엔드포인트(:9100/metrics)를 열고 CCU, 패킷 처리량, 틱 지연시간을 수집하는 구현 예제입니다.

using System;
using System.Threading;
using System.Threading.Tasks;
using Prometheus;

public class GameServerMetrics
{
    // 1. 현재 접속자 수 (CCU) - Gauge
    private static readonly Gauge ConnectedUsers = Metrics.CreateGauge(
        "game_server_ccu",
        "현재 접속 중인 세션 수",
        new GaugeConfiguration
        {
            LabelNames = new[] { "server_id", "world_id", "channel_id" }
        });

    // 2. 누적 처리 패킷 수 (TPS 계산용) - Counter
    private static readonly Counter ProcessedPacketsTotal = Metrics.CreateCounter(
        "game_server_packets_total",
        "서버에서 처리된 누적 패킷 수",
        new CounterConfiguration
        {
            LabelNames = new[] { "server_id", "packet_type" }
        });

    // 3. 틱 처리 소요 시간 - Histogram
    private static readonly Histogram TickDurationSeconds = Metrics.CreateHistogram(
        "game_server_tick_duration_seconds",
        "1틱 연산 소요 시간(초)",
        new HistogramConfiguration
        {
            LabelNames = new[] { "server_id" },
            // 10ms(0.01s)부터 100ms(0.1s)까지의 버킷 정의
            Buckets = new[] { 0.005, 0.010, 0.020, 0.033, 0.050, 0.075, 0.100 }
        });

    private MetricServer _metricServer;

    public void StartExporter(int port = 9100)
    {
        // 메트릭 스크랩용 경량 HTTP 서버 구동
        _metricServer = new MetricServer(port: port);
        _metricServer.Start();
        Console.WriteLine($"[Metrics] Prometheus Exporter started on port {port}");
    }

    public void OnUserConnected(string serverId, string worldId, string channelId)
    {
        ConnectedUsers.WithLabels(serverId, worldId, channelId).Inc();
    }

    public void OnUserDisconnected(string serverId, string worldId, string channelId)
    {
        ConnectedUsers.WithLabels(serverId, worldId, channelId).Dec();
    }

    public void OnPacketProcessed(string serverId, string packetType)
    {
        ProcessedPacketsTotal.WithLabels(serverId, packetType).Inc();
    }

    public void RecordTickDuration(string serverId, double durationSeconds)
    {
        TickDurationSeconds.WithLabels(serverId).Observe(durationSeconds);
    }
}

2단계: Prometheus 스크랩(Scrape) 설정 및 인프라 구성

게임 서버 인스턴스들이 노출하는 메트릭을 수집하도록 prometheus.yml 파일을 구성합니다. 실시간 모니터링을 위해 수집 주기(scrape_interval)를 기본 15초보다 짧은 5초로 설정합니다.

global:
  scrape_interval: 5s
  evaluation_interval: 5s

scrape_configs:
  - job_name: "game-servers"
    metrics_path: "/metrics"
    static_configs:
      - targets:
          - "10.0.1.101:9100"
          - "10.0.1.102:9100"
          - "10.0.1.103:9100"
        labels:
          env: "production"
          region: "ap-northeast-2"

[!NOTE] Kubernetes 또는 Agones를 사용하는 전용 서버 클러스터 환경에서는 static_configs 대신 kubernetes_sd_configs를 사용하여 Pod 레이블 기반으로 동적 탐색(Service Discovery)을 구성하는 것이 표준입니다.

Grafana 게임 서버 대시보드 구성 화면


3단계: Grafana 실시간 CCU 및 TPS 대시보드 구축과 PromQL 작성법

Grafana에서 Prometheus를 데이터 소스로 연결한 후, 핵심 모니터링 패널들을 PromQL(Prometheus Query Language)로 구현합니다.

1. 전체 및 월드별 실시간 CCU 쿼리

현재 활성화된 모든 서버의 접속자 수를 합산하거나 특정 월드별로 그룹화합니다.

  • 전체 동시 접속자 수 (Stat 패널):
sum(game_server_ccu{env="production"})
  • 월드별 CCU 추이 (Time Series 그래프):
sum(game_server_ccu{env="production"}) by (world_id)

2. 실시간 초당 트랜잭션 및 패킷 처리량 (TPS / PPS)

누적 카운터 지표에 rate() 함수를 적용하여 초당 변화량을 산출합니다. 초당 트랜잭션 공식은 다음과 같습니다.

TPS=ΔCounterΔt\text{TPS} = \frac{\Delta \text{Counter}}{\Delta t}
  • 서버 전체 실시간 TPS (Time Series 그래프):
sum(rate(game_server_packets_total{env="production"}[1m]))
  • 패킷 타입별 TPS 상위 5개 (TopK Bar Gauge):
topk(5, sum(rate(game_server_packets_total{env="production"}[1m])) by (packet_type))

3. P99P_{99} 틱 지연시간 및 패킷 레이턴시 모니터링

히스토그램 버킷 데이터를 기반으로 상위 99% 사용자가 경험하는 지연시간(P99P_{99})을 계산합니다.

histogram_quantile(0.99, sum(rate(game_server_tick_duration_seconds_bucket{env="production"}[1m])) by (le, server_id))

대규모 트래픽 환경에서 메트릭 수집 시 주의할 점

  1. 카디널리티 폭발(Cardinality Explosion) 방지: 레이블 값으로 user_id, session_id, room_uuid와 같이 고유값이 무한히 생성되는 필드를 넣으면 Prometheus의 메모리가 급격히 고갈됩니다. 레이블은 server_id, world_id, packet_type 등 제한된 유한 집합에만 부여해야 합니다.
  2. 메인 루프 블로킹 방지: 메트릭 수집 라이브러리는 내부적으로 원자적 연산(Atomic Operation)이나 락프리 구조를 사용하지만 수집 로직 내에서 문자열 포맷팅이나 무거운 조회를 수행하면 게임 루프 성능에 악영향을 줍니다. 레이블 객체는 미리 캐싱하여 사용하십시오.
  3. 스크랩 오버헤드 최적화: 1초 단위 스크랩은 Prometheus 스토리지 및 네트워크 I/O에 부담을 줍니다. 실시간 대시보드 용도로는 5초~10초 수집 주기가 가장 권장되는 타협점입니다.

자주 묻는 질문 (FAQ)

Q1. 수천 대의 데디케이트 서버가 동적으로 생성/종료될 때 Prometheus 스크랩 대상은 어떻게 관리하나요?

고정 IP/포트 기반의 static_configs 대신 서비스 디스커버리(Service Discovery) 메커니즘을 사용합니다. Kubernetes 환경에서는 PodMonitor 또는 Agones의 Fleet 메트릭 애노테이션을 활용하고 AWS EC2/GameLift 환경에서는 aws_sd_configs나 Consul 연동을 통해 동적으로 인스턴스 목록을 갱신합니다.

Q2. TPS 계산 시 rate()irate() 중 어떤 함수를 사용해야 하나요?

Grafana 대시보드의 장기 추세 및 안정적인 그래프 표현에는 지정한 시간 범위의 평균 초당 증가율을 계산하는 rate() 함수가 적합합니다. irate()는 범위 내의 마지막 두 데이터 포인트만 계산하므로 순간적인 스파이크를 포착하는 데는 유리하지만 그래프 변동성이 심해 알람 룰이나 대시보드 메인 패널에는 rate()를 권장합니다.

Q3. Prometheus의 Pull 방식 대신 Pushgateway를 게임 서버에 사용해도 되나요?

Pushgateway는 배치 작업(Batch Job)처럼 즉시 종료되는 프로세스의 메트릭 수집을 위해 설계되었습니다. 상시 실행되는 게임 서버에 Pushgateway를 적용하면 단일 장애점(SPOF)이 되고 오래된 메트릭이 지워지지 않는 문제가 발생하므로 표준 Pull 방식을 직접 유지하는 것이 바람직합니다.


마치며: 관측 가능성으로 지키는 플레이어 경험

모니터링의 진정한 가치는 장애가 터진 뒤 빠르게 파악하는 것이 아니라 임계치에 도달하기 전 선제적으로 위험을 방어하는 데 있습니다. Prometheus의 Gauge와 Counter를 목적에 맞게 활용하고 날카로운 PromQL 쿼리를 구성하여 어떤 트래픽 급증에도 유저 경험을 온전히 지켜내는 탄탄한 인프라를 운영하시길 바랍니다.

#게임서버#Prometheus#Grafana#CCU#TPS#서버모니터링#DevOps

계속 읽어보기

이런 글은 어떠세요?

< Back to Logs