Registro distribuído para conjuntos de rodas: uma experiência com o Hyperledger Fabric

Olá, trabalho na equipe do projeto DRD KP (cadastro de dados distribuídos para monitoramento do ciclo de vida de rodados). Quero compartilhar aqui a experiência de nossa equipe no desenvolvimento de um blockchain empresarial para este projeto sob as restrições da tecnologia. Falarei principalmente sobre o Hyperledger Fabric, mas a abordagem descrita aqui pode ser extrapolada para qualquer blockchain permitido. O objetivo final da nossa pesquisa é preparar soluções empresariais de blockchain para que o produto final seja agradável de usar e não muito difícil de manter.

Não haverá descobertas, soluções inesperadas e nenhum desenvolvimento único será destacado aqui (porque não tenho nenhum). Quero apenas compartilhar minha modesta experiência, mostrar que “foi possível” e, quem sabe, ler nos comentários sobre a experiência de outras pessoas em tomar decisões boas e não tão boas.

Problema: Blockchains ainda não escalam

Hoje, os esforços de muitos desenvolvedores visam tornar o blockchain uma tecnologia verdadeiramente conveniente, e não uma bomba-relógio em uma bela embalagem. Canais estaduais, rollup otimista, plasma e fragmentação provavelmente se tornarão comuns. Algum dia. Ou talvez a TON adie novamente o lançamento por seis meses, e o próximo Grupo Plasma deixará de existir. Podemos acreditar no próximo roteiro e ler documentos brancos brilhantes à noite, mas aqui e agora precisamos fazer algo com o que temos. Faça a merda.

A tarefa definida para a nossa equipe no projeto atual é em geral assim: há muitos sujeitos, chegando a vários milhares, que não querem construir relações de confiança; É necessário construir uma solução em DLT que funcione em PCs comuns sem requisitos especiais de desempenho e forneça uma experiência de usuário não pior do que qualquer sistema de contabilidade centralizado. A tecnologia por trás da solução deve minimizar a possibilidade de manipulação maliciosa de dados – é por isso que o blockchain está aqui.

Slogans de white papers e da mídia nos prometem que o próximo desenvolvimento nos permitirá fazer milhões de transações por segundo. O que é isso realmente?

Mainnet Ethereum está atualmente rodando a aproximadamente 30 tps. Só por causa disso, é difícil percebê-lo como blockchain de alguma forma adequado às necessidades corporativas. Entre as soluções permitidas, há benchmarks que mostram 2000 tps (Quorum) ou 3000 tps (Tela de hiperligação, há um pouco menos na publicação, mas é preciso levar em consideração que o benchmark foi realizado no antigo mecanismo de consenso). Era uma tentativa de processamento radical de Fabric, que não deu os piores resultados, 20000 tps, mas até agora são apenas pesquisas acadêmicas, aguardando sua implementação estável. É improvável que uma empresa que possa manter um departamento de desenvolvedores de blockchain tolere tais indicadores. Mas o problema não é apenas o rendimento, há também a latência.

Latência

O atraso desde o início de uma transação até a sua aprovação final pelo sistema depende não apenas da velocidade com que a mensagem passa por todas as etapas de validação e ordenação, mas também dos parâmetros de formação do bloco. Mesmo que nosso blockchain nos permita comprometer a uma velocidade de 1000000 tps, mas exija 10 minutos para gerar um bloco de 488 MB, será mais fácil para nós?

Vamos dar uma olhada mais de perto no ciclo de vida da transação no Hyperledger Fabric para entender onde o tempo é gasto e como ele se relaciona com os parâmetros de geração de blocos.

Registro distribuído para conjuntos de rodas: uma experiência com o Hyperledger Fabric
tirado daqui: hyperledger-fabric.readthedocs.io/en/release-1.4/arch-deep-dive.html#swimlane

(1) O cliente cria uma transação, envia-a para os pares endossantes, estes últimos simulam a transação (aplicam as alterações feitas pelo chaincode ao estado atual, mas não se comprometem com o razão) e recebem RWSet - nomes de chaves, versões e valores ​retirado da coleção no CouchDB, (2) os endossantes enviam de volta um RWSet assinado ao cliente, (3) o cliente verifica a presença de assinaturas de todos os pares necessários (endossantes) e, em seguida, envia a transação para o serviço de pedido , ou envia sem verificação (a verificação ainda ocorrerá posteriormente), o serviço de pedido forma um bloco e (4) envia de volta para todos os pares, não apenas para os endossantes; os pares verificam se as versões da chave no conjunto de leitura correspondem às versões no banco de dados, se todos os endossantes possuem assinaturas e, finalmente, confirmam o bloco.

Mas isso não é tudo. As palavras “ordenador forma um bloco” escondem não apenas a ordem das transações, mas também 3 solicitações de rede sequenciais do líder para os seguidores e vice-versa: o líder adiciona uma mensagem ao log, envia para os seguidores, este último adiciona para seu log, envia a confirmação de replicação bem-sucedida ao líder, o líder confirma a mensagem, envia a confirmação de commit aos seguidores, os seguidores confirmam. Quanto menor o tamanho e o tempo de formação do bloco, mais frequentemente o serviço de pedidos terá que estabelecer consenso. O Hyperledger Fabric possui dois parâmetros para formação de bloco: BatchTimeout - tempo de formação do bloco e BatchSize - tamanho do bloco (o número de transações e o tamanho do próprio bloco em bytes). Assim que um dos parâmetros atingir o limite, um novo bloco é liberado. Quanto mais nós de pedido, mais tempo levará. Portanto, você precisa aumentar BatchTimeout e BatchSize. Mas como os RWSets são versionados, quanto maior o bloco que criamos, maior a probabilidade de conflitos de MVCC. Além disso, à medida que BatchTimeout aumenta, a UX degrada catastroficamente. O esquema a seguir para resolver esses problemas parece razoável e óbvio para mim.

Como evitar a espera pela finalização do bloco e não perder a capacidade de rastrear o status da transação

Quanto maior o tempo de formação e o tamanho do bloco, maior será o rendimento do blockchain. Um não decorre diretamente do outro, mas deve-se lembrar que o estabelecimento de consenso no RAFT requer três solicitações de rede do líder aos seguidores e vice-versa. Quanto mais nós de pedido, mais tempo levará. Quanto menor o tamanho e o tempo de formação do bloco, mais interações existem. Como aumentar o tempo de geração e o tamanho do bloco sem aumentar o tempo de resposta do sistema para o usuário final?

Primeiro, precisamos resolver de alguma forma os conflitos de MVCC causados ​​por um tamanho de bloco grande, que pode incluir diferentes RWSets com a mesma versão. Obviamente, do lado do cliente (em relação à rede blockchain, este poderia muito bem ser o backend, e estou falando sério) você precisa Manipulador de conflitos MVCC, que pode ser um serviço separado ou um decorador regular acima da chamada que inicia a transação com lógica de nova tentativa.

A nova tentativa pode ser implementada com uma estratégia exponencial, mas a latência será degradada de forma igualmente exponencial. Portanto, você deve usar uma nova tentativa aleatória dentro de certos limites pequenos ou uma tentativa constante. De olho em possíveis colisões na primeira opção.

O próximo passo é tornar assíncrona a interação do cliente com o sistema para que ele não espere 15, 30 ou 10000000 segundos, que definiremos como BatchTimeout. Mas, ao mesmo tempo, é necessário manter a capacidade de verificar se as alterações iniciadas pela transação são/não são registradas no blockchain.
Um banco de dados pode ser usado para armazenar o status da transação. A opção mais simples é o CouchDB devido à sua facilidade de uso: o banco de dados possui uma UI pronta para uso, uma API REST e você pode configurar facilmente a replicação e o sharding para ele. Você pode simplesmente criar uma coleção separada na mesma instância do CouchDB que usa o Fabric para armazenar seu estado mundial. Precisamos armazenar esses tipos de documentos.

{
 Status string // Статус транзакции: "pending", "done", "failed"
 TxID: string // ID транзакции
 Error: string // optional, сообщение об ошибке
}

Este documento é gravado no banco de dados antes que a transação seja enviada aos pares, o ID da entidade é retornado ao usuário (o mesmo ID é usado como chave) se esta for uma operação de criação, e então os campos Status, TxID e Erro são atualizado à medida que informações relevantes são recebidas dos pares.

Registro distribuído para conjuntos de rodas: uma experiência com o Hyperledger Fabric

Nesse esquema, o usuário não espera a formação definitiva do bloco, observando a roda girando na tela por 10 segundos, ele recebe uma resposta instantânea do sistema e continua trabalhando.

Escolhemos o BoltDB para armazenar status de transações porque precisamos economizar memória e não queremos perder tempo na interação da rede com um servidor de banco de dados separado, especialmente quando essa interação ocorre usando um protocolo de texto simples. A propósito, quer você use o CouchDB para implementar o esquema descrito acima ou simplesmente para armazenar o estado mundial, em qualquer caso, faz sentido otimizar a forma como os dados são armazenados no CouchDB. Por padrão, no CouchDB, o tamanho dos nós da árvore b é 1279 bytes, que é muito menor que o tamanho do setor no disco, o que significa que tanto a leitura quanto o rebalanceamento da árvore exigirão mais acesso físico ao disco. O tamanho ideal corresponde ao padrão Formato Avançado e tem 4 kilobytes. Para otimizar, precisamos definir o parâmetro btree_chunk_size igual a 4096 no arquivo de configuração do CouchDB. Para BoltDB, tal intervenção manual Não exige.

Contrapressão: estratégia de buffer

Mas pode haver muitas mensagens. Mais do que o sistema pode suportar, compartilhando recursos com uma dúzia de outros serviços além daqueles mostrados no diagrama - e tudo isso deve funcionar perfeitamente mesmo em máquinas nas quais executar o Intellij Idea seria extremamente tedioso.

O problema das diferentes capacidades dos sistemas de comunicação, produtor e consumidor, é resolvido de diferentes maneiras. Vamos ver o que poderíamos fazer.

Caindo: Podemos afirmar que somos capazes de processar no máximo X transações em T segundos. Todas as solicitações que excederem esse limite serão descartadas. Isso é bastante simples, mas você pode esquecer a UX.

Controlador: o consumidor deve ter algum tipo de interface através da qual, dependendo da carga, possa controlar o TPS do produtor. Nada mal, mas impõe obrigações aos desenvolvedores do cliente que criam a carga para implementar esta interface. Isto é inaceitável para nós, uma vez que a blockchain será, no futuro, integrada num grande número de sistemas já existentes.

Carregando: Em vez de tentar resistir ao fluxo de dados de entrada, podemos armazenar esse fluxo em buffer e processá-lo na velocidade necessária. Obviamente esta é a melhor solução se quisermos proporcionar uma boa experiência ao usuário. Implementamos o buffer usando uma fila no RabbitMQ.

Registro distribuído para conjuntos de rodas: uma experiência com o Hyperledger Fabric

Duas novas ações foram adicionadas ao esquema: (1) após a chegada de uma solicitação à API, uma mensagem com os parâmetros necessários para chamar uma transação é colocada na fila e o cliente recebe uma mensagem informando que a transação foi aceita pelo o sistema, (2) o backend lê os dados na velocidade especificada na configuração da fila; inicia uma transação e atualiza os dados no armazenamento de status.
Agora você pode aumentar o tempo de formação e bloquear a capacidade o quanto quiser, escondendo atrasos do usuário.

Outras ferramentas

Nada foi dito aqui sobre chaincode, pois, via de regra, não há nada para otimizar nele. O Chaincode deve ser o mais simples e seguro possível - isso é tudo o que é necessário. A estrutura nos ajuda a escrever chaincode de forma simples e segura CCKit do S7 Techlab e analisador estático reviver^CC.

Além disso, nossa equipe está desenvolvendo um conjunto de utilitários para tornar o trabalho com o Fabric simples e agradável: explorador de blockchain, um utilitário para alterações automáticas na configuração da rede (adicionar/remover organizações, nós RAFT), utilitário para revogação de certificados e remoção de identidade. Se você quiser contribuir, será bem-vindo.

Conclusão

Essa abordagem permite substituir facilmente o Hyperledger Fabric pelo Quorum, outras redes Ethereum privadas (PoA ou mesmo PoW), reduzir significativamente o rendimento real, mas ao mesmo tempo manter a UX normal (tanto para usuários no navegador quanto para sistemas integrados). Ao substituir Fabric por Ethereum no esquema, você só precisará alterar a lógica do serviço/decorador de nova tentativa do processamento de conflitos MVCC para incremento de nonce atômico e reenvio. O buffer e o armazenamento de status possibilitaram dissociar o tempo de resposta do tempo de formação do bloco. Agora você pode adicionar milhares de nós de pedidos e não ter medo de que os blocos sejam formados com muita frequência e carreguem o serviço de pedidos.

Basicamente, isso é tudo que eu queria compartilhar. Ficarei feliz se isso ajudar alguém em seu trabalho.

Fonte: habr.com

Adicionar um comentário