Design de banco de dados para lidar com 1 bilhão de linhas e contando

10

Recebemos dados de GPS em tempo real a uma taxa de cerca de 5000 pr. minuto (de 4 servidores TCP). Cada servidor usa uma conexão única para inserir os dados e armazena em buffer os dados entre as inserções. A cada 15 minutos, aproximadamente, um serviço busca esses dados e os processa em viagens. Depois que as viagens são geradas, os dados reais do GPS geralmente não são tão importantes, apenas se o usuário quiser ver a rota em um mapa.

O problema é que parece que o banco de dados está lutando para acompanhar a taxa de dados inseridos. Às vezes, quando a carga aumenta, o tempo de inserção aumenta repentinamente drasticamente (> 30 segundos), o que, por sua vez, permite que mais dados sejam armazenados em buffer, o que resulta em pastilhas maiores e maior duração da pastilha.

Espero receber alguns comentários sobre o design atual e algumas das idéias que temos para melhorar o desempenho, além de respostas para algumas de nossas perguntas - e outras dicas que as pessoas possam ter!

Design atual

Atualmente, os dados são separados em tabelas que representam uma semana e os dados anteriores a um ano são arquivados em um banco de dados secundário. A coisa toda é unida em uma exibição editável, usada para inserções e leituras.

Design da mesa

  • ID (PK, identificador exclusivo)
  • DeviceId (FK, int)
  • PersonId (FK, int)
  • ID do veículo (FK, int)
  • TokenId (FK, int)
  • UtcTime (PK, datetime2 (3))
  • Latitude (flutuante)
  • Longitude (flutuante)
  • Velocidade (smallint)
  • Título (smallint)
  • Satélites (tinyint)
  • IOData (varbinário (100))
  • IgnitionState (tinyint)
  • UserInput (tinyint)
  • CreateTimeUtc (datetime2 (3))

Índices

  • DeviceId_CreateTimeUtc_Desc
  • DeviceId_UtcTime_Desc (agrupado)
  • PersonId_UtcTime_Desc
  • TokenId_UtcTime_Desc
  • VehicleId_UtcTime_Desc

Atualmente, toda semana ocupa cerca de 10 GB, incluindo índices, e atualmente existem cerca de 300 GB de dados no banco de dados principal.

As tabelas de dados no banco de dados principal têm seu próprio grupo de arquivos com 1 arquivo, mas estão no mesmo disco que todas as outras tabelas no banco de dados principal. O banco de dados secundário está em um disco diferente, mas na mesma máquina.

Acho que também estamos executando um trabalho de reconstrução do índice semanalmente, quando uma nova partição de tabela (semana) é usada. Nenhum encolhimento é realizado.

A máquina é um HP de 8 núcleos com 12 GB de memória e o disco que contém o banco de dados principal está executando o RAID 10.

Ideias

  • Limite a quantidade de dados armazenados no banco de dados primário para, por exemplo, no máximo 1 mês. No mínimo, isso tornaria o banco de dados mais gerenciável para backup / restauração, mas poderíamos esperar uma melhoria no desempenho fazendo isso?
  • Crie 2 arquivos no grupo de arquivos para dados atuais e distribua-os em 2 partições físicas diferentes
  • Crie bancos de dados mestre-escravo com dados atuais, para que inserções e leituras sejam executadas em diferentes bancos de dados
  • Colocar arquivos para dados atuais em discos SSD (o espelhamento faria alguma diferença de desempenho com os discos SSD?)

Entre em contato se precisar de mais informações. Há terrivelmente muitos fatores que influenciam o desempenho e, provavelmente, muitas maneiras de ajustá-lo.

sondergard
fonte
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Paul White 9

Respostas:

8

5000 inserções por minuto são cerca de 83 inserções por segundo. Com 5 índices, são 400 linhas físicas inseridas por segundo. Se a carga de trabalho estivesse na memória, isso não seria um problema, mesmo para o menor dos servidores. Mesmo se essa fosse uma inserção linha por linha, da maneira mais ineficiente possível. 83 consultas triviais por segundo simplesmente não são interessantes do ponto de vista da CPU.

Provavelmente, você está ligado ao disco. Você pode verificar isso consultando as estatísticas de espera ou STATISTICS IO.

Suas consultas provavelmente tocam muitas páginas diferentes, para que o buffer pool não tenha espaço para todas elas. Isso causa leituras freqüentes de páginas e provavelmente gravações aleatórias em disco também.

Imagine uma tabela na qual você apenas insere fisicamente no final por causa de uma chave cada vez maior. O conjunto de trabalho seria uma página: a última. Isso geraria E / S sequencial, assim como o processo preguiçoso do gravador ou do ponto de verificação grava o "final" da tabela no disco.

Imagine uma tabela com inserções colocadas aleatoriamente (exemplo clássico: uma chave guid). Aqui, todas as páginas são o conjunto de trabalho porque uma página aleatória será tocada para cada inserção. IOs são aleatórios. Este é o pior caso quando se trata de conjunto de trabalho.

Você está no meio. Seus índices são da estrutura (SomeValue, SequentialDateTime). O primeiro componente randomiza parcialmente a sequencialidade fornecida pelo segundo. Eu acho que existem alguns valores possíveis para " SomeValue" para que você tenha muitos pontos de inserção aleatoriamente inseridos em seus índices.

Você diz que os dados são divididos em tabelas de 10 GB por semana. Esse é um bom ponto de partida, porque o conjunto de trabalho agora é limitado por 10 GB (desconsiderando qualquer leitura que você possa fazer). Com 12 GB de memória do servidor, porém, é improvável que todas as páginas relevantes possam permanecer na memória.

Se você pudesse reduzir o tamanho das "partições" semanais ou aumentar um pouco a memória do servidor, provavelmente está bem.

Eu esperava que as inserções no início da semana fossem mais rápidas que no final. Você pode testar essa teoria em um servidor de desenvolvimento executando uma referência com um determinado tamanho de dados e reduzindo gradualmente a memória do servidor até ver o tanque de desempenho.

Agora, mesmo que todas as leituras e gravações caibam na memória, você ainda poderá ter E / S liberando aleatoriamente páginas sujas. A única maneira de se livrar disso é escrever em posições co-localizadas nos seus índices. Se você puder converter seus índices para usar (mais) chaves seqüenciais que ajudariam muito.

Como solução rápida, eu adicionaria uma camada de buffer entre os clientes e a tabela principal. Talvez acumule 15 minutos de gravações em uma tabela intermediária e limpe-a periodicamente. Isso elimina os picos de carga e usa um plano mais eficiente para gravar na grande mesa.

usr
fonte
11
@usr Obrigado pela resposta muito abrangente e bem explicada! Na verdade, discutimos o aumento da memória do servidor, sem saber o efeito que isso teria - mas agora temos um motivo muito convincente para fazê-lo :) Você está certo que o "SomeValue" randomiza parcialmente os pontos de inserção - provavelmente existem cerca de 10000 IDs de dispositivo. Em relação à tabela de preparação, sua sugestão é uma tabela sem índices e, em seguida, um trabalho para inserir na tabela principal a cada X minutos?
Sondergard
@usr Reg. sua sugestão para converter o índice em cluster para ser seqüencial, poderíamos adicionar um aumento automático. coluna de identidade (inteiro) e altere o índice clusterizado para esta coluna com o único objetivo de mantê-lo seqüencial? Não seria exclusivo nas tabelas, mas enquanto a chave primária for, devemos ficar bem.
Sondergard
11
Se a tabela intermediária for pequena e suas consultas puderem conviver com ela, você não precisará indexar nada. Mas você poderia .; Uma estratégia seria criar o IC em uma coluna de identidade (como você diz). Isso pode fazer maravilhas se o IC for grande e os outros índices forem pequenos. Como as gravações de IC são agora seqüenciais, elas contribuem muito menos para o seu problema. Essa estratégia é mais bem-sucedida se houver uma diferença significativa de tamanho .; Outra idéia seria ter uma mesa por dia. Talvez mesclar mensalmente.
usr
Ok, então analisamos a criação da coluna de identidade para o IC, mas infelizmente não é possível em uma exibição particionada (nenhuma coluna de identidade é permitida, nenhum valor padrão e todas as colunas devem ser incluídas na inserção). Talvez a visão partioned era um projeto mal escolhido, apesar de ter sido recomendado por um consultor
Søndergård
2
Sério, porém, para quem enfrenta o mesmo problema, se você tem muitas gravações e apenas algumas leituras, você realmente deseja anexar no final e atrasar qualquer indexação. Por outro lado, se você deseja leituras rápidas e não se importa quanto tempo leva para inserir, precisa de um índice em cluster.
tiktak