Produtos secos 丨 Explicação detalhada do gerenciamento de memória DolphinDB no banco de dados de série temporal

DolphinDB é um software de banco de dados de série temporal distribuído de alto desempenho que suporta operações simultâneas multiusuário e multitarefa. O gerenciamento eficiente de memória para big data é uma das razões para seu excelente desempenho. O gerenciamento de memória envolvido neste tutorial inclui o seguinte:

  • Gerenciamento de memória variável : Fornece e recupera a memória exigida pelo ambiente de programação para os usuários.
  • Gerenciamento de cache de tabelas distribuídas : várias sessões compartilham dados da tabela de partição para melhorar o uso de memória.
  • Cache de dados de streaming : O nó de envio de dados de streaming fornece persistência e cache de fila de envio, e o nó de assinatura fornece cache de fila de dados de recebimento.
  • Cache de gravação do banco de dados DFS : os dados gravados no DFS são gravados primeiro no WAL e no cache, e a taxa de transferência é aprimorada pela gravação em lote.


1. Mecanismo de gerenciamento de memória

O DolphinDB se aplica a blocos de memória do sistema operacional e os gerencia por si mesmo. Quando o bloco de memória solicitado está ocioso, o sistema o verifica e libera periodicamente. Atualmente, a alocação de memória de vetores, tabelas e todas as strings foi incorporada ao sistema de gerenciamento de memória do DolphinDB.

Defina o uso máximo de memória do nó por meio do parâmetro maxMemSize : este parâmetro especifica a memória máxima utilizável do nó. Se a configuração for muito pequena, ela limitará severamente o desempenho do cluster. Se a configuração for muito grande, como exceder a memória física, pode fazer com que o sistema operacional desligue o processo à força. Se a memória da máquina for de 16 GB e apenas um nó for implantado, é recomendado definir este parâmetro para cerca de 12 GB.

Solicite um bloco de memória do sistema operacional em unidades de 512 MB : Quando o usuário faz perguntas sobre as operações ou alterações do programa na memória necessária, o DolphinDB aplica a memória do sistema operacional em unidades de 512 MB. Se o sistema operacional não puder fornecer um grande bloco de memória contínua, ele tentará 256 MB, 128 MB e outros blocos de memória menores.

O sistema faz uso total da memória disponível para armazenar os dados do banco de dados : Quando o uso total da memória de um nó for menor que maxMemSize, o DolphinDB armazenará os dados da partição do banco de dados tanto quanto possível para melhorar a velocidade dos usuários acessando o bloco de dados na próxima vez. Quando a memória é insuficiente, o sistema remove automaticamente parte do cache.

Faça a varredura a cada 30 segundos, e o bloco de memória livre é retornado ao sistema operacional : quando o usuário libera as variáveis ​​na memória ou libera o cache usando a função clearAllCache, se o bloco de memória estiver completamente livre, ele será retornado ao sistema operacional sistema como um todo. Parte da memória está em uso, por exemplo, se 10 MB do bloco de memória de 512 MB ainda estiver em uso, ele não será devolvido ao sistema operacional.


2. Gerenciamento de memória variável

2.1 Criar variáveis

No nó DolphinDB, primeiro crie um usuário user1 e, em seguida, efetue login. Crie um vetor, contendo 100 milhões de elementos do tipo INT, cerca de 400 MB.

Exemplo 1. Crie uma variável vetorial

 login("admin","123456")  //创建用户需要登陆admin
createUser("user1","123456")
login("user1","123456")
v = 1..100000000
sum(mem().blockSize - mem().freeSize) //输出内存占用结果

O resultado é: 402.865.056, e a memória ocupa cerca de 400 MB, o que está em linha com o esperado.

Crie uma tabela com 10 milhões de linhas, 5 colunas, 4 bytes por coluna, cerca de 200 MB.

Exemplo 2. Criar variável de tabela

n = 10000000
t = table(n:n,["tag1","tag2","tag3","tag4","tag5"],[INT,INT,INT,INT,INT])
(mem().blockSize - mem().freeSize).sum()

O resultado é: 612.530.448, cerca de 600 MB, em linha com o esperado.


2.2 Variáveis ​​de liberação

A função undef pode liberar a memória da variável.

Exemplo 3. Use a função undef ou atribua NULL à variável de liberação

undef(`v)

ou

v = NULL

Além de liberar variáveis ​​manualmente, quando a sessão é fechada, como fechar a GUI e outras conexões da API, isso acionará a reciclagem de toda a memória da sessão. Quando conectado por meio de um web notebook, se não houver operação em 10 minutos, o sistema fechará a sessão e automaticamente recuperará a memória.


3. Gerenciamento de cache de tabelas distribuídas

DolphinDB gerencia tabelas distribuídas em unidades de partições. O cache de tabelas distribuídas é compartilhado globalmente.Na maioria dos casos, diferentes sessões ou transações de leitura verão a mesma cópia de dados (a versão pode ser diferente), o que economiza muito o uso de memória.

Os bancos de dados históricos são armazenados no banco de dados na forma de tabelas distribuídas e os usuários geralmente interagem diretamente com as tabelas distribuídas para operações de consulta. O gerenciamento de memória de tabelas distribuídas tem as seguintes características:

  • A memória é gerenciada em unidades de colunas de partição.
  • Os dados são carregados apenas no nó onde estão localizados e não serão transferidos entre os nós.
  • Quando vários usuários acessam a mesma partição, o mesmo cache é usado.
  • Se o uso de memória não exceder maxMemSize, tente armazenar em cache o máximo de dados possível.
  • Quando os dados em cache alcançam maxMemSize, o sistema os recupera automaticamente.

Os exemplos a seguir são baseados no seguinte cluster: implantado em 2 nós, usando o modo de cópia única. 30 distritos são divididos por dia, cada distrito tem 10 milhões de linhas e 11 colunas (1 coluna do tipo DATE, 1 coluna do tipo INT, 9 colunas do tipo LONG), então o volume de dados de cada coluna (tipo LONG) de cada partição tem 10 milhões de linhas * 8 bytes / coluna = 80M, cada partição tem um total de 10 milhões de linhas * 80 bytes / linha = 800M, a tabela inteira tem um total de 300 milhões de linhas e o tamanho é 24 GB.

A função clearAllCache () pode limpar os dados armazenados em cache. Antes de cada teste abaixo, use esta função para limpar todos os caches no nó.


3.1 A memória é gerenciada em unidades de colunas de partição

O DolphinDB usa armazenamento em colunas.Quando os usuários consultam dados em tabelas distribuídas, o princípio de carregamento de dados é carregar apenas as partições e colunas exigidas pelo usuário na memória.

Exemplo 4. Calcule o valor tag1 máximo da partição 01/2019. A partição é armazenada em node1 e a distribuição da partição pode ser verificada por meio da função getClusterChunksStatus () no controlador, e pode ser visto acima que cada coluna tem cerca de 80 MB. Execute o seguinte código em node1 e verifique o uso de memória.

select max(tag1) from loadTable(dbName,tableName) where day = 2019.01.01
sum(mem().blockSize - mem().freeSize) 

O resultado de saída é 84.267.136. Consultamos apenas uma coluna de dados em 1 partição, portanto, todos os dados desta coluna são carregados na memória e as outras colunas não são carregadas.

Exemplo 5. Consulte os primeiros 100 dados de 2019.01.01 em node1 e observe o uso de memória.

select top 100 * from loadTable(dbName,tableName) where day = 2019.01.01
sum(mem().blockSize - mem().freeSize)

O resultado de saída é 839.255.392. Apesar de pegarmos apenas 100 dados, a menor unidade para o DolphinDB carregar dados é a coluna de partição, então você precisa carregar todos os dados em cada coluna, ou seja, todos os dados em toda a partição, cerca de 800 MB.

Nota:  Particionar corretamente para evitar "falta de memória": DolphinDB gerencia a memória em unidades de partições, portanto, o uso da memória está intimamente relacionado às partições. Se a partição do usuário for irregular, resultando em uma grande quantidade de dados em uma partição, e mesmo a memória inteira da máquina não é suficiente para acomodar toda a partição, o sistema lançará uma exceção de "falta de memória" quando se trata de o cálculo da consulta da partição. Em geral, se o usuário definir maxMemSize = 8, a soma das colunas de consulta comumente usadas em cada partição deve ser 100-200 MB. Se a tabela tiver 10 colunas de campos de consulta comumente usados, cada coluna terá 8 campos, então cada partição terá cerca de 1 a 2 milhões de linhas.


3.2 Os dados são carregados apenas para o nó onde estão

No caso de uma grande quantidade de dados, a transferência de dados entre os nós é uma operação que consome muito tempo. Os dados do DolphinDB são armazenados de forma distribuída. Ao realizar uma tarefa de computação, a tarefa é enviada para o nó onde os dados estão localizados, ao invés de transferir os dados para o nó onde o cálculo está localizado, o que reduz muito a transferência de dados entre nós e melhora a eficiência da computação.

Exemplo 6. Calcule o valor máximo de tag1 em duas partições no nó1. A matriz da partição 2019.01.02 é armazenada em node1 e os dados da partição 2019.01.03 são armazenados em node2.

select max(tag1) from loadTable(dbName,tableName) where day in [2019.01.02,2019.01.03]
sum(mem().blockSize - mem().freeSize) 

O resultado de saída é 84.284.096. O resultado da verificação do uso de memória no nó 2 é 84.250.624. Os dados armazenados por cada nó são cerca de 80M, ou seja, os dados da partição 2019.01.02 são armazenados no node1, e os dados de 2019.01.03 são armazenados no node2.

Exemplo 7. Consultar todos os dados das partições 2019.01.02 e 2019.01.03 em node1. Esperamos que node1 carregue os dados de 2019.01.02 e node2 carregue os dados de 2019.01.03. Ambos têm cerca de 800 M. Execute o seguinte código e observe a memória.

select top 100 * from loadTable(dbName,tableName) where day in [2019.01.02,2019.01.03]
sum(mem().blockSize - mem().freeSize)

A saída em node1 é 839.279.968. A saída em node2 é 839.246.496. O resultado é o esperado.

Nota:  Use "selecionar *" sem condições de filtro com cuidado, pois isso carregará todos os dados na memória. Preste atenção especial a isso quando houver muitas colunas. Recomenda-se carregar apenas as colunas necessárias. Se você usar "select top 10 *" sem condições de filtragem, todos os dados da primeira partição serão carregados na memória.


3.3 Quando vários usuários acessam a mesma partição, use o mesmo cache

DolphinDB suporta consulta simultânea de dados massivos. Para usar a memória de maneira eficiente, apenas a mesma cópia dos dados na mesma partição é mantida na memória.

Exemplo 8. Abra duas GUIs, conecte node1 e node2 respectivamente e consulte os dados da partição 01/01/2019. Os dados desta partição são armazenados em node1.

select * from loadTable(dbName,tableName) where date = 2019.01.01
sum(mem().blockSize - mem().freeSize)

Não importa quantas vezes o código acima seja executado, a exibição da memória no nó 1 é sempre 839,101,024 e não há uso de memória no nó 2. Como os dados da partição são armazenados apenas em node1, node1 carregará todos os dados, enquanto node2 não ocupa nenhuma memória.


3.4 A relação entre o uso de memória do nó e dados em cache

3.4.1 Quando o uso de memória do nó não excede maxMemSize, tente armazenar em cache o máximo de dados possível

Geralmente, os dados acessados ​​recentemente são mais fáceis de acessar novamente, portanto, o DolphinDB armazena em cache o máximo de dados possível quando a memória permite (o uso da memória não excede o maxMemSize definido pelo usuário) para melhorar a eficiência do acesso subsequente aos dados.

Exemplo 9. O maxMemSize = 8 definido pelo nó de dados. Carregue 9 partições continuamente, cada partição tem cerca de 800 MB e o uso total de memória é de cerca de 7,2 GB. Observe a tendência das mudanças de memória.

days = chunksOfEightDays();
for(d in days){
    select * from loadTable(dbName,tableName) where  = day
    sum(mem().blockSize - mem().freeSize)
}

A memória muda com o aumento do número de partições carregadas conforme mostrado na figura abaixo:

Ao percorrer os dados de cada partição, se o uso da memória não exceder maxMemSize, os dados da partição serão todos armazenados em cache na memória, de modo que, quando o usuário acessar na próxima vez, os dados serão fornecidos diretamente da memória em vez de serem carregados do disco novamente.

 

3.4.2 Quando o uso de memória do nó atinge maxMemSize, o sistema recupera automaticamente


Se a memória usada pelo servidor DolphinDB não exceder o maxMemSize definido pelo usuário, a memória não será recuperada. Quando o uso total de memória atingir maxMemSize, o DolphinDB usará a estratégia de reciclagem de memória LRU para liberar memória suficiente para os usuários.

Exemplo 10. O caso de uso acima carrega apenas 8 dias de dados. Nesse momento, continuamos a analisar 15 dias de dados para verificar o uso de memória quando o cache atinge maxMemSize. Como mostrado abaixo:

Conforme mostrado na figura acima, quando os dados em cache excedem maxMemSize, o sistema automaticamente recupera a memória e o uso total de memória ainda é menor do que a quantidade máxima de memória definida pelo usuário 8 GB.

Exemplo 11. Quando os dados em cache estão próximos do maxMemSize definido pelo usuário, continue a se inscrever para o espaço de memória da variável Session e verifique o uso de memória do sistema. Primeiro verifique o uso de memória do sistema neste momento:

sum(mem().blockSize - mem().freeSize)

O resultado de saída é 7.550.138.448. A memória ocupa mais de 7 GB, e o uso máximo de memória definido pelo usuário é de 8 GB, neste momento continuamos a solicitar 4 GB de espaço.

v = 1..1000000000
sum(mem().blockSize - mem().freeSize)

O resultado de saída é 8.196.073.856. Cerca de 8GB, ou seja, se o usuário definir uma variável, também acionará a recuperação da memória dos dados em cache para garantir que haja memória suficiente para o usuário utilizar.


4. Fila de buffer de mensagem de dados de streaming

Quando os dados entram no sistema de dados de streaming, eles são primeiro gravados na tabela de fluxo, depois na fila persistente e na fila de envio (supondo que o usuário esteja configurado para persistência assíncrona), a fila persistente é gravada de forma assíncrona no disco e a fila de envio é enviado ao assinante.

Quando o assinante recebe os dados, ele primeiro os coloca na fila de recebimento e, em seguida, o manipulador definido pelo usuário busca os dados da fila de recebimento e os processa. Se o manipulador for lento, isso fará com que os dados se acumulem na fila de recebimento e ocupem memória. Como mostrado abaixo:

Opções de configuração relacionadas à memória de dados de streaming:

  • maxPersistenceQueueDepth : O número máximo de mensagens na fila persistente da tabela de fluxo. Para a tabela de fluxo de publicação persistente de forma assíncrona, os dados são primeiro colocados na fila persistente e, em seguida, persistente de forma assíncrona no disco. Esta opção é definida como 10 milhões por padrão. Quando a gravação em disco se torna um gargalo, a fila acumula dados.
  • maxPubQueueDepthPerSite : Profundidade máxima da fila de publicação de mensagens. Para um determinado nó de assinatura, o nó de publicação estabelece uma fila de publicação de mensagens e as mensagens na fila são enviadas ao assinante. O valor padrão é 10 milhões. Quando a rede está congestionada, a fila de envio acumula dados.
  • maxSubQueueDepth : A maior profundidade da fila de cada encadeamento de assinatura que pode receber mensagens no nó de assinatura. A mensagem assinada será colocada na fila de mensagens de assinatura primeiro. A configuração padrão é 10 milhões.Quando a velocidade de processamento do manipulador for lenta e não puder processar as mensagens assinadas a tempo, a fila acumulará dados.
  • Capacidade da tabela de fluxo : O quarto parâmetro é especificado na função enableTablePersistence (). Este valor indica o número máximo de linhas armazenadas na memória da tabela de fluxo. Quando esse valor é atingido, metade dos dados é excluída da memória . Quando há muitas tabelas de fluxo no nó de dados de fluxo, o valor deve ser definido razoavelmente como um todo para evitar memória insuficiente.

Durante a operação, você pode usar a função getStreamingStat () para visualizar o tamanho da tabela de fluxo e a profundidade de cada fila.


5. Fornece um cache para gravação no banco de dados DFS

Para melhorar o rendimento de leitura e escrita e reduzir a latência de leitura e escrita, o DolphinDB adota a prática comum de escrever primeiro no WAL e no cache e, quando o acúmulo atinge um determinado valor, escrever em lote. Isso reduz o número de interações com arquivos de disco, melhora o desempenho de gravação e pode aumentar a velocidade de gravação em mais de 30%. Portanto, uma certa quantidade de espaço de memória também é necessária para armazenar temporariamente esses dados em cache, conforme mostrado na figura a seguir:

Quando as transações t1, t2 e t3 são concluídas, os dados das três transações são gravados no disco do banco de dados DFS de uma vez. O espaço do Cache Engine é geralmente recomendado para ser 1/8 ~ 1/4 de maxMemSize, que pode ser ajustado de acordo com a memória máxima e a quantidade de dados gravados. O tamanho do CacheEngine pode ser configurado por meio do parâmetro de configuração chunkCacheEngineMemSize.

  • chunkCacheEngineMemSize : Especifique a capacidade do mecanismo de cache. Depois que o mecanismo de cache é ligado, ao gravar dados, o sistema irá gravar os dados no cache primeiro.Quando a quantidade de dados no cache atingir 30% de chunkCacheEngineMemSize, eles serão gravados no disco.


6. Use a memória de forma eficiente

No ambiente de produção das empresas, o DolphinDB é frequentemente usado como um data center de streaming e data warehouse histórico para fornecer consulta de dados e cálculos para o pessoal de negócios. Quando há muitos usuários, o uso impróprio pode fazer com que o servidor fique sem memória e lance uma exceção de "sem memória". As sugestões a seguir podem ser seguidas para evitar o uso irracional de memória.

  • Particionar de maneira razoável e uniforme : o DolphinDB carrega dados em partições, então o tamanho da partição tem um grande impacto na memória. O particionamento razoável e uniforme tem um efeito positivo no uso e no desempenho da memória. Portanto, ao criar um banco de dados, planeje o tamanho da partição razoavelmente de acordo com o tamanho dos dados. A quantidade de dados de campo comumente usados ​​em cada partição é de cerca de 100 MB.
  • Libere variáveis ​​com uma grande quantidade de dados no tempo : se o usuário criar uma variável com uma grande quantidade de dados, como v = 1..10000000, ou atribuir um resultado de consulta contendo uma grande quantidade de dados a uma variável t = selecionar * de t onde data = 01.01.2010, v e t ocuparão muita memória na sessão do usuário. Se não for lançado a tempo, quando outros usuários solicitarem memória, uma exceção pode ser lançada devido à memória insuficiente.
  • Consulte apenas as colunas que você precisa : Evite usar select *. Se você usar select *, todas as colunas da partição serão carregadas na memória. Na prática, apenas algumas colunas são frequentemente necessárias. Portanto, para evitar o desperdício de memória, tente escrever claramente todas as colunas de consulta em vez de usar *.
  • A consulta de dados usa condições de filtro de partição tanto quanto possível : DolphinDB executa a recuperação de dados de acordo com as partições. Se as condições de filtro de partição não forem adicionadas, todos os dados serão verificados. Quando a quantidade de dados for grande, a memória se esgotará rapidamente. Se houver várias condições de filtro, grave as condições de filtro de partição primeiro.
  • Libere as variáveis ​​ou sessões que não são mais necessárias o mais rápido possível : De acordo com a análise acima, as variáveis ​​privadas do usuário são armazenadas na sessão criada. Essa memória será recuperada quando a sessão for encerrada. Portanto, use a função undef ou feche a sessão o mais rápido possível para liberar a memória.
  • Configure razoavelmente a área de buffer dos dados de streaming : Geralmente, a capacidade dos dados de streaming afetará diretamente o uso de memória do nó de publicação. Por exemplo, se a capacidade for configurada para 10 milhões, quando a tabela de dados de fluxo ultrapassar 10 milhões, cerca da metade do uso da memória será recuperada, ou seja, cerca de 5 milhões serão retidos na memória. Portanto, a capacidade da tabela de fluxo deve ser projetada razoavelmente de acordo com a memória máxima do nó de publicação. Especialmente no caso de várias tabelas de liberação, é necessário um design cuidadoso.


7. Monitoramento de memória e problemas comuns


7.1 Monitoramento de memória


7.1.1 Monitore o uso de memória de nós no cluster no controlador

Forneça a função getClusterPerf () no controlador para exibir o uso de memória de cada nó no cluster. incluir:

MemAlloc : A memória total alocada no nó, que é semelhante à memória total solicitada do sistema operacional.

MemUsed : Memória usada pelo nó. Essa memória inclui variáveis, caches de tabelas distribuídas e várias filas de cache.

MemLimit : O limite máximo de memória que o nó pode utilizar, ou seja, o maxMemSize configurado pelo usuário.

 

7.1.2 A função mem () monitora o uso de memória de um nó

A função mem () pode exibir a alocação e ocupação de memória de todo o nó. A função produz 4 colunas. A coluna blockSize representa o tamanho do bloco de memória alocado e freeSize representa o tamanho do bloco de memória restante. O tamanho total da memória usado pelo nó é obtido por meio de sum (mem (). BlockSize-mem (). FreeSize) .


7.1.3 Monitorar o uso de memória de diferentes sessões no nó

Você pode usar a função getSessionMemoryStat () para visualizar a quantidade de memória ocupada por cada sessão no nó. A memória contém apenas as variáveis ​​definidas na sessão. Quando o uso de memória em um nó é muito alto, você pode usar esta função para verificar qual usuário usa muita memória.


7.1.4 Ver o tamanho da memória ocupada por um objeto

Use a função memSize para ver o tamanho específico da memória ocupada por um objeto, em bytes. tal como:

v=1..1000000
memSize(v)

Saída: 4000000.


7.2 Perguntas mais frequentes


7.2.1 O monitoramento mostra que o uso de memória do nó é muito alto

Por meio da análise acima, pode-se ver que o DolphinDB armazenará em cache o máximo de dados possível quando a memória permitir. Portanto, se ele mostrar apenas que o uso de memória do nó é muito alto, próximo a maxMemSize, e não há outros erros relacionados à memória, então esta situação é normal. Se um erro semelhante como "falta de memória" ocorrer, primeiro use a função getSessionMemoryStat () para visualizar o tamanho da memória ocupada por cada sessão e, em segundo lugar, use a função clearAllCache () para liberar manualmente os dados em cache do nó.

7.2.2 O valor exibido de MemAlloc é diferente do valor real exibido do sistema operacional

DolphinDB é um programa C ++, que por si só requer alguma estrutura básica de dados e sobrecarga de memória. MemAlloc exibe memória que não inclui esta memória. Se a diferença entre os dois monitores não for grande, dentro de algumas centenas de MB, é normal.


7.2.3 Ao consultar, relate "sem memória"

Essa exceção geralmente é causada pelo fato de a memória exigida pela consulta ser maior do que a memória fornecida pelo sistema. Isso pode ser causado pelos seguintes motivos:

  • A consulta não adiciona condições de filtro de partição ou as condições são muito amplas, resultando em uma grande quantidade de dados envolvidos em uma única consulta.
  • A partição é irregular. Talvez uma partição seja muito grande e os dados na partição excedam a memória máxima configurada no nó.
  • Uma determinada sessão contém grandes variáveis, resultando em uma memória muito pequena disponível para o nó.

7.2.4 Ao consultar, o processo DolphinDB sai e nenhum coredump é gerado

Essa situação geralmente ocorre porque a memória alocada ao nó excede o limite da memória física do sistema e o sistema operacional força o DolphinDB a sair. No Linux, você pode ver o motivo por meio do log do sistema operacional.


7.2.5 Depois de executar a função clearAllCache (), MemUsed não é significativamente reduzido

Você pode ver a quantidade de memória ocupada por cada sessão por meio de getSessionMemoryStst (). Pode ser que uma determinada sessão contenha uma variável que ocupe uma grande quantidade de memória e não seja liberada, fazendo com que parte da memória não possa ser reciclada.

Acho que você gosta

Origin blog.csdn.net/qq_41996852/article/details/112553436
Recomendado
Clasificación