At a Glance
게임 서버 문제는 "느리다"는 로그 한 줄로 재현되지 않습니다. request·match·instance·player·tick·server build를 같은 correlation context로 연결하고, 로그는 사건의 이유, metric은 추세, trace는 backend와 game server 사이의 경로를 보여 주도록 역할을 나눕니다.
| 신호 | 먼저 넣을 값 | 답하는 질문 |
|---|---|---|
| structured log | matchId, instanceId, playerId, tick, event | 어느 규칙이 어떤 순서로 실행됐는가 |
| metric | tick duration, queue depth, snapshot bytes, reconnect rate | 언제부터 어떤 규모로 나빠졌는가 |
| trace | login -> ticket -> placement -> join 흐름 | 어디서 대기·실패했는가 |
| debug snapshot | rule version, seed, 관련 entity state | 같은 판정을 다시 확인할 수 있는가 |
client join 실패
-> admission log에서 grant·seat 전이 확인
-> match trace에서 ticket / placement 대기 구간 확인
-> instance metric에서 tick 지연·정원·queue depth 확인
-> 같은 match의 server build·protocol version·event 순서 대조상관관계 키를 처음부터 넣기
playerId만 로그에 남기면 한 player가 여러 match를 거친 뒤 어느 사건을 봐야 하는지 알기 어렵습니다. matchId, instanceId, sessionId 또는 안전한 session generation, ticket ID, server build, protocol version, current tick을 함께 넣습니다. raw join token, authorization header, 채팅 원문처럼 민감한 값은 log에 넣지 않습니다.
한 로그 event에는 결과와 원인을 함께 남깁니다. 예를 들어 join_rejected에는 reason=expired_grant, matchId, seatState를 넣고, snapshot_deferred에는 connectionId, budgetBytes, candidateCount를 넣습니다. 사람이 문장을 해석해야 하는 free-form log보다 field를 query할 수 있는 structured log가 장애 중 훨씬 유용합니다.
trace는 client packet 하나마다 무조건 만들면 비용이 커질 수 있습니다. login, matchmaking, placement, join, reward persistence처럼 backend 경계를 넘는 작업을 중심으로 trace를 만들고, battle tick은 aggregated metric과 sampled debug event로 봅니다. OpenTelemetry의 log·metric·trace 구분을 따르되, match 단위 context가 빠지지 않게 설계합니다.
서버 건강을 보는 metric
평균 tick duration만으로는 부족합니다. p95·p99 tick duration, tick overrun 수, command queue depth, connection 수, packet loss·retransmit 추정, snapshot byte와 drop·defer 수를 함께 봅니다. 평균이 정상이어도 특정 crowded instance의 한 tick이 오래 걸리면 그 match의 조작감과 판정이 무너질 수 있습니다.
매치메이킹은 queue wait time, 취소·timeout 비율, placement 실패 사유, region별 latency를 봅니다. 입장은 grant 만료·정원 초과·중복 연결 수를, persistence는 commit latency·중복 operation conflict·recovery 처리 수를 봅니다. 수치가 올라갔다는 사실만이 아니라 어떤 rule version·server build·map에서 시작했는지 낮은 cardinality label로 연결합니다. playerId, matchId, connectionId처럼 값 종류가 계속 늘어나는 ID는 metric label이 아니라 log·trace context에 둡니다.
재현 가능한 debug 기록
desync나 hit 판정 이슈를 확인하려면 해당 시점의 player input 전체를 영구 보관할 필요는 없습니다. 신고·오류가 발생한 짧은 tick window에 한해 command sequence, authoritative snapshot hash, RNG seed, relevant entity state, protocol version을 제한적으로 남기면 원인 추적에 도움이 됩니다. 보존 기간과 접근 권한을 정하고, 개인 정보와 비밀 token은 제외합니다.
관측성 데이터도 server rule입니다. event 이름, field type, reason code, sampling 비율을 versioned contract로 관리하지 않으면 dashboard·alert·recovery worker가 서로 다른 의미의 값을 읽습니다. client UX에서 보이는 오류 code와 server metric의 reason code를 연결하면 support와 운영이 같은 사건을 볼 수 있습니다.
오류가 난 뒤에만 로그를 추가하면 재현에 필요한 tick·version·state가 이미 사라집니다. 모든 packet을 저장하는 대신, 핵심 상태 전이와 느린 경로를 식별할 수 있는 작은 공통 context를 평소부터 남기세요.
참고 링크
2 sources