Redis Vector Search로 구현하는 유사 플레이어 성향 AI 매치메이킹

Redis Vector Search로 구현하는 유사 플레이어 성향 AI 매치메이킹

Redis Vector Search와 HNSW 인덱스로 플레이어 행동 벡터를 실시간 검색해 실력 점수만으로 놓치던 플레이 스타일·협동 성향·이탈 위험을 반영하는 AI 매치메이킹 시스템의 설계와 구현 방법을 설명합니다.

TL;DR

Redis Vector Search는 플레이어의 최근 행동과 성향을 FLOAT32 벡터로 저장하고 코사인 거리 기반 최근접 이웃 검색으로 비슷한 플레이어를 빠르게 찾는 기능이다. MMR이나 Elo가 실력 균형을 담당한다면 벡터 검색은 공격성, 협동성, 목표 지향성, 이탈 위험 같은 플레이 경험의 균형을 보완한다. 실전에서는 후보를 벡터 검색으로 넓게 찾은 뒤 리전·핑·파티·MMR·큐 대기 시간 규칙으로 다시 거르는 2단계 구조가 안전하다.

왜 MMR만으로는 좋은 게임 매칭이 어려운가?

MMR은 승패 결과를 기반으로 실력 차이를 줄이는 데 효과적이다. 하지만 같은 MMR이라도 플레이 경험은 크게 다를 수 있다. 예를 들어 한 플레이어는 팀과 함께 오브젝트를 공략하고 다른 플레이어는 단독 교전을 반복할 수 있다. 두 사람의 실력이 비슷하다는 사실만으로는 함께 플레이할 때 만족도가 높다고 말하기 어렵다.

매치메이킹에서 분리해 다룰 문제는 다음과 같다.

대표 데이터기존 점수만 사용할 때의 한계
실력MMR, 승률, K/D실력 격차는 줄일 수 있다.
플레이 성향교전 빈도, 오브젝트 참여, 힐·지원 비율팀 조합과 선호 모드를 반영하기 어렵다.
운영 조건리전, RTT, 파티, 플랫폼게임 품질과 공정성을 보장해야 한다.
세션 위험최근 이탈, 재접속, 신고 상태악의적 사용자를 성향 유사도로 묶으면 안 된다.

따라서 벡터 검색은 MMR의 대체재가 아니라 후보 생성 단계의 보조 신호로 사용해야 한다.

Redis Vector Search 기반 매치메이킹 아키텍처는 어떻게 구성할까?

권장 구조는 행동 이벤트를 수집해 주기적으로 성향 벡터를 계산하고 Redis의 HNSW 인덱스에서 근접 후보를 찾은 뒤 게임 서버가 최종 규칙을 적용하는 방식이다.

flowchart LR
  A[게임 이벤트] --> B[성향 피처 집계]
  B --> C[플레이어 벡터 생성]
  C --> D[(Redis Hash + Vector Index)]
  E[매치 요청] --> F[KNN 후보 검색]
  D --> F
  F --> G[MMR·리전·핑·파티 필터]
  G --> H[팀 조합 점수화]
  H --> I[매치 확정]

벡터 검색 결과를 그대로 매치로 확정하면 안 된다. Redis는 유사 후보를 빠르게 찾고 권위 있는 매치메이킹 서비스는 동시성 제어와 게임 규칙 검증을 담당해야 한다.

플레이어 행동 이벤트에서 성향 벡터를 만들고 Redis KNN 검색 뒤 규칙 필터링으로 매치를 확정하는 서버 아키텍처

Step 1. 플레이어 성향 벡터를 설계한다

처음부터 대규모 임베딩 모델을 도입할 필요는 없다. 게임 로그에서 설명 가능한 수치형 피처를 만들고 정규화한 16~64차원 벡터로 시작하면 검증과 운영이 쉽다.

다음은 32차원 벡터의 예시다.

피처 그룹예시 차원계산 예시
전투 성향8분당 교전 수, 선공 비율, 평균 교전 거리
팀 기여8어시스트 비율, 힐량, 아군 근접 시간
목표 수행6오브젝트 참여율, 점령 기여 시간
운영 성향5탐색 시간, 자원 획득 우선도, 생존 시간
세션 품질5최근 이탈률, 평균 RTT, 신고 신호

각 피처는 최근 20경기 또는 최근 14일처럼 고정된 관측 창에서 계산한다. 분포가 긴 지표는 로그 변환이나 분위수 정규화를 적용하고 결측치는 게임 전체 평균이 아닌 해당 모드·티어·리전의 중앙값으로 채우는 편이 안전하다.

코사인 유사도를 사용할 경우 벡터는 L2 정규화한다.

v^=vi=1dvi2+ϵ\hat{v}=\frac{v}{\sqrt{\sum_{i=1}^{d}v_i^2}+\epsilon}

두 플레이어 벡터의 코사인 유사도는 다음과 같다. 정규화된 벡터라면 내적만으로 비교할 수 있다.

cosineSimilarity(a,b)=aba2b2\operatorname{cosineSimilarity}(a,b)=\frac{a\cdot b}{\lVert a\rVert_2\lVert b\rVert_2}

leave_rate_14d처럼 제재나 이탈과 연관된 신호는 유사도 벡터에 직접 크게 반영하지 않는 것이 좋다. 해당 값은 최종 필터 또는 별도의 위험 점수로 다뤄 정상 사용자가 불필요하게 배제되는 일을 줄인다.

Step 2. Redis HNSW 벡터 인덱스를 만든다

Redis Query Engine이 포함된 Redis 배포 환경에서 Hash 문서와 VECTOR 필드를 사용한다. 아래 예시는 32차원 FLOAT32 벡터와 코사인 거리를 위한 HNSW 인덱스다.

FT.CREATE idx:player-profile ON HASH PREFIX 1 player: SCHEMA \
  region TAG \
  mode TAG \
  mmr NUMERIC \
  updated_at NUMERIC SORTABLE \
  behavior_vec VECTOR HNSW 6 \
    TYPE FLOAT32 \
    DIM 32 \
    DISTANCE_METRIC COSINE \
    M 16 \
    EF_CONSTRUCTION 200

HNSW는 메모리를 사용해 근사 최근접 이웃을 빠르게 찾는다. 일반적인 초기값은 M 16, EF_CONSTRUCTION 200이며 실제 값은 벡터 수와 지연 시간 목표에 따라 부하 테스트로 결정해야 한다.

선택지장점적합한 상황
FLAT정확한 최근접 검색, 설정이 단순함수만 건 이하의 후보군 또는 품질 기준선 측정
HNSW대규모 검색에서 낮은 지연 시간동시 매치 요청이 많고 후보 풀이 큰 서비스

벡터는 JSON 배열이 아니라 리틀엔디언 FLOAT32 바이너리로 저장한다. Node.js의 Buffer를 이용하면 변환할 수 있다.

function toFloat32Buffer(values: number[]): Buffer {
  const vector = new Float32Array(values);
  return Buffer.from(vector.buffer);
}

await redis.hSet(`player:${playerId}`, {
  region: "ap-northeast-2",
  mode: "ranked",
  mmr: String(mmr),
  updated_at: String(Date.now()),
  behavior_vec: toFloat32Buffer(profileVector),
});

프로필 갱신은 매 경기 종료 직후보다 비동기 집계 작업에서 수행하는 편이 좋다. 매치 요청의 핫패스에서 피처 계산이나 모델 추론을 실행하면 대기열 지연이 커진다.

Step 3. KNN 검색 뒤 게임 규칙으로 후보를 좁힌다

Redis KNN 검색에는 동일 모드와 리전 조건을 먼저 적용하고 충분히 넓은 후보를 가져온다. 아래 쿼리는 같은 모드·리전에서 가장 가까운 50명을 찾는다.

const query = "@mode:{ranked} @region:{ap-northeast-2}=>[KNN 50 @behavior_vec $vec AS vector_distance]";

const result = await redis.sendCommand([
  "FT.SEARCH",
  "idx:player-profile",
  query,
  "PARAMS", "2", "vec", toFloat32Buffer(requesterVector),
  "SORTBY", "vector_distance",
  "RETURN", "5", "region", "mode", "mmr", "updated_at", "vector_distance",
  "DIALECT", "2",
]);

이후 애플리케이션에서 다음 순서로 검증한다.

  1. 요청자 본인, 이미 매칭된 사용자, 차단 관계, 제재 대상, 오래된 프로필을 제거한다.
  2. MMR 허용 범위와 RTT 상한을 적용한다. 대기 시간이 길어질수록 허용 MMR 범위를 점진적으로 넓힌다.
  3. 남은 후보로 팀을 구성하고 실력 분산, 역할 분포, 벡터 유사도 또는 보완성 점수를 함께 최적화한다.

최종 점수의 한 예시는 다음과 같다.

S=0.45Sskill+0.25Sbehavior+0.20Snetwork+0.10SqueueS=0.45S_{skill}+0.25S_{behavior}+0.20S_{network}+0.10S_{queue}

여기서 S_skill은 팀 간 MMR 차이, S_behavior는 성향 유사도 또는 역할 보완성, S_network는 예상 RTT, S_queue는 오래 기다린 플레이어에 대한 보정이다. 계수는 고정된 정답이 아니므로 매치 완료율, 재큐율, 경기 중 이탈률, 경기 후 만족도 같은 지표로 실험해야 한다.

유사 플레이어만 묶으면 왜 문제가 되는가?

모든 성향을 유사하게 만들면 단조로운 팀 구성이 될 수 있다. 예를 들어 지원 성향이 높은 플레이어만 한 팀에 모이거나 공격 성향이 높은 플레이어가 한 팀에 과도하게 쏠릴 수 있다.

따라서 게임 장르에 따라 목표를 구분한다.

게임 목표벡터 활용 방식
협동 PvE의사소통·목표 수행 성향이 가까운 후보를 우선한다.
경쟁 PvP팀 내부 역할은 보완하고 양 팀의 총점은 균형 있게 맞춘다.
캐주얼 파티친구 파티와 모드 선호도를 강한 제약 조건으로 둔다.

성향 유사도는 후보 검색용이며 팀 조합의 정답을 단독으로 결정하는 점수가 아니다.

운영에서 확인할 지표와 보안 원칙

매치 품질은 벡터 거리만으로 평가할 수 없다. 대조군을 둔 A/B 테스트에서 아래 지표를 같은 리전과 모드 단위로 비교한다.

  • 매칭 중앙 대기 시간과 p95 대기 시간
  • 매치 수락률, 재큐율, 경기 중 이탈률
  • 팀 간 MMR 차이와 경기 결과의 편향
  • 경기 종료 후 재플레이율 또는 간단한 만족도 설문
  • Redis 검색 p95 지연 시간, 인덱스 메모리 사용량, 벡터 갱신 실패율

개인정보 보호도 중요하다. 채팅 원문이나 음성 원본을 벡터에 저장하지 말고 서비스 목적에 필요한 집계 피처만 사용한다. 플레이어 프로필 키의 접근 권한을 매치메이킹 서비스로 제한하고 원시 이벤트와 파생 벡터의 보존 기간을 분리한다. 클라이언트가 벡터나 MMR을 직접 제출하도록 설계해서도 안 된다. 모든 프로필은 서버가 검증한 이벤트에서 생성해야 한다.

자주 묻는 질문 (FAQ)

Redis Vector Search가 MMR을 대체할 수 있나?

아니다. MMR은 공정한 실력 균형을 위한 핵심 제약이다. 벡터 검색은 플레이 성향과 팀 경험을 보완하는 후보 검색 신호로 사용하는 것이 적절하다.

HNSW 검색 결과는 항상 정확한가?

아니다. HNSW는 낮은 지연 시간을 위해 근사 최근접 이웃을 찾는다. 품질 기준선이 필요하면 같은 데이터에서 FLAT 검색 결과와 비교하고 EF_RUNTIME 및 인덱스 파라미터를 조정한다.

벡터는 얼마나 자주 갱신해야 하나?

대부분의 게임에서는 매 경기 종료 뒤 비동기 갱신으로 충분하다. 최근 성향 변화를 더 민감하게 반영해야 한다면 최근 5~20경기에 가중치를 두되 매치 요청 시점의 동기 갱신은 피한다.

정리

Redis Vector Search를 사용하면 수치화하기 어려웠던 플레이 성향을 낮은 지연 시간으로 후보 검색에 반영할 수 있다. 다만 좋은 매치메이킹의 핵심은 벡터 하나가 아니라 HNSW 후보 검색 위에 MMR·리전·RTT·파티·안전 정책을 명시적으로 쌓는 것이다. 작은 피처 벡터와 명확한 A/B 지표로 시작해 품질을 검증한 뒤 차원 수와 모델 복잡도를 늘리는 순서가 가장 운영하기 쉽다.

#Redis Vector Search#AI 매치메이킹#게임 서버#HNSW#벡터 검색#멀티플레이#Redis

계속 읽어보기

이런 글은 어떠세요?

< Back to Logs