O maior portal de MU Online do Brasil — desde 2003
Tutorial Avançado Infra

Como particionar tabelas grandes do banco de dados no seu servidor de MU Online

Aprenda a particionar tabelas grandes do MS SQL Server (LogRecords, Character, AccountCharacter) no seu servidor de MU Online para manter as consultas rápidas mesmo com milhões de linhas de log e histórico.

RO Rodrigo · Atualizado em 19 fev 2014 · ⏱ 16 min de leitura
Resposta rápida

Servidores de MU Online que rodam por meses acumulam milhões de linhas em tabelas de log, histórico de item e movimentação de conta — e é comum ver o painel administrativo, o ranking ou até o próprio GameServer travarem porque uma query simples precisa varrer uma tabela de 40 milhões de linhas. O pa

Servidores de MU Online que rodam por meses acumulam milhões de linhas em tabelas de log, histórico de item e movimentação de conta — e é comum ver o painel administrativo, o ranking ou até o próprio GameServer travarem porque uma query simples precisa varrer uma tabela de 40 milhões de linhas. O particionamento de tabelas é a técnica de banco de dados que resolve esse problema: divide fisicamente uma tabela grande em pedaços menores (partições) sem mudar a forma como a aplicação enxerga a tabela. Neste tutorial você vai aprender o que particionar, como criar partition function e partition scheme no SQL Server, como migrar uma tabela existente sem perder dados e como manter as partições ao longo do tempo com uma rotina de manutenção (sliding window).

Por que tabelas de MU Online crescem tão rápido

O MuServer registra praticamente todo evento relevante: login/logout, troca de item, uso de Chaos Machine, chat, PK, drop de boss, compra na loja de cash. Em um servidor com 500 jogadores simultâneos, a tabela LogRecords (ou equivalente do seu emulador) pode passar de 100 mil linhas por dia. Depois de seis meses isso são 18 milhões de linhas em uma única tabela, sem nenhuma segmentação física — cada SELECT com filtro de data faz um table scan completo, mesmo com índice, porque o otimizador ainda precisa decidir quais páginas ler dentro de uma estrutura monolítica.

Diagnosticando o problema antes de particionar

Antes de sair criando partições, confirme que o gargalo é realmente volume de dados. Rode o plano de execução das queries mais lentas do painel admin e do ranking e observe se aparece um Table Scan ou Clustered Index Scan em vez de Seek. Verifique também o tamanho físico da tabela com sp_spaceused 'LogRecords' e o tempo de resposta em produção com SET STATISTICS TIME ON. Se o gargalo for falta de índice, resolva isso primeiro — particionar uma tabela mal indexada só troca um problema por outro maior e mais difícil de reverter.

Escolhendo a chave de partição correta

Tabela candidataChave de partição sugeridaCritério de corte
LogRecords / MuveLogCreateDate (datetime)Mensal ou semanal
ChatLogLogDateMensal
AccountCharacter / CharacterCharacterID (range)Faixas de ID por servidor
ItemHistory / TradeLogTransactionDateSemanal, com purge após 90 dias
GuildWarLogEventDatePor temporada de evento

A regra prática: a coluna escolhida precisa aparecer no WHERE da maioria das consultas que hoje são lentas. Particionar por uma coluna que ninguém filtra não gera partition elimination nenhuma — o SQL Server continua varrendo todas as partições.

Criando a partition function e o partition scheme

No SQL Server, o particionamento nativo usa duas peças: a partition function, que define os limites (boundaries), e o partition scheme, que mapeia cada faixa para um filegroup físico.

-- Função de partição por mês (RANGE RIGHT: o limite pertence à partição seguinte)
CREATE PARTITION FUNCTION PF_LogPorMes (datetime)
AS RANGE RIGHT FOR VALUES (
    '2026-01-01', '2026-02-01', '2026-03-01',
    '2026-04-01', '2026-05-01', '2026-06-01'
);

-- Esquema de partição associando cada faixa a um filegroup
CREATE PARTITION SCHEME PS_LogPorMes
AS PARTITION PF_LogPorMes
ALL TO ([PRIMARY]);

Usar ALL TO ([PRIMARY]) simplifica o começo, jogando todas as partições no filegroup padrão. Em servidores maiores, vale criar um filegroup por partição para poder fazer backup/restore incremental de partições antigas isoladamente.

Migrando uma tabela existente para o novo esquema

Não é possível transformar uma tabela comum em particionada com um ALTER TABLE simples quando ela já tem dados e chaves. O caminho seguro é:

-- 1. Criar a tabela nova já particionada, com a mesma estrutura
SELECT * INTO LogRecords_New FROM LogRecords WHERE 1 = 0;

-- 2. Recriar o índice clustered sobre o esquema de partição
CREATE CLUSTERED INDEX CIX_LogRecords_New
ON LogRecords_New (CreateDate)
ON PS_LogPorMes (CreateDate);

-- 3. Copiar os dados em lotes (evita travar a produção)
INSERT INTO LogRecords_New
SELECT TOP (100000) * FROM LogRecords
ORDER BY CreateDate;
-- repita em lote até esgotar

-- 4. Trocar os nomes dentro de uma janela de manutenção curta
EXEC sp_rename 'LogRecords', 'LogRecords_Old';
EXEC sp_rename 'LogRecords_New', 'LogRecords';

Faça a cópia em lotes fora do horário de pico (madrugada, quando o servidor tem menos jogadores online) e só execute a troca de nomes quando a defasagem entre as duas tabelas for pequena o suficiente para copiar em segundos.

Sliding window: mantendo apenas o histórico necessário

A maior vantagem prática do particionamento em tabelas de log é o sliding window: em vez de fazer DELETE de milhões de linhas antigas (operação lenta e que gera muito log de transação), você faz SWITCH da partição inteira para uma tabela de staging e depois trunca essa tabela — operação quase instantânea.

-- Move a partição mais antiga para uma tabela vazia de mesmo schema
ALTER TABLE LogRecords SWITCH PARTITION 1 TO LogRecords_Staging;
TRUNCATE TABLE LogRecords_Staging;

-- Em seguida, cria a próxima boundary para o mês futuro
ALTER PARTITION FUNCTION PF_LogPorMes()
SPLIT RANGE ('2026-07-01');

Agende essa rotina como um job mensal no SQL Server Agent, alinhado com a política de retenção que você decidir (por exemplo, manter 6 meses de log detalhado e arquivar o resto).

Validando o ganho com partition elimination

Depois de particionar, confirme que as queries realmente usam a partição certa em vez de varrer tudo. Habilite o plano de execução e procure pelo atributo Actual Partition Count no operador de scan — ele deve mostrar 1 (ou poucas partições), não o total. Se o número de partições acessadas for igual ao total, a query não está filtrando pela chave de partição e o ganho é nulo.

SET STATISTICS IO ON;
SELECT COUNT(*) FROM LogRecords
WHERE CreateDate >= '2026-06-01' AND CreateDate < '2026-07-01';

Compare o logical reads antes e depois — a redução costuma ser de uma ordem de grandeza em tabelas grandes.

Impacto em backup e manutenção de índices

Tabelas particionadas permitem reindexar apenas as partições "quentes" (as mais recentes, com mais fragmentação por inserts constantes), em vez de reconstruir o índice inteiro toda noite. Isso reduz drasticamente a janela de manutenção em servidores com tabelas de dezenas de gigabytes.

RotinaAntes (tabela única)Depois (particionada)
Reindex diárioTabela inteira, 40+ minSó últimas 2-3 partições, 3-5 min
Backup completoCresce a cada mêsBackup por filegroup, incremental
Purge de dados antigosDELETE lento, bloqueia writesSWITCH + TRUNCATE, quase instantâneo
Consulta de ranking históricoTable scan completoPartition elimination, seek direto

Cuidados com chaves estrangeiras e replicação

Se a tabela particionada tem foreign keys referenciando ou sendo referenciada por outras tabelas do MuServer (por exemplo Character referenciando AccountCharacter), confirme que a chave de partição não quebra a integridade referencial — no SQL Server, chaves estrangeiras funcionam normalmente com tabelas particionadas, mas o planejamento de migração precisa considerar a ordem de criação e a janela de indisponibilidade de cada tabela dependente. Em ambientes com replicação (por exemplo, um banco de leitura para o site/ranking), teste a migração primeiro no ambiente de réplica.

Monitorando o crescimento das partições

Depois de implantado, monitore o tamanho de cada partição periodicamente para confirmar que a distribuição está equilibrada — uma partição desproporcionalmente grande (por exemplo, um mês com evento especial que gerou log em excesso) pode voltar a causar lentidão isolada.

SELECT p.partition_number, p.rows, au.total_pages * 8 / 1024 AS SizeMB
FROM sys.partitions p
JOIN sys.allocation_units au ON au.container_id = p.hobt_id
WHERE p.object_id = OBJECT_ID('LogRecords')
ORDER BY p.partition_number;

Erros comuns e soluções

SintomaCausa provávelSolução
ALTER TABLE trava o GameServerMigração feita em horário de picoFaça a cópia em lotes fora do pico e troque nomes em janela curta
Query continua lenta após particionarChave de partição não usada no filtroAjuste a query ou reavalie a chave escolhida
SPLIT/MERGE falha com erro de dadosBoundary sobreposta a dados existentesSempre faça SPLIT antes de a partição futura receber dados
Índices fragmentam rápidoReindex não segmentado por partiçãoReconstrua apenas as partições recentes, diariamente
Backup muito grande e lentoUm único filegroup para tudoSepare partições antigas em filegroups próprios e some backups incrementais

Checklist de particionamento

  • Identificado o gargalo real via plano de execução (não assumido de cabeça).
  • Escolhida a chave de partição alinhada aos filtros das queries mais pesadas.
  • Partition function e partition scheme criados e testados em ambiente de homologação.
  • Migração feita via tabela nova + cópia em lotes, com janela curta de troca de nomes.
  • Rotina de sliding window agendada para purge/arquivamento automático.
  • Partition elimination validado com STATISTICS IO antes/depois.
  • Monitoramento periódico do tamanho de cada partição configurado.
  • Backup e reindexação ajustados para trabalhar por partição.

Com as tabelas de log sob controle, o próximo passo natural é revisar toda a infraestrutura do servidor à luz da carga esperada — desde o dimensionamento do banco até a topologia de GameServer e ConnectServer descrita no tutorial de criação de servidor de MU Online.

Perguntas frequentes

Quando eu realmente preciso particionar uma tabela?

Quando ela passa de alguns milhões de linhas e as consultas do ranking, do painel administrativo ou dos logs de GM começam a demorar mais de 1-2 segundos. Tabelas de log (LogRecords, MuveLog, ChatLog) costumam ser as primeiras candidatas, pois crescem todo dia sem limite natural.

Particionar substitui a necessidade de índices?

Não. Particionamento e indexação resolvem problemas diferentes e se complementam. O particionamento reduz o volume de dados que uma consulta precisa varrer (partition elimination); os índices aceleram a busca dentro de cada partição. Um sem o outro entrega ganho parcial.

Posso particionar uma tabela que já está em produção sem downtime?

É possível, mas exige cuidado. A abordagem mais segura é criar uma tabela nova particionada, copiar os dados em lotes fora do horário de pico, e trocar os nomes das tabelas dentro de uma transação curta. Particionar in-place com ALTER TABLE trava a tabela por mais tempo.

Qual coluna eu uso como chave de partição?

Quase sempre uma coluna de data/hora (CreateDate, LogDate) para tabelas de log, ou o CharacterID para tabelas de dados de personagem em servidores muito grandes. A chave precisa aparecer na maioria dos filtros das queries mais pesadas, senão o particionamento não ajuda.

Particionamento funciona em qualquer edição do SQL Server?

O particionamento nativo (partition function/scheme) é recurso Enterprise nas versões mais antigas, mas está disponível também na edição Standard desde o SQL Server 2016 SP1. Confirme a versão do seu servidor antes de planejar a arquitetura.

RO
Fundador e editor-chefe

Rodrigo mantém o ViciadosMU desde os primórdios do portal. Especialista em criação e administração de servidores de MU Online, história do jogo e a evolução das seasons — escreveu boa parte do acervo antes de 2024.

Continue lendo

Artigos relacionados