Spanner를 게임 데이터베이스로 사용 시 권장사항

이 문서에서는 게임 상태 스토리지의 기본 백엔드 데이터베이스로 Spanner를 사용하기 위한 권장사항을 설명합니다. 일반적인 데이터베이스 대신 Spanner를 사용하여 플레이어 인증 데이터와 인벤토리 데이터를 저장할 수 있습니다. 이 문서는 장기적인 상태 스토리지에서 작업하는 게임 백엔드 엔지니어와 그러한 시스템을 지원하며 Google Cloud에서 백엔드 데이터베이스를 호스팅하려는 게임 인프라 운영자 및 관리자를 대상으로 합니다.

멀티플레이어 및 온라인 게임은 플레이어 자격과 상태, 인벤토리 데이터를 추적하기 위해 점점 복잡해지는 데이터베이스 구조가 필요하도록 발전을 거듭해 왔습니다. 플레이어가 증가하고 게임이 복잡해짐에 따라 데이터베이스 솔루션을 확장하고 관리하기가 어려워져서 샤딩 또는 클러스터링을 자주 사용해야 합니다. 중요한 게임 아이템이나 플레이어의 게임 진행 과정을 추적하려면 일반적으로 트랜잭션이 필요하며 많은 유형의 분산 데이터베이스를 통해 이 문제를 해결해야 합니다.

Spanner는 클라우드용으로 설계된 strong consistency를 갖춘 전 세계에 분산된 최초의 확장 가능한 엔터프라이즈급 데이터베이스 서비스로서 관계형 데이터베이스 구조와 비관계형 수평적 확장의 이점을 결합했습니다. 프로덕션급 시스템에서 게임 상태 데이터베이스와 인증 데이터베이스를 모두 교체하기에 Spanner가 적합하다는 게임 회사가 많이 있습니다. Google Cloud Console을 사용하여 노드를 추가함으로써 성능 또는 스토리지를 추가적으로 확장할 수 있습니다. Spanner는 strong consistency로 전역 복제를 투명하게 처리할 수 있으므로 리전 복제본을 관리할 필요가 없습니다.

이 권장사항 문서에서는 다음 내용에 대해 설명합니다.

  • 중요한 Spanner의 개념과 게임에서 일반적으로 사용되는 데이터베이스와의 차이점
  • Spanner가 게임용 데이터베이스로 적합한 경우
  • 게임에 Spanner를 사용할 때 피해야 할 패턴
  • 게임 데이터베이스로 Spanner를 사용하여 데이터베이스 작업 설계
  • Spanner를 사용하여 최상의 성능을 실현하기 위한 데이터 모델링 및 스키마 생성

용어

자격
플레이어에 속한 게임, 확장 또는 인앱 구매입니다.
PII(개인 식별 정보)
게임에서 일반적으로 신용카드 번호와 청구서 수신 주소와 같은 결제 계정 정보와 이메일 주소를 포함하는 정보입니다. 일부 지역에서는 이 정보에 주민등록번호가 포함될 수 있습니다.
게임 데이터베이스(게임 DB)
플레이어의 게임 진행 과정과 게임 인벤토리를 저장하는 데이터베이스입니다.
인증 데이터베이스(인증 DB)
플레이어 자격과 플레이어가 구매할 때 사용하는 개인 식별 정보를 포함하는 데이터베이스입니다. 인증 DB는 계정 DB 또는 플레이어 DB라고도 합니다. 이 데이터베이스는 게임 DB에 통합되기도 하지만 여러 타이틀을 보유한 스튜디오 또는 게시자에서는 자주 분리해서 사용됩니다.
거래
데이터베이스 트랜잭션으로, 효과가 극단적으로 나타나는 쓰기 작업의 집합입니다. 즉, 트랜잭션이 성공하여 모든 업데이트가 적용되거나 데이터베이스가 트랜잭션의 업데이트를 전혀 포함하지 않는 상태로 돌아갑니다. 게임에서 데이터베이스 트랜잭션은 결제를 처리할 때와 중요한 게임 인벤토리 또는 통화의 소유권을 할당할 때 가장 중요합니다.
관계형 데이터베이스 관리 시스템(RDBMS)
서로 참조하는 테이블과 행에 기반한 데이터베이스 시스템입니다. SQL Server, MySQL, Oracle®(드물게 사용)이 게임에 사용되는 관계형 데이터베이스의 예입니다. 이는 익숙한 방법을 제공하고 트랜잭션을 완벽하게 보장하므로 자주 사용됩니다.
NoSQL 데이터베이스(NoSQL DB)
관계형으로 구조화되지 않은 데이터베이스입니다. 이 데이터베이스는 데이터 모델이 변경될 때 유연성이 강력하므로 게임에서의 사용이 늘어나고 있습니다. NoSQL 데이터베이스에는 MongoDB 및 Cassandra가 포함됩니다.
기본 키
일반적으로 인벤토리 아이템, 플레이어 계정, 구매 트랜잭션에 대한 고유 ID가 포함된 열입니다.
인스턴스
단일 데이터베이스입니다. 예를 들어 클러스터는 데이터베이스 소프트웨어의 여러 사본을 실행하지만 게임 백엔드에 단일 인스턴스로 나타납니다.
노드
이 문서에서는 데이터베이스 소프트웨어의 사본을 실행 중인 단일 머신입니다.
복제본
데이터베이스의 두 번째 사본입니다. 복제본은 데이터를 복구하고 가용성을 높이거나 읽기 처리량을 늘리는 데 자주 사용됩니다.
클러스터
많은 머신에서 실행 중인 소프트웨어의 여러 사본으로 게임 백엔드에 단일 인스턴스로 나타납니다. 클러스터링은 확장성 및 가용성을 위해 사용됩니다.
분할
데이터베이스의 인스턴스입니다. 많은 게임 스튜디오는 여러 동종 데이터베이스 인스턴스를 실행하는데, 각 인스턴스에는 게임 데이터의 하위 집합이 있습니다. 이러한 인스턴스 각각을 보통 샤드라고 합니다. 샤딩은 일반적으로 성능 또는 확장성을 위해 수행되므로 관리 효율성을 저하시키는 동시에 앱 복잡성을 증가시킵니다. Spanner에서의 샤딩은 분할을 사용하여 구현됩니다.
분할
Spanner는 데이터를 분할이라고 칭하는 청크로 나누며, 이때 개별 분할은 서로 독립적으로 이동할 수 있으며 다른 서버에 할당될 수 있습니다. 분할이란 기본 키에 따라 행의 순서가 정해지는 최상위 수준(즉, 인터리브 처리되지 않은) 테이블의 행의 범위로 정의됩니다. 이 범위의 시작 키와 종료 키를 '분할 경계'라고 합니다. Spanner는 분할 경계를 자동으로 추가 및 삭제하고 이에 따라 데이터베이스 내 분할 수가 바뀝니다. Spanner는 부하에 따라 데이터를 분할합니다. 즉, 한 분할 내의 여러 키 사이에 읽기 또는 쓰기 부하가 많이 분산된 것을 감지하면 자동으로 분할 경계를 추가합니다.
핫스팟
Spanner와 같은 분산 데이터베이스의 단일 분할에 데이터베이스로 이동하는 모든 쿼리의 상당 부분을 수신하는 레코드가 포함되는 경우입니다. 이 시나리오는 성능을 저하시키므로 바람직하지 않습니다.

게임에 Spanner 사용

게임에 RDBMS 사용을 고려하는 대부분의 경우 Spanner는 게임 DB, 인증 DB 또는 둘 모두를 효과적으로 대체할 수 있으므로 적합한 선택입니다.

게임 DB

Spanner는 전 세계에서 단일 트랜잭션 기관으로 운영될 수 있으므로 게임 인벤토리 시스템에 적합합니다. 플레이어 간에 거래하거나 판매, 선물, 전달할 수 있는 모든 게임 통화 또는 아이템은 대규모 게임 백엔드에서 당면 과제를 나타냅니다. 종종 게임의 인기는 단일 노드 데이터베이스에서 모든 것을 처리하는 기존 데이터베이스의 기능 이상을 요구할 수 있습니다. 게임의 유형에 따라 데이터베이스는 플레이어 로드와 저장된 데이터의 양을 처리하는 데 필요한 작업 수로 인해 어려움을 겪을 수 있습니다. 이로 인해 게임 개발자는 추가 성능을 위해 데이터베이스를 샤딩하거나 끊임없이 증가하는 테이블을 저장하게 됩니다. 이러한 유형의 솔루션은 운영상의 복잡성과 유지보수 오버헤드 증가를 초래합니다.

이러한 복잡성을 줄이는 일반적인 전략 중 하나는 데이터가 서로 이동할 수 없는 완전히 분리된 게임 리전을 실행하는 것입니다. 이 경우 각 리전의 인벤토리가 별도의 데이터베이스로 분리되므로 다른 게임 리전의 플레이어 간에 아이템과 통화를 거래할 수 없습니다. 하지만 이 설정에서는 개발자 및 운영 단순성을 위해 선호하는 플레이어 환경이 저하될 수 있습니다.

반면 지리적으로 샤딩된 데이터베이스에서 리전 간 거래를 허용할 수 있지만 복잡성에 따른 비용이 증가할 수 있습니다. 이 설정에서는 트랜잭션이 여러 데이터베이스 인스턴스에 걸쳐 분산되어야 하므로 애플리케이션 측 로직이 복잡하고 오류가 발생하기 쉬워집니다. 여러 데이터베이스에서 트랜잭션을 잠그려고 시도하면 성능에 상당한 영향을 미칠 수 있습니다. 또한 원자성 트랜잭션을 사용할 수 없게 되면 게임 통화 또는 아이템 중복과 같이 게임 생태계 및 커뮤니티에 피해가 되는 플레이어 악용으로 이어질 수 있습니다.

Spanner는 인벤토리 및 통화 트랜잭션에 대한 접근방식을 단순화할 수 있습니다. Spanner를 사용하여 전 세계 모든 게임 데이터를 보유하는 경우에도 Spanner는 기존의 원자성, 일관성, 격리성, 내구성(ACID) 속성보다 강력한 읽기-쓰기 트랜잭션을 제공합니다. Spanner의 확장성으로 인해 추가 성능 또는 스토리지가 필요할 때도 데이터를 별도의 데이터베이스 인스턴스로 샤딩할 필요가 없습니다. 대신 노드를 추가하기만 하면 됩니다. 또한 게임이 데이터베이스를 클러스터링하는 고가용성 및 데이터 복원력은 Spanner에서 투명하게 처리되므로 추가 설정이나 관리가 필요 없습니다.

인증 DB

인증 DB는 특히 스튜디오 또는 게시자 수준에서 단일 RDBMS로 표준화하려는 경우 Spanner를 제대로 활용할 수 있습니다. 게임용 인증 DB에는 Spanner의 규모가 필요하지 않은 경우도 많지만 트랜잭션 보장과 높은 데이터 가용성으로 인해 Spanner가 유용할 수 있습니다. Spanner에서 데이터 복제는 투명하고 동기식이며 내장되어 있습니다. Spanner의 구성은 99.99% 또는 99.999%의 가용성을 제공합니다. 99.999%는 사용할 수 없는 시간이 연간 5.5분 미만에 해당합니다. 이러한 유형의 가용성은 모든 플레이어 세션을 시작할 때 필요한 중요한 인증 경로에 적합합니다.

권장사항

이 섹션에서는 게임 설계에서 Spanner를 사용하는 방법에 대한 권장사항을 제공합니다. Spanner에서 제공하는 고유한 기능을 활용하려면 게임 데이터를 모델링하는 것이 중요합니다. 관계형 데이터베이스 시맨틱스를 사용하여 Spanner에 액세스할 수 있지만 일부 스키마 설계 포인트는 성능을 높이는 데 도움이 될 수 있습니다. Spanner 문서에는 검토할 수 있는 구체적인 스키마 설계 권장사항이 나와 있지만 다음 섹션은 게임 DB와 관련한 몇 가지 권장사항입니다.

이 문서의 권장사항은 고객 사용 및 우수사례를 기반으로 합니다.

UUID를 플레이어 및 캐릭터 ID로 사용

플레이어 테이블에는 일반적으로 각 플레이어에 대한 행과 게임 통화, 진행 상황, 개별 인벤토리 테이블 행에 손쉽게 매핑되지 않는 기타 데이터에 대한 행이 하나씩 있습니다. 게임 플레이어가 많은 대규모 영구 멀티플레이어 게임과 같이 게임에서 여러 캐릭터의 진행 상황을 개별적으로 저장할 수 있는 경우, 이 테이블에는 일반적으로 각 캐릭터에 대한 행이 대신 포함됩니다. 그 외에 패턴은 동일합니다.

전역 고유 캐릭터 또는 플레이어 ID(캐릭터 ID)를 캐릭터 테이블의 기본 키로 사용하는 것이 좋습니다. 범용 고유 식별자(UUID) v4를 사용하는 것도 좋습니다. 이는 플레이어 데이터를 DB 노드 전반에 분산시키고 Spanner에서 성능을 향상시키는 데 도움이 되기 때문입니다.

인벤토리 테이블에 인터리브 처리 사용

인벤토리 테이블에는 캐릭터 장비, 카드, 유닛과 같은 게임 아이콘이 있을 수 있습니다. 일반적으로 한 플레이어는 인벤토리에 많은 아이템을 가지고 있습니다. 각 아이템은 테이블에서 한 행으로 표시됩니다.

다른 관계형 데이터베이스와 유사하게, Spanner의 인벤토리 테이블에는 다음 테이블에 설명된 대로 아이템의 전역 고유 ID인 기본 키가 있습니다.

itemID type playerID
7c14887e-8d45 1 6f1ede3b-25e2
8ca83609-bb93 40 6f1ede3b-25e2
33fedada-3400 1 5fa0aa7d-16da
e4714487-075e 23 5fa0aa7d-16da
d4fbfb92-a8bd 14 5fa0aa7d-16da
31b7067b-42ec 3 26a38c2c-123a

예시 인벤토리 테이블에서 itemIDplayerID는 가독성을 위해 잘려 있습니다. 실제 인벤토리 테이블에는 예에 나와 있지 않은 다른 열도 많이 있습니다.

RDBMS에서 아이템 소유권을 추적하는 일반적인 접근방식은 현재 소유자의 플레이어 ID가 있는 열을 외래 키로 사용하는 것입니다. 이 열은 별도 데이터베이스 테이블의 기본 키입니다. Spanner에서 향상된 성능을 위해 관련 플레이어 테이블 행 근처에 인벤토리 행을 저장하는 인터리브 처리를 사용할 수 있습니다. 인터리브 처리 테이블을 사용할 때는 다음 사항에 유의하세요.

  • 소유자가 없는 객체는 생성할 수 없습니다. 제한사항을 사전에 공지하면 게임 설계에서 소유자가 없는 객체를 피할 수 있습니다.

핫스팟을 피하기 위한 설계 색인 생성

많은 게임 개발자는 많은 인벤토리 필드에 색인을 구현하여 특정 쿼리를 최적화합니다. Spanner에서 해당 색인의 데이터로 행을 만들거나 업데이트하면 색인 생성된 열 수에 비례하는 추가 쓰기 부하가 발생합니다. 자주 사용되지 않는 색인을 제거하거나 데이터베이스 성능에 영향을 주지 않는 다른 방식으로 해당 색인을 구현하여 Spanner의 성능을 개선할 수 있습니다.

다음 예시에는 플레이어의 장기 최고점수 레코드에 대한 테이블이 있습니다.

CREATE TABLE Ranking (
        PlayerID STRING(36) NOT NULL,
        GameMode INT64 NOT NULL,
        Score INT64 NOT NULL
) PRIMARY KEY (PlayerID, GameMode)

이 테이블에는 플레이어 ID(UUIDv4), 게임 모드, 단계, 시즌을 나타내는 번호, 플레이어 점수가 포함됩니다.

게임 모드를 필터링하는 쿼리의 속도를 높이려면 다음 색인을 고려하세요.

CREATE INDEX idx_score_ranking ON Ranking (
        GameMode,
        Score DESC
)

모든 플레이어가 1이라는 동일한 게임 모드를 플레이하는 경우 이 색인은 GameMode=1일 때 핫스팟을 만듭니다. 이 게임 모드의 순위를 가져오려는 경우 색인이 GameMode=1이 포함된 행만 검색하여 신속하게 순위를 반환합니다.

이전 색인의 순서를 변경하는 경우 이 핫스팟 문제를 해결할 수 있습니다.

CREATE INDEX idx_score_ranking ON Ranking (
        Score DESC,
        GameMode
)

이 색인은 점수가 가능한 범위에서 분산되어 있으면 동일한 게임 모드에서 경쟁하는 플레이어로부터 중요한 핫스팟을 만들지 않습니다. 하지만 이전 색인에서는 쿼리가 GameMode=1인지 여부를 확인하기 위해 모든 모드의 모든 점수를 검색하므로 점수를 빠르게 가져올 수 없습니다.

결과적으로 순서가 변경된 색인이 게임 모드에서 이전 핫스팟을 해결하지만 다음 설계에 설명된 대로 여전히 개선의 여지가 있습니다.

CREATE TABLE GameMode1Ranking (
        PlayerID STRING(36) NOT NULL,
        Score INT64 NOT NULL
) PRIMARY KEY (PlayerID)

CREATE INDEX idx_score_ranking ON Ranking (
        Score DESC
)

테이블 스키마에서 게임 모드를 이전하고 가능하면 모드당 하나의 테이블을 사용하는 것이 좋습니다. 이 방법을 사용하면 모드의 점수를 검색할 때 해당 모드의 점수가 있는 테이블만 쿼리합니다. 이 테이블은 핫스팟의 심각한 위험 없이 점수 범위를 신속하게 검색하기 위해 점수별로 색인을 생성할 수 있습니다(점수가 잘 분산된 경우). 이 문서를 작성할 시점 기준으로 Spanner에서 데이터베이스당 최대 테이블 수는 2,560개로 대부분의 게임에 충분합니다.

테넌트당 별도의 데이터베이스 사용

게임 데이터에 대해 다른 기본 키 값을 사용하여 Spanner에서 멀티테넌시를 설계하는 것이 좋은 다른 워크로드와 달리, 테넌트당 별도의 데이터베이스를 사용하는 보다 편리한 방법을 사용하는 것이 좋습니다. 스키마 변경은 실시간 서비스 게임에서 새로운 게임 기능을 출시할 때 일반적이며, 데이터베이스 수준에서 테넌트 격리는 스키마 업데이트를 단순화할 수 있습니다. 이러한 작업은 전체 데이터베이스에서 한번에 수행되므로 이 전략은 테넌트의 데이터를 백업하거나 복원하는 데 걸리는 시간도 최적화할 수 있습니다.

증분 스키마 업데이트 방지

기존에 사용되던 일부 관계형 데이터베이스와 달리 Spanner는 스키마 업데이트 중에서도 계속 작동합니다. 이전 스키마에 대한 모든 쿼리가 반환되고(평소보다 늦게 반환될 수 있음), 새로운 스키마에 대한 쿼리는 사용할 수 있을 때 반환됩니다. 이전 제약조건을 염두에 두면 Spanner에서 실행될 때 스키마 업데이트 중에도 게임이 계속 실행되도록 업데이트 프로세스를 설계할 수 있습니다.

하지만 처리 중인 스키마가 있는 동안 또 다른 스키마 변경을 요청하는 경우 새로운 업데이트가 큐에 추가되고 모든 이전 스키마 업데이트가 완료될 때까지 진행되지 않습니다. 이러한 상황은 짧은 기간에 많은 증분 스키마 업데이트를 실행하지 않고 대규모 스키마 업데이트를 계획하면 방지할 수 있습니다. 데이터 검증이 필요한 스키마 업데이트를 수행하는 방법 등 스키마 업데이트에 대한 자세한 내용은 Spanner 스키마 업데이트 문서를 참조하세요.

데이터베이스 액세스 및 크기 고려

Spanner를 사용하기 위해 게임 서버 및 플랫폼 서비스를 개발할 때 불필요한 비용이 발생하지 않도록 하기 위해 게임에서 데이터베이스에 액세스하는 방법과 데이터베이스의 크기를 조정하는 방법을 고려하세요.

기본 제공되는 드라이버 및 라이브러리 사용

Spanner로 개발할 때 코드가 데이터베이스와 어떻게 상호작용하는지 고려하세요. Spanner는 많은 인기 언어에 대해 기본 클라이언트 라이브러리를 제공합니다. 이 라이브러리는 일반적으로 기능이 다양하고 성능이 뛰어납니다. DML 문과 데이터 정의 언어(DDL) 문을 지원하는 JDBC 드라이버도 사용할 수 있습니다. Spanner가 신규 개발에 사용되는 경우 Spanner용 Cloud 클라이언트 라이브러리를 사용하는 것이 좋습니다. 일반적인 게임 엔진 통합의 경우 언어 선택에서 유연성이 없는 편이지만, Spanner에 액세스하는 플랫폼 서비스의 경우 게임 고객이 자바 또는 Go를 사용하는 경우가 있습니다. 처리량이 높은 애플리케이션의 경우 여러 순차 요청에 동일한 Spanner 클라이언트를 사용할 수 있는 라이브러리를 선택하세요.

테스트 및 프로덕션 니즈에 맞게 데이터베이스 크기 조정

개발 중에는 단일 노드 Spanner 인스턴스가 기능 테스트와 같은 대부분의 활동에 충분할 수 있습니다.

프로덕션의 Spanner 니즈 평가

개발에서 테스트로 전환한 다음 프로덕션으로 전환할 때 게임이 실시간 플레이어 트래픽을 처리할 수 있도록 Spanner 니즈를 다시 평가하는 것이 중요합니다.

프로덕션으로 전환하기 전에 부하 테스트를 통해 백엔드가 프로덕션 중에 부하를 처리할 수 있는지 확인해야 합니다. 사용량 급증과 게임이 예상보다 인기가 높은 경우에 대비하기 위해 프로덕션을 제외하고는 부하를 두 배로 늘려 부하 테스트를 실행하는 것이 좋습니다.

실제 데이터를 사용하여 부하 테스트 실행

합성 데이터를 사용하여 부하 테스트를 실행하는 것으로는 충분하지 않습니다. 또한 프로덕션에서 예상되는 것과 최대한 가까운 데이터와 액세스 패턴을 사용하여 부하 테스트를 실행해야 합니다. 합성 데이터는 Spanner 스키마 설계에서 잠재적인 핫스팟을 감지하지 못할 수 있습니다. Spanner가 실제 데이터를 사용할 때 어떻게 작동하는지 확인하기 위해 실제 플레이어를 사용하여 베타 테스트(공개 또는 비공개)를 실행하는 것이 가장 좋습니다.

다음 다이어그램은 부하 테스트에 베타 테스트를 사용하는 것이 왜 중요한지 보여주는 게임 스튜디오의 예시 플레이어 테이블 스키마입니다.

부하 테스트용 플레이어 이름과 속성 목록

스튜디오는 2년 동안 운영해 온 이전 게임의 트렌드에 따라 이 데이터를 준비했습니다. 회사는 스키마가 새 게임의 데이터를 잘 나타낼 것이라고 예상했습니다.

각 플레이어 레코드에는 게임에서 플레이어의 진행 상황(순위, 플레이 시간 등)을 추적하는 숫자 속성이 연결되어 있습니다. 앞의 테이블에 사용된 예시 속성의 경우 새 플레이어에게 시작 값으로 50이 주어지며 플레이어가 게임을 진행하면 이 값이 1에서 100 사이의 값으로 변경됩니다.

스튜디오에서는 게임플레이 중에 중요한 쿼리의 속도를 높이기 위해 이 속성에 색인을 생성하려고 합니다.

이 데이터를 기반으로 스튜디오는 PlayerIDAttribute에 대한 보조 색인을 사용하여 기본 키로 아래 Spanner 테이블을 만들었습니다.

CREATE TABLE Player (
        PlayerID STRING(36) NOT NULL,
        Attribute INT64 NOT NULL
) PRIMARY KEY (PlayerID)

CREATE INDEX idx_attribute ON Player(Attribute)

그리고 다음과 같이 Attribute=23이 있는 플레이어 열 명을 검색하기 위해 색인을 쿼리했습니다.

SELECT PlayerID
        FROM Player@{force_index=idx_attribute}
        WHERE Attribute = 23
        LIMIT 10

스키마 설계 최적화 문서에 따르면 Spanner는 색인 항목별로 하나의 행을 사용하여 테이블과 동일한 방식으로 색인 데이터를 저장합니다. 아래 다이어그램과 같이 부하 테스트에서 이 모델은 보조 색인 읽기 및 쓰기 부하를 여러 Spanner 분할에 분산시키는 허용 가능한 작업을 수행합니다.

Spanner 분할 전체에서 속성별로 분산된 플레이어

부하 테스트에 사용되는 합성 데이터는 Attribute 값이 잘 분산된 게임의 안정적인 최종 상태와 유사하지만 게임 설계는 모든 플레이어가 Attribute=50으로 시작하도록 지시합니다. 각 새 플레이어가 Attribute=50으로 시작하므로 새 플레이어가 가입하면 보조 색인 idx_attribute의 동일한 부분에 삽입됩니다. 즉, 동일한 Spanner 분할로 업데이트가 라우팅되어 게임 출시 기간 동안 핫스팟이 발생합니다. 이는 Spanner를 비효율적으로 사용하는 것입니다.

출시 시 단일 Spanner 분할로 핫스팟을 만드는 동일한 속성을 가진 플레이어들

아래 다이어그램에서 출시 후 스키마에 IndexPartition 열을 추가하면 핫스팟 문제가 해결되며 플레이어가 이용 가능한 Spanner 분할 전반에 고르게 분산됩니다. 테이블 및 색인을 만들기 위해 업데이트된 명령어는 다음과 같습니다.

CREATE TABLE Player (
        PlayerID STRING(36) NOT NULL,
        IndexPartition INT64 NOT NULL
        Attribute INT64 NOT NULL
) PRIMARY KEY (PlayerID)

CREATE INDEX idx_attribute ON Player(IndexPartition,Attribute)

스키마에 IndexPartition 열을 추가하여 출시 시 플레이어를 고르게 분산

효율적인 쿼리를 위해서는 IndexPartition 값의 범위를 제한해야 하지만 효율적인 분산을 위해서는 이 값의 범위가 분할 수의 최소 두 배 이상이어야 합니다.

이 예시의 경우 스튜디오는 게임 애플리케이션에서 1부터 6까지 모든 플레이어에게 수동으로 IndexPartition을 할당했습니다.

다른 방법은 각 플레이어에게 무작위로 숫자를 할당하거나 PlayerID 값의 해시에서 도출한 값을 할당하는 것입니다. 애플리케이션 수준 샤딩 전략에 대한 자세한 내용은 Spanner에 대해 DBA가 알아야 하는 것-파트1: 키와 색인을 참조하세요.

이 개선된 색인을 사용하도록 이전 쿼리를 업데이트하면 다음과 같습니다.

SELECT PlayerID
        FROM Player@{force_index=idx_attribute}
        WHERE IndexPartition BETWEEN 1 and 6
        AND Attribute = 23
        LIMIT 10

베타 테스트를 실행하지 않았으므로 스튜디오는 잘못된 가정의 데이터를 사용하여 테스트를 수행한다는 사실을 인식하지 못했습니다. 합성 부하 테스트는 인스턴스에서 처리할 수 있는 초당 쿼리 수(QPS)를 검증하는 효과적인 방법이지만, 스키마를 검증하고 성공적인 실행을 준비하려면 실제 플레이어를 통한 베타 테스트가 필요합니다.

최대 수요를 예측하여 프로덕션 환경 크기 조정

주요 게임은 실행 시 트래픽이 급증할 수 있습니다. 확장 가능한 백엔드 빌드는 플랫폼 서비스와 전용 게임 서버뿐만 아니라 데이터베이스에도 적용됩니다. App Engine과 같은 Google Cloud 솔루션을 사용하면 신속하게 수직 확장할 수 있는 프런트엔드 API 서비스를 빌드할 수 있습니다. Spanner는 온라인으로 노드를 추가하거나 삭제할 수 있는 유연성이 있지만 자동 확장 데이터베이스는 아닙니다. 출시 시 트래픽 급증을 처리하기에 충분한 노드를 프로비저닝해야 합니다.

부하 테스트 중에 또는 공개 베타 테스트 중에 수집한 데이터를 기반으로 실행 시 요청을 처리하는 데 필요한 노드 수를 추정할 수 있습니다. 플레이어가 예상보다 많은 경우를 대비해 일부 노드를 버퍼로 추가하는 것이 좋습니다. 항상 평균 CPU 사용량이 65%를 초과하지 않도록 데이터베이스의 크기를 조정해야 합니다.

게임 출시 전에 데이터베이스 준비

게임을 출시하기 전에 Spanner 병렬화 기능을 활용할 수 있도록 데이터베이스를 준비하는 것이 좋습니다. 자세한 내용은 애플리케이션을 시작하기 전에 데이터베이스 준비를 참조하세요.

성능 모니터링 및 파악

모든 프로덕션 데이터베이스는 포괄적인 모니터링 및 성능 측정항목이 필요합니다. Spanner는 Cloud Monitoring에 기본 제공되는 측정항목과 함께 제공됩니다. 제공된 gRPC 라이브러리에는 OpenCensus 추적 기능이 포함되어 있으므로 가능한 경우 해당 라이브러리를 게임 백엔드 프로세스에 통합하는 것이 좋습니다. OpenCensus 추적 기능을 사용하면 Cloud Trace와 기타 지원되는 오픈소스 추적 도구에서 쿼리 추적 내용을 볼 수 있습니다.

Cloud Monitoring에서는 데이터 스토리지 및 CPU 사용량을 비롯한 Spanner 사용에 대한 세부정보를 볼 수 있습니다. 대부분의 경우 이 CPU 사용량 측정항목 또는 관찰된 지연 시간을 토대로 Spanner 확장을 결정하는 것이 좋습니다. 최적의 성능을 위해 제안된 CPU 사용량에 대한 자세한 내용은 권장사항을 참조하세요.

Spanner는 쿼리 실행 계획을 제공합니다. Google Cloud Console에서 이러한 계획을 검토할 수 있고 쿼리 성능을 파악하는 데 도움이 필요한 경우 지원팀에 문의할 수 있습니다.

Spanner는 데이터 액세스 패턴을 기반으로 성능을 최적화하기 위해 백그라운드에서 데이터를 투명하게 분할하므로 성능을 평가할 때는 짧은 주기 테스트를 최소로 유지하세요. 지속적이고 실제적인 쿼리 부하를 사용하여 성능을 평가해야 합니다.

데이터 삭제 시 테이블을 다시 만들지 않고 행 삭제

Spanner를 사용하여 작업할 때 새로 만든 테이블은 아직 부하 기반 또는 크기 기반 분할을 수행하여 성능을 개선할 준비가 되어 있지 않습니다. 테이블을 삭제한 다음 다시 만들어서 데이터를 삭제하면 Spanner에는 테이블에 맞는 분할을 결정하기 위한 데이터, 쿼리, 시간이 필요합니다. 동일한 종류의 데이터로 테이블을 다시 채울 계획인 경우(예: 연속 성능 테스트를 실행하는 경우), 더 이상 필요하지 않은 데이터가 포함된 행에서 DELETE 쿼리를 실행할 수 있습니다. 동일한 이유로 스키마 업데이트는 제공된 Cloud Spanner API를 사용해야 하고 새 테이블 만들기, 다른 테이블 또는 백업 파일의 데이터 복사와 같은 수동 전략은 피해야 합니다.

규정 준수 요건을 준수하도록 데이터 지역성 선택

많은 게임은 전 세계에서 플레이할 때 GDPR 같은 현지의 데이터 관련 법률을 준수해야 합니다. GDPR 요건을 준수하려면 Google Cloud 및 GDPR 백서를 참조하여 올바른 Spanner 리전 구성을 선택하세요.

다음 단계