• Nenhum resultado encontrado

Capítulo 3 – Graphene: Um Protótipo de SGBD Distribuído Orientado a Grafos

3.1. Visão Geral

3.1.1. Estratégia de Distribuição

Para realizar a distribuição dos dados, atualmente o Graphene suporta a fragmentação 1D. Cada vértice é alocado em um único nó, juntamente com todas as suas arestas de saída. Para recriar o grafo original a partir dos dados distribuídos, o Graphene utiliza um conjunto de propriedades que são associadas aos vértices e arestas para representar os elementos e adjacências virtuais. Arestas virtuais são relacionamentos entre vértices alocados em nós distintos.

Existem três propriedades principais criadas e gerenciadas internamente pelo Graphene para representação de identificadores e arestas virtuais. Estas propriedades possuem chaves denominadas: __id, _partition e _destVertexId. Por serem utilizados para gerência interna dos dados, o middleware não permite que um usuário utilize esses nomes reservados em propriedades próprias.

A propriedade de chave identificadora __id é utilizada para armazenar o identificador de um elemento de uma forma única. É necessário garantir que cada elemento do grafo possua um identificador único. Cada solução de banco de dados de grafos possui uma estratégia própria quanto à atribuição de identificadores aos seus elementos. Algumas soluções como o Neo4j (NEO4J, 2014) geram identificadores

44

sequenciais de forma automática enquanto outras como o TinkerGraph8 (banco de dados

de grafo leve, em memória), por exemplo, permitem que o identificador do elemento possa ser escolhido pelo usuário. Algumas soluções podem ainda reutilizar o valor de identificadores atribuídos anteriormente a elementos do grafo que já foram removidos. Por esse motivo, é uma boa prática que os usuários de banco de dados de grafos criem identificadores próprios aos elementos do domínio através de propriedades associados aos vértices e arestas, gerenciando manualmente a atribuição destes valores ao invés de utilizar os valores gerados pelo próprio SGBDG.

Cada SGBDG participante na solução distribuída funciona de maneira independente e não conhece a existência dos outros SGBDG, pois o Graphene é a camada superior responsável por conhecer e orquestrar as operações entre os SGBDG existentes. Por este motivo, elementos diferentes com identificadores idênticos podem surgir entre os BDG participantes. Durante a criação de um novo elemento o usuário informa o identificador que aquele elemento irá receber. Este identificador é gravado na propriedade __id associada ao elemento. O SGBDG responsável pela criação do novo elemento pode escolher qualquer identificador a seu critério para este elemento mas este valor não será utilizado pelo Graphene, uma vez que qualquer referência futura que o usuário venha fazer a este elemento será feita utilizando o identificador definido pelo usuário durante a criação do elemento, que foi o valor armazenado na propriedade __id.

As propriedades _partition e _destVertexId são utilizadas pelo middleware para representar arestas virtuais. As arestas virtuais são arestas do Graphene que conectam dois vértices que estão em fragmentos distintos. Quando uma aresta é criada entre dois vértices que estão no mesmo fragmento, isto é, pertencem ao mesmo BDG, as arestas são criadas diretamente entre os elementos da mesma forma que seriam criadas no banco de dados original. Quando os vértices de origem e destino estão em BDG diferentes, uma aresta virtual é criada pelo Graphene para representar a ligação entre os elementos. Para representar uma aresta virtual, optou-se por criar uma aresta no vértice de origem que aponta para o próprio vértice e que possui uma propriedade com chave “_partition” e

45

valor “virtual”. As demais arestas são marcadas com a propriedade “_partition” com valor “original”. No caso das arestas virtuais, uma outra propriedade com chave _destVertexId é adicionada ao elemento e seu valor é o identificador do vértice de destino (__id).

O Graphene suporta três políticas de fragmentação predefinidas: fragmentação baseada em Espalhamento, circular (round-robin) ou baseada em intervalo (range). Outras políticas de fragmentação personalizadas podem ser definidas pelo usuário. Cada nó computacional participante possui um identificador próprio, único conhecido pelo

middleware. A política de fragmentação é uma função que recebe como entrada o

identificador único de um vértice e retorna, como saída, o identificador do nó responsável por armazenar aquele vértice. O usuário pode utilizar uma das funções de fragmentação predefinidas ou implementar sua própria lógica de fragmentação dos dados.

A fragmentação baseada em Espalhamento utiliza o identificador do vértice como entrada em uma função de Espalhamento. A saída desta função é um número inteiro. O valor absoluto do número inteiro é recuperado e dividido pelo número de nós existentes na solução distribuída. O resto da divisão é utilizado para indicar qual nó é responsável pelo vértice cujo identificador foi utilizado como entrada da função de Espalhamento. O nó de destino para um vértice que possui um identificador igual a 𝑖𝑑𝑉𝑒𝑟𝑡𝑖𝑐𝑒𝐸𝑛𝑡𝑟𝑎𝑑𝑎 é calculado pela Equação 1.

𝑓(𝑖𝑑𝑉𝑒𝑟𝑡𝑖𝑐𝑒𝐸𝑛𝑡𝑟𝑎𝑑𝑎) = (|ℎ𝑎𝑠ℎ(𝑖𝑑𝑉𝑒𝑟𝑡𝑖𝑐𝑒𝐸𝑛𝑡𝑟𝑎𝑑𝑎)| 𝑚𝑜𝑑 𝑁𝑈𝑀𝑁Ó𝑆) + 1

Equação 1 – Função de distribuição de Espalhamento predefinida disponível no Graphene.

A ideia da fragmentação baseada em intervalo (range) é garantir que vértices com identificadores consecutivos sejam alocados no mesmo nó. É útil particularmente quando a estrutura do grafo é conhecida previamente. É importante que o número total de vértices que serão armazenados seja conhecido previamente. A fragmentação baseada em intervalo recebe o identificador de um vértice como entrada e utiliza dois parâmetros para calcular o destino daquele vértice: o número total de vértices que serão armazenados no grafo 𝑁𝑈𝑀𝑇𝑂𝑇𝐴𝐿_𝑉𝐸𝑅𝑇𝐼𝐶𝐸𝑆 e o número de nós participantes na solução 𝑁𝑈𝑀𝑁Ó𝑆. O nó de

46

𝑓(𝑖𝑑𝑉𝑒𝑟𝑡𝑖𝑐𝑒𝐸𝑛𝑡𝑟𝑎𝑑𝑎) = ⌊𝑖𝑑𝑉𝑒𝑟𝑡𝑖𝑐𝑒𝐸𝑛𝑡𝑟𝑎𝑑𝑎 ∗ 𝑁𝑈𝑀𝑁Ó𝑠 𝑁𝑈𝑀𝑇𝑂𝑇𝐴𝐿_𝑉𝐸𝑅𝑇𝐼𝐶𝐸𝑆

⌋ + 1

Equação 2 – Função de distribuição baseada em intervalo predefinida disponível no Graphene.

A terceira função de distribuição presente no Graphene é uma função circular. Esta função de distribuição garante que os vértices com identificadores consecutivos sejam alocados em diferentes bases de dados. Cada vértice consecutivo é distribuído em um nó diferente seguindo uma estratégia circular, em sequência. O nó de destino é dado pela Equação 3.

𝑓(𝑖𝑑𝑉𝑒𝑟𝑡𝑖𝑐𝑒𝐸𝑛𝑡𝑟𝑎𝑑𝑎) = (𝑖𝑑𝑉𝑒𝑟𝑡𝑖𝑐𝑒𝐸𝑛𝑡𝑟𝑎𝑑𝑎 𝑚𝑜𝑑 𝑁𝑈𝑀𝑁Ó𝑆) + 1

Equação 3 – Função de distribuição circular predefinida disponível no Graphene.

Para facilitar o entendimento sobre como os dados são armazenados de forma distribuída no middleware, um pequeno grafo é utilizado como exemplo na Fig. 8. O grafo original possui quatro vértices e quatro arestas. Algumas propriedades foram associadas aos vértices que se conectam aos outros vértices através de arestas com etiqueta do tipo “relacionado”. Os vértices são identificados por valores de 1 a 4. Este grafo foi carregado no Graphene e distribuído em duas bases de dados distintas, usando uma política de fragmentação de Espalhamento para distribuir os vértices entre dois nós. O SGBDG 1 ficou responsável por armazenar os vértices de identificador 2 e 4 enquanto o SGBDG 2 armazenou os vértices 1 e 3. A aresta que liga os vértices 4 e 2 é representada diretamente no BDG 1 já que ambos os vértices são armazenados pelo mesmo nó. As demais arestas ligam vértices armazenados por nós distintos e, por esse motivo, estas adjacências são representadas por arestas virtuais que possuem como propriedade o identificador do vértice de destino.

47

(a) – Grafo Original

48 (c) – BDG 2

Fig. 8 - Grafo original (a) distribuído no Graphene entre dois BDG de acordo com a fragmentação baseada em Espalhamento. Em (b) é possível verificar a porção do grafo armazenada no BDG 1 enquanto

(c) representa a porção armazenada no BDG 2.

Documentos relacionados