- A Apple usa Cassandra e FoundationDB para o iCloud e o CloudKit
- Esses bancos de dados armazenam bilhões de bancos de dados em uma arquitetura de multitenancy extrema
Lições reais que atravessam o tempo
- Tanto a Meta quanto a Apple usam processamento assíncrono para viabilizar recursos para os usuários
- As duas empresas usam uma arquitetura stateless para resolver problemas de escalabilidade
- Isolam recursos logicamente para garantir confiabilidade e disponibilidade
- Lidam com requisitos diversos de forma simples
- Constroem camadas de abstração para melhorar a experiência do desenvolvedor
- Conhecem seus usuários e definem cada camada, API e design com base nisso
Cassandra
- Cassandra é um sistema de gerenciamento de banco de dados NoSQL de colunas largas
- Foi originalmente desenvolvido no Facebook para o recurso de busca da caixa de entrada do Facebook
- Curiosamente, a própria Meta substituiu a maior parte do uso de Cassandra pelo ZippyDB
- O iCloud usa Cassandra em parte, e a Apple opera uma das maiores implantações de Cassandra do mundo
- mais de 300 mil instâncias/nós
- centenas de petabytes de dados
- mais de 2 petabytes por cluster
- milhões de consultas por segundo
- milhares de aplicações
- O Cassandra continua sendo ativamente aprimorado dentro da Apple
- Porém, CloudKit + Cassandra esbarrou em limites de escalabilidade, o que levou à adoção do FoundationDB
FoundationDB
- A Apple usa FoundationDB publicamente e o adquiriu em 2015
- FoundationDB é um armazenamento transacional distribuído open source de chave-valor, projetado para lidar com dados em grande escala
- adequado tanto para cargas de leitura/escrita quanto para cargas com muita escrita
- A Apple usa amplamente o FoundationDB Record Layer no CloudKit
- O FoundationDB Record Layer fornece uma API Java para armazenamento de dados estruturados
- O Record Layer oferece suporte a multitenancy extrema
Por que usar o Record Layer sobre o FoundationDB
- O FoundationDB lida com sistemas distribuídos e controle de concorrência.
- O Record Layer funciona como um banco de dados relacional que torna o FoundationDB mais fácil de usar.
- O CloudKit fica na camada mais alta, oferecendo recursos e APIs para desenvolvedores de aplicações.
- Por meio do Record Layer, a Apple oferece suporte a multitenancy em larga escala
- usado para multitenancy extrema, fornecendo um armazenamento de registros independente para cada usuário de cada aplicação
- hospeda bilhões de bancos de dados independentes compartilhando milhares de esquemas
Como o CloudKit usa FoundationDB e o Record Layer
- No CloudKit, as aplicações são representadas como 'contêineres lógicos' que seguem um esquema definido
- esse esquema descreve de forma resumida os tipos de registro, campos e índices necessários para recuperação e consulta eficiente dos dados
- as aplicações podem organizar dados em 'zonas' dentro do CloudKit, agrupando registros logicamente para permitir sincronização opcional com dispositivos clientes
- Cada usuário recebe um subspace exclusivo dentro do FoundationDB, e é criado um armazenamento de registros para cada aplicação com a qual o usuário interage
- em essência, o CloudKit gerencia um número gigantesco de bancos de dados lógicos equivalente ao número de usuários multiplicado pelo número de aplicações
- cada banco de dados inclui seu próprio conjunto de registros, índices e metadados, chegando a bilhões de bancos de dados
- Quando o CloudKit recebe uma solicitação de um dispositivo cliente, ele a encaminha por balanceamento de carga para um processo de serviço do CloudKit disponível
- o processo interage com um armazenamento de registros específico do Record Layer para atender à solicitação
- O CloudKit converte o esquema definido da aplicação em definições de metadados dentro do Record Layer, armazenadas em um repositório de metadados separado
- esses metadados são complementados por campos de sistema específicos do CloudKit, que rastreiam criação e modificação de registros, além da zona em que o registro foi armazenado
- para permitir acesso eficiente aos registros dentro de cada zona, a chave primária recebe como prefixo o nome da zona
- junto com índices definidos pelo usuário, o CloudKit também mantém 'índices de sistema' para usos internos, como gerenciar cotas de armazenamento, acompanhando por exemplo o tamanho dos registros por tipo
O uso conjunto de FoundationDB e Record Layer resolve 4 problemas principais da Apple que só o Cassandra não conseguia resolver
1. Resolver o problema da busca de texto completo personalizada
- O FoundationDB oferece suporte à busca de texto completo personalizada, permitindo que usuários acessem rapidamente seus próprios dados
- Aproveitando a ordenação de chaves do FoundationDB, é possível pesquisar rapidamente o início do texto (correspondência por prefixo), além de executar buscas mais complexas sem overhead adicional, como busca por proximidade e busca por frase, encontrando palavras próximas entre si ou em uma ordem específica
- Em sistemas de busca tradicionais, muitas vezes é preciso executar processos extras em background para manter os índices de busca atualizados, mas o sistema da Apple faz tudo em tempo real, então o índice é atualizado imediatamente sempre que os dados mudam, sem etapas adicionais
2. Resolver o problema de zonas com alta concorrência
- O CloudKit usa FoundationDB para lidar de forma fluida com muitas atualizações simultâneas
- Antes, ao usar Cassandra, o CloudKit dependia de um índice especial que rastreava mudanças em cada zona para sincronizar dados entre vários dispositivos
- quando um dispositivo precisava atualizar dados, verificava esse índice para ver o que havia de novo
- porém, quando várias atualizações aconteciam ao mesmo tempo, isso podia gerar conflitos
- Com FoundationDB, o CloudKit usa um tipo especial de índice que acompanha a ordem exata de cada mudança sem causar conflitos
- isso é feito atribuindo uma 'versão' única a cada alteração, e o CloudKit verifica essas versões quando precisa sincronizar para descobrir quais atualizações o dispositivo perdeu
- Quando o CloudKit precisa mover dados entre vários clusters de armazenamento para distribuir melhor a carga, a situação fica mais complicada, porque os números de versão de cada cluster não coincidem
- para resolver isso, o CloudKit atribui a cada dado de usuário uma 'contagem de migrações' (chamada de 'incarnation') que aumenta sempre que os dados são movidos para um novo cluster
- cada atualização de registro inclui o número atual de 'incarnation' do usuário, então, mesmo após a migração, o CloudKit consegue determinar a ordem correta das atualizações verificando tanto a incarnation quanto o número de versão
- Ao migrar para o novo sistema, o CloudKit enfrentou o problema de lidar com dados antigos que não tinham esses números de versão
- mas superou isso de forma inteligente com um recurso especial que ordena atualizações antigas feitas pelo sistema anterior antes das atualizações do novo sistema
- isso evitou a necessidade de mudanças complexas nas aplicações ou de manter código legado
- para preservar a ordem correta do histórico, considera os valores de incarnation, versão e contagem de atualizações antigas
3. Resolver o problema de consultas com alta latência
- O FoundationDB foi projetado para alta concorrência, e não para baixa latência. Ou seja, consegue lidar com muitas operações ao mesmo tempo em vez de focar na velocidade de cada operação individualmente
- Para aproveitar isso ao máximo, o Record Layer executa muitas operações de forma assíncrona
- colocando tarefas em fila para conclusão futura e permitindo executar outras tarefas nesse meio-tempo
- essa abordagem ajuda a encobrir a latência que pode surgir durante essas operações
- Porém, a ferramenta que o FoundationDB usa para se comunicar com o banco de dados foi projetada para usar uma única thread para rede, processando uma tarefa por vez
- em versões anteriores, essa configuração causava congestionamento porque todas as operações precisavam esperar sua vez nessa thread de rede
- como o Record Layer usava essa abordagem de thread única, isso criava um gargalo
- Para melhorar isso, a Apple reduziu a carga de trabalho dessa thread de rede
- agora o sistema consegue trabalhar com o banco de dados em vários aspectos ao mesmo tempo, sem formar filas, tornando operações complexas mais rápidas
- isso mascara a latência ou a lentidão percebida, porque o sistema não precisa esperar uma operação terminar antes de iniciar outra
4. Resolver o problema de transações em conflito
- No FoundationDB, ocorre um 'conflito de transação' quando uma transação lê uma chave específica enquanto outra transação modifica essa mesma chave
- o FoundationDB oferece controle sobre os conjuntos de chaves que podem gerar esses conflitos em leituras ou escritas, permitindo gerenciá-los com precisão
- Uma forma comum de evitar conflitos desnecessários é fazer um tipo especial de leitura que não gera conflito, a leitura de 'snapshot', sobre várias chaves
- se essa leitura encontrar uma chave importante, a transação marca apenas a chave específica com potencial de conflito, e não o intervalo inteiro
- assim, a transação só é afetada por mudanças que realmente importam para o resultado
- O Record Layer usa essa estratégia para gerenciar com eficiência uma estrutura chamada skip list, que faz parte do sistema de índice de ranking
- porém, definir manualmente esses intervalos de conflito pode ser complicado e, especialmente quando misturado à lógica principal da aplicação, pode levar a bugs difíceis de identificar
- por isso, em sistemas construídos sobre o FoundationDB, é recomendável criar ferramentas de nível mais alto, como índices customizados, para lidar com esses padrões
- essa abordagem ajuda a evitar situações em que a responsabilidade por flexibilizar as regras de conflito fica com cada aplicação cliente, o que pode causar erros e inconsistências
1 comentários
Comentários do Hacker News
Um usuário do Hacker News compartilhou percepções sobre a diferença entre bancos de dados e sistemas de arquivos quando trabalhava na Apple. Ele mencionou que bancos de dados e sistemas de arquivos desempenham fundamentalmente a mesma função e são otimizações para resolver problemas específicos. Como exemplo, o iCloud mostra uma forma de definir um sistema de arquivos com base em um banco de dados. Esse usuário também compartilhou sua experiência usando Cassandra para armazenar vídeos.
Outro usuário mencionou a experiência de ter construído um sistema de catálogo transacional em uma empresa anterior usando FoundationDB e RecordLayer. O sistema era muito eficaz, e usar gRPC e Protobuf parecia natural. No entanto, apontou como desvantagem a alta barreira de entrada para operar o FoundationDB em grande escala.
Um usuário avaliou que o recurso de sincronização do Apple Notes lida melhor com conflitos do que aplicativos de notas baseados em Markdown. Por isso, mencionou que acabou migrando para o Apple Notes.
Foram mencionadas postagens anteriores sobre o FoundationDB. Isso inclui links sobre o armazenamento distribuído de chave-valor do FoundationDB, a Record Layer, a aquisição pela Apple, além do funcionamento e das características do FoundationDB.
Foi mencionado um ponto interessante sobre a arquitetura de softwares nativos de desktop que estão migrando gradualmente para armazenamento e colaboração baseados em nuvem. É importante lidar bem com mudanças de esquema e migrações de versão, e isso acontece em grande escala sem intervenção de administradores.
Um usuário gostaria que o iCloud pudesse armazenar backups do Time Machine.
Como o FoundationDB é baseado em SQLite, foi levantada uma curiosidade sobre a possibilidade de aplicar o mecanismo HCTree ao FoundationDB. O HCTree tem potencial para melhorar em 10 vezes o desempenho de leitura e escrita do SQLite.
Houve reclamações sobre como o iCloud gerencia os arquivos dos usuários. Às vezes, torna-se um problema quando o iCloud move automaticamente para a nuvem arquivos, aplicativos e fotos usados recentemente para liberar espaço.
Um usuário relembrou um sistema de relatórios chamado Hyperion, que usou quando trabalhava em um banco no passado. Esse sistema criava um novo banco de dados para cada relatório, o que na época parecia estranho, mas hoje, pensando bem, ele mencionou que era uma abordagem à frente do seu tempo.