Como usar o Cloud SQL para MySQL, segunda geração, como banco de dados de back-end de um jogo para dispositivos móveis

Um padrão bem testado para criar um back-end de jogo on-line usa um banco de dados relacional, como MySQL. Esse banco de dados armazena dados de persistência essencial e de estado do mundo do jogo. Em jogos simples baseados em sessão, o banco de dados não contém nada mais complicado do que os resultados das partidas. Em jogos on-line e em massa para multijogadores (MMOs, na sigla em inglês) grandes e de mundo persistente, é possível armazenar um conjunto bastante complexo de tabelas relacionadas entre si e que contêm a progressão dos jogadores e o inventário. A velocidade das consultas na camada do banco de dados de back-end afeta diretamente a experiência de responsividade do usuário no cliente do jogo.

O modelo é familiar, mas a maioria das equipes de desenvolvimento não tem um administrador de banco de dados dedicado. Conforme o banco de dados aumenta de tamanho e as relações que ele modela ficam mais complexas, a administração pode se tornar uma tarefa que muitas equipes preferem delegar. Em jogos para dispositivos móveis pequenos a médios, assíncronos e com base em turnos, um banco de dados como o Google Cloud SQL pode ser uma excelente escolha. No Cloud SQL para MySQL de segunda geração, há uma instância totalmente hospedada e gerenciada do MySQL com desempenho sólido, o mínimo de operações e backups automáticos.

Design de padrão de banco de dados voltado a serviço

Visão geral da arquitetura do banco de dados.

O paradigma de microsserviços é útil para back-ends de bancos de dados de jogos para dispositivos móveis. Um design comum é ter o banco de dados voltado a um serviço de banco de dados, formado por um pool de processos trabalhadores que aceitam solicitações de consulta front end do jogo, executam essas consultas contra o banco de dados e retornam os resultados.

Vantagens do padrão de banco de dados voltado a serviço

Ter um serviço intermediário para consultar o banco de dados em nome dos servidores do jogo tem diversas vantagens:

  • Aumento da disponibilidade – os bancos de dados muitas vezes limitam o número de conexões simultâneas. Com o uso de um serviço, o número de servidores do jogo que pode fazer solicitações para o banco de dados é desacoplado do número máximo de conexões permitidas.
  • Tolerância a falhas – pode-se criar serviços de bancos de dados para processar as solicitações temporariamente se o banco de dados estiver com algum problema.
  • Otimização de solicitações – as solicitações ao banco podem ser otimizadas pelo serviço de banco de dados fornecendo:
    • validação de consulta
    • priorização de consultas
    • controle de fluxo de taxas de consultas
    • armazenamento em cache read-through e na memória
  • Abstração do banco de dados: o serviço de banco de dados pode ser substituído pelo backup sem modificações aos servidores de front-end do jogo, desde que o contrato de serviço seja atendido. Com esse design, é possível usar bancos de dados diferentes em ambientes de desenvolvimento ou controle de qualidade, ou migrar para uma tecnologia de banco de dados diferente na produção.

Um padrão de banco de dados voltado a serviço para jogos

O diagrama a seguir ilustra a construção de um padrão de banco de dados voltado a serviço robusto usando os serviços do Google Cloud Platform.

Padrão de banco de dados usando o Google Cloud Platform.

Um padrão de banco de dados voltado a serviço robusto pode ser criado usando os seguintes componentes:

  • Servidores/front end de jogo dedicado – os aplicativos cliente do jogo são conectados diretamente com os servidores do jogo, tornando-os um serviço de front end. Servidores de jogos dedicados normalmente são executáveis personalizados, criados com o mecanismo de jogo, que precisam ser executados em hardware virtualizado, como VMs do Google Compute Engine. Se você estiver escrevendo um jogo em que a interação on-line pode ser modelada com semântica de solicitação/resposta no estilo HTTP, o Google App Engine também é uma boa opção.

  • Comunicação do servidor do jogo com os serviços de banco de dados: geralmente é modelada usando o estilo de acesso criar, ler, atualizar e excluir (CRUD na sigla em inglês), o que faz com que um endpoint da API REST ou do gRPC no Compute Engine se torne uma ótima escolha.

  • Comunicação de endpoints da API/RPC com um pool de workers de banco de dados: um caso clássico de enfileiramento, as opções conhecidas são os middlewares autogerenciados, de código aberto e de enfileiramento, como RabbitMQ ou ZeroMQ. No Cloud Platform, é possível usar o Google Cloud Pub/Sub seguro, durável e altamente disponível. Ele fornece uma solução de enfileiramento gerenciada sem a necessidade de administrar servidores.

  • Conexão entre trabalhadores de bancos de dados e o Cloud SQL de segunda geração: os trabalhadores de banco de dados podem ser desenvolvidos em qualquer linguagem que possibilite um método atualizado de acesso ao MySQL. Esses trabalhadores podem ser gerenciados manualmente no Compute Engine ou empacotados em contêineres do Docker para serem facilmente gerenciados usando a DSL do Kubernetes no Google Kubernetes Engine.

Quando você utiliza o Cloud SQL de segunda geração, algumas limitações precisam ser consideradas:

  • O limite de tamanho para o banco de dados é de 10 TB.
  • O limite de conexões é de 4000 conexões simultâneas.
  • Não há suporte a NDB (fragmentação), mas as réplicas são compatíveis.

Para lidar com esses pontos:

  • Escolha um modelo de dados que otimize a quantidade de dados armazenados.

  • Incorpore processos que movam os dados raramente acessados do banco de dados primário para um serviço de armazenamento de dados, como o Google BigQuery.

  • Use um padrão em que os servidores do jogo acessem o banco de dados por meio de um microsserviço. Ainda que o número de jogadores seja modesto o bastante para que os servidores do jogo consigam acessar o banco de dados diretamente, desacoplar a camada do banco de dados do servidor tem muitas vantagens: enfileiramento, nivelamento da taxa de consultas, tolerância a falhas de conexão, entre outras. Além disso, a tentativa de adicionar uma camada de acesso ao banco de dados separada depois que o jogo se tornar famoso pode causar inatividade e perda de receita.

  • Execute a análise do jogo e a telemetria dos jogadores contra uma réplica somente leitura do banco de dados. Isso evita que a análise impacte a responsividade do banco de dados. A replicação MySQL padrão pode ser feita no Cloud SQL para o MySQL de segunda geração, e é possível dimensionar a segunda instância de acordo com as consultas de análise, mantendo baixos os custos.

Design de amostra: jogo social em massa para um jogador (MASS, na sigla em inglês)

Um paradigma de jogo emergente na última década consiste em um grande número de jogadores únicos jogando sessões on-line simultaneamente, com mecânicas sociais, como o empréstimo e a troca de unidades, e placares como os únicos pontos de contato entre eles. Exemplos de jogos MASS incluem Puzzle and Dragons™ e Monster Strike™. Os jogos MASS para dispositivos móveis foram criados para serem econômicos na comunicação cliente/servidor. Assim, os usuários podem aproveitar o jogo mesmo com conectividade limitada ou esporádica. O modelo de dados para um jogo desse tipo, em que quase todo o armazenamento de estado persistente está relacionado ao metajogo (a coleta de unidades e a manutenção da moeda do jogador), gera dois tipos básicos de objetos a serem armazenados no banco de dados. Estes podem ser facilmente manipulados com o uso de mecanismos CRUD.

Objetos dos jogadores que monitoram:

  • moedas reais e do jogo
  • o número total de slots no inventário da unidade
  • força
  • experiência

Objetos de unidade que monitoram:

  • o proprietário (código do jogador)
  • experiência
  • custo de aquisição
  • inventário da unidade

Para menos de 100.000 jogadores, esse modelo de dados se encaixa bem em um banco de dados relacional, como o Cloud SQL de segunda geração.

Mimus

O Mimus é um aplicativo de jogo para dispositivos móveis MASS simulado com um back-end no estilo de Puzzle and Dragons™ ou Monster Strike™. Ele supõe que cada jogador esteja conectado a apenas um dispositivo por vez e que é preciso completar uma ação antes de começar outra. Com o Mimus, é possível executar cargas de trabalho simuladas para avaliar a capacidade máxima da arquitetura, que geralmente é baseada no número de usuários simultâneos (CCU, na sigla em inglês).

O código-fonte do Mimus está disponível em https://github.com/GoogleCloudPlatform/mimus-game-simulator.

Visão geral da arquitetura do Mimus

Simulação de cliente do Mimus dentro do servidor do Mimus

Nos jogos MASS, a taxa em que o cliente do jogo gera consultas ao banco de dados pode ser controlada pelo desenvolvedor quanto este exige que o jogador visualize animações e interaja com o cliente do jogo para continuar. O Mimus simula essas estratégias de limitação de taxa com chamadas sleep(). Dessa maneira, ele estimula uma aproximação razoável da carga do banco de dados por cliente com a execução de tantos processos quanto o número de jogadores simulado. Com o uso de um pod cliente/servidor do Mimus em contêineres, a simulação é orquestrada de maneira eficiente em um cluster Kubernetes que gera consultas contra o banco de dados.

O cliente de jogo do Mimus estimula a comunicação com o servidor de back-end usando um loop contínuo que chama os procedimentos do servidor do Mimus diretamente, escolhendo chamados de função com base no estado do jogador e no inventário dele.

As ações do jogador simuladas pelo Mimus incluem:

  • Jogar uma rodada do jogo.
  • Comprar moeda.
  • Gastar moeda.
  • Evoluir ou aumentar o nível das unidades.

Cada uma dessas ações é implementada como múltiplas interações CRUD com os objetos de jogador ou de unidade, manipuladas pelo cliente por meio de chamados para o servidor do Mimus. O servidor do Mimus faz essas solicitações de banco de dados usando a API síncrona (bloqueada) do banco de dados do Mimus. Essa API é um módulo Python importado pelo servidor do Mimus e pode ser configurada para testar diferentes implementações de back-end de banco de dados.

Comunicação da API de banco de dados do Mimus com o pool de workers de banco de dados do Mimus

A comunicação entre o servidor do Mimus e o serviço de banco de dados está ilustrada no diagrama a seguir:

Design de comunicação do Mimus.

A API do banco de dados do Mimus aceita lotes de consultas ao banco de dados e retorna os resultados. Ela publica esses lotes em mensagens Cloud Pub/Sub e espera que os resultados sejam retornados por meio do Redis. Antes de enviar uma mensagem, a API de banco de dados valida todos os valores que serão gravados no banco de dados e marca a mensagem com um ID de transação exclusivo. A mensagem é publicada em um tópico Work no Cloud Pub/Sub. Em seguida, a API do banco de dados entra em loop, pesquisando a existência do código de transação como chave Redis. Os resultados do valor da chave são então recuperados pela API do banco de dados e retornados para o servidor do Mimus, de onde ficam disponíveis para o cliente Mimus.

Considerações sobre a seleção de comunicação

O Mimus usa o Cloud Pub/Sub para a comunicação de consultas pendentes porque as ações do usuário exigem durabilidade e envio confiável. Ele também usa o Redis para comunicar os resultados em que a durabilidade e o nível de confiança são menos essenciais. Se os resultados forem perdidos devido a um erro de aplicativo ou de uma falha no Redis, o cliente do Mimus consulta novamente os resultados do banco de dados. Com o uso de transações em um banco de dados relacional, garante-se que ou todas as alterações solicitadas ocorreram, ou nenhuma delas. O raro caso em que o Mimus precisa fazer uma segunda solicitação é considerado uma contrapartida aceitável em troca da simplicidade e da velocidade da recuperação fornecida pelo Redis. Para manter o uso razoável do Redis, os resultados de solicitações podem expirar após trinta segundos.

Pool de trabalhador do banco de dados do Mimus

No pool de trabalhador do banco de dados do Mimus há vários processos em execução no Kubernetes Engine. Cada instância de contêiner em execução pesquisa o tópico Work do Cloud Pub/Sub em um loop infinito para novas mensagens. Quando recebe uma mensagem, ele executa as consultas na mensagem usando o módulo Python MySQLdb. Cada mensagem representa uma transação relacional e pode conter várias consultas. Todas as consultas da mensagem precisam ser completadas antes de o commit ser feito no banco de dados. Depois que as consultas são completadas (ou falham), o trabalhador do banco de dados publica os resultados no Redis sob o código de transação recebido como parte da mensagem original.

Banco de dados do Mimus

O banco de dados relacional por trás do serviço do banco de dados do Minus é uma instância do Cloud SQL para o MySQL de segunda geração. Ao criar a instância, selecione as configurações de máquina de até 32 núcleos, 208 GB de RAM e tamanhos de disco de até 10 TB. Além de ter suporte para réplicas, as instâncias do Cloud SQL de segunda geração são configuradas para executar backups regulares por padrão. Caso seja necessário um ajuste extra do MySQL, configure as sinalizações MySQL específicas na instância do Cloud SQL. Mais informações sobre a configuração estão disponíveis na documentação do Cloud SQL.

Conclusões do teste do Cloud SQL usando o Mimus

Tipo de máquina Cloud SQL SG Número sugerido de usuários simultâneos
n1-standard-4 15.000
n1-standard-8 30.000
n1-standard-16 60.000
n1-standard-32 120.000
n1-highmem-8 50.000
n1-highmem-16 100.000
n1-highmem-32 200.000

Quando o arcabouço de testes do Mimus foi usado para simular 100.000 usuários simultâneos contra uma instância do Cloud SQL para o MySQL de segunda geração criada em uma instância do Compute Engine n1-highmem-16, o tempo de resposta à consulta permaneceu abaixo de dois segundos durante o teste.

Um padrão de banco de dados com base no Cloud SQL para o MySQL de segunda geração pode fornecer o desempenho necessário para quem está criando um jogo MASS para dispositivos móveis e precisa de suporte para centenas de milhares de jogadores simultâneos. Quando a popularidade do jogo crescer, será possível introduzir instâncias do Cloud SQL adicionais, sejam elas fragmentadas ou réplicas, e uma estratégia de armazenamento em cache Redis ou Memcached no nível do serviço de banco de dados para manter o desempenho em níveis aceitáveis.

Pode-se usar um tipo de máquina menor para reduzir os custos em jogos com um número menor de jogadores simultâneos projetados.

Para os jogos com CCU projetado em milhões, pense em usar um banco de dados NoSQL, como o Google Cloud Datastore ou o Google Cloud Bigtable, com fortes características de escalabilidade e desempenho.

A seguir