Sistemas de banco de dados distribuídos 101
Ou, bancos de dados distribuídos - o que o FK realmente significa ' escala da web '?
Os sistemas de banco de dados distribuídos são criaturas complexas e apresentam uma variedade de sabores diferentes. Se eu me aprofundar nos meus estudos pouco lembrados sobre isso na universidade, tentarei explicar alguns dos principais problemas de engenharia para a construção de um sistema de banco de dados distribuído.
Primeiro, alguma terminologia
Propriedades ACID (Atomicidade, Consistência, Isolamento e Durabilidade): essas são as principais invariantes que devem ser aplicadas para que uma transação seja implementada de maneira confiável, sem causar efeitos colaterais indesejáveis.
A atomicidade requer que a transação seja concluída ou revertida completamente. As transações parcialmente concluídas nunca devem estar visíveis e o sistema deve ser construído de forma a impedir que isso aconteça.
A consistência requer que uma transação nunca viole quaisquer invariantes (como integridade referencial declarativa) garantidos pelo esquema do banco de dados. Por exemplo, se uma chave estrangeira existir, será impossível inserir um registro filho com uma reverência a um pai inexistente.
O isolamento requer que as transações não interfiram entre si. O sistema deve garantir os mesmos resultados se as transações forem executadas em paralelo ou sequencialmente. Na prática, a maioria dos produtos RDBMS permite modos que compensam o isolamento em relação ao desempenho.
A durabilidade exige que, uma vez confirmada, a transação permaneça no armazenamento persistente de maneira robusta à falha de hardware ou software.
Vou explicar alguns dos obstáculos técnicos que esses requisitos apresentam nos sistemas distribuídos abaixo.
Arquitetura de disco compartilhado: Uma arquitetura na qual todos os nós de processamento em um cluster têm acesso a todo o armazenamento. Isso pode apresentar um gargalo central para o acesso a dados. Um exemplo de sistema de disco compartilhado é Oracle RAC ou Exadata .
Arquitetura de nada compartilhado: uma arquitetura na qual os nós de processamento em um cluster possuem armazenamento local que não é visível para outros nós do cluster. Exemplos de sistemas com compartilhamento de nada são Teradata e Netezza .
Arquitetura de memória compartilhada: Uma arquitetura na qual várias CPUs (ou nós) podem acessar um conjunto compartilhado de memória. A maioria dos servidores modernos possui um tipo de memória compartilhada. A memória compartilhada facilita certas operações, como caches ou primitivas de sincronização atômica, que são muito mais difíceis de fazer em sistemas distribuídos.
Sincronização: um termo genérico que descreve vários métodos para garantir acesso consistente a um recurso compartilhado por vários processos ou threads. Isso é muito mais difícil de fazer em sistemas distribuídos do que em sistemas de memória compartilhada, embora algumas arquiteturas de rede (por exemplo, BYNET da Teradata) possuam primitivas de sincronização no protocolo de rede. A sincronização também pode vir com uma quantidade significativa de sobrecarga.
Semi-Join: Uma primitiva usada na junção de dados mantidos em dois nós diferentes de um sistema distribuído. Essencialmente, ele consiste em informações suficientes sobre as linhas para ingressar, sendo agrupadas e passadas por um nó ao outro para resolver a associação. Em uma consulta grande, isso pode envolver tráfego de rede significativo.
Consistência Eventual: Um termo usado para descrever a semântica de transações que negociam atualizações imediatas (consistência nas leituras) em todos os nós de um sistema distribuído para desempenho (e, portanto, maior rendimento da transação) nas gravações. A consistência eventual é um efeito colateral do uso da Replicação de Quorum como uma otimização de desempenho para acelerar confirmações de transação em bancos de dados distribuídos, onde várias cópias de dados são mantidas em nós separados.
Algoritmo de Lamport: um algoritmo para implementar exclusão mútua (sincronização) entre sistemas sem memória compartilhada. Normalmente, a exclusão mútua dentro de um sistema requer uma instrução atômica de leitura-comparação-gravação ou instrução semelhante de um tipo normalmente apenas prática em um sistema de memória compartilhada. Existem outros algoritmos de sincronização distribuídos, mas o Lamport foi um dos primeiros e é o mais conhecido. Como a maioria dos mecanismos de sincronização distribuídos, o algoritmo de Lamport depende muito do tempo exato e da sincronização do relógio entre os nós do cluster.
Confirmação em duas fases (2PC): Uma família de protocolos que garante que as atualizações do banco de dados envolvendo vários sistemas físicos sejam confirmadas ou revertidas de forma consistente. Se o 2PC é usado em um sistema ou em vários sistemas, por meio de um gerenciador de transações, ele carrega uma sobrecarga significativa.
Em um protocolo de confirmação de duas fases, o gerenciador de transações solicita aos nós participantes que persistam na transação, de forma que eles possam garantir que ela será confirmada e sinalize esse status. Quando todos os nós retornam um status 'feliz', ele sinaliza os nós para confirmar. A transação ainda é considerada aberta até que todos os nós enviem uma resposta indicando que a confirmação está concluída. Se um nó for desativado antes de sinalizar a conclusão da confirmação, o gerenciador de transações fará uma nova consulta ao nó quando voltar a funcionar até obter uma resposta positiva indicando que a transação foi confirmada.
Controle de simultaneidade de várias versões (MVCC): gerenciando contenções gravando novas versões dos dados em um local diferente e permitindo que outras transações vejam a versão antiga dos dados até que a nova versão seja confirmada. Isso reduz a contenção do banco de dados à custa de algum tráfego de gravação adicional para gravar a nova versão e depois marcar a versão antiga como obsoleta.
Algoritmo de eleição: Os sistemas distribuídos que envolvem vários nós são inerentemente menos confiáveis que um único sistema, pois há mais modos de falha. Em muitos casos, é necessário algum mecanismo para os sistemas em cluster lidarem com a falha de um nó. Os algoritmos de eleição são uma classe de algoritmos usados para selecionar um líder para coordenar uma computação distribuída em situações em que o nó 'líder' não é 100% determinado ou confiável.
Particionamento horizontal: uma tabela pode ser dividida em vários nós ou volumes de armazenamento por sua chave. Isso permite que um grande volume de dados seja dividido em partes menores e distribuído pelos nós de armazenamento.
Sharding: Um conjunto de dados pode ser particionado horizontalmente em vários nós físicos em uma arquitetura de nada compartilhado. Onde esse particionamento não é transparente (ou seja, o cliente deve estar ciente do esquema de partição e descobrir qual nó consultar explicitamente), isso é conhecido como sharding. Alguns sistemas (por exemplo, Teradata) dividem dados entre nós, mas o local é transparente para o cliente; o termo normalmente não é usado em conjunto com esse tipo de sistema.
Hashing consistente: um algoritmo usado para alocar dados para partições com base na chave. É caracterizada pela distribuição uniforme das chaves de hash e pela capacidade de expandir ou reduzir elasticamente o número de caçambas com eficiência. Esses atributos o tornam útil para particionar dados ou carregar em um cluster de nós em que o tamanho pode mudar dinamicamente com os nós sendo adicionados ou retirados do cluster (talvez devido a falha).
Replicação multimestre: Uma técnica que permite que gravações em vários nós em um cluster sejam replicadas para os outros nós. Essa técnica facilita o dimensionamento, permitindo que algumas tabelas sejam particionadas ou divididas entre servidores e outras sejam sincronizadas no cluster. As gravações devem ser replicadas para todos os nós, em oposição a um quorum, para que as confirmações de transação sejam mais caras em uma arquitetura replicada multimestre que em um sistema replicado por quorum.
Comutador sem bloqueio: Um comutador de rede que usa paralelismo interno de hardware para obter uma taxa de transferência proporcional ao número de portas sem gargalos internos. Uma implementação ingênua pode usar um mecanismo de barra cruzada, mas isso tem complexidade O (N ^ 2) para N portas, limitando-o a comutadores menores. Switches maiores podem usar mais uma topologia interna complexa chamada switch de abrangência mínima não-bloqueante para obter escala de throughput linear sem a necessidade de hardware O (N ^ 2).
Criando um DBMS distribuído - quão difícil pode ser?
Vários desafios técnicos tornam isso bastante difícil na prática. Além da complexidade adicional de criar um sistema distribuído, o arquiteto de um DBMS distribuído precisa superar alguns problemas complicados de engenharia.
Atomicidade em sistemas distribuídos: se os dados atualizados por uma transação estiverem espalhados por vários nós, a confirmação / reversão dos nós deverá ser coordenada. Isso adiciona uma sobrecarga significativa nos sistemas de nada compartilhado. Em sistemas de disco compartilhado, isso é menos problemático, pois todo o armazenamento pode ser visto por todos os nós, para que um único nó possa coordenar a confirmação.
Consistência em sistemas distribuídos: Para usar o exemplo de chave estrangeira citado acima, o sistema deve poder avaliar um estado consistente. Por exemplo, se o pai e o filho de um relacionamento de chave estrangeira pudessem residir em nós diferentes, algum tipo de mecanismo de bloqueio distribuído é necessário para garantir que informações desatualizadas não sejam usadas para validar a transação. Se isso não for imposto, você poderá ter (por exemplo) uma condição de corrida em que o pai é excluído após a verificação de sua presença antes de permitir a inserção do filho.
A imposição atrasada de restrições (ou seja, aguardar a confirmação para validar o DRI) exige que o bloqueio seja mantido durante a transação. Esse tipo de bloqueio distribuído vem com uma sobrecarga significativa.
Se várias cópias de dados forem mantidas (isso pode ser necessário em sistemas de nada compartilhado para evitar tráfego de rede desnecessário de semi-junções), todas as cópias dos dados deverão ser atualizadas.
Isolamento em sistemas distribuídos: onde os dados afetados em uma transação residem em vários nós do sistema, os bloqueios e a versão (se o MVCC estiver em uso) devem ser sincronizados entre os nós. Garantir a serialização das operações, particularmente em arquiteturas de compartilhamento de nada, onde cópias redundantes de dados podem ser armazenadas, requer um mecanismo de sincronização distribuído, como o Algorithm de Lamport, que também possui uma sobrecarga significativa no tráfego de rede.
Durabilidade em sistemas distribuídos: em um sistema de disco compartilhado, o problema de durabilidade é essencialmente o mesmo que um sistema de memória compartilhada, com a exceção de que os protocolos de sincronização distribuídos ainda são necessários entre os nós. O DBMS deve registrar em diário as gravações no log e gravar os dados de maneira consistente. Em um sistema de nada compartilhado, pode haver várias cópias dos dados ou partes dos dados armazenados em nós diferentes. É necessário um protocolo de confirmação de duas fases para garantir que a confirmação ocorra corretamente entre os nós. Isso também gera uma sobrecarga significativa.
Em um sistema de nada compartilhado, a perda de um nó pode significar que os dados não estão disponíveis para o sistema. Para mitigar esses dados, eles podem ser replicados em mais de um nó. A consistência nessa situação significa que os dados devem ser replicados para todos os nós em que normalmente residem. Isso pode gerar uma sobrecarga substancial nas gravações.
Uma otimização comum feita nos sistemas NoSQL é o uso de replicação de quorum e eventual consistência para permitir a replicação lenta dos dados, garantindo um certo nível de resiliência dos dados gravando em um quorum antes de relatar a transação como confirmada. Os dados são replicados preguiçosamente para os outros nós em que as cópias dos dados residem.
Observe que 'consistência eventual' é uma grande troca de consistência que pode não ser aceitável se os dados precisarem ser visualizados de maneira consistente assim que a transação for confirmada. Por exemplo, em um aplicativo financeiro, um saldo atualizado deve estar disponível imediatamente.
Sistemas de disco compartilhado
Um sistema de disco compartilhado é aquele em que todos os nós têm acesso a todo o armazenamento. Assim, o cálculo é independente da localização. Muitas plataformas DBMS também podem funcionar nesse modo - o Oracle RAC é um exemplo dessa arquitetura.
Os sistemas de disco compartilhado podem escalar substancialmente, pois podem suportar um relacionamento M: M entre nós de armazenamento e nós de processamento. Uma SAN pode ter vários controladores e vários servidores podem executar o banco de dados. Essas arquiteturas têm um comutador como gargalo central, mas os comutadores de barra cruzada permitem que esse comutador tenha muita largura de banda. Algum processamento pode ser transferido para os nós de armazenamento (como no caso do Exadata da Oracle), o que pode reduzir o tráfego na largura de banda de armazenamento.
Embora o switch seja teoricamente um gargalo, a largura de banda disponível significa que as arquiteturas de disco compartilhado serão dimensionadas com bastante eficiência para grandes volumes de transações. A maioria das arquiteturas de DBMS convencionais adotam essa abordagem porque oferece escalabilidade 'suficientemente boa' e alta confiabilidade. Com uma arquitetura de armazenamento redundante, como o Fibre Channel, não há um ponto único de falha, pois há pelo menos dois caminhos entre qualquer nó de processamento e qualquer nó de armazenamento.
Sistemas Shared-Nothing
Sistemas de compartilhamento de nada são sistemas em que pelo menos alguns dados são mantidos localmente em um nó e não são diretamente visíveis para outros nós. Isso remove o gargalo de um comutador central, permitindo que o banco de dados seja escalado (pelo menos em teoria) com o número de nós. O particionamento horizontal permite que os dados sejam divididos entre nós; isso pode ser transparente para o cliente ou não (consulte Sharding acima).
Como os dados são inerentemente distribuídos, uma consulta pode exigir dados de mais de um nó. Se uma junção precisar de dados de nós diferentes, uma operação de semi-junção será usada para transferir dados suficientes para suportar a junção de um nó para outro. Isso pode resultar em uma grande quantidade de tráfego de rede, portanto, otimizar a distribuição dos dados pode fazer uma grande diferença no desempenho da consulta.
Geralmente, os dados são replicados entre os nós de um sistema de nada compartilhado para reduzir a necessidade de semi-junções. Isso funciona muito bem em dispositivos de armazenamento de dados, pois as dimensões são geralmente muitas ordens de magnitude menores que as tabelas de fatos e podem ser facilmente replicadas entre os nós. Eles também são normalmente carregados em lotes, portanto a sobrecarga de replicação é menos problemática do que em um aplicativo transacional.
O paralelismo inerente a uma arquitetura de nada compartilhado os torna adequados para o tipo de consulta de varredura de tabela / consultas agregadas característica de um data warehouse. Esse tipo de operação pode ser dimensionado quase linearmente com o número de nós de processamento. Junções grandes entre nós tendem a gerar mais sobrecarga, pois as operações de semi-junção podem gerar muito tráfego de rede.
Mover grandes volumes de dados é menos útil para aplicativos de processamento de transações, onde a sobrecarga de várias atualizações torna esse tipo de arquitetura menos atraente que um disco compartilhado. Portanto, esse tipo de arquitetura tende a não ser amplamente utilizado em aplicativos de data warehouse.
Fragmento, Replicação de Quorum e Consistência Eventual
A Replicação de Quorum é um recurso em que um DBMS replica dados para alta disponibilidade. Isso é útil para sistemas destinados a trabalhar em hardware básico mais barato que não possui recursos internos de alta disponibilidade, como uma SAN. Nesse tipo de sistema, os dados são replicados em vários nós de armazenamento para desempenho de leitura e armazenamento redundante para tornar o sistema resiliente à falha de hardware de um nó.
No entanto, a replicação de gravações em todos os nós é O (M x N) para M nós e N gravações. Isso torna as gravações caras se a gravação precisar ser replicada para todos os nós antes que uma transação possa confirmar. A replicação de quorum é um compromisso que permite que as gravações sejam replicadas em um subconjunto dos nós imediatamente e depois gravadas preguiçosamente nos outros nós por uma tarefa em segundo plano. As gravações podem ser confirmadas mais rapidamente, fornecendo um certo grau de redundância, garantindo que sejam replicadas para um subconjunto mínimo (quorum) de nós antes que a transação seja relatada como confirmada no cliente.
Isso significa que a leitura de nós fora do quorum pode ver versões obsoletas dos dados até que o processo em segundo plano termine de gravar os dados no restante dos nós. A semântica é conhecida como 'Consistência Eventual' e pode ou não ser aceitável, dependendo dos requisitos do seu aplicativo, mas significa que os commits de transação estão mais próximos de O (1) que O (n) no uso de recursos.
O compartilhamento exige que o cliente esteja ciente do particionamento de dados nos bancos de dados, geralmente usando um tipo de algoritmo conhecido como 'hash consistente'. Em um banco de dados fragmentado, o cliente faz o hash da chave para determinar em qual servidor do cluster emitir a consulta. Como as solicitações são distribuídas entre nós no cluster, não há gargalo com um único nó coordenador de consulta.
Essas técnicas permitem que um banco de dados seja escalado a uma taxa quase linear, adicionando nós ao cluster. Teoricamente, a replicação de quorum é necessária apenas se o meio de armazenamento subjacente for considerado não confiável. Isso é útil se os servidores comuns forem usados, mas terá menos valor se o mecanismo de armazenamento subjacente tiver seu próprio esquema de alta disponibilidade (por exemplo, uma SAN com controladores espelhados e conectividade de vários caminhos para os hosts).
Por exemplo, o BigTable do Google não implementa a Replicação de Quorum por si só, embora assente no GFS, um sistema de arquivos em cluster que usa replicação de quorum. O BigTable (ou qualquer sistema que não compartilha nada) pode usar um sistema de armazenamento confiável com vários controladores e particionar os dados entre os controladores. O acesso paralelo seria alcançado através do particionamento dos dados.
Voltar para plataformas RDBMS
Não há razão inerente para que essas técnicas não possam ser usadas com um RDBMS. No entanto, o gerenciamento de bloqueio e versão seria bastante complexo em um sistema desse tipo e qualquer mercado para esse sistema provavelmente será bastante especializado. Nenhuma das plataformas RDBMS convencionais usa replicação de quorum e não estou especificamente ciente de qualquer produto RDBMS (pelo menos não um com qualquer aceitação significativa) que o faça.
Os sistemas de disco compartilhado e nada compartilhado podem escalar cargas de trabalho muito grandes. Por exemplo, o Oracle RAC pode suportar 63 nós de processamento (que podem ser grandes máquinas SMP por si só) e um número arbitrário de controladores de armazenamento na SAN. Um IBM Sysplex (um cluster de mainframes do zSeries) pode suportar vários mainframes (cada um com capacidade de processamento substancial e largura de banda de E / S própria) e vários controladores SAN. Essas arquiteturas podem suportar volumes de transações muito grandes com semântica ACID, embora eles assumam armazenamento confiável. Teradata, Netezza e outros fornecedores criam plataformas analíticas de alto desempenho baseadas em projetos de compartilhamento de nada que escalam para volumes de dados extremamente grandes.
Até agora, o mercado de plataformas de baixo custo, mas ultra-altas e totalmente ACID RDBMS é dominado pelo MySQL, que suporta fragmentação e replicação multimestre. O MySQL não usa replicação de quorum para otimizar a taxa de transferência de gravação, portanto, as confirmações de transação são mais caras do que em um sistema NoSQL. O sharding permite taxas de transferência de leitura muito altas (por exemplo, o Facebook usa o MySQL extensivamente); portanto, esse tipo de arquitetura se adapta bem a cargas de trabalho com muita leitura.
Um debate interessante
BigTable é uma arquitetura de nada compartilhado (essencialmente um par de valor-chave distribuído), como apontado por Michael Hausenblas abaixo . Minha avaliação original incluiu o mecanismo MapReduce, que não faz parte do BigTable, mas normalmente seria usado em conjunto com ele em suas implementações mais comuns (por exemplo, Hadoop / HBase e a estrutura MapReduce do Google).
Comparando essa arquitetura com o Teradata, que possui afinidade física entre armazenamento e processamento (ou seja, os nós têm armazenamento local em vez de uma SAN compartilhada), você pode argumentar que BigTable / MapReduce é uma arquitetura de disco compartilhada por meio do sistema de armazenamento paralelo visível globalmente.
A taxa de transferência de processamento de um sistema de estilo MapReduce, como o Hadoop, é restringida pela largura de banda de um comutador de rede sem bloqueio. 1 Os switches sem bloqueio podem, no entanto, lidar com grandes agregados de largura de banda devido ao paralelismo inerente ao projeto, de modo que raramente são uma restrição prática significativa ao desempenho. Isso significa que uma arquitetura de disco compartilhado (talvez mais conhecida como sistema de armazenamento compartilhado) pode ser dimensionada para grandes cargas de trabalho, mesmo que o comutador de rede seja teoricamente um gargalo central.
O ponto original era observar que, embora esse gargalo central exista em sistemas de disco compartilhado, um subsistema de armazenamento particionado com vários nós de armazenamento (por exemplo, servidores de tablets BigTable ou controladores SAN) ainda pode escalar até grandes cargas de trabalho. Uma arquitetura de comutador sem bloqueio pode (em teoria) lidar com tantas conexões atuais quanto portas.
1 Obviamente, o processamento e a taxa de transferência de E / S disponíveis também constituem um limite no desempenho, mas o comutador de rede é um ponto central pelo qual todo o tráfego passa.