Como colocar centenas de bilhões de arquivos em um sistema de arquivos, a história por trás do documento EuroSys'23 CFS

foto

guia

Esta é uma história de inovação tecnológica.

Sob a pressão de negócios reais e a inspiração de ideais técnicos, com um mapa vago, as duas equipes técnicas do Baidu Canghai·Storage CFS e TafDB partiram para a terra de ninguém, procurando maneiras de desbloquear "100 bilhões de arquivos, o armazenamento de arquivos sistema ainda é a chave para manter o alto desempenho".

A surpresa trazida pela nova estrutura após um pequeno teste do helicóptero não durou muito, e foi bloqueada pelas montanhas à minha frente. Voltei ao ponto de partida ou continuei avançando...

Como disse o autor deste artigo, o objetivo de contar a história por trás do papel é ajudar os leitores a entender melhor o resultado inovador em si e também fornecer referência para leitores que estão em processo de inovação. Espero que você encontre a chave O mais breve possível.

O texto completo tem 18.851 palavras e o tempo de leitura previsto é de 22 minutos.

I. Introdução

O principal objetivo deste artigo é interpretar o artigo "CFS: Scaling Metadata Service for Distributed File System via Pruned Scope of Critical Sections" publicado pela Baidu Canghai Storage Team na EuroSys 2023. O texto completo do artigo pode ser encontrado em https: //dl.acm.org/doi /10.1145/3552326.3587443 Baixar.

O documento revela o design principal do sistema de metadados do Baidu Intelligent Cloud File Storage CFS e responde ao problema de compatibilidade POSIX e alta escalabilidade (especialmente escalabilidade de gravação) que há muito atormentam o campo de metadados do sistema de arquivos. Esta é uma questão-chave se um sistema de arquivos distribuído em grande escala pode se expandir para dezenas de bilhões ou mesmo centenas de bilhões de arquivos, mantendo alto desempenho e estabilidade.

Por ser um gênero altamente condensado, a tese não informa ao leitor como essas inovações foram concebidas, a não ser para demonstrar a análise necessária. Acreditamos que esclarecer todo o processo dessa inovação não é apenas útil para entender o artigo em si, mas também um complemento importante para o conteúdo do artigo . Com base nessa consideração, organizamos o conteúdo de todo o artigo de acordo com a seguinte estrutura:

  • Primeiro, apresentamos brevemente o histórico do problema de metadados do sistema de arquivos e a exploração do problema na indústria e, especialmente, realizamos uma análise quantitativa do problema de escalabilidade de gravação no qual este documento se concentra;

  • Em seguida, introduzindo a história da evolução da arquitetura de metadados CFS, mostramos aos leitores o processo de nossa exploração e nosso pensamento sobre a essência das questões de metadados e, finalmente, conduzimos à arquitetura do sistema apresentada no artigo;

  • Por fim, detalhamos o projeto geral da arquitetura do papel e seus principais detalhes.

2. Antecedentes

2.1 O conceito de sistema de arquivos

A definição de um sistema de arquivos é um método de armazenamento e organização de dados de computador em uma estrutura de árvore, que geralmente é chamada de namespace hierárquico (Hierarchical Namespace). Conforme mostrado na figura abaixo, o recurso desse namespace é que toda a estrutura é como uma árvore invertida e os nós não folha podem ser apenas diretórios. Se você não considerar links simbólicos e hard links, começando no nó raiz (/), cada item de diretório (tipo legal, como diretório, arquivo, link flexível) pode ser alcançado por um caminho exclusivo.

foto

O sistema de arquivos pode ser dividido em duas partes: metadados (Metadata) e dados (Data). A parte de dados refere-se a qual conteúdo é armazenado em um arquivo, e a parte de metadados refere-se à própria estrutura hierárquica da árvore de namespace. Por exemplo, se queremos ler os dados do arquivo /a/b/file, a parte de metadados é responsável por encontrar onde o arquivo /a/b/file está armazenado, e a parte de dados é responsável por ler o conteúdo de o arquivo.

Existem dois estilos principais de implementação de sistemas de arquivos, POSIX e HDFS:

  • POSIX: O nome completo é Portable Operating System Interface, que é um padrão de compatibilidade de sistema operacional portátil UNIX formulado pelo IEEE. Este padrão define um subconjunto de interfaces relacionadas ao sistema de arquivos e é o padrão mais básico e autoritário no campo de sistemas de arquivos. Um sistema de arquivos compatível com POSIX refere-se a um sistema de arquivos compatível com este padrão;

  • HDFS: Originado da ecologia de big data do Hadoop, algumas simplificações e modificações práticas foram feitas no padrão POSIX, principalmente desistindo do suporte para hardlink e gravação aleatória e adicionando algumas operações recursivas práticas. Também é geralmente classificado como um sistema de arquivos do tipo POSIX, ou seja, semelhante ao POSIX.

Não há uma linha divisória clara entre os sistemas de arquivos estilo POSIX e HDFS, e eles são interoperáveis ​​em termos de tecnologia de implementação. No uso real, eles também podem ser substituídos por uma simples conversão de interface. Claro, essa conversão será baseada na premissa de sacrificar certa compatibilidade. O sistema descrito no artigo é do estilo POSIX, mas os resultados da pesquisa também são aplicáveis ​​aos sistemas do estilo HDFS, e até mesmo o HopsFS e o InifiniFS usados ​​para comparação no artigo são ambos sistemas do estilo HDFS.

2.2 Abstração de Problemas de Metadados do Sistema de Arquivos

foto

No processo de exploração dos problemas de metadados, estabelecemos gradualmente a abstração dos problemas de metadados do sistema de arquivos, que serão introduzidos aqui primeiro para facilitar o conteúdo subsequente. Um sistema de metadados do sistema de arquivos que implementa corretamente essa abstração resolve uma parte central do problema de compatibilidade POSIX.

Essa abstração significa que um espaço de nomes POSIX padrão no nível do sistema de arquivos é necessário para implementar a seguinte semântica:

  • Requisitos de operação de gravação

  • Alteração associada: todas as operações precisam implementar a alteração associada (nº 1), ou seja, atualizar as informações de atributo necessárias do diretório pai enquanto adiciona e exclui subitens no diretório. Mudanças de associação precisam ser completadas atomicamente, ou seja, tudo ou nada, ou mudam todas, ou nenhuma muda;

  • renomear: A função de renomear é renomear as entradas do diretório. Esta é a operação mais complicada em todo o sistema de arquivos. Além de atender aos requisitos das alterações associadas, existem muitos outros requisitos complicados. Consulte a definição do padrão POSIX por você mesmo.

  • ler solicitação de operação

  • Clique para ler: incluindo pesquisa de caminho (lookup, número 2) e atributo de aquisição de inode (getattr, número 3):

  • Pesquisa de caminho: é usado para encontrar o item de diretório correspondente nível por nível. A entrada de uma única operação de pesquisa de caminho de pesquisa é o inode do diretório pai + o nome do item filho e o valor de retorno é o inode do item de diretório. inode é um número que marca exclusivamente uma entrada de diretório em um sistema de arquivos, geralmente um número inteiro de 64 bits;

  • Obter atributos: obter informações de atributo do inode especificado, informações de atributo incluem tamanho, nlinks, ctime, mtime, etc.

  • Faixa de leitura: travessia do diretório (readdir, número 4), obter uma lista de todos os subitens em um diretório, por meio de uma série de chamadas readdir, retornar uma parte dos subitens a cada vez, até que todos os subitens sejam obtidos.

Para a abstração acima, fazemos ainda a seguinte explicação complementar:

  • A essência das alterações de associação é refletir com precisão as alterações na lista de subitens

  • Por um lado, está relacionado com a consistência dos metadados. Por exemplo, ao excluir um diretório, é necessário determinar se o diretório não está vazio. Em muitos sistemas, um campo é mantido para registro. Se as informações neste campo ficarem atrasadas, existe a possibilidade de julgamento incorreto. Quando um diretório não vazio é julgado erroneamente como um diretório vazio, o próprio diretório será excluído e os subitens sob o diretório permanecerão. Esses filhos residuais são chamados de nós órfãos, que não podem ser acessados ​​por meio de pesquisa de caminho;

  • Por outro lado, está relacionado com a correção e eficiência do cache. Para otimizar o desempenho dos metadados do sistema de arquivos, o cliente geralmente armazena em cache os resultados das operações de pesquisa e readdir e, em seguida, pode determinar rapidamente se o diretório pai foi modificado obtendo as informações de atributo do diretório pai ao acessar novamente, para que para confirmar se o cache é válido. Sem esse mecanismo, as operações devem ser reexecutadas incondicionalmente para garantir que os dados estejam corretos ou tolerar possíveis atrasos nas informações. Esse mecanismo de cache é especialmente importante para o efeito readdir de diretórios grandes, que pode economizar muita sobrecarga de E/S causada pela execução repetida de readdir de diretórios grandes.

  • Dois requisitos de índice implícitos em operações de leitura

  • <inódio do diretório pai, nome do filho> índice: Este índice precisa suportar leitura de ponto e leitura de intervalo, correspondendo a pesquisa e readdir respectivamente. Deve-se notar que o POSIX não estipula que os resultados de readdir devem ser retornados em ordem alfabética, ele só precisa obter retornos não repetitivos e não omissos internal cursor (marker);

  • Índice: Este índice só precisa suportar a leitura de pontos, correspondendo à solicitação getattr para obter informações de atributos.

2.3 Serviço de metadados do sistema de arquivos distribuído

Mais de 20 anos atrás, com o surgimento de sistemas de arquivos distribuídos, como GFS e HDFS, a arquitetura de metadados e separação de dados tornou-se gradualmente o consenso dominante no campo de sistemas de arquivos distribuídos. Essa arquitetura divide todo o sistema em duas partes: Metadata Service responsável pelos metadados e Data Service responsável pelos dados.

foto

Para serviços de dados, como os dados de diferentes arquivos são independentes entre si, diferentes partes do mesmo arquivo são facilmente divididas e processadas em blocos, o que naturalmente possui condições para processamento paralelo, facilitando a expansão em grande escala. Mas para serviços de metadados, a estrutura de diretório do namespace hierárquico traz fortes dependências pai-filho, e o processamento paralelo não é uma tarefa fácil.

Normalmente usamos os seguintes indicadores para medir de forma abrangente os prós e contras de uma implementação de serviço de metadados :

  • Escalabilidade: Para medir o quanto uma implementação pode escalar, ela pode ser subdividida em dois indicadores:

  • Escalabilidade de escala: refere-se a quantos itens de diretório o sistema pode armazenar, centenas de milhões, dezenas de bilhões ou centenas de bilhões. Como a proporção de arquivos é a maior, esse indicador geralmente é chamado de número de arquivos suportados pelo sistema;

  • Escalabilidade de desempenho: ao adicionar nós, em que magnitude o OPS de leitura e gravação dos metadados do sistema pode ser expandido e se há uma relação linear entre o OPS de leitura e gravação e o tamanho do nó.

  • Latência: mede quanto tempo leva para uma única requisição ser processada;

  • Equilíbrio: Para medir se o sistema tem a capacidade de dispersar pontos quentes, de modo que a pressão de processamento de diferentes nós seja aproximadamente equilibrada.

Para escalabilidade e balanceamento, deve-se ressaltar que, embora muitas implementações pensem ter uma escalabilidade muito boa, de acordo com o princípio do barril, o shortboard de escalabilidade é determinado pelo nó com o pior desempenho. Portanto, se o equilíbrio de um sistema não for bem feito, o desempenho real da escalabilidade não será particularmente bom.

O desenvolvimento da arquitetura de serviços de metadados passou por três etapas na indústria:

Fase 1: arquitetura de metadados de ponto único

Os arquivos individuais armazenados no sistema de arquivos distribuído inicial são relativamente grandes e o número de arquivos não excederá centenas de milhões. O serviço de metadados de ponto único pode atender totalmente à demanda. Sistemas como GFS e HDFS são representantes desta etapa.

Este único ponto é apenas lógico. Fisicamente, ainda haverá vários nós de espera para serviços de metadados, que serão alternados automaticamente em caso de falha para garantir a continuidade do serviço. O sistema neste estágio obviamente não tem escalabilidade e equilíbrio, mas o desempenho de atraso é relativamente bom.

foto

Fase 2: Arquitetura de metadados distribuídos (acoplada)

Com a popularidade de novas cargas de trabalho, como IA, os arquivos armazenados em sistemas de arquivos modernos estão ficando cada vez menores, mas o número de arquivos está aumentando. O tamanho do arquivo de um único sistema de arquivos pode ser de apenas dezenas de KB, mas o número de arquivos pode chegar a bilhões. O limite de uma arquitetura de ponto único é armazenar e processar centenas de milhões de arquivos, que não podem mais atender às necessidades da época. Portanto, os serviços de metadados distribuídos de vários nós escaláveis ​​tornaram-se uma obrigação. Federação HDFS, Lustre DNE1/DNE2, CephFS, BeeGFS e outros sistemas foram todos desenvolvidos nesta fase.

O serviço de metadados distribuídos neste estágio é uma expansão horizontal na arquitetura de ponto único, e cada nó é responsável pelo armazenamento de dados e processamento semântico do sistema de arquivos ao mesmo tempo. Para distingui-la da arquitetura separada posteriormente, este artigo se refere a essa arquitetura como uma arquitetura acoplada.

Os sistemas de arquitetura acoplada geralmente usam fragmentação de subárvore ou fragmentação de hash para distribuir todo o namespace hierárquico para diferentes nós, o que pode dispersar metadados de maneira aproximadamente uniforme em termos de volume de dados, mas os metadados são determinados quando são criados. Devido à sua localização física, balanceamento de carga dinâmico não pode ser alcançado e o problema dos pontos de acesso de dados não pode ser bem resolvido.

Embora muitos estudos tenham proposto várias soluções de balanceamento de carga e alguns códigos de sistema de código aberto também ofereçam suporte a esse recurso, não há casos reais de produção. Em última análise, como a arquitetura de metadados está acoplada à lógica de armazenamento e processamento de dados ao mesmo tempo, o processo de migração dinâmica deve não apenas concluir a realocação de dados, mas também garantir que o processo de processamento não seja interrompido ou o tempo de interrupção seja extremamente curto (vários segundos), e a implementação de engenharia é extremamente difícil. grande.

Quando uma operação requer a participação de vários nós, como garantir a consistência dos metadados é um problema relativamente difícil. Em termos de implementação, é necessário introduzir um mecanismo de bloqueio distribuído complexo e sujeito a erros envolvendo vários nós (Luster DNE2, BeeGFS , CephFS). O primeiro tem uma grande sobrecarga durante os conflitos, o que causa um gargalo na escalabilidade de gravação do sistema (a análise quantitativa será fornecida posteriormente), enquanto o último requer transformação de negócios para perceber a distribuição de dados subjacentes.

O objetivo de introduzir bloqueios distribuídos é implementar corretamente as alterações de associação. Um dos principais requisitos nas alterações de associação é a atomicidade. Quando os dados que precisam ser alterados estão espalhados por vários nós, o bloqueio distribuído pode garantir que as alterações entrem em vigor ao mesmo tempo ou não ao mesmo tempo. Obviamente, implementar um mecanismo de bloqueio distribuído que possa tolerar corretamente várias exceções também é uma tarefa técnica com certas dificuldades e não será expandida porque tem pouco a ver com este artigo.

Em geral, o sistema neste estágio resolveu o problema de escalabilidade até certo ponto e pode armazenar bilhões de arquivos. O índice de atraso é melhor quando a solicitação não cruza os nós, mas a escalabilidade de gravação e o desempenho do equilíbrio não são muito. bom.

foto

Fase 3: Arquitetura de Metadados Separada

Existe uma metodologia clássica na área de informática:

Qualquer problema na ciência da computação pode ser resolvido por outra camada de indireção.

Qualquer problema em ciência da computação pode ser resolvido adicionando uma camada intermediária de indireção.

O método específico desta metodologia é a camada de sistemas complexos, cada camada se concentra na resolução de problemas em um campo ao extremo e, finalmente, sobrepõe diferentes camadas para alcançar funções completas. Esta é uma rotina testada pelo tempo no campo do computador. O clássico modelo de rede OSI de 7 camadas e a arquitetura de separação de computação de armazenamento popular nos campos de big data e bancos de dados nos últimos anos são as melhores evidências.

Em 2017, os dois trabalhos de HopsFS e ADLS introduziram ideias semelhantes no campo de metadados de sistemas de arquivos distribuídos, e os importantes sistemas e trabalhos de pesquisa que surgiram desde então são quase todos baseados nessa ideia. Essa ideia divide o serviço de metadados em duas camadas da arquitetura:

  • Camada de banco de dados: essa camada é responsável pelo armazenamento de dados, geralmente usando NewSQL ou sistema KV distribuído (doravante referido coletivamente como sistema de tabelas), para obter persistência de dados e fornecer recursos de transação distribuída;

  • Camada de agente de metadados: esta camada fornece interface POSIX ou HDFS externamente, converte internamente os dados no namespace hierárquico em registros no sistema Table e usa transações para garantir a correção das operações durante o processamento.

foto

O pensamento em camadas torna o design de todo o sistema muito simples. Acontece que o mesmo nó na arquitetura acoplada é responsável tanto pelo armazenamento de dados quanto pela semântica do arquivo. Após a camada, a parte de armazenamento de dados é primeiro removida do nó de metadados, e o nó de metadados possui apenas lógica de processamento, que se torna mais focada.

A interface SQL ou KV fornecida pelo sistema Table é muito fácil de usar e versátil, além de facilitar a expressão da lógica de interação com o armazenamento. Mais importante ainda, o sistema Table fornece transações. A transação é um dos recursos mais poderosos e úteis no campo da computação. Com ACID e um nível de isolamento adequado, (quase) todos os problemas complexos de consistência podem ser resolvidos de forma concisa com base nesse recurso. A consistência exigida pela semântica do sistema de arquivos é obviamente também Sem exceção.

Além de tornar a arquitetura mais concisa, a arquitetura dividida também resolve os problemas de escalabilidade e equilíbrio que eram difíceis de resolver na arquitetura anterior com a ajuda de muitos recursos maduros do sistema Table:

  • Escalabilidade de escala: o sistema Table tem uma escalabilidade muito boa e pode armazenar grandes quantidades de dados. Por exemplo, em termos de serviços de armazenamento de objetos, o TafDB do Baidu Smart Cloud armazenou trilhões de dados e a escala ainda está crescendo. Ao codificar e armazenar os metadados do sistema de arquivos neste tipo de sistema em um formato apropriado, os requisitos de armazenamento de serviços de metadados para dezenas de bilhões ou centenas de bilhões de arquivos podem ser atendidos;

  • Equilíbrio: após a codificação dos metadados do sistema de arquivos no sistema Tabela, a manutenção do relacionamento hierárquico torna-se um problema de atomicidade e consistência quando vários registros são alterados ao mesmo tempo, o que pode ser garantido por transações. Depois de remover a semântica do sistema de arquivos, não há nada de especial sobre os dados em si. O sistema Table pode usar mecanismos de divisão, fusão e balanceamento de fragmentação de dados para evacuar pontos de acesso. Esses mecanismos são muito comuns e maduros, e o tempo de interrupção do serviço resultante pode ser controlado no segundo nível.

Existem razões históricas pelas quais a arquitetura dividida não apareceu em um momento anterior.

As transações só podem funcionar bem em uma única máquina por um longo tempo. Não foi até o nascimento do Spanner em 2012 que as pessoas entenderam como combinar algoritmos de consenso distribuído, como mecanismos de transação Paxos/Raft e Percolator, para construir um poderoso sistema de transação distribuída. . Desde então, vários sistemas Table de código aberto ou fechado derivados do Spanner surgiram como cogumelos depois da chuva, e empresas de tecnologia de um certo tamanho são capazes de desenvolver tais sistemas. A época em que a arquitetura de metadados separados apareceu coincidiu com a época em que esse tipo de sistema inicialmente amadureceu e começou a ser implementado em vários cenários, e foi quase uma coisa natural.

Infelizmente, a arquitetura dividida também não resolve o problema de escalabilidade de gravação, e o desempenho do atraso de gravação é ainda pior do que o da arquitetura acoplada:

  • O papel essencial das transações na arquitetura é, na verdade, um bloqueio distribuído, que não resolve os defeitos de outros mecanismos de bloqueio distribuído.Quando uma operação de gravação requer a participação de vários nós, o desempenho de throughput e atraso será relativamente ruim;

  • Quando uma solicitação é processada em uma arquitetura desacoplada, ela precisa passar primeiro pela camada de proxy de metadados e depois para a camada de banco de dados. Em comparação com a arquitetura acoplada, o caminho de processamento é mais longo, portanto, é naturalmente inferior em desempenho, especialmente em indicadores de latência. As solicitações de leitura podem ser otimizadas lendo diretamente a camada de banco de dados do cliente, mas não há como lidar com as solicitações de gravação dessa maneira.

2.4 Por que os serviços de metadados precisam de bloqueios distribuídos

No artigo anterior, enfatizamos o uso de bloqueios distribuídos para garantir a consistência, que se refere à correção das alterações associadas. Neste capítulo, para que todos entendam melhor este problema, adicionaremos uma explicação de como os bloqueios distribuídos alcançam esta garantia. Tomando como exemplo a criação do arquivo "/A/f2" na arquitetura separada, são necessários os seguintes passos:

  1. A operação de criação é enviada ao proxy de metadados;

  2. Leia os atributos do diretório A e bloqueie A. Nesta etapa, ele realmente verificará se o diretório existe, se o usuário tem permissão para criá-lo, etc.;

  3. Insira o registro do arquivo f2;

  4. Atualize os atributos do diretório A, principalmente mtime/ctime/links/size e outros atributos. As alterações nesta fase ainda não foram enviadas e o cliente não pode vê-las;

  5. Desbloqueie e envie esta alteração. Se este envio envolver vários fragmentos de metadados, você precisará usar 2PC commit (duas fases de confirmação) para garantir que a alteração entre em vigor ao mesmo tempo;

foto

foto

As operações 2 a 5 precisam ser executadas sob a proteção de bloqueios.Sem a proteção de bloqueios, problemas de simultaneidade ocorrerão facilmente. Conforme mostrado na figura abaixo, o sistema utiliza o campo filhos para indicar quantos subdiretórios ou arquivos válidos existem no diretório, este campo pode determinar rapidamente se o diretório está vazio ao deletar o diretório. Quando não há proteção de bloqueio, as operações simultâneas criam o arquivo "/A/f2" e criam o arquivo "/A/f3" respectivamente, lêem A.children=0, cada +1 e atualizam, e os dados do campo filhos finalmente obtidos estão errados 1.

foto

Este erro fará com que as operações subsequentes de exclusão do diretório julguem erroneamente que o diretório está vazio, tornando alguns nós órfãos de arquivos . Por exemplo, após o término deste exemplo, o usuário executa primeiro a exclusão do arquivo "/A/f2" e, em seguida, a exclusão do diretório "/A". Nesse momento, children=0 é considerado um diretório vazio, portanto, o diretório A pode ser deletado. . Neste ponto, o arquivo f3 ainda existe, mas não pode mais ser acessado pelo caminho /A/f3!

A simultaneidade do sistema real é muito mais complicada do que os exemplos acima, mas a partir dos exemplos acima, podemos simplesmente entender por que as operações de metadados do sistema de arquivos distribuído precisam de bloqueios distribuídos para proteção.

2.5 Análise quantitativa do impacto no desempenho do bloqueio distribuído

Depois de responder por que o serviço de metadados precisa de bloqueios distribuídos, demonstramos ainda mais o impacto dos bloqueios distribuídos no desempenho de gravação neste capítulo. Aqui está uma referência direta à análise quantitativa da operação do arquivo de criação do HopsFS no documento, e os resultados da análise também são aplicáveis ​​a outros sistemas.

foto

No gráfico Peak throughput, construímos cargas com taxas de conflito de bloqueio variando de 0% a 100%, e observamos a relação entre o OPS do sistema e o número de clientes. No caso de nenhum conflito (taxa de conflito 0%), a OPS aumenta quase linearmente com o número de clientes, mas quando a taxa de conflito se torna cada vez maior, a OPS cai significativamente. Quando a taxa de conflito atinge 100%, toda a curva torna-se muito plana, indicando que o desempenho do sistema perdeu completamente sua escalabilidade.

No gráfico de quebra de latência, decompondo ainda mais o tempo de execução do HopsFS, podemos descobrir que quando a taxa de conflito é de 50% e 100%, a proporção de conflito de bloqueio em toda a operação chega a 83,18% e 93,86%.

Os experimentos acima mostram que o conflito de bloqueio é a chave para a escalabilidade do sistema e o atraso na operação.

Além disso, podemos observar um resultado interessante, ou seja, mesmo não havendo conflitos, o índice de demora de bloqueios em toda a operação chegou a 52,9%. Isso porque o sistema não sabe quando vai ocorrer um conflito, por isso precisa estar sempre preparado para o pior caso. Se esta parte do atraso puder ser reduzida ou mesmo eliminada, o atraso do sistema pode ser bastante reduzido. Na verdade, seja uma arquitetura acoplada ou uma arquitetura separada, muitos sistemas usarão algumas estratégias de posicionamento de dados para distribuir os dados de uma subárvore ou diretório para um shard tanto quanto possível para facilitar a otimização de conflitos de bloqueio.

3. História da evolução da arquitetura de metadados CFS

O nome de código interno da arquitetura de metadados CFS divulgado no documento é Namespace 2.0, a arquitetura de segunda geração. A arquitetura de segunda geração tem uma óbvia relação de herança com a arquitetura de primeira geração, e é precisamente porque analisamos claramente as limitações da arquitetura de primeira geração que o projeto da arquitetura de segunda geração é possível.

Antes de apresentar oficialmente a arquitetura do Namespace 2.0, vamos dedicar algum tempo para falar sobre como o serviço de metadados CFS evoluiu passo a passo até a arquitetura atual, o que é útil para a compreensão de todo o documento.

3.1 Namespace 1.0: microinovação na arquitetura separada

Quando começamos a projetar o CFS em 2017, um dos objetivos de design mais importantes era que o sistema tivesse a capacidade de suportar arquivos massivos, muito maiores do que os sistemas tradicionais. O sistema de arquivos na nuvem é multilocatário, e o número de locatários sobrepostos na escala de um único locatário levará a um crescimento explosivo do número de arquivos em todo o cluster. Por exemplo, um usuário com 1 bilhão de arquivos, 100 inquilinos é 100 bilhões de arquivos. Mesmo que a escala inicial de dados não seja tão grande, como um projeto para os próximos três a cinco anos ou até mais, ela deve ser voltada para o futuro até certo ponto, deixando espaço suficiente para futuras expansões arquitetônicas.

Descartamos arquiteturas de ponto único desde o início. Diante de um grande número de inquilinos, a arquitetura de ponto único exigirá muitos pequenos clusters, o que trará uma enorme complexidade de O&M. Além disso, com a expansão do tamanho de um único sistema de arquivos, problemas como o limite superior da capacidade de uma única máquina e dados quentes serão encontrados mais cedo ou mais tarde. Esses fatores impedirão que a arquitetura de ponto único vá muito longe, por isso é melhor trabalhar em direção a uma direção verdadeiramente distribuída desde o início.

Durante a pesquisa, notamos o trabalho desses dois artigos, HopsFS e ADLS. Após pesquisa e julgamento, acreditamos que a arquitetura de metadados separada representada por esses dois trabalhos será a tendência de desenvolvimento de todo o campo no futuro. Naquela época, já tínhamos visto as desvantagens dessa arquitetura em termos de latência e escalabilidade de gravação, mas pensávamos que era apenas uma nuvem escura. Quando o carro foi inventado, ele não podia correr mais rápido que um carro. era hora de dissipar essa nuvem escura.

Dentro do Baidu Smart Cloud, uma nova geração de sistema de armazenamento com o nome da marca "Canghai" estava em construção na época. Este sistema é baseado em brpc + braft e realiza vários tipos de serviços de armazenamento distribuído fáceis de operar, de alto desempenho e de grande escala por meio de componentização. TafDB, a base de metadados deste sistema, é um sistema semelhante ao Spanner que foi desenvolvido quase ao mesmo tempo que o CFS.

Combinando esses fatores, o TafDB fornece recursos massivos de armazenamento de dados e transações distribuídas, e o CFS realiza a rota técnica da camada semântica de arquivo em princípio. Antes que o TafDB estivesse pronto, o CFS iniciou o trabalho de desenvolvimento preliminar baseado no MySQL.

Depois de determinar a rota técnica, fizemos alguns ajustes de design de acordo com o cenário de negócios alvo do CFS:

1. Separação de atributo de arquivo

Este ajuste significa que os atributos do arquivo (atributos do arquivo) são separados do serviço de metadados e colocados junto com os dados do arquivo (dados do arquivo) no serviço de dados (Data Service) para processamento. As principais considerações estão relacionadas ao desempenho:

  • Consideração do desempenho de leitura: No Capítulo 2.2, apontamos que a parte do atributo só precisa satisfazer o ponto lido do índice inode. .;

  • Consideração do desempenho da gravação: ao modificar os dados do arquivo, o POSIX requer a modificação de atributos como ctime e mtime, e a gravação adicional é acompanhada por uma atualização do tamanho do arquivo, que introduzirá operações frequentes de atualização de metadados no caminho de gravação de dados. O limite superior de OPS de gravação de um sistema semelhante ao Spanner é de cerca de um milhão e é impossível consumir tudo isso em operações de modificação de dados de arquivo. Portanto, o desempenho de gravação de todo o sistema será menor do que esse valor, que não pode atender necessidades de negócios.

Esse ajuste tornou muito boa a capacidade de expansão da operação de atributo de arquivo de todo o sistema e, posteriormente, o Namespace 2.0 seguiu esse design. Os leitores podem encontrar uma análise quantitativa dos benefícios desse projeto na seção experimental do artigo.

2. Separação de leitura e gravação

Outro ajuste é separar os caminhos de leitura e gravação de metadados. Para solicitações de leitura, ignoramos a camada de proxy de metadados e acessamos diretamente o TafDB, o que pode reduzir a latência de leitura e, ao mesmo tempo, reduzir a sobrecarga de encaminhamento da camada de proxy de metadados, e o OPS também será aprimorado até certo ponto. Para solicitações de gravação, as solicitações de gravação de cada sistema de arquivos (o CFS suporta multilocação e há muitas instâncias do sistema de arquivos em um sistema) são convergidas para um único ponto de processamento. Os motivos para isso são detalhados abaixo.

O nível de isolamento fornecido pelo TafDB é o nível de isolamento de instantâneo (Snapshot Isolation), mas o cenário do sistema de arquivos realmente precisa do nível de isolamento de instantâneo serializável (Serializable Snapshot Isolation) para evitar o problema do nó órfão. No exemplo da figura abaixo, as operações rmdir "/a" e create "/a/b" são simultâneas. Após o início da operação, eles leem os dados do instantâneo antes do início da transação e verificam se o diretório /a existe em ao mesmo tempo e, em seguida, ambos A operação é bem-sucedida, o que torna b um nó órfão inacessível.

foto

Esse problema pode ser contornado criando conflitos de gravação em registros no diretório pai, ao custo de tornar os conflitos de transação mais frequentes. Analisamos quantitativamente o custo dos bloqueios no artigo anterior, que é mais caro em sistemas como o TafDB que adota um modelo de bloqueio otimista, pois tais sistemas julgam que os conflitos de transação estão no último momento antes do commit, e revertem devido a conflitos antes disso A transação de concluiu a maioria das operações, incluindo várias rodadas de RPC e despesas gerais de colocação.

Esses custos têm um grande impacto no desempenho de gravação e podem facilmente levar a um grande número de caudas longas incontroláveis ​​e até mesmo uma avalanche de todo o sistema. Não existe uma solução particularmente boa para este problema a curto prazo. Ao mesmo tempo, de acordo com a experiência da empresa, se o desempenho de gravação da arquitetura de ponto único for otimizado, pode atingir cerca de 50.000 OPS. Acreditamos que esse desempenho seja suficiente no curto prazo. Portanto, decidimos reduzir as solicitações de gravação de cada sistema de arquivos a um único ponto para processamento e deixar os problemas de escalabilidade de gravação e atraso de gravação para serem resolvidos posteriormente.

Após os ajustes acima, o Namespace 1.0 foi desenvolvido e implementado com sucesso, cuja estrutura geral é mostrada na figura abaixo. Nesta arquitetura, todas as operações relacionadas a arquivos são tratadas pelo FileStore distribuído. Outras operações de leitura de metadados são enviadas diretamente para o TafDB pelo ClientLib para processamento. As operações de gravação são processadas pelo Namespace, que é uma implementação Multi-Raft. Cada grupo de replicação Raft é responsável por um sistema de arquivos.

foto

Esquema de metadados CFS Namespace 1.0

A estrutura de dados do sistema é mostrada na figura abaixo. Os dados no TafDB são armazenados em uma grande tabela, tendo como chave primária <parent_inode, name>, responsável por implementar lookup e readdir, e como chave secundária, responsável por implementar getattr do diretório.

foto

3.2. Namespace 1.X: explorações menos bem-sucedidas baseadas em 1.0

Depois que o 1.0 foi lançado, o CFS empreendeu com sucesso alguns negócios que exigiam alto desempenho de leitura de metadados. Tome um negócio interno como exemplo. Antes de migrar para o CFS, uma solução de código aberto foi adotada. Durante o período de pico, centenas de milhares de getattr OPS causaram a CPU do nó de metadados cheia e um grande número de os pedidos até falharam. O sistema era precário e pairava à beira do colapso. . A CFS assumiu o negócio com facilidade e todo o processo transcorreu sem intercorrências. Posteriormente, analisamos este caso e acreditamos que o principal benefício vem da separação dos atributos do arquivo, essa otimização faz com que o getattr do arquivo seja processado diretamente pelo FileStore, o que quebra totalmente o getattr.

No entanto, o desempenho da gravação é sempre um ponto problemático. Com base na arquitetura 1.0, fizemos muitos experimentos e análises para tentar melhorar o desempenho de uma única máquina. Algumas das otimizações mais bem-sucedidas nesse processo incluem:

  • Simplificação do fluxo de processamento: analisamos todo o processo de cada tipo de solicitação de gravação, do kernel ao TafDB, simplificamos algumas verificações de condição repetida e mesclamos operações que podem ser combinadas;

  • Introduzir cache: Como a escrita de metadados de cada sistema de arquivos é um ponto único, os dados vistos pelo módulo Namespace a qualquer momento são os mais recentes e ele tem condições de introduzir o cache. No caso de um hit de cache, a maioria as solicitações de leitura na transação podem ser processadas.

  • Pré-processamento de conflitos de transação: O custo de conflitos de transação no TafDB é relativamente alto. Mencionamos o processamento de conflitos de transação para a camada CFS. O módulo Namespace analisa os pontos de conflito existentes na transação antes de executá-la, e enfileira de acordo com cada ponto de conflito, para que não haja conflito de transação na requisição enviada ao TafDB.

Por meio dessas otimizações, mais que dobramos a latência de gravação e o OPS, respectivamente. No entanto, em comparação com a arquitetura de ponto único e a arquitetura acoplada, essas otimizações não podem compensar a sobrecarga causada pelo alongamento do caminho de processamento e ainda há uma grande lacuna nos indicadores de desempenho de gravação. Além disso, se quisermos expandir a capacidade de processamento para vários nós, o cache original e o mecanismo de pré-processamento de conflito de transação serão inválidos até certo ponto, e o atraso de processamento e o OPS autônomo se deteriorarão novamente.

Em meados de 2019, as coisas ficaram um pouco desesperadoras. Ficamos desapontados ao descobrir que depois de esgotar quase todas as soluções e ideias possíveis e impossíveis, parecia haver apenas um caminho diante de nós, ou seja, retornar à arquitetura acoplada, fazer desenvolvimento secundário baseado no TafDB e realizar a semântica do sistema de arquivos, mantendo as vantagens do TafDB tanto quanto possível.

Costumávamos acreditar firmemente que a arquitetura separada é a tendência geral do desenvolvimento futuro. Embora existam alguns problemas óbvios, quando o carro foi lançado, ele não poderia superar o carro. É apenas uma questão de tempo até que esses problemas sejam resolvidos . No entanto, pelo menos no mundo POSIX, parece que o novo carro não é um carro, mas uma bicicleta um pouco melhor.

3.3. Namespace 2.0: Outra aldeia com salgueiros escondidos e flores brilhantes

Não importa o quanto estejamos otimistas sobre a arquitetura separada, problemas práticos ainda precisam ser resolvidos. As duas equipes de CFS e TafDB decidiram finalmente sentar e trabalhar duro juntas.Se não funcionar, elas realmente se separarão.

Na história da matemática e da física, muitas novas teorias começam com o desmantelamento da pedra angular de velhas teorias. Exemplos típicos incluem a geometria não-euclidiana e a relatividade. Claro, comparado com os mestres, nosso trabalho é insignificante, aqui é apenas uma metáfora.

Decidimos deixar de lado todo o conhecimento e preconceitos anteriores para encontrar os tijolos inferiores de toda a construção de metadados do sistema de arquivos e iniciar a discussão a partir desses tijolos.

Logo, descobrimos que quase todo o trabalho existente partiu de um monte de interfaces que atendem ao padrão POSIX, mas essas próprias interfaces também possuem estruturas e lógicas internas e não são o nível mais baixo de tijolos.

Ao extrair os requisitos essenciais por trás da interface POSIX, estabelecemos o modelo abstrato descrito na Seção 2.2 e escrevemos cada operação com pseudocódigo extremamente simples.

A partir desse modelo abstrato, derivamos um conjunto de conclusões extremamente simples:

  • A fonte de afetar a escalabilidade dos serviços de metadados é o conflito ao atualizar o atributo do diretório pai, que faz parte da mudança de associação;

  • Essas mudanças são essencialmente apenas um pouco de matemática e vêm em dois sabores. Uma é adição e subtração. Para atributos como links, filhos e tamanho (como mencionado acima, filhos não é um atributo padrão, mas para a conveniência de manter o número de filhos no diretório), cada vez que um filho é criado ou excluído, ele precisa ser atualizado com precisão. Outra operação de atribuição, para atributos como ctime, atime, mtime, substitui o valor antigo pelo novo valor toda vez que um filho é criado e excluído;

  • Os dois acima são verdadeiros para todas as operações de modificação e os requisitos para renomear serão mais complicados e adicionais.

Alunos com certa experiência em programação devem ser capazes de descobrir que o segundo requisito abstrato pode ser expresso por operações de variáveis ​​atômicas! Para a correção dessas operações de variáveis ​​atômicas, a implementação tradicional expande o escopo de conflitos para pelo menos todo o registro do diretório pai. Essa é uma abordagem muito imprecisa, semelhante a colocar um bloqueio mutex em toda a operação para proteger a seção crítica (critical seção).

A maneira de otimizar esse tipo de problema na programação é tentar restringir o escopo da seção crítica e substituir o mutex por um método de proteção menos caro. Seria melhor se ele pudesse ser otimizado para garantir a correção apenas contando com a variável atômica operações. Na verdade, essa é a ideia de otimização que adotaremos a seguir, e o título do artigo traz um resumo mais preciso disso.

No campo da informática, muitas vezes vemos algum conhecimento ou experiência gradualmente se tornando senso comum, mas o processo de produção desse conhecimento ou experiência passou por algumas reviravoltas.

Passamos um trimestre discutindo e projetando o Namespace 2.0. Os pontos-chave acima parecem ter se tornado nosso senso comum agora, mas levamos mais de um mês para deduzi-los na época. Depois que o projeto do esquema foi concluído, o próximo trabalho correu muito bem. Com base no sistema existente, passamos mais um trimestre fazendo uma versão de demonstração para verificação da solução e avaliação de desempenho. A versão demo é realmente muito diferente da versão oficial posterior, o que torna o ciclo oficial de desenvolvimento e teste relativamente curto. No final do primeiro trimestre de 2020, o Namespace 2.0 começará a ter uma pequena quantidade de tráfego online. Desde então, algumas otimizações foram lançadas uma após a outra, mas a estrutura geral não mudou muito até agora.

4. Ideias de implementação

foto

A ideia orientadora do Namespace 2.0 é reduzir continuamente o escopo da seção crítica das operações de gravação e, finalmente, obter o bloqueio livre. A figura acima mostra um diagrama esquemático simples.No Namespace 1.0, as alterações associadas protegidas por bloqueios são satisfeitas apenas por operações atômicas no Namespace 2.0, e as operações simultâneas não precisam mais ser executadas em série.

Para conseguir isso, empregamos uma combinação de tecnologias:

A primeira etapa é restringir o escopo dos conflitos a um único fragmento por meio de um layout de dados adequado

O objetivo principal do sistema é reduzir o escopo de execução das solicitações de metadados do TafDB de cross-shards para um único shard, de modo que seja possível remover bloqueios distribuídos, caso contrário haverá problemas de consistência, que já demonstramos em segundo plano seção . Esse objetivo nos leva a repensar o layout dos dados.

Em primeiro lugar, verificou-se que a prática do Namespace 1.0 de atribuir atributos de arquivo apenas ao armazenamento de arquivo fornece melhor desempenho de operação de atributo de arquivo, e o Namespace 2.0 continua a usar esse design.

Em segundo lugar, observamos que os metadados de qualquer item de diretório precisam atender aos requisitos de dois tipos de índices ao mesmo tempo. Naturalmente, existem duas partes, uma parte é usada para getattr, que armazena informações de atributo, e a outra parte é usado para pesquisa e readdir, que armazena as informações de índice relacionadas ao diretório pai. O último faz parte de uma alteração associada, cujo restante está relacionado ao atributo do diretório pai. Ao ajustar o layout de dados e acoplar os dados envolvidos em toda a mudança de associação a um estilhaço, pode-se obter o efeito de focar os conflitos de transação em um único estilhaço. Na verdade, esse ajuste significa que a regra "atributos são armazenados separadamente" se estende a todos os tipos de entradas de diretório, incluindo arquivos.

Por fim, desmontamos toda a operação da proteção de transação original em uma combinação de duas operações TafDB single-shard (no caso de um diretório) ou uma combinação de uma operação FileStore + uma operação TafDB single-shard (no caso de um arquivo), e por meio da ordem cuidadosamente organizada, as duas operações precisam apenas satisfazer a atomicidade, respectivamente, para garantir o efeito de execução e não precisam mais de um grande bloqueio para proteger todo o intervalo.

A segunda etapa é restringir o escopo dos conflitos em operações de fragmento único ao nível de campo e obter bloqueio sem

Depois que o conflito é reduzido a um único fragmento, sem nenhuma otimização, as operações em um único diretório ainda precisam ser executadas em série. O TafDB expande o mecanismo de armazenamento e introduz a tecnologia primitiva atômica de fragmento único para reduzir os conflitos de linha originais a operações atômicas em campos específicos e mesclar automaticamente as operações simultâneas.

A terceira etapa é simplificar a camada de proxy de metadados e encurtar ainda mais o caminho de execução

Após a otimização acima, exceto para a operação de renomeação muito complexa, o papel da camada de proxy de metadados (o módulo Namespace do Namespace 1.0) é apenas implementar a interface POSIX e encaminhar solicitações. Integramos diretamente os recursos dessa camada no cliente , e mantenha apenas o processamento de renomeação complexo, renomeado para Renamer.

Cinco, a estrutura geral

foto

Esquema de metadados CFS Namespace 2.0

De acordo com as ideias de implementação acima, toda a arquitetura do sistema é dividida em quatro partes:

  • Camada de armazenamento de namespace (TafDB): TafDB é responsável por armazenar outras partes do namespace hierárquico, exceto atributos de arquivo;

  • Camada de Armazenamento de Arquivos (FileStore): Esta camada é uma camada de armazenamento de blocos organizada de maneira plana, e um bloco é a unidade básica de dados de arquivo. Os atributos do arquivo são armazenados no sistema junto com os dados do arquivo. Para atributos de arquivo, este é um sistema KV que não suporta leitura de alcance e suporta apenas leitura de ponto;

  • Serviço de renomeação (Renamer): arquitetura Multi-Raft, cada sistema de arquivos é fornecido por um grupo de replicação Raft para suportar renomeação complexa, a chamada renomeação de caminho normal;

  • Biblioteca cliente (ClientLib): O cliente é responsável por receber requisições específicas e desmontá-las em requisições internas dos módulos acima. ClientLib fornece as interfaces necessárias para facilitar o acesso à camada Linux VFS e atualmente suporta FUSE, Samba e NFS-Ganesha.

Como pode ser visto no diagrama de arquitetura acima, as funções MDS ou NameNode do sistema de arquivos distribuído tradicional não existem mais no CFS e todas as funções estão espalhadas para outros módulos.

Mantendo as vantagens da arquitetura separada, esse projeto resolve todas as suas deficiências e atinge um estado relativamente bom em termos de escalabilidade, atraso e equilíbrio.

Acreditamos que essa nova ideia coloca um novo motor no carro de estrutura dividida, o que faz com que o carro supere o carro em todos os aspectos.

6. Detalhes da implementação

6.1 Organização de metadados e sharding

Semelhante ao InfiniFS, o CFS desmonta o registro de cada entrada de diretório em duas partes, ou seja, registro de id de inode e registro de atributos. Essa decomposição é a base para uma maior redução dos domínios de conflito.

foto

TafDB usa uma tabela inode_table para armazenar todos os dados, e a chave primária da tabela é <kID, kStr>. Na verdade, essa tabela armazena dois tipos de dados de armazenamento mistos, incluindo todos os registros de id de inode e registros de atributos de diretórios. FileStore é responsável por armazenar o registro de atributos dos arquivos. O todo é organizado como mostrado abaixo:

foto

Para o registro de id do inode, a parte kID de <kID, kStr> representa o inode do diretório pai e kStr representa o nome da subchave. Este registro é para atender aos requisitos de pesquisa e readdir, exceto para inode e tipo, outros campos são inválidos.

Para o registro de atributos do diretório, kID é o inode do próprio diretório e kStr é a string reservada /_ATTR. Os outros campos armazenam vários campos de atributo e deixar o campo inode em branco aqui não tem efeito prático.

A inode_table como um todo armazena todos os dados de maneira ordenada de acordo com <kID, kStr>, e as regras de fragmentação são baseadas em kID. O TafDB fez uma garantia especial de que não importa como os shards são divididos e mesclados, os dados do mesmo kID sempre serão armazenados no mesmo shard. Isso significa que os dados envolvidos na alteração associada do mesmo diretório são processados ​​em um fragmento e o CFS implementa a partição de intervalo no nível do diretório. Todas as operações de divisão e fusão de shard são executadas automaticamente pelo TafDB, sem a necessidade de atenção do CFS e intervenção manual.

Deve-se ressaltar que não achamos necessário continuar a explorar o multi-sharding de diretório único no projeto atual. A causa raiz desse tipo de projeto em sistemas acoplados anteriores é que os pontos de acesso em um nó não podem ser evacuados com precisão, e os pontos de acesso afetarão o mesmo nó. As operações de outros diretórios, vários pontos de acesso também afetarão uns aos outros, e o problema só pode ser aliviado dispersando a pressão nos diretórios. Mas nossa arquitetura não tem esse problema pelos seguintes motivos:

  • Em casos extremos, o TafDB pode ser dividido de modo que um único diretório monopolize todo o shard e um único shard monopolize toda a máquina. O poder de processamento de um único diretório é equivalente ao de uma arquitetura de ponto único. Essa granularidade é pequena o suficiente e a capacidade de processamento é forte o suficiente;

  • Depois que os atributos de arquivo são separados para FileStore, um único shard não precisa lidar com as operações de atributo de arquivo com a maior proporção e a pressão é muito menor do que o design tradicional.

6.2 Reduzir sobrecarga de bloqueio distribuído

6.2.1. Otimizando a sobrecarga de colaboração entre componentes

Depois que os metadados são distribuídos para os dois componentes do TafDB e do FileStore, a primeira coisa a garantir é a consistência externa dos dois sistemas. Contornamos isso com uma ordem de execução cuidadosamente elaborada:

  • Para todas as operações de criação, primeiro crie o registro de atributos de arquivo FileStore e, em seguida, crie o registro de id de inode TafDB;

  • Para todas as operações de exclusão, primeiro exclua o registro de id de inode do TafDB e, em seguida, exclua o registro de atributos de arquivo do FileStore;

Considerando criar, desvincular e renomear como exemplos, o processo de execução específico é o seguinte:

foto

O sistema de arquivos passará pelo processo de pesquisa de um diretório específico antes de operar no arquivo. Somente o arquivo correspondente com o registro de ID do inode será visto. Portanto, é necessário apenas garantir que o registro de atributos do arquivo seja gerado anteriormente e morre depois do registro de id do inode. Isso pode garantir que dados inválidos não sejam vistos pelos usuários e, de uma perspectiva externa, a consistência não foi quebrada. A sequência de operações mostrada na figura pode conseguir esse efeito.O único efeito colateral é que o lixo pode permanecer se a operação falhar, o que pode ser resolvido pela coleta de lixo.

Este método pode ser estendido para a situação interna do TafDB e também é aplicável ao diretório onde o registro de id do inode e o registro de atributos são armazenados no TafDB. Obviamente, a sequência de execução da operação específica precisa considerar os requisitos de compatibilidade do POSIX, que podem não ser totalmente consistentes com o arquivo e não serão detalhados aqui.

6.2.2. Otimizando a sobrecarga de cross-shard no TafDB

Em um sistema como o TafDB, se uma transação envolve vários shards, um mecanismo de confirmação de transação 2PC é necessário para garantir o ACID. O layout de dados que adotamos permite que o registro de atributos do diretório e o registro de id de inode de seus subitens estejam em um shard, de modo que essas operações de modificação necessárias possam ser simplificadas de transações tradicionais entre shards para transações de shard único. No próximo capítulo, apresentaremos essa técnica de simplificação. Após essa remoção, exceto para a renomeação do caminho normal, todas as operações do TafDB no CFS são transações de fragmento único altamente otimizadas.

6.3 Reduzindo bloqueios de fragmento único

6.3.1. Primitivas de fragmento único

A alteração da associação envolve vários registros e ainda há um modo de leitura e modificação no processo de execução. Em particular, a modificação do atributo do diretório pai precisa primeiro ler o valor antigo e depois atualizá-lo. Uma implementação ingênua envolveria várias rodadas de verificação de condição, operações de leitura e gravação, o que definitivamente não é eficiente. Para resolver esse problema, propomos o conceito de primitivo atômico de fragmento único.

Cada primitiva implementa uma transação de fragmento único personalizada. Essa transação completa atomicamente todas as leituras, gravações e verificações de condição necessárias para garantir que o processo de execução seja tudo ou nada. Somente quando todas as verificações de condição forem verdadeiras, o It será executado com sucesso , caso contrário, nenhuma modificação será feita nos dados do shard. Primitivos são uma transação especial de um único shard, e o custo de execução altamente otimizado é equivalente a escrever um log WAL.

Resumimos todas as operações POSIX e, finalmente, resumimos três primitivas, conforme mostrado na tabela a seguir, que também marcou o escopo de uso de cada primitiva, a descrição da interface do tipo SQL e um breve processo de execução:

foto

No artigo, os pseudocódigos de criar, desvincular e renomear arquivos no mesmo diretório são fornecidos como exemplos para explicar como as primitivas funcionam. Aqui podemos ver um exemplo de create.

foto

A primeira etapa de create é criar um arquivo no FileStore, e a segunda etapa é a primitiva instert_with_update, que conclui principalmente as seguintes tarefas:

  • WHERE verifica duas condições, kID=@parent_id, kStr="/_ATTR", type=dir Essas condições verificam em conjunto se kID, kStr, type atendem ao valor especificado. Quando a verificação é aprovada, significa que o registro de atributos cujo inode é @parent_id existe no TafDB e o tipo é um diretório;

  • SET atualiza os atributos dos registros que atendem à condição WHERE, especificamente atualizando campos como filhos, tamanho e mtime;

  • INSERT insere o registro de id do inode, a chave primária é kID=@parent_id, kStr=@name, e o campo principal é inode;

No artigo, as primitivas são expressas na forma de SQL, que é apenas para conveniência da expressão.Na verdade, o TafDB fornece a interface da camada KV. Comparado com a interface padrão, o primitivo fortalece a capacidade de verificação de condição e, ao mesmo tempo, pode retornar condições de erro mais detalhadas quando ocorre um erro de execução de instrução. O CFS é responsável por traduzir essas condições em códigos de erro POSIX. De acordo com nossa experiência, mesmo que o sistema Table utilizado não suporte tais extensões, a carga de trabalho necessária para desenvolvimento secundário é muito pequena.Comparada com serviços de metadados acoplados, a carga de trabalho e a dificuldade de implementação são de magnitude várias vezes menor.

Finalmente, resumimos as vantagens dos primitivos:

  • A sobrecarga de comunicação com o TafDB é bastante reduzida e a rodada de interação é reduzida a uma;

  • Comprima muitas operações associadas em um processamento, o que reduz a perda geral de processamento e há espaço para otimização adicional;

  • Simplifica o design do serviço de metadados, diferentes operações POSIX podem ser resumidas por três tipos de primitivas e a implementação tradicional precisa implementar todas as interfaces POSIX.

6.3.2. Fusão de conflitos

Se a implementação padrão for seguida, as primitivas acima ainda resultarão em filas para atualizações de atributos do diretório pai. Como analisamos acima, a essência do conflito aqui é, na verdade, a operação de atualização de atributos, que é essencialmente algumas operações de variáveis ​​atômicas. Com base nisso, implementamos ainda dois mecanismos de aprimoramento para enfraquecer e mesclar conflitos de transação.

O primeiro mecanismo é delta apply, para os atributos numéricos de links, filhos e tamanho. Eles irão adicionar e subtrair incrementos quando forem alterados, assim como a adição e subtração de variáveis ​​atômicas, a ordem não é importante, desde que o resultado final da superposição esteja correto. delta apply alcança esse efeito combinado de adição e subtração.

O segundo mecanismo é last-writer-win.Para operações simples de substituição, como permissions, mtime e ctime, simplesmente retemos o resultado da última operação de atribuição.

A primitiva pode decidir automaticamente se deve usar delta apply ou last-writer-win verificando se a instrução SET envolve adição e subtração (como children+=1) ou atribuição (como mtime=@now). Essa detecção não precisa perceber a semântica do sistema de arquivos e é universal. Na verdade, pode ser estendida a qualquer cena onde apenas algumas variáveis ​​atômicas de campo são operadas e atingir uma gama menor de conflitos do que conflitos de gravação .

6.4 Remover camada de proxy de metadados

Depois de desmontar todas as operações, exceto a renomeação do caminho normal em uma combinação de primitivas, os conflitos podem ocorrer entre essas operações somente quando elas caem em um único fragmento. O único valor da camada de proxy de metadados na arquitetura separada tornou-se uma tradução de interface, conectando vários links em série para realizar a interface POSIX, mas isso pode ser totalmente suportado pelo cliente, então tomamos uma decisão ousada de integrar totalmente essa camada de funções no cliente para implementação.

6.5 Renomear fortemente consistente

Renomear é a operação mais complicada em um sistema de arquivos.No pior caso, envolve 6 objetos de metadados, incluindo 4 registros de atributos e 2 registros de id de inode. De acordo com nossas estatísticas online, 99% das renomeações ocorrem entre arquivos no mesmo diretório.As alterações envolvidas neste caso estão todas em um shard TafDB, que pode ser otimizado pelo método mencionado acima. Portanto, dividimos a renomeação em Fast Path e Normal Path.

A renomeação do Fast Path é implementada com base na primitiva insert_and_delete_with_update, que trata apenas da renomeação de arquivos no mesmo diretório, e o restante dos tipos são Normal Path. O Normal Path é processado pelo Renamer, que é um grupo de replicação Raft para cada sistema de arquivos e é responsável pela detecção de conflitos para as operações de renomeação do Normal Path. Somente as solicitações que passarem pela detecção de conflito serão enviadas ao TafDB para continuar o processamento usando transações 2PC. Essa detecção pode garantir que as solicitações de saída não causem órfãos ou loops.

A exatidão da renomeação do caminho normal e outras operações simultâneas é garantida com base em dois aspectos:

  • Outras operações não alterarão a propriedade do subitem, no máximo as informações vistas não estarão atualizadas, mas não terão impacto real no julgamento de formação de loop ou órfãos. Ao processar a transação 2PC de renomeação de caminho normal, verificaremos ainda mais o possível erro de julgamento e, se houver uma alteração, reverteremos e tentaremos novamente.Todo o processo de processamento é semelhante ao mecanismo de bloqueio otimista;

  • Transações 1PC são altamente otimizadas apenas para transações 2PC comuns, e as garantias de nível de isolamento ACID e transação não são quebradas.

A correção de todo o mecanismo pode ser comprovada por métodos formais de verificação, como o TLA+.

6.6 Coleta de Lixo

Quando ClientLib tem uma partição de rede ou falhas de processo, as operações inacabadas resultarão em lixo residual no TafDB ou FileStore. O sistema lida com esses lixos por meio de dois mecanismos:

  • Reconciliação periódica: reconcilia periodicamente contas entre TafDB e FileStore e recicla dados de lixo;

  • Processamento sob demanda: quando getattr e readdir descobrem que alguns registros de atributos não podem ser encontrados durante a execução, uma coleta de lixo pode ser iniciada online para reciclar o registro de ID do inode correspondente.

7. Experimente

No artigo, o CFS é comparado com o HopsFS e o InfiniFS em detalhes. Os resultados do teste mostram que no teste de escala de 50 nós, em comparação com HopsFS e InfiniFS, a taxa de transferência de cada operação do CFS é aumentada para 1,76 – 75,82 vezes e 1,22 – 4,10 vezes, e seu atraso médio é reduzido em até 91,71% e 54,54%. No caso de alta contenção e diretórios grandes, a vantagem de taxa de transferência do CFS é expandida ainda mais em uma ordem de grandeza.

Os leitores que desejam conhecer os resultados completos do teste podem ler a parte experimental do artigo original, portanto não entrarei em detalhes aqui.

8. Resumo

Este artigo apresenta o design principal do sistema de metadados do Baidu Intelligent Cloud File Storage CFS e responde ao problema de que é difícil equilibrar compatibilidade POSIX e alta escalabilidade (especialmente escalabilidade de gravação) que há muito atormentam o campo de metadados do sistema de arquivos. Essa é uma questão fundamental para saber se um sistema de arquivos distribuído em grande escala pode ser dimensionado para centenas de bilhões de arquivos, mantendo alto desempenho e estabilidade.

A arquitetura de metadados separados é uma tendência de desenvolvimento na área de metadados de sistemas de arquivos nos últimos anos.Todos os sistemas da indústria com potencial para armazenar centenas de bilhões de arquivos são implementados com base nessa arquitetura. Esse tipo de arquitetura adota uma ideia semelhante à "separação de armazenamento e computação" e divide os serviços de metadados em duas camadas, a saber, a camada de banco de dados responsável pelo armazenamento de dados e a camada de proxy de metadados que é parcial para a lógica de computação e responsável pela implementação de arquivos semântica do sistema. No entanto, essa arquitetura não resolve os problemas de baixa escalabilidade de gravação e alta latência de gravação.

O CFS de armazenamento de arquivos desenvolve ainda mais a arquitetura de metadados separados.Ao projetar cuidadosamente o layout dos dados, diferentes partes dos metadados podem ser espalhadas e agregadas no sistema de uma maneira mais científica. A dispersão é para melhorar o paralelismo do processamento de dados, e a agregação permite que dados interdependentes sejam processados ​​ao mesmo tempo sem várias rodadas de interação. Com base nesse layout de dados, o CFS corta continuamente o intervalo da seção crítica das operações de gravação de metadados e, finalmente, realiza o bloqueio livre, o que resolve os problemas de baixa escalabilidade de gravação e alta latência de gravação da arquitetura de metadados separados.

Esse design do CFS tem executado de forma estável no ambiente de produção por mais de 3 anos, fornecendo forte suporte para o boom de big data, IA, contêiner, ciências biológicas e outros cenários na nuvem.

Deve-se ressaltar que a inovação do CFS é inseparável do forte suporte de todo o sistema de tecnologia de armazenamento Baidu Canghai. A árvore que abraça nasce no fim do milheto; a plataforma com nove andares ergue-se do monte de terra. É precisamente porque nossos antecessores e outras equipes fizeram muito trabalho sólido que podemos verificar rapidamente, testar e errar algumas ideias imaturas e não convencionais e pousar rapidamente no ambiente de produção após a conclusão da verificação. Dentro do Baidu, existem muitos desses exemplos.

No processo de desenvolvimento do sistema de metadados CFS, outra colheita que ganhamos é o espírito de ceticismo. Não há autoridade em tecnologia que não possa ser contestada. Não é prejudicial perguntar "É sempre tão conveniente?"

Finalmente, gostaria de agradecer aos professores e alunos do Advanced Data Systems Laboratory (ADSL) da Universidade de Ciência e Tecnologia da China, o colaborador do artigo CFS.A publicação deste artigo é inseparável de seus esforços conjuntos.

-- FIM--

Leitura recomendada:

A prática de hospedar scripts baseados em openfaas

Guia do engenheiro da Baidu para evitar armadilhas no desenvolvimento móvel - linguagem Swift

Guia do engenheiro da Baidu para evitar armadilhas no desenvolvimento móvel - vazamentos de memória

Modelos de linguagem aumentada - o caminho para a inteligência geral?

Realização da mensagem completa com base na caixa de correio pública

Baidu APP Terminal iOS tamanho do pacote 50M prática de otimização (2) Otimização de imagem

おすすめ

転載: blog.csdn.net/lihui49/article/details/130942836