“Dedupeer: um Algoritmo para Deduplicação de Arquivos Através de Processamento Particionado” Por Paulo Fernando Almeida Soares Dissertação de Mestrado Universidade Federal de Pernambuco [email protected] www.cin.ufpe.br/~posgraduacao RECIFE, JULHO/2013 Universidade Federal de Pernambuco Centro de Informática Pós-graduação em Ciência da Computação Paulo Fernando Almeida Soares “Dedupeer: um Algoritmo para Deduplicação de Arquivos Através de Processamento Particionado” Trabalho apresentado ao Programa de Pós-graduação em Ciência da Computação do Centro de Informática da Universidade Federal de Pernambuco como requisito parcial para obtenção do grau de Mestre em Ciência da Computação. Orientador: Silvio Romero de Lemos Meira Co-Orientador: Vinicius Cardoso Garcia RECIFE, JULHO/2013 Catalogação na fonte Bibliotecária Jane Souto Maior, CRB4-571 Soares, Paulo Fernando Almeida Dedupeer: um algoritmo para deduplicação de arquivos através de processamento particionado. / Paulo Fernando Almeida Soares. - Recife: O Autor, 2013. xvi, 100 f., il., fig., tab. Orientador: Silvio Romero de Lemos Meira. Dissertação (mestrado) - Universidade Federal de Pernambuco. CIn, Ciência da Computação, 2013. Inclui referências e apêndice. 1. Sistemas distribuídos. 2. Deduplicação de dados. Silvio Romero de Lemos (orientador). II. Título. 004.36 CDD (23. ed.) I. Meira, MEI2014 – 007 Dissertação de Mestrado apresentada por Paulo Fernando Almeida Soares à Pós-Graduação em Ciência da Computação do Centro de Informática da Universidade Federal de Pernambuco, sob o título “Um Algoritmo para Deduplicação de Arquivos Através de Processamento Particionado” orientada pelo Prof. Silvio Romero de Lemos Meira e aprovada pela Banca Examinadora formada pelos professores: ______________________________________________ Prof. Vinicius Cardoso Garcia Centro de Informática / UFPE ______________________________________________ Prof. Manoel Gomes de Mendonça Neto Departamento de Ciência da Computação /UFBA _______________________________________________ Prof. Rodrigo Elia Assad Departamento de Estatística e Informática / UFRPE Visto e permitida a impressão. Recife, 28 de agosto de 2013. ___________________________________________________ Profa. Edna Natividade da Silva Barros Coordenadora da Pós-Graduação em Ciência da Computação do Centro de Informática da Universidade Federal de Pernambuco. Eu dedico esta dissertação à minha tia Joarina (in memorian) por todo o apoio e incentivo em meus estudos. iii Resumo A deduplicação é uma técnica de compressão de dados sem perda que elimina dados redundantes tanto intra-file como inter-file, diferente de ferramentas de compressão de dados como o gzip que só eliminam a redundância intra-file. A deduplicação reduz a necessidade de armazenamento através da eliminação de blocos de dados redundantes. Na deduplicação, todos os blocos de dados que estão duplicados em um sistema de armazenamento podem ser reduzidos à uma única cópia, esses blocos desalocados pela deduplicação são transformados em referência para o que foi mantido no sistema. Técnicas de deduplicação começaram a ser estudadas para sistemas de armazenamento comerciais em meados de 2004. Hoje, os principais sistemas de armazenamento de dados usam deduplicação, mas os algoritmos implementados e as técnicas utilizadas não são detalhadas publicamente. Existem alguns trabalhos acadêmicos focados na implementação de algoritmos de deduplicação, mas eles são raros e não são voltados para a sua utilização em sistemas de armazenamento existentes. O principal objetivo deste trabalho é criar um algoritmo para deduplicação de arquivos no cliente de forma remota, através de processamento particionado e utilizando comparação por fingerprints. Este algoritmo foi incorporado em um componente de software com interface interoperável para facilitar a utilização em qualquer sistema de armazenamento de dados e beneficiá-los com economia de armazenamento, e na transferência de dados no caso dos sistemas de armazenamento distribuídos. Além do componente de software, foi desenvolvido também um sistema de armazenamento com gerenciamento de dados baseado no Apache Cassandra, o que o torna capaz de ser distribuído, com o objetivo de validar o algoritmo de deduplicação. A integração do componente de software com o sistema de armazenamento foi implementada e avaliada neste trabalho. Palavras-chave: Deduplicação, compressão de dados, economia de armazenamento, sistemas de armazenamento de dados iv Abstract The deduplication is a technique for lossless data compression that eliminates redundant data intra-file and inter-file, different data compression tools like gzip only eliminates the redundancy intra-file. The deduplication reduces storage needs through eliminating redundant blocks. In the deduplication, all data blocks or files that are duplicates in a storage system are reduced to a solely copy, and the unallocated data are transformed in reference to the data content maintained in the system. Deduplication techniques began to be studied in mid-2004. Today, the main storage systems use deduplication, but the algorithms implemented and the techniques used are not detailed. There are researches focused in the implementation of algorithms, but they are rare and the main goal not is the integration with the existing systems. The main aim of this research is to create an algorithm to deduplication of files on the source with remote data, through of the partitioned processing and using of fingerprint comparisons. This algorithm was integrated in a software component with interoperable interface to facilitate the using in any storage system and benefit them with storage economy, and in the data transfer when the system was distributed. Besides the software component was developed also a storage system with data management made with the Apache Cassandra, which make it able to be distributed with the goal to validate the deduplication algorithm. The integration of the software component with the storage system was implemented and evaluated in this research. Keywords: Deduplication, data compression, storage economy, storage systems v Agradecimentos Primeiramente eu gostaria de agradecer ao meu orientador e co-orientador, Silvio Meira e Vinicius Garcia, respectivamente, por toda a dedicação para me ajudar a tornar possível a realização desse meu sonho. Minha família, em especial para minha mãe e minha tia Joarina por todo o apoio e incentivo que me deram nos meus estudos. Meus amigos da Usto.re, Rodrigo Assad, Anderson Fonseca, Francisco Soares, Marco Machado e Thiago Jamir por sempre estarem dispostos a me ajudar nos desafios técnicos que enfrentei. Todos os meus amigos do CIn, em especial para os mais presentes: Thiago Vieira, Lenin Ernesto, Marco Machado, Jamilson Batista, Adriano Tito, Rodolfo Arruda, Dhiego Abrantes, Francisco Airton, João Emanoel, Bruno Felipe e Kellyton Brito. Eduardo Almeida pelo grande apoio, ajuda e conselhos. Tassio Vale pelas conversas sobre o Mestrado e por me mostrar o quão bom seria estudar na UFPE. Rogério por me receber em seu apartamento nos meus primeiros meses no Recife. Frederico Durão pela ajuda na estruturação da dissertação. Minhas amigas forrozeiras, Natália, Dalyla, Ione, Anne e Ana Tereza, as pessoas mais animadas de todo o Pernambuco. E a todos os que contribuiram direta e indiretamente para que este trabalho fosse realizado! Muito obrigado! vi Lista de Figuras 1.1 2.1 2.2 2.3 Aumento da demanda e redução do custo de hardware de armazenamento (gráfico baseado em Kaplan et al. [21]) . . . . . . . . . . . . . . . . . . 1 Armazenamento dos super-chunks nos nós [46]. . . . . . . . . . . . . . Processo do algoritmo do FERAPARDA . . . . . . . . . . . . . . . . . Exemplo da economia de espaço oferecida em um sistema de armazenamento com 20% de alteração nos dados. . . . . . . . . . . . . . . . . . 11 15 Diagrama de componentes de alto nível da biblioteca do Dedupeer . . . Modelo de dados do sistema distribuído desenvolvido com o Cassandra Visão de componentes e conectores do DeFS integrado com a biblioteca do Dedupeer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 28 4.1 4.2 Desempenho na serialização dos dados [39] . . . . . . . . . . . . . . . Desempenho na desserialização dos dados [39] . . . . . . . . . . . . . 42 43 5.1 5.2 5.3 Funcionamento do algoritmo 1. . . . . . . . . . . . . . . . . . . . . . . Funcionamento do algoritmo 2 . . . . . . . . . . . . . . . . . . . . . . Vantagem da utilização da carga extra de dados . . . . . . . . . . . . . 51 55 59 6.1 Tempo em milissegundos para as execuções da operação de armazenamento utilizando MD5 . . . . . . . . . . . . . . . . . . . . . . . . . . Tempo em milissegundos para as execuções da operação de armazenamento utilizando SHA-1 . . . . . . . . . . . . . . . . . . . . . . . . . Tempo em milissegundos para as execuções da operação de armazenamento, separadas por tamanho de chunk . . . . . . . . . . . . . . . . . Tempo em milissegundos das execuções da operação de deduplicação, separadas por tamanho de chunk, utilizando SHA-1 . . . . . . . . . . . Tempo em milissegundos das execuções da operação de deduplicação, separadas por tamanho de chunk, utilizando MD5 . . . . . . . . . . . . Tempo em milissegundos das execuções da operação de deduplicação + armazenamento, separadas por tamanho de chunk, utilizando SHA-1 . . Tempo em milissegundos das execuções da operação de deduplicação + armazenamento, separadas por tamanho de chunk, utilizando MD5 . . . 3.1 3.2 3.3 6.2 6.3 6.4 6.5 6.6 6.7 20 30 64 64 65 67 67 69 70 vii LISTA DE FIGURAS 6.8 Tempo em milissegundos das execuções da operação de reidratação, separadas por tamanho de chunk, utilizando SHA-1 . . . . . . . . . . . 6.9 Tempo em milissegundos das execuções da operação de reidratação, separadas por tamanho de chunk, utilizando MD5 . . . . . . . . . . . . 6.10 Mapa de chunks deduplicados da versão 3.10-rc7 do código-fonte do Kernel do Linux processado com os chunks da versão 3.9.8, para chunks com tamanho 128, 64, 32, 16 e 8KB . . . . . . . . . . . . . . . . . . . 6.11 Tamanho em megabytes ocupado pelo arquivo no sistema, com a porcentagem de economia alcançada . . . . . . . . . . . . . . . . . . . . . . 6.12 Mapa de chunks deduplicados da máquina virtual Linux 12.10 atualizada processada com os chunks da mesma máquina virtual sem atualização, para chunks com tamanho 128, 64, 32, 16 e 8KB . . . . . . . . . . . . 71 71 72 74 74 C.1 Tempo emparelhado para a operação de armazenamento com chunks de 128KB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C.2 Tempo emparelhado para a operação de armazenamento com chunks de 64KB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C.3 Tempo emparelhado para a operação de armazenamento com chunks de 32KB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C.4 Tempo emparelhado para a operação de armazenamento com chunks de 16KB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C.5 Tempo emparelhado para a operação de armazenamento com chunks de 8KB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C.6 Tempo emparelhado para a operação de deduplicação com chunks de 128KB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C.7 Tempo emparelhado para a operação de deduplicação com chunks de 64KB C.8 Tempo emparelhado para a operação de deduplicação com chunks de 32KB C.9 Tempo emparelhado para a operação de deduplicação com chunks de 16KB C.10 Tempo emparelhado para a operação de deduplicação com chunks de 8KB C.11 Tempo emparelhado para a operação de deduplicação + armazenamento com chunks de 128KB . . . . . . . . . . . . . . . . . . . . . . . . . . C.12 Tempo emparelhado para a operação de deduplicação + armazenamento com chunks de 64KB . . . . . . . . . . . . . . . . . . . . . . . . . . . C.13 Tempo emparelhado para a operação de deduplicação + armazenamento com chunks de 32KB . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 91 91 92 92 93 93 94 94 95 95 96 96 viii LISTA DE FIGURAS C.14 Tempo emparelhado para a operação de deduplicação + armazenamento com chunks de 16KB . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 C.15 Tempo emparelhado para a operação de deduplicação + armazenamento com chunks de 8KB . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 C.16 Tempo emparelhado para a operação de reidratação com chunks de 128KB 98 C.17 Tempo emparelhado para a operação de reidratação com chunks de 64KB 98 C.18 Tempo emparelhado para a operação de reidratação com chunks de 32KB 99 C.19 Tempo emparelhado para a operação de reidratação com chunks de 16KB 99 C.20 Tempo emparelhado para a operação de reidratação com chunks de 8KB 100 ix Lista de Tabelas 3.1 Força do consistência baseada nos níveis das operações [7] . . . . . . . 34 6.1 6.2 p-values para os dados capturados na operação de armazenamento . . . Quantidade de chunks criados . . . . . . . . . . . . . . . . . . . . . . 66 73 x Lista de Acrônimos CPU Central Processing Unit CTO Chief Technology Officer DDR2 Double Data Rate DE Delta Encoding DeFS Dedupeer File Storage DHT Distributed Hash Table EPA Environmental Protection Agency FBH Fixed Block Hashing FIFO First In First Out GSI Green Storage Initiative GUI Graphical User Interface HDFS Hadoop Distributed File System I/O Input/Output JXTA Juxtapose MD5 Message-Digest Algorithm 5 NoSQL Not only SQL P2P Peer-to-peer RAM Random-Access Memory RPC Remote Procedure Call SAN Storage Area Network SHA-1 Secure Hash Algorithm SQL Structured Query Language xi LISTA DE TABELAS SSD Solid State Drive TI Tecnologia da Informação VBH Variable Block Hashing VDI Virtual Disk Image xii Sumário Lista de Acrônimos xi 1 . . . . . . 1 3 4 4 5 6 6 . . . . . . . . . . . . . . . . . . 8 8 9 9 10 12 13 13 14 14 16 17 17 17 18 18 19 20 21 O Dedupeer File Storage 3.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.1 Escopo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 O sistema de armazenamento distribuído . . . . . . . . . . . . . . . . . 22 22 22 23 2 3 Introdução 1.1 Motivação . . . . . . . 1.2 Definição do problema 1.3 Resultados esperados . 1.4 Escopo negativo . . . . 1.5 Contribuições . . . . . 1.6 Sumário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Trabalhos relacionados 2.1 Sistemas de armazenamento de dados com foco em deduplicação 2.1.1 Trabalho de Kathpal et al. . . . . . . . . . . . . . . . . 2.1.2 Trabalho de Kaiser et al. . . . . . . . . . . . . . . . . . 2.1.3 Σ-Dedup . . . . . . . . . . . . . . . . . . . . . . . . . 2.1.4 Dropbox . . . . . . . . . . . . . . . . . . . . . . . . . 2.1.5 Conclusão sobre os sistemas com foco em deduplicação 2.2 Algoritmos para eliminação de redundância de dados . . . . . . 2.2.1 FERAPARDA . . . . . . . . . . . . . . . . . . . . . . 2.2.2 DYNABYTE . . . . . . . . . . . . . . . . . . . . . . . 2.2.3 Trabalho de Langille et al. . . . . . . . . . . . . . . . . 2.3 Deduplicação . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3.1 Localização . . . . . . . . . . . . . . . . . . . . . . . . Cliente (source) . . . . . . . . . . . . . . . . . . . . . . Servidor (target) . . . . . . . . . . . . . . . . . . . . . 2.3.2 Momento . . . . . . . . . . . . . . . . . . . . . . . . . 2.3.3 Algoritmo . . . . . . . . . . . . . . . . . . . . . . . . . 2.3.4 Benefícios . . . . . . . . . . . . . . . . . . . . . . . . 2.4 Sumário do capítulo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii LISTA DE TABELAS . . . . . . . . . . . . . . . . . . . 23 29 29 30 31 31 31 33 33 34 34 35 35 35 35 36 36 37 37 4 Componente para deduplicação em sistemas de armazenamento de dados 4.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 Interoperabilidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2.1 Interoperabilidade de comunicação através do Thrift . . . . . . 4.2.2 Comparação com o Protocols Buffer . . . . . . . . . . . . . . . Protocols Buffer . . . . . . . . . . . . . . . . . . . . . . . . . Comparação de desempenho . . . . . . . . . . . . . . . . . . . 4.2.3 Comparação por hash (fingerprints) . . . . . . . . . . . . . . . Vantagem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3 Sumário do capítulo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 38 39 39 41 41 42 42 44 45 5 Os algoritmos 5.1 Fundamentos . . . . . . . 5.1.1 O algoritmo Rsync Rolling checksum 5.1.2 Fingerprinting . . 46 46 46 47 48 3.3 3.4 3.5 3.2.1 Modelo de dados . . . . . . . . . . . . . . . . . . . Visão de componentes e conectores . . . . . . . . . . . . . O componente Dedupeer File Storage . . . . . . . . O componente Dedupeer . . . . . . . . . . . . . . . Decisões de projeto e considerações . . . . . . . . . . . . . 3.4.1 Tamanho do chunk . . . . . . . . . . . . . . . . . . 3.4.2 Cassandra para gerenciamento do armazenamento . Tolerância à falhas . . . . . . . . . . . . . . . . . . Recuperação de falhas . . . . . . . . . . . . . . . . Consistência . . . . . . . . . . . . . . . . . . . . . Disponibilidade . . . . . . . . . . . . . . . . . . . . Empacotamento de requisições . . . . . . . . . . . . 3.4.3 Alternativa para o gerenciamento de armazenamento Facilidade de instalação e manutenção . . . . . . . . Clusters pequenos . . . . . . . . . . . . . . . . . . 3.4.4 Escalabilidade . . . . . . . . . . . . . . . . . . . . O que é escalabilidade? . . . . . . . . . . . . . . . . Escalabilidade com o Cassandra . . . . . . . . . . . Sumário do capítulo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiv LISTA DE TABELAS 5.2 5.3 6 7 Detalhando os algoritmos . . . . . . . . . . . . . . . . . . . . . . . . . 5.2.1 Algoritmo para deduplicação com carga completa do arquivo na memória . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2.2 Algoritmo com deduplicação particionada . . . . . . . . . . . . Sumário do capítulo . . . . . . . . . . . . . . . . . . . . . . . . . . . . Análise de desempenho 6.1 Datasets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Análise de desempenho utilizando o código fonte do Kernel do Linux Operação de armazenamento . . . . . . . . . . . . . . . . . . Operação de deduplicação . . . . . . . . . . . . . . . . . . . Operação de deduplicação + armazenamento . . . . . . . . . Operação de reidratação . . . . . . . . . . . . . . . . . . . . 6.2.1 Mapa de chunks deduplicados . . . . . . . . . . . . . . . . . 6.3 Análise de economia utilizando máquinas virtuais . . . . . . . . . . . 6.4 O Ustore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.5 Sumário do capítulo . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 49 54 60 . . . . . . . . . . 61 61 63 63 66 68 70 72 73 75 75 Conclusão 7.1 Trabalhos futuros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 78 Referências Bibliográficas 80 A Apêndice A.1 Arquivo de definição dos serviços Thrift . . . . . . . . . . . . . . . . . 85 85 B Apêndice 87 B.1 Resultados da avaliação dos arquivos do código fonte do Kernel do Linux 87 C Apêndice C.1 Gráficos dos tempos emparelhados por tamanho de chunk para a operação de armazenamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . C.2 Gráficos dos tempos emparelhados por tamanho de chunk para a operação de deduplicação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C.3 Gráficos dos tempos emparelhados por tamanho de chunk para a operação de deduplicação + armazenamento . . . . . . . . . . . . . . . . . . . . 90 90 93 95 xv LISTA DE TABELAS C.4 Gráficos dos tempos emparelhados por tamanho de chunk para a operação de reidratação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Appendices 98 85 xvi 1 Introdução Com o aumento da capacidade das unidades de armazenamento, aumentou também a demanda por sua utilização. Em 2000, a estimativa da indústria era de um aumento anual entre 50% a 100% dessa demanda [26], estimativa essa que se consolidou. Nos últimos anos esse aumento atingiu um valor anual de mais de 50% [21]. A Figura 1.1 mostra dois gráficos com informações sobre demanda e preço do armazenamento entre os anos de 2006 e 2011. Um deles demonstra o forte crescimento da demanda por armazenamento nas empresas, e o segundo, exibe a queda de 20% ao ano no preço por Gigabytes. Figura 1.1 Aumento da demanda e redução do custo de hardware de armazenamento (gráfico baseado em Kaplan et al. [21]) A indústria de TI passou a apostar em sistemas de armazenamento de dados distribuído 1 nos últimos anos. Empresas como Amazon1 , Google2 , Microsoft3 e Salesforce4 começaram a investir em centros de armazenamentos de dados na nuvem (Cloud Storages), espalhados pelo mundo, com garantia de redundância e tolerância à falhas [47]. Com esse aumento da demanda por armazenamento, a utilização de técnicas de compressão de dados se torna um fator chave para redução dos custos. A compressão de dados é uma técnica que codifica informações, usando uma quantidade menor de dados que a original, para economizar espaço de armazenamento. As técnicas mais conhecidas de compressão de dados funcionam apenas com a compressão intra-file, ou seja, utilizando no processamento apenas os bytes do arquivo a ser comprimido, como exemplo, pode ser citado o formato de arquivo zip [32], que é um tipo de compressão sem perda. E também existe a compressão intra-file com perda de dados, que é o caso dos famosos formatos de arquivos jpg e mp3, o qual reduz a qualidade da imagem e do áudio, respectivamente, para obter uma redução no espaço necessário para armazenar o arquivo [35]. A deduplicação é um técnica de compressão sem perda que elimina dados redundantes tanto intra-file quanto inter-file, o que possibilita utilizar dados remotos para ajudar na compressão dos dados. Com a popularização dos sistemas de armazenamento distribuído em nuvem, a deduplicação ajudará muito na economia de espaço de armazenamento, já que ela pode utilizar informações em diferentes arquivo e entidades para economizar o espaço utilizado no sistema. A deduplicação de dados é um técnica de compressão que armazena uma única cópia de dados redundantes, eliminando as outras do sistema de armazenamento. Ela pode ser feita em nível de arquivo completo ou de chunks, os quais são blocos de dados pertencentes ao arquivo. Muitos sistemas de backup atuais escolhem fazer o armazenamento dos arquivos em fitas magnéticas por serem mais baratas que os discos rígidos. A fita magnética tem a desvantagem de ter o acesso apenas sequencial dos dados, enquanto que no disco o acesso pode ser randômico. Com a utilização das fitas, a deduplicação dos dados se torna eficientemente inviável pela falta de capacidade em acesso randômico. Por esse fator, a deduplicação está tornando um diferencial na escolha dos discos rígidos como dispositivo de armazenamento dos backups, a vantagem do preço das fitas em relação aos discos é contornada pelo fato de que com os discos rígidos a deduplicação pode ser feita e como consequência o espaço de armazenamento é economizado [20]. 1 http://aws.amazon.com/pt/s3/ 2 http://www.google.com/enterprise/cloud/storage/ 3 http://www.windowsazure.com 4 http://www.salesforce.com 2 1.1. MOTIVAÇÃO A utilização de deduplicação de dados em sistemas de armazenamento em nuvem dá uma grande vantagem à empresa que fornece o serviço. Com a utilização da deduplicação, a empresa poderá vender para os cliente mais espaço de armazenamento do que ela poderia fornecer em um sistema sem deduplicação, já que a deduplicação diminui a quantidade de bytes armazenados por arquivos no sistema, principalmente quando o mesmo arquivo em diferentes versões é salvo ou quando o mesmo arquivo é enviado para o sistema, mesmo que por usuários distintos. Uma forma de aumentar os blocos de dados redundantes em sistemas de armazenamento que possua arquivos com partes idênticas, é através da redivisão dos chunks baseado no conteúdo, e a partir disso, aumentar a quantidade de blocos de dados idênticos. Para que esse procedimento seja executado é preciso uma comparação byte à byte entre os arquivos, tarefa essa que demanda muito processamento e utilização de E/S (entrada e saída). Esse processo torna-se mais viável com a distribuição do processamento dos dados para a redefinição dos novos pontos de cortes dos chunks e utilização de algoritmos de hashing para identificar a redundância de dados. 1.1 Motivação Os principais sistemas distribuídos de deduplicação de dados são feitos para serem executados em clusters [4] [10] [20] [48] ou em nós simples, alguns deles usam framework para processamento distribuído de dados [22], como Hadoop5 por exemplo, mas é difícil encontrar trabalhos de pesquisa envolvendo sistemas de deduplicação para ambientes de armazenamento de dados construídos com arquitetura peer-to-peer sobre computadores pessoais para armazenar esses dados. No desenvolvimento do Usto.re6 , um sistema de armazenamento de dados distribuído que usa o espaço ocioso de computadores em rede para armazenamento de dados e que será utilizado para validação deste projeto em ambiente comercial, foi demandada a adoção de deduplicação de dados para reduzir o espaço de armazenamento para versões diferentes de um mesmo arquivo. Um pesquisa foi executada para identificar bibliotecas de código-fonte aberto com licença que não restrigisse o uso comercial para utilização da mesma e nenhuma foi encontrada. Baseado nos fatos supracitados, este trabalho propõe o desenvolvimento de um componente de software para deduplicação de dados que pode ser integrado com qualquer 5 "http://hadoop.apache.org/" 6 "http://usto.re" 3 1.2. DEFINIÇÃO DO PROBLEMA sistema de armazenamento de dados que tenha sido implementado em uma das linguagens suportadas pelo Thrift7 , que é uma biblioteca para serialização de dados que suporta comunicação entre diferentes linguagens de programação. O componente será integrado a um sistema comercial de armazenamento de dados que foi construído em uma arquitetura peer-to-peer. A deduplicação traz vários benefícios para o sistema que a utiliza, entre eles: • Redução no espaço necessário para armazenar arquivos. Isso ajuda um sistema de armazenamento ser considerado dentro dos padrões da green storage, o qual será melhor detalhado no Capítulo 4. Com a redução do espaço necessário para armazenar os arquivos, um sistema de armazenamento pode ser vendido com mais espaço do que ele possui fisicamente. • Redução da quantidade de bytes trafegados, para o caso de sistemas distribuído, pois com a deduplicação feita na fonte os dados redundantes não são enviados. • Maior facilidade no gerenciamento de replicação de chunks, já que a deduplicação reduz a quantidade deles. 1.2 Definição do problema Algoritmos detalhados para deduplicação utilizando processamento de forma particionada são escassos. Existe também uma falta de componentes de softwares interoperáveis para deduplicação que possam ser integrados aos sistemas de armazenamento de dados existentes. 1.3 Resultados esperados Esta pesquisa tem como principal objetivo desenvolver um algoritmo para deduplicação de dados, que faça processamento particionado e que seja interoperável e de fácil integração com os sistemas existentes. Este algoritmo deverá ser disponibilizado como um componente de software, com uma interface de comunicação multilinguagem, para executar a deduplicação exata dos dados em sistemas de armazenamento. Ele deverá ser integrado ao sistema de armazenamento que será desenvolvido como parte da pesquisa e com um sistema distribuído de armazenamento de dados comercial. 7 "http://thrift.apache.org/" 4 1.4. ESCOPO NEGATIVO O algoritmo a ser desenvolvido nesta pesquisa deve resolver os três benefícios citados na Motivação proporcionados pela deduplicação e com o mínimo de alteração no sistema de armazenamento para integrá-lo, isso porque o algoritmo será feito tendo a interoperabilidade e modificabilidade mínima para integração como alguns dos principais requisitos. O algoritmo deve ser disponibilizado através de uma biblioteca com uma interface Thrift, que vai receber os valores de hash dos chunks e o arquivo a ser deduplicado, e retornará o arquivo deduplicado como um mapa dos chunks e das referências aos chunks redundantes. O algoritmo deve fazer a busca por redundância baseada em conteúdo, independente da quantidade de bytes alterados, removidos ou adicionado entre duas versões de um arquivo ou entre arquivos distintos. O algoritmo será capaz de identificar as redundâncias se a sequência de bytes não modificados for no mínimo do tamanho padrão do chunk. 1.4 Escopo negativo Deduplicação através de chunks maiores que 128KB está fora do escopo da pesquisa, pelo fato da detecção de redundância diminuir com o aumento do tamanho do chunk e este ser o tamanho padrão do Ustore, o sistema comercial de backup de dados onde o Dedupeer será implantado. Também foi definido como limite mínimo de tamanho de chunk 8KB. Apesar de conseguir um maior ganho de redução de espaço com chunks menores, o tempo para processamento e a quantidade de chunks gerados começa a degradar muito o desempenho do sistema. O Dedupeer File Storage será desenvolvido utilizando o Cassandra8 , que é um banco de dados distribuído que utiliza arquitetura P2P, mas está fora de escopo o teste do sistema de forma distribuída. Todos os testes serão feitos localmente, já que serão suficientes para avaliar o algoritmo de deduplicação. Os testes do algoritmo funcionando com deduplicação no alvo, tanto post-processing, que é a deduplicação feita depois que os dados são armazenados, como inline, que é a deduplicação onde os dados são processados no momento em que chegam no alvo e antes de serem armazenados, estão fora do escopo do trabalho. Estes tipos de deduplicação serão descritos na Seção 2.3.2. Por serem os algoritmo de hashing mais comuns em processamentos de deduplicação de dados, os testes só serão feitos com MD5 e SHA-1. 8 http://cassandra.apache.org/ 5 1.5. CONTRIBUIÇÕES 1.5 Contribuições As principais contribuições desta pesquisa serão: • O algoritmo para deduplicação de dados com processamento particionado encapsulado em um componente de software interoperável. Essa forma de processamento torna possível fazer a deduplicação de arquivos muito maiores do que a memória principal, já que a configuração do tamanho da carga dos bytes para a memória, independente do tamanho do arquivo, será configurável no algoritmo. • O sistema de armazenamento de dados distribuído, com código-fonte aberto, administrável através de interface gráfica e com gerenciamento de armazenamento delegado para um banco de dados não-relacional construído em uma arquitetura peer-to-peer com topologia de anel. • A melhoria da descoberta de redundância de dados através da carga extra de dados no processamento particionado, possibilitando a diminuição da perda de identificação por causa da quebra do arquivo. 1.6 Sumário Neste capítulo foi apresentada uma introdução sobre o crescimento da demanda por sistemas de armazenamento de dados e a importância da deduplicação para a economia de espaço nos tempos onde os sistemas de cloud storage estão sendo cada vez mais utilizados. Este trabalho está dividido em sete Capítulos e um Apêndice. No Capítulo 2 serão descritos como funcionam alguns sistemas de armazenamento de dados que utilizam deduplicação. Em seguida será feita uma introdução sobre quais são os tipos de deduplicação existentes e quais os benefícios que ela pode trazer. No Capítulo 3 será detalhada a arquitetura do Dedupeer File Storage, que é o sistema de armazenamento de arquivos desenvolvido neste trabalho, e serão discutidas algumas decisões de projeto relacionadas à construção desse sistema. No Capítulo 4 será detalhado o funcionamento do componente de software que disponibilizará o algoritmo de deduplicação como um serviço. Será discutida a interoperabilidade do componente e quais poderiam ser as alternativas à biblioteca que foi escolhida, também será discutida a decisão de utilização da comparação por hash e quais são as suas vantagens e desvantagens. 6 1.6. SUMÁRIO O Capítulo 5 detalhará os dois algoritmos de deduplicação desenvolvidos na pesquisa, tanto o algoritmo mais simples como o algoritmo de processamento particionado, que é a principal contribuição deste trabalho. Serão apresentados os fundamentos do algoritmo desenvolvido para identificação de redundância entre arquivos remotos, os quais serviram de base para os algoritmos desenvolvidos. Serão detalhados também os pseudocódigos de alto nível dos dois algoritmos criados. O Capítulo 6 apresentará os testes de desempenho e compressão executados para demonstrar a eficiência do algoritmo de deduplicação e o sistema de armazenamento de dados desenvolvidos. E o Capítulo 7 apresentará as conclusões sobre o trabalho e algumas propsotas para trabalhos futuros. 7 2 Trabalhos relacionados Neste capítulo, os conceitos e técnicas fundamentais sobre os assuntos relevantes para o entendimento dessa pesquisa serão discutidos. Serão discutidos os principais trabalhos relacionados à sistemas de armazenamento de dados com foco em deduplicação, os quais serão detalhados afim de identificar conceitos e técnicas que aprimorem o desenvolvimento do projeto Dedupeer, o qual é dividido em dois, o Dedupeer File Storage e o componente de software que implementa o algoritmo de deduplicação chamado de Dedupeer. Primeiramente vai ser feito um estudo com alguns dos sistemas de armazenamento de dados que utilizam deduplicação para identificar as técnicas e os conceitos, e com isso, analisar quais as que melhor servirão de base para o desenvolvimento do Dedupeer File Storage. A análise feita nesses sistemas servirá para que uma melhor estratégia para o processamento da deduplicação seja implementada no Dedupeer, com base nos princípios desejados para a execução do mesmo. É pequeno o número de sistemas de armazenamento que declaram a utilização de deduplicação, e ainda menor os que detalham esse processo. Será apresentado o conceito de deduplicação de dados e quais são os seus tipos. 2.1 Sistemas de armazenamento de dados com foco em deduplicação Nesta seção serão descritos alguns dos sistemas pesquisados que têm como objetivo armazenamento de dados e que têm como um dos focos principais a deduplicação. Os trabalhos acadêmicos serão apresentados por ordem de ano de publicação. 8 2.1. SISTEMAS DE ARMAZENAMENTO DE DADOS COM FOCO EM DEDUPLICAÇÃO 2.1.1 Trabalho de Kathpal et al. Devido ao desafio cada vez mais comum em armazenar e gerenciar o crescente volume de dados gerados nos dias atuais, conhecido como Big Data[19], é importante pensar em arquiteturas escaláveis para que os sistemas consigam dar suporte a essa grande quantidade de dados. Uma proposta de um sistema escalável para dar suporte à deduplicação para grande volume de dados foi descrita em [22]. Diferente das pesquisas mais comuns para escalabilidade de sistemas de deduplicação, que geralmente têm o foco na otimização dos algoritmos de deduplicação e em formas de deduplicação de mais alto nível, como arquivos e objetos. [22] tem o trabalho direcionado para o projeto arquitetural do sistema e usa deduplicação em nível de bloco, que apesar de ter vantagens de velocidade, em contrapartida tem problemas no gerenciamento da enorme quantidade de blocos a serem processados. O projeto descrito em [22] foi proposto para funcionar de forma otimizada em ambientes de cluster, o qual é um conjunto de computadores interligados através de um sistema distribuído com uma finalidade em comum. Suas rotinas de deduplicação offline de dados são executadas nestes cluster externos, isso é feito para que o processamento da deduplicação não dispute os recursos com as outras rotinas do sistema. Para a execução das tarefas foi usado o Apache Hadoop, que é um framework para computação distribuída baseado em Map/Reduce, o qual ajudou a aumentar a capacidade de escalabilidade do projeto. Todo o processo para deduplicação dos dados é feito após o armazenamento no sistema, o que tem como desvantagem a necessidade do dado ser todo transferido para o destino, para que depois seja analisado para eliminação de blocos duplicados. Com isso, não existe a economia de banda que é obtida com a deduplicação feita no cliente. 2.1.2 Trabalho de Kaiser et al. Em Kaiser et al.[20], é explicado sobre o funcionamento de um sistema que executa em cluster e utiliza um tipo de deduplicação que eles chamam de "exact deduplication", que recebe esse nome pelo fato do sistema conseguir detectar todos os chunks duplicados. Os sistemas com exact deduplication geralmente possuem chunks com tamanho entre 4KB e 16 KB. Sistemas que utilizam chunks grandes perdem muitas vezes a detecção da redundância, já a utilização de chunks muito pequenos, apesar de aumentar a chance de deduplicação, torna o sistema menos eficiente por causa da alta sobrecarga ocasionada pela alta quantidade deles. 9 2.1. SISTEMAS DE ARMAZENAMENTO DE DADOS COM FOCO EM DEDUPLICAÇÃO O HYDRAstor, descrito em [10], utiliza uma granularidade alta nos chunks (64KB) para a deduplicação. Ele não faz o compatilhamento dos dados entre os nós e distribui os chunks na rede através de uma DHT (Distributed Hashtable). A escolha da granularidade alta dos chunks é para aumentar o desempenho da deduplicação através da redução do envio de metadados. O sistema em [20] foi projetado para trafegar a menor quantidade de dados possível na deduplicação para aumentar a escalabilidade do mesmo. Basicamente o que é trocado entre as máquinas do cluster são fingerprints. O sistema descrito foi baseado no dedupv1 [28], o qual faz deduplicação em discos de estado sólido (SSDs) para evitar gargalos de leitura e escrita de dados. Ele é dividido nos seguintes componentes: deduplication nodes, shared storage, interconnect between deduplication nodes, clients and distributed coordination. Os deduplication nodes são responsáveis por grande parte das funções mais importantes para a deduplicação e gerenciamento dos dados no sistema, neles são feitas as quebras dos dados em chunks e em seguida o cálculo do fingerprint de cada um deles. Eles também retornam os pedidos de chunks para outros nós e são responsáveis pela escrita deles no container de armazenamento. Cada deduplication node tem acesso às várias partições em que os discos são divididos pela rede através de storage area network (SAN) [42], as quais são redes de alta velocidade para acesso à dados. Cada partição só é acessada por um único nó, caso esse nó falhe, a tarefa de gerenciamento da partição é passada para outro nó, essa é a responsabilidade do componente do sistema que é chamado de shared storage. A distributed coordination é delegada para o Zookeeper 1 , um software de código aberto que integra o projeto Apache 2 e o qual é o responsável pela escolha dos nós mestres e da decisão de qual deduplication node assumirá o controle de determinada partição. Cada delegação de controle das partições para os nós são armazenadas no Zookeeper e gerenciada por um nó mestre, no caso de uma falha ocorrer nesse mestre, o Zookeeper elegerá outro nó para tomar o lugar do que falhou. 2.1.3 Σ-Dedup O Σ-Dedup, que é descrito em [13], é um middleware de deduplicação de dados para data centers e cloud storages. O Σ-Dedup foi criado para otimizar a deduplicação de dados em clusters. 1 http://zookeeper.apache.org/ 2 http://apache.org 10 2.1. SISTEMAS DE ARMAZENAMENTO DE DADOS COM FOCO EM DEDUPLICAÇÃO O Σ-Dedup utiliza um conceito chamado de super-chunk, que é um agrupamento de chunks consecutivos. Com os super-chunks são calculados um handprint que será enviado para um conjunto de nós, que já possui um índice de similaridade de todos os handprints dos super-chunks armazenados nele para aumentar a eficiência e diminuir a consulta ao disco. Os handprints dos super-chunks semelhantes são enviados para um mesmo nó para aumentar a probabilidade de encontrar chunks idênticos e com isso a eficiência da deduplicação. Com o handprint, os nós efetuam um cálculo para determinar a semelhança entre ele e os demais que estão armazenados no nó. Quando for encontrado o nó que possui um super-chunk mais semelhante ao que vai ser enviado, todos os fingerprints dos chunks pertencentes ao super-chunk serão enviados para o nó selecionado. Com esses fingerprints o nó verificará quais deles já estão armazenados e quais são novos. Depois dessa identificação, o nó pede ao cliente que está fazendo o backup apenas os chunks que não foram encontrados. Na Figura 2.1 os super-chunks do arquivo são identificados pelas letras. Eles são enviados para nós diferentes, já que a escolha do nó onde cada um deles será enviado no momento do backup depende da similaridade com os que estão armazenados nos nós. A deduplicação no Σ-Dedup é feita à nível de nó, logo, podem existir chunks duplicados se eles estiverem armazenados em diferentes nós. Figura 2.1 Armazenamento dos super-chunks nos nós [46]. A arquitetura do Σ-Dedup possui 3 componentes principais: o Backup Client, o Deduplication Server Cluster e o Director. O Backup Client tem como principal função fazer o backup e o restore dos arquivos. Ele é composto de três módulos, um deles é Data Partitioning que é o responsável pelo particionamento do arquivo e agrupamento dos chunks em super-chunks. O cálculo dos 11 2.1. SISTEMAS DE ARMAZENAMENTO DE DADOS COM FOCO EM DEDUPLICAÇÃO fingerprints é feito no módulo Chunk Fingerprinting que é uma camada abaixo do Data Partitioning. O terceiro e último módulo do Backup Client é o Similarity-aware Data Routing, é nesse módulo que é definido o nó de deduplicacação para o qual os dados serão enviados. Por ser um sistema de deduplicação inline, o qual será descrito na seção 2.3.2, os dados são avaliados antes do envio para os nós e só são transferidos os chunks que ainda não foram armazenados no sistema. Com isso, é economizada a banda de transferência da rede. O Deduplication Server Cluster também possui três módulos: Similarity Index Lookup, Chunk Fingerprinting Cache e Parallel Container Management. Eles são responsáveis respectivamente por retornar o índice de similaridade para o roteamento dos dados, pelo armazenamento dos fingerprints mais utilizados em um cache para facilitar a busca por chunks idênticos, e o último é o responsável pelo armazenamento de forma paralela dos chunks únicos nos containers. Um outro trabalho similar ao Σ-Dedup, onde os dados são enviados para determinados nós baseados na similaridade, é o Extreme Binning [4]. 2.1.4 Dropbox Apesar de não haver nenhuma declaração explícita na homepage do Dropbox 3 , segundo um artigo publicado no site Slight Paranoia, o mais popular sistema de armazenamento de dados em cloud [3] utiliza deduplicação de dados entre arquivos de usuários distintos [40]. Se dois usuários enviarem o mesmo arquivo para o sistema, o Dropbox só armazenará um deles nos seus servidores. Segundo o mesmo artigo, o CTO da empresa declarou que o Dropbox utiliza deduplicação no Forum oficial da empresa, respondendo à uma pergunta de um usuário que questionava o porquê de um arquivo de 750MB ser enviado tão rápido. Esse tópico não está mais hospedado no forum, mas a resposta do CTO para a pergunta "Woah! How did that 750MB file upload so quickly?"está no Slight Paranoia: Dropbox tries to be very smart about minimizing the amount of bandwidth used. If we detect that a file you’re trying to upload has already been uploaded to Dropbox, we don’t make you upload it again. Similarly, if you make a change to a file that’s already on Dropbox, you’ll only have to upload the pieces of the file that changed. This works across all data on Dropbox, not 3 "http://dropbox.com" 12 2.2. ALGORITMOS PARA ELIMINAÇÃO DE REDUNDÂNCIA DE DADOS just your own account. There are no security implications [emphasis added] - your data is still kept logically separated and not affected by changes that other users make to their data." Com a explicação do CTO, fica claro que o Dropbox utiliza deduplicação de dados, mas o processo de deduplicação usado no Dropbox não é detalhado pela empresa. 2.1.5 Conclusão sobre os sistemas com foco em deduplicação Todos os sistemas de armazenamento estudados utilizam armazenamento de dados baseado em chunks ou blocos de dados. Pelo fato de todos eles serem sistemas distribuídos para armazenamento de dados, precisam se preocupar com os problemas decorrentes desse tipo de sistema descritos no trabalho sobre o Dynamo [8], como: robustez e escalabilidade no balanceamento de carga, detecção de filiação e de falhas, entre outros, os quais serão melhor descritos na seção 3.4.2. Como esse não é o foco principal do trabalho, para a resolução desses problemas foi adotado para o Dedupeer File Storage o banco de dados Cassandra, que funciona em uma rede P2P e já trata todos eles. Com essa escolha será possível dedicar um maior tempo no que realmente interessa no estudo, que é o desenvolvimento do algoritmo de deduplicação. Os sistemas comerciais, como o Dropbox, não tem seus algoritmos ou processo de deduplicação detalhados publicamente, e os trabalhos acadêmicos não possuem seus algoritmos ou sistemas de deduplicação facilmente acessíveis para utilização em um sistema de armazenamento. Esses foram os principais fatos que despertaram o interesse em desenvolver um algoritmo, com um processo diferente dos descritos, de códigoaberto e interoperável entre linguagens para ser facilmente integrado aos sistemas de armazenamento existente. 2.2 Algoritmos para eliminação de redundância de dados Nesta seção será descrito alguns dos trabalhos relacionados ao desenvolvimento de algoritmos para eliminação de redundância de dados. Apesar de não estarem totalmente relacionado à forma como o Dedupeer irá trabalhar, a abordagem dos algoritmos descritos poderá ajudar em algumas das decisões no desenvolvimento do mesmo. O principal foco do Dedupeer será deduplicação de arquivos, enquanto que os trabalhos abaixo descritos estão em áreas como eliminação de dados redundantes em banco de dados, tráfego 13 2.2. ALGORITMOS PARA ELIMINAÇÃO DE REDUNDÂNCIA DE DADOS de redes de computadores e regiões de imagens. Como dito anteriormente, apesar de muitas empresas que trabalham com softwares para gerenciamento de armazenamento de dados terem seus próprios algoritmos de deduplicação, os detalhes sobre funcionamento, técnicas de otimização entre outros fatores que ajudariam a criação de novos algoritmos, não são reveladas por motivos comerciais. 2.2.1 FERAPARDA Um trabalho desenvolvido na UFMG [34] apresenta um algoritmo paralelo e escalável para deduplicação de dados. O foco principal deste trabalho é a identificação de duplicações em bancos de dados, e não em arquivos armazenados, como o presente trabalho. O trabalho apresentado por Santos et al. pode ser executado de forma paralela, e como consequência fazer processamento de grande conjunto de dados em um tempo viável. Em um teste apresentado neste trabalho, eles executaram o algoritmo em um cluster de 20 computadores e conseguiram realizar a detecção de duplicatas em um banco de dados com 1 milhão de registros em 7 minutos. A Figura 2.2 mostra o processo de deduplicação do algoritmo. O processo de padronização dos dados, chamado de Standardization não foi desenvolvido no trabalho descrito. A segunda etapa do processo, chamada de Blocking, tem por finalidade limitar o número de comparação entre os registros para que não inviabilize o processamento. Apesar de no caso geral uma quantidade n2 de comparações ser necessaria, o algoritmo consegue eliminar várias dessas comparações. A fase de Pair comparison simplesmete pega todos os produtos cartesianos que foram gerados na fase de Blocking e compara os atributos, em busca de encontrar os registros duplicados, esta comparaçao pode ser simples ou levar em consideração erros de digitação e conseguir identificar registros que foram gravados com valores diferentes mas que representam o mesmo objeto. Por fim, a fase Classifier é responsável por classificar os resultados dos pares "não réplicas", "possiveis réplicas"e "réplicas". Uma função de similaridade é aplicada aos dados, e a depender do resultado, os pares são classificados em uma das três classes acima citadas. 2.2.2 DYNABYTE DYNABYTE [15] é um algoritmo para identificação de redundância de dados em pacotes trafegados em redes de computadores. O algoritmo RTE - redundant traffic elimination, apesar de não estar classificado como algoritmo de deduplicação, tem o 14 2.2. ALGORITMOS PARA ELIMINAÇÃO DE REDUNDÂNCIA DE DADOS Figura 2.2 Processo do algoritmo do FERAPARDA mesmo propósito, que é o de eliminar dados redudntantes, e por isso será incluído como um dos trabalhos relacionados ao Dedupeer. O principal objetivo deste trabalho é o de eliminar a redundância dos dados trafegados na rede, que segundo trabalhos citados no mesmo, está entre 15% a 60% de tudo que é trafegado na rede. Os trabalhos desenvolvidos para RTE geralmente são executados dentro da camada de rede. Com uma abordagem diferente, o algoritmo SAMPLEBYTE, no qual o DYNABYTE é baseado, é executado dentro dos sistemas finais, o que possibilita a sua utilização até em telefones celulares. Ele foi projetado para que pudesse ser configurado de forma a não consumir muita bateria dos dispositivos móveis, o que o tornou viável em ser utilizados por eles. O problema do SAMPLEBYTE é que ele exige uma préconfiguração baseada no treinamento dos dados, e a proposta do DYNABYTE é que essa configuração seja feita de forma dinâmica, sem a necessidade do treinamento. Em Halepovic et al. [15] foi feita a coleta de todo o tráfego de rede do link de acesso da Universidade de Calgary. Foi coleta um total de oito traces, todos eles bidirecionais, chegando a um total de 233,6GB de dados. Os traces foram então separados em tráfego de entrada e saída e estudados separadamente. Para a avaliação foram utilizadas as métricas: economia de bytes, tempo de processamento e taxa de amostragem. Foram utilizados chunks de 64 bytes, um valor inviável para ser utilizado em algoritmos de deduplicação para grandes arquivos, mas que funciona bem na identificação de redundância para tráfego de rede. Como o DYNABYTE é baseado no SAMPLEBYTE, o processo de funcionamento a ser descrito será o do SAMPLEBYTE. Ele usa uma tabela com 256 entradas, uma para cada valor possível de um byte. Os blocos são varridos byte a byte, o qual é escolhido com um marcador caso o seu correspondente na tabela esteja configurado. Após isso, o 15 2.2. ALGORITMOS PARA ELIMINAÇÃO DE REDUNDÂNCIA DE DADOS seu fingerprint é computado através do algoritmo Jenkis Hash, algoritmo este que foi substituído pelo FNV Hash Function4 para o DYNABYTE. Fingerprints serão detalhados na seção 5.1.2. Os principais benefícios do SAMPLEBYTE são: evita cálculo de fingerprints que não serão mais selecionados e limita a taxa de amostragem, o que resulta em um controle do custo de processamento. No modelo cliente-servidor os fingerprints só são necessários no servidor, deixando o cliente mais flexível para criar os marcadores dos chunks. O DYNABYTE conseguiu facilitar a configuração do SAMPLEBYTE mantendo os seus benefícios e taxa de economia. 2.2.3 Trabalho de Langille et al. A proposta de Langille et al. [25] é um algoritmo eficiente para detecção de regiões duplicadas em imagens. Com o algoritmo proposto é possível identificar partes duplicadas dentro de uma mesma imagem, como por exemplo, quando se usa as ferramentas de clone do Phosothop, o que dá a possibilidade de encontrar adulterações em imagens. Para que a identificação seja realizada, é preciso que as regiões duplicadas da imagem tenham as mesmas dimensões e orientação. Por utilizar o algoritmo ZNCC (Zero-Mean Normalized Cross Correlation) 5 para identificação de similaridade é possivel identificar as regiões duplicadas até em imagens onde o algoritmo de compressão do JPEG tenha sido aplicado. O algoritmo pode ser resumido nos seguintes passos: • A imagem é segmentada em pequenos blocos sobrepostos • Para cada um dos blocos é feita uma busca por outro bloco similar através do algoritmo ZNCC • Os blocos são ordenados com base nas informações dos seus pixels através de uma técnica baseada no kd-tree 6 , o que coloca os blocos similares mais próximos um do outro. Com isso, a busca só precisa ser efetuada nos blocos vizinhos • É aplicada uma série de operações morfológicas baseadas em cor, a qual é feita para eliminação de possíveis erros do resultado. Fica provado no trabalho que esta técnica reduz eficientemente os erros que podem ocorrer na busca por regiões duplicadas 4 "http://www.isthe.com/chongo/tech/comp/fnv/index.html" 5 ZNCC em Python: "https://github.com/MartinThoma/algorithms/blob/master/crosscorrelation/zncc.py" 6 "http://www.cs.sunysb.edu/ algorith/files/kd-trees.shtml" 16 2.3. DEDUPLICAÇÃO Para medir a eficiência do algoritmo foram criadas imagens com regiões duplicadas onde a localização exata das regiões era conhecida, com isso, foi possível verificar se o algoritmo estava funcionando corretamente. 2.3 Deduplicação A deduplicação de dados é um técnica de compressão sem perda que elimina dados redundantes tanto intra-file quanto inter-file, diferente de ferramentas de compressão de dados como o gzip que só eliminam a redundância intra-file. A deduplicação reduz a quantidade de espaço necessário para armazenamento de dados através da eliminação de blocos e/ou arquivos redundantes. Na deduplicação, todos os blocos de dados ou arquivos que estão duplicados em um sistema de armazenamento são reduzidos à uma única cópia, e os dados que foram desalocados são convertidos para uma referência ao conteúdo mantido no sistema. As técnicas de compressão de dados referem-se ao trabalho de dois algoritmos. Há o algoritmo de compressão, e o de reconstrução. O algoritmo de compressão pega um arquivo X e gera uma representação Xc desse arquivo. O algoritmo de reconstrução é responsável por pegar a representação gerada pelo algoritmo de compressão e transformála no arquivo Y. Baseado no funcionamento do algoritmo de reconstrução, a técnica pode ser classificada de dois modos: sem perdas (lossless), quando Y = X; e com perdas (lossy), permite Y 6= X [35]. 2.3.1 Localização A deduplicação distribuída pode ser feita tanto na entidade que envia os dados quanto na que os recebe. Para facilitar, será utilizado o padrão cliente/servidor para explicar as formas de deduplicação. A entidade que requisita o armazenamento do dado será referenciada como Cliente; a entidade de destino dos dados será referenciada como Servidor. Cliente (source) Mandagere et al.[27] descreve a deduplicação baseada no cliente como sendo a deduplicação feita antes do cliente enviar seus dados para o servidor. O cliente faz o cálculo e obtem o valor do hash de um chunk, chamado de fingerprint, em seguida ele os envia para o servidor de metadados, que faz a comparação com os fingertprints que estão 17 2.3. DEDUPLICAÇÃO armazenados para poder identificar prováveis dados redundantes no sistema. Ao receber as informações, o cliente executa uma busca de conjuntos de bytes redundantes e remove esses dados antes de enviar o arquivo através da rede. Esse tipo de deduplicação tem como vantagem a redução da quantidade de dados trafegados na rede e redução da sobrecarga de processamento no servidor. Em contrapartida, há um consumo maior de CPU e I/O no cliente, pelo fato dele ser o executor da tarefa de detecção de dados redundantes. Um ponto crítico desta técnica é a capacidade que o cliente tem de identificar blocos de arquivos armazenados no sistema e que são idênticos aos seus. O cliente precisa saber os bytes dos blocos dos arquivos cujo fingerprint é igual ao do seu para fazer a comparação e verificar se os dados são realmente idênticos, já que mais de um bloco de mesmo tamanho pode ter um fingerprint igual mesmo tendo conteúdos diferentes. Apesar da probabilidade disso ocorrer ser quase zero, é preciso garantir a integridade dos dados armazenados pelos usuário, o que torna importante essa verificação byte a byte. Servidor (target) Considerada nessa categoria toda deduplicação que é processada na entidade que recebe os dados para armazenamento. Nela estão os appliances para armazenamento, storage arrays, peers de recebimento de dados em sistemas de armazenamento construídos com arquitetura peer-to-peer, entre outros. A deduplicação feita no servidor pode acontecer em dois momentos: inline e postprocessing. Eles serão descritos no próximo tópico. Uma desvantagem dessa abordagem é a centralização do processamento para identificação de dados redundantes no servidor, o que pode ocasionar a sobrecarregar do mesmo. 2.3.2 Momento A deduplicação pode ser de dois momentos: Inline deduplication ou Post-process deduplication [12]. • Inline deduplication: os dados são examinados no momento em que chegam, antes da escrita no sistema de armazenamento. Esse processamento antes da escrita pode tornar o servidor um gargalo. • Post-process deduplication: a deduplicação é feita depois que os dados são armazenados, em intervalos de tempo regulares ou quando um certo limite definido é 18 2.3. DEDUPLICAÇÃO alcançado. Esse tipo de deduplicação é a melhor para ser utilizada em sistemas onde a velocidade de recebimento de dados é um fator crítico, já que o processamento pode deixar pra ser feito quando o servidor estiver ocioso. 2.3.3 Algoritmo Os algortimos para fazer a deduplicação são categorizados em três tipos, segundo [27]: Whole File Hashing, Sub File Hashing e Delta Encoding. • Whole File Hashing: é aplicada alguma função de hashing como o SHA1 [1] ou o MD5[33] no arquivo todo, esse valor é comparado com a base de armazenamento de arquivos armazenados no sistema, caso algum outro arquivo possua o hash com o mesmo valor, é feita uma comparação byte a byte para ter certeza que os arquivos são idênticos, se forem, um dos arquivos é removido do sistema e o usuário ao qual o arquivo apagado pertencia terá um referência para o arquivo que é idêntico ao seu. • Sub File Hashing: nessa categoria, o arquivo é dividido em pedaços que podem ser de tamanhos iguais, chamado de Fixed Block Hashing (FBH), ou variáveis, chamado de Variable Block Hashing (VBH). No algoritmo FBH, o arquivo é todo dividido em blocos de tamanho fixo, em seguida, é aplicada alguma função de hash nos blocos para obtenção dos fingerprints. O VBH utiliza o Rabin Fingerprinting [31], a ideia de computar o fingerprint de cada bloco para apenas transferir os que têm fingerprint diferente dos blocos que estão no computador de destino, com isso é possível reduzir os dados trafegados na transferência de arquivos que possuem partes comuns aos arquivos do outro lado, mas esta técnica tem um ponto que precisa ser tratado. Quando qualquer dado é inserido ou removido de um arquivo, todos os fingerprints dos blocos subsequentes serão modificados se o algoritmo utiliza blocos de tamanho fixo. O Rabin Fingerprinting utiliza uma janela deslizante (rolling checksum e utiliza uma técnica para subdividir os blocos que tenham maior probabilidade de serem iguais a outros. • Delta Encoding/Compression (DE): com o Delta Encoding é gerado um arquivo conhecido como patch que é um arquivo que tem informações das diferenças entre dois arquivos. Um algoritmo é usado para identificar quais partes devem ser copiadas e substituidas em um arquivo e quais partes devem ser inseridas para que seja possível remontar o arquivo apenas com as informações das diferenças. 19 2.3. DEDUPLICAÇÃO 2.3.4 Benefícios Um exemplo do benefício que pode-se obter com a deduplicação foi apresentado pela IBM. A IBM representou ganho de economia com um sistema de deduplicação baseado em alterações de 20% do conteúdo dos arquivos através da Figura 2.3 [18]. A Figura mostra um backup inicial de 7TB de dados, e em uma semana o backup dos dados chegou a 79TB de dados. Em um sistema de armazenamento sem a deduplicação, todos os 49TB de dados seriam ocupados, mesmo se a alteração nos dados for de 20%, mas se esse sistema usa deduplicação, é possível armazenar todo o conteúdo no sistema utilizando apenas 8,45TB físico no disco. Ainda no exemplo, em 30 dias seria necessário 210TB de disco para armazenamento em um sistema sem deduplicação, e de apenas 26,2TB para armazenar os mesmos dados em um sistema com deduplicação. É grande o ganho de economia que pode ser atingido com a utilização de deduplicação. Figura 2.3 Exemplo da economia de espaço oferecida em um sistema de armazenamento com 20% de alteração nos dados. 20 2.4. SUMÁRIO DO CAPÍTULO 2.4 Sumário do capítulo Neste capítulo, foi feita a descrição de alguns dos sistemas de armazenamento de dados, que utilizam deduplicação e fornecem detalhes sobre como ela é feita. Alguns dos problemas enfrentados por esses sistemas, que não têm relação direta com a deduplicação, só com o armazenamento distribuído, será atribuído ao Cassandra, que vai ser usado como base para o gerenciamento dos dados do Dedupeer File Storage, fazendo com que o foco principal do trabalho, que é o algoritmo de deduplicação, tenha um maior tempo para ser dedicado na pesquisa. A utilização de chunks para o processo de deduplicação, apesar de ser óbvio, foi confirmado como base em todos os sistemas que fornecem esse serviço. Neste capítulo também foi feita uma introdução sobre o que é deduplicação e quais são os seus tipos. No próximo capítulo, será feito um aprofundamento no desenvolvimento do Dedupeer File Storage, o modelo de dados utilizado, algumas decisões de projetos que foram tomadas e será detalhada a visão de componentes e conectores do mesmo. 21 3 O Dedupeer File Storage 3.1 Introdução O Projeto Dedupeer é composto pela biblioteca homônima e pelo Dedupeer File Storage (DeFS), que foi desenvolvido para facilitar os testes e avaliação dos algoritmos implementados para a deduplicação, que é a principal contribuição deste trabalho. Para representar de forma simples a comunicação entre o componente, o sistema de arquivos e o sistema de armazenamento que adicionar o componente, foi usado o diagrama de componentes apresentado na Figura 3.1. Observando a interação entre os artefatos que compõem todo o sistema para deduplicação, percebe-se que o acesso aos arquivos é abstraído pelo Dedupeer. De uma forma transparente para o sistema de armazenamento o Dedupeer lê, quebra, e processa os bytes do arquivo especificado, levando em consideração os parâmetros passados pelo sistema através da interface de comunicação Thrift 1 . O Thrift será melhor detalhado na seção 4.2.1. O DeFS foi desenvolvido para ocupar o lugar no diagrama que pertence aos sistemas de armazenamento de dados. Com a arquitetura planejada dessa forma, fica fácil fazer a integração do algoritmo em muitos sistemas. O Thrift dá suporte à comunicação entre diversas linguagens, o que aumenta a quantidade de sistemas que podem ser integrados ao Dedupeer. 3.1.1 Escopo O Dedupeer File Storage é um sistema de armazenamento de dados, que pode ser usado de forma distribuída, com capacidade de armazenamento e recuperação de arquivos. 1 http://thrift.apache.org/ 22 3.2. O SISTEMA DE ARMAZENAMENTO DISTRIBUÍDO Figura 3.1 Diagrama de componentes de alto nível da biblioteca do Dedupeer Apesar de poder ser usado de forma distribuída, como escopo deste trabalho só será considerado a utilização dele em uma máquina simples. 3.2 O sistema de armazenamento distribuído Para facilitar os testes e a validação dos algoritmos, foi desenvolvido um sistema de armazenamento que tem como base o banco de dados não relacional desenvolvido dentro do Facebook, e hoje projeto incubado no Apache Foundation, chamado Cassandra2 . O Cassandra foi desenvolvido com o objetivo de ser escalável e altamente disponível. Para mais informações sobre a escolha do banco de dados para ser usado como base do sistema de armazenamento, ele é descrito em detalhes na seção 3.4.2. 3.2.1 Modelo de dados Pelo fato do Cassandra ser um banco de dados NoSQL, também conhecido como banco de dados não relacional, o seu modelo de dados é totalmente diferente dos utilizados nos bancos relacionais, os quais possuem tabelas com colunas, cada registro na tabela é adicionado em uma nova linha, com toda linha tendo que ter a mesma quantidade de colunas das outras. Como a maioria das pessoas está acostumada com essa forma de organização de um banco de dados, será feita uma breve apresentação do modelo de dados utilizado no Cassandra, para facilitar o entendimento sobre as escolhas de projeto para a modelagem do sistema de armazenamento. 2 http://cassandra.apache.org/ 23 3.2. O SISTEMA DE ARMAZENAMENTO DISTRIBUÍDO No modelo de dados do Cassandra existe uma flexibilidade muito grande sobre como estruturar os dados. A forma mais simples de dado que se pode armazenar é uma lista ou array. Com a adição de uma nova dimensão é possível transformar essa lista em um Map e deixar os dados mais organizados. Essa nova dimensão pode servir para nomear os dados da dimensão citada primeiramente, o que torna o modelo um pouco parecido com a estrutura utilizada no relacional. Abaixo tem um exemplo de um registro simples no modelo de dados do Cassandra [17]. "Paulo Fernando": "email": "[email protected]", "idade": "25" Essa esttutura tem como chave o valor "Paulo Fernando". A primeira dimensão, citada acima, seria os valores que representam os nomes das colunas "email"e "idade". A consideração desses valores como nome de coluna é apenas organizacional, pois eles não precisam ser necessariamente uma string, o valor armazenado em qualquer uma das dimensões pode ser até dados binários com limite de 2 GB, mas para melhor estruturação a maioria dos bancos usam esse valor como uma string ou um long para representar um identificador para o valor da segunda dimensão, que está sendo representada no exemplo pelos valores "[email protected]"e "25". Todos esses valores juntos representam uma linha, e a reunião das linhas recebe o nome de família de colunas. As famílias de colunas são associadas a um keyspace, que é um container de família de colunas, e que seria o equivalente a um banco de dados no modelo relacional. Cada instância do Cassandra pode ter vários keyspaces. Além da coluna normal, representada pelos pares dos valores no exemplo acima, como por exemplo, "idade": "25", existe também a chamada super coluna, que é um tipo especial de coluna que possui uma estrutura mais complexa, onde existe um campo que é usado como identificação e uma agregação lógica de colunas simples. O exemplo abaixo, demonstra como seria um conjunto de dados representado por uma super coluna. "Paulo Fernando": "endereço" "rua": "Rua ABC", "número": "123" "informações pessoais" 24 3.2. O SISTEMA DE ARMAZENAMENTO DISTRIBUÍDO "email": "[email protected]", "idade": "25" O Cassandra, quando utiliza super colunas, funciona como uma hastable de 5 dimensões [Keyspace] [ColumnFamily] [Key] [SuperColumn] [SubColumn] [17]. Para acessar a "idade"de "Paulo Fernando", por exemplo, o caminho seria [Keyspace] [ColumnFamily] ["Paulo Fernando"] ["informações pessoais"] ["idade"], onde Keyspace e ColumnFamily teriam que ser previamente criadas no Cassandra para que esses dados pudessem ser armazenados dentro da ColumnFamily. Toda a estrutura que foi definida para o sistema de armazenamento desenvolvido está representado na Figura 3.2. A escolha da estrutura utilizada será discutida a seguir. Foram definidas duas famílias de super colunas para armazenar e organizar os dados dos arquivos: UserFiles, Chunk; e um família de colunas para agilizar o processo de identificação do arquivo: Files. Na família de super colunas UserFiles, a chave é representada pelo nome do usuário. O nome da super coluna é definido como sendo o nome do arquivo, no caso do exemplo o arquivo se chama "lorem.txt". Foram definidas seis subcolunas para essa família: file id Armazena o identificador único do arquivo no sistema. size Armazena o tamanho do arquivo em bytes. chunks Armazena a quantidade de chunks em que o arquivo foi dividido. with content Armazena a quantidade de chunk que possui conteúdo without content Armazena a quantidade de chunks que não possui conteúdo. Este valor representa a quantidade de chunks que são referências à chunks que estão duplicados no sistema. A criação das colunas with content e without content foi uma decisão para aumentar o desempenho evitando ter que consultar todos os chunks do arquivo para identificar quais deles são referências e quais armazenam o conteúdo. A criação de apenas um dos dois resolveria o problema, mas nesse caso, para a identificação do outro valor seria necessário 25 3.2. O SISTEMA DE ARMAZENAMENTO DISTRIBUÍDO a consulta da quantidade total de chunks, então foi tomada a decisão de já armazenar o valor calculado. Na família de super colunas chamada Chunk a chave que identifica a linha é o identificador único do arquivo, localizado na coluna com nome file id na família de super colunas UserFiles. Cada linha representa as informações de todos os chunks pertencentes ao arquivo referenciado. Cada arquivo só tem uma linha nessa família de super colunas. As subcolunas dessa família são detalhadas abaixo. md5 Armazena o valor do fingerprint representado por um valor do cálculo de uma função SHA-1 sobre os dados do chunk atual. hash32 Armazena o valor do fingerprint representado por o resultado do cálculo de uma hash de 32 bits sobre os dados do chunk. index Armazena a posição do início do chunk dentro do arquivo completo. Por exemplo, se ele for o primeiro segmento do arquivo, esse valor será 0, se ele for o segundo segmento do arquivo e o primeiro segmento tiver 512 bytes de tamanho, esse valor será 512. length Armazena a quantidade de bytes que o chunk possui. content Armazena o conteúdo binário que se inicia na posição index e vai até a posição index + length dentro do arquivo. Armazena a quantidade de bytes que o chunk possui. pfile Armazena o identificador do arquivo ao qual o segmento referenciado pertence. pchunk Armazena o identificador do segmento ao qual foi identificado que o conjunto de dados é idêntico. Em outras palavras, adiciona uma referência ao segmento que possui o conteúdo que não foi armazenado no sistema por ser duplicado. 26 3.2. O SISTEMA DE ARMAZENAMENTO DISTRIBUÍDO Percebe-se a flexibilidade da utilização do Cassandra nessa família de colunas, na mesma família de colunas são armazenados dois tipo estruturais de dados, um para representar um segmento com conteúdo binário e outro para representar uma referência para um outro segmento já armazenado no sistema. Para uma melhor visualização dessa família de super colunas, elas serão detalhada na Figura 3.2. A família de colunas chamada Files é bem simples, ela é apenas uma atalho de consulta para o identificador de um arquivo de um determinado usuário baseado no nome desse arquivo. A chave dessa família de colunas é o nome do usuário. Ela possui apenas uma coluna que tem o nome do arquivo do usuário e o id desse arquivo. 27 3.2. O SISTEMA DE ARMAZENAMENTO DISTRIBUÍDO Figura 3.2 Modelo de dados do sistema distribuído desenvolvido com o Cassandra 28 3.3. VISÃO DE COMPONENTES E CONECTORES 3.3 Visão de componentes e conectores Nesta seção será descrito o funcionamento em tempo de execução do Dedupeer Project, que engloba DeFS integrado com o Dedupeer. A visão de componentes e conectores do Dedupeer Project pode ser vista na Figura 3.3. Existem 3 grandes componentes no sistema: O DeFS, o Dedupeer e Cassandra, os quais serão descritos a seguir: DeFS O Dedupeer File Storage é o sistema distribuído de armazenamento de dados que foi desenvolvido na pesquisa para que os algoritmos de deduplicação propostos pudessem ser testados com maior facilidade. O DeFS foi descrito na seção 3.2. Dedupeer O Dedupeer é a biblioteca onde os algoritmos propostos foram implementados. Ele possui uma interface Thrift para interoperabilidade de comunicação entre linguagens de programação distintas, para facilitar a integração do mesmo com a maioria dos sistemas de armazenamento existentes. O componente Dedupeer será descrito no capítulo 4, e o funcionamento dos algoritmos do Dedupeer serão descritos no capítulo 5. Cassandra O Cassandra é um banco de dados distribuído que foi usado como base do sistema de armanzenamento DeFS. Mais informações sobre o Cassandra na seção 3.4.2. O componente Dedupeer File Storage O componente GUI é o que apresenta a interface com o usuário através de uma janela com componentes gráficos. Esse componente ainda possui os renderizadores dos campos da tabela de listagem dos arquivos e o frame de configuração dos parâmetros que ficam armazenados no arquivo. Ele tem ligação com as filas de backup (BackupQueue) e de restore (RestoreQueue) dos arquivos, as quais são incrementadas com novos itens através da interação do usuário. Os componentes para gerenciamento das filas de backup e restore (também conhecido como reidratação no contexto de deduplicação) funcionam com uma LinkedBlockingQueue como base, que é uma fila com propriedade FIFO (first-in-first-out). O componente StoredFile é o item usado tanto na fila de backup como na de reidratação, esse é o componente central do DeFS. Ele é responsável pelo gerenciamento do 29 3.3. VISÃO DE COMPONENTES E CONECTORES Figura 3.3 Visão de componentes e conectores do DeFS integrado com a biblioteca do Dedupeer processo de backup, reidratação e cálculo de economia de espaço. Tem ligação com o Dedupeer através do ThriftClient e indireta com o Cassandra através do componente de gerenciamento de dados que foram agrupados em DaoOperations. O ThriftClient, como o nome já diz, é um cliente Thrift para fazer a comunicação com a biblioteca Dedupeer. O Componente Chunking é usado para quebrar o arquivo em chunks para que sejam enviados para o Cassandra. Ele é usado apenas quando não é requisitada a utilização da biblioteca do Dedupeer, já que na deduplicação o Dedupeer já retorna todas as informações sobre os chunks do arquivo, não sendo necessário nesse caso a divisão dos chunks através do componente Chunking. O componente Dedupeer O componente Dedupeer possui apenas dois componente internos, o ThriftServer e o DeduplicationImpl. O ThriftServer é o servidor que fica esperando por comunicações dos clientes Thrift, é ele que roda o serviço que espera pelos parâmetros necessários 30 3.4. DECISÕES DE PROJETO E CONSIDERAÇÕES para o processamento da deduplicação, processamento esse que é executado dentro do DeduplicationImpl, o qual retorna a estrutura gerada através do processamento para o ThriftServer para que este o envie de volta ao ThriftClient. 3.4 Decisões de projeto e considerações Nesta seção serão discutidas as principais escolhas de tecnologias ou conceitos feitas na construção do Dedupeer File Storage e o motivo dessas escolhas. A tecnologia que foi escolhida será detalhada e as vantagens da sua escolha serão destacadas. 3.4.1 Tamanho do chunk A definição do tamanho padrão do chunk é muito importante e não pode ser feita sem um estudo sobre as consequências da escolha. Esse valor impacta tanto no desempenho do sistema como a taxa de compressão médio que pode ser obtido na deduplicação. Quanto menor for o tamanho de um segmento, maior será o raio de compressão pelo fato de haver uma maior probabilidade de encontrar outros segmentos idênticos, já que um menor conjunto de bytes precisará ser igual. Em contrapartida, com segmentos pequenos será preciso processar uma maior quantidade deles, e a depender da implementação, acessar o disco mais vezes, o que degrada o desempenho do sistema. E como cada chunk requer uma mesma quantidade de metadados, independente do seu tamanho, ter chunks pequenos ocasiona um maior espaço necessário para armazenamento. Um projeto deve possuir o menor segmento possível, contanto que ele não degrade o desempenho do sistema à ponto de ficar abaixo das especificações requeridas. Zhu et al.[49] afirma que depois dos testes executados para medir o tamanho ideal, eles encontraram o valor de 8 KB [49], que é o mesmo valor encontrado pelos criadores do Venti [30]. Kaiser et al.[20] relata que o tamanho do chunk para um sistema de deduplicação exata (exact deduplication), deve estar entre 4KB e 16 KB [20]. Esses valores serão levados em conta após o teste de desempenho que será feito para identificar a melhor combinação de tamanho de chunk e algoritmo de hashing no Capítulo 6. 3.4.2 Cassandra para gerenciamento do armazenamento Muitos dos desafios enfrentados quanto ao desenvolvimento de um sistema de armazenamento distribuído são delegados para serem resolvidos através do gerenciamento 31 3.4. DECISÕES DE PROJETO E CONSIDERAÇÕES executado pelo Cassandra. Alguns desses desafios são citados no artigo sobre o Dynamo, entre eles, robustez e escalabilidade no balanceamento de carga, detecção de filiação e de falhas, recuperação de falhas, concorrência e agendamento de tarefas (jobs), e empacotamento de requisições [8]. Além desses, existem outros desafios como, garantia de disponibilidade e consistência dos arquivos, replicação dos dados e tamanho e quantidade das mensagens trafegadas. Nesta seção será discutida a forma como o Cassandra resolve todos esses problemas. O Cassandra foi desenvolvido em uma arquitetura peer-to-peer, também conhecida como P2P, que é uma das arquiteturas comuns existentes na internet, além dela, a outra arquitetura muito usada é a Cliente/Servidor. Na arquitetura Cliente/Servidor, existe no mínimo duas entidades, uma que requisita um recurso e outra que atende a essa requisição, nessa arquitetura os nós agem de uma forma ou de outra, nunca as duas ao mesmo tempo. Na arquitetura P2P as entidades se comportam tanto como clientes quanto como servidores, consumindo e oferecendo recursos na rede. Schollmeier[38] define P2P como uma rede na qual os nós compartilham parte dos seus recursos de hardware, entre eles, processamento, capacidade de armazenamento, impressora, entre outros. Esses recursos compartilhados são essenciais para os serviços e conteúdos oferecidos pela rede, como, por exemplo, compartilhamento de arquivos. A arquitetura P2P é dividida em dois tipos, as chamadas arquitetura P2P pura e híbrida. [38] define a arquitetura P2P pura se ela está classificada dentro definição anterior e, além disso, se qualquer nó da rede puder ser removido sem que a rede sofra qualquer perda. O mesmo trabalho define que uma arquitetura P2P é dita híbrida quando ela está classificada na primeira definição de P2P e, além disso, uma entidade central é necessária para fornecer parte do serviço que a rede se dispõe a oferecer [38]. No Cassandra, a arquitetura P2P usada foi a pura. Todo nó no Cassandra é idêntico aos outros, não existe nó mestre e nem nó escravo. Com essa estrutura, a escalabilidade horizontal se torna mais fácil, já que só é preciso adicionar um novo nó no cluster para que ele se integre e se organize dentro da rede, para isso, ele possui um tempo para aprender como está a topologia do anel (a rede do Cassandra funciona como um anel). Depois da descoberta de como a rede está topologicamente estruturada o nó começa a receber como qualquer outro que integra a rede. O Cassandra utiliza o próprio sistema para armazenar as informações de configurações dele. Todas essas informações são armazenadas dentro do Keyspace chamado system. Entre essas informações estão o token do nó, o nome do cluster, definições de schema, entre outras. Esse Keyspace não pode ser modificado pelo usuário. 32 3.4. DECISÕES DE PROJETO E CONSIDERAÇÕES Tolerância à falhas O Cassandra usa o gossip protocol[23] para a detecção de falhas. Periodicamente, uma mensagem é enviada para um outro nó, quando o nó recebe a mensagem ele responde com um Ack, quando a mensagem é recebida pelo nó que a iniciou, ele responde com um Ack2. Com a utilização desse protocolo é possível identificar os nós que pararam de funcionar. O Cassandra ainda usa um algoritmo que ao invés de apenas informar se um sistema é ou não confiável, ele retorna um valor que é o nível de suspeita, esse algoritmo é chamado de Phi Accrual Failure Detection [9]. O Cassandra possui um método que ajuda na decisão se um nó está morto ou não, baseado no valor do nível de suspeita, com assinatura interpret(InetAddress), onde InetAddress representa o endereço do nó. Recuperação de falhas Todo sistema que utiliza gossip protocol deve ter um mecanismo para tratar os problemas decorrentes da sua utilização, o principal deles é chamado de anty-entropy e é um mecanismo para sincronização de réplicas, que assegura que os valores das réplicas armazenados nos nós estejam atualizados com a versão mais recente. Durante a fase de compactação, os nós fazem a requisição das árvores Merkle dos vizinhos para poder comparar com a sua árvore e assim poder identificar possíveis valores desatualizados. A árvore Merkle funciona como um snapshot da família de colunas atual, ela é uma árvore que possui os valores dos hashes dos dados armazenados nas famílias de colunas, esses valores são comparados com os do nó requisitante, caso for encontrado algum valor diferente, é feito o reparo nos dados atualizando-os para a versão mais recente [38]. Anti-entropy também é usado no Dynamo [8], mas o Cassandra faz um uso diferente, na implementação do Cassandra existe uma árvore Merkle para cada família de colunas. O algoritmo de anti-entropy é executado depois de cada atualização no banco de dados. As operações de escrita no Cassandra são feitas primeiramente em um log que é chamado de commit log. Este mecanismo tem como finalidade possibilitar a recuperação de uma escrita mal sucedida. A escrita só é marcada como feita quando a operação é salva no commit log, com ele é possível recuperar uma falha na escrita feita em memória. Após a escrita no commit log o dado é salvo em uma estrutura de dados alocada na memória que é chamada de Memtable. O Memtable só terá os dados salvos no disco quando ele atingir um limite de objetos armazenados. A estrutura aonde os dados são salvos no disco é chamada de SSTable. 33 3.4. DECISÕES DE PROJETO E CONSIDERAÇÕES Write.ANY Write.ONE Write.QUORUM Write.ALL Read.ONE Weak Weak Weak Strong Read.QUORUM Weak Weak Strong Strong Read.ALL Weak Strong Strong Strong Tabela 3.1 Força do consistência baseada nos níveis das operações [7] Consistência A consistência dos dados que são armazenados no Cassandra tem o seu nível definido pelo usuário. A consistência pode ser usada como valor ONE (consistência fraca), nesse nível de consistência o Cassandra pode retornar o dado encontrado no primeiro nó consultado sem verificar se aquele é o valor mais recente. No caso de existir muitos clientes na rede, é recomendável ter um nível de consistência no mínimo QUORUM, que é um nível no qual o Cassandra precisa ler de vários nós antes de retornar o valor, o que assegura que o sistema encontrará o valor do dado mais recente. No nível de consistência ALL, o Cassandra consulta os dados armazenados em todos os nós antes de retornar o valor. Se uma das consistências fortes forem utilizadas (QUORUM ou ALL), a recuperação de falhas, conhecida como read-repair é executa antes do dado ser retornado. O CAP Theorem descreve um sistema distribuído com relação à três aspectos: consistência, disponibilidade e tolerância à partição. O teorema diz que é impossível que um sistema distribuído possua os três aspectos ao mesmo tempo, sempre um desses aspectos tem que ser sacrificado. O Cassandra tem flexibilidade e permite que o usuário escolha os aspectos do CAP Theorem que estão no sistema [7]. A Tabela 3.1 mostra qual o nível de consistência do Cassandra a depender das escolhas do usuário. O quorum é o número de nós que deve ser consultado para se chegar a um consenso sobre a validade do dado (pode ser ANY, ONE, QUORUM ou ALL). Se o valor do quorum for ONE, o dado só será armazenado em um único nó, o que vai torná-lo sempre consistente, mas em contrapartida, ele nunca será replicado, o que diminui a disponibilidade do mesmo. Disponibilidade O Cassandra possui um mecanismo para disponibilidade de escrita mesmo quando o nó que receberia a mensagem tem uma falha de hardware, ou qualquer outra que o impeça de receber a mensagem, esse mecanismo é chamado de hinted handoff. Quando um nó tenta enviar uma mensagem para um outro que não pode ser alcançado por alguma falha, 34 3.4. DECISÕES DE PROJETO E CONSIDERAÇÕES o nó remetente grava a mensagem em uma área do keyspace system para que quando o nó onde a mensagem deveria ser escrita voltar, ela possa ser enviada e persistida. Empacotamento de requisições A troca de mensagens entre os nós é feita através de um serviço. As mensagens que chegam são encaminhadas para um pool de threads, o qual é responsável pelo gerenciamento delas. Em seguida, é analisado se a mensagem é do tipo streaming, que é uma forma eficiente utilizada pelo Cassandra para fazer a transferência de SSTable entre os nós, ou se é uma mensagem serializada padrão, e a partir disso é determinado para qual manipulador ela será atribuída. 3.4.3 Alternativa para o gerenciamento de armazenamento Foram analisados alguns pontos para a escolha entre o Cassandra e o HBase3 para ser usado no Dedupeer File Storage. Os pontos considerados críticos para a escolha e que fizeram com que o Cassandra fosse escolhido foram: facilidade de instalação/manutenção e funcionamento em pequenos clusters. Esses foram os pontos considerados mais importantes pelo fato do objetivo ser a criação de um sistema para armazenamento de arquivos que funcionasse tanto como um sistema de armazenamento stand-alone como um sistema de armazenamento distribuído, e que fosse fácil de manter e instalar. Facilidade de instalação e manutenção O HBase é mais complexo para ser gerenciado pelo fato de ter sido desenvolvido para dar suporte ao Hadoop, logo ele é mais complicado de ser gerenciado já que por padrão ele está integrado com o Hadoop, HDFS e Zookeeper4 . O Cassandra não tem por padrão essa ligação com outros sistemas, ele é mais fácil de instalar e manter que o HBase. Clusters pequenos Segundo o site oficial do HBase5 , ele não é adequado para ser usado em um cluster com menos de 5 DataNodes, o que inviabilizaria a possibilidade da utilização do Dedupeer File Storage em um computador stand-alone. Esta foi uma funcionalidade colocada como prioritária no projeto, pois ela é útil para conseguir a economia de espaço proporcionada 3 "http://hbase.apache.org/" 4 http://whynosql.com/cassandra-vs-hbase/. Acessado em abril de 2013 Acessado em abril de 2013 5 http://hbase.apache.org/book/architecture.html. 35 3.4. DECISÕES DE PROJETO E CONSIDERAÇÕES pela deduplicação em computadores, sem que para isso precise criar um sistema de armazenamento distribuído. O HBase precisa de mais nós porque ele é dependente do HDFS, e o HDFS utiliza uma replicação de blocos de 3 cópias. 3.4.4 Escalabilidade Primeiramente, o conceito do que é escalabilidade será descrito, antes da apresentação das vantagens que a escolha do Cassandra para gerenciamento do sistema de armazenamento oferece para a escalabilidade. O que é escalabilidade? Construir arquitetura de sistema para dar suporte à milhões de usuários é um dos maiores desafios enfrentados pela indústria de software. Esse desafio fica mais complexo quando a arquitetura é projetada para escalar e se adaptar à quantidade de usuários sem precisar ter que reprojetá-la. Quando se fala em escalabilidade, grande parte dos engenheiros de software só conseguem enxergar sistemas que escalam para mais usuários (scale up), mas escalabilidade também está relacionado à capacidade do sistema em escalar para suportar menos usuários (scale down), ou seja, ele deve ser capaz de reduzir a quantidade de recursos alocados quando a demanda de usuários diminuir [36]. Existem dois tipos de escalabilidade de sistemas: horizontal (scale up) e vertical (scale out). Escalabilidade vertical é a capacidade de adicionar recursos a um nó do sistema e ele se adequar à nova configuração. Escalabilidade horizontal é a capacidade do sistema se adequar à adição de novos nós. Quando um sistema precisa escalar horizontalmente (scale up), é um forte sinal de que a demanda por ele está crescendo, esse fator é um indício de que o lucro da empresa também está crescendo junto. Quando isso acontece, e a arquitetura do sistema foi pobremente planejada para escalar horizontalmente, a empresa pode investir parte dos lucros obtidos com a quantidade de usuário para solucionar o problema de escalabilidade, mesmo que não seja pela melhor forma. O maior problema é que quando essa demanda cai bruscamente, os lucros da empresa com o sistema geralmente também caem, e a empresa tenta ao máximo reduzir os custos. Percebe-se que se a arquitetura não é planejada para scale down, a perda de dinheiro com a redução de cliente pode se tornar maior, já que a quantidade de recursos alocados para o sistema se tornam ociosos por ter sido reservado 36 3.5. SUMÁRIO DO CAPÍTULO para atender a uma quantidade de usuários muito maior do que a real. Escalabilidade com o Cassandra Com a escolha de um banco de dados NoSQL, distribuído, e alto gerenciável, a questão da escalabilidade é um outro ponto que o Dedupeer File Storage não precisará se preocupar, já que todo o gerenciamento é feito pelo cluster de Cassandras. 3.5 Sumário do capítulo Este capítulo apresentou o Dedupeer File Storage, que foi sistema de armazenamento de arquivos desenvolvido para auxiliar a pesquisa, e que pode ser usado tanto um sistema de armazenamento local como um sistema de armazenamento distribuído. Ele é integrado através da interface Thrift com o componente para deduplicação de arquivos, o qual será descrito em detalhes no próximo capítulo. Foi discutido o modelo de dados utilizado, a visão de componentes e conectores do sistema integrado com o componente para deduplicação e as principais decisões de projeto, como a escolha do Cassandra para gerenciamento dos dados. Os benefícios relacionados à utilização do Cassandra também foram detalhados, o qual ficou responsável por tratar os problemas comuns enfrentados na criação de sistemas de armazenamento distribuídos. O próximo capítulo apresentará o componente para deduplicação de arquivos e detalhará as principais decisões de projeto no seu desenvolvimento. 37 4 Componente para deduplicação em sistemas de armazenamento de dados 4.1 Introdução O desenvolvimento do Dedupeer foi planejado para que os algoritmos desenvolvidos pudessem ser usados pela comunidade de forma fácil. O Dedupeer foi projetado para que as escolhas de configurações fossem feitas através de parâmetros da interface de serviço, entre essas configurações estão o tamanho do chunk e o algoritmo de hashing. O Dedupeer também foi desenvolvido para que pudesse ser usado por qualquer sistema de armazenamento que tenha como objetivo obter um ganho na economia de espaço utilizado, o que traz benefícios tanto para o fornecedor, por ter mais espaço para oferecer, quanto para o meio ambiente. O chamado Green Storage Initiative(GSI) 1 é liderado pelo SNIA (Storage Networking Industry Association) em parceria com empresas como Dell, EMC, HP, IBM, Oracle, NetAPP, Seagate, entre outras. Na GSI são discutidas técnicas que diminuam o impacto do uso de sistemas de armazenamento ao meio ambiente. Das discussões sobre uso eficiente dos sistemas de armazenamento, a EPA liberou um documento de especificação para discussão pública, onde deduplicação de dados é considerada umas das tecnologias da green storage [11], veja na citação abaixo. "EPA understands that there are many software-based approaches to improving the energy efficiency of storage products. The benefits of virtualization, data deduplication, and other software-based data management techniques are well documented. These software solutions, perhaps even more so than 1 http://www.snia.org/forums/green 38 4.2. INTEROPERABILIDADE the hardware itself, are heavily customized for specific customers and applications. Achieving maximum efficiency gains is highly dependent upon proper software architecture, implementation, operation, and maintenance by individual users." Nas seções seguintes serão detalhadas as decisões de projeto que foram tomadas, assim como uma técnica utilizada para tornar o processamento da deduplicação temporalmente viável. 4.2 Interoperabilidade Engenharia de Software baseada em componentes desperta muito interesse devido a reusabilidade do software. O seu objetivo é reduzir o custo e o esforço no desenvolvimento, enquanto provê flexibilidade, confiabilidade e reusabilidade, pelo fato do componente já ter sido validado e testado antes da integração [44]. Um dos principais conceitos chave para a criação de componentes de software reusáveis é a interoperabilidade. A Interoperabilidade foi definida por Vallecillo et al. [44] como a habilidade de duas ou mais entidades se comunicar ou cooperar, apesar das diferenças na linguagem de implementação, ambiente de execução, ou modelo de abstração. Existem dois níveis de interoperabilidade: assinatura e semântica. A primeira é baseada na assinatura das operações que o componente disponibiliza para interação, como por exemplo, nome de métodos, tipos dos parâmetros e valores de retorno. A interoperabilidade semântica tenta garantir que a troca de dados entre os componentes faça sentido, mas pelo fato desse nível de interoperabilidade englobar todos os aspectos semânticos, ele é considerado muito complexo, o que direcionou a maioria dos trabalhos sobre esse nível para uma de suas áreas, relacionada às questões comportamentais dos componentes [44]. O Dedupeer foi desenvolvido com o nível de interoperabilidade de assinatura, a qual é definida e disponibilizada através do Thrift, que será detalhado na seção seguinte. 4.2.1 Interoperabilidade de comunicação através do Thrift O Thrift foi um projeto que surgiu dentro do Facebook e hoje é gerenciado pela Apache Software Foundation2 . Ele possui uma ferramenta de geração de código para 2 http://thrift.apache.org/ 39 4.2. INTEROPERABILIDADE facilitar a criação de serviços em diversas linguagens de programação. O principal objetivo do Thrift é fornecer a capacidade de comunicação entre linguagens de programação distintas. O Thrift permite que os desenvolvedores definam tipos de dados e serviços em um arquivo de linguagem neutra e gera toda a estrutura necessária, tanto para o servidor como para o cliente RPC (remote procedure call) [2]. O Thrift tem flexibilidade para a criação de estruturas de dados, além de também dar suporte aos principais tipos, independente da linguagem utilizada. O Thrift possui os seguintes tipos base: bool Um valor booleano, true ou false. byte Um byte com sinal. i16 Um inteiro com sinal de 16 bits. i32 Um inteiro com sinal de 32 bits. i64 Um inteiro com sinal de 64 bits. double Um número de ponto flutuante de 64 bits. string Uma sequência de caracteres. Além desses tipos de dados mais simples, existem estruturas de dados mais complexas que podem ser usados para a comunicação entre linguagens com o Thirft. Para isso, ele faz as conversões entre as estruturas definidas nessas linguagens. As 3 estruturas de dados que podem ser usados no Thrift são: list<type> Um lista ordenada de elementos. Essa estrutura é traduzida em um ArrayList em Java ou em array nativo nas linguagens de script. set<type> Um conjunto de elementos. Traduzido para HashSet em Java. map<type1,type2> Um mapa chave/valor. Em Java ele é traduzido para um HashMap. Ainda relacionado à utilização de estruturas mais complexas, pode-se criar estruturas equivalente à objetos para serem transferidas através do Thrift, os quais são chamados structs. Dentro de um structs pode-se adicionar quantos tipos forem necessários, os quais podem ser opcionais ou obrigatórios. Quando um campo é configurado como obrigatório, o construtor do objeto receberá o campo como parâmetro, caso o campo seja configurado 40 4.2. INTEROPERABILIDADE como opcional, o objeto terá um método que poderá ser usado para adicionar um valor ao campo e ele não será um dos parâmetros do construtor. O Thrift possui o conceito de serviços, os quais são definidos no mesmo arquivo que os tipos. Com a geração automática do código através do compilador do Thrift, toda a estrutura necessária para que o serviço funcione é criada, tanto a parte cliente como a parte servidora, só é preciso implementar o algoritmo que funcionará dentro do serviço. Os serviços são definidos no Thrift da seguinte forma: service <name> { <returntype> <name>(<arguments>) [throws (<exceptions>)] ... } A escolha do Thrift para o projeto será discutida a seguir. 4.2.2 Comparação com o Protocols Buffer Nesta seção será discutida a escolha do Apache Thrift como opção para a serialização dos dados trocados entre o sistema de armazenamento e o Dedupeer. O Thrift será comparado com um outro mecanismo parecido com ele e que também é muito utilizado nos sistemas atuais, o Protocols Buffer. Protocols Buffer O Protocols Buffer foi desenvolvido na Google e é utilizado internamente em quase todos os seus protocolos RPC [24]. Segundo o Developer Guide [14], o Protocols Buffer é uma forma extensível de serialização de dados estruturados que possui linguagem neutra, e tem a finalidade de ser usado em protocolos de comunicação, armazenamento de dados, entre outras. O Protocols Buffer possui uma boa documentação, diferente do Thrift onde a documentação é bem reduzida. Ele dá suporte à apenas 3 linguagens: C++, Java, Python. Isso acaba limitando a integração com sistemas desenvolvidos em outras linguagens. O Thrift suporta as seguintes linguagens: As3, C Glib, C++, CSharp, D, Delphi, Erlang, Go, Haskell, Java, Java Me, Javascript, Node.js, Objective-c, OCaml, Perl, PHP, Python, Ruby e Smalltalk, o que dá uma maior interoperabilidade para o componente. 41 4.2. INTEROPERABILIDADE Quanto à quantidade de tipos suportados, ele também possui uma quantidade inferior ao Thrift. O Protocols Buffer dá suporte aos tipos: bool, 32/64-bit integers, float, double, string, sequência de byte e o "repeated"que funciona como uma lista. O Thrift dá suporte aos tipos: bool, byte, 16/32/64-bit integers, double, string, sequência de bytes, map<t1,t2>, list<t>, set<t>. Como no Dedupeer a estrutura de dados trafegada necessitava da utilização de um map, a facilidade de implementação dessa estrutura no Thrift foi um dos motivos para decidir por ele quando comparado ao Protocols Buffer. Comparação de desempenho O Protocols Buffer leva vantagem no desempenho em relação ao Thrift. Nas Figuras 4.1 e 4.2, são apresentados resultados de um teste de desempenho feito com algumas bibliotecas de serialização. Percebe-se que o Protocols Buffer, chamado de protobuf, tem um melhor desempenho que o Thrift. Na serialização dos dados, o Protocols Buffer teve um desempenho aproximadamente 50% mais rápido que o do Thrift. Na desserialização, o Protocols Buffer foi aproximadamente 31% mais rápido. Mas o peso dessa vantagem não influenciou na decisão de escolha, devido ao fato de que o suporte a mais linguagens e a possibilidade de utilização de estruturas de dados como Map foram considerados mais importantes para o projeto do que a diferença entre o desempenho das ferramentas. Figura 4.1 Desempenho na serialização dos dados [39] 4.2.3 Comparação por hash (fingerprints) Uma função hash mapeia um conjunto de dados de tamanho variável para um resultado com tamanho fixo. Como o valor no qual o conjunto de dados é mapeado tem um tamanho menor do que o conjunto de entrada, existe uma possibilidade de dois ou mais conjuntos 42 4.2. INTEROPERABILIDADE Figura 4.2 Desempenho na desserialização dos dados [39] de dados distintos terem um valor de hash igual. Quando o mesmo hash é obtido de conjunto de dados diferentes, é dito que houve uma colisão de hashes [16]. Comparar a igualdade entre arquivos ou entre blocos de dados através do resultado de uma função hash aplicada aos seus conteúdos, é uma forma eficiente de verificar se os conteúdos são diferentes com probabilidade de erro igual a zero. Muitos sistemas utilizam a comparação para identificar igualdade em conteúdos também, como por exemplo, o rsync[43] e o Venti[30]. Apesar da utilização dessa técnica em vários grandes projetos, a comparação de igualdade entre conteúdos através de uma função hash aplicada à ele tem probabilidade de encontrar um falso positivo. O cálculo de uma função hash é muito eficiente. O cálculo do SHA1 de 160 bits em um bloco de 8KB, utilizando um computador Pentium 3 de 700Mhz, é feito em 130 microssegundos, o que dá a média de 60MB/s [30]. Comparar todos os bytes de dois blocos de dados para identificar se os mesmos são iguais, é uma tarefa muito custosa. O custo computacional para fazer uma comparação de blocos de dados com 128 KB cada um é menor, se ao invés de comparar todos os 128 KB de conteúdo fizer a comparação apenas dos valores de hash dos dois, que se for obtido através de uma função MD5 de 128 bits, por exemplo, terá um valor de representação com 32 dígitos hexadecimais. A comparação de 128 KB de dados é substituída por uma simples comparação entre 16 bytes. Fazer a comparação de todos os bytes de um segmento para identificação de igualdade é chamado de comparação byte a byte. Esse método de determinação de igualdade de segmentos é totalmente confiável, mas em contrapartida, é extremamente custoso. Apesar da comparação por hashes ter a probabilidade de dar um falso positivo para um segmentos de dados com conteúdos diferentes, o principal argumento dos defensores dessa técnica é 43 4.2. INTEROPERABILIDADE que a probabilidade de ocorrer um erro no hardware que armazena o dado é muito maior do que a probabilidade desse falso positivo acontecer. Quando um dado é corrompido, é quase certo que esse erro seja causado por falha de hardware, como erros não detectados na memória principal, na transferência pela rede, nos dispositivos de armazenamento ou qualquer outro componente de hardware, do que por uma colisão de hashes. Por este motivo, foi definido que o método de identificação de segmentos iguais utilizado no projeto seria através da comparação por hashes. Para assegurar que os dados são realmente idênticos, algumas empresas, como a NetAPP, fazem a análise bit a bit dos chunks, que pode ser uma alternativa, mas geralmente é usada como complemento da comparação por fingerprints para confirmar se os dados são iguais, e assim evitar o problema que pode ser causado pela colisão de hashes. A probabilidade de ocorrer uma colisão de hashes pode ser estimada através dessa função matemática: P = 1 − (1 − 2−b )n , com n representando o número de blocos de entrada e b representando o número de bits no valor do hash de saída. Considerando um sistema que usa blocos de 8 KB com fingerprints calculados com SHA1 de 160 bits e que possua 1 exabyte (1018 bytes) de dados armazenados, o que dá um quantidade de aproximadamente 1014 blocos, a probabilidade de uma colisão de hashes ocorrer neste cenário ainda é extremamente baixa, essa probabilidade é menor que 10−20 [30]. Vantagem A comparação bit a bit, apesar de ser uma técnica na qual se pode ter uma certeza absoluta sobre a igualdade de dois conjunto de dados, ela possui desvantagens. Além de ser uma técnica muito custosa, já que todos os bits precisam ser comparados, um a um, ela elimina toda a vantagem da análise remota de chunks, que é a utilizada no Dedupeer. Por isso, ela não foi implementada para garantir o positivo na comparação entre chunks. A utilização do SHA-1 para a identificação de igualdade de conjunto de dados foi usado porque apesar da possibilidade de colisão, ele permite a possibilidade de analisar a igualdade de dois conjuntos de dados sem tê-los em um mesmo computador, o que possibilita o processamento remoto. Outro ponto positivo para a escolha do SHA-1, é o fato de até hoje não ter registros de colisão de hashes com ele [37]. Se for utilizado SHA-256, a probabilidade de colisão é ainda menor, segundo [29] a probabilidade de uma colisão em um conjunto de 1024 exabytes de dados em um 44 4.3. SUMÁRIO DO CAPÍTULO sistema de deduplicação, com um tamanho de chunk de 4KB e utilizando o SHA-256 para calcular os fingerprints, é menor que 10−42 . 4.3 Sumário do capítulo Neste capítulo foram discutidas algumas das principais decisões de projeto para a criação do Dedupeer como um componente interoperável, com facilidade de adaptação com sistemas de armazenamento na maioria das linguagens populares. Também foi explicada a técnica de análise de fingerprints, usada para a identificação de conjunto de dados idênticos, que é usada pelos algoritmos de deduplicação em geral, e as vantagens e problemas que podem ocorrer com a utilização dessa técnica. Também foi apresentada uma alterativa para identificação de conjuntos de dados iguais, que no caso do projeto proposto, não é viável ser aplicada. No próximo capítulo serão detalhados os dois algoritmos implementados, entre eles, o que é utilizado no core do componente Dedupeer. 45 5 Os algoritmos Dois algoritmos foram desenvolvidos para fazer a deduplicação no projeto, um mais simples, que carrega todo o arquivo a ser deduplicado para a memória em uma única vez, e um segundo, que faz a carga particionada do arquivo. O segundo algoritmo permite que o processamento do arquivo seja feito de forma particionada, com a carga do bytes para a memória de forma parcial. Este capítulo é dividido em duas partes, na primeira é feita a fundamentação base para o desenvolvimento dos algoritmos, e a segunda é descrito o funcionamento de cada um dos dois algoritmos, tanto uma explicação de alto nível como uma mais aprofundada na implementação. 5.1 Fundamentos Nesta Seção, serão explicados os dois principais conceitos utilizados para tornar o desempenho do processamento da deduplicação viável, o rolling checksum e o fingerprinting. Uma breve introdução sobre o percursor da análise de igualdade entre arquivos de forma remota também será discutida. 5.1.1 O algoritmo Rsync O algoritmo Rsync foi desenvolvido para otimização de transferência de arquivos entre computadores e foi utilizado primeiramente na ferramenta de mesmo nome, desenvolvido por Andrew Tridgell and Paul Mackerras [43]. Suponha que dois computadores tenham versões diferentes de um mesmo arquivo, e que o objetivo seja atualizar o arquivo mais antigo para a versão mais recente, ao invés de enviar o arquivo todo e substituir a versão mais antiga, pode-se calcular e enviar para o 46 5.1. FUNDAMENTOS outro lado apenas a os bytes diferentes entre esses arquivos. A identificação da diferença entre arquivos pode ser feita de várias formas, a maioria dos algoritmos usa a abordagem de análise de dois arquivos localmente para a identificação da redundância de conjunto de bytes entre eles, criando a partir do processamento um arquivo da diferença, que é chamada de patch. O Rsync usa uma abordagem diferente, ele faz a identificação da diferença entre os arquivos de forma remota, com isso ele identifica as partes do arquivo que precisam ser enviadas para o outro computador e quais partes precisam ser apenas referenciadas, evitando assim a transferência do arquivo todo para o destino. O algoritmo Rsync funciona da seguinte maneira [43]: • O computador β quebra o arquivo B em chunks de tamanho fixo S, não sobrepostos, sendo que o último chunk pode ter um tamanho menor que S; • Para cada chunk é calculado dois fingerprints, um fraco "rolling" de 32 bits, e um mais forte de 128 bits; • β envia um lista com os fingerprints para o computador α; • α primeiro faz a busca no arquivo A com um rolling checksum, comparando o fingerprint obtido com o de 32 bits enviado por β . Caso encontre alguma parte do arquivo que o valor calculado seja idêntico a algum fingerprint enviado por β , é feito um cálculo no mesmo bloco de dados para obter o fingerprint de 128 bits, que é comparado à lista recebida; • α envia para β as instruções necessárias para montar o arquivo idêntico ao A, definindo quais chunks de B podem ser aproveitados e enviando os dados de A que não estão em B. Rolling checksum O rolling checksum foi usado nos dois algoritmos implementados no projeto. Ele é um tipo de checksum que tem a propriedade de ser calculado de forma pouco custosa quando o cálculo do checksum da janela anterior já tiver sido feito, por exemplo, se o checksum do bloco de bytes Xk ... Xl já for conhecido, obter o do bloco Xk+1 ... Xl+1 é mais rápido [43]. Para a identificação dos dados duplicados, o Rsync usa o rolling checksum. A não utilização de uma técnica como esta, torna inviável a identificação de partes iguais dentro de um arquivo, até mesmo em arquivos que sejam backups sucessivos e que 47 5.1. FUNDAMENTOS possuam apenas uma pequena parte diferente. O problema é que qualquer alteração, seja ela uma adição, uma remoção ou qualquer outra mudança, desloca o conteúdo do arquivo, isso causa uma alteração em cadeia em todos os segmentos dos chunks posteriores, que receberão os bytes de um vizinho e fornecerá bytes para o vizinho que o sucede. 5.1.2 Fingerprinting Fingerprints é uma outra técnica que foi aplicada como base nos dois algoritmos desenvolvidos, eles são valores que identificam uma sequência específica de dados. A sua principal propriedade é a identificação da diferença entre conjuntos de dados, independente de formato, que possuam o mesmo tamanho. Ele é usado também para identificar igualdade entre conjuntos de dados, pelo fato da probabilidade dos valores serem idênticos, quando os conjuntos de dados são diferentes, ser quase zero. O fingerprint é o resultado do cálculo de uma função hash aplicada em um conjunto de dados. Na maioria das vezes os fingerprints são calculados a partir das funções hash MD5 ou SHA1 por serem funções com baixa probabilidade de colisão. [13] Para um esquema de fingerprinting é um conjunto de função n um melhor entendimento, o k F = f : ω → {0, 1} , onde ω representa o conjunto de todos os segmentos possíveis e k é o tamanho do fingerprint. Se for escolhido um conjunto S ⊂ ω de n seguimentos distintos, e for escolhido f ∈ F uniforme e randomicamente, então f (A) 6= f (B) =⇒ A 6= B P( f (A) = f (B)|A 6= B) = muitobaixa [6]. Blocos de dados de mesmo tamanho podem ter um fingerprint igual mesmo tendo conteúdos diferentes. Segundo Jeff Bonwick, líder no desenvolvimento do ZFS, a probabilidade de colisão de hashes entre dados não idênticos com a utilização de uma função hash SHA256 é de aproximadamente 2−256 = 10−77 [5]. Mesmo os fingerprints sendo uma representação muito reduzida de um segmento de dados, é preciso ter cautela com a quantidade deles que são carregados para a memória de uma só vez. Considerando o tamanho padrão dos segmentos como 8 KB de tamanho, 48 5.2. DETALHANDO OS ALGORITMOS sendo representado por um fingerprint de 160 bits, um arquivo de 8 TB teria um conjunto de fingerprints com tamanho de 20 GB [49]. 5.2 Detalhando os algoritmos O Dedupeer possui dois algoritmos para fazer a deduplicação de um arquivo. O primeiro, e mais simples, foi desenvolvido com base no princípio de comparação de conjunto sequencial de bytes do Rsync. O segundo, e mais complexo algoritmo, foi desenvolvido com uma forma de comparação diferente do primero para possibilitar indentificações de sequências de dados idênticas, sem a necessidade do arquivo estar totalmente carregado na memória principal. Nas próximas Subseções os dois algoritmos desenvolvidos serão descritos detalhadamente, tanto o funcionamento como as decisões de implementação. 5.2.1 Algoritmo para deduplicação com carga completa do arquivo na memória Esse algoritmo foi o primeiro a ser desenvolvido. Ele foi baseado na forma de identificação de igualdade de conjuntos sequenciais de bytes do Rsync, no qual é informado um valor de um função de hash aplicada à um chunk e esse valor é comparado com os valores de hash obtidos a partir da execução da função de rolling hash aplicada ao arquivo em que se deseja encontrar as partes duplicadas. Será feita uma explicação do funcionamento do algoritmo utilizando um diagrama de atividade simples em conjunto com abstrações da estrutura de dados em caixas, e em seguida, o algoritmo será explicado mais profundamente, com discussão sobre a implementação, utilizando pseudocódigo de alto nível para isso. No cenário de exemplo para fundamentar a explicação, existem dois arquivos de uma mesma música, um deles possui alguns bytes no início a mais do que o outro. Suponha que esses bytes são informações de metadados inexistentes no segundo arquivo. A música que não possui esses metadados a mais, está armazenada em um sistemas de armazenamento de dados distribuído, que possui as informações sobre cada chunk desse arquivo, como o valor do hash de 32 bits e do SHA-1. O cenário mostra o processamento que é feito pelo algoritmo antes da música ser enviada para o sistema de armazenamento distribuído. Sem um algoritmo de deduplicação integrado ao sistema de armazenamento, esse novo arquivo seria totalmente copiado para ele, e no caso do sistema possuir um 49 5.2. DETALHANDO OS ALGORITMOS algoritmo de deduplicação apenas para arquivos completos, ele também não conseguiria a compressão dos dados nesse caso, já que apesar da maior parte do conteúdo dos dois arquivos serem idênticos, esse tipo de algoritmo não consegue identificar igualdade de partes do arquivo. Antes de enviar o novo arquivo para o sistema de armazenamento, o processamento para identificar partes do arquivo que são iguais a um dos chunks já armazenados é feita. Para isso, o algoritmo exige, através da implementação da sua interface de comunicação, que o sistema forneça as informações essenciais para fazer o processamento da deduplicação, como as informações dos valores dos fingerprints dos chunks. O algoritmo então carrega o arquivo completo para a memória principal para poder fazer a análise dos bytes. A variável windowsIndex indica a posição inicial da janela do rolling checksum no arquivo. Essa janela tem o tamanho definido através de um parâmetro que é passado pelo sistema de armazenamento e que tem como objetivo informar o tamanho padrão do chunk armazenado no sistema. A comparação inicia-se com a procura pelo primeiro chunk da lista passada para o algoritmo através da interface. Como descrito na seção sobre o Rsync, o conteúdo do chunk não é necessário para a identificação de uma sequência de bytes idêntica, para isso, só é necessário o conhecimento dos valores dos fingerprints. Primeiramente, o valor do hash fraco desse chunk é procurado no conteúdo do arquivo em memória. O hash fraco é também calculado no conjunto inicial de bytes do arquivo em memória, que tem o seu início indicado a partir na posição apontada pelo windowsIndex, e vai até o final da janela. Esses dois valores são comparados, se eles forem diferentes, o byte inicial da janela é incrementado em 1 (windowsIndex++) e o cálculo do rolling hash é feito em cima do valor do hash da janela anterior. Esse procedimento é executado até que os valores dos hashes sejam iguais ou o fim do arquivo seja alcançado. No exemplo monstrado na Figura 5.1, quando o início da janela estiver no terceiro byte, o cálculo do hash será idêntico ao procurado. Quando esses valores são iguais, é feito um cálculo do SHA-1 no conjunto de bytes que está dentro da janela, e o valor é comparado com o SHA-1 do chunk armazenado no sistema. Se esses valores forem diferentes, significa que a comparação dos hashes fracos deu um falso positivo e o procedimento de busca segue com o incremento do início da janela, caso contrário, é considerado que o conteúdo que está dentro da janela é igual ao do chunk armazenado no sistema, e uma indicação de referência é criada, para que os bytes não sejam enviados para o sistema. O início da janela salta para a posição após o último byte do chunk encontrado, e a busca pelo próximo começa a partir desse ponto. 50 5.2. DETALHANDO OS ALGORITMOS Figura 5.1 Funcionamento do algoritmo 1. A análise do pseudocódigo de alto nível do algoritmo vai ser feita a seguir. O algoritmo implementado em Java está disponível na página do projeto no Github1 . Como o algoritmo foi desenvolvido para ser uma interface Thrift 4.2.1, ele precisa receber algumas variáveis que serão informadas pelo sistema de armazenamento. A estrutura esperada com as informações dos chunks é basicamente, o hash de 32 bits, o SHA-1 e o ID de cada um deles, para um maior ganho de desempenho eles precisam estar ordenados, baseado no fato de que a probabilidade de que o chunk posterior seja duplicado se o anterior for é grande. Vale ressaltar que nem todos os chunks armazenados no sistemna precisam ser passados para o algoritmo, é recomendável que somente os pertencentes à arquivos que tem probabilidade de serem similares sejam informados, como por exemplo, os arquivos que são do mesmo tipo e que possuam um nome similar com o que vai ser processado à procura de partes duplicadas. O outro parâmetro esperado é uma string representando o caminho absoluto para o 1 https://github.com/paulofernando/dedupeer 51 5.2. DETALHANDO OS ALGORITMOS arquivo no sistema de arquivos local, para que o algoritmo possa ler os seus bytes e fazer o processamento com esses dados. Um objeto map<position,Chunk> é retornado, onde position é referente à posição do byte inicial do conjunto sequencial de dados que formam o chunk, e o valor no map é referente à informações sobre o chunk, como, o identificador do chunk que está armazenado no sistema distribuído e que foi encontrado como duplicado, ou, no caso de não ter encontrado nenhum idêntico, as informações armazenadas são as necessárias para que o sistema consiga identificar o conjunto sequencial que não está duplicado, como posição inicial e final dentro do arquivo, e os valores calculados dos hashes desse novo chunk, para que o dados dele sejam enviados para armazenamento. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: input: informações dos chunks armazenados e path do arquivo para ser deduplicado output: apontador para os chunks duplicados no arquivo informado for i = 0 to amountChunks do index ← searchDeduplication(modFile, chunk[i].hash32) if index <> -1 then if window.sha1 = chunk[i].sha1 then chunkRe f erences ←< index, chunkIn f o > end if end if end for index ← 0 bu f f er ← allocate(de f aultChunkSize) while index < modFile.length do if chunkReferences contains index then if buffer position > 0 then chunkRe f erences ←< index, newChunkIn f o > bu f f er.clear() end if index ← index + newChunk.length else if buffer is full then chunkRe f erences ←< index, newChunkIn f o > bu f f er.clear() else bu f f er.put(nextByteInT heWindow) index ← index + 1 52 5.2. DETALHANDO OS ALGORITMOS 27: 28: 29: 30: 31: 32: 33: 34: end if end if end while if buffer position > 0 then chunkRe f erences ←< index, newChunkIn f o > bu f f er.clear() end if returnchunkRe f erences Na linha 3, o algoritmo executa uma iteração com todas as informações dos chunks recebidas do sistema de armazenamento para serem procurados no arquivo passado como parâmetro, arquivo esse previamente carregado na variável modFile. A variável index que aparece pela primeira vez na linha 4, representa a posição incial do chunk duplicado no arquivo local, essa identificação é feita através do método searchDeduplication, que nada mais é do que um método que utiliza o rolling checksum para identificar um conjunto de dados com o mesmo hash fraco do chunk atual da iteração. Quando o valor do index for diferente de -1, será porque o método encontrou um conjunto de dados que possui o mesmo valor de hash fraco do chunk atual da iteração, mas como já explicado anteriormente, o hash fraco é só uma estratégia para economizar cálculos de um SHA-1, pois ele é muito mais rápido de ser calculado, mas em contrapartida, a probabilidade de colisão é alta, logo, para termos uma probabilidade de quase 100% que os dados são idênticos, é feita uma comparação com o SHA-1. No caso dos valores do SHA-1 serem iguais, é considerado que os chunks também são iguais, apesar de existir uma mínima probabilidade de não serem. Essa problemática é discutida na seção 4.2.3. A partir da linha 12 o algoritmo funcionará com o objetivo de identificar as partes do arquivos que não estão duplicados, e adicionar os mesmo na variável que será retornada para o sistema de armazenamento, à qual conterá as referências para os chunks que estão no sistema, identificação essa que foi executada entre as linhas 3 e 10 do algoritmo. Nessa linha, a variável buffer é criada com o tamanho de bytes igual ao padrão do sistema. O buffer serve para armazenar os bytes iniciais que são descartados da janela toda vez que o conjunto Xk ...Xl de bytes, que é o conteúdo dentro dela, não tenha seu fingerprint no map passado pelo sistema de armazenamento. Como o próximo conjunto a ser procurado é o Xk+1 ... Xl+1 , o primeiro byte que estava no conjunto de dados anterior é descartado da nova verificação, com isso ele entra no buffer para se tornar parte de um novo chunk. Na linha 14, é verificado se o conjunto de dados com início na posição atual do índice foi encontrado duplicado no sistema na busca feita anteriormente, caso a condição seja 53 5.2. DETALHANDO OS ALGORITMOS verdadeira, é verificado se o buffer possui algum dado, casa haja, é criado um novo chunk com esses bytes alocados nele e a posição do índice é incrementada com o tamanho padrão do chunk. Se a condição da linha 14 resultar em falso, é feito um teste se o buffer ainda pode receber dados, se ele já estiver cheio, é criado um novo chunk com esses dados e em seguida é feita uma limpeza no buffer. Se o buffer não tiver atingido o limite máximo de alocação, o byte atual é armazenado nele e a posição do índice é incrementado em 1, que foi a quantidade de bytes adicionados. A linha 30 é necessária, porque o final do arquivo pode ser atingido antes do buffer estar totalmente cheio, a linha 31 cria o chunk final com o conteúdo armazenado no buffer e o coloca na estrutura que vai ser retornada para o sistema de armazenamento na linha 34. 5.2.2 Algoritmo com deduplicação particionada Para que o Dedupeer fosse capaz de fazer deduplicação de arquivos sem um limite de tamanho, foi desenvolvido um segundo algoritmo que tem como princípio base a capacidade de processar esse tipo de arquivo, para isso, ele utiliza uma técnica de particionamento em tamanhos que seja possível a alocação na memória principal. Como na descrição do algoritmo anterior, será feita uma explicação de alto nível com o mesmo cenário do primeiro algoritmo descrito em 5.2.1, em seguida, o algoritmo será detalhadamente explicado através do pseudocódigo. Esse algoritmo implementado em Java também pode ser encontrada na página do projeto no Github. O processamento feito antes do envio do arquivo, para identificar as partes iguais com os chunks no sistema, exige que as informações dos chunks estejam estruturadas de uma forma diferente do primeiro algoritmo. Como entrada, a interface espera por informações dos chunks que estão armazenados no sistema para que o algoritmo os procure no arquivo a ser deduplicado, como no anterior, mas a estrutura de dados definida que organiza essas informações é um map< weakHash, map<strongHash,chunkIDs> >, pois existe uma probabilidade real de que dois chunks com conteúdo diferente tenham o mesmo valor de um hash de 32 bits, que é conhecido como colisão de hash. Foi criado um outro map interno que armazena os SHA-1 como chave, para que as informações de chunks de mesmo hash fraco não sejam perdidas por sobrescrita de valor no map e tem como valor um objeto que possui dois IDs, o do chunk e o do arquivo. A interface também espera o caminho absoluto do arquivo a ser deduplicado, e um parâmetro que é a quantidade máxima de bytes do arquivo que pode ser carregada para a memória por vez. Primeiramente, o algoritmo recebe o map com as informações dos chunks armazena- 54 5.2. DETALHANDO OS ALGORITMOS Figura 5.2 Funcionamento do algoritmo 2 dos no sistema. Em seguida, a quantidade de bytes máxima informada pelo sistema como parâmetro da interface Thrift é carrega para a memória principal, ou o arquivo completo se ele for menor que a quantidade máxima de bytes que pode ser carregada por vez. Veja a 5.2. Diferente do primeiro algoritmo, este utiliza uma estratégia de comparação diferente do Rsync, e que o torna capaz de fazer o processamento de arquivos em partes. O Rsync procura um determinado conjunto de dados que tenha um fingerprint específico, e pra isso ele precisa percorrer o arquivo até que o encontre, ou até que alcance o final do mesmo. O algoritmo para deduplicação particionada aproveita o complexidade temporal (tempo de execução) da estrutura de map (hashtable), a qual é O(1), e ao invés de percorrer o conteúdo de um arquivo à procura de um fingerprint, ele calcula o fingerprint dos dados na janela do arquivo em memória e em seguida faz uma busca no map, ou seja, diferente do Rsync a comparação é feita de forma inversa. Se o hash de 32 bits dos dados dentro da janela estiver na estrutura de map, é feito o cálculo do SHA-1 dos mesmos dados e essa valor é procurado dentro do map interno que é o valor da chave de hash 32 bits encontrada. Se o hash de 32 bits ou o SHA-1 55 5.2. DETALHANDO OS ALGORITMOS não forem encontrados, é feito um teste para saber se a quantidade de dados restante na parte carregada para a memória é menor que o tamanho padrão do chunk, se for menor, é criado um chunk com o conteúdo do buffer e outro com o conteúdo restante, e uma outra parte do arquivo é carregado para a memória. Isso é feito porque para calcular o valor do hash de 32 bits da função de rolling checksum, é necessário ter uma quantidade de dados com o tamanho padrão exato do chunk do sistema. Se o tamanho restante for maior, o processamento segue normalmente, com a janela sendo incrementada em 1 byte por vez. A seguir vai ser feita uma explicação mais aprofundada sobre o algoritmo através da análise do pseudocódigo de alto nível. O input já foi descrito acima, e o output é o mesmo do primeiro algoritmo, uma referência para os chunks duplicados dentro do arquivo e as informações sobre os novos chunks. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: input: informações dos chunks armazenados, path do arquivo para ser deduplicado e quantidade de bytes para carregar para a memória por vez output: apontador para os chunks duplicados no arquivo informado o f f set ← 0 validate(bytesToLoadByTime) divideInTime ← f ileLength/bytesToLoadByTime bu f f er ← allocate(de f aultChunkSize) for i = 0 to divideInTime do localIndex ← 0 partO f File ← getNextBlockO f File(o f f set) currentChunk ← getNextChunkPartO f File(localIndex) while localIndex < partOfFile.length do if partOfFile.length - localIndex + moreBytesToLoad = defaultChunkSize then partO f File ← getNextBlockO f File(o f f set) currentChunk ← getNextChunkPartO f File(localIndex) o f f set ← o f f set + moreBytesToLoad end if if chunk[i].hash32 contains window.hash32 then if currentChunk = null then currentChunk ← getNextChunkPartO f File(localIndex) end if if chunk[i].sha1 contains window.sha1 then if buffer has data then newChunk ← createNewChunk(bu f f er) 56 5.2. DETALHANDO OS ALGORITMOS 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: chunkRe f erences ←< globalIndex−newChunk.length, newChunk.in f o > bu f f er.clear() end if chunkRe f erences ←< globalIndex, re f erenceToT heChunk > globalIndex ← globalIndex + currentChunk.length localIndex ← localIndex + currentChunk.length currentChunk ← getNextChunkPartO f File(localIndex) end if end if if not duplicate then currentChunk ← null if buffer is full then newChunk ← createNewChunk(bu f f er) chunkRe f erences ←< globalIndex − bu f f er.position, newChunk.in f o > bu f f er.clear() else if partOfFile.length - (localIndex + defaultChunkSize) > 0 then bu f f er.put(partO f File[localIndex]) globalIndex ← globalIndex + 1 localIndex ← localIndex + 1 else newChunk ← createNewChunk(partO f File, localIndex−bu f f er.position) 46: chunkRe f erences ←< globalIndex−bu f f er.position, newChunk.in f o > 47: localIndex ← localIndex + newChunk.length − bu f f er.position globalIndex ← globalIndex + newChunk.length − bu f f er.position bu f f er.clear() if localIndex < partOfFile.length then newChunk ← createNewChunk(partO f File, localIndex) chunkRe f erences ←< globalIndex, newChunk.in f o > bu f f er.clear() localIndex ← localIndex + newChunk.length 48: 49: 50: 51: 52: 53: 54: 57 5.2. DETALHANDO OS ALGORITMOS 55: 56: 57: 58: 59: 60: 61: 62: 63: globalIndex ← globalIndex + newChunk.length end if end if end if end if end while o f f set ← o f f set + bytesToLoadByTime end for returnchunkRe f erences O método validate, chamado na linha 4, verifica se o valor passado pelo sistema para informar a quantidade de bytes para ser carregado por vez na memória é maior que o tamanho do arquivo, se esse valor for maior, ele é configurado com o tamanho do arquivo, caso contrário, é feita uma aproximação para o múltiplo do tamanho padrão do chunk que seja mais próximo do valor informado, para que a probabilidade de um chunk que possua um idêntico armazenado no sistema não seja dividido entre duas cargas diferentes para a memória, fato que se ocorresse, impossibilitaria o algoritmo de identificar a igualdade. A linha 5 calcula a quantidade de cargas que precisarão ser feitas para a memória, valor este utilizado como quantidade de iterações que a estrutura de repetição da linha 7 executará. O método getNextBlockOfFile, utilizado na linha 9, retorna um array de bytes do arquivo que será deduplicado a partir da posição definida como parâmetro, esse array de bytes é o conteúdo que é carregado para a memória por iteração. A partir desse array, é criado o primeiro chunk do arquivo que está na memória para análise de duplicata. Na linha 11 é iniciada a iteração na parte do arquivo carregada em memória para análise. A condicional da linha 12 é uma estratégia para não perder a identificação de um chunk duplicado quando este está no final da parte do arquivo que foi carregado para a memória, e tiver sido encontrado grande parte da carga como idêntica à de outro arquivo já no sistema. Basicamente, esta parte do algoritmo serve para deixar o último chunk da carga com o tamanho padrão do sistema, e assim, possibilitar a identificação de igualdade. Observando o exemplo da 5.3, percebe-se que sem a carga extra de bytes, o chunk representado pelos 4 hexadecimais 7C CE 72 A4, mesmo sendo duplicado, não será identificado como tal pelo algoritmo sem a carga extra de dados. Este parece ser um problema pequeno, mas não é. Supondo que todo conteúdo das cargas posteriores do arquivo já estejam armazenadas no sistema por fazer parte de uma versão anterior do mesmo arquivo, sem a carga extra de bytes, será deixado de deduplicar um chunk por carga, pois parte dele estará no final de uma carga e a outra estará no 58 5.2. DETALHANDO OS ALGORITMOS Figura 5.3 Vantagem da utilização da carga extra de dados começo da posterior. Essa parte que ficará no começo da outra carga, sempre deixará o último chunk sem todos os bytes necessários para a identificação da deduplicação na mesma carga, acarretando assim uma perda de deduplicação para cada iteração à frente. Na linha 17 é verificado se o map possui uma chave com o hash fraco calculado da janela atual. A linha 18 é para evitar recarga de bytes na memória. Em seguida é verificado se o map que está como valor da chave encontrada possui o SHA-1 que representa os bytes dentro da janela, se esse valor for encontrado, é verificado se o buffer está com algum dado, se sim, esses dados são transformados em um novo chunk e na linha 27 a referência do chunk duplicado é salva na estrutura. Na linha 35 é feita uma verificação para saber se o buffer está cheio, caso esteja, todo o seu conteúdo é transformado em um único chunk e o mesmo é esvaziado para poder receber os bytes iniciais da janela quando a mesma estiver em uma posição em que o conjunto de bytes dentro dela não seja encontrado armazenado no sistema. Se o buffer ainda possuir espaço vazio, é verificado se o restante do conteúdo carregado para a memória que ainda não foi processado é maior que o tamanho padrão do chunk, caso seja, o byte inicial da janela é adicionado ao buffer, já que não foi encontrado o fingerprint do conteúdo da janela nos dados passados pelo sistema, e o índice do início da janela é incrementado em 1. Se a quantidade de bytes não processados for menor que o tamanho padrão, é criado um chunk com os dados dentro do buffer e um outro com o restante dos bytes. Por fim, na linha 61 o offset para a carga dos dados no arquivo local é incrementado 59 5.3. SUMÁRIO DO CAPÍTULO com a quantidade de bytes que foram carregados na última vez. E na 63 é onde está localizado o comando de retorno da variável com todos os novos chunks e referências aos já armazenados, que juntos formam o arquivo local. 5.3 Sumário do capítulo Neste capítulo, inicialmente foi feita uma introdução sobre o funcionamento do Rsync, que é um algoritmo para otimizar a transferência de arquivos entre computadores remotos e que serve como base de grande parte dos algoritmos de deduplicação de arquivos. Em seguida, foram discutidos os principais conceitos por trás da eficiência da deduplicação, o rolling checksum e o fingerprinting. Depois da introdução dos conceitos fundamentais para o desenvolvimento dos dois algoritmos que foram implementados no projeto, foi feita uma análise bem detalhada do funcionamento do primeiro deles, que é o algoritmo para deduplicação com carga completa do arquivo na memória, o qual utiliza uma técnica muito similar ao Rsync. Foi apresentado um diagrama sobre o algoritmo, e o seu funcionamento explicado. Depois da apresentação do funcionamento, o pseudocódigo de alto nível do algoritmo foi explicado em detalhes. Por fim, foi feita a explicação do segundo algoritmo desenvolvido, o algoritmo com deduplicação particionada, o qual seguiu a mesma estrutura da primeira explicação. Primeiro, foi feita uma análise detalhada do funcionamento do algoritmo através de um diagrama, e depois, apresentado e explicado o pseudocódigo de alto nível. No capítulo a seguir, será apresentada uma analise de desempenho, tanto de tempo como de compressão, para o algoritmo com deduplicação particionada que foi apresentado neste Capítulo. 60 6 Análise de desempenho Neste capítulo é feita uma análise do desempenho do Dedupeer integrado com o Dedupeer File Storage. Será feita uma análise para identificação do melhor algoritmo de hashing para o sistema e um estudo sobre as taxas de compressão alcançadas dentro de um conjunto de tamanho de chunks para identificar a eficiência da economia proporcionada pelo algoritmo de deduplicação desenvolvido. Serão feitas avaliações no tempo de armazenamento, deduplicação, deduplicação juntamente com o armazenamento, e reidratação, que é a remontagem do arquivo original através dos chunks armazenados e deduplicados. 6.1 Datasets Para comparar sistemas de deduplicação de dados diferentes é preciso possuir o mesmo conjunto de dados, pois a recriação de datasets a partir da mesma porcentagem de modificação não funcionam com deduplicação. As modificações precisam estar exatamente localizada na mesma posição ou a avaliação será falha, logo, os arquivos testados devem ser os mesmos. O trabalho [41] trata justamente sobre quais datasets devem ser usados para fazer avaliações em sistemas de deduplicação de dados. Nele foram analisados 33 artigos sobre deduplicação publicados entre os anos de 2000 e 2011 nas mais relevantes conferências sobre storage do mundo, entre elas: Usenix ATC, Usenix FAST, SYSTOR e IEEE MSST. Foi feita uma categorização dos 120 datasets utilizados nesses artigos, os quais ficaram divididos em: 1. Datasets privados, não acessível para qualquer indivíduo; 2. Datasets públicos mas difíceis de serem reproduzidos; 61 6.1. DATASETS 3. Datasets criados sinteticamente; 4. Datasets públicos facilmente reproduzível. Dos 33 artigos avaliados, 29 deles utilizaram pelo menos um dataset privado, logo não pode ser feita uma comparação entre os sistemas analisados nesses artigos e outro que não possa ser avaliado com esses dados privados. Os 4 restantes utilizam conjuntos de dados sinteticamente criados os quais não podem ser reproduzidos pois algumas informações sobre a sua criação foram omitidos. Baseado na categorização citada acima, os datasets são: 64 privados (53%), 17 são públicos e difíceis de reproduzir (14%), 11 são sintetizados com detalhes da sua geração omitidos (9%). O que dá um total de 76% de datasets inutilizáveis para comparação entre sistemas. Os 24% que são públicos são considerados muito pequenos para uma comparação ou são arquivos de máquinas virtuais de tipos muito variados. Os dados utilizados nas análises de desempenho do Dedupeer e do Dedupeer File Storage foram arquivos de máquinas virtuais e de código-fonte armazenados em um arquivo tar. Para a análise do tempo gasto nas principais operações foram utilizadas as seguintes versões do Kernel do Linux, os quais podem ser encontrados nesse endereço: https://www.kernel.org/. 1. stable: 3.9.8 datado de 27 de junho de 2013 (Latest Stable Kernel). 2. mainline: 3.10-rc7 datado de 22 de junho de 2013 A versão 3.9.8 (stable) foi utilizada como base para a deduplicação da versão 3.10-rc7 (mainline). Para a avaliação da taxa de compressão alcançada com o algoritmo desenvolvido, foram utilizados dados com modificações de conteúdo mais complexas e maiores dos que as obtidas com o Kernel do Linux. Para essa avaliação foram utilizadas duas imagens de máquinas virtuais, que são simulações de computadores reais através de software, com Ubuntu 12.10 instalados. Em uma delas o Ubuntu foi instalado sem atualizações, essa é a máquina virtual que será usada como arquivo base para deduplicação. A segunda máquina virtual é a primeira máquina com uma atualização. O sistema operacional da máquina virtual foi atualizado com todas as atualizações disponíveis até o dia 2 de junho de 2013 às 13:17. 356 arquivos foram baixados, totalizando 330,3MB de dados, os quais após a instalação ocuparam 1350MB a mais do que a máquina virtual antes da atualização. A escolha de dados públicos e de fácil acesso possibilitará futuras comparações com trabalhos relacionados. 62 6.2. ANÁLISE DE DESEMPENHO UTILIZANDO O CÓDIGO FONTE DO KERNEL DO LINUX 6.2 Análise de desempenho utilizando o código fonte do Kernel do Linux Esta primeira análise será focada no desempenho para deduplicação do Dedupeer, e do armazenamento e reidratação do Dedupeer File Storage. Foram feitas 14 execuções de cada uma das principais operações, armazenamento, deduplicação, deduplicação + armazenamento e reidratação, cada uma delas para 128KB, 64KB, 32KB, 16KB e 8KB. Esses valores foram escolhidos por serem as potências de dois próximas ao tamanho recomendado por alguns sistemas de deduplicação, 8KB e 16KB, e o tamanho utilizado no Ustore, o sistema onde o Dedupeer foi implantado, que é 128KB. O computador utilizado para a execução da análise de desempenho foi um Windows 7 Profissional com Service Pack 1 de 32 bits com processador Intel Core 2 Quad Q6600 2.40GHz e 3,24GB de memória RAM DDR2. Para a operação de armazenamento foi enviado um arquivo .tar com todo o códigofonte do Kernel do Linux na versão 3.9.8 (513.945.600 bytes) e o tempo até a conclusão da operaçao foi registrado. A utilização do formato tar é muito importante para fazer a deduplicação dos arquivos pois nenhuma compressão é aplicada aos arquivos que são armazenados nele, diferente do zip por exemplo, que modifica o conteúdo dos arquivos e isso degrada a taxa de compressão obtida com a deduplicação pelo fato do conteúdo dos arquivos serem transformados, o que impossibilita a descoberta de conjunto de dados idênticos. Foi registrado também o tempo do início da operação de deduplicação até o término, e também o tempo do início da deduplicação até o término do armazenamento do arquivo deduplicado. Para essas operações foi utilizado o arquivo .tar com todo o código fonte do Kernel do Linux na versão posterior à 3.9.8 que estava disponível no site, que foi a versão 3.10-rc7 (521.420.800 bytes). Por último, foi registrado o tempo gasto para fazer a reidratação do arquivo deduplicado. Todos os dados capturados na avaliação estão disponibilizados no formato do Mathematica 91 no apêndice B.1. Operação de armazenamento As Figuras 6.1 e 6.2 mostrarão todos os tempos em milissegundos capturados das 14 execuções da operação de armazenamento do Dedupeer File Storage para os algoritmos 1 http://www.wolfram.com/mathematica/ 63 6.2. ANÁLISE DE DESEMPENHO UTILIZANDO O CÓDIGO FONTE DO KERNEL DO LINUX de hashing MD5 e SHA-1 para cada tamanho de chunk analisado, os quais foram para todas as operações: 128KB, 64KB, 32KB, 16KB e 8KB. Os dados foram plotados no gráficos na mesma ordem em que foram capturados. Figura 6.1 Tempo em milissegundos para as execuções da operação de armazenamento utilizando MD5 Figura 6.2 Tempo em milissegundos para as execuções da operação de armazenamento utilizando SHA-1 Para uma melhor comparação entre o tempo gasto no armazenamento, o gráfico 64 6.2. ANÁLISE DE DESEMPENHO UTILIZANDO O CÓDIGO FONTE DO KERNEL DO LINUX na Figura 6.3 mostrará o emparelhamento dos dados ordenados entre os algoritmo de hashing usados para cada tamanho de chunk separado. A partir dos gráficos, percebe-se que os tempos para cada operação de armazenamento são similares independente do algoritmo de hashing utilizado. Para uma melhor visualização, a comparação do tempo para cada tamanho de chunk separado está no apêndice C.1. Figura 6.3 Tempo em milissegundos para as execuções da operação de armazenamento, separadas por tamanho de chunk A seguir será feito um teste estatístico para descobrir se a utilização do SHA-1 tem o mesmo desempenho do MD5. Como explicado anteriormente, o MD5 é utilizado em alguns dos sistemas que fazem deduplicação de dados, e que apesar da probabilidade de colisão de hashes existir tanto no MD5 como no SHA-1, já se tem registro de colisão de hashes com MD5 mas ainda nenhum registro desse problema com o SHA-1. Se os dois algoritmos tiverem um desempenho similar ou se o desempenho do SHA-1 for inferior ao MD5 mas não à ponto de anular a vantagem da falta de registro com colisão de hashes, não há motivos para não escolher o SHA-1 como algoritmo de hashing padrão para o Dedupeer File Storage. Todos os valores de p podem ser encontrados na Tabela 6.1, esse é o valor utilizado para identificar se os dados capturados seguem a curva normal, e com isso saber qual 65 6.2. ANÁLISE DE DESEMPENHO UTILIZANDO O CÓDIGO FONTE DO KERNEL DO LINUX p-values MD5 SHA-1 128KB 0,674511 0,903653 64KB 0,621354 0,868695 32KB 0,198659 0,258855 16KB 0,0330624 0,44823 8KB 0,293449 0,0219761 Tabela 6.1 p-values para os dados capturados na operação de armazenamento método estatístico utilizar. Todos os valores vão ser exibidos na tabela para caso alguém queira fazer teste com outro tamanho de chunk. Na Tabela 6.1 serão mostrados os valores de p dos dados da operação de armazenamento. O intervalo de confiança adotado para os testes foi de 95% ou seja, caso o valor de p seja maior que o nível de significância, que é 0,05, o T-Test pode ser selecionado, caso contrário, a análise será feita através de um teste não-paramétrico, já que o tamanho da amostra é inferior à 40 e o T-Test só deve ser feito com dados não normais se a amostra tiver tamanho superiores à 40. Para analisar qual o algoritmo se comporta melhor na operação de armazenamento, vai ser feito um T-Test emparelhado com as amostras coletadas com 32KB, pois todos os dados são normais, o que torna a análise mais fácil. Como hipótese nula, será considerado que a média de tempo gasto para duas amostras são iguais, e como hipótese alternativa será considerado que a média de tempo gasto para a operação de armazenamento com MD5 é maior do que com SHA-1. H0 : µ0 − µ1 = 0 vs. H1 : µ0 > µ1 Aplicando o T-Test emparelhado, resultou em um p-value 0,695936. Como o p-value é maior do que 0,05, não devemos rejeitar a hipótese nula, logo, não podemos afirmar que a média de tempo com MD5 é maior do que com SHA-1. Operação de deduplicação Os dados obtidos a partir das 14 execuções de deduplicação estão plotados nos Gráficos 6.4 e 6.5. Com a análise desses dados é possível avaliar apenas o desempenho do algoritmo de deduplicação desenvolvido e utilizado no Dedupeeer File Storage, pois os dados registrados nessas execuções foram o tempo necessário para o algoritmo buscar os chunks dentro do arquivo a ser enviado para armazenamento. Logo, os valores plotados nesse gráfico se referem ao tempo entre o início e o fim da deduplicação apenas, o qual engloba os seguintes passos: a busca da informações dos chunks armazenados no sistema, a montagem da estrutura de dados para comunicação com a biblioteca através do Thrift, a procura dos chunks dentro do arquivo pela biblioteca Dedupeer, a criação da estrutura 66 6.2. ANÁLISE DE DESEMPENHO UTILIZANDO O CÓDIGO FONTE DO KERNEL DO LINUX de dados com o mapeamento dos chunks e das referências aos chunks duplicados, e o retorno da estrutura através do Thrift para o Dedupeer File Storage. Figura 6.4 Tempo em milissegundos das execuções da operação de deduplicação, separadas por tamanho de chunk, utilizando SHA-1 Figura 6.5 Tempo em milissegundos das execuções da operação de deduplicação, separadas por tamanho de chunk, utilizando MD5 Da mesma forma que na operação anterior, a comparação do tempo para cada tamanho de chunk está no apêndice, na seção C.2. Analisando os dados obtidos das execuções de deduplicação, percebe-se que o algoritmo de deduplicação utilizando o SHA-1 obteve um desempenho superior ao mesmo 67 6.2. ANÁLISE DE DESEMPENHO UTILIZANDO O CÓDIGO FONTE DO KERNEL DO LINUX algoritmo utilizando MD5. A média de tempo foi bem significativa, para 128KB o SHA-1 obteve uma média de 36553 milissegundos para executar a operação, enquanto que com MD5 o tempo médio foi de 133255 milissegundos. Com chunks de 8KB de tamanho a diferença entre as médias foi menor, o tempo para o MD5 (227582 ms) foi menos de duas vezes maior do que o do SHA-1 (118409 ms). Apesar da média do tempo ser significativamente maior utilizando MD5 em relação ao SHA-1, é preciso fazer a análise estatística para confirmar se com esses dados é possível saber qual o algoritmo de hashing torna a deduplicação mais eficiente. Para analisar qual o algoritmo se comporta melhor na operação de deduplicação, o T-Test emparelhado foi feito para as amostras de 16KB. Da mesma forma que o teste anterior, como hipótese nula será considerado que a média de tempo gasto para deduplicação é igual entre as amostras, e como hipóteses alternativa será considerado que a média de tempo com MD5 é maior do que com SHA-1. H0 : µ0 − µ1 = 0 vs. H1 : µ0 > µ1 Ao aplicar o T-Test emparelhado, o p-value foi 1,03839x10−10 . Como o p-value é menor do que 0,05, devemos rejeitar a hipótese nula e aceitar a alternativa. Como a hipótese alternativa deve ser aceita, é concluído que a média de tempo gasta com MD5 para deduplicação é realmente maior do que o tempo gasto com SHA-1, ou seja, para a operação de deduplicação o SHA-1 foi mais eficiente que o MD5. Operação de deduplicação + armazenamento Nesta seção será mostrado os dados da operação de deduplicação somada com a operação de armazenamento, que representa a operação completa de deduplicação de um arquivo no sistema de armazenamento. Nesta operação, os dados capturados também apontam uma vantagem significativa para o SHA-1, esses dados podem ser vistos plotados em gráficos separados por algoritmo de hashing nas Figuras 6.6 e 6.7. A média do tempo calculado para o SHA-1 utilizando chunks de 128KB foi 36552.9 milissegundos, enquanto a médio do MD5 para 128KB foi 249779 milissegundos, ou seja, com o SHA-1 a operação foi em média 6,8 vezes mais rápida que com MD5. Para chunks de 8KB o SHA-1 teve média de 118409 milissegundos, e o MD5 de 1213682 milissegundos. Com chunks de 8KB o SHA-1 foi em média 10,2 vezes mais rápido. Os gráficos emparelhados dessa operação separados por tamanho de chunk também estão no Apêndice, especificamente nessa seção C.3. 68 6.2. ANÁLISE DE DESEMPENHO UTILIZANDO O CÓDIGO FONTE DO KERNEL DO LINUX A hipótese nula foi definida como a média de tempo gasto para deduplicação + armazenamento sendo igual tanto para o MD5 como SHA-1, e como hipótese alternativa considerando que o tempo com MD5 é diferente com SHA-1. H0 : µ0 − µ1 = 0 vs. H1 : µ0 6= µ1 Como não tem duas amostras normais para serem comparadas, nesse caso será feita a análise a partir do Wilcoxon signed-rank test. O p-value resultante foi 0,00109705, com isso a hipóteses nula deve ser rejeitada e concluímos que o tempo médio entre eles foi diferente. Figura 6.6 Tempo em milissegundos das execuções da operação de deduplicação + armazenamento, separadas por tamanho de chunk, utilizando SHA-1 69 6.2. ANÁLISE DE DESEMPENHO UTILIZANDO O CÓDIGO FONTE DO KERNEL DO LINUX Figura 6.7 Tempo em milissegundos das execuções da operação de deduplicação + armazenamento, separadas por tamanho de chunk, utilizando MD5 Operação de reidratação Para a operação de reidratação também foram feitas 14 execuções para cada um dos algoritmos de hashing SHA-1 e MD5. Nas operações de deduplicação os resultados foram diferentes das duas operações anteriores, nesta operação o algoritmo de MD5 foi mais rápido do que o SHA-1. Comparando as médias do maior e menor tamanho de chunk testado, para o chunk de 128KB o tempo médio de execução da operação de reidratação com MD5 foi de 43087 milissegundos, enquanto o tempo médio do SHA-1 foi 68590,6 milissegundos. Para o chunk de 8KB o MD5 foi pouco mais de 5 vezes mais rápido, tendo média de 226346 milissegundos contra 1166748 milissegundos do SHA-1. Todos os dados das execuções dessa operação podem ser vistos nas Figuras 6.8 e 6.9, e os dados emparelhados por tamanho de chunk estão no Apêndice C.4. 70 6.2. ANÁLISE DE DESEMPENHO UTILIZANDO O CÓDIGO FONTE DO KERNEL DO LINUX Figura 6.8 Tempo em milissegundos das execuções da operação de reidratação, separadas por tamanho de chunk, utilizando SHA-1 Figura 6.9 Tempo em milissegundos das execuções da operação de reidratação, separadas por tamanho de chunk, utilizando MD5 Para avaliar qual o melhor algoritmo para essa operação, foi definido o teste de hipótese, onde a hipótese nula diz que a média de tempo gasto para a reidratação com MD5 é igual à com SHA-1, e como hipóteses alternativa que a média de tempo com MD5 é menor do que com SHA-1. H0 : µ0 − µ1 = 0 vs. H1 : µ0 < µ1 71 6.2. ANÁLISE DE DESEMPENHO UTILIZANDO O CÓDIGO FONTE DO KERNEL DO LINUX Aplicando o T-Test emparelhado, o p-value foi 6,34527x10−17 . Como o p-value é menor do que 0,05, devemos rejeitar a hipótese nula e aceitar a alternativa. Logo, como previsto, é concluído que a média de tempo gasto com MD5 para reidratação é menor do que o tempo gasto com SHA-1, ou seja, para a operação de reidratação o MD5 foi mais eficiente que o SHA-1. 6.2.1 Mapa de chunks deduplicados Foi desenvolvida uma funcionalidade no Dedupeer File Storage que exibe, em uma barra horizontal, as áreas do arquivo que foram economizadas com a deduplicação aplicada nele. A barra possui duas cores, a azul representa as áreas do arquivo onde o seu conteúdo binário foi salvo no sistema de armazenamento pelo fato de não ter sido encontrado partes duplicadas já armazenadas, e a área pintada de cinza representa as partes do arquivo que foram economizadas no armazenamento, os dados pertencentes à essas áreas foram encontrados como já presentes nos chunks armazenados no sistema e só as referências à eles foram salvas. Figura 6.10 Mapa de chunks deduplicados da versão 3.10-rc7 do código-fonte do Kernel do Linux processado com os chunks da versão 3.9.8, para chunks com tamanho 128, 64, 32, 16 e 8KB Este mapa apresentado pode ocultar algumas áreas, pois como as áreas pintadas são proporcionais e a quantidade de chunks do arquivo deduplicado e apresentado na Figura 6.10 foi de 66318 na versão de 8KB, chunks deduplicados de forma isoladas podem não ser exibidos pois a área ocupada por eles pode não ser suficiente para que seja exibida na barra de modificações. Como esperado, a deduplicação com chunk de 128KB foi a que menos economizou espaço, já que para que um chunk fosse identificado como duplicado, seria necessário encontrar uma sequência de dados binários com tamanho de 128KB que pertencesse à versão de 3.9.8. Para a versão de 128KB o arquivo foi dividido em 4025 chunks dos quais 119 foram deduplicados, o que dá uma economia de aproximadamente 3%. Para 64KB foram 72 6.3. ANÁLISE DE ECONOMIA UTILIZANDO MÁQUINAS VIRTUAIS Chunks 128KB 64KB 32KB 16KB 8KB VDI armazenado 24337 48674 97347 194694 389387 VDI atualizado deduplicado 35571 71280 142498 284218 566985 VDI atualizado armazenado 35137 70274 140547 281094 562187 Tabela 6.2 Quantidade de chunks criados criados 8088 chunks dos quais 528 foram deduplicados, resultando em um total de 6,5% de economia. Com 32KB o arquivo foi dividido em 16351 pedaços dos quais 2378 foram deduplicados, mais de 11% do total. Para 16KB, o arquivo foi dividido em 32995 partes das quais 8870 foram deduplicados, resultando em 25% de economia. E por fim, pra chunks de 8KB, dos 66318 partes, 27989 foram deduplicados, o que acarretou em uma economia de 41%. Como os arquivos de código-fonte são pequenos, geralmente menores do que os tamanhos de chunk adotados, qualquer modificação em um deles faz com que nenhuma parte do mesmo seja deduplicada, por isso, a deduplicação desse tipo de arquivo não é tão eficiente. Para uma deduplicação mais eficiente, o ideal seria que o tamanho dos arquivos que foram agrupados dentro do tar fosse maior do que o tamanho padrão do chunk. 6.3 Análise de economia utilizando máquinas virtuais Essa segunda análise foi basicamente para avaliar a economiza que pode ser alcançada através do algoritmo com a deduplicação de máquinas. Foram utilizados dois arquivos .vdi (Virtual Disk Image), um deles com o Ubuntu versão 10.12 sem qualquer alteração e o segundo com o mesmo sistema operacional só que com uma atualização, como descrito na seção 6.1. Primeiramente, o arquivo vdi sem a atualização do Linux, arquivo esse com tamanho 3.189.854.208 bytes, foi enviado para o Dedupeer File Storage. Em seguida, foi feita a deduplicação do arquivo vdi com o Linux atualizado, de tamanho 4.605.431.808 bytes, tendo como base o arquivo subido anteriormente. Esses passos foram executados para 5 tamanho diferentes de chunk: 128KB, 64KB, 32KB, 16KB e 8 KB. Para questões comparativas, o arquivo Linux atualizado também foi armazenado no sistema sem deduplicação. A quantidade de chunks geradas com essas três execuções serão exibidas na tabela 6.2. O fato da quantidade de chunks ser diferente entre a deduplicação e o armazenamento do mesmo arquivo, se dá porque com a deduplicação podem ser criados vários chunks 73 6.3. ANÁLISE DE ECONOMIA UTILIZANDO MÁQUINAS VIRTUAIS com tamanho inferior ao tamanho padrão, enquanto que no armazenamento, o único chunk que pode ter um tamanho inferior ao padrão é o último. O gráfico de barras abaixo mostra a quantidade de dados armazenados no sistema tanto para o arquivo sem deduplicação, como para o arquivo deduplicado com chunks de tamanhos variados. A economia alcançada foi de 55% para chunks de 128KB, 60% para 64KB, 63% para 32KB, 66% para 16KB e 69% para 8KB. A quantidade de bytes armazenados para cada um deles foi respectivamente: 4.605.431.808 bytes (sem economia), 2.072.762.428, 1.878.494.141, 1.710.946.626, 1.574.787.731, 1.447.578.619. Levando em consideração que o arquivo utilizado como base para a deduplicação tinha 3.189.854.208 bytes, e que a diferença entre ele e o arquivo que foi deduplicado é de 1.415.577.600 bytes, a economia alcançada em relação à quantidade de dados comparados é de: 79,397% para 128KB, 85,488% para 64KB, 90,740% para 32KB, 95,731% para 16KB e 98,997% para 8KB. Figura 6.11 Tamanho em megabytes ocupado pelo arquivo no sistema, com a porcentagem de economia alcançada O mapa de modificações para cada um dos tamanhos de chunk analisados pode ser visto na Figura 6.11. Da mesma forma que o apresentado anteriormente, a cor azul representa as áreas do arquivo onde o seu conteúdo binário foi salvo no sistema e a cinza representa as áreas onde os chunks foram deduplicados. Figura 6.12 Mapa de chunks deduplicados da máquina virtual Linux 12.10 atualizada processada com os chunks da mesma máquina virtual sem atualização, para chunks com tamanho 128, 64, 32, 16 e 8KB 74 6.4. O USTORE 6.4 O Ustore O Ustore2 tem como propósito a realização de backups de dados em nuvens privadas. As empresas podem usar parte da área de armazenamento do disco que está ociosa nos seus computadores para criar um nuvem de dados interna e ter maior controle sobre seus dados. Ele tem como principal objetivo o backup distribuído de dados e é construído em uma arquitetura peer-to-peer (P2P) híbrida, que são redes peer-to-peer que possuem entidades centrais [38]. Como a ideia principal do sistema é a comunicação direta entre os computadores, essa arquitetura foi escolhida. Para obter um melhor desempenho, optou-se pela utilização de servidores, o que fez com que a arquitetura peer-to-peer tornar-se híbrida. A comunicação entre as entidades que compõem o sistema é feita através da plataforma JXTA, que é um conjunto de protocolos utilizado para criar uma rede peer-to-peer [45]. Na versão 1.7 do Ustore o Dedupeer foi adicionado para fazer a deduplicação dos arquivos versionados dos usuários. Para cada nova versão de um arquivo que é sincronizado no sistema, o processo de deduplicação é executado antes dos dados serem enviados para os peers, o que economiza tanto a banda de rede como o espaço ocupado nos peers. Outra vantagem que o Dedupeer adiciona ao Ustore é a possibilidade de ser oferecido mais espaço do que o sistema possui fisicamente, já que os chunks deduplicados não têm seus dados salvos, apenas as referência para os chunks que já estão armazenamentos. 6.5 Sumário do capítulo Neste capítulo foram analisados dois testes, um de desempenho e outro de taxa de compressão para máquinas virtuais. No teste de desempenho concluiu-se que para a operação de armazenamento não se pode afirmar se o tempo médio da operação com MD5 é diferente do tempo com SHA-1. Para deduplicação, o sistema com SHA-1 foi mais eficiente que com MD5. Para deduplicação + armazenamento foi confirmado que o tempo médio com SHA-1 e MD5 são diferentes, e a observação visual no gráfico fica claro que o SHA-1 é mais eficiente nesse caso também. E por último, na operação de reidratação, o sistema com MD5 foi mais eficiente que com SHA-1. 2 "http://usto.re" 75 6.5. SUMÁRIO DO CAPÍTULO Para a segunda avaliação, foi analisada a taxa de compressão obtida para duas máquinas virtuais Linux, e como esperado, a taxa de compressão foi maior para chunks menores. E por último, a partir das análises em todas as operações, o SHA-1 por ter sido melhor comprovadamente em 2 delas, e por ter a vantagem de não possuir colisões de hashes comprovadas, vai ser definido como padrão para o Dedupeer File Storage. 76 7 Conclusão Nesta pesquisa foi desenvolvido um algoritmo para deduplicação exata de dados, interoperável, com processamento particionado em cargas de bytes para a memória principal. O algoritmo foi encapsulado em um componente de software com interface Thrift, que é uma biblioteca de serialização com linguagem neutra e suporte à diversas linguagens de programação, para facilitar a integração com sistemas de armazenamento existentes. O algoritmo foi desenvolvido com uma funcionalidade de otimização de descoberta de redundância de dados através da carga extra de dados no processamento particionado, o que aumenta a precisão da identificação de redundância mesmo com a quebra do arquivo para ser processado. Para a avaliação do algoritmo foi desenvolvido o Dedupeer File Storage, um sistema de armazenamento de dados com as mesmas características dos principais sistemas de armazenamento distribuídos, utilizando a quebra dos arquivos salvos em várias partes menores, as quais são chamadas de chunks. O gerenciamento de replicação, tolerância à falha, entre outros problemas comuns enfrentados por esses sistemas são delegados ao Cassandra, que é a base de gerenciamento do sistema de armazenamento desenvolvido. O Dedupeer File Storage possui uma interface gráfica com o usuário à qual possibilita o gerenciamento dos arquivos armazenados, através de funcionalidades como backup, deduplicação, reidratação, cálculo de economia, renderização de mapa de chunks e referências do arquivo e painel de configurações básicas, como tamanho padrão de chunk e quantidade de chunks carregados pra memória. Com a utilização do algoritmo de deduplicação com processamento particionado, foi possível fazer o deduplicação de arquivos maiores do que a memória RAM do computador utilizado, o que não é possível para algoritmos com a carga do arquivo completa para a memória. Nesta análise foram utilizados dois arquivos, um com 2,97GB e outro com 4,28GB, sendo que o computador onde a análise foi feita possui 3,24GB de RAM. 77 7.1. TRABALHOS FUTUROS A eficiência do algoritmo também foi comprovada através da compressão alcançada entre máquinas virtuais, chegando até a uma economia equivalente à 98,997% em relação ao tamanho total do arquivo armazenado no sistema que foi utilizado na deduplicação, o qual possuia aproximadamente 2,97GB e conseguiu uma economia no arquivo processado de aproximadamente 2,93GB. 7.1 Trabalhos futuros A fim de promover a utilização do algoritmo de deduplicação particionada ou o sistema de armazenamento de dados desenvolvidos, há algumas possibilidades de trabalhos futuros, os quais estão listados abaixo: • Criar um buffer do mapa dos chunks processados na deduplicação em disco, para que não haja estouro de memória pelo fato do mapa de chunks ficar maior que o espaço reservado para a memória heap do Java, o que possibilitará fazer a deduplicação de arquivo de qualquer tamanho. • Comparar os ganhos de desempenho trazidos pela deduplicação particionada em relação à um algoritmo de deduplicação com carga completa do arquivo para a memória. • Fazer a análise do impacto de utilizar o Dedupeer File Storage em uma rede P2P com vários nós, já que o Cassandra dá suporte nativo à isso e o Dedupeer File Storage funciona com o Cassandra sendo o gerenciador dos dados armazenados. • Modificar o Dedupeer File Storage para dar suporte à distribuição de tarefas de deduplicação entre os nós da rede P2P criada a partir da sugestão acima, possibilitando a deduplicação de um mesmo arquivo em vários nós, o que aumentaria a velocidade para conclusão da tarefa. Teoricamente o algoritmo já dá suporte à isso, mas o sistema de armazenamento precisa fornecer a distribuição do arquivo base para deduplicação entre os nós que farão o processamento, e também fazer a coordenação dos nós da deduplicação distribuída. • Utilizar o algoritmo como base para o desenvolvimento de um software de compressão de dados através de deduplicação, mantendo as informações necessárias para a reidratação dos dados no mesmo arquivo, com funcionamento independente do sistema de arquivos presente no computador onde o software será executando. Ele teria um funcionamento parecido com o de softwares como o WinRar, 7zip, WinZip, 78 7.1. TRABALHOS FUTUROS entre outros, mas com a possibilidade de obter maiores ganhos de economia entre versões de um arquivo. • Analisar ganho de desempenho e economia no Ustore, comparando o sistema com a utilização do Dedupeer como componente de deduplicação e sem ele. 79 A Apêndice A.1 Arquivo de definição dos serviços Thrift namespace java com.dedupeer.thrift typedef i32 int typedef i32 weakHash typedef i64 position typedef string strongHash typedef string chunkID struct ChunkIDs { 1: optional string fileID, 2: required string chunkID, } typedef map<weakHash,map<strongHash,ChunkIDs» hashesToCompare struct Chunk { 1: required string fileID, 2: required string chunkNumber, 3: required string index, 4: required string length, 5: optional string strongHash, 6: optional string weakHash, 7: optional string pfile, 8: optional string pchunk, 9: optional string destination 10: optional binary content } 85 A.1. ARQUIVO DE DEFINIÇÃO DOS SERVIÇOS THRIFT service DeduplicationService { map<position,Chunk> deduplicate(1:hashesToCompare chunksInfo, 2:string pathOfFile, 3:int chunkSizeInBytes, 4:int bytesToLoadByTime), } 86 B Apêndice B.1 Resultados da avaliação dos arquivos do código fonte do Kernel do Linux Nesta seção estão todas as atribuições de vetores com o tempo em milisegundos das 14 execuções para cada tamanho de chunk e algoritmo de hashing utilizadas no Wolfram Mathematica 9 para a geração dos gráficos. O nome das variáveis já são auto explicativos: operação + algoritmo de hashing + tamanho do chunk. storeMd5128 = {116890, 128522, 161175, 127015, 182025, 138390, 163266, 164578, 174934, 172479, 264644, 163050, 196002, 199964} dedupMd5128 = {152626, 121048, 116854, 147668, 107600, 169585, 137500, 128414, 109421, 120321, 152501, 136377, 150033, 115625} dedupStoreMd5128 = {302343, 266764, 205891, 235066, 181023, 390554, 284572, 225852, 240909, 146712, 257380, 257291, 283357, 219191} rehydMd5128 = {43120, 39682, 24423, 27270, 32202, 38042, 31570, 29929, 32843, 33967, 74339, 78179, 54132, 63520} storeMd564 = {224443, 229646, 263209, 287779, 201734, 190391, 146932, 213240, 170712, 243824, 362548, 307237, 209613, 245140} dedupMd564 = {174321, 147211, 139559, 133334, 120627, 124088, 139953, 119768, 130386, 129988, 138782, 163945, 130047, 166978} dedupStoreMd564 = {630062, 545441, 520411, 223001, 291992, 231066, 452831, 246117, 335005, 339177, 341458, 306297, 312459, 451774} rehydMd564 = {44016, 44776, 53259, 42332, 39908, 42605, 51627, 45562, 42655, 49344, 43065, 43992, 39501, 54063} storeMd532 = {287477, 294028, 200850, 377811, 259185, 230927, 247576, 207965, 87 B.1. RESULTADOS DA AVALIAÇÃO DOS ARQUIVOS DO CÓDIGO FONTE DO KERNEL DO LINUX 249530, 208838, 455668, 270963, 320549, 277556} dedupMd532 = {145037, 157748, 170891, 132478, 138431, 131754, 133539, 130283, 154380, 150167, 148359, 148353, 166233, 138308} dedupStoreMd532 = {577638, 412407, 317166, 292932, 259089, 330367, 316198, 365495, 837278, 506792, 602081, 561788, 601541, 354776} rehydMd532 = {51287, 46370, 46104, 49237, 44773, 47537, 58604, 58947, 57708, 64215, 66442, 55603, 60782, 59282} storeMd516 = {562270, 454941, 424759, 502178, 381864, 412521, 407050, 405793, 473307, 386267, 593349, 421730, 375943, 376105} dedupMd516 = {188752, 185564, 161651, 193435, 162934, 172242, 163898, 155829, 155889, 188695, 175961, 203188, 202666, 167698} dedupStoreMd516 = {622743, 629450, 513485, 713067, 639438, 603385, 388159, 337782, 1436801, 831398, 565027, 1698535, 957346, 289319} rehydMd516 = {86674, 82294, 81711, 84303, 98120, 85317, 85441, 85200, 115681, 121885, 92514, 101977, 105959, 112096} storeMd58 = {964520, 706285, 701453, 776959, 652153, 626387, 729390, 649180, 647969, 740110, 810059, 638516, 664015, 685365} dedupMd58 = {262806, 222173, 222521, 213340, 226187, 213091, 211391, 215724, 206816, 208526, 290567, 217865, 255022, 220123} dedupStoreMd58 = {1233607, 1147788, 950798, 1352302, 992080, 1149349, 1455682, 1091442, 1296342, 1088684, 978422, 1409732, 1212028, 1633295} rehydMd58 = {211929, 209033, 219621, 214522, 213657, 228344, 208617, 203970, 217254, 211841, 260067, 218043, 235251, 316691} storeSha1128 = {97349, 109037, 178196, 131649, 222211, 164740, 156184, 175117, 201404, 241228, 186827, 199992, 211887, 171740} storeSha1128 = {97349, 109037, 178196, 131649, 222211, 164740, 156184, 175117, 201404, 241228, 186827, 199992, 211887, 171740} dedupSha1128 = {63519, 31808, 31076, 62923, 28399, 39641, 30170, 32386, 27387, 28872, 45208, 28240, 33598, 28513} dedupStoreSha1128 = {106842, 40104, 38955, 71471, 37059, 48198, 38517, 40062, 35608, 36583, 53701, 36477, 41966, 36642} rehydSha1128 = {80398, 66018, 74281, 73583, 77102, 79250, 71369, 74586, 61423, 54298, 70373, 54420, 63678, 59489} storeSha164 = {227047, 246043, 220085, 246523, 258685, 343073, 273425, 163953, 180418, 168436, 241606, 197194, 149808, 340464} 88 B.1. RESULTADOS DA AVALIAÇÃO DOS ARQUIVOS DO CÓDIGO FONTE DO KERNEL DO LINUX dedupSha164 = {44627, 55439, 37672, 36791, 37862, 45950, 37520, 42662, 36343, 36836, 90213, 37043, 36457, 34334} dedupStoreSha164 = {60352, 71177, 53407, 52170, 53417, 60901, 51731, 56645, 49782, 50206, 105105, 51850, 51185, 49028} rehydSha164 = {113640, 121360, 115431, 116551, 120308, 121186, 118612, 119106, 119012, 126016, 120365, 121915, 115323, 122687} storeSha132 = {342456, 307489, 444142, 271943, 241923, 278932, 234001, 291040, 247489, 419230, 310343, 235232, 248579, 230177} dedupSha132 = {88725, 94374, 83564, 75430, 53590, 42292, 42023, 42247, 42377, 45404, 59797, 52593, 48748, 50663} dedupStoreSha132 = {311124, 668717, 668684, 318272, 83442, 72593, 71620, 71011, 69193, 71844, 90368, 80840, 76438, 78293} rehydSha132 = {284121, 283260, 275700, 281079, 280853, 277080, 279279, 271631, 280641, 288302, 224400, 210995, 216494, 227920} storeSha116 = {562142, 463576, 444615, 530067, 454989, 442680, 441273, 492828, 448203, 426066, 415538, 294142, 544621, 352227} dedupSha116 = {93354, 78566, 82060, 84914, 87426, 77438, 79562, 82759, 82814, 77072, 86109, 61880, 72285, 68897} dedupStoreSha116 = {153686, 137637, 380842, 143149, 146810, 134846, 134432, 134202, 134325, 222198, 140885, 116085, 127409, 124290} rehydSha116 = {555020, 546197, 551883, 577015, 560025, 561874, 569340, 583346, 586910, 439744, 546468, 553587, 573509, 561451} storeSha18 = {821022, 698555, 790524, 714050, 699568, 718900, 687966, 724312, 936965, 1007309, 675155, 624304, 564328, 626949} dedupSha18 = {125576, 117776, 118081, 121136, 122290, 114841, 114762, 114715, 125726, 129404, 122153, 104723, 110433, 116113} dedupStoreSha18 = {256510, 244094, 248895, 664530, 239424, 265320, 231658, 235930, 237475, 416532, 229066, 212203, 217454, 223275} rehydSha18 = {1124600, 1107152, 1122944, 1178347, 1130081, 1135656, 1129751, 1150545, 1189838, 1162052, 1145106, 1253311, 1251508, 1253582} 89 C Apêndice C.1 Gráficos dos tempos emparelhados por tamanho de chunk para a operação de armazenamento Figura C.1 Tempo emparelhado para a operação de armazenamento com chunks de 128KB 90 C.1. GRÁFICOS DOS TEMPOS EMPARELHADOS POR TAMANHO DE CHUNK PARA A OPERAÇÃO DE ARMAZENAMENTO Figura C.2 Tempo emparelhado para a operação de armazenamento com chunks de 64KB Figura C.3 Tempo emparelhado para a operação de armazenamento com chunks de 32KB 91 C.1. GRÁFICOS DOS TEMPOS EMPARELHADOS POR TAMANHO DE CHUNK PARA A OPERAÇÃO DE ARMAZENAMENTO Figura C.4 Tempo emparelhado para a operação de armazenamento com chunks de 16KB Figura C.5 Tempo emparelhado para a operação de armazenamento com chunks de 8KB 92 C.2. GRÁFICOS DOS TEMPOS EMPARELHADOS POR TAMANHO DE CHUNK PARA A OPERAÇÃO DE DEDUPLICAÇÃO C.2 Gráficos dos tempos emparelhados por tamanho de chunk para a operação de deduplicação Figura C.6 Tempo emparelhado para a operação de deduplicação com chunks de 128KB Figura C.7 Tempo emparelhado para a operação de deduplicação com chunks de 64KB 93 C.2. GRÁFICOS DOS TEMPOS EMPARELHADOS POR TAMANHO DE CHUNK PARA A OPERAÇÃO DE DEDUPLICAÇÃO Figura C.8 Tempo emparelhado para a operação de deduplicação com chunks de 32KB Figura C.9 Tempo emparelhado para a operação de deduplicação com chunks de 16KB 94 C.3. GRÁFICOS DOS TEMPOS EMPARELHADOS POR TAMANHO DE CHUNK PARA A OPERAÇÃO DE DEDUPLICAÇÃO + ARMAZENAMENTO Figura C.10 Tempo emparelhado para a operação de deduplicação com chunks de 8KB C.3 Gráficos dos tempos emparelhados por tamanho de chunk para a operação de deduplicação + armazenamento Figura C.11 Tempo emparelhado para a operação de deduplicação + armazenamento com chunks de 128KB 95 C.3. GRÁFICOS DOS TEMPOS EMPARELHADOS POR TAMANHO DE CHUNK PARA A OPERAÇÃO DE DEDUPLICAÇÃO + ARMAZENAMENTO Figura C.12 Tempo emparelhado para a operação de deduplicação + armazenamento com chunks de 64KB Figura C.13 Tempo emparelhado para a operação de deduplicação + armazenamento com chunks de 32KB 96 C.3. GRÁFICOS DOS TEMPOS EMPARELHADOS POR TAMANHO DE CHUNK PARA A OPERAÇÃO DE DEDUPLICAÇÃO + ARMAZENAMENTO Figura C.14 Tempo emparelhado para a operação de deduplicação + armazenamento com chunks de 16KB Figura C.15 Tempo emparelhado para a operação de deduplicação + armazenamento com chunks de 8KB 97 C.4. GRÁFICOS DOS TEMPOS EMPARELHADOS POR TAMANHO DE CHUNK PARA A OPERAÇÃO DE REIDRATAÇÃO C.4 Gráficos dos tempos emparelhados por tamanho de chunk para a operação de reidratação Figura C.16 Tempo emparelhado para a operação de reidratação com chunks de 128KB Figura C.17 Tempo emparelhado para a operação de reidratação com chunks de 64KB 98 C.4. GRÁFICOS DOS TEMPOS EMPARELHADOS POR TAMANHO DE CHUNK PARA A OPERAÇÃO DE REIDRATAÇÃO Figura C.18 Tempo emparelhado para a operação de reidratação com chunks de 32KB Figura C.19 Tempo emparelhado para a operação de reidratação com chunks de 16KB 99 C.4. GRÁFICOS DOS TEMPOS EMPARELHADOS POR TAMANHO DE CHUNK PARA A OPERAÇÃO DE REIDRATAÇÃO Figura C.20 Tempo emparelhado para a operação de reidratação com chunks de 8KB 100