O que torna um back-end de jogo diferente de todo o resto
Se você já desenvolveu um backend para uma aplicação web, sabe como funciona: um cliente envia uma solicitação HTTP, o servidor o processa, consulta um banco de dados e retorna uma resposta JSON. O ciclo se repete cada interação. Alguns milissegundos a mais de latência? O usuário nem percebe.
Un Back-end de jogo multijogador Ele vive em um universo completamente diferente. Não estamos fazendo isso aqui pedidos e respostas: riamos falar sobre um fluxo contínuo de dados bidirecionais, para que caiam misigundo Estas são as razões pelas quais centenas de jogadores partilham um estado comum que muda durante décadas a cada segundo, e onde um tempo de 100ms pode significar a diferença entre a vitória e a derrota.
Neste primeiro artigo da série Engenharia de back-end de jogos, exploraremos a anatomia completo com um backend de jogo multijogador: da arquitetura de rede aos protocolos de comunicação, do jogo server-side loops to message serialization, up to scaling strategies and platforms Back-end como serviço. Ao final você terá um mapa mental completo de cada componente e as escolhas questões arquitetônicas com as quais você terá que lidar.
Visão geral da série
| # | Articolo | Focus |
|---|---|---|
| 1 | Você está aqui - Anatomia de um Backend de Jogo | Arquitetura, protocolos, componentes |
| 2 | State Synchronization | Netcode, interpolazione, predizione |
| 3 | Matchmaking Engine | Algoritmi ELO, code, lobby |
| 4 | Dedicated Game Servers | Infrastruttura e orchestrazione |
| 5 | Anti-Cheat Architecture | Validação e detecção do lado do servidor |
| 6 | LiveOps e Monetizzazione | Economia in-game, eventi live |
| 7 | Telemetria e Analytics | Metriche, data pipeline, dashboards |
| 8 | Observability | Logging, tracing, alerting |
| 9 | Cloud Gaming Infrastructure | Streaming, edge computing, latenza |
| 10 | Open Source Game Stacks | Nakama, Colyseus, Agones |
O que Voce Aprendera
- As diferenças fundamentais entre um back-end da web e um back-end de jogo
- Modelos de rede: cliente-servidor, ponto a ponto e retransmissão
- Como funciona o loop de jogo do lado do servidor (servidor autoritativo)
- Protocolos de comunicação: TCP vs UDP vs WebSocket vs WebRTC
- Serialização de mensagens: Buffers de protocolo, FlatBuffers, MessagePack
- Gestione connessioni: sessioni, reconnection, heartbeats
- Opções de banco de dados: Redis, PostgreSQL, série temporal
- Padrão de escala: servidor lobby, servidor mundial, zonas instanciadas
- Piattaforme BaaS: PlayFab, GameLift, Nakama, Colyseus
1. Web Backend vs Game Backend: Due Mondi Diversi
Para entender por que o back-end de um jogo requer uma abordagem radicalmente diferente, vamos comparar os requisitos fundamentais com os de uma aplicação web tradicional.
Comparacao Requisiti: Web vs Game
| Aspetto | Web Backend | Game Backend Multiplayer |
|---|---|---|
| Modello comunicazione | Request-response (HTTP) | Streaming bidirezionale continuo |
| Latenza accettabile | 200-500ms | 16-50ms (um quadro a 60fps = 16,6ms) |
| Frequenza aggiornamenti | On-demand (click, submit) | 20-128 vezes por segundo (taxa de ticks) |
| Stato | Apátrida (cada solicitação independente) | Stateful (estado compartilhado na memória) |
| Consistenza | Eventual consistency accettabile | Strong consistency in tempo reale |
| Scalabilità | Orizzontale (load balancer + repliche) | Vertical por sessão + horizontal por sessões |
| Tolleranza perdita dati | Zero (cada transação conta) | Seletivo (posições perdidas estão OK, compras não) |
| Durata sessione | Minuti (navigazione) | Horário (sessão de jogo) |
| Largura de banda por usuário | KB sporadici | 5-50 KB/s continui |
O ponto crucial é a natureza com estado do back-end do jogo. Um servidor web pode ser substituído a qualquer momento - basta direcionar o tráfego para outra instância. Um servidor de jogo mantém na memória o estado de um jogo ativo: as posições dos jogadores, as balas em voo, os efeitos ativos, a pontuação. Se esse servidor travar, o jogo será perdido.
Ou problema de latência nossos jogadores
Em um FPS competitivo a 128 tick/s, o servidor processa uma atualização a cada 7,8ms. Se um jogador tiver latência de rede (RTT) de 50ms, sua entrada chegará ao servidor com 25ms de atraso. e a resposta chega com mais 25ms. O jogador vê o mundo 50ms atrás à realidade do servidor. Aos 200ms o jogo fica impossível de jogar. É por isso que técnicas como a previsão do lado do cliente e o compensação de atraso eles são fundamentais.
2. Modelos de rede: como os jogadores se conectam
A primeira decisão arquitetônica diz respeito ao modelo de rede: como os clientes se comunicam entre si e com o servidor? Existem três abordagens principais, cada uma com compensações específicas.
2.1 Client-Server (Authoritative Server)
O modelo dominante nos jogos modernos. Um servidor central é o único autoridade ligado estado do jogo. Os clientes enviam suas entradas (pressionamento de teclas, movimentos do mouse) para o servidor, que valida-os, atualiza o estado do mundo e envia o resultado a todos os clientes. Nenhum cliente pode alterar diretamente o estado do jogo.
Client A Server Client B
| | |
|--- Input (W,A) -->| |
| |--- Input (S,D) ---|<
| | |
| [Valida input] |
| [Aggiorna stato] |
| [Rileva collisioni] |
| [Calcola risultato] |
| | |
|<-- Stato mondo ---|--- Stato mondo -->|
| | |
| [Interpola/ | [Interpola/ |
| Predici] | Predici] |
Vantagens: segurança máxima (o servidor controla tudo), anti-cheat integrado, Estado consistente para todos os jogadores, fácil de depurar. Desvantagens: alto custo de infraestrutura (um servidor para cada jogo), latência adicional (cada entrada deve fazer ida e volta), ponto único de falha.
2.2 Peer-to-Peer (P2P)
Cada cliente se comunica diretamente com todos os outros. Não existe um servidor central - cada jogador e cliente e "servidor" para si. Este modelo se tornou popular em jogos de luta e no RTS, onde o número de jogadores é limitado (2-8).
Vantagens: sem custos de servidor, latência mínima entre pares (conexão direta), sobrevive à morte de qualquer nó. Desvantagens: impossível evitar fraudes (cada cliente tem autoridade sobre seu próprio estado), complexidade exponencial com o número de jogadores (N*(N-1)/2 conexões), problemas de travessia de NAT.
2.3 Servidor de retransmissão (servidor como proxy)
Um compromisso entre os dois modelos. Um servidor central atua como relé: Recebe mensagens de cada cliente e os encaminha para todos os outros, mas não processa a lógica do jogo. A simulação acontece nos clientes, no servidor e apenas no “carteiro”.
Compare modelos de rede
| Característica | Client-Server | P2P | Relay |
|---|---|---|---|
| Sicurezza | Alta (server autorita) | Bassa (no autorita) | Media (dipende dal client) |
| Costo server | Alto | Nullo | Baixo |
| Scalabilità | Centenas de jogadores | 2-8 giocatori | Dezenas de jogadores |
| Latenza | Media (round-trip) | Bassa (diretta) | Media (via server) |
| Uso tipico | FPS, MMO, Battle Royale | Fighting, RTS classici | Co-op, casual, mobile |
| Esempi | Valorant, Fortnite, CS2 | Street Fighter, StarCraft | Among Us, Fall Guys |
3. O loop de jogo do lado do servidor
O coração de um back-end de jogo confiável é o ciclo de jogo: um ciclo de repetição a frequência constante (o taxa de tick), processando entrada, atualizando estado e enviando os resultados aos clientes. A frequência da taxa de tick determina a "resolução temporal" do simulação.
Taxa de ticks por gênero
| Genere | Tick Rate | Intervallo | Exemplo |
|---|---|---|---|
| FPS Competitivo | 128 Hz | 7.8ms | Valorant, CS2 |
| FPS Standard | 64 Hz | 15.6ms | Overwatch 2 |
| Battle Royale | 20-30 Hz | 33-50ms | Fortnite, PUBG |
| MMO | 10-20 Hz | 50-100ms | World of Warcraft |
| Strategico/Turni | 1-10 Hz | 100ms-1s | Civilization, Hearthstone |
interface PlayerInput {
readonly playerId: string;
readonly tick: number;
readonly keys: ReadonlyArray<string>;
readonly mouseX: number;
readonly mouseY: number;
readonly timestamp: number;
}
interface GameState {
readonly tick: number;
readonly players: ReadonlyMap<string, PlayerState>;
readonly projectiles: ReadonlyArray<Projectile>;
readonly timestamp: number;
}
class AuthoritativeGameServer {
private readonly TICK_RATE = 64; // 64 aggiornamenti/secondo
private readonly TICK_INTERVAL = 1000 / 64; // ~15.625ms per tick
private currentTick = 0;
private gameState: GameState;
private readonly inputBuffer: Map<string, PlayerInput[]> = new Map();
start(): void {
console.log(`Server avviato a ${this.TICK_RATE} tick/s`);
setInterval(() => this.tick(), this.TICK_INTERVAL);
}
// Riceve input dal client (chiamato dalla rete)
onPlayerInput(input: PlayerInput): void {
const buffer = this.inputBuffer.get(input.playerId) ?? [];
// Crea nuovo array invece di mutare
this.inputBuffer.set(input.playerId, [...buffer, input]);
}
private tick(): void {
const tickStart = performance.now();
this.currentTick++;
// 1. Processa tutti gli input ricevuti
const processedInputs = this.processInputs();
// 2. Aggiorna la simulazione di gioco
const updatedState = this.updateSimulation(processedInputs);
// 3. Rileva collisioni
const stateAfterCollisions = this.detectCollisions(updatedState);
// 4. Aggiorna lo stato di gioco (immutabile)
this.gameState = {
...stateAfterCollisions,
tick: this.currentTick,
timestamp: Date.now(),
};
// 5. Invia lo stato aggiornato a tutti i client
this.broadcastState(this.gameState);
// 6. Monitora le performance del tick
const tickDuration = performance.now() - tickStart;
if (tickDuration > this.TICK_INTERVAL) {
console.warn(
`Tick ${this.currentTick} overrun: ${tickDuration.toFixed(2)}ms ` +
`(budget: ${this.TICK_INTERVAL.toFixed(2)}ms)`
);
}
}
private processInputs(): Map<string, PlayerInput> {
const latest = new Map<string, PlayerInput>();
for (const [playerId, inputs] of this.inputBuffer) {
if (inputs.length > 0) {
// Prendi l'ultimo input valido
const lastInput = inputs[inputs.length - 1];
if (this.validateInput(lastInput)) {
latest.set(playerId, lastInput);
}
}
}
// Svuota il buffer (nuovo Map, non mutare)
this.inputBuffer.clear();
return latest;
}
private validateInput(input: PlayerInput): boolean {
// Anti-cheat: verifica che i valori siano plausibili
const maxSpeed = 10;
return (
Math.abs(input.mouseX) <= 360 &&
Math.abs(input.mouseY) <= 90 &&
input.keys.length <= 6
);
}
}
Tick Budget e Overrun
A 64 tick/s, cada tick tem um orçamento de 15,6ms. Se a lógica do tick (física, colisões, IA, networking) demora mais, o servidor acumula lag e os jogadores eles percebem o atraso. Acompanhar o tick budget é fundamental: na produção você acompanha o p99 da duração do tick, não da média.
4. Gestão do Estado: Os Três Níveis do Estado
O estado de um jogo multijogador não é uma bolha monolítica: é dividido em níveis com características e requisitos diferentes. Cada camada requer estratégias de armazenamento, sincronização e persistência diferente.
Os três níveis de estado do jogo
| Livello | O que contém | Frequenza Update | Persistenza | Storage |
|---|---|---|---|---|
| Frame State | Posizioni, velocità, rotazioni, proiettili | Cada tick (20-128 Hz) | Só na memória | RAM del game server |
| Session State | HP, inventario, punteggio, buff/debuff | No evento (dano, cobrança) | Durante uma partida | RAM + Redis (backup) |
| Persistent State | Profilo, statistiche, acquisti, ranking | No final do jogo ou na transação | Permanente | PostgreSQL / MongoDB |
// === FRAME STATE: aggiornato ogni tick ===
interface FrameState {
readonly tick: number;
readonly entities: ReadonlyMap<string, EntityTransform>;
readonly projectiles: ReadonlyArray<ProjectileState>;
readonly effects: ReadonlyArray<ActiveEffect>;
}
interface EntityTransform {
readonly posX: number;
readonly posY: number;
readonly posZ: number;
readonly rotYaw: number;
readonly rotPitch: number;
readonly velocityX: number;
readonly velocityY: number;
readonly velocityZ: number;
}
// === SESSION STATE: aggiornato su eventi ===
interface SessionState {
readonly playerId: string;
readonly health: number;
readonly maxHealth: number;
readonly armor: number;
readonly inventory: ReadonlyArray<InventoryItem>;
readonly activeWeapon: string;
readonly kills: number;
readonly deaths: number;
readonly score: number;
readonly buffs: ReadonlyArray<BuffEffect>;
readonly team: string;
}
// === PERSISTENT STATE: salvato su database ===
interface PersistentPlayerData {
readonly odlayerId: string;
readonly username: string;
readonly elo: number;
readonly totalMatches: number;
readonly totalWins: number;
readonly totalKills: number;
readonly unlockedItems: ReadonlyArray<string>;
readonly purchaseHistory: ReadonlyArray<Purchase>;
readonly createdAt: Date;
readonly lastLoginAt: Date;
}
A separação em níveis é fundamental para o desempenho. O estado do quadro deve ser o mais compacto possível porque é serializado e enviado a todos os clientes a cada tick. O fui do sexo só é enviado mediante alteração. O estado persistente nunca é transmitido: apenas o proprietário pode solicitá-lo e é salvo de forma assíncrona no banco de dados.
5. Protocolos de comunicação: TCP, UDP, WebSocket, WebRTC
A escolha do protocolo de transporte é uma das decisões mais impactantes na arquitetura de um back-end do jogo. Cada protocolo oferece diferentes compensações entre confiabilidade, latência e complexidade.
5.1 TCP (Transmission Control Protocol)
O TCP garante a entrega ordenada e confiável de cada pacote. Se um pacote for perdido, o TCP ele o retransmite e bloqueia a entrega dos pacotes subsequentes até que chegue o pacote perdido. Isto fenômeno é chamado bloqueio de linha e é o inimigo mortal dos jogos em tempo real.
5.2 UDP (User Datagram Protocol)
UDP e "disparar e esquecer": envia pacotes sem garantias de entrega, ordem ou integridade. Se um pacote é perdido, ele não é retransmitido. Isso parece terrível, mas para um jogo em tempo real e exatamente o que é necessário: a posição de um jogador há 100 ms e irrelevante se você tiver isso de 16ms atrás.
5.3 WebSocket
WebSocket opera sobre TCP, mas fornece uma conexão bidirecional full-duplex. E o protocolo padrão para jogos móveis e baseados em navegador, onde o UDP nativo não está disponível. Latência e maior que o UDP puro, mas a facilidade de implementação e compatibilidade universal torne-o a escolha pragmática para muitos gêneros.
5.4 WebRTC (DataChannel)
WebRTC DataChannel oferece comunicação ponto a ponto (ou cliente-servidor) baseada em SCTP sobre UDP. Suporta modos confiáveis e não confiáveis, configuráveis por canal. E a única opção para obtenha comunicação semelhante a UDP em seu navegador.
Compare protocolos de rede para jogos
| Aspetto | TCP | UDP | WebSocket | WebRTC DC |
|---|---|---|---|---|
| Trasporto | TCP | UDP | TCP | SCTP/UDP |
| Consegna garantita | Si | No | Si | Configurabile |
| Ordine garantito | Si | No | Si | Configurabile |
| Head-of-line blocking | Si | No | Si | No (unreliable) |
| Latenza tipica | Alta (retransmit) | Minima | Media | Bassa |
| Browser support | No (diretto) | No | Si | Si |
| NAT traversal | Não é necessário | Problematico | Não é necessário | Integrato (ICE) |
| Complexidade | Bassa | Alta (custom reliability) | Bassa | Alta (signaling) |
| Ideal para | Chat, turni, lobby | FPS, racing, simulazioni | Browser games, mobile | Browser FPS, VoIP |
WebTransporte: o futuro?
WebTransporte e um novo padrão baseado em HTTP/3 (QUIC) que promete combinar o melhor de todos os mundos: fluxos bidirecionais multiplexados, modos confiáveis e não confiáveis, sem bloqueio direto e acesso nativo ao navegador. Em 2026, o suporte do navegador é amadurecendo (o Chrome e o Edge oferecem suporte), mas o suporte do lado do servidor ainda é limitado. E o protocolo para observar a próxima geração de jogos baseados em navegador.
5.5 Pattern Ibrido: Multi-Channel
Os back-ends de jogos modernos raramente usam apenas um protocolo. O padrão mais comum é o multicanal: Canais diferentes para tipos de dados diferentes.
Canale Unreliable (UDP / WebRTC unreliable):
- Posizioni dei giocatori (ogni tick)
- Rotazioni e animazioni
- Effetti particellari
- Dati audio posizionale
Canale Reliable (TCP / WebSocket / WebRTC reliable):
- Danno inflitto/subito
- Cambio arma/inventario
- Chat
- Eventi di gioco (kill, obiettivo)
- Transazioni economiche
- Comandi di matchmaking
6. Serialização de mensagens: cada byte conta
Quando você envia 64 atualizações por segundo para 100 jogadores, cada byte extra na mensagem será multiplique por 6.400 vezes por segundo. A escolha do formato de serialização tem impacto diretamente na largura de banda, latência e custo de infraestrutura.
Compare formatos de serialização
| Formato | Tipo | Dimensione (relativa) | Velocita encode | Velocita decode | Schema |
|---|---|---|---|---|---|
| JSON | Testo | 100% (baseline) | Lenta | Lenta | No |
| MessagePack | Binario | ~60-70% | Veloce | Veloce | No |
| Protocol Buffers | Binario | ~30-40% | Muito rápido | Muito rápido | Si (.proto) |
| FlatBuffers | Binario | ~35-45% | Zero-copy | Zero-copy | Si (.fbs) |
| Cap'n Proto | Binario | ~35-45% | Zero-copy | Zero-copy | Si |
// === JSON: ~180 bytes ===
const jsonMsg = JSON.stringify({
type: "player_state",
playerId: "p_abc123",
posX: 123.456,
posY: 78.901,
posZ: 45.678,
rotYaw: 180.5,
rotPitch: -12.3,
health: 85,
weapon: "rifle",
tick: 15042,
});
// === Protocol Buffers: ~42 bytes ===
// Definizione .proto:
// message PlayerState {
// uint32 player_id = 1;
// float pos_x = 2;
// float pos_y = 3;
// float pos_z = 4;
// float rot_yaw = 5;
// float rot_pitch = 6;
// uint32 health = 7;
// WeaponType weapon = 8;
// uint32 tick = 9;
// }
// Risparmio: ~77% meno bandwidth
// A 64 tick/s, 100 giocatori:
// JSON: 180 * 100 * 64 = 1.15 MB/s
// Protobuf: 42 * 100 * 64 = 0.27 MB/s
// Risparmio: 0.88 MB/s = 76% in meno
Quando usar isso
- JSON: Prototipagem, jogos passo a passo, comunicação com serviços web externos. Fácil de depurar, universal
- Pacote de mensagens: "JSON binário" - quando você deseja reduzir o tamanho sem alterar a lógica do código. Substituição imediata para JSON
- Buffers de protocolo: O padrão de fato para servidores de jogos de alto desempenho. Esquema digitado, codegen multilíngue, compatibilidade com versões anteriores
- FlatBuffers: Quando até o custo da desserialização é muito alto. Acesso de cópia zero: leia campos diretamente do buffer sem alocar memória
7. Connection Management: Sessioni, Heartbeat, Reconnection
Num jogo multijogador, a ligação à rede nunca é 100% fiável. Os jogadores fazem desconectar devido a WiFi instável, mudança de rede (4G/WiFi), falha do cliente ou simplesmente porque eles fecham o jogo. Um back-end de jogo robusto deve lidar com tudo isso de forma transparente.
7.1 Session Lifecycle
[Client avvia connessione]
|
v
[Handshake + Autenticazione] -- Token JWT o session ticket
|
v
[Session creata sul server] -- SessionID, PlayerID, Timestamp
|
v
[Heartbeat loop avviato] -- Ping ogni 1-5 secondi
|
v
[Gioco attivo] -- Input/State loop
|
v
[Disconnessione rilevata] -- Timeout heartbeat (10-30s)
|
+--- [Reconnection window] -- 30-120s per riconnettersi
| |
| +--> Riconnesso: ripristina sessione, invia stato corrente
| |
| +--> Timeout: sessione distrutta, giocatore rimosso
|
v
[Sessione terminata] -- Salva statistiche, libera risorse
7.2 Heartbeat e Disconnect Detection
Il batimento cardíaco e uma mensagem periódica que o cliente envia ao prestador de serviço (ou vice-versa) para indicar que a conexão está ativa. Se o serviço não receber pulsos por um período de tempo configurável, considere ou cliente desconectado. As batidas do coração também servem para meditar latência (RTT) e ou nervosismo.
interface ConnectionMetrics {
readonly lastHeartbeat: number;
readonly rttMs: number;
readonly jitterMs: number;
readonly packetLoss: number;
readonly rttHistory: ReadonlyArray<number>;
}
class ConnectionManager {
private readonly HEARTBEAT_INTERVAL = 2000; // 2 secondi
private readonly DISCONNECT_TIMEOUT = 15000; // 15 secondi
private readonly RECONNECT_WINDOW = 60000; // 60 secondi
private readonly sessions: Map<string, SessionData> = new Map();
handleHeartbeat(sessionId: string, clientTimestamp: number): void {
const session = this.sessions.get(sessionId);
if (!session) return;
const now = Date.now();
const rtt = now - clientTimestamp;
// Calcola jitter (variazione del RTT)
const prevRtt = session.metrics.rttMs;
const jitter = Math.abs(rtt - prevRtt);
// Crea nuovo oggetto metrics (immutabile)
const updatedMetrics: ConnectionMetrics = {
lastHeartbeat: now,
rttMs: rtt,
jitterMs: jitter,
packetLoss: session.metrics.packetLoss,
rttHistory: [...session.metrics.rttHistory.slice(-19), rtt],
};
// Aggiorna sessione con nuove metriche
this.sessions.set(sessionId, {
...session,
metrics: updatedMetrics,
});
}
checkDisconnections(): void {
const now = Date.now();
for (const [sessionId, session] of this.sessions) {
const elapsed = now - session.metrics.lastHeartbeat;
if (elapsed > this.DISCONNECT_TIMEOUT && session.status === 'connected') {
// Passa a stato "disconnesso" ma mantiene la sessione
this.sessions.set(sessionId, {
...session,
status: 'disconnected',
disconnectedAt: now,
});
console.log(`Player ${session.playerId} disconnesso. Reconnect window: 60s`);
}
if (session.status === 'disconnected') {
const disconnectElapsed = now - (session.disconnectedAt ?? now);
if (disconnectElapsed > this.RECONNECT_WINDOW) {
// Finestra di reconnection scaduta
this.destroySession(sessionId);
}
}
}
}
handleReconnect(playerId: string, newSocket: WebSocket): boolean {
// Cerca sessione disconnessa per questo player
for (const [sessionId, session] of this.sessions) {
if (session.playerId === playerId && session.status === 'disconnected') {
// Ripristina la sessione con la nuova connessione
this.sessions.set(sessionId, {
...session,
status: 'connected',
socket: newSocket,
metrics: { ...session.metrics, lastHeartbeat: Date.now() },
});
// Invia lo stato corrente del gioco al client riconnesso
this.sendFullStateSync(sessionId);
return true;
}
}
return false;
}
private destroySession(sessionId: string): void {
const session = this.sessions.get(sessionId);
if (session) {
console.log(`Sessione ${sessionId} distrutta per player ${session.playerId}`);
this.sessions.delete(sessionId);
}
}
}
8. Banco de dados: a pilha de persistência
O backend de um jogo não usa apenas um banco de dados: ele usa um pilha de motores de armazenamento, cada um otimizado para um tipo de dados e padrão de acesso específicos.
Banco de dados de pilha para back-end de jogo
| Layer | Tecnologia | O que armazena | Latenza |
|---|---|---|---|
| L1 - In-memory | Strutture dati in RAM | Frame state, input buffer | < 0.001ms |
| L2 - Cache distribuita | Redis / Dragonfly | Session state, leaderboard, matchmaking queue | 0.1-1ms |
| L3 - Database relazionale | PostgreSQL / CockroachDB | Profili giocatori, inventario, acquisti, ranking | 1-10ms |
| L4 - Time-series | TimescaleDB / InfluxDB | Telemetria, metriche performance, analytics | 1-5ms |
| L5 - Object storage | S3 / GCS | Replay, screenshot, asset, backup | 50-200ms |
8.1 Redis para estado em tempo real
Redis é o componente mais crítico da pilha de banco de dados de back-end de um jogo. Suas estruturas dados nativos (conjuntos classificados, hashes, listas, pub/sub) mapeiam diretamente nos padrões de jogo:
- Conjuntos classificados para tabela de classificação e classificação (ZADD, ZRANK, ZRANGE)
- Hashes para estado de sessão (HSET, HGETALL)
- Listas para filas de matchmaking (LPUSH, RPOP)
- Pub/Sub para comunicação entre servidores e serviços de jogos
- Fluxos para fornecimento de eventos e registros de auditoria
8.2 PostgreSQL para Persistência
Para todos os dados que devem sobreviver a uma reinicialização do servidor: perfis de jogadores, progressão,
inventário, transações econômicas, histórico de lote. PostgreSQL oferece transações ACID,
JSON(B) para dados semiestruturados e com extensões como pgvector suporta
também pesquisas de similaridade para matchmaking baseado em habilidades.
9. Infrastruttura: Dedicated Servers, Container, Orchestrazione
Ao contrário dos servidores web sem estado que escalam adicionando réplicas atrás de um balanceador de carga, servidores de jogos são com estado: cada instância gerencia um ou mais jogos ativos com status na memória. Isso torna o dimensionamento e a orquestração muito mais complexos.
9.1 Dedicated Game Servers
Un servidor de jogo dedicado É um processo que realiza uma simulação de um único Eu jogo (ou uma área do mundo do jogo). Ao contrário dos "militares disponíveis" (onde um jogador também usado como servidor), ou um servidor dedicado rodando em novo hardware dedicado, garantindo desempenho consistente e de justiça.
[Internet]
|
[CDN / Edge]
|
[Load Balancer (L7)]
/ \
[Gateway API] [Gateway API]
| |
+--------+--------+-------+--------+
| | | | |
[Matchmaker] [Auth] [Social] [Shop] [Leaderboard]
| |
v v
[Fleet Manager / Orchestrator] [Redis Cluster]
| |
+---+---+---+---+ |
| | | | | |
[GS] [GS] [GS] [GS] [GS] <---> [PostgreSQL]
Game Server Instances
Legenda:
GS = Game Server (una partita ciascuno)
Ogni GS e un container/pod con CPU e RAM dedicate
Il Fleet Manager scala il numero di GS in base alla domanda
9.2 Conteinerização com Docker
Cada servidor de jogo é executado em um contêiner Docker, garantindo isolamento, reprodutibilidade e implantação rápido. O contêiner inclui o binário do servidor, configurações e dependências. A leveza dos containers permite lançar novas instâncias em poucos segundos.
9.3 Orquestração com Kubernetes e Agones
Agonés e um projeto de código aberto do Google que estende o Kubernetes para gerenciar servidores de jogos dedicados. Fornece definições de recursos personalizadas (CRD) para definir GameServer, Frota e FleetAutoscaler. O gerente de frota monitora a demanda do lote e escala automaticamente o número de servidores disponíveis.
Compare plataformas de orquestração
| Piattaforma | Tipo | Cloud | Pontos fortes |
|---|---|---|---|
| Agones | Open-source (K8s) | Qualsiasi + on-premise | Flessibilità totale, no vendor lock-in |
| Amazon GameLift | Managed (AWS) | AWS | Integração AWS, combinação FlexMatch |
| Azure PlayFab | Managed (Azure) | Azure | Ecosystem completo (LiveOps, analytics, economy) |
| Google Cloud Game Servers | Managed (GCP) | GCP | Desenvolvido por Agones, escala global |
10. Padrão de escala: Lobby, Mundo, Zona
Nem todos os jogos têm a mesma escala. O padrão de escala depende do gênero e estrutura do jogo. Aqui estão os três padrões principais.
10.1 Lobby/Match Server
Usado por FPS, Battle Royale, MOBA. Cada jogo é uma instância isolada com um número número fixo de jogadores (10-100). No final da partida, o servidor é destruído e os recursos reciclado. Dimensionamento = aumentar o número de instâncias.
10.2 World Server (Persistent World)
Usado por MMOs. Um mundo persistente e dividido em áreas, cada um gerenciado de um servidor dedicado. Os jogadores se movem entre zonas com uma transferência transparente. Os servidores de zona comunicam-se entre si para gerenciar limites.
10.3 Instanced Zones
Um híbrido: o mundo é persistente, mas áreas específicas (masmorras, ataques, arenas) surgem instanciado dinamicamente. Cada grupo de jogadores recebe sua própria cópia da área. Isso permite dimensionar áreas de alta densidade sem sobrecarregar o servidor mundial.
LOBBY/MATCH SERVER (FPS, BR):
[Matchmaker] --> [Server Pool]
|
+---------+---------+
| | |
[Match 1] [Match 2] [Match 3]
10 players 10 players 10 players
(30 min) (25 min) (nuovo)
WORLD SERVER (MMO):
[World Manager]
|
+---+---+---+---+
| | | | |
[Zona A][Zona B][Zona C][Zona D]
Citta Foresta Dungeon PvP
200 p. 50 p. 30 p. 80 p.
^ ^
|--- Handoff zone ---|
INSTANCED ZONES (MMO + Dungeon):
[Zona Citta] --> [Ingresso Dungeon]
|
+---------+---------+
| | |
[Dungeon [Dungeon [Dungeon
Copia 1] Copia 2] Copia 3]
5 players 5 players 5 players
11. Backend como serviço para jogos: as plataformas
Nem todo mundo precisa (ou quer) construir um back-end de jogo do zero. Existem plataformas Backend-as-a-Service (BaaS) que oferece componentes prontos para uso: matchmaking, placar, autenticação, armazenamento, economia no jogo. A escolha depende do orçamento, controle desejado e o gênero do jogo.
Compare plataformas BaaS para jogos
| Piattaforma | Linguaggio Server | Open Source | Self-Hosted | Pontos fortes | Ideal para |
|---|---|---|---|---|---|
| Nakama | Go, TS, Lua | Si | Si | Real-time, matchmaking, storage | Indie, mid-size, mobile |
| Colyseus | TypeScript | Si | Si | Autoritativo, state sync automatico | Browser games, prototipi |
| PlayFab | C# (Azure Functions) | No | No | LiveOps, economia, analytics | AAA, mobile F2P |
| Amazon GameLift | C++, C# | No | No | Fleet management, FlexMatch | AAA multiplayer |
| Photon | C# | No | Parziale | Unity integration, relay | Unity games, mobile |
| Mirror | C# | Si | Si | Unity networking, HLAPI replacement | Unity indie games |
Construir vs Comprar: Critérios de Escolha
- Use um BaaS se: budget limitato, time-to-market prioritario, genere standard (casual, puzzle, card game), team piccolo
- Construir personalizado se: requisitos extremos de latência (<20ms), lógica de jogo única, necessidade de controle total sobre netcode e anti-cheat, escala >100K CCU
- Abordagem híbrida: use um BaaS para autenticação, placar e redes sociais, mas crie o servidor de jogo personalizado para a lógica do jogo
12. Métricas de desempenho: o que monitorar
Um back-end de jogo sem observabilidade e uma bomba-relógio. Estas são as métricas fundamentos que toda equipe deve monitorar na produção.
Metriche Chiave del Game Backend
| Metrica | Descrição | Target | Allarme |
|---|---|---|---|
| CCU | Concurrent Connected Users | Dipende dal genere | > 90% capacità |
| Tick Duration (p99) | Tempo de processamento do tick | < 80% del tick budget | > 90% del budget |
| RTT (p50 / p95 / p99) | Round-Trip Time client-server | p50 < 50ms, p99 < 150ms | p99 > 200ms |
| Packet Loss | Percentuale pacchetti persi | < 1% | > 3% |
| Jitter | Variação de latência | < 10ms | > 30ms |
| Messages/sec | Mensagens processadas por segundo por servidor | > 10K msg/s | < 5K msg/s |
| Largura de banda por jogador | Saída em KB/s por jogador | 5-30 KB/s | > 50 KB/s |
| Reconnection Rate | Porcentagem de reconexões bem-sucedidas | > 90% | < 70% |
| Server Startup Time | Hora de iniciar uma nova instância | < 10s | > 30s |
// Struttura per le metriche di un game server in Go
type TickMetrics struct {
TickNumber uint64
Duration time.Duration
PlayersActive int
InputsProcessed int
MessagesOut int
BytesOut int64
}
type ServerMetrics struct {
mu sync.RWMutex
tickDurations []time.Duration // ring buffer ultimi 1000 tick
totalTicks uint64
overrunCount uint64
ccu int32
}
func (m *ServerMetrics) RecordTick(metrics TickMetrics) {
m.mu.Lock()
defer m.mu.Unlock()
idx := m.totalTicks % 1000
m.tickDurations[idx] = metrics.Duration
m.totalTicks++
if metrics.Duration > tickBudget {
m.overrunCount++
log.Printf(
"TICK OVERRUN #%d: tick=%d duration=%v budget=%v players=%d",
m.overrunCount,
metrics.TickNumber,
metrics.Duration,
tickBudget,
metrics.PlayersActive,
)
}
}
// Calcola il percentile p99 della durata dei tick
func (m *ServerMetrics) P99TickDuration() time.Duration {
m.mu.RLock()
defer m.mu.RUnlock()
sorted := make([]time.Duration, len(m.tickDurations))
copy(sorted, m.tickDurations)
sort.Slice(sorted, func(i, j int) bool {
return sorted[i] < sorted[j]
})
idx := int(float64(len(sorted)) * 0.99)
return sorted[idx]
}
13. Juntando tudo: a arquitetura completa
Aqui está uma visão geral de todos os componentes de um back-end de jogo multijogador moderno e como eles interagem entre si.
+==============================================================+
| CLIENT LAYER |
+==============================================================+
| [Game Client] [Game Client] [Game Client] [Game Client] |
| | | | | |
| WebSocket/UDP WebSocket/UDP WebSocket/UDP WebSocket/UDP |
+======|==============|==============|==============|==========+
| | | |
+======|==============|==============|==============|==========+
| EDGE LAYER |
+==============================================================+
| [CDN] [DDoS Protection] [Load Balancer] [SSL Termination]|
+==========================|===================================+
|
+==========================|===================================+
| GATEWAY LAYER |
+==============================================================+
| [API Gateway] [WebSocket Gateway] |
| REST per login, Routing verso il game server |
| shop, profili corretto per la partita |
+=========|========================|===========================+
| |
+---------+----------+ +--------+--------+
| SERVICE LAYER | | GAME SERVER |
+====================+ | LAYER |
| [Auth Service] | +=================+
| [Matchmaker] | | [GS Instance 1] |
| [Leaderboard] | | [GS Instance 2] |
| [Shop/Economy] | | [GS Instance 3] |
| [Social/Chat] | | [GS Instance N] |
| [Analytics] | +=================+
+========|===========+ |
| |
+========|======================|===========================+
| DATA LAYER |
+============================================================+
| [Redis] [PostgreSQL] [TimescaleDB] [S3/GCS] |
| Sessions, Profili, Telemetria, Replay, |
| Leaderboard, Inventario, Metriche, Assets, |
| Match Queue Transazioni Analytics Backup |
+============================================================+
| |
| [Agones / GameLift] - Fleet Management & Autoscaling |
| [Prometheus + Grafana] - Monitoring & Alerting |
| [ELK / Loki] - Logging centralizzato |
+============================================================+
Próximas etapas
Neste artigo você obteve uma compreensão completa da anatomia do backend de um jogo multijogador: da arquitetura de rede aos protocolos de transporte, do loop oficial do jogo à serialização de mensagens, desde o gerenciamento de conexões até padrões de escalonamento. Você viu como cada componente interage com os outros e as compensações arquitetônicas orientar as escolhas de design.
Nel próximo artigo entraremos na seção multijogador netcode: o sincronização de estado. Exploraremos o lado do cliente em profundidade previsão, reconciliação de serviço, interpolação, compensação de atraso e reversão código vermelho. Vamos ver como os jogadores modernos criam a ilusão de uma experiência pacífica, pesar latência da rede.
Risorse Aggiuntive
- Gabriel Gambeta: "Fast-Paced Multiplayer" - A série de artigos de referência sobre arquitetura cliente-servidor para jogos
- Valve Developer Wiki: "Source Multiplayer Networking" - Documentazione tecnica sul netcode di Source Engine
- Glenn Fiedler: "Game Networking" - Serie completa su protocolli, state sync e sicurezza
- Documentação de Agones: Guia completo para orquestrar servidores de jogos no Kubernetes
- Coliseu.io: Estrutura de servidor de jogos Node.js de código aberto com sincronização automática de estado
- Nakama by Heroic Labs: Game server open-source con matchmaking, storage e real-time multiplayer







