otimização de consultas paralelas usando o - FACOM
Transcrição
otimização de consultas paralelas usando o - FACOM
OTIMIZAÇÃO DE CONSULTAS PARALELAS USANDO O AGRUPAMENTO PRÉVIO E UM FILTRO SELETIVO Nilton Cézar de Paula Dissertação de Mestrado Orientador: Prof. Dr. José Craveiro da Costa Neto Área de Concentração: Ciência da Computação Dissertação apresentada ao Departamento de Computação e Estatística (DCT) da Universidade Federal de Mato Grosso do Sul (UFMS) como parte dos requisitos para a obtenção do título de Mestre em Ciência da Computação. Departamento de Computação e Estatística Centro de Ciências Exatas e Tecnologia Universidade Federal de Mato Grosso do Sul Novembro de 2003 "...As invenções são sobretudo o resultado de um trabalho teimoso..." Alberto Santos Dumont i Dedicatória Aos meus pais, José Ramos (in memorian) e Maria Veloso, por todos os esforços e sacrifícios realizados para me proporcionar o término do curso de graduação. A Elisângela, pela sua presença constante e efetiva, que com seu estímulo e sua ajuda me faz enfrentar e ultrapassar desafios. ii Agradecimentos "Não mostre para Deus o tamanho do seu problema, mostre para o problema o tamanho do seu Deus" (autor desconhecido). Deus, obrigado pelo dom da vida e pela paciência. Ao Prof. Dr. José Craveiro da Costa Neto, pela orientação, acompanhamento e paciência que dedicou a este trabalho e a mim. Pelas sugestões valiosas, conduzindo de forma coerente a construção deste trabalho. Por orientar um aluno que não é da mesma cidade e que não podia dedicar tempo integral para os estudos. À Profª. Liria Matsumoto Sato, ao Prof. Marcelo Augusto Santos Turine e ao Prof. Hélio Crestana Guardia, pelas sugestões e contribuições feitas na qualificação. Aos membros da banca examinadora, pela boa vontade em aceitar o convite para participar da minha defesa. Aos professores do DCT/UFMS, que lutaram para tornar realidade o curso de Mestrado em Ciência da Computação. Muito obrigado. Em especial ao Prof. Marcelo Henriques de Carvalho e ao Prof. Nalvo Franco de Almeida Júnior, pelos conselhos, mas com destaque daquele da primeira semana de aula do mestrado, ao Prof. Edson Norberto Cáceres pela amizade desde a sua passagem na Pró-Reitoria de Graduação-UFMS, ao Prof. Henrique Mongelli, Coordenador do Mestrado, pela dedicação que vem demonstrando na direção do curso e por nos manter sempre informado dos acontecimentos. Aos professores das disciplinas do mestrado, que além de ensinarem conteúdos também demonstraram exemplos de vida e amor. Aos funcionários do DCT/UFMS, pela atenção dispensada em todos os momentos que lá estive. Aos colegas da turma de mestrado, pelo apoio e amizade, com destaque para o Alexandre, Christian, Daniel, Erik, Érika, Graziela, Humberto, Luciana, Luciano, Marta, Sibelis, Rodrigo e Welton. Se esqueci de mencionar alguém me perdoe. A todos os meus parentes e familiares, pelo apoio, incentivo e soberam compreender a minha ausência em muitos momentos de confraternização. À minha irmã Adriana Betânia, iii Mário Marcos, Flávia Aline e Larissa Beatriz pelo carinho e palavras de força e estímulo. Ao meu irmão Fabrício que não mediu esforços para conseguir inúmeros artigos e que também teve paciência e disposição de fazer a correção deste texto. À Danieli, Oséias, Maria Hilda, Joaquim Arcanjo, Eliane, Franklin, Franklin Júnior, Willian, Aparecida e Milton Molgora que torceram pela finalização deste texto. Ao meu filho Juninho, por estar conosco e, assim, com seu sorriso nos faz reviver a criança que existe dentro de nós. Aos meus alunos da UEMS e da UNIGRAN, que souberam compreender a minha ausência às aulas quando estive em muitos momentos em Campo Grande-Ms. Aos colegas professores da UEMS e da UNIGRAN, pelo apoio nos momentos em que sempre precisei. Aos técnicos do laboratório de computação da UEMS que organizaram as máquinas para os testes. Ao Adriano Câmara, Diretor de Informática da UNIGRAN, pela amizade e apoio ao acesso ao provedor de internet através do dourados.br. À Universidade Estadual de Mato Grosso do Sul, pela concessão de afastamento total no último ano do mestrado. À UNIGRAN, pelo apoio às viagens para Campo Grande-Ms. À demais pessoas que contribuiram para o desenvolvimento deste trabalho, direta ou indiretamente. iv Sumário LISTA DE FIGURAS............................................................................................................viii LISTA DE TABELAS..............................................................................................................x RESUMO..................................................................................................................................xi ABSTRACT..............................................................................................................................xii 1. INTRODUÇÃO ..........................................................................................................1 1.1. Definição do Problema ...................................................................................................... 1 1.2. Motivação ........................................................................................................................... 2 1.3. Objetivos do Trabalho ...................................................................................................... 3 1.4. Organização do Texto ....................................................................................................... 3 2. BANCO DE DADOS E PROCESSAMENTO DE CONSULTAS .............................5 2.1. Introdução .......................................................................................................................... 5 2.2. Bancos de Dados Relacionais............................................................................................ 5 2.2.1. Data Warehouses.......................................................................................................... 6 2.2.2. Bancos de Dados Distribuídos e Paralelos ................................................................... 7 2.2.3. CDBS............................................................................................................................ 9 2.3. Processamento de Consultas........................................................................................... 11 2.3.1. Processador de Consultas ........................................................................................... 13 2.4. Técnicas de Otimização de Consultas............................................................................ 14 2.4.1. Da Álgebra Relacional para um Plano Lógico Otimizado ......................................... 15 2.4.2. Do Plano Lógico Otimizado para Planos de Execução ..............................................17 2.4.3. Ordem para Junções ................................................................................................... 17 2.4.4. Abordagens para Enumerar Planos de Execução ....................................................... 18 2.4.5. Otimização de Consultas Paralelas.............................................................................19 2.5. Considerações Finais ....................................................................................................... 23 3. PARADIGMA DE TROCA DE MENSAGENS........................................................24 3.1. Introdução ........................................................................................................................ 24 3.2. Criação de Processos ....................................................................................................... 24 3.3. Envio e Recebimento de Mensagens ..............................................................................25 v 3.4. Ambientes de Troca de Mensagens................................................................................25 3.5. MPI-2.0 ............................................................................................................................. 26 3.6. Métricas para Avaliar Programas Paralelos................................................................. 31 3.7. Considerações Finais ....................................................................................................... 31 4. AGRUPAMENTO PRÉVIO E FILTRO SELETIVO ................................................33 4.1. Introdução ........................................................................................................................ 33 4.2. Aplicando o Agrupamento Prévio..................................................................................33 4.2.1. Tipos de transformação .............................................................................................. 36 4.2.2. Conjuntos necessários para a transformação .............................................................. 37 4.2.3. Identificando o nó de transformação .......................................................................... 37 4.3. Estratégias para o Agrupamento Prévio ....................................................................... 39 4.3.1. Processamento Seqüencial de Agregados................................................................... 39 4.3.2. Processamento Paralelo de Agregados ....................................................................... 41 4.3.3. Usando um Filtro Seletivo.......................................................................................... 43 4.4. Considerações Finais ....................................................................................................... 46 5. FAP: UM AMBIENTE PARA A EXECUÇÃO DE CONSULTAS PARALELAS COM AGREGADOS ...................................................................................................48 5.1. Introdução ........................................................................................................................ 48 5.2. Execução de Consultas com Agregados......................................................................... 49 5.2.1. Estratégias para a Junção e Agregação....................................................................... 49 5.2.2. Aplicando o Agrupamento Prévio em Paralelo .......................................................... 53 5.2.3. Aplicando o Filtro Seletivo......................................................................................... 54 5.3. Arquitetura do FAP ........................................................................................................ 55 5.4. O Módulo Cliente ............................................................................................................ 57 5.4.1. Catálogo...................................................................................................................... 57 5.4.2. Gerador de Catálogo................................................................................................... 58 5.4.3. Replicador de Catálogo .............................................................................................. 58 5.4.4. Atualizador de Instância ............................................................................................. 59 5.4.5. Gerador Estatístico .....................................................................................................59 5.4.6. Analisador de Sintaxe................................................................................................. 60 5.4.7. Gerador de Plano Inicial ............................................................................................. 61 5.4.8. Otimizador Heurístico ................................................................................................ 61 5.4.9. Otimizador de Agregação........................................................................................... 62 5.4.10. Gerador de Plano de Execução.................................................................................62 5.4.11. Coordenador de Execução ........................................................................................ 62 5.5. O Módulo Servidor de Arquivos Paralelos ................................................................... 63 vi 5.6. Aspectos de Implementação do FAP..............................................................................64 5.7. Considerações Finais ....................................................................................................... 68 6. MEDIDAS, DESEMPENHO E COMPARAÇÕES ..................................................70 6.1. Introdução ........................................................................................................................ 70 6.2. Um Estudo de Caso com o FAP...................................................................................... 71 6.3. Comparativo entre Consultas com/sem o Agrupamento Prévio ................................. 72 6.4. Comparativo entre Consultas com/sem o Filtro Seletivo.............................................78 6.5. Comparativo entre Consultas com/sem um Data Warehouse...................................... 82 6.6. Comparativo entre Consultas Variando a Carga de Trabalho................................... 86 6.7. Considerações Finais ....................................................................................................... 88 7. CONCLUSÕES, CONTRIBUIÇÕES E TRABALHOS FUTUROS.........................89 7.1. Conclusões ........................................................................................................................ 89 7.2. Contribuições ...................................................................................................................90 7.3. Trabalhos Futuros ........................................................................................................... 91 REFERÊNCIAS BIBLIOGRÁFICAS ..........................................................................93 APÊNDICES................................................................................................................97 Apêndice A. Um Exemplo de uma Aplicação de Banco de Dados ..................................... 97 Apêndice B. Principais Classes do FAP................................................................................99 Apêndice C. Estrutura de Diretórios do FAP .................................................................... 105 Apêndice D. Catálogo do Banco de Dados para o Estudo de Caso .................................. 106 Apêndice E. Código Fonte utilizado no Estudo de Caso ................................................... 108 vii Lista de Figuras Figura 2-1: Arquiteturas de Bancos de Dados Paralelos.................................................................... 9 Figura 2-2: Arquitetura do Sistema CDBS [Cos01, p. 64]................................................................. 10 Figura 2-3: Componentes do Processador de Consultas. ................................................................ 13 Figura 2-4: Árvore de Consulta Inicial. ........................................................................................... 16 Figura 2-5: Árvore de Consulta Otimizada...................................................................................... 16 Figura 2-6: Representações de Junções numa Árvore de Consulta..................................................18 Figura 2-7: Mecanismos de Paralelismo. ....................................................................................... 20 Figura 2-8: Distribuição de dados – (a) circular, (b) por faixa de valores e (c) hash............................ 21 Figura 3-1: Componentes para um Esquema de um Arquivo no MPI-IO. .......................................... 29 Figura 3-2: Exemplo de um Particionamento de um Arquivo entre 2 Processos................................. 29 Figura 4-1: Árvore de Consulta Otimizada...................................................................................... 34 Figura 4-2: Árvore de Consulta Otimizada com Agrupamento Prévio................................................35 Figura 4-3: Plano de Consulta Otimizado para Q2 com Agrupamento Prévio. ................................... 44 Figura 4-4: Exemplo de Construção de Vetores de Bits para a Consulta Q2. .................................... 46 Figura 4-5: Fase de Filtragem para a Consulta Q2.......................................................................... 46 Figura 5-1: Junção de uma única Passagem..................................................................................50 Figura 5-2: Esquema das Estratégias de Agregação....................................................................... 51 Figura 5-3: Esquema de um Algoritmo de duas Passagens. ............................................................ 52 Figura 5-4: Um Esquema para a fase de Mesclagem no Agrupamento Prévio................................... 52 Figura 5-5: Diagrama da Estratégia para o Agrupamento Prévio em Paralelo.................................... 53 Figura 5-6: Arquitetura do FAP...................................................................................................... 56 Figura 5-7: Estrutura t_left_deep_tree Existente no FAP. ................................................................ 67 Figura 5-8: Estrutura t_input_aggregate Existente no FAP............................................................... 68 Figura 6-1: Tempo de Execução da Consulta Q3............................................................................ 73 Figura 6-2: Tempo de Execução da Consulta Q4............................................................................ 73 Figura 6-3: Desempenho para a Consulta Q3.................................................................................74 Figura 6-4: Desempenho para a Consulta Q4.................................................................................75 Figura 6-5: Eficiência para a Consulta Q3. ..................................................................................... 75 Figura 6-6: Eficiência para a Consulta Q4. ..................................................................................... 76 Figura 6-7: Espaço em Disco para os Arquivos Temporários nas Consultas Q3 e Q4. ....................... 77 viii Figura 6-8: Tempos de Execução da Junção e Agregação nas Consultas Q3 e Q4. .......................... 77 Figura 6-9: Aplicação do Filtro Seletivo na Consulta Q5. ................................................................. 79 Figura 6-10: Aplicação do Filtro Seletivo na Consulta Q6. ............................................................... 79 Figura 6-11: A Ocorrência de Falsos Positivos na Consulta Q5........................................................ 80 Figura 6-12: A Ocorrência de Falsos Positivos na Consulta Q6........................................................ 80 Figura 6-13: Nº de Grupos para a Consulta Q5 Com/Sem Falsos Positivos. ..................................... 81 Figura 6-14: Nº de Grupos para a Consulta Q6 Com/Sem Falsos Positivos. ..................................... 81 Figura 6-15: Tempos de Execução das Consultas Q3 e Q7 sem Agrupamento Prévio. ...................... 83 Figura 6-16: Tempos de Execução das Consultas Q3 e Q7 com Agrupamento Prévio. ...................... 83 Figura 6-17: Speedup das Consultas Q3 e Q7 sem Agrupamento Prévio.......................................... 84 Figura 6-18: Eficiência das Consultas Q3 e Q7 sem Agrupamento Prévio......................................... 85 Figura 6-19: Speedup das Consultas Q3 e Q7 com Agrupamento Prévio.......................................... 85 Figura 6-20: Eficiência das Consultas Q3 e Q7 com Agrupamento Prévio......................................... 86 Figura 6-21: Tempo de Execução de Consultas Variando a Carga de Trabalho ................................ 87 ix Lista de Tabelas Tabela 2-1: Operadores da Álgebra Relacional. .............................................................................12 Tabela 5-1: Principais Classes Implementadas do FAP................................................................... 65 Tabela 6-1:Consultas Executadas no Estudo de Caso. ................................................................... 72 x Resumo Processamento paralelo e distribuído é uma alternativa para melhorar o desempenho de consultas sobre data warehouses. A técnica de agrupamento prévio tem sido enfatizada recentemente em sistemas centralizados para melhorar as consultas com agregação. Esta técnica permite a execução da operação de agregação antes da junção. Como resultado, a eficiência da consulta é aumentada. Este trabalho propõe e implementa um ambiente de software, denominado FAP, para a execução de consultas com agregados. FAP faz uso do paralelismo, agrupamento prévio e um filtro seletivo. FAP também adiciona novas características ao CDBS (Concurrent Database System), tornando possível analisar a execução de consultas com agregados. Finalmente, a implementação modular do FAP provê uma maneira fácil de incluir novas funcionalidades e modificar as existentes. Os resultados obtidos com o estudo de caso no uso do FAP mostram que a aplicação do agrupamento prévio em um ambiente paralelo reduz o tempo de execução da consulta e os acessos a disco. Além disso, o desempenho das consultas com agregados fica mais interessante se o agrupamento prévio é combinado com o filtro seletivo, que também é estudado neste trabalho. xi Abstract Parallel and distributed processing is an alternative to improve the performance of queries on data warehouses. The early grouping technique has been recently emphasized in centralized systems to improve queries with aggregation. This technique allows the execution of aggregates before the join operation. As a result, query efficiency is increased. This work proposes and implements a software environment, called FAP, for the execution of queries with aggregates. FAP makes use of parallelism, previous grouping and a selective filter. FAP also adds new features to CDBS (Concurrent Database System), which make it possible to analyze the execution of queries with aggregates. Finally, the modular implementation of FAP provides an easier way of including new functionalities and modifying existent ones. The results obtained by a case study in the use of FAP show that the application of the early grouping in a parallel environment reduces both query execution time and disk access. Furthermnore, the performance of queries with aggregates becomes even more attractive if early grouping is combined with a selective filter, which is also studied in this work. xii 1 1. Introdução 1.1. Definição do Problema Os sistemas de gerenciamento de bancos de dados (SGBDs) têm se tornado ferramentas indispensáveis para usuários de computadores. Eles são projetados para gerenciar grandes quantidades de dados de forma eficiente e permitir que esses dados estejam disponíveis ao longo de anos e com segurança. O acesso aos dados em um banco de dados pode ser realizado através do processador de consultas, um componente do SGBD, que recebe uma consulta do usuário em linguagem declarativa, analisa-a, transforma-a e executa-a [Jar84]. Para garantir o processamento eficiente de uma consulta são utilizadas técnicas de otimização de consultas. As técnicas de otimização de consultas mais utilizadas são as baseadas em heurísticas e as baseadas em estimativas de custos [Elm00]. A primeira, utiliza regras de transformações da álgebra relacional para gerar uma consulta equivalente mais eficiente. A segunda, estima e compara os custos de alguns planos de consulta e escolhe, para execução, o de menor custo. Em ambientes especializados como os data warehouses [Kim98], responder uma consulta de maneira eficiente é uma tarefa bastante complexa para o processador de consultas, mesmo usando as técnicas de otimização existentes. Esses ambientes, geralmente, armazenam muitos dados, que podem chegar a algumas centenas de gigabytes e, até mesmo, terabytes. Além disso, as consultas submetidas, em geral, são complexas e ad-hoc e requerem, com freqüência, operações que consomem bastante tempo de processamento como a junção e o agrupamento [Cha97]. Como as consultas nos data warehouses são importantes para ajudar na tomada de decisões nas empresas e possuem características que diferem esses ambientes dos ambientes tradicionais [Gar99], novas propostas têm surgido para melhorar o tempo de resposta dessas consultas e resolver o problema do armazenamento de grandes volumes de dados. Uma proposta indica a aplicação do processamento paralelo e distribuído [Cha97, Gar99, Cos01]. 2 As pesquisas para o processamento de consultas têm sido direcionadas, em sua maioria, para a construção de algoritmos paralelos e distribuídos visando o processamento eficiente da operação de junção [Yan97]. Tal fato justifica-se porque a ordem de execução dessa operação pode levar o processamento de uma consulta a diferentes tempos de execução. Existem trabalhos, porém, que exploram outras operações, como o agrupamento. Um estudo interessante feito em [Yan94a] e [Cha94] apresenta a idéia de executar a operação de agrupamento antes da operação de junção, de maneira a reduzir o tamanho de uma tabela antes de aplicar operações de junções, denominada neste trabalho de agrupamento prévio. Embora seja uma idéia interessante, poucos trabalhos têm explorado este assunto. Surge, assim, uma oportunidade para unir os conceitos do processamento paralelo de consultas e as idéias do agrupamento prévio para explorar novas técnicas em ambientes de data warehousing paralelos e distribuídos. 1.2. Motivação As consultas em SQL com agregados têm sido comuns em bancos de dados para suporte à decisão, como os data warehouses, que particionam os dados em grupos e aplicam funções de agregação (por exemplo, COUNT, SUM, AVG, MAX e MIN) sobre cada grupo. Devido ao grande volume de informações existentes nos data warehouses, é indispensável a aplicação de técnicas para processar as consultas de maneira eficiente. O processamento paralelo tem sido uma opção interessante para melhorar o desempenho na execução de consultas com agregados, pois os dados distribuídos podem ser processados por diversos processadores simultaneamente. No entanto, as plataformas atuais para o processamento paralelo ainda são pouco difundidas e, em geral, possuem custo elevado. Neste contexto, são necessários mecanismos que auxiliem na compreensão da aplicação do paralelismo e do agrupamento prévio. Assim, a motivação inicial deste trabalho foi contribuir para a otimização de consultas através de um ambiente de software para a execução de consultas com agregados usando o processamento paralelo e a técnica do agrupamento prévio. 3 1.3. Objetivos do Trabalho Este trabalho apresenta o FAP, um ambiente de software que visa apoiar o processamento paralelo e distribuído de consultas. O FAP propõe a inclusão de uma nova facilidade no sistema CDBS (Concurrent Database System), que foi proposto em [Cos01]. A arquitetura do FAP define as unidades de software, as interfaces, o catálogo do banco de dados e as principais estratégias para a execução de uma consulta em paralelo. O FAP tem como objetivo principal fornecer um ambiente para melhor compreensão do comportamento da execução de uma consulta com a aplicação do paralelismo, do agrupamento prévio e de um filtro seletivo. Desta forma, a partir do entendimento de certos comportamentos, podem-se gerar contribuições teóricas no sentido de melhorar a eficiência na execução de consultas para data warehouses paralelos e distribuídos. Para alcançar o objetivo do ambiente proposto, seus módulos básicos (Cliente e Servidor de Arquivos Paralelos) foram implementados usando as especificações do CDBS que descrevem as primitivas para a criação e manutenção de um banco de dados paralelo e um data warehouse. A linguagem de programação utilizada no desenvolvimento é C++ com a distribuição LAM/MPI-2 (Local Area Multicomputer/Message Passing Interface) para a troca de mensagens. Por fim, para que se possa ilustrar a arquitetura proposta, apresenta-se um estudo de caso e, através do mesmo, resultados experimentais são obtidos durante a execução de vários tipos de consultas. O estudo de caso escolhido relaciona-se ao sistema de uma rede de locadoras, onde são apresentadas situações que exigem a utilização do paralelismo [Cos01]. Para tanto, foram avaliados aspectos tais como: (i) a aplicação do agrupamento prévio; (ii) o custo da execução de uma consulta com ou sem um data warehouse; (iii) o custo de uma consulta em ambiente centralizado ou paralelo e (iv) o uso do filtro seletivo, que é uma proposta deste trabalho. 1.4. Organização do Texto Este texto está organizado em sete capítulos e cinco apêndices. O primeiro capítulo apresentou o problema, a motivação e os objetivos pretendidos no presente trabalho. 4 O segundo capítulo apresentará uma revisão de conceitos de banco de dados e processamento e otimização de consultas em ambientes monoprocessados e multiprocessados. O terceiro capítulo descreverá o paradigma e um ambiente de troca de mensagens e métricas para avaliar programas paralelos. O quarto capítulo apresentará o conceito de agrupamento prévio e fará uma revisão das principais estratégias utilizadas para o processamento de consultas com agregados. O quinto capítulo apresentará o FAP, descrevendo seus objetivos e detalhes de sua especificação, projeto e implementação. O sexto capítulo apresentará um estudo de caso para ilustrar a aplicação das idéias desenvolvidas nesta dissertação e os resultados obtidos durante sua execução no FAP são comentados. O sétimo capítulo apresentará as conclusões e contribuições conquistadas com este trabalho e uma relação de trabalhos futuros que deverão ser conduzidos no sentido de evidenciar novas contribuições nesta área. Os apêndices apresentarão um exemplo de uma aplicação de banco de dados que será utilizada no decorrer deste trabalho, as principais classes implementadas no FAP, a estrutura de diretórios usada pelo ambiente, o catálogo do banco de dados e o código fonte das principais rotinas usadas durante a execução do estudo de caso. 5 2. Banco de Dados e Processamento de Consultas 2.1. Introdução A grande maioria das interações do usuário com um SGBD ocorre através de uma consulta. Por isto, um SGBD deve oferecer mecanismos eficientes para obter dados de um banco de dados através de seu processador. Neste capítulo serão apresentados os conceitos básicos necessários para a compreensão de um processador de consultas. 2.2. Bancos de Dados Relacionais Os bancos de dados têm se tornado essenciais, ajudando a organizar as diversas atividades das pessoas e empresas, tais como data warehouses, bancos de dados distribuídos e bancos de dados paralelos. Um banco de dados é uma coleção de dados relacionados que representam qualquer aspecto do mundo real para a solução de problemas [Elm00]. Esses dados podem ser gerados, mantidos e guardados em meios físicos para o acesso ao longo dos anos usando mecanismos manuais ou informatizados. Um banco de dados informatizado pode ser criado e mantido por um grupo de programas escritos especificamente para aquela tarefa ou por um SGBD. Um SGBD facilita o gerenciamento de um banco de dados usando um conjunto de programas de finalidade genérica [Ull97]. Estes programas automatizam os processos de definição e manipulação de um banco de dados. A definição de dados consiste em criar, alterar e excluir as estruturas de dados necessárias para o acesso aos dados em um banco de dados. Na manipulação de dados, os dados do banco de dados são consultados ou modificados. Uma tecnologia de banco de dados bastante consolidada no mercado é a de banco de dados relacional, que está baseada no modelo relacional [Cod70]. Esse modelo representa o banco de dados como um conjunto de tabelas (relações) que são compostas por tuplas (linhas) e atributos (colunas). Cada tupla representa uma entidade do mundo real ou um relacionamento entre entidades. Cada atributo é associado a um domínio indicando o conjunto 6 de valores que pode assumir. Uma tabela é descrita por um esquema que consiste no nome da tabela e seus atributos. Para qualquer tupla e qualquer atributo de uma tabela, é requerido que o valor do atributo seja atômico, isto é, não pode ser um atributo composto ou multivalorado. Para cada tabela, existe um atributo ou combinação de atributos que apresentam valor único ou combinação única de valores, chamados de superchave da tabela. Toda tabela possui pelo menos uma superchave; um número mínimo de atributos que garantem a combinação única de valores é chamado de superchave minimal ou, apenas, chave. Dentre todas as chaves de uma tabela, escolhe-se uma, que será denominada de chave primária. Em bancos de dados relacionais, existem tabelas de base e visões [Ull97]. Uma tabela de base armazena os dados em um meio físico, de tal forma que eles persistem por tempo indeterminado e podem ser consultados e modificados. Uma visão é construída a partir de uma ou mais tabelas através de uma consulta e não precisa ser necessariamente armazenada; porém, se o for, é chamada de visão materializada. Um aspecto importante que favorece o desempenho dos bancos de dados relacionais é a utilização de um catálogo para o acesso aos dados pelo SGBD. O catálogo oferece informações sobre as estruturas de dados, os tipos e formatos de armazenamento e as restrições sobre os dados. Estas informações são armazenadas na forma de tabelas e são requisitadas várias vezes; logo, uma implementação não planejada do catálogo poderá ocasionar um baixo desempenho no acesso aos dados. Por isto, recomenda-se utilizar uma estrutura de dados que consiga oferecer um bom desempenho. 2.2.1. Data Warehouses Um banco de dados usado extensivamente no dia-a-dia é também chamado de banco de dados de produção, pois registra os dados gerados pela execução das rotinas da organização. A partir de um banco de dados de produção, podem ser gerados novos conjuntos de dados que auxiliam na tomada de decisões nas empresas e são armazenados em um banco de dados específico, que será chamado banco de dados para análise ou data warehouse. Um data warehouse é constituído por dados derivados de fontes de dados e é consultado através de um tipo especial de processamento conhecido como processamento analítico [Gar99]. Em geral, as aplicações de data warehousing diferem das aplicações 7 tradicionais de bancos de dados, pois: (i) suas consultas são de longa duração devido ao grande volume de dados armazenados; (ii) são mantidos os estados anteriores do banco de dados, acrescentando informações temporais; (iii) processam sumários pré-computados e (iv) são estruturados para execução de consultas analíticas e construção de relatórios [Kim98]. A implementação de um data warehouse é feita, geralmente, usando-se um esquema em estrela que é composto por uma tabela de fatos e duas ou mais tabelas de dimensão. A tabela de fatos é representada no centro da estrela e contém dados numéricos e chaves estrangeiras que apontam para as tabelas de dimensão [Wei02]. 2.2.2. Bancos de Dados Distribuídos e Paralelos As tecnologias em bancos de dados centralizados e as redes de computadores tornaram viável a utilização de bancos de dados distribuídos e bancos de dados paralelos. Os primeiros bancos de dados eram gerenciados e armazenados usando um único computador, o que, com seu crescimento, tornou-se um problema devido às limitações de armazenamento e processamento de grandes volumes de dados. Uma solução para ajudar a diminuir estes problemas foram a descentralização do armazenamento e a autonomia de processamento, possível com a disseminação das redes de computadores. Um sistema de banco de dados distribuído consiste em uma coleção de vários bancos de dados logicamente inter-relacionados e distribuídos em diversos nós de uma rede de computadores, de maneira tal que cada nó possui capacidade de processamento autônomo e pode cooperar com os outros na execução de tarefas que requerem o acesso a dados distribuídos [Kim95]. Sistemas de bancos de dados distribuídos podem ser executados em diferentes tipos de arquiteturas. Para isto, possuem um conjunto de facilidades para a troca de dados e serviços, provêem diferentes níveis de transparência de distribuição de dados de forma que os dados podem ser representados em uma variedade de modelos [Kim95]. A distribuição de dados oferece oportunidades para aplicar técnicas de paralelismo. Para tanto, formas de distribuição estão disponíveis como a fragmentação e a replicação [Yu98]. A fragmentação consiste em particionar horizontalmente (tuplas) ou verticalmente (atributos) os 8 dados de uma tabela. A replicação consiste no armazenamento de cópias de tabelas completas ou fragmentadas nos diferentes nós da rede. Um sistema de banco de dados paralelo consiste na distribuição dos dados do banco de dados pelos vários nós do sistema de uma maneira que permita transparência plena. A distribuição dos dados em vários discos também oferece a oportunidade de redução do custo de acesso aos dados em disco, pois a divisão do banco de dados em muitos discos aumenta a largura de banda de E/S (entrada e saída) que, assim, permite o acesso simultâneo aos dados [DeW90]. Arquiteturas de bancos de dados paralelos têm sido propostas envolvendo a combinação de três recursos: processadores (P), memórias (M) e discos (D). Segundo [Waq96], as arquiteturas são: memória compartilhada, discos compartilhados e memória distribuída, conforme ilustra a Figura 2-1. Na arquitetura de memória compartilhada, todos os processadores têm acesso a qualquer módulo de memória ou disco através de uma rede de interconexão. Esta arquitetura permite um balanceamento de carga adequado com o uso da memória compartilhada, mas possui custo elevado porque os componentes (processador, memória e disco) devem ser ligados entre si. Um outro problema é o acesso conflitante aos recursos compartilhados, reduzindo, assim, a escabilidade [Tha90]. Bancos de dados paralelos com esta arquitetura incluem os protótipos XPRS [Hon92], o DBS3 [Ber91] e o Volcano [Gra90]. Na arquitetura de discos compartilhados, cada processador possui uma memória exclusiva e tem acesso a qualquer unidade de disco através de uma rede de interconexão. O custo desta rede é bem menor que no caso da arquitetura de memória compartilhada, porque o barramento padrão de interconexão pode ser utilizado. O balanceamento de carga pode ser tão bom quanto na memória compartilhada e falhas na memória podem ser isoladas dos outros nós, possibilitando, assim, uma maior disponibilidade. O acesso a discos compartilhados representa um gargalo desta arquitetura e é mais indicada para aplicações de bancos de dados somente de leituras ou para as aplicações sem compartilhamentos [DeW92]. Bancos de dados paralelos com esta arquitetura são IMS/VS Data Sharing(IBM) e VAX DBMS(DEC), conforme [DeW92]. 9 M P P M P D ... P ... D D ... D ... D ... M P M P M P P M D Memória Compartilhada Discos Compartilhados Memória Distribuída Figura 2-1: Arquiteturas de Bancos de Dados Paralelos. Na arquitetura de memória distribuída, cada processador tem acesso exclusivo à sua memória e às suas unidades de discos. Cada nó (processador + memória + discos) comunicase para trocar informações através da rede que liga os demais nós. O custo para acrescentar novos nós é relativamente baixo em comparação com a arquitetura de memória compartilhada e favorece uma escalabilidade bem maior, pois o crescimento suave pela adição de novos nós pode chegar na ordem de milhares (o DBC da Teradata pode acomodar até 1024 nós). Um dos maiores problemas desta arquitetura é garantir um balanceamento de carga adequado entre os vários nós, principalmente quando os dados apresentam uma distribuição não uniforme. Exemplos de bancos de dados paralelos que utilizam esta arquitetura incluem os produtos comerciais DBC(Teradata), NonStop SQL/MP(Tandem) [Eng95, Tan95] e DB2-PE(IBM) [Bar95], além de protótipos como o BUBBA [Bor90] e o GAMMA [DeW90]. Segundo [DeW92], vários trabalhos têm explorado a arquitetura de memória distribuída em bancos de dados paralelos, porque esta arquitetura alcança speedups (ganhos de velocidade) próximos do linear no processamento de consultas complexas. 2.2.3. CDBS O CDBS (Concurrent Database System) é a especificação e o projeto de um sistema que integra um banco de dados de produção e um data warehouse paralelos, que foi proposto em [Cos01] para oferecer um maior desempenho nas atividades de data warehousing. Os dados 10 são integrados em um único sistema, denominado ambiente de dados paralelo, favorecendo a consistência entre as tabelas e visões materializadas. A Figura 2-2 ilustra a integração no CDBS. Processamento de Transações Banco de Dados de Produção Processamento Analítico Integração dos Dados Data Warehouse Ambiente de Dados Paralelo Sistema de Arquivos Paralelos Figura 2-2: Arquitetura do Sistema CDBS [Cos01, p. 64]. No ambiente de dados paralelo, o banco de dados é formado por um conjunto de tabelas que visam apoiar tanto o processamento de transações como também o processamento analítico. Para permitir o processamento analítico, visões materializadas são criadas a partir das tabelas do banco de dados de produção (tabelas fontes), de maneira que o custo de execução de uma consulta submetida ao data warehouse seja reduzido e a interferência no banco de dados de produção seja pequena. Essas visões materializadas são atualizadas automaticamente através de um conjunto de primitivas usando os critérios de atualização completo ou incremental. O critério de atualização completo recria uma visão materializada a partir da leitura completa das tabelas fontes que lhe dão origem; já no incremental, apenas os registros das tabelas fontes que sofreram modificações desde a última atualização serão usados para a atualização da visão materializada. Para oferecer um alto desempenho no ambiente de dados paralelo foi adotado o sistema de arquivos NPFS (Network Parallel File System) [Gua99]. O NPFS é um sistema de arquivos paralelos destinado a um ambiente computacional distribuído, onde diferentes tipos de estações de trabalho podem manipular os arquivos paralelos de maneira cooperativa em uma rede. A estratégia adotada pelo NPFS consiste em utilizar o modelo cliente/servidor para 11 possibilitar o acesso paralelo aos arquivos distribuídos entre os discos das estações de trabalho da rede. A técnica de distribuição de dados é baseada na ordem de definição dos servidores. Além das primitivas já existentes no NPFS, outras foram adicionadas para atender especificações do CDBS, tratando problemas de replicação e distribuição de dados por faixa de valores e por função de hashing. 2.3. Processamento de Consultas Uma consulta expressa em uma linguagem de consulta é uma especificação do usuário para obter os dados de um banco de dados. No contexto de bancos de dados relacionais, dois tipos de linguagens descrevem uma consulta: a formal e a comercial. A linguagem formal define a base matemática para a construção de linguagens comerciais. A linguagem comercial é uma interface mais amigável para obter os dados de um banco de dados. Uma linguagem formal utilizada nos bancos de dados relacionais é a álgebra relacional [Ull97] que é representada por operações da teoria dos conjuntos (produto cartesiano, união, intersecção e diferença) e operações específicas para bancos de dados relacionais (seleção, projeção, junção e agregação). O resultado de uma operação é dado em forma de tabela. A Tabela 2-1 ilustra as características e notações dos operadores que representam as operações da álgebra relacional que serão utilizadas neste trabalho, conforme notação apresentada em [Gar00]. Uma linguagem comercial para representar as operações de um banco de dados relacional é a SQL (Structured Query Language), que se tornou um padrão universal na construção e na manipulação de bancos de dados relacionais [Ull97]. SQL apresenta vários componentes para o gerenciamento dos dados em um banco de dados, tais como definição e manipulação de dados, interface com linguagens de programação hospedeiras, definição de restrições e suporte para transações. Uma consulta escrita em SQL especifica o que o usuário quer do banco de dados, mas não como serão produzidos os resultados, ficando a cargo do SGBD o processamento e otimização necessária para conseguir esses resultados. Devido a esta facilidade, a linguagem SQL tem se tornado bastante popular e amplamente utilizada como uma ferramenta de apoio nas aplicações comerciais. SQL apresenta uma série de operações que podem ser executadas 12 sobre um banco de dados, tais como seleção, projeção, junção, agregação e ordenação. Operador Seleção Projeção Junção Agrupamento Álgebra Relacional Significado σC(R) πL(R) R Seleciona as tuplas da relação R que satisfazem a condição de seleção C. Seleciona os atributos da relação R que satisfazem a lista de atributos L. Cada tupla da relação R é combinada com cada tupla da relação S que satisfazem a condição de junção C. As tuplas com valores idênticos nos atributos L da relação R são agrupadas em grupos. Cada grupo é convertido, através da aplicação de uma ou mais funções F, em uma única tupla. F representa as funções de agregação COUNT, SUM, AVG, MAX e MIN. O resultado de cada F será armazenado em um novo atributo A. Se A não for especificado, então será utilizado o mesmo nome do atributo de R que está sendo aplicado a F. cS γL, FÆA (R) ou γL, F (R) Tabela 2-1: Operadores da Álgebra Relacional. Uma consulta SQL pode consistir de até seis cláusulas, mas somente as duas primeiras, SELECT e FROM, são obrigatórias. As cláusulas são especificadas na seguinte ordem, com as cláusulas entre colchetes [...] sendo opcionais: SELECT <lista de atributos e ou funções de agregação> FROM <lista de tabelas> [WHERE <condição>] [GROUP BY <lista de atributos de agrupamento> [HAVING <condição de agrupamento>]] [ORDER BY <listas de atributos >]; As três primeiras cláusulas definem a forma básica em SQL: a cláusula SELECT relaciona os atributos que representam os dados desejados da consulta; a cláusula FROM relaciona as tabelas envolvidas na consulta; e a cláusula WHERE especifica as condições de escolha das tuplas das tabelas. A cláusula GROUP BY agrupa tuplas que possuem o mesmo valor para a lista de atributos de agrupamento em grupos e, sobre estes, são aplicadas funções de agregação. Os atributos de agrupamento podem aparecer na lista de atributos da cláusula SELECT juntamente com os resultados das funções de agregação. 13 A cláusula HAVING especifica as condições de escolha dos grupos de tuplas produzidos pela cláusula GROUP BY. Por último, a cláusula ORDER BY especifica a ordem da saída dos resultados. Uma consulta escrita em uma linguagem de alto nível, como a SQL, é submetida ao processador de consultas, um componente do SGBD, para ser transformada em uma seqüência de operações que serão executadas sobre o banco de dados. Determinadas seqüências de operações podem levar a execução da consulta a demorar mais tempo do que outras. 2.3.1. Processador de Consultas O processador de consultas, ilustrado na Figura 2-3, pode ser dividido em duas etapas: a compilação e a execução [Gar00]. consulta Compilação metadados plano de execução Execução dados Banco de Dados Figura 2-3: Componentes do Processador de Consultas. Na compilação de uma consulta são definidas três tarefas principais: a análise, a geração do plano lógico e a geração do plano de execução. A análise consiste em construir uma árvore de análise para representar a consulta e sua estrutura. Na geração do plano lógico, a árvore de análise é convertida em um plano de consulta inicial, uma representação algébrica da consulta, e, posteriormente, este plano é transformado em um plano equivalente que deverá exigir menor tempo para ser executado. Na geração do plano de execução, o plano lógico é transformado utilizando os algoritmos de implementação de cada operador lógico e a seleção 14 de uma ordem de execução para esses operadores. Na execução, as operações do plano de execução são processadas e o resultado da consulta é entregue ao usuário. Em geral, o número de possíveis planos de execução gerados para uma consulta é grande e dois fatores influenciam nessa geração: o número de operações na consulta e o número de métodos que podem ser usados para avaliar cada operação. Esses métodos podem ser encontrados em [Gra93]. A quantidade de planos de execução pode aumentar ainda mais caso a consulta seja executada em um ambiente distribuído porque o fator de localização dos dados deve ser considerado. Encontrar o melhor plano de execução não é uma tarefa trivial e, por isso, técnicas de otimização de consultas têm sido estudadas [Cha98]. 2.4. Técnicas de Otimização de Consultas A execução eficiente de uma consulta depende de como as técnicas de otimização de consultas são aplicadas pelo otimizador de consultas, um outro componente do SGBD. Como foi visto na seção anterior, uma consulta pode levar a muitos planos (tanto lógicos como de execução) e escolher um desses planos que seja adequado para processar a consulta é um processo conhecido como otimização de consultas. O termo otimização talvez não seja apropriado porque, em alguns casos, o plano de execução escolhido não é a melhor estratégia, mas apenas uma estratégia razoavelmente eficiente para executar a consulta. Encontrar a melhor estratégia geralmente consome muito tempo, exceto para consultas muito simples, pois pode requerer informações sobre a maneira como as tabelas estão implementadas e, até mesmo, sobre o conteúdo das tabelas, que geralmente podem não estar plenamente disponíveis no catálogo do banco de dados. Assim, o planejamento de uma estratégia de execução pode ser o termo mais apropriado do que otimização de consultas. Em seguida serão detalhados os passos para a aplicação da otimização de consultas e serão apresentadas as técnicas de otimização baseadas em heurísticas e em estimativas de custos. 15 2.4.1. Da Álgebra Relacional para um Plano Lógico Otimizado Uma consulta SQL é inicialmente transformada em uma expressão equivalente da álgebra relacional e representada por uma estrutura de dados conhecida como árvore de consulta. A vantagem da estrutura em árvore em relação a outras formas de representações é que esta mostra a ordem de execução de cada operação de uma consulta, onde os nós folhas representam as relações de entrada e os nós internos, as operações da álgebra relacional. Uma execução da árvore de consulta consiste na execução de uma operação do nó interno sempre que seus operandos estejam disponíveis e, em seguida, na substituição desse nó interno pela relação resultante da execução da operação. A execução termina quando o nó raiz da árvore é executado e produz os resultados de uma consulta. Com a aplicação de transformações algébricas [Elm00, Gar00] espera-se obter um plano lógico otimizado a partir da árvore de consulta inicial que reduza o tempo de execução da consulta. No entanto, várias expressões algébricas serão construídas até chegar a esse plano otimizado através da utilização de algumas estratégias resultando em uma otimização baseada em heurísticas. Nesse tipo de otimização são aplicadas estratégias como: 1ª. Uma seleção pode ser movida para baixo no plano, atuando sobre uma tabela ou produto cartesiano. Se uma condição de seleção é o AND de várias seleções, então divide-se a condição e cada fragmento será movido separadamente. Essa estratégia deve ser a técnica de otimização mais efetiva, pois uma operação de seleção tende a reduzir bastante o tamanho de uma relação; 2ª. Novas projeções podem ser adicionadas na árvore para reduzir o comprimento das tuplas; 3ª. Há seleções que podem ser combinadas com o produto cartesiano sobre o qual estão operando, convertendo-se numa junção, cuja avaliação, em geral, é muito mais eficiente que a avaliação das duas operações separadamente. As Figuras 2-4 e 2-5 representam respectivamente as árvores de consulta inicial e otimizada para a consulta Q1 que: define a quantidade de fitas locadas e o valor faturado com locações, por bairro, anualmente, para filmes do gênero 'drama' e locações efetuadas a partir de 1993. 16 Q1: SELECT FROM WHERE GROUP BY bairro, anoloc, COUNT(*), SUM(valorlocfita) Loja, Locacao, Filme idloja=idlojaloc AND idfilme=idfilmeloc AND anoloc≥1993 AND genero='drama' bairro, anoloc Para a construção da árvore de consulta otimizada foram aplicadas as três heurísticas apresentadas: a 1ª (por exemplo, σanoloc≥1993 na relação LOCACAO); a 2ª (por exemplo, πidloja, bairro na relação LOJA) e a 3ª (por exemplo, a troca de duas operações por uma). γbairro, anoloc, COUNT(*)Ænloc, SUM(valorlocfita)Ævfat π bairro, anoloc, valorlocfita σ idloja= idlojaloc AND idfilme= idfilmeloc AND anoloc≥1993 AND genero='drama' X X Loja Filme Locacao Figura 2-4: Árvore de Consulta Inicial. γbairro, anoloc, COUNT(*)Ænloc, SUM(valorlocfita)Ævfat π bairro, anoloc, valorlocfita idfilmeloc=idfilme π idfilme idloja=idlojaloc π idloja, bairro Loja π idlojaloc, idfilmeloc, anoloc, valorlocfita σ genero='drama' σ anoloc≥1993 Filme Locacao Figura 2-5: Árvore de Consulta Otimizada. Essas heurísticas permitem melhorar o desempenho de uma consulta, pois reduzem o tamanho de uma relação beneficiando operações como a junção, reduzem o comprimento das 17 tuplas permitindo um melhor aproveitamento dos buffers de memória e aumentam as opções de escolha de algoritmos para a implementação de operações. 2.4.2. Do Plano Lógico Otimizado para Planos de Execução O plano de execução de uma consulta inclui informações sobre os métodos de acesso disponíveis para cada relação, bem como os algoritmos a serem utilizados na execução dos operadores do plano. Freqüentemente, cada operação do plano lógico será representada por um operador do plano de execução, mas existem operadores que precisam ser acrescentados ao plano e estes não têm representação na álgebra relacional, por exemplo, o operador de ordenação. Os principais métodos de acesso e algoritmos podem ser encontrados em [Elm00] e [Gar00]. Para executar uma consulta, muitos planos de execução poderão ser obtidos devido à grande quantidade de métodos de acesso e algoritmos disponíveis. Para escolher o plano mais adequado é preciso saber seu custo. Assim sendo, o otimizador de consultas deve calcular e comparar o custo de execução de cada plano e escolher o de custo menor. Essa abordagem é a otimização baseada em custos que, geralmente, é calculado o custo do componente que tem maior importância, pois desta maneira chega-se a fórmulas menos complicadas. Entre os componentes existentes têm-se: o acesso a disco, o armazenamento dos dados, a computação, o uso de memória e a comunicação. 2.4.3. Ordem para Junções As leis algébricas para transformar uma árvore de consulta incluem uma regra comutativa e uma regra associativa para a operação de junção que aumentam o número de árvores equivalentes e, por conseguinte, o número de planos de execução será muito maior. Portanto, para minimizar esse problema foram definidas formas para representar uma árvore de consulta como a árvore de profundidade à esquerda e a árvore balanceada [Gar00], conforme ilustra a Figura 2-6. A árvore de profundidade à esquerda é a representação natural de um plano de execução porque favorece a execução dos algoritmos de junção (ordem linear), mas não favorece o paralelismo. O melhor paralelismo é verificado com a utilização de uma árvore balanceada 18 quando mais de uma junção pode ser executada ao mesmo tempo. U T R S Árvore de profundidade à esquerda R S T U Árvore balanceada Figura 2-6: Representações de Junções numa Árvore de Consulta. Para encontrar uma ordem de execução das operações de junção usando uma árvore de profundidade à esquerda pode ser utilizada a programação dinâmica. Essa abordagem proposta em [Sel79] tem como idéia central manter a cada passo da solução a junção de menor custo. 2.4.4. Abordagens para Enumerar Planos de Execução Para enumerar os planos de execução de um determinado plano lógico, as seguintes tarefas ajudam nesse processo: (i) definir a ordem das operações de junção; (ii) escolher um método de acesso, se existir, para cada relação; (iii) escolher um algoritmo para cada operação; (iv) inserir operações adicionais, por exemplo, a ordenação e (v) definir a forma como os resultados intermediários serão repassados de uma operação para a seguinte. As estratégias mais comuns para enumerar planos de execução são baseadas no estilo bottom-up que computam os custos de todos os modos possíveis de cada sub-expressão da árvore do plano lógico [Gar00]. Essas estratégias ou utilizam a programação dinâmica ou a enumeração no estilo Selinger [Sel79]. A programação dinâmica mantém para cada sub-expressão do plano lógico apenas o plano de menor custo. No estilo Selinger, mantém-se para cada sub-expressão não apenas o plano de menor custo, mas também outros planos que têm custo mais alto. Esses planos com custo elevado podem favorecer um resultado ordenado em uma ordem que poderá ser útil mais acima na árvore, chamada de ordem de interesse. Essas ordens ocorrem quando o 19 resultado da sub-expressão é ordenado pelos atributos de operações como a ordenação, a junção e o agrupamento. 2.4.5. Otimização de Consultas Paralelas O paralelismo para aplicações de bancos de dados paralelos pode ser alcançado em três níveis: paralelismo entre consultas (o nível de consulta), entre operações (o nível de operação) e dentro da operação (o nível de instrução) [DeW92, Yu98]. No paralelismo entre consultas, múltiplas consultas podem ser executadas em paralelo por diferentes processadores. No paralelismo entre operações, diferentes operações de uma consulta podem ser processadas em paralelo por diferentes processadores e, assim, o resultado de uma operação é enviado para outra operação. No paralelismo dentro da operação, uma operação pode ser processada por diferentes processadores em paralelo. Os dois últimos tipos de paralelismo são mais interessantes, por oferecerem maior desempenho; por outro lado, requerem maiores cuidados por parte do processamento. O trabalho de computação de uma consulta pode ser dividido usando três formas de paralelismo: independente, pipelined e particionado [Waq96], conforme ilustra a Figura 2-7. Com o paralelismo independente, algumas operações de uma consulta são processadas independentemente, de tal forma que não usam os dados produzidos entre elas. Por exemplo, em uma consulta com quatro junções, pode-se tomar duas a duas e executá-las de maneira independente e depois uma nova junção definirá o resultado final. Com o paralelismo pipelined, duas operações de uma consulta produzem e consomem dados entre elas, assim sendo é uma outra forma para conseguir o paralelismo entre operações. Com o paralelismo particionado divide-se a entrada em subconjuntos menores, utilizam-se diferentes processadores para processar cada subconjunto simultaneamente e, no final, combinam-se os resultados parciais produzidos compondo o resultado final. Este tipo de paralelismo também é uma outra forma para conseguir o paralelismo dentro da operação. Os três mecanismos de paralelismo apresentados podem ser utilizados juntos, buscando oferecer uma boa estratégia de processamento paralelo para consultas complexas [Yu98]. 20 Entrada de dados Operação 1 Processador b Entrada de dados ... Entrada de dados Operação n Processador k Operações adicionais Resultado Paralelismo Independente Entrada de dados Operação 1 Processador b ... Operação n Processador k Resultado Paralelismo Pipelined Operação 1 Processador b Entrada de dados Particionar . . . Combinar Resultado Operação 1 Processador k Paralelismo Particionado Figura 2-7: Mecanismos de Paralelismo. Segundo [Waq96], os benefícios da aplicação do paralelismo estão limitados a restrições existentes nos elementos utilizados durante o processamento paralelo. Por exemplo, uma tabela hash impõe restrições de tempo para uma operação de junção porque somente poderá ser utilizada quando estiver completamente construída. O tempo de execução de uma consulta ou operação pode ser reduzido com o uso de processadores e discos simultaneamente; porém, para um melhor aproveitamento desses recursos, as tabelas que fazem parte da operação são particionadas em vários fragmentos, que são distribuídos por diferentes processadores [DeW92]. Estes fragmentos permitem que os bancos de dados paralelos explorem a largura de banda de E/S de múltiplos discos para a leitura e escrita dos dados em paralelo sem a necessidade de técnicas especiais, tais como a RAID (Redundant Array of Independent Disks) [Pat88]. O processo de distribuição associa os fragmentos aos diversos processadores disponíveis e pode ser realizado com ou sem replicação de fragmentos. Assim, a base de dados pode apresentar-se totalmente replicada, parcialmente replicada ou sem replicação. As 21 técnicas de distribuição são: (a) circular, (b) por faixa de valores e (c) hash, ilustradas na Figura 2-8, conforme [Yu98]. P1 P2 D D ... o o ... o o o Pn P1 P2 D D D A .. G H .. N ... … Pn P1 P2 D D D T .. Z Pn ... D o o ... o o o (a) (b) (c) Figura 2-8: Distribuição de dados – (a) circular, (b) por faixa de valores e (c) hash A distribuição circular consiste da associação seqüencial de cada fragmento de uma relação sem levar em consideração o valor de qualquer atributo. Sua principal vantagem é o balanceamento de carga, mas não há garantias de que os processadores terão a mesma quantidade de trabalho porque porções de uma tabela podem ser utilizadas mais do que outras e os tamanhos dos resultados intermediários podem ser diferentes. Por outro lado, para acessos sofisticados, como operações de junção, haverá muito tráfego de comunicação entre os processadores para que os fragmentos sejam juntados corretamente. A distribuição por faixa de valores é o esquema de distribuição baseado em um atributo escolhido da relação, chamado de atributo de distribuição. O método consiste de dois passos. No primeiro passo, o domínio do atributo de distribuição é dividido em um número de intervalos e cada um desses intervalos é associado a um processador. No segundo passo, as tuplas da relação são distribuídas aos diferentes processadores. Este tipo de distribuição favorece consultas com condição de seleção sobre atributos de distribuição, pois apenas os processadores que contém os fragmentos que envolvem a condição serão necessários para a execução dessa seleção. O grande problema da distribuição por faixa de valores é que na definição dos intervalos não há garantia de um balanceamento de carga adequado, a não ser que seja feita uma análise da distribuição dos valores da relação. A distribuição por hash é o esquema de distribuição baseado na técnica de hashing, que consiste em aplicar uma função hash aos valores do atributo de distribuição. Embora possa ocorrer a distribuição de todas tuplas para um único ou poucos fragmentos, será muito menor essa ocorrência em relação à distribuição por faixa de valores, pois uma função hash 22 adequada tende a distribuir as tuplas de maneira balanceada e facilita sua localização. Este tipo de distribuição é bastante eficaz para o processamento paralelo da operação de junção [Yu98]. Outras questões sobre a distribuição de dados são o número de fragmentos e o balanceamento de carga de execução. Uma estratégia interessante é aumentar o número de fragmentos e, assim, pode-se ter um processamento mais rápido, uma vez que o tamanho de cada fragmento tende a ser menor. No entanto, esse aumento também aumenta o esforço para juntar os fragmentos e, se o tamanho do fragmento for muito pequeno, então o custo para iniciar os processos deverá ser significativo em comparação com o custo do processamento em um ambiente monoprocessado. Para tanto, muitas estratégias têm sido propostas para resolver o problema da execução balanceada, por exemplo, a estratégia conhecida como execução balanceada centralizada [Wil99]. A distribuição dos dados é a base para explorar o paralelismo nas consultas em bancos de dados paralelos. Em arquiteturas de memória distribuída, por exemplo, o princípio básico é o de execução onde estiver localizado o dado, de tal modo que a comunicação entre os processadores seja a mínima possível [DeW92]. A otimização de uma consulta paralela trata de técnicas de otimização apresentadas com maior nível de complexidade. Além de determinar a ordem das junções, os métodos de acesso apropriados e os algoritmos de cada operação, deve-se, também, considerar os níveis e mecanismos de paralelismo, a forma de distribuição dos dados e a escala e a alocação dos recursos. Uma escala determina a ordem de utilização dos recursos alocados, tais como memória e processadores, necessários para o processamento paralelo de uma consulta. Por exemplo, uma forma de alocação de processadores para executar as operações de junção seria processar cada junção separadamente usando todos os processadores e, assim, explorar o paralelismo dentro da operação. Uma outra forma de alocação poderia ser a execução em paralelo das junções, uma para cada conjunto de processadores, de tal forma que possa explorar o paralelismo pipelined e também os paralelismos entre operações e dentro da operação [Yu98]. 23 Na otimização de uma consulta paralela o problema de construir o melhor plano de execução pode ser resolvido usando uma das duas estratégias seguintes. Na primeira estratégia, um plano de execução é construído sem considerar a escala e a alocação de recursos, tal como é feito em um ambiente monoprocessado. Depois, produz-se a escala e a alocação para as operações descritas no plano de execução. Na segunda estratégia, tenta-se gerar os planos de execução e alocação ao mesmo tempo [Has96]. Os principais algoritmos paralelos que são utilizados no processamento de consultas podem ser encontrados em [Yu98]. 2.5. Considerações Finais O processamento de consultas é uma tarefa importante de um sistema de banco de dados e a maioria das pesquisas tem sido desenvolvida com o objetivo de melhorar cada vez mais o desempenho de execução das consultas. Os principais tópicos envolvidos com o processamento de consultas foram apresentados neste capítulo; a otimização de consultas mostra-se como o requisito mais importante e o mais discutido na literatura. Com o avanço tecnológico dos processadores e das redes de comunicação os sistemas bancos de dados começaram a ser utilizados em ambientes paralelos e distribuídos, oferecendo assim mais uma opção para melhorar a execução das consultas. Para tanto, surgiram os ambientes de troca de mensagens para a construção de aplicações paralelas. 24 3. Paradigma de Troca de Mensagens 3.1. Introdução A programação paralela teve início com os sistemas multiprocessados com memória compartilhada. Um dos problemas nesses ambientes é a dificuldade em implementar uma máquina em que todos os processadores acessem rapidamente qualquer parte da memória. Uma forma alternativa para esses sistemas é a construção de uma rede de computadores que se comunicam através de troca de mensagens. Em um ambiente com troca de mensagens, uma forma de programação é levada adiante usando uma linguagem de programação seqüencial de alto nível e uma biblioteca com rotinas para a troca de mensagens [Wil99]. Neste trabalho será utilizada esta opção que possui os seguintes mecanismos para a troca de mensagens: (i) criação de processos e (ii) envio e recebimento de mensagens. 3.2. Criação de Processos A criação de um processo pode ocorrer de modo estático ou dinâmico. No modo estático, o sistema executará um número fixo de processos que foram especificados antes de sua execução. No modo dinâmico, um processo pode ser criado, destruído e executado durante a execução do sistema. A última técnica é muito poderosa porque permite um melhor gerenciamento dos processos; porém, exige-se maior esforço computacional durante a criação de um processo. Na maioria das aplicações existe um processo (mestre) que controla os demais processos (escravos). O código executável de cada processo pode ser escrito usando o modelo SPMD (Single Program Multiple Data) ou o modelo MPMD (Multiple Program Multiple Data). A diferença básica entre os dois modelos é que no SPMD são utilizados processos estáticos que selecionam suas tarefas de um único programa executável e no MPMD, cada processo dinâmico executa suas tarefas localizadas em programas separados. 25 3.3. Envio e Recebimento de Mensagens O envio e recebimento de uma mensagem ocorrem através de rotinas de troca de mensagens. Em cada rotina devem ser fornecidas a identificação do processo e a localização dos dados a serem enviados. Estas rotinas podem ser implementadas através de técnicas com bloqueio ou sem bloqueio. Uma rotina com bloqueio espera até que a mensagem seja enviada ou recebida e a rotina sem bloqueio não aguarda o recebimento da mensagem. Uma rotina sem bloqueio melhora o desempenho das aplicações paralelas porque a computação pode continuar enquanto as operações de comunicação estão em andamento. 3.4. Ambientes de Troca de Mensagens Existem dois ambientes principais que usam o paradigma de troca de mensagens: o PVM (Parallel Virtual Maquine) e o MPI (Message Passing Interface) [Wil99]. Esses ambientes foram concebidos para serem portáveis para sistemas heterogêneos. O PVM é um conjunto integrado de bibliotecas e de ferramentas de software com o objetivo de permitir que dois ou mais computadores conectados, apresentando, possivelmente, diferentes arquiteturas, possam trabalhar cooperativamente formando uma máquina paralela virtual. Seu projeto teve início em 1989 no Oak Ridge National Laboratory (ORNL). A partir da versão 2.0, liberada em 1991, outras instituições começaram a utilizar esse ambiente em aplicações científicas [Gei94]. Várias mudanças foram introduzidas no PVM para retirar erros de implementação e permitir o seu uso em novas plataformas. Atualmente, em sua versão mais recente, a 3.4.4, as aplicações podem ser desenvolvidas usando as linguagens Fortan, C e C++, oferecendo, também, suporte para as plataformas Unix e Windows. A escolha desse conjunto de linguagens deve-se ao fato de que as maiorias das aplicações sujeitas a paralelização estão escritas nessas linguagens. Este ambiente pode ser obtido no endereço http://www.csm.ornl.gov/pvm/. O MPI resultou de um esforço de padronização para tentar resolver os problemas de portabilidade que ainda persistiam nos ambientes de troca de mensagens existentes, como o PVM [Wil99], pois a maioria possui apenas um subconjunto das características necessárias para os diversos equipamentos fabricados, dificultando a tarefa de implementação em 26 diferentes máquinas. Assim, em 1992, o Comitê MPI definiu um padrão para esses ambientes com os seguintes objetivos principais: (i) melhorar a comunicação entre os processos evitando a cópia de dados e permitir sobreposição de computação e comunicação, quando possível; (ii) permitir que a aplicação possa ser executada em um ambiente heterogêneo sem a necessidade de mudança no código; (iii) utilizar as linguagens Fortran e C para a construção das aplicações paralelas; (iv) garantir a execução da aplicação sobre uma interface de comunicação confiável, sem a necessidade da interferência do usuário nas falhas de comunicação e (v) definir uma interface semelhante à do padrão PVM, Express, P4 ou Linda, provendo extensões para maior flexibilidade [Mpi95]. No MPI existem quatro versões, neste trabalho será descrita brevemente sua última versão, a MPI-2.0 [Mpi97] que dá suporte a E/S paralela através do padrão MPI-IO que é um recurso útil para a criação e manipulação de arquivos paralelos. 3.5. MPI-2.0 Um programa MPI é executado por processos autônomos (estáticos ou dinâmicos) que utilizam seu próprio espaço de endereçamento, embora implementações de memória compartilhada sejam possíveis. O código de cada processo pode ser diferente e a comunicação entre os processos ocorre através das rotinas de envio e recebimento de mensagens. Um processo é identificado pelo seu rank que corresponde a um número inteiro que varia entre zero e N - 1 (onde N representa a quantidade de processos ativos). A comunicação no MPI é realizada de modo seguro, de maneira que uma mensagem enviada por um processo não seja recebida por um outro indevidamente. Para tanto, utiliza-se um identificador de mensagem chamado de tag. Mas, seu uso incorreto pode ocasionar um problema de deadlock1. Um outro mecanismo para evitar o recebimento indevido de uma mensagem é a delimitação do domínio de comunicação entre os processos através da criação de grupos de processos, revelando-se como uma característica importante para a construção de aplicações complexas. 1 Deadlock ou impasse é um erro que ocorre quando dois processos ou mais ficam aguardando um pelo outro para que possam dar continuidade ao processamento. 27 O envio e o recebimento de mensagens no MPI é realizado através de rotinas de troca de mensagens. Entre as principais características dessas rotinas pode-se citar: com e sem bloqueio e com comunicação nos dois ou único sentido. A ocorrência de deadlocks em uma aplicação paralela é muito comum com rotinas de comunicação com um único sentido. O deadlock pode ser evitado quando se utiliza rotinas de comunicação em dois sentidos, que são formadas pela aglutinação de operações de envio e recebimento de mensagens consecutivas em uma única rotina. Desta maneira, as rotinas são ativadas ao mesmo tempo e executam concorrentemente, permitindo ganho de desempenho em determinadas situações. As rotinas de comunicação em dois sentidos são implementadas com a técnica de bloqueio e combinam-se com quaisquer outras rotinas de envio ou recebimento. Uma novidade no MPI-2.0 é a proposta de uma interface padrão para E/S paralela. MPI-IO, como é conhecido, suporta uma interface de alto nível para descrever o particionamento dos dados de um arquivo entre processos, fornece uma interface para transferência coletiva de dados entre arquivos e memória, suporta operações de E/S assíncronas, permite a sobreposição de computação com E/S e, resumindo, sua interface visa o estabelecimento de um padrão flexível, portável e eficiente para descrever as operações E/S independentes e coletivas realizadas pelos processos de uma aplicação paralela. Os sistemas de arquivos paralelos existentes suportam interfaces similares ao do MPIIO, mas as aplicações paralelas sobre esses arquivos não são portáveis [Tha99]. É difícil desenvolver aplicações científicas complexas, por exemplo, para cada geração de máquinas paralelas, devido às diferenças de desempenho. No padrão de E/S de arquivos do Unix, o acesso simultâneo a um arquivo por vários processos não é possível, a menos que seja realizado por pipes (um processo passa os dados a outro processo) e, ainda, de modo muito restrito. Este ambiente também não provê operações para o acesso a arquivos como é feito em memória, de forma não contígua. Estas restrições reduzem o desempenho de aplicações paralelas que exigem muito acesso a disco. Assim, o MPI-IO surge como uma tentativa de minimizar os problemas nessas aplicações. 28 MPI-IO é baseado na idéia que E/S em arquivo pode ser tratada como uma troca de mensagens. Desta maneira, escrever em um arquivo é como se estivesse enviando uma mensagem e ler em um arquivo é como se estivesse recebendo uma mensagem. A interface do MPI-IO foi desenvolvida com os seguintes objetivos: (i) primeiramente, atender às aplicações científicas, embora atenda a outros tipos de aplicações; (ii) permitir o uso de padrões sobre diferentes unidades de acesso; (iii) oferecer características aplicáveis ao mundo real, por exemplo, estruturas de acesso à memória e aos discos; (iv) permitir ao programador especificar informações de alto nível sobre E/S e (v) oferecer desempenho. Quando um arquivo é aberto com o MPI-IO, um comunicador2 é especificado para determinar que grupo de processos poderá acessar o arquivo. O acesso poderá ser independente, quando não existe um coordenador entre os processos, ou coletivo, quando todos os processos participam da mesma operação de E/S. Para tanto, define-se um esquema de particionamento usando tipos derivados dos tipos básicos do MPI. Desta forma, permite-se ampliar um padrão de acesso existente garantindo maior flexibilidade para as aplicações paralelas. Assim, um arquivo no MPI-IO pode ser definido como sendo uma coleção ordenada de itens de tipos de dados que são acessíveis seqüencialmente ou randomicamente. A seguir, serão apresentados os conceitos que determinam o esquema de particionamento de um arquivo paralelo, conforme ilustrados nas Figuras 3-1 e 3-2: • Deslocamento (displacement): É um valor que define a localização inicial dos dados para um processo. Esse valor também pode ser utilizado para determinar uma área para o cabeçalho de um arquivo; • Tipo elementar (etype): Uma unidade de acesso ou de posicionamento é representada por um dos tipos básico (por exemplo, numérico) do MPI, ou um tipo derivado de um básico, ou ainda um tipo estendido daquele; • Tipo de arquivo (filetype): Um tipo de arquivo é a base para o particionamento de um arquivo entre os processos e define um esquema de acesso ao arquivo. Este esquema é composto por uma quantidade de tipos elementares e buracos (os quais são de tamanhos múltiplos de um tipo elementar). Um tipo de arquivo básico repete2 Um comunicador é um domínio de comunicação que define um conjunto de processos que podem comunicar-se. Um comunicador global para todos processos no MPI é o MPI_COMM_WORLD. 29 se várias vezes, criando regiões de acesso permitidas (onde são definidos os tipos elementares) e regiões de acesso negado (onde são definidos os buracos) para os processos; • Visão (view): Define quais regiões de um arquivo que um processo terá acesso. Uma visão é descrita pelo deslocamento, tipo elementar e tipo de arquivo; • Posição relativa (offset): Determina o posicionamento dentro de um arquivo para uma visão corrente, expressa em quantidades de um tipo elementar; • Tamanho do arquivo (filesize): O tamanho de um arquivo no MPI é medido em bytes a partir do seu início. Tipo de arquivo Deslocamento E B E B Tipo elementar B B B E E B B B ... Buracos Figura 3-1: Componentes para um Esquema de um Arquivo no MPI-IO. Para melhorar o desempenho em uma aplicação paralela, o MPI-IO suporta sobreposição de computação com E/S em disco através das rotinas sem bloqueio para o acesso a dados. Essas rotinas mantêm a mesma idéia de funcionamento das rotinas com e sem bloqueios. Uma outra maneira de melhorar desempenho das aplicações é o acesso coletivo dos dados pelos processos, cada um obtendo seus próprios. Para tanto, deve-se definir, durante a abertura do arquivo, qual grupo de processos realizará esse tipo de acesso. Tipo elementar Tipo de arquivo – Processo 0 Tipo de arquivo – Processo 1 ... Deslocamento Figura 3-2: Exemplo de um Particionamento de um Arquivo entre 2 Processos. 30 Hoje em dia, são oferecidas várias implementações do padrão MPI entre as quais destacam-se HP-MPI, IBM-MPI, SGI-MPT e SUN-MPI. Contudo, parte de seu sucesso é creditado às duas primeiras implementações livres que foram oferecidas, que são a LAM e a MPICH. A LAM (Local Área Multicomputer) foi desenvolvida inicialmente pela Universidade de Ohio (Ohio Supercomputing Center), mas a partir da versão 6.2b, a Universidade de Notre Dame vem dando continuidade no projeto. A última versão disponibilizada recentemente é a 7.0 que provê alto desempenho para uma variedade de plataformas, que vão desde pequenos clusters com máquinas com um único processador até máquinas mutiprocessadas. Essa versão inclui a implementação completa do padrão MPI-1.2 e grande parte do MPI-2.0, como por exemplo, a criação de processos, MPIIO usando o ROMIO3, suporte a threads, compatível com C++, entre outras. No entanto, a LAM ainda não dispõe uma implementação para o ambiente windows e pode ser obtida nos endereços http://www.mpi.nd.edu/lam/download/ ou http://www.lam-mpi.org/. A MPICH surgiu durante o processo de padronização do MPI com o intuito de prover um feedback ao fórum de implementação. Suas primeiras versões desenvolvidas no Argonne National Laboratory e na Universidade do Estado do Mississippi atendem completamente o padrão inicialmente proposto. A versão recente da MPICH 1.2.5 tem evoluído para o ambiente windows (NT4/2000/XP Professional) com a implementação completa do padrão MPI-1.2 e partes do MPI-2.0. Entre os recursos existentes pode-se citar a implementação do padrão MPI/IO suportado através do ROMIO, o suporte para construção de programas no modelo MPMD, o uso de threads, a compatibilidade com C++, entre outras. O MPICH pode ser obtido no endereço http://www-unix.mcs.anl.gov/mpi/mpich/. 3 ROMIO é uma implementação portável do padrão MPI-IO para alto desempenho com suporte a diversos tipos de máquinas e sistemas de arquivos tais como IBM PIOFS, Intel PFS, HP HFS, SGI XFS, NEC SFS, NFS, NTFS, entre outros. 31 3.6. Métricas para Avaliar Programas Paralelos Na computação paralela, o aumento de velocidade de processamento obtida com o paralelismo é um dos fatores mais importantes. Para se calcular esse aumento, dois parâmetros são abordados na literatura: o speedup e a eficiência [Wil99]. O speedup (Sp) é utilizado para determinar o aumento de velocidade obtido durante a execução de um programa paralelo em relação ao seqüencial. Sp = Ts / Tp, onde: Ts = tempo para executar o melhor programa seqüencial; Tp = tempo para executar o programa paralelo utilizando p processadores. O caso ótimo é obtido quando Sp = p, ou seja, na medida em que aumenta o número de processadores, aumenta-se diretamente a velocidade de processamento. Fatores que influenciam a obtenção do caso ótimo estão relacionados principalmente com a comunicação entre os processadores e a parte seqüencial (aquela que não pode ser paralelizada) do algoritmo. A eficiência (Ep) determina o quanto que está sendo utilizado dos processadores, ou seja, quando Sp não é diretamente proporcional a p, há uma perda de desempenho. Ep = Sp / p, onde: p = é o número de processadores. A variação de Ep é entre zero e um, sendo que o valor um indica uma eficiência de 100%. 3.7. Considerações Finais Nos últimos anos, os ambientes de troca de mensagens tornaram-se bastante populares e muitos ambientes têm sido propostos e desenvolvidos com o objetivo de melhorar cada vez mais o uso dos recursos computacionais. As principais características de ambientes de troca 32 de mensagens foram apresentadas neste capítulo; a padronização mostra-se como um requisito importante e o mais discutido nesses tipos de ambientes. Escolher um ambiente de troca de mensagens para o desenvolvimento de aplicações paralelas não é uma tarefa simples e deve ser feita analisando a real necessidade da aplicação. Levando em consideração apenas a necessidade de acesso paralelo aos arquivos, o MPI-IO possui as especificações e implementações para atender essa necessidade. No caso do PVM, mesmo existindo alguns projetos nesta área, ainda não houve resultados. Com o crescimento dos bancos de dados, muitas maneiras têm surgido para melhorar o desempenho da execução de consultas; o uso do paralelismo através de ambientes de troca de mensagens é um exemplo. A aplicação do agrupamento prévio também é uma outra forma que surgiu como mais uma alternativa para reduzir o tempo de execução das consultas sobre grandes volumes de dados. 33 4. Agrupamento Prévio e Filtro Seletivo 4.1. Introdução A agregação é uma operação importante em uma consulta para construir dados précomputados. Esses dados construídos podem ser utilizados pelo usuário ou armazenados em data warehouses, que posteriormente poderão ser consultados. Uma agregação pode ser dividida em dois passos. O primeiro passo é o agrupamento que consiste em localizar os registros do banco de dados que compartilham os mesmos valores sobre o atributo de agrupamento. O segundo, é avaliar a agregação, aplicando-se as funções para os grupos encontrados. No restante do capítulo será apresentada uma técnica para otimizar consultas envolvendo a operação de agregação. Além disso, serão detalhadas as principais estratégias estudadas para o processamento seqüencial e paralelo de consultas com agregados. 4.2. Aplicando o Agrupamento Prévio As consultas formuladas em SQL para data warehouses, em geral, utilizam a cláusula GROUP BY e funções de agregação. Como exemplo, a consulta Q1, já apresentada anteriormente, tem esta característica. Q1: SELECT FROM WHERE GROUP BY bairro, anoloc, COUNT(*), SUM(valorlocfita) Loja , Locacao, Filme idloja= idlojaloc AND idfilme= idfilmeloc AND anoloc≥1993 AND genero='drama' bairro, anoloc Os dados são particionados em grupos (locações anuais por bairro) e agregados sobre atributos (quantidade de locações e valor faturado). A eficiência de tais consultas deve ser tratada com muita atenção, embora muito pouco tenha sido feito para melhorar seu desempenho. 34 Os processadores de consultas em bancos de dados relacionais ainda exploram o modelo tradicional para o processamento de consultas, isto é, a execução do operador de junção antes do operador de agrupamento, conforme ilustra a Figura 4-1. γbairro, anoloc, COUNT(*)Ænloc, SUM(valorlocfita)Ævfat π bairro, anoloc, valorlocfita idfilmeloc=idfilme π idfilme idloja=idlojaloc π idloja, bairro Loja π idlojaloc, idfilmeloc, anoloc, valorlocfita σ genero='drama' σ anoloc≥1993 Filme Locacao Figura 4-1: Árvore de Consulta Otimizada. Em dois importantes trabalhos, Yan e Larson [Yan94a] e Chaudhuri e Shim [Cha94], mostram como o operador de agrupamento pode ser executado antes do operador de junção. A aplicação desta estratégia visa reduzir o número de tuplas antes da execução do operador de junção; desta forma, o desempenho da consulta pode ser melhorado. A diferença entre os dois estudos é a maneira como é realizada a otimização. No trabalho de Yan e Larson [Yan94a], utiliza-se à transformação de uma consulta em SQL em outras duas: uma com o operador de agrupamento e a outra sem este operador. O resultado da consulta é determinado pela junção dos resultados obtidos com a execução das duas consultas. Devido ao fato de se estar tratando duas consultas separadamente, a ordem das junções fica restrita, reduzindo o espaço de busca pelo otimizador. No estudo apresentado por Chaudhuri e Shim [Cha94], generaliza-se a transformação apresentada por [Yan94a]. Busca-se transformar um plano de consulta otimizado em outro equivalente. Na construção do plano equivalente, a ordem das junções e dos agrupamentos é considerada, diminuindo, desta forma, o problema do espaço de busca pelo otimizador. Para 35 tanto, utiliza-se uma heurística que ajuda encontrar um plano de execução eficiente a partir de sub-planos, ótimos ou não, e, no final, é escolhido um plano considerado o melhor. Um exemplo da aplicação do agrupamento prévio está ilustrado na Figura 4-2. Neste novo plano, um agrupamento é executado antes da junção, causando diminuição no custo da junção. γbairro, anoloc, SUM(nloc), SUM(vfat) idfilmeloc=idfilme γidfilmeloc, bairro, anoloc, SUM(nloc), SUM(vfat) π idfilme idloja=idlojaloc π idloja, bairro σ genero='drama' γidlojaloc, idfilmeloc, anoloc, COUNT(*)Ænloc, SUM(valorlocfita)Ævfat Filme Loja σ anoloc≥1993 Locacao Figura 4-2: Árvore de Consulta Otimizada com Agrupamento Prévio. Levando em consideração a massa de dados de exemplos do Apêndice A, pode-se chegar a alguns resultados interessantes neste novo plano. Por exemplo, se a quantidade de registros que satisfazem as condições de seleção das tabelas LOCACAO e FILME são respectivamente 900.000 e 1.000 registros, conclui-se que no plano tradicional não haverá nenhuma redução do número de tuplas para as operações de junção. A primeira junção utilizará 16 e 900.000 registros. Mas, optando-se pelo plano com agrupamento prévio, então, na mesma junção, serão usados 16 e 160.000 registros. A redução do número de registros de 900.000 para 160.000 é possível porque o primeiro operador de agrupamento agrupa os registros sobre os atributos idlojaloc, idfilmeloc e anoloc. Além disso, a segunda junção será beneficiada por uma nova redução, pois um outro operador de agrupamento será executado antes da junção. 36 A seguir, apresentam-se os tipos de transformação, as definições e as condições necessárias para aplicação do agrupamento prévio segundo os estudos em [Cha94]. 4.2.1. Tipos de transformação Existem três tipos de transformação para o agrupamento prévio: o invariante, o de união simples e o de união generalizada. No agrupamento invariante o operador de agrupamento é movimentado da raiz para algum nó interno da árvore de consulta. No agrupamento de união simples, novos operadores de agrupamento são adicionados à árvore de consulta, correspondendo a uma generalização do agrupamento invariante. Os operadores de agrupamento adicionais são necessários tendo em vista que as operações subseqüentes poderão gerar mais de uma tupla por grupo. Por esta razão, estas tuplas precisam ser juntadas ao mesmo grupo. A união é possível porque as funções de agregação podem ser calculadas sobre partições. O agrupamento de união generalizada é um tipo de agrupamento de união simples; a diferença é que para cada operador de agrupamento adicionado no plano, também é adicionado um contador de tuplas por grupo. Para aplicar esta transformação, as funções de agregação devem ser calculadas conforme as seguintes derivações: MIN(N,s)=s, MAX(N,s)=s, AVG(N,s)=s, SUM(N,s)=N*s, e COUNT (N,s)=N, onde N é o número de tuplas s em um grupo. Cada transformação apresentada está em uma ordem de generalidade e complexidade crescentes. As transformações adicionam complexidade para a construção do plano de execução da consulta em relação aos planos tradicionais, pois o espaço de busca por uma solução é muito maior. Com isto, alguns problemas surgem: (i) a quantidade de planos de execução equivalentes cresce rapidamente quando se considera às ordens de interesse das junções e dos agrupamentos; (ii) é difícil construir um plano que satisfaça, a um mesmo tempo, as orderns de interesse das junções e dos agrupamentos e (iii) se os operadores de agrupamento são implementados usando uma estratégia de ordenação, o número de ordens de interesse cresce. 37 As transformações apresentadas devem ser aplicadas levando em consideração um modelo de estimativa de custos, sem esquecer, no entanto, os problemas apresentados; senão, o custo da enumeração de planos pode ser proibitivo. 4.2.2. Conjuntos necessários para a transformação Para aplicar uma das transformações descritas anteriormente é necessária a apresentação de algumas definições. Por exemplo, em uma consulta SQL com agregados, analisam-se seus componentes (atributos de agrupamento e de agregação) e anotam-se informações na árvore de consulta. Essas anotações são úteis para determinar o tipo de agrupamento prévio a empregar. Em cada nó da árvore de consulta, são definidos três conjuntos: colunas de junção, colunas requeridas e colunas candidatas à agregação. As colunas de junção são as colunas de um nó do plano que participam em predicados de junção que são avaliados por nós ancestrais. As colunas requeridas compreendem as colunas de junção de um nó do plano mais as colunas de agrupamento da consulta. As colunas candidatas à agregação são as colunas de um nó do plano que também são colunas de agregação da consulta, mas não estão entre as colunas requeridas. Esta definição exclui as colunas de agregação que participarão de predicados futuros, embora sejam colunas requeridas, devendo ser preservadas no plano por um nó até que os predicados sejam avaliados. As colunas que serão utilizadas em um processamento subseqüente estarão entre as colunas requeridas e as colunas candidatas à agregação. 4.2.3. Identificando o nó de transformação O nó da árvore de consulta que será escolhido para a aplicação do agrupamento prévio deverá satisfazer uma ou mais condições dentre as apresentadas a seguir: (i) cada coluna de agregação de uma consulta está no conjunto de colunas candidatas à agregação de um nó candidato da árvore de consulta; (ii) cada coluna de junção do nó candidato da árvore de consulta pertence ao conjunto de colunas de agrupamento de um consulta e (iii) os nós ancestrais ao nó candidato avaliam cada predicado de junção sobre uma chave estrangeira. 38 A primeira condição não permite que o nó candidato realize a agregação sobre colunas que pertencem a outras tabelas; pois, se assim fosse, tuplas poderiam ser perdidas na agregação. Neste caso, esta condição será satisfeita quando uma operação de junção determina a união das colunas de agregação da consulta avaliada. As outras condições determinam o nó apropriado de tal forma que as junções subseqüentes não resultem em mais que uma tupla para um mesmo grupo, pelo fato das junções subseqüentes serem feitas sobre chaves estrangeiras. Um nó apresenta a propriedade de agrupamento invariante quando satisfaz as três condições. Seus nós ancestrais também terão esta propriedade; desta forma, qualquer um poderá ser escolhido para a movimentação do operador GROUP-BY, constituindo, assim, uma importante informação sob o ponto de vista do espaço de execução pelo otimizador. No agrupamento de união simples, não há obrigação que as colunas de junção sejam iguais às colunas de agrupamento da consulta correspondentes, dispensando a segunda condição. Desta forma, o nó escolhido só precisa satisfazer as condições 1 e 3. Um nó será identificado com a propriedade de agrupamento de união generalizada quando algumas colunas de agregação da consulta estão presentes neste nó, satisfazendo de forma parcial a condição 1. Com o resultado desta discussão fica fácil deduzir que a aplicação da estratégia de agrupamento prévio deve reduzir significativamente o tempo de execução de uma operação de junção e, conseqüentemente, melhorar o desempenho de execução da consulta. A árvore de consulta com agrupamento prévio é construída a partir da identificação dos três conjuntos, conforme apresentados anteriormente. Por exemplo, na árvore de consulta representada na Figura 4-1, esses conjuntos são os seguintes para o nó LOCACAO: • Colunas de junção: idlojaloc, idfilmeloc; • Colunas requeridas: idlojaloc, idfilmeloc, anoloc e • Colunas candidatas à agregação: *,valorlocfita. 39 Assim, configuração do operador de agrupamento para o nó LOCACAO será definida usando as seguintes regras: (i) as colunas requeridas são transformadas em colunas de agrupamento e (ii) as colunas candidatas à agregação são transformadas em colunas de agregação. Esta configuração está representada na Figura 4-2. Considerando uma variação da consulta Q1, onde a condição de seleção (valorlocfita*numfitasloja>200) é adicionada, então o conjunto de colunas candidatas à agregação será vazio para os nós de LOCACAO e junções; portanto, não haverá o agrupamento prévio, enquanto não for avaliada esta condição. 4.3. Estratégias para o Agrupamento Prévio Nesta seção serão apresentados os principais trabalhos estudados sobre o processamento seqüencial e paralelo de agregados. Além disso, será apresentada uma nova estratégia para ser utilizada junto com o agrupamento prévio, denominada de filtro seletivo. 4.3.1. Processamento Seqüencial de Agregados Em [Gra93] é relatada uma estratégia baseada em ordenação para execução da agregação. Nessa estratégia, os resultados, enviados a um arquivo temporário, somente serão utilizados quando todas as entradas forem processadas. Graefe apresenta três algoritmos para o processamento do agrupamento baseados em: (i) loop aninhado, (ii) ordenação e (iii) hashing, respectivamente. Os mais utilizados em bancos de dados relacionais são os dois últimos, por apresentarem melhor desempenho para grandes volumes de dados. Em [Yan94b] é apresentada uma estratégia para o agrupamento usando o método ordenação-mesclagem (sort-merge), chamada de agregação ansiosa. A idéia consiste em aplicar a agregação na fase de ordenação, usando uma tabela heap, com o objetivo de reduzir o número de tuplas durante a fase de mesclagem e, conseqüentemente, o acesso a disco. Outras vantagens são a possibilidade de descartar a fase de mesclagem caso o número de grupos produzidos seja pequeno e reduzir a quantidade de níveis de processamento durante a mesclagem. No entanto, pode ocorrer de não haver agrupamentos durante a ordenação devido aos seguintes fatores: tamanho de memória menor que o número de grupos ou à distância 40 entre duas tuplas pertencentes ao mesmo grupo é sempre maior que o número de grupos que cabem na memória. Em [Lar97] são apresentados algoritmos que usam a idéia da agregação ansiosa baseados em: varredura repetida, ordenação e particionamento. Na agregação por varredura repetida, uma varredura completa da tabela de entrada define o primeiro conjunto de grupos em memória e também uma tabela temporária com as tuplas que não coincidem com esses grupos. Os demais grupos são formados usando a tabela temporária e termina quando todas tuplas são consumidas pelos grupos. O número de passos desta estratégia é definido em função do tamanho da memória e do número de grupos, que determina o custo de acesso a disco. Na agregação por ordenação, a ordenação somente ocorre quando os grupos forem gravados em disco, desta maneira deve-se utilizar pesquisa seqüencial para encontrar um grupo de uma determinada tupla. A agregação usando particionamento consiste em particionar a entrada em pequenos arquivos temporários através de uma função hashing sobre os atributos de agrupamento e, então, aplicar uma outra função hashing sobre cada partição. O objetivo dessa estratégia é criar partições contendo poucos grupos de maneira que o agrupamento final possa ser feito em memória. A agregação ansiosa é aplicada antes de gravar as partições em disco. Um problema desta estratégia é determinar o tamanho das partições de maneira que não desperdice memória sem aumentar o custo do acesso a disco. A estratégia que usa varredura repetida possui baixo desempenho quando menos de 45% dos grupos cabem dentro da memória. Para valores acima de 45%, o desempenho fica muito próximo da estratégia por particionamento. Dentre as três estratégias a que apresenta pior desempenho é a baseada em ordenação para todas configurações de memória e a melhor de todas é usando particionamento [Lar97]. 41 4.3.2. Processamento Paralelo de Agregados Em [Bit83] é apresentada uma estratégia para o processamento paralelo de agregados a qual foi implementada no projeto Gamma [DeW90], sendo baseada em um único coordenador central. O resultado do processamento em cada nó é enviado para o coordenador central que compõe o resultado final. Esta abordagem é indicada para consultas em que o número de grupos gerados é pequeno, pois, caso contrário, o coordenador central ficará sobrecarregado. É apresentada, também, a estratégia baseada no método de broadcast que evita o problema da sobrecarga de trabalho do coordenador central, pois ao invés de apenas um nó fazer a composição do resultado final, composições parciais intermediárias são realizadas através de um esquema de merge binário, em pipeline; mas, no final, um nó fará a composição do resultado final. Este esquema é realizado em log2N - 1 passos de distribuição, onde N é o número de processadores, sendo ideal para agrupamentos que não produzem muitos grupos, pois caso contrário, a fase merge será o gargalo. Em [Sha94] são apresentadas duas estratégias para o processamento de agregados: agregação de duas fases e agregação por redistribuição. Na agregação de duas fases, primeiro, realiza-se a agregação local em cada processador e, depois, os grupos produzidos são distribuídos entre os processadores usando uma função hashing. Nesta fase, cada processador, em paralelo, produzirá o resultado final das tuplas pertencentes a um mesmo grupo. Na agregação por redistribuição, o agrupamento é feito em uma única fase. Esta estratégia deve ser utilizada em redes de alta velocidade e seus problemas são relacionados à ociosidade em alguns processadores devido a dois fatores: o número de grupos formados é menor que o número de processadores ou os grupos são distribuídos a alguns processadores. Com relação às estratégias apresentadas têm-se as seguintes conclusões: (i) para muitos grupos formados, a estratégia de duas fases é um pouco inferior em relação à redistribuição, mas, se pouca memória estiver disponível, o desempenho da estratégia de redistribuição é superior; (ii) para poucos grupos produzidos, a estratégia de duas fases é a melhor alternativa, mesmo existindo pouca memória e (iii) aumentando-se a quantidade de nós, a estratégia de duas fases tem desempenho muito próximo ao da estratégia de redistribuição. A escolha de uma das estratégias deve ser feita baseada na estimativa do número de grupos, que nem sempre está disponível, mas, se estiver, pode ser imprecisa. 42 Para evitar uma escolha errada de uma das estratégias apresentadas em [Sha94], foram apresentados os algoritmos adaptativos em [Sha95]. A idéia consiste em trocar o algoritmo por outro durante o processamento do agrupamento conforme varia a carga de trabalho. Esta estratégia deve ser evitada em redes de banda limitada. Em [Tan00a] são apresentados três algoritmos para o processamento de agregados usando uma única tabela. Estes algoritmos seguem as estratégias apresentadas em [Sha94], sendo que os dois primeiros são propostos para arquiteturas de memória distribuída e o último é especializado para uma arquitetura em cluster (cada nó com memória compartilhada). Além disso, outro ponto é aplicação ou não da agregação local antes da distribuição. No algoritmo de duas fases, o número de grupos produzidos reduz a comunicação entre os nós, mas, se a redução do número de tuplas for mínima, então, a agregação local não oferecer benefícios. O algoritmo para a arquitetura em cluster divide o processamento em duas etapas: na primeira, o processamento local de cada nó é feito usando a estratégia de redistribuição e, na segunda, a comunicação entre os nós é realizada através da estratégia de duas fases. O algoritmo teve melhor desempenho nesta arquitetura devido a dois fatores: (i) a redistribuição, na fase inicial, ocorre dentro do nó usando memória compartilhada e (ii) a comunicação entre os nós é feita após o agrupamento local. Em [Tan00b] são apresentados três algoritmos para o processamento de agregados usando mais de uma tabela que exploram a questão da escolha do atributo de distribuição (junção/agrupamento). Para tanto, duas estratégias foram apresentadas: distribuição por junção e distribuição por agregação. Na distribuição por junção, usa-se o atributo de junção das duas tabelas para a distribuir os dados entre os processadores e após esse processo são realizadas a junção local e a agregação local. No final, os resultados parciais são redistribuídos usando o atributo de agrupamento para a composição do resultado final. Na distribuição por agregação, a tabela com o atributo de agrupamento é distribuída e a outra tabela é replicada. 43 Essas estratégias foram implementadas em uma arquitetura de memória distribuída e também em uma arquitetura em cluster (cada nó com memória compartilhada), onde cada nó usa a estratégia de distribuição por agregação para seu processamento local e a comunicação entre os nós é feita utilizando a distribuição por junção. Na arquitetura em cluster, a replicação de uma tabela é feita usando a memória compartilhada, favorecendo esta tarefa. Em [Tan01] são apresentadas duas estratégias para o processamento de consultas na qual os atributos de junção e agrupamento são iguais. Estas estratégias, implementadas sobre uma arquitetura de memória distribuída, assemelham-se com as estratégias apresentadas em [Sha94] com relação à aplicação ou não da agregação local antes da distribuição. As estratégias serão apresentadas usando a seguinte notação para as tabelas envolvidas: R é tabela que contém pelo menos um atributo de agregação e S não contém esse atributo. A primeira estratégia, chamada de distribuição preguiçosa, é dividida em três fases: agregação local, distribuição e agregação/junção final. A agregação local consiste em realizar a operação de agregação sobre R em cada processador; depois, os resultados parciais são distribuídos juntamente com S baseado no atributo de junção/agregação. Finalmente, é realizada a agregação de R e a junção com S. O custo da comunicação existente na fase de distribuição depende do número de grupos produzidos de R e se esse número for inferior ao número de processadores, o desempenho durante a etapa paralela é reduzido. A distribuição ansiosa é ideal para redes de alta velocidade e divide-se em duas fases: distribuição e agregação/junção. Na distribuição particiona-se R e S com base no atributo de agrupamento/junção para cada processador. Na segunda fase, uma operação de agregação sobre R define o resultado que deverá ser juntado com a tabela S. Depois da junção, cada nó terá o resultado da consulta local. O resultado final da consulta é a união dos resultados produzidos em cada nó. 4.3.3. Usando um Filtro Seletivo O agrupamento prévio é uma técnica que pode reduzir o custo de uma consulta, sendo importante a sua aplicação em bancos de dados com muitas informações. Geralmente, essas consultas realizam algum tipo de seleção sobre os dados que devem ser executadas o quanto antes pelo processador de consultas. Esta seção apresenta um filtro seletivo para ser usado 44 durante a aplicação do agrupamento prévio. Esse filtro está baseado nas idéias apresentadas em [Wei02] para o processamento de consultas envolvendo junções. Costa Neto apresenta em [Cos01] idéias para reduzir o tempo de execução de uma consulta e evitar a sua interferência no banco de dados de produção. Estas idéias estão baseadas na criação e manipulação de visões materializadas sobre uma tabela de fatos. O filtro seletivo poderá também ser aplicado durante a criação de visões materializadas. A consulta Q2 será usada para exemplificar o filtro seletivo que determina mensalmente a quantidade de locações de filmes do gênero 'faroeste'. Seu plano de consulta está ilustrado na Figura 4-3 e será executado usando a massa de dados de exemplos do Apêndice A. Q2: SELECT FROM WHERE GROUP BY nomefilme, anoloc, mesloc, COUNT(*) Filme, Locacao idfilme=idfilmeloc AND genero='faroeste' nomefilme, anoloc, mesloc Se 200 filmes são do gênero 'faroeste', então o plano da consulta Q2 produzirá 26.400 registros. O custo dominante do plano deverá ser sobre a tabela LOCACAO (tabela de fatos), na qual serão produzidos 264.000 registros pelo operador de agrupamento. Essa consulta possui duas características essenciais para o uso do filtro seletivo: (i) envolve uma tabela de fatos e (ii) pelo menos uma de suas tabelas de dimensão possui ao menos uma condição de seleção. π nomefilme, anoloc, mesloc, nloc idfilme =idfilmeloc π idfilme γidfilmeloc, anoloc, mesloc, COUNT(*)Ænloc σ genero='faroeste' Locacao Filme Figura 4-3: Plano de Consulta Otimizado para Q2 com Agrupamento Prévio. Um filtro seletivo tem por objetivo reduzir o número de grupos durante a execução do primeiro agrupamento prévio. Os grupos possivelmente descartados, da tabela de fatos, não 45 serão necessários para as operações subseqüentes do plano de uma consulta, por exemplo, na junção da consulta Q2; assim sendo, reduz-se o custo da consulta. Portanto, os 264.000 grupos produzidos pela agregação deverão ser reduzidos em 90%, ficando apenas 26.400 grupos para serem processados pela operação de junção. Esta quantidade de grupos, provavelmente, poderá ser processada por uma estrutura que caiba na memória principal. Para utilizar o filtro seletivo são consideradas duas fases: (i) fase de construção e (ii) fase de filtragem. Na fase de construção, constrói-se um vetor de bits para cada tabela de dimensão com condições seletivas. Cada posição do vetor de bits é, inicialmente, preenchida com 0 (zeros). Durante a varredura da tabela de dimensão preenche-se com 1 as posições que satisfazem a condição de seleção. Por exemplo, para a consulta Q2 existirão 200 posições no vetor marcadas com 1. Estas marcas são determinadas através de uma função de hashing que mapeia o atributo chave para um valor v dentro da faixa {0, . . ., n – 1}, onde n representa a quantidade de posições do vetor de bits. O tamanho do vetor de bits deverá ser, de preferência, do mesmo tamanho da tabela de dimensão, mas se essa tabela for relativamente grande pode-se determinar um tamanho menor. Neste caso, uma mesma posição será mapeada por diferentes valores e, assim, os chamados falsos positivos poderão existir. Se uma tabela de dimensão apresentar mais de uma condição de seleção, mesmo assim, apenas um vetor de bits precisa ser construído. Para tanto, os operadores conjuntivos (OR) e disjuntivos (AND), existentes em uma consulta, deverão ser aplicados durante a construção do vetor de bits. A Figura 4-4 ilustra dois vetores de bits para a consulta Q2 que se diferenciam nos tamanhos. A fase de filtragem corresponde à utilização do filtro seletivo e deve ser realizada conforme a seguir. À medida que os registros da tabela de fatos são percorridos para a execução do agrupamento, deve-se aplicar à mesma função de hashing utilizada pela fase de construção nos valores dos atributos de ligação e os resultados são comparados com os bits do vetor de bits. Se a posição de comparação tiver marcada com um zero, então o registro correspondente da tabela de fatos será descartado, caso contrário, o registro é enviado para o operador de agregação. A Figura 4-5 ilustra a fase de filtragem usando a consulta Q2. 46 idfilme mod 2000 Falsos positivos Filme Idfilme 1 2 3 4 5 6 7 8 9 ... ... Genero ... 'drama' ... 'faroeste' ... 'bíblico' ... 'policial' ... 'faroeste' ... 'faroeste' ... 'guerra' ... 'policial' ... 'bíblico' ... ... Censura 16 12 0 16 12 15 16 16 0 ... 0 0 1 0 0 1 1 0 0 0 ... 0 0 1 2 3 4 5 6 7 8 9 ... 1999 Vetor de bits sem falsos positivos σ genero='faroeste' 1 1 1 0 0 0 1 2 3 4 Vetor de bits com falsos positivos σ genero='faroeste' idfilme mod 5 Figura 4-4: Exemplo de Construção de Vetores de Bits para a Consulta Q2. Assim, o filtro seletivo é um mecanismo para melhorar o desempenho durante a aplicação do agrupamento prévio. π nomefilme, anoloc, mesloc, nloc 0 0 1 0 0 1 1 0 0 0 ... 0 0 1 2 3 4 5 6 7 8 9 ... 1999 Vetor de bits sem falsos positivos idfilme =idfilmeloc π idfilme Filtragem (idfilmeloc mod 2000) γidfilmeloc, anoloc, mesloc, COUNT(*)Ænloc σ genero='faroeste' Locacao Filme Figura 4-5: Fase de Filtragem para a Consulta Q2. 4.4. Considerações Finais Com o crescimento do número de aplicações que tratam dados para o gerenciamento nas empresas, cada vez mais se faz necessária a adoção de estratégias para minimizar os problemas do processamento e armazenamento de grandes volumes de dados. Entre estas estratégias, tem-se a aplicação do paralelismo e, mais recentemente, a utilização do agrupamento prévio como mais uma alternativa para a otimização de consultas com agregados. Um filtro seletivo também é apresentado como sendo uma nova opção para melhorar o agrupamento prévio e conseqüentemente o desempenho das consultas. 47 A construção de um ambiente de software para executar consultas constitui um desafio. Assim, o próximo capítulo apresentará a especificação e o projeto de um ambiente para a execução de consultas paralelas com a aplicação do agrupamento prévio e do filtro seletivo. 48 5. FAP: Um Ambiente para a Execução de Consultas Paralelas com Agregados 5.1. Introdução Para analisar o desempenho de uma consulta com agregados faz-se necessária a disponibilidade de um ambiente que proporcione a sua execução. Para tanto, apresenta-se o FAP, que é um ambiente de software responsável pela execução de consultas com a aplicação do paralelismo, do agrupamento prévio e de um filtro seletivo. Sua arquitetura alvo consiste de um conjunto de estações de trabalho com memória distribuída que se comunicam através de troca de mensagens. Uma implementação portável do FAP possibilita a escolha de um ambiente de troca de mensagens para o gerenciamento da comunicação entre as estações de trabalho e para a manipulação de arquivos paralelos. Isso é possível graças a duas características presentes no projeto do FAP: a modularidade existente na estrutura do ambiente e a flexibilidade nas classes de software. Considerando o grande número de estratégias existentes para a implementação das operações de junção e agregação, outra característica desse ambiente é a possibilidade do acréscimo de novas estratégias para as operações de uma consulta. Assim, permite-se que um usuário escolha as estratégias que sejam de seu interesse para serem analisadas. Na concepção e no desenvolvimento do FAP, foram desenvolvidas ações que se preocuparam com a resolução dos três problemas seguintes: (i) determinar estratégias para a execução eficiente de consultas com agregados envolvendo muitos dados; (ii) criar um ambiente de dados paralelos, composto de arquivos paralelos para a execução das consultas e (iii) oferecer mecanismos para a construção, otimização e execução de consultas. Com a resolução destes três problemas tem-se favorecida a execução de consultas, dando oportunidades para a análise de desempenho dessas consultas. 49 5.2. Execução de Consultas com Agregados A execução eficiente de uma consulta envolvendo o operador de agrupamento sobre grandes quantidades de dados requer a aplicação de estratégias para esse operador. Neste contexto, o FAP oferece mecanismos para a otimização de consultas usando estratégias para as operações de junção e agregação. Além disso, o processamento paralelo é uma outra forma de melhorar o desempenho das consultas. No restante do capítulo são discutidas as idéias do ambiente proposto, para a resolução dos três problemas anteriormente apresentados. 5.2.1. Estratégias para a Junção e Agregação As estratégias para a execução das operações de junção e agregação utilizadas no FAP são discutidas nesta seção. Estas estratégias são versões seqüenciais, tendo em vista que o ambiente explora a localidade dos dados para o processamento de uma consulta. Para a operação de junção estão disponíveis três estratégias: junção de laço aninhado usando blocos, junção baseada em fusão e junção baseada em função hash. A junção de laço aninhado usando blocos é a estratégia mais simples e o mais genérica, pois pode ser empregada com qualquer condição de junção e com tabelas de qualquer tamanho. Seu desempenho depende da quantidade de memória para a tabela do laço externo. Para isto, utilizou-se a seguinte fórmula: buffersexterno=buffersmáximo – 2, sendo reservado um buffer para o laço interno, um para os resultados e os demais para o laço externo. Na junção baseada em fusão considera-se que as tabelas estão ordenadas sobre o atributo de junção e a condição de junção ocorra na igualdade. Desta forma, os registros das tabelas podem ser percorridos ao mesmo tempo, compondo um resultado também ordenado. Como essa estratégia exige menos memória que a anterior, optou-se por reservar três buffers, um para cada tabela e um para os resultados. A junção baseada em função hash é, geralmente, a estratégia mais eficiente, sendo utilizada quando não existem índices para os atributos de junção. Para melhorar seu desempenho, optou-se por construir a tabela hash na memória principal usando a menor 50 tabela. Desta maneira apenas três buffers são necessários. As colisões são tratadas usando uma lista encadeada. A quantidade de entradas da tabela hash é determinada pelo usuário da aplicação e, assim, pode-se, por exemplo, analisar diferentes desempenhos para a execução de uma consulta. Essas estratégias usam algoritmos de uma única passagem. Sendo assim, exige-se apenas leituras dos dados em disco para completar a operação, conforme ilustra a Figura 5-1. R T R.a=S.a S buffer de entrada buffer de saída Figura 5-1: Junção de uma única Passagem. Os buffers de entrada, um para cada tabela, são preenchidos com os registros das tabelas R e S, quantas vezes for necessário, de acordo com o tipo de junção utilizada. A junção é aplicada nos buffers de entrada e seus resultados são enviados para o buffer de saída. Para a operação de agregação, escolheu-se algumas estratégias baseadas em: ordenação, varredura repetida, função hashing e árvore binária de busca. Todas se beneficiam, de algum modo, da agregação ansiosa. Assim, o objetivo é tentar reduzir a quantidade de dados intermediários transferidos entre memória principal e disco durante o processamento. As duas primeiras estratégias foram discutidas neste trabalho e as demais são exploradas a seguir. A agregação baseada em função hashing é uma estratégia bastante eficiente e, no melhor caso (sem colisões), o acesso é direto. Para cada entrada da tabela hash definiu-se uma árvore binária de busca para o tratamento de colisões, de forma que possa oferecer bom desempenho durante a busca dos grupos e, que, no pior caso, seja necessário percorrer todos os grupos da entrada. Além disso, o usuário da aplicação pode escolher a quantidade de entradas e, assim, realizar os testes com diferentes valores. 51 A agregação baseada em árvore binária de busca compreende em usar uma árvore binária de busca para o armazenamento dos grupos, assim a quantidade de nós determinará o número de grupos existentes na estrutura. Além disso, a ordem dos grupos na árvore poderá ser útil para uma outra operação, por exemplo, a junção baseada em fusão. Para que o desempenho da busca não seja ruim, esta estrutura é evitada se os atributos de agrupamento já estão ordenados. Isto pode ser testado através do n primeiros, médios e últimos registros da tabela de entrada. Outra forma é detectar o desbalanceamento da árvore durante a execução do agrupamento e trocá-la por outra estrutura. Novos estudos poderão apontar mecanismos para a decisão da aplicação deste tipo de agregação. A Figura 5-2 apresenta um resumo das estratégias de agregação apresentadas anteriormente. Estratégia Mesclagem Agrupamento Resultado dos Grupos Ordenados Partições de Grupos Ordenados Registros na Tabela R 1ª fase 2ª fase Figura 5-2: Esquema das Estratégias de Agregação. As estratégias de agregação podem utilizar algoritmos de uma única passagem. Mas, adotou-se um algoritmo de duas passagens, tendo em vista que as consultas deverão ser submetidas a grandes quantidades de dados. Um algoritmo de duas passagens carrega os dados do disco uma primeira vez, processa-os, grava todos ou parte deles no disco e, depois, faz uma segunda leitura para o processamento adicional durante a segunda passagem [Gar00]. Uma idéia geral para esse tipo de algoritmo é apresentada na Figura 5-3. O algoritmo de duas passagens possui duas fases: a partição e a mesclagem. Na fase de partição, os dados são lidos da tabela R (origem) até o tamanho máximo do buffer e depois são organizados de alguma forma e gravados em um arquivo temporário. Esse processo se 52 repete até serem processados todos os registros da tabela de origem, formando várias partições (arquivos temporários). O número de partições depende do tamanho do buffer e da tabela de origem. A fase de mesclagem compreende em obter repetidamente o menor valor do atributo de agrupamento presente entre os primeiros registros disponíveis nos buffers, aplicar a agregação e gravar o resultado final na tabela de destino. R R1 buffer S ... ... Rn buffers fase de partição fase de mesclagem Figura 5-3: Esquema de um Algoritmo de duas Passagens. Na estratégia baseada em ordenação utilizou-se o esquema da Figura 5-3, mas, para as estratégias baseadas em função hashing e árvore binária de busca, esse esquema foi modificado de forma que apenas três buffers são necessários para a fase de mesclagem, conforme ilustra a Figura 5-4. buffers ... ... S fase de mesclagem Figura 5-4: Um Esquema para a fase de Mesclagem no Agrupamento Prévio. Esta modificação constitui uma característica importante para ambientes com pouca memória disponível. A idéia consiste em processar um par de partições por vez ao invés de envolver todas as partições ao mesmo tempo. O processo termina quando não existem mais pares de partições a serem processados. Os grupos de cada partição são ordenados para 53 facilitar o processo de mesclagem. Espera-se, também, que o tamanho de cada partição resultante seja menor que a soma dos tamanhos das partições envolvidas, pois, desta maneira, alguns grupos serão unidos. 5.2.2. Aplicando o Agrupamento Prévio em Paralelo Para o processamento do agrupamento prévio em paralelo, pode-se adotar uma das estratégias apresentadas na seção 4.3. No entanto, para explorar a localidade dos dados em uma arquitetura de memória distribuída será utilizado um único coordenador central, conforme os estudos apresentados em [Bit83]. Desta forma, o objetivo principal da estratégia será o de minimizar a comunicação entre os nós durante o processamento da consulta. Ocorrerá comunicação entre os nós somente durante a distribuição do plano de execução e o recebimento dos resultados parciais entregues ao nó coordenador. A seguir serão apresentados os passos desta estratégia conforme o diagrama de execução que está ilustrado na Figura 5-5, onde Pn representa um nó. Composição do Resultado Final P1 Resultados Parciais Distribuição de um Plano de Execução de uma Consulta P2 P3 ... Pn Figura 5-5: Diagrama da Estratégia para o Agrupamento Prévio em Paralelo. 1º. O plano lógico de uma consulta é construído em um nó (nó emissor), otimizado aplicando-se heurísticas e a estratégia de agrupamento prévio e transformado no plano de execução; 2º. O plano de execução é distribuído entre os nós para ser executado, inclusive o nó emissor pode executá-lo; 54 3º. Cada nó processa todas as operações correspondentes ao plano de execução e envia os resultados parciais obtidos ao nó emissor e 4º. O nó emissor realizará a composição do resultado final da consulta. A escolha dos nós para a distribuição do plano de execução de uma consulta é feita de acordo com a consulta e informações existentes no catálogo do banco de dados. Além disso, o plano de execução deverá ser codificado antes de sua distribuição e decodificado após o seu recebimento. Assim, os nós precisarão conhecer o padrão de mapeamento existente entre a codificação e decodificação do plano. Esse padrão descreve as tabelas e operações do plano de tal modo que a sua decodificação seja realizada de maneira correta. 5.2.3. Aplicando o Filtro Seletivo A idéia de usar o filtro seletivo consiste em selecionar as tuplas que são de interesse para o agrupamento prévio. Desta forma, o custo de execução dessa operação é reduzido e conseqüentemente a consulta será executada em menos tempo. Para a aplicação do filtro seletivo são consideradas três fases, conforme descritas a seguir. A primeira fase consiste em determinar se a consulta tem as características necessárias para a aplicação do filtro. Para isto, deve-se determinar se na consulta existe uma operação de junção entre tabelas de dimensão e fatos e a tabela de dimensão possui pelo menos uma condição de seleção. Caso essas condições sejam satisfeitas então o vetor de bits é construído e suas posições são preenchidas com 0 (zeros). O tamanho do vetor de bits é definido como sendo o mesmo tamanho da tabela de dimensão ou um valor determinado pelo usuário. A segunda fase corresponde em marcar as posições do vetor de bits com 1. Essas posições são calculadas usando os valores do atributo chave das tuplas da tabela de dimensão que satisfazem a condição de seleção. Para o cálculo utiliza-se uma função de hashing. Essa fase é realizada antes da execução do primeiro agrupamento prévio. A terceira fase ocorre durante a execução do primeiro agrupamento prévio. Essa fase corresponde em selecionar as tuplas da tabela de fatos que possuem correspondência com o vetor de bits. Para isto, os valores do atributo chave de cada tupla são utilizados para o cálculo das posições do vetor de bits usando a mesma função de hashing da fase anterior. Caso a 55 posição calculada esteja marcada com 1 então a tupla é enviada para o operador de agrupamento. Com a aplicação do filtro seletivo espera-se que o agrupamento prévio possa ser executado usando-se uma estrutura que caiba na memória principal e, assim, minimizam-se os acessos a disco. Uma vez escolhidas as estratégias que serão utilizadas para a execução das consultas, deve-se oferecer mecanismos para a construção de um ambiente de dados para a execução dessas consultas. Além disso, esses mecanismos também devem favorecer a execução das consultas. Neste contexto, a seguir será apresentada a arquitetura do FAP que tem objetivo resolver essas questões. 5.3. Arquitetura do FAP A arquitetura do FAP, apresentada na Figura 5-6, é composta por dois módulos principais: o Cliente e o Servidor de Arquivos Paralelos [Pau03]. O módulo Cliente é responsável por receber as solicitações dos usuários de aplicação e prepará-las para serem executadas pelo módulo Servidor de Arquivos Paralelos. Além disso, o módulo Cliente também poderá realizar um pré-processamento, como em uma consulta. A divisão modular do FAP permite uma melhor distribuição do processamento de uma consulta, pois cada módulo estará envolvido em uma parte da solução. Por exemplo, as tarefas para criar um plano de consulta e otimizá-lo são realizadas pelo módulo Cliente e a execução do plano é feita pelo módulo Servidor de Arquivos Paralelos. Essa divisão permite uma maior modularidade ao ambiente e facilita a portabilidade para outras plataformas. O módulo Cliente é pouco dependente da plataforma que está sendo utilizada, podendo ser executado facilmente em outras plataformas que não a usada inicialmente. Já o módulo Servidor de Arquivos Paralelos é mais dependente, pois utiliza primitivas para E/S paralela. No módulo Cliente encontra-se uma interface pela qual o usuário da aplicação tem acesso às várias opções do ambiente. Através dessa interface pode-se definir as especificações do banco de dados, criar o catálogo, gerar as estatísticas dos dados, inserir e modificar dados, 56 definir e executar uma consulta, determinar se uma consulta será materializada, escolher as estratégias para execução das operações de uma consulta e determinar se o ambiente aplicará o agrupamento prévio e o filtro seletivo. Além disso, nesse módulo encontram-se os algoritmos disponíveis para a aplicação das otimizações no plano de uma consulta. Módulo Cliente Consulta em SQL Analisador de Sintaxe Especificação do banco de dados Gerador de Catálogo Gerador de Plano Inicial Otimizador Heurístico Replicador de Catálogo Otimizador de Agregação Catálogo resultado Atualizações Atualizador de Instância Gerador de Plano de Execução Coordenador de Execução Atualizar estatística Gerador Estatístico Barramento Comunicação Servidor de Arquivos Servidor de Arquivos Servidor de Arquivos ... Catálogo Instância de Dados Catálogo Instância de Dados Catálogo Instância de Dados Módulo Servidor de Arquivos Paralelos Figura 5-6: Arquitetura do FAP. No módulo Servidor de Arquivos Paralelos encontram-se as estruturas e os algoritmos necessários para a execução de um plano de consulta, assim como a interface necessária para o acesso aos arquivos paralelos. Essas interfaces serão discutidas durante apresentação das características de implementação do ambiente. 57 Nas duas próximas seções, é feita uma apresentação detalhada dos módulos do sistema FAP. 5.4. O Módulo Cliente O módulo Cliente é a interface entre os usuários de aplicação e o FAP. Através dele as solicitações emitidas pelos usuários são enviadas para o módulo Servidor de Arquivos Paralelos para serem executadas. Os resultados dessas solicitações são enviados ao usuário. Esse módulo será um processo que deverá ser ativado em uma das estações de trabalho utilizada pelo usuário da aplicação. Durante sua inicialização, algumas tarefas básicas serão realizadas antes de executar qualquer consulta, como: (i) criar o catálogo com as especificações do banco de dados; (ii) determinar os servidores de arquivos paralelos de acordo com informações do catálogo; (iii) enviar aos servidores o catálogo e (iv) criar o banco de dados. O módulo Cliente interage com o restante do ambiente através de chamadas a métodos do sistema de maneira transparente para a aplicação, usando serviços encapsulados nas classes. Para cada serviço, pode ser necessária a comunicação com os servidores de arquivos paralelos. Os seguintes componentes4 são utilizados para o processamento de uma solicitação no módulo Cliente: catálogo; gerador e replicador de catálogo; atualizador de instância; gerador estatístico; analisador de sintaxe; gerador de plano inicial; otimizador heurístico; otimizador de agregação; gerador de plano de execução e coordenador de execução, como já se ilustrou na Figura 5-6. 5.4.1. Catálogo O catálogo armazena as informações que serão utilizadas pelos componentes do FAP durante o processamento das solicitações. Essas informações são guardadas em um arquivo e detalham as características das tabelas do ambiente, como seus atributos e restrições de sua utilização. Outra informação do catálogo é a especificação dos servidores de arquivos 4 O termo componente utilizado neste capítulo descreve elementos que compõe a arquitetura do FAP. 58 paralelos que acessam o banco de dados. No Apêndice B são apresentados maiores detalhes sobre as informações que são armazenadas no catálogo e no Apêndice D um exemplo de um arquivo de catálogo, adaptado do trabalho de [Cos01]. O acesso aos dados do catálogo é feito com a ajuda de uma tabela hash; desta forma, o ambiente oferece um meio para obter estes dados de maneira eficiente. Em um sistema paralelo e distribuído, deve-se adotar outras estratégias para o acesso eficiente ao catálogo; assim, optou-se por manter o catálogo replicado nos módulos. Essa decisão permite uma independência de processamento entre os módulos e é possível porque o tamanho do arquivo de catálogo é pequeno, quando comparado com o volume de dados armazenados pelo ambiente. 5.4.2. Gerador de Catálogo O gerador de catálogo é responsável por armazenar as especificações do banco de dados no arquivo de catálogo. Inicialmente, essas especificações são criadas na estação de trabalho onde o processo cliente está em execução e, depois, são replicadas usando o componente replicador de catálogo. Para que as informações do catálogo sejam obtidas de maneira correta, elas devem estar organizadas dentro do arquivo. Assim, para cada conjunto de informações é armazenado também um cabeçalho que indica o tipo de informação de cada coluna do arquivo, conforme ilustra o catálogo para o estudo de caso apresentado no Apêndice D. 5.4.3. Replicador de Catálogo O replicador de catálogo é um componente que é ativado de maneira automática cada vez que o arquivo de catálogo é criado pelo componente gerador de catálogo. Durante a replicação, o processo cliente determina quais servidores de arquivos paralelos deverão armazenar o catálogo, lê os dados do arquivo de catálogo e transfere-os para os servidores. Cada servidor é responsável por armazenar os dados recebidos em seu próprio arquivo de catálogo. Desta maneira, elimina-se uma comunicação entre os módulos para obter as informações do catálogo quando a aplicação é inicializada. Outra vantagem é o baixo custo para aplicar replicação, pois a comunicação durante essa tarefa pode ser desprezível devido a dois fatores: o tamanho do catálogo é pequeno e a sua manutenção é pouco freqüente. 59 5.4.4. Atualizador de Instância O atualizador de instância é responsável apenas por obter do usuário da aplicação as atualizações (inserções, alterações ou remoções) e repassá-las aos servidores de arquivos paralelos que atuam sobre o banco de dados a ser modificado. Para isto, o atualizador deverá utilizar informações do catálogo. Dentre as atualizações existentes, a alteração é a que deve ser tratada com mais cautela, pois quando ela envolve um campo de segmentação, o registro deverá ser transferido para o segmento apropriado e o registro antigo deverá ser removido. Na remoção, por questões de desempenho, será colocada uma marca de remoção no registro. As alterações em uma visão materializada serão realizadas através de consultas ao banco de dados. 5.4.5. Gerador Estatístico O gerador estatístico realiza uma estatística sobre a base dados para obter uma contagem dos valores distintos dos atributos das tabelas que estão pré-definidos no catálogo para participar desta estatística. Essas informações são armazenadas no catálogo e serão úteis para orientar cada servidor de arquivos paralelos na decisão quanto à aplicação da otimização baseada no agrupamento prévio. O processo de obter as informações estatísticas ocorre em cada servidor de arquivos paralelos que armazena no seu catálogo apenas as estatísticas coletadas de seu próprio banco de dados. As informações coletadas em todos servidores são enviadas para o módulo Cliente e serão úteis para gerar planos individuais para cada servidor. Essa funcionalidade deverá ser implementada em trabalhos futuros. Quanto ao período de atualização das informações estatísticas, não há necessidade de uma atualização constante. Mas, sua atualização reflete diretamente no desempenho da execução das consultas submetidas ao FAP. No ambiente proposto, essa atualização fica sob responsabilidade do usuário da aplicação que realiza essa tarefa usando uma interface oferecida por este componente. 60 5.4.6. Analisador de Sintaxe O analisador de sintaxe recebe uma consulta escrita em SQL e faz uma série de análises, para garantir sua execução corretamente. Para tanto, extrai seus elementos léxicos tais como as palavras reservadas (por exemplo, GROUP BY), os nomes das tabelas e atributos e analisa a semântica da consulta verificando a existência das tabelas e atributos no catálogo do banco de dados e uso dos tipos dos atributos. Por fim, se existem condições de seleção analisa seus operandos e operadores. Esse componente é o mais simples entre todos os componentes do FAP. No entanto, exerce um papel fundamental durante o processamento de uma consulta porque essa somente será executada se sua escrita for reconhecida pelo padrão do analisador. Esse padrão é representado pelas cláusulas do SQL conforme a seguir. SELECT <lista de atributos e ou funções de agregação> FROM <lista de tabelas> [WHERE <condição>] [GROUP BY <lista de atributos de agrupamento> [HAVING <condição de agrupamento>]] [ORDER BY <listas de atributos >]; As regras utilizadas pelo analisador de sintaxe para reconhecimento do padrão de escrita de uma consulta são: (i) as cláusulas, as funções de agregação e os conectores (AND e OR) são reconhecidos com letras maiúsculas, já os nomes de tabelas e atributos devem coincidir com a forma que foram informados pelo usuário da aplicação; (ii) apenas as cláusulas SELECT e FROM são obrigatórias; (iii) o nome de cada tabela na cláusula FROM deve possuir um apelido seguido pela palavra reservada AS, por exemplo, locacao AS loc; (iv) o nome de cada atributo deve ser referenciado através do apelido de uma tabela separado por um ponto, por exemplo, loc.idfilmeloc; (v) a função de agregação COUNT aceita como argumento o sinal asterisco (*) e as demais funções o nome do atributo; (vi) um operador pode ser um dos seguintes sinais >, <, >=, <=, =, <> e (vi) um operando pode ser um número ou uma palavra, sendo esta entre aspas simples (' '). Exemplos de consultas usando estas regras podem visualizadas no Apêndice E. O analisador de sintaxe também reconhece uma visão que será materializada pela aplicação e segue as mesmas regras apresentadas. Se uma consulta não está escrita conforme 61 as regras apresentadas, então uma mensagem apropriada é mostrada ao usuário da aplicação, caso contrário, a consulta é repassada para o gerador de plano inicial. 5.4.7. Gerador de Plano Inicial O gerador de plano inicial transforma uma consulta em uma representação não otimizada usando a álgebra relacional e uma árvore de profundidade à esquerda. Este tipo de árvore define a ordem de execução das operações de uma consulta e limita o número de planos equivalentes que serão gerados durante a aplicação das otimizações e da geração do plano de execução. Quando um plano inicial é construído uma operação de projeção que é aplicada antes da agregação, próxima à raiz da árvore, será retirada do plano. Assim, o desempenho da consulta poderá ser melhorado, tendo em vista que a projeção será resolvida quando for executada a agregação. Mas, isto só é possível quando existir igualdade nos atributos das cláusulas SELECT e GROUP BY. Esta estratégia será utilizada também durante a aplicação do agrupamento prévio. 5.4.8. Otimizador Heurístico O otimizador heurístico transforma o plano inicial, usando heurísticas para obter um plano equivalente que se espera ser mais eficiente. Uma característica importante desse componente é que as novas projeções não são adicionadas antes da operação de agregação por questões de desempenho. O otimizador heurístico também determina a ordem de execução para as operações de junção. Inicialmente, segue a ordem das tabelas definida na cláusula FROM, mas caso esta ordem não possibilite a aplicação das junções o otimizador utilizará a ordem das condições de junção existentes na cláusula WHERE. Desta forma, este componente apenas determina uma ordem possível para a aplicação da junção sem, no entanto, garantir que a ordem encontrada é a melhor estratégia para a execução da consulta. A solução aplicada pelo otimizador heurístico para resolver a questão da ordem de execução das junções permite que o usuário da aplicação possa determinar sua própria 62 configuração. Desta forma, pode-se obter diferentes medidas de desempenho para realizar os estudos dos comparativos entre diferentes configurações. 5.4.9. Otimizador de Agregação O otimizador de agregação aplica o agrupamento prévio no plano de consulta otimizado. Para tanto, são construídos três conjuntos de atributos para cada nó da árvore a partir da análise dos atributos das cláusulas SELECT e GROUP BY da consulta e dos atributos do plano de consulta. Depois, anota-se em cada nó da árvore o tipo de agrupamento testando as informações dos conjuntos e, finalmente, uma varredura completa na árvore determinará os nós nos quais será aplicado o agrupamento prévio e realizará as transformações necessárias na árvore. Além disso, anota também no plano se aplicará o filtro seletivo. O agrupamento prévio não melhora o desempenho para todas as consultas, assim esse componente deverá decidir quando aplicá-lo. No ambiente proposto essa questão é resolvida no módulo Servidor de Arquivos Paralelos através de um modelo de custo básico aplicado no operador de agrupamento que está próximo a uma das tabelas no plano de consulta. Para isto, estima-se seu número de grupos usando a quantidade de valores distintos dos atributos de agrupamento e calcula-se a taxa de redução da tabela envolvida na agregação. A determinação de uma taxa de redução para qualquer consulta submetida ao ambiente proposto está fora do escopo do trabalho. Essa questão poderá ser melhor resolvida, por exemplo, usando um modelo de estimativa de custo envolvendo as outras operações do plano. 5.4.10. Gerador de Plano de Execução O gerador de plano de execução é responsável por transformar o plano otimizado de uma consulta no plano de execução. Assim, deve-se escolher uma estratégia para cada operação do plano otimizado. Essa escolha é realizada pelo usuário da aplicação dando a ele a liberdade para que possa executar as estratégias para os comparativos de desempenho. 5.4.11. Coordenador de Execução A última tarefa do módulo Cliente para execução de uma consulta é realizada pelo coordenador de execução. Neste componente, o plano de execução é distribuído aos 63 servidores de arquivos paralelos que cooperarão em sua execução. A decisão de escolher um servidor é tomada levando em consideração a disponibilidade do servidor, a técnica de fragmentação por faixa de valores dos dados e a existência ou não de tabelas replicadas. As informações necessárias para essa decisão estão armazenadas no catálogo do banco de dados. Quando o plano de consulta possui tabelas que estão fragmentadas por seus atributos de ligação e não envolve uma condição de seleção sobre esses atributos, então todos os servidores especificados na criação do banco de dados deverão executar a consulta. Caso esse mesmo plano tenha tabelas replicadas e pelo menos uma condição de seleção baseada no atributo de fragmentação, então apenas os servidores envolvidos na faixa da distribuição serão escolhidos. Uma outra configuração ocorre quando o plano de consulta tem apenas tabelas replicadas, assim sendo direciona-se a consulta para um dos servidores. Finalmente, o coordenador de execução deverá repassar para o módulo Servidor de Arquivos Paralelos o plano de execução da consulta e aguardar o recebimento dos resultados. Esses resultados poderão representar o resultado final, quando o plano é executado por um único servidor, ou um resultado parcial, se mais de um servidor é utilizado. Quando se tratar de um resultado parcial, então o coordenador de execução deverá compor o resultado final. Esse resultado é gravado em arquivo para que o usuário possa visualizá-lo. 5.5. O Módulo Servidor de Arquivos Paralelos O módulo Servidor de Arquivos Paralelos é responsável por armazenar e obter os dados dos arquivos paralelos em disco. Para tanto, um processo será executado nas estações de trabalho escolhidas para compartilhar seu disco local. O padrão de armazenamento e acesso é feito usando as informações sobre as tabelas detalhadas no catálogo. Deste modo, suas operações resumem-se ao recebimento das requisições através da rede e à leitura ou escrita dos dados solicitados. Além disso, cada servidor possui uma estatística sobre seus dados para decidir o uso da estratégia de agrupamento prévio. O processo de ativação e término de cada processo servidor é feito pela aplicação paralela através de chamadas a métodos no módulo Cliente. Toda interação entre os módulos Cliente e Servidor de Arquivos Paralelos ocorre de maneira transparente para o usuário da aplicação e através dos serviços encapsulados nas chamadas. 64 O número de servidores de arquivos paralelos será determinado pelo usuário da aplicação de acordo com suas necessidades e qualquer alteração desse valor implicará em uma redistribuição dos dados do banco de dados. Isto poderá também ocorrer se a política de fragmentação ou replicação dos dados for alterada. Na ativação do processo servidor deve ser especificado o nome do banco de dados que será utilizado. Esse nome determina o local de armazenamento na estrutura de diretórios para conter os arquivos paralelos do banco de dados. Se os dados forem armazenados em um diretório remoto utilizando, por exemplo, o NFS (Network File System), então a aplicação paralela deverá informar aos servidores a existência desse tipo de diretório. No Apêndice C é mostrada a estrutura de diretórios do FAP. Durante a execução de uma consulta, cada servidor cumpre apenas as tarefas definidas no seu plano de execução. No entanto, um determinado servidor pode alterar seu plano sem a necessidade de informar os demais, o que ocorre quando ele decide em não aplicar o agrupamento prévio existente no plano de execução. 5.6. Aspectos de Implementação do FAP A implementação do ambiente proposto tem por objetivo abranger aplicações paralelas sobre o sistema operacional Linux. A escolha dessa plataforma deve-se à sua grande utilização e à facilidade de se encontrar os recursos de software que viabilizam a construção do ambiente proposto. Esses recursos de software normalmente são encontrados em ferramentas de programação, aplicações diversas e no próprio sistema operacional. Para a comunicação entre as estações de trabalho está sendo considerada a distribuição LAM com recursos de E/S paralela. As funcionalidades do FAP foram implementadas usando o paradigma orientado a objetos através da linguagem de programação C++ e a biblioteca de comunicação e tratamento de arquivos paralelos LAM/MPI-2. No Apêndice B, encontra-se parte da interface das principais classes implementadas. Nesta seção são apresentadas as principais classes e a descrição de seu funcionamento, conforme a Tabela 5-1. 65 Classe BitVector Buffer_Data Catalog Client, Server DbFap Field File_MPI File_Text Sql Table Objetivo Interface para gerenciar o filtro seletivo. Interface para gerenciar área de buffers para o armazenamento temporário de registros. Interface para gerenciar o catálogo. Interface para gerenciar uma estação de trabalho cliente ou servidor. Interface para gerenciar todos os eventos do sistema FAP. Interface para um campo de uma tabela. Interface para gerenciar o armazenamento e acesso dos dados no disco usando arquivo do padrão MPI. Classe derivada da classe File. Interface para gerenciar o armazenamento e acesso dos dados no disco usando arquivo do padrão texto. Classe derivada da classe File. Interface para gerenciar a execução das consultas. Interface para uma tabela do banco de dados. Tabela 5-1: Principais Classes Implementadas do FAP. Antes que possa manipular os arquivos paralelos, cada estação de trabalho precisa ser identificada através de uma relação hierárquica do tipo cliente (mestre) e servidor (escravo). Isto é feito de acordo com a especificação do usuário da aplicação usando o modelo SPMD. A idéia central dessa atribuição é determinar uma estação como cliente e as demais como servidores. Desta forma, o módulo Cliente é implementado como um processo cliente e o módulo Servidor de Arquivos Paralelos como um processo servidor. O gerenciamento do cliente e do servidor é feito respectivamente pelas classes Client e Server. O usuário da aplicação interage com o processo cliente através da criação e instanciação de um único objeto da classe DbFap, que oferece uma interface transparente para a aplicação. O argumento deste objeto determinará o banco de dados que será utilizado e o local de armazenamento dos dados e do catálogo na estrutura de diretório, conforme descrito no Apêndice C. O catálogo é criado, inicialmente, na estação cliente usando os seguintes métodos: o AddServer() que adiciona os nomes dos servidores de arquivos paralelos em que os dados serão armazenados; o AddTable() e o AddFieldTable() que adiciona a tabela e seus campos para o armazenamento dos dados; e o AddMatView() e o AddFieldMatView(), que adiciona respectivamente uma visão materializada e seus campos. 66 A execução do método CreateCatalog() determina quais servidores serão utilizados para o gerenciamento dos arquivos paralelos e replica o catálogo. Desta maneira, o ambiente está preparado para que as estruturas das tabelas e visões materializadas possam ser criadas. Isso é feito executando o método Create() que busca no catálogo as características das tabelas e das visões materializadas. Além disso, são construídas em memória estruturas para armazenar as informações do catálogo que permitam agilizar as pesquisas. As alterações e consultas aos dados no ambiente podem ser realizadas quando as tabelas e visões materializadas estiverem disponíveis. Para tanto, o método Open() faz a abertura de todos os arquivos do ambiente usando as informações do catálogo. Enquanto o ambiente estiver sendo usado, esses arquivos permanecerão abertos, o que melhora seu o desempenho. Ao encerrar o processamento sobre o ambiente, são fechados todos os arquivos do banco de dados que estão abertos, com a execução do método Close(). Uma consulta é executada através do método Query(). As estratégias que serão usadas durante o processamento da consulta são aquelas pré-definidas pelo ambiente. Contudo, o usuário da aplicação tem a liberdade de alterá-las para obter diferentes desempenhos, por exemplo, o método ApplyGroupByBeforeJoin_Yes() e o método SetTechnical_GroupBy_Hash() determinam que no processamento de uma consulta deverá ser aplicado o agrupamento prévio e a estratégia hash para a agregação, respectivamente. Para construção de uma visão materializada é utilizado o método MatView() que difere do método Query() porque seus resultados deverão ser gravados no próprio ambiente, em arquivos paralelos ou não, para servir como base para outras consultas. O suporte à execução de consultas foi definido através da estrutura t_left_deep_tree que é utilizada durante a construção do plano de consulta, conforme ilustra a Figura 5-7. Essa estrutura armazena a característica de uma tabela ou operação do plano. Assim, o conjunto de estruturas t_left_deep_tree determina uma árvore de profundidade à esquerda que será posteriormente otimizada e executada. As informações do agrupamento prévio serão anotadas na área estendida da estrutura, permitindo flexibilidade para a determinação do tipo de agrupamento prévio em cada nó. 67 typedef struct t_left_deep_tree { char *tablename; // nome da tabela int operation; // nome da operação (seleção, projeção, produto, agregação, junção, nenhuma) struct t_condition *condition; //condição de junção ou seleção int *n_condition; //quantidade de condições de junção ou seleção char *fields[20]; //campos de projeção ou agregação int *n_fields; //quantidade de campos de projeção ou agregação struct t_left_deep_tree *left; //próxima operação ou tabela à esquerda struct t_left_deep_tree *right; //próxima operação ou tabela à direita struct t_left_deep_tree *father; //nó pai // para uso pela arvore estendida ListString *list_table; // as tabelas de um NÓ que estão abaixo de uma determinada operação/tabela ListString *cj; //colunas de junção ListString *cr; //colunas requeridas ListString *cac; //colunas de agregação candidatas int *type_transformation; //tipo de agrupamento prévio int *is_filter_selective; //aplicar ou não o filtro seletivo int ordem_node; //ordem para auxiliar na decodificação do plano int execute; //flag para executar ou não o NÓ } LEFT_DEEP_TREE; Figura 5-7: Estrutura t_left_deep_tree Existente no FAP. Um plano de consulta é codificado pelo cliente e enviado aos servidores para ser executado. O trabalho do servidor consiste em decodificar o plano de consulta de tal forma que a árvore que representa esse plano tenha a mesma estrutura da árvore existente no cliente. Essas funções de codificação e decodificação são realizadas pelos métodos QueryPlanCod() e QueryPlanDecodif(). O paralelismo é aplicado aproveitando-se da localidade dos dados e, desta forma são determinados os servidores que executarão a consulta de acordo com a técnica de distribuição existente no catálogo. Ao executar o operador de agrupamento, cada servidor mantém uma cópia da estrutura t_input_aggregate para armazenar o cálculo das funções de agregação para cada grupo, conforme Figura 5-8, que representa uma entrada para cada grupo. Se um campo de uma tabela está relacionado a mais de uma função de agregação, então apenas uma entrada é suficiente para registrar os valores destas funções. Outra vantagem desta estrutura é realizar o cálculo da média (val_avg) a partir dos campos val_count e val_sum. 68 typedef struct t_input_aggregate { char fieldname[MAX_FIELDNAME]; long val_count; //cálculo count long val_sum; //calculo sum long val_avg; //calculo avg long val_max; //calculo max long val_min; //calculo min //campo que será aplicado a função de agregação } INPUT_AGGREGATE; Figura 5-8: Estrutura t_input_aggregate Existente no FAP. Para utilizar o FAP, deve-se considerar o perfil do usuário que irá utilizá-lo. São considerados dois tipos de usuários: o da aplicação e o da implementação. Um usuário da aplicação obterá os resultados de desempenho para análise quando uma consulta for submetida à execução. Para analisar os resultados, esse usuário deverá ter conhecimento do funcionamento das estratégias de execução disponíveis no ambiente, podendo tirar suas conclusões de maneira mais clara. Além disso, deverá decidir quais estratégias serão utilizadas para o processamento de cada operação da consulta e se o agrupamento prévio e o filtro seletivo serão aplicados. Outras tarefas, como a definição das informações do catálogo e a inserção dos dados no banco de dados, também serão realizadas por esse tipo de usuário. Um usuário de implementação deverá ser responsável por acrescentar novas estratégias de execução no FAP ou adotar um novo ambiente de troca de mensagens. Assim, esse usuário deverá ter um conhecimento maior sobre os recursos utilizados no desenvolvimento do FAP. 5.7. Considerações Finais Neste capítulo foi descrito o FAP, um ambiente de execução de consultas paralelas que oferece mecanismos para a análise de desempenho de consultas usando estratégias como o agrupamento prévio e o filtro seletivo. Sua arquitetura em módulos é uma de suas principais características, permitindo adicionar gradativamente novas funcionalidades não previstas no projeto inicial, como o filtro seletivo. 69 Outra característica do ambiente é a transparência. O usuário da aplicação pode interagir com o ambiente sem dominar profundamente as funcionalidades de um processador de consultas, oferecendo assim um ambiente para um maior número de usuários. O uso de uma linguagem de programação orientada a objetos ajudou na implementação das especificações dos componentes dos módulos, permitindo um maior gerenciamento do código durante o desenvolvimento e o acréscimo de estratégias para a junção e a agregação. A interação entre o usuário e o FAP é relativamente simples. Para auxiliar nessa interação foram descritas as principais classes e métodos implementados. No próximo capítulo ilustram-se as idéias desenvolvidas neste capítulo com a execução de consultas no ambiente proposto e são discutidos os resultados obtidos com essas execuções. 70 6. Medidas, Desempenho e Comparações 6.1. Introdução Neste capítulo, mostram-se os resultados obtidos na execução de um estudo de caso de um sistema de uma rede de locadoras [Cos01]. Serão apresentadas as situações em que é interessante aplicar o agrupamento prévio e o filtro seletivo. Várias comparações possibilitarão confrontar o custo de execução de consultas submetidas a ambientes centralizados e paralelos e avaliar o desempenho de consultas submetidas diretamente ao banco de dados e a um data warehouse. O Apêndice E apresenta as principais rotinas implementadas no estudo de caso. Para analisar as situações propostas, os resultados foram avaliados segundo o tempo de relógio de parede [Cro94], que reflete o intervalo de tempo entre a submissão da consulta ao sistema e a obtenção da resposta final. Neste tipo de medição são considerados tanto os custos computacionais como a espera por bloqueios, paginação e E/S em disco. A aferição do tempo foi feita no processo cliente para obtenção do tempo total da execução da consulta e nos processos servidores de arquivos paralelos para obter os tempos de execução de cada operação de uma consulta. Uma outra medida foi obter o espaço em disco utilizado pela consulta para que o comportamento do armazenamento temporário fosse avaliado. Os experimentos foram realizados no LABCOMP/UEMS (Laboratório do Curso de Computação da UEMS) com 1, 2, 4, 8 e 16 servidores de arquivos paralelos conectados a uma rede Ethernet de 10Mbps. Cada servidor possui um único processador Pentium III 1.3GHz, 128 MB de memória e 2GB de disco com o sistema Linux e o ambiente LAM-MPI. A rede de estações de trabalho não foi isolada para a realização dos experimentos; apesar disso, pode-se garantir que, durante os testes, as estações estavam dedicadas à execução das consultas. Tendo em vista a capacidade das máquinas e da rede de interconexão existente no laboratório são apresentadas projeções sobre uma amostra reduzida do volume dos dados. Analisar os resultados em aplicações paralelas de bancos de dados não é uma tarefa trivial, pois um grande número de variáveis pode influenciar esses resultados. Como não se 71 dispõe de um modelo que avalie as variáveis de forma adequada, então os resultados são apresentados mostrando-se os tempos de execução das consultas e seus ganhos obtidos com o aumento do número de nós na execução paralela. A seguir são apresentados mais detalhes sobre o estudo de caso, as medidas, os desempenhos e os comparativos para as situações propostas. 6.2. Um Estudo de Caso com o FAP Devido às limitações já mencionadas, referentes ao ambiente de testes, será utilizada a massa de dados de testes do Apêndice A. Para garantir um bom desempenho nas consultas através da maximização do paralelismo, explorou-se a localidade dos dados através de políticas de replicação e fragmentação. Assim, os dados utilizados nos experimentos foram armazenados nos servidores de arquivos paralelos através dos passos do algoritmo a seguir executado no cliente. 1º. Obter as informações do catálogo, determinar a quantidade de servidores de arquivos paralelos, inicializar, em cada um, o processo servidor e replicar o catálogo; 2º. Replicar os dados gerados das tabelas LOJA e FILME nos servidores; 3º. Determinar a política de fragmentação para as tabelas CLIENTE e LOCACAO a partir do número de lojas, determinando a faixa de valores correspondente a cada servidor; 4º. Distribuir os dados gerados das tabelas CLIENTE através do atributo idlojacli e LOCACAO usando o atributo idlojaloc, de acordo com a política definida no passo anterior. Para a execução do estudo de caso e avaliação do ambiente proposto foram definidas dez consultas e uma visão materializada, mostradas na Tabela 6-1. Todas as consultas foram executadas tanto no ambiente centralizado como no paralelo. Nas próximas seções são mostrados os resultados dos testes e desempenhos para o processamento destas consultas, executadas no FAP, segundo o número de nós já definidos, e 72 será feita a análise comparativa dos resultados. Nas figuras, as siglas SAP, CAP, SFS e CFS representam, respectivamente, sem agrupamento prévio, com agrupamento prévio, sem filtro seletivo e com filtro seletivo. Consulta Q3 Descrição Define a quantidade de fitas locadas e o valor faturado com locações, por bairro, anualmente, para filmes do gênero 'drama'. Q4 Define a quantidade de fitas locadas diariamente, para filmes do gênero 'faroeste'. Q5 Define a quantidade de fitas locadas anualmente, para filmes do gênero 'faroeste'. Q6 V1 Q7 Q8, Q9, Q10, Q11, Q12 Define a quantidade de fitas locadas diariamente, para filmes do gênero 'faroeste'. Principais Características Qual Situação a Avaliar? Agrupamento de união simples (produz 90 grupos) Aplicação do agrupamento prévio Agrupamento de união simples (produz 10.781 grupos) Agrupamento de união simples (produz 30 grupos). Agrupamento de união simples (produz 10.781 grupos) Aplicação do agrupamento prévio Aplicação do filtro seletivo com e sem falsos positivos Aplicação do filtro seletivo com e sem falsos positivos Define a quantidade de locações e o valor faturado com locações, diariamente, filme por filme, para cada loja. Cria uma Visão Materializada - A mesma descrição da consulta Q3, mas utilizando a visão materializada V1 no lugar da tabela LOCACAO. Agrupamento de união simples (produz 90 grupos) Uso ou não da visão materializada e aplicação do agrupamento prévio Define o valor faturado com locações por gênero cuja loja tenha idlojaloc=16 (Q8) ou idlojaloc>=15 (Q9) ou idlojaloc>=13 (Q10) ou idlojaloc>=9 (Q11) ou idlojaloc>=1 (Q12). Agrupamento de união simples (produz 16 grupos) Variação da carga de trabalho para os servidores Tabela 6-1:Consultas Executadas no Estudo de Caso. 6.3. Comparativo entre Consultas com/sem o Agrupamento Prévio As consultas Q3 e Q4 foram definidas com o intuito de verificar em que circunstâncias a aplicação do agrupamento prévio é viável e estão representadas em SQL a seguir. Q3: SELECT FROM WHERE GROUP BY bairro, anoloc, COUNT(*), SUM(valorlocfita) Loja , Locacao, Filme idloja=idlojaloc AND idfilmeloc=idfilme AND genero='drama' bairro, anoloc Q4: SELECT FROM WHERE GROUP BY nomefilme, anoloc, mesloc, dialoc, COUNT(*) Filme, Locacao idfilme =idfilmeloc AND genero='faroeste' nomefilme, anoloc, mesloc, dialoc 73 As Figuras 6-1 e 6-2 apresentam os tempos de execução das consultas Q3 e Q4 levando em consideração a aplicação ou não do agrupamento prévio (SAP e CAP) e o número de grupos produzidos durante o primeiro agrupamento. Na consulta Q3 são produzidos 10.240 grupos durante o primeiro agrupamento, o que é uma quantidade muito inferior aos 229.245 grupos produzidos pela consulta Q4. Outra diferença entre essas consultas é que a Q3 realiza três agrupamentos, enquanto na consulta Q4 faz dois agrupamentos. Tempo de execução da consulta Q3 1.600 Tempos em segundos SAP 1.483,49 1.400 CAP 1.200 1.000 800 748,38 600 354,88 315,82 400 218,28 200 97,72 35,71 74,07 17,88 0 1 2 4 8 16 9,60 Nº de servidores de arquivos paralelos Figura 6-1: Tempo de Execução da Consulta Q3. Tempo de execução da Consulta Q4 1.000 976,62 Tempos em segudos SAP 804,31 800 659,29 600 CAP 582,54 494,37 400 200 172,94 141,96 119,56 140,00 129,80 0 1 2 4 8 16 Nº de servidores de arquivos paralelos Figura 6-2: Tempo de Execução da Consulta Q4. Na execução da consulta Q3, observa-se que o tempo de execução diminui à medida que se aumenta o número de servidores, isto porque os dados distribuídos permitem a 74 aplicação do paralelismo. Cada servidor contribui para a execução da consulta levando em consideração a localidade dos dados. A aplicação da técnica CAP revelou-se interessante quando a consulta produz poucos grupos durante o primeiro agrupamento, conforme apresenta o gráfico da Figura 6-1. Esta característica permite que os grupos sejam construídos diretamente na memória principal utilizando um algoritmo de agregação de uma única passagem. Assim, os agrupamentos intermediários não influenciam de maneira negativa quando aplicado o agrupamento prévio. Em todas as configurações de servidores obteve-se os melhores desempenhos com a técnica CAP. O desempenho da consulta Q4 mostra que o agrupamento prévio deve ser evitado nesse tipo de consulta, que produz muitos grupos durante o primeiro agrupamento, pois na construção dos grupos exige-se a utilização de arquivos temporários. Assim, será necessária a aplicação de um algoritmo de duas passagens para a execução dos agrupamentos da consulta. Outro aspecto importante que pode ser observado nas Figuras 6-3 e 6-4 diz respeito ao speedup para a execução das consultas Q3 e Q4, ou seja, o aumento de desempenho em função do aumento do número de servidores. Speedup da consulta Q3 SAP CAP 36 32,911 32 28 24 20,029 20 17,659 16 12 8 4 8,844 3,232 6,796 4,180 1,982 0 2 4 8 16 Nº servidores de arquivos paralelos Figura 6-3: Desempenho para a Consulta Q3. Observa-se que o aumento de desempenho proporcionado pelo acréscimo de novos servidores para a consulta Q3 ocorre tanto no SAP como no CAP, mas com a aplicação do 75 agrupamento prévio esse desempenho é quase o dobro em todas as configurações de servidores. Isto porque a redução do número de grupos no primeiro agrupamento contribui para a diminuição do custo de execução das operações de junção. No entanto, para consultas com muitos agrupamentos, como a Q4, o desempenho fica prejudicado devido aos acessos a disco para compor os grupos. Apesar disso, a aplicação do agrupamento prévio tende a reduzir o custo de acesso aos arquivos temporários. Speedup da consulta Q4 SAP 2,0 CAP 1,976 1,8 1,676 1,6 1,481 1,4 1,446 1,214 1,332 1,2 1,218 1,235 1,0 2 4 8 16 Nº servidores de arquivos paralelos Figura 6-4: Desempenho para a Consulta Q4. A eficiência das consultas também muda em relação ao número de grupos criados durante o primeiro agrupamento, conforme apresenta as Figuras 6-5 e 6-6. Eficiência da consulta Q3 SAP CAP 2,6 2,211 2,207 2,2 2,057 1,8 1,616 1,4 1,252 1,0 0,991 1,045 0,850 0,6 2 4 8 16 Nº servidores de arquivos paralelos Figura 6-5: Eficiência para a Consulta Q3. Como se pode observar, a eficiência da consulta Q3 é melhor com aplicação do agrupamento prévio para até 8 servidores de arquivos e diminui quando são utilizados 16 76 servidores, mesmo apresentado um bom speedup nesta configuração. Essa diminuição reflete a sobreposição das cargas de comunicação em relação à computação necessária para a execução da consulta. Além disso, há uma boa eficiência quando não é aplicado o agrupamento prévio, mas em proporções menores em relação à CAP. Por outro lado, em consultas com muitos agrupamentos, como a Q4, o acréscimo de servidores reduziu a eficiência em todas configurações de servidores, devido aos acessos a disco e a computação necessária para compor o resultado final. Mas, a técnica CAP ajuda a minimizar esse problema. Eficiência da consulta Q4 0,7 SAP 0,607 0,6 CAP 0,609 0,5 0,370 0,4 0,362 0,3 0,210 0,2 0,123 0,167 0,1 0,077 0,0 2 4 8 16 Nº servidores de arquivos paralelos Figura 6-6: Eficiência para a Consulta Q4. A técnica de agrupamento prévio também se mostra significativa quanto à utilização do espaço em disco para o armazenamento temporário dos resultados de cada operação da consulta Q3, conforme ilustra a Figura 6-7. Apesar da existência de três agrupamentos nessa consulta, o acesso a disco não influenciou negativamente no tempo de execução da consulta. O espaço em disco requerido com a técnica CAP foi de 107,7 vezes menor em relação à técnica SAP, um fator de grande importância porque o acesso a disco ainda é o gargalo em banco de dados. No entanto, este mesmo comportamento não ocorreu na consulta Q4, que produz 229.245 grupos durante o primeiro agrupamento. Esse resultado indica, novamente, que a utilização do agrupamento prévio nesse tipo de consulta não é viável. 77 Tamanho dos arquivos temporários 237,0 SAP CAP 200,0 Mb 150,0 100,0 50,0 14,7 8,2 2,2 0,0 Q3 Q4 Figura 6-7: Espaço em Disco para os Arquivos Temporários nas Consultas Q3 e Q4. Técnicas de pipelining podem reduzir os tamanhos dos arquivos temporários utilizados para o processamento de operações em uma consulta; porém, como o agrupamento é uma operação unária torna-se mais difícil à aplicação dessa técnica. Os custos dominantes para a execução das consultas são apresentados no gráfico da Figura 6-8. 1.600 Junção X Agregação 18,940 Junção Tempos em segundos 1.400 Agregação 1.200 1.462,408 1.000 800 600 400 219,839 263,576 200 50,164 0 Q3 e SAP Q3 e CAP 8,837 15,474 Q4 e SAP 2,206 Q4 e CAP Figura 6-8: Tempos de Execução da Junção e Agregação nas Consultas Q3 e Q4. Quando a técnica SAP é aplicada, as duas consultas têm o maior custo de execução nas operações de junção. No entanto, quando a técnica CAP é aplicada na consulta Q3, o maior custo passa ser o da operação de agregação, mas em proporções menores em relação ao tempo de execução da junção na técnica SAP. O baixo desempenho da consulta Q4 com a técnica 78 CAP deve-se ao fato de ser gerada uma grande quantidade de grupos durante o primeiro agrupamento. 6.4. Comparativo entre Consultas com/sem o Filtro Seletivo Como já foi mencionado, um filtro seletivo é um mecanismo que pode melhorar o desempenho de uma consulta. No entanto, compreender seu comportamento em situações diversas pode ajudar durante sua aplicação. Nesta seção serão comparadas as execuções entre as consultas Q5 e Q6, representadas em SQL a seguir, onde a primeira produz poucos grupos no primeiro agrupamento e a segunda consulta produz muitos grupos, 30 e 10.781 grupos respectivamente. As duas consultas possuem uma condição de seleção sobre a tabela de dimensão FILME, que possibilitará a aplicação do filtro sobre a tabela LOCACAO. Q5: SELECT FROM WHERE GROUP BY nomefilme, anoloc, COUNT(*) Filme, Locacao idfilme=idfilmeloc AND genero='faroeste' nomefilme, anoloc Q6: SELECT FROM WHERE GROUP BY nomefilme, anoloc, mesloc, dialoc, COUNT(*) Filme, Locacao idfilme=idfilmeloc AND genero='faroeste' nomefilme, anoloc, mesloc, dialoc A Figura 6-9 apresenta os resultados da execução da Consulta Q5 com e sem a utilização do filtro seletivo (CFS, SFS), sem envolver falsos positivos. Além disso, são apresentadas as medições envolvendo as técnicas SAP e CAP para melhor exemplificar o filtro seletivo. O bom desempenho alcançado na execução da consulta Q5 com a aplicação do filtro seletivo (CAP/CFS) já era esperado, pois os grupos que não satisfazem a condição de seleção (genero='faroeste') são descartados durante o primeiro agrupamento. Desta maneira, reduz-se ainda mais o custo do agrupamento e da junção. Em todas configurações de servidores os tempos de execução da consulta Q5 foi melhor em relação à não aplicação do filtro seletivo. Mas, à medida que se aumenta o número de servidores, então os ganhos são menores. Esse fato ocorre devido à pouca quantidade de grupos descartados em cada servidor, uma vez que o aumento do número de servidores 79 contribui para que os agrupamentos no SFS sejam realizados diretamente em partições na memória principal. Aplicação do filtro seletivo na consulta Q5 70 SAP/SFS 64,47 CAP/SFS CAP/CFS Tempos em segundos 60 50 40 30 34,32 37,46 16,94 20 21,95 8,31 10 10,93 0 1 2 4 5,64 6,34 4,78 8 16 Nº servidores de arquivos paralelos Figura 6-9: Aplicação do Filtro Seletivo na Consulta Q5. Na consulta Q6, que produz muitos grupos, o filtro seletivo não melhorou o tempo de execução da consulta, pois a quantidade de grupos descartados não foi suficiente para minimizar os acessos a disco, conforme apresentado no gráfico da Figura 6-10. Aplicação do filtro seletivo na consulta Q6 1.000 976,62 800 Tempos em segudos SAP/SFS CAP/SFS CAP/CFS 804,31 659,29 582,54 600 494,37 400 276,90 200 172,94 141,96 125,60 0 1 127,63 119,56 138,24 129,80 2 4 8 Nº de servidores de arquivos paralelos 145,65 140,00 16 Figura 6-10: Aplicação do Filtro Seletivo na Consulta Q6. Na análise do comportamento da aplicação do filtro seletivo também é verificado o seu desempenho em relação à variação do tamanho do vetor de bits. Desta maneira deseja-se averiguar a ocorrência dos chamados falsos positivos e determinar estratégias para minimizar este problema. As Figuras 6-11 e 6-12 apresentam os tempos de execução para as consultas 80 Q5 e Q6 envolvendo falsos positivos. Para tanto, o tamanho do vetor de bits foi definido em N/2, onde N é o tamanho necessário para que não ocorram falsos positivos. A presença de falsos positivos na consulta Q5 50 46,44 45 Sem falsos positivos Com falsos positivos Tempos em segundos 40 35 30 37,46 26,33 25 20 21,95 14,03 15 10 6,73 10,93 5 4,57 6,34 0 1 2 4 4,78 8 16 Nº servidores de arquivos paralelos Figura 6-11: A Ocorrência de Falsos Positivos na Consulta Q5. A presença de falsos positivos na consulta Q6 300 298,94 Sem falsos positivos 280 Tempos em segudos 260 Com falsos positivos 276,90 240 220 200 179,25 180 150,82 160 161,24 150,38 140 138,24 120 145,65 127,63 125,60 100 1 2 4 8 16 Nº de servidores de arquivos paralelos Figura 6-12: A Ocorrência de Falsos Positivos na Consulta Q6. Quando o filtro seletivo é aplicado usando-se vetores de bits que não mapeiam cada valor de um atributo de seleção em uma posição do vetor, grupos que deveriam ser descartadas durante o primeiro agrupamento não o são, elevando o custo de execução da consulta, conforme ilustra os gráficos das Figuras 6-11 e 6-12. A capacidade do tamanho do vetor de bits está diretamente relacionada à redução da quantidade de grupos em uma consulta, conforme ilustra as Figuras 6-13 e 6-14 para as 81 consultas Q5 e Q6, respectivamente. Esses valores levam à conclusão de que, quanto menor for o tamanho de um vetor de bits, então maiores serão as chances de ocorrer falsos positivos durante o processamento do agrupamento. Redução do número de grupos para a consulta Q5 700 640 Grupos no 1º agrupamento 600 Nº de grupos 500 400 300 200 60 100 30 0 CAP CAP e filtro sem falsos positivos CAP e filtro com falsos positivos Figura 6-13: Nº de Grupos para a Consulta Q5 Com/Sem Falsos Positivos. Redução do número de grupos para a consulta Q6 250.000 229.945 Grupos no 1º agrupamento Nº de grupos 200.000 150.000 100.000 50.000 10.781 21.565 0 CAP CAP e filtro sem falsos positivos CAP e filtro com falsos positivos Figura 6-14: Nº de Grupos para a Consulta Q6 Com/Sem Falsos Positivos. É desejável aplicar o filtro seletivo sem a ocorrência de falsos positivos. No entanto, mapear cada valor de um atributo em um vetor de bits pode consumir muita memória quando os atributos têm um elevado número de valores distintos. Neste caso, se poucos valores distintos satisfazem a condição de seleção, então muitas posições do vetor de bits não serão utilizadas, causando desperdício de memória. Logo, em situações como estas deve-se aplicar estratégias que não anulem o uso do filtro seletivo, por exemplo, utilizar uma lista encadeada 82 para o armazenamento dos valores dos atributos definidos pela condição de seleção. Para situações onde a condição de seleção define uma quantidade moderada de valores distintos pode-se utilizar uma árvore binária de busca. Assim, o custo de busca de um valor de comparação deverá ser menor em relação ao uso de uma lista encadeada. 6.5. Comparativo entre Consultas com/sem um Data Warehouse Nesta seção deseja-se confrontar os custos de uma consulta executada em dois ambientes paralelos distintos: um banco de dados tradicional com todos os dados da movimentação da empresa e um data warehouse construído a partir do banco de dados tradicional com dados pré-computados. O objetivo deste teste é analisar a relevância da utilização de um data warehouse sem, no entanto, analisar os custos para sua manutenção. Para alcançar o objetivo exposto foi construída a visão materializada LOCDIALOJAFILME a partir da tabela LOCACAO. O plano de execução da consulta Q7 é, praticamente, o mesmo da consulta Q3, mas o tamanho da visão materializada é menor que o tamanho da tabela de LOCACAO. A seguir apresenta-se a visão materializada V1 e a consulta Q7. V1: CREATE MATERIALIZED VIEW LocDiaLojaFilme(idlojadlf, idfilmedlf, anodlf, mesdlf, diadlf, nfitdlf, slocdlf) AS SELECT idlojaloc, idfilmeloc, anoloc, mesloc, dialoc, COUNT(*), SUM(valorlocfita) FROM Locacao GROUP BY idlojaloc, idfilmeloc, anoloc, mesloc, dialoc REFRESH INCREMENTAL CICLE 1 DAY Q7: SELECT FROM WHERE GROUP BY bairro, anodlf, SUM(nfitdlf), SUM(slocdlf) Loja, LocDiaLojaFilme, Filme idloja=idlojadlf AND idfilmedlf=idfilme AND genero='drama' bairro, anodlf Os tempos de execução das consultas Q3 e Q7 são ilustrados na Figura 6-15. Estas consultas são processadas sem a aplicação do agrupamento prévio. O uso de uma visão materializada, consulta Q7, garantiu a redução do tempo de execução da consulta, pois os grupos já pré-computados são utilizados imediatamente. Assim, evita-se um processamento a mais durante a execução da consulta, o que ocorreu em todas as configurações de arquivos paralelos. Mas, essa redução pode ser ainda mais evidente caso 83 outras visões materializadas sejam criadas a partir de V1 ou haja uma mudança na especificação da própria V1, por exemplo, realizando o agrupamento das locações anualmente para todos os filmes. Desta maneira, a dimensão tempo irá refletir as locações anuais. Tempos de execução das Consultas Q3 e Q7 sem agrupamento prévio 1.600 Tempos em segundos Q3 (Sem DW) 1.483,49 1.400 Q7 (Com DW) 1.200 1.191,31 1.000 800 748,38 600 614,43 400 354,88 218,28 200 74,07 296,29 124,84 0 1 2 4 50,83 16 8 Nº de servidores de arquivos paralelos Figura 6-15: Tempos de Execução das Consultas Q3 e Q7 sem Agrupamento Prévio. A seguir será ilustrada a aplicação do agrupamento prévio juntamente com um data warehouse que também melhorou o desempenho da consulta Q3, conforme apresenta o gráfico da Figura 6-16. Tempos de execução das consultas Q3 e Q7 com agrupamento prévio 350 Tempos em segundos Q3 (Sem DW) 315,82 300 Q7 (Com DW) 250 200 213,13 150 97,72 100 75,81 50 35,71 17,88 9,60 36,02 0 1 2 4 8 21,20 16 10,52 Nº de servidores de arquivos paralelos Figura 6-16: Tempos de Execução das Consultas Q3 e Q7 com Agrupamento Prévio. Como pode ser observado, os ganhos obtidos são melhores com um reduzido número de servidores, pois a aplicação do agrupamento prévio sobre um único servidor reduz sensivelmente a diferença entre o uso e não do data warehouse e, deste modo, influencia nos 84 resultados quando são acrescidos mais servidores. Outro fato que colabora com este comportamento é a reduzida quantidade de dados utilizada nos testes devido às limitações existentes no laboratório. Embora os resultados apresentados com a utilização do agrupamento prévio não tenham sido satisfatórios a sua aplicação pode evitar dois problemas existentes em um data warehouse: o custo necessário de espaço em disco e sua manutenção. Mas, não foram analisados os custos de concorrência entre essa consulta e as outras operações sobre as tabelas bases. Assim, um projeto bem cuidadoso determinará a coexistência entre um data warehouse e a técnica de agrupamento prévio. O speedup e a eficiência das consultas Q3 e Q7 são apresentadas nas Figuras 6-17 e 618 sem a aplicação do agrupamento prévio. Speedup das consultas Q3 e Q7 sem agrupamento prévio Q3 (Sem DW) Q7 (Com DW) 24 23,436 Tempos em segundos 20 20,029 16 12 9,543 8 4,021 4 1,939 1,982 0 2 6,796 4,180 4 8 16 Nº servidores de arquivos paralelos Figura 6-17: Speedup das Consultas Q3 e Q7 sem Agrupamento Prévio. Nota-se que a partir de 4 servidores o speedup aumenta gradativamente quando utilizado o data warehouse. Isso ocorre porque o acréscimo de servidores aumenta o paralelismo, há uma melhor distribuição dos dados e, assim, menos processamento será exigido em cada servidor. Outra questão que colabora com esse comportamento vem do fato desta consulta produzir poucos grupos e, desta forma, a comunicação não sobrepõe à computação. 85 Eficiência das consultas Q3 e Q7 sem agrupamento prévio Q3 (Sem DW) Q7 (Com DW) 1,5 1,465 1,4 1,3 1,252 1,2 1,193 1,045 1,1 0,991 1,0 0,969 0,9 1,005 0,850 0,8 2 4 8 16 Nº servidores de arquivos paralelos Figura 6-18: Eficiência das Consultas Q3 e Q7 sem Agrupamento Prévio. Comparando-se a eficiência com a aplicação do data warehouse nota-se, também, o ganho a partir de 4 servidores, devido aos mesmos motivos já apresentados. As Figuras 6-19 e 6-20 ilustram respectivamente, o speedup e a eficiência para c consulta Q7. O baixo speedup apresentado com a aplicação do agrupamento prévio deve-se aos mesmos motivos já apresentados quando foram feitas as considerações sobre a Figura 616. Speedup das consultas Q3 e Q7 com agrupamento prévio Q3 (Sem DW) Q7 (Com DW) 36 32,911 Tempos em segundos 32 28 24 20,267 20 17,659 16 12 8 8,844 10,051 3,232 4 2,811 0 2 5,917 4 8 16 Nº servidores de arquivos paralelos Figura 6-19: Speedup das Consultas Q3 e Q7 com Agrupamento Prévio. 86 Eficiência das consultas Q3 e Q7 com agrupamento prévio Q3 (Sem DW) 2,4 2,211 Q7 (Com DW) 2,207 2,2 2,057 2,0 1,8 1,616 1,6 1,479 1,4 1,406 1,256 1,267 1,2 1,0 2 4 8 16 Nº servidores de arquivos paralelos Figura 6-20: Eficiência das Consultas Q3 e Q7 com Agrupamento Prévio. 6.6. Comparativo entre Consultas Variando a Carga de Trabalho Para obter o tempo de execução de consultas que selecionam quantidades variadas de cargas de trabalho para os servidores foram definidas as consultas Q8, Q9, Q10, Q11 e Q12. Essas consultas diferem-se pela condição de seleção sobre o atributo idlojaloc da tabela LOCACAO. Desta maneira, os servidores relacionados com determinadas lojas são escolhidos para executar a consulta. As consultas são representadas em SQL conforme a seguir. Q8: SELECT FROM WHERE GROUP BY genero, SUM(valorlocfita) Filme, Locacao idfilme=idfilmeloc AND idlojaloc =16 genero Q9: SELECT FROM WHERE GROUP BY genero, SUM(valorlocfita) Filme, Locacao idfilme=idfilmeloc AND idlojaloc >=15 genero Q10: SELECT FROM WHERE GROUP BY genero, SUM(valorlocfita) Filme, Locacao idfilme=idfilmeloc AND idlojaloc >=13 genero Q11: SELECT FROM WHERE GROUP BY genero, SUM(valorlocfita) Filme, Locacao idfilme=idfilmeloc AND idlojaloc >=9 genero 87 Q12: SELECT FROM WHERE GROUP BY genero, SUM(valorlocfita) Filme, Locacao idfilme=idfilmeloc AND idlojaloc >=1 genero A Figura 6-21 apresenta o gráfico com os tempos de execução dessas consultas. Execução de consultas variando a carga de trabalho 1.500 1.632,405 1 Servidor 2 Servidores Tempos em Segundos 4 Servidores 1.200 8 Servidores 16 Servidores 880,550 900 600 300 505,285 206,110 303,513 73,254 0 Q8 73,071 Q9 73,486 Q10 73,913 Q11 74,893 Q12 Figura 6-21: Tempo de Execução de Consultas Variando a Carga de Trabalho O tempo de execução entre as consultas para um único servidor aumenta à medida que se executam as consultas. Esse fato ocorre porque essas consultas aumentam a carga de trabalho para o servidor através da seleção de uma quantidade maior de dados. Observa-se, também, que o mesmo comportamento ocorre para 2, 4 e 8 servidores. No entanto, algumas consultas apresentam tempos de execução muito próximos para determinadas configurações de servidores, por exemplo, as consultas Q11 e Q12 para dois servidores. Isso ocorre porque a carga de trabalho da consulta Q12 é distribuída entre os dois servidores, mantendo-se, assim, praticamente o mesmo custo da consulta Q11. Desta forma, conclui-se que o tempo de execução de uma consulta depende de como os dados estão distribuídos e da condição de seleção envolvida na consulta. Portanto, é importante que os dados estejam particionado de tal forma que o custo da comunicação não supere o ganho com o paralelismo. 88 6.7. Considerações Finais As avaliações de desempenho obtidas com a execução do estudo de caso demonstraram o potencial existente no FAP. Esse ambiente possibilitou a descoberta do comportamento de certos tipos de consultas, mesmo considerando as limitações de teste existentes. Os resultados obtidos durante a execução das consultas com a aplicação do agrupamento prévio indicaram que essa estratégia permite ganhos de desempenho com algumas consultas. Para consultas que produzem poucos grupos no primeiro agrupamento, tem-se um ganho relativamente bom. Se a quantidade de grupos produzidos for grande, o custo de execução da consulta aumenta. Um filtro seletivo ajuda a melhorar ainda mais o desempenho da consulta. Mas, sua aplicação está diretamente relacionada com a condição de seleção da consulta. Assim, deve-se utilizá-lo quando o custo da consulta possa ser reduzido. A utilização de tabelas pré-computadas de um data warehouse para a execução de uma consulta revelou-se muito importante. No entanto, necessita-se realizar novos esforços de pesquisa evidenciando sua utilização concorrente e seus custos para manutenção. No próximo capítulo são registradas as conclusões deste trabalho, apresentadas as suas contribuições e algumas perspectivas para trabalhos futuros. 89 7. Conclusões, Contribuições e Trabalhos Futuros 7.1. Conclusões O desempenho de consultas com agregados tem se tornado um fator crítico em bancos de dados com muitas informações, pois o acesso a disco ainda é um grande problema nesses sistemas. Assim, diversas propostas têm surgido para melhorar o desempenho, como o uso do paralelismo e a aplicação do agrupamento prévio. Neste trabalho, foram apresentados resultados favoráveis quanto à utilização do paralelismo e do agrupamento prévio. Para isso, foi proposto e implementado um ambiente, o FAP, para a execução de consultas paralelas com agregados que vêm auxiliar na análise do comportamento de consultas. Para atingir este objetivo, foi feita uma revisão bibliográfica, que mostrou as principais estratégias seqüenciais e paralelas utilizadas no processamento de agregados e no agrupamento prévio. Além disso, foram detalhados os módulos básicos da arquitetura do FAP. Para a implementação do FAP foram utilizadas as especificações do CDBS que ajudou a criação do banco de dados paralelo. Além disso, foi importante o estudo e a utilização de uma biblioteca com rotinas para a troca de mensagens para o gerenciamento dos dados paralelos. A escolha da distribuição LAM foi muito apropriada devido ao seu suporte à linguagem C++ e ao padrão MPI-IO. Apesar dos resultados favoráveis obtidos quanto à utilização do agrupamento prévio, deve-se realizar novos estudos para determinar um modelo de estimativas de custos que auxilie na tomada de decisão quanto à utilização dessa estratégia. Ao final deste trabalho, conclui-se que o entendimento do comportamento da execução de consultas não é uma tarefa fácil. Essa dificuldade deve-se aos recursos disponíveis em uma máquina, aos diferentes tipos de consultas, ao tipo de distribuição dos dados e a quantidade de 90 dados que influenciam as tarefas de um processador de consultas. No entanto, o ambiente proposto permitiu um melhor entendimento do comportamento da execução de consultas com agregados. 7.2. Contribuições Este trabalho apresenta uma série de contribuições envolvendo o processamento de consultas em ambientes paralelos e distribuídos que serão destacados a seguir. Foi examinada a bibliografia existente, mostrando um contexto geral do processamento e otimização de consultas, dando uma atenção especial à operação de agregação. Além disso, foram apresentados os conceitos do paradigma de troca de mensagens e a descrição de um ambiente de troca de mensagens com recursos para o acesso a arquivos paralelos. Foi definido e implementado o FAP, um ambiente de software para o processamento de consultas que: (i) possibilita a criação de um banco de dados com dados distribuídos; (ii) oferece um ambiente para testes e coleta de resultados, de maneira que os resultados coletados visam uma melhor compreensão dos fatores que podem limitar o tempo de execução de uma consulta; (iii) permite o acréscimo de novas estratégias para a execução das operações de junção e agregação e (iv) possibilita o acréscimo de outras estratégias para o processamento paralelo com agregados. Os resultados mostram o desempenho nas situações propostas neste trabalho, apesar das limitações existentes no ambiente físico de testes. A análise desses resultados oferece importantes direções, complementando a avaliação como um todo. Os resultados analisados ajudaram descobrir que o número de grupos produzidos no primeiro agrupamento é um fator que limita o desempenho de uma consulta. Desta forma, foi definida uma estratégia simples, baseada na contagem do número de valores distintos dos atributos de agrupamento, que evita o uso agrupamento prévio. No entanto, essa estratégia não constitui uma solução definitiva, pois outras alternativas devem ser estudadas e comparadas. 91 Como conseqüência natural destes resultados, foi proposto e descrito um filtro seletivo, que é uma estratégia de otimização a ser utilizada junto com o agrupamento prévio. Desta forma, reduz-se ainda mais o tempo de execução de uma consulta. Além disso, foram identificados e mencionados os pontos críticos relacionados a utilização do filtro seletivo. Um dos problemas que ainda persiste com a execução de uma consulta sobre muitos dados é o acesso a disco, principalmente, no processamento de agregados. Para consultas que produzem poucos grupos obteve-se melhor desempenho, pois o processamento é realizado na memória principal. Por outro lado, quando a consulta produz muitos grupos então são necessários acessos a disco usando um algoritmo de duas passagens. Para minimizar este problema, a adoção de um data warehouse paralelo é uma boa alternativa, pois o uso de dados pré-computados armazenados em visões materializadas ajuda a reduzir a quantidade de processamento. Uma outra idéia é o uso de uma estratégia para a fase de mesclagem em algoritmo de duas passagens destinado ao processamento de agregados. Esta abordagem demonstrou ganhos em até três vezes para quantidades de dados não muito grandes durante os testes. Uma outra vantagem é que, para essa estratégia, são necessários apenas três buffers durante o processamento de uma consulta. 7.3. Trabalhos Futuros Diversas questões podem ser abordadas em trabalhos futuros e, dentre elas, pode-se enumerar: • Implementação de outras estratégias para o processamento do agrupamento prévio em paralelo avaliando suas características e determinando políticas de utilização; • Avaliação do agrupamento prévio usando outros tipos de distribuição de dados, tais como a distribuição hash e a distribuição circular; • Verificação do comportamento de estruturas de dados estáticas e dinâmicas no processamento de consultas com agregados, tais como vetores, tabelas hash e árvores, levando em consideração o tamanho da memória principal disponível; • Inclusão de técnicas de pipelining durante a execução de consultas e a utilização de diferentes políticas de buffering e caching para minimizar os acessos a discos; 92 • Definição de métodos de acesso mais apropriados para um plano de consulta, a fim de que os recursos disponíveis sejam utilizados da melhor forma possível; • Construção de planos de execução para cada servidor de arquivos paralelos, levando em consideração as informações estatísticas dos dados de cada servidor; • Determinação de um modelo de estimativas de custos que defina de maneira apropriada, a melhor utilização do agrupamento prévio e do filtro seletivo; • Avaliação de consultas com outras propriedades de agrupamento prévio, tais como a invariante e a união generalizada; • Construção de uma interface amigável para o processamento de consultas no FAP, para que o ambiente possa ser utilizado por um maior número de pessoas; • Avaliação da estratégia proposta quanto ao esquema de composição dos resultados usando pares de partições durante a agregação para grandes quantidades de dados; e • Adoção de novas políticas de paralelismo para o agrupamento prévio como o paralelismo particionado e pipelined. 93 Referências Bibliográficas [Bar95] Baru, C. K.; Fecteau G.; Goyal A.; Hsiao, H.; Jhingran, A.; Padmanabhan, S.; Copeland, G. P.; Wilson, W. G. DB2 Parallel Edition. IBM Systems Journal, v.34, p.292-322, abril/1995. [Ber91] Bergsten, M. C.; Valduriez, P. Prototyping DBS3, a Shared-Memory Parallel Database System, In First International Conference on Parallel and Distributed Information Systems, p.226-234, Miami Beach, Florida, dezembro/1991. [Bit83] Bitton, D.; Boral, H.; DeWitt, D. J.; Wilkinson, W. K. Parallel Algorithms for the Execution of Relational Database Operations, ACM Transactions on Database Systems, v.8, p.324-353, setembro/1983. [Bor90] Boral, H.; Clay, L.; Copeland, G.; Danforth, S.; Franklin, M.; Hart, B.; Smith, M.; Valduriez, P. Prototyping Bubba, A Highly Parallel Database System. IEEE Transactions on Knowledge and Data Engineering, v.2, p.424, março/1990. [Cha94] Chaudhuri, S.; Shim, K. Including Group-By in Query Optimization. Proceedings of 20th International Conference on VLDB, p.354-366, Santiago de Chile, Chile, setembro/1994. [Cha97] Chaudhuri, S.; Dayal, U. An Overview of Data Warehousing and OLAP Technology. SIGMOD Record, v.26, p.65-74, março/1997. [Cha98] Chaudhuri, S. An Overview of Query Optimization in Relational Systems. Proceedings of the Seventeenth ACM SIGACT-SIGMOD-SIGART Symposium on Principles of Database Systems, p.34-43, Seattle, Washington, junho/1998. [Cod70] Codd, E. A Relational Model for Large Shared Communications of the ACM, v.13, p.377-387, junho/1970. [Cos01] Costa Neto, J. C. Considerações sobre a Integração de um Banco de Dados e um Data Warehouse sobre um Sistema de Arquivos Paralelos. Tese de doutorado, Escola Politécnica da Universidade de São Paulo, 2001. [Cro94] Crowl, L. How to Measure, Present and Compare Parallel Performance. IEEE Parallel and Distributed Technology, v.2, p.9-25, 1994. [DeW90] DeWitt, D. J.; Ghandeharizadeh, S.; Schneider, D.; Bricker, A.; Hsiao, H.; Rasmussen, R. The Gamma Database Machine Project. IEEE Trans. On KDE, v.2, p.44-62, março/1990. [DeW92] DeWitt, D. J.; Gray, J. Parallel Database Systems: The Future of High Performance Database Processing. CACM v. 35, p.85-98, junho/1992. Data Banks. 94 [Elm00] Elmasri, R.; Navathe, S. R. Fundamentals of Database Systems. Addison Wesley, 3ª ed., 2000. [Eng95] Englert, S.; Glasstone, R.; Hasan, W. Parallelism and its Price: A Case Study of NonStop SQL/MP. SIGMOD RECORD, v.24, p.61-71, dezembro/1995. [Gar99] Garcia-Molina, H.; Labio, W. J.; Wiener, L. J.; Zhuge, Y. Distributed and Parallel Computing Issues in Data Warehousing, 1999. [on line]. <URL: http://www-db.stanford.edu/warehousing/publications.html. [Gar00] Garcia-Molina, H.; Ullman, J. D.; Widom, J. Database System Implementation.Prentice-Hall, Inc., 2000. [Gei94] Geist, A.; Beguelin, A.; Dongarra, J.; Jiang, W.; Manchek, R.; Sunderam, V. PVM: Parallel Virtual Machine – A Users’ Guide And Tutorial for Networked Parallel Computing, The MIT Press, 1994. [Gra90] Graefe, G. Encapsulation of Parallelism in the Volcano Query Processing System, ACM SIGMOD, v.19, p.102-111, Atlantic City, NJ, USA, junho/1990. [Gra93] Graefe, G. Query Evaluation Techniques for Large Databases, ACM Computing Surveys v.25, p.73-170, junho/1993. [Gua99] Guardia, H. C. Considerações sobre as Estratégias de um Sistema de Arquivos Paralelos Integrado ao Processamento Distribuído. Tese de doutorado, Escola Politécnica da Universidade de São Paulo, 1999. [Has96] Hasan, W. Optimization of SQL Queries for Parallel Machines. Springer Verlag, 1996. [Hon92] Hong, W. Exploiting Inter-Operation Parallelism in XPRS, ACM SIGMOD, p.19-28, San Diego, California, junho/1992. [Jar84] Jarke, M.; Koch, J. Query Optimization in Database Systems, ACM Computing Surveys, v.16, p.111-152, junho/1984. [Kim95] Kim, W. Modern Database Systems: The Object Model, Interoperability, and Beyond. Reading, MA, Addison Wesley/ACM Press, 1995. [Kim98] Kimball, R. Data Warehouse Tookit. Makron Books, 1998. [Lar97] Larson, P. Grouping and Duplicate Elimination: Benefits of Early Aggregation, Technical Report MSR-TR-97-36, Microsoft Research, Redmond, dezembro/1997. 95 [Mpi95] Message Passing Interface Forum. MPI: A Message Passing Interface Standard, Versão 1.0, junho/1995. [on line]. <URL: http://www.mpi-forum.org/docs/docs.html. [Mpi97] Message Passing Interface Forum. MPI-2: Extension to the Message Passing Interface, Versão 2.0, julho/1997. [on line]. <URL: http://www.mpi-forum.org/docs/docs.html. [Pat88] Patterson, D. A.; Gibson, G.; Katz, R. H. A Case for Redundant Arrays of Inexpensive Disks (RAID). Proc. of the ACM SIGMOD Conf., p.109-116, Chicago, junho/1988. [Pau03] Paula, N. C.; Costa Neto, J. C.; Sato, L. M. Uma Arquitetura para o Processamento de Consultas com Agregados. WSCAD 2003 – IV Workshop em Sistemas Computacionais de Alto Desempenho, p.101-108, São Paulo, novembro/2003. [Sel79] Selinger, P. G.; Astrahan, M. M.; Chamberlin, D. D.; Lorie, R. A.; Price, T. G. Access Path Selection in a Relational Database Management System. Proc. of the ACM SIGMOD Conf., p.23-34, New York, USA, 1979. [Sha94] Shatdal, A.; Naughton, J. F. Processing Aggregates in Parallel Database Systems, Technical Report CS-TR-94-1233, Computer Sciences Department, University of Wisconsin, Madison, junho/1994. [Sha95] Shatdal, A.; Naughton, J. F. Adaptive Parallel Aggregation Algorithms, Conference SIGMOD, p.104-114, San Jose, California, 1995. [Tan95] Tandem. Query Processing Using NonStop SQL/MP, 1995. [on line]. <URL:http://www.tandem.com. [Tan00a] Taniar, D.; Rahayu, J. W. Parallel Processing of Aggregate Queries in a Cluster Architecture. Proc. of the 7th Australasian Conf. on Parallel and Real-Time Systems, Springer-Verlag, novembro/2000. [Tan00b] Taniar, D.; Liu, K. H.; Jiang, Y.; Leung, C. H. C. Aggregate-Join Query Processing in Parallel Database Systems, Proc. of the 4th HPCAsia’2000 Int. Conf, v.2, p.824-829, IEEE CS Press, 2000. [Tan01] Taniar, D.; Rahayu, J. W. Parallel Processing of "GroupBy-Before-Join" Queries in Cluster Architecture. 1st International Symposium on Cluster Computing and the Grid, v.26, p.15-18, março/2001. [Tha90] Thakkar, S. S.; Sweiger, M. Performance of an OLP Application on Symmetric Multiprocessor System. 17th International Symposium on Computer Architecture, p.228-238, maio/1990. 96 [Tha99] Thakur, R.; Gropp, W.; Lusk, E. On Implementing MPI-IO Portably and with High Performance. 6th Workshop on I/O in Parallel and Distributed Systems, p.23-32, maio/1999. [Ull97] Ullman. J. D.; Widom, J. A First Course in Database Systems. PrenticeHall, Inc., 1997. [Waq96] Waqar, H.; Florescu, D.; Valduriez, P. Open Issues in Parallel Query Optimization, SIGMOD Record, v.25, p.28-33, setembro/1996. [Wei02] Weininger, A. Efficient Execution of Joins in a Star Schema, ACM SIGMOD, p.542-545, junho/2002. [Wil99] Wilkinson, B.; Allen, M. Parallel Programming: Techniques and Applications Using Networked Workstations and Parallel Computers. Prentice-Hall, Inc., 1999. [Yan94a] Yan, W. P.; Larson, P. Performing Group-By Before Join, Proceedings of the IEEE, v.10, p.89-100, Houston, Texas, USA, 1994. [Yan94b] Yan, W. P.; Larson, P. Data reduction through early grouping, Proc. of the 1994 IBM CAS Conference, Toronto, Ontario, novembro/1994. [Yan97] Yang, Y.; Singhal, M. A Comprehensive Survey of Join Techniques in Relational Databases, 1997. [on line]. <URL:http://citeseer.nj.nec.com/ yang97comprehensive.html. [Yu98] Yu, C. T.; Meng, W. Principles of Database Query Processing for Advanced Applications. Morgan Kaufmann Publishers, Inc., 1998. 97 Apêndices Apêndice A. Um Exemplo de uma Aplicação de Banco de Dados Neste apêndice encontra-se o exemplo de uma aplicação, o de uma rede de locadoras de filmes para vídeo-cassete, utilizado no decorrer deste trabalho. Essa aplicação foi proposta em [Cos01]. Além disso, serão apresentados dois conjuntos de dados também utilizados durante o trabalho. As lojas da rede estão distribuídas em dez bairros de uma grande cidade e recebem remuneração pelos serviços prestados de aluguel de cópias de filmes a seus clientes. A quantidade de locações feitas pelas lojas da rede a partir de 1992 é muito grande, considerando o tempo de existência da rede, por volta de onze anos. As relações do banco de dados da rede de locadoras estão ilustradas pela Figura A-1. Loja Cliente idcli nomecli idlojacli dtnasc responsavel rua num_rua bairro cep fone Locacao idloc idcliloc idlojaloc idfilmeloc dialoc mesloc anoloc valorlocfita idloja nomeloja classe rua num_rua bairro cep zona fone Filme idfilme nomefilme gênero censura produtora numfitasloja numfitaslocdia valorlocdia Figura A-1: Modelo Lógico da Rede de Locadoras [Cos01, p. 13]. O esquema a seguir apresenta as tabelas do banco de dados da rede de locadoras: CLIENTE (idcli, nomecli, idlojacli, dtnasc, responsavel, rua, num_rua, bairro, cep, fone), LOJA (idloja, nomeloja, classe, rua, num_rua, bairro, cep, zona, fone), FILME (idfilme, nomefilme, genero, censura, produtora, numfitasloja, numfitaslocdia, valorlocdia), LOCACAO (idloc, idcliloc, idlojaloc, idfilmeloc, dialoc, mesloc, anoloc, valorlocfita). 98 As tabelas LOJA, CLIENTE, FILME e LOCACAO têm como chave primária os campos idloja, idcli, idfilme e idloc, respectivamente. Há relacionamentos entre as tabelas através de chave estrangeira, por exemplo, os atributos idcliloc, idlojaloc e idfilmeloc presentes na tabela LOCACAO, fazendo ligação com as respectivas tabelas CLIENTE, LOJA e FILME. Devido aos descontos e promoções periódicos, o valor recebido correspondente à locação de uma fita não é funcionalmente determinado pela chave primária da tabela FILME (idfilme), por isto é armazenado na tabela LOCACAO no campo valorlocfita. A tabela LOCACAO determina a ordem de grandeza do banco de dados porque o movimento das locações de filmes nas lojas da rede é registrado nesta tabela. A Tabela A-1 apresenta a massa de dados de exemplos do banco de dados da rede de locadoras utilizada para exemplificar algumas situações no decorrer do trabalho. Tabela Cliente Loja Filme Locação Nº de Nº de Registros Atributos 5.000 10 16 12 2.000 9 1.000.000 9 Comprimento do Registro (bytes) 93 121 77 44 Tamanho 454,1 (Kb) 1,9 (Kb) 150,4 (Kb) 42,0 (Mb) Tabela A-1: Massa de Dados de Exemplos. Outro conjunto de dados foi definido para fazer a avaliação do ambiente conforme está ilustrado na Tabela A-2. Tabela Cliente Loja Filme Locação Nº de Nº de Registros Atributos 5.000 10 16 12 64 9 5.760.000 9 Comprimento do Registro (bytes) 93 121 77 44 Tabela A-2: Massa de Dados de Testes. Tamanho 454,1 (Kb) 1,9 (Kb) 4,8 (Kb) 241,7(Mb) 99 Apêndice B. Principais Classes do FAP Neste apêndice encontra-se a descrição das principais classes implementadas no FAP. A interface de cada classe está representada pelos seus métodos e atributos principais. //classe catálogo: gerenciar o catálogo da base dados. class Catalog { public: ... //adicionar servidor de arquivos paralelos void AddServer(char *server); //adicionar tabela void AddTable(char *tablename, int segtype, char *segfield, int reptype, int tabletype); //adicionar campo de tabela void AddField(char *tablename, char *fieldname, char *fieldtype, int fieldsize, int is_pk, int is_fk, char *ref_field, int field_statistic); //adicionar visao void AddMatView(char *viewname, int segtype, char *segfield, int reptype, int viewtype, int refreshtype, int maniptype, char *refreshcycle, char *dependsfrom, double nextrefreshtime); //adicionar campo de visao void AddFieldMatView(char *viewname, char *fieldname, char *fieldtype, int fieldsize, int is_pk, int is_fk, char *ref_field, int iscalc, int func, int is_grp_field, int field_statistic); //criar o catalogo void Create(); … //server: é o nome do servidor de arquivos paralelos. //tablename: é o nome da tabela. //segtype: indica o tipo de segmentação (0:nenhuma distribuição; 1:distribuição circular; 2:distribuição // por faixa de valores; 3:distribuição por função hashing). //segfield: indica o campo de segmentação da tabela. //reptype: indica o tipo de replicação (0:sem replicação; 1:com replicação). //tabletype: indica o tipo da tabela (1:tabela de fato; 2:tabela de dimensão; 3:outros). //fieldname: é o nome do campo. //fieldtype: é o tipo do campo (int, char, long) //fieldsize: é o tamanho do campo. //is_pk: indica se o campo é chave primária. //is_fk: indica se o campo é chave estrangeira. //ref_field: o nome do campo da tabela da qual ele é chave estrangeira. //field_statistic: indica se campo participará da coleta estatística. //viewname: indica o nome da visão materializada. //viewtype: ver tabletype. //refreshtype: indica o tipo de atualizacao (0:completo; 1:incremental). //maniptype: indica se a visao gerara novos registros, sem alterar os antigos, ou recalculara os // registros antigo. //refreshcycle: é o periodo entre um refresh e outro. //dependsfrom: indica ligacao entre o campo e outro da tabela fonte. //nextrefreshtime: indica o instante do proximo refresh. //iscalc: indica se o campo é direto (0) ou calculado (1). //func: qual funcao de calculo que sera disparada (0:null; 1:sum; 2:count; 3:avg; 4:min; 5:max; etc). //is_grp_field: se o campo eh de agrupamento. }; //classe buffer de dados: gerenciar uma área da memória principal para armazenamento temporário dos registros //que serão processados pelo sistema. class Buffer_Data { 100 public: ... int Insert(char *data); int Get(char *retval); int Remove(char *retval); int TableToBuffer(Table *table); int BufferToTable(Table *table); int GetLenght(); int GetMaxLenght(); void Free(); ... }; //inserir um registro //recuperar um registro corrente sem remove-lo do buffer e avançar o ponteiro //recuperar um registro corrente removendo-a do buffer e avançar o ponteiro //ler os registros de table da posicao corrente e armazena-los no buffer //gravar os registros do buffer para a table a partir da posição corrente //obter a quantidade de rgistros armazenados no buffer //obter a capacidade maxima do buffer //liberar os dados do buffer //classe campo de uma tabela: gerenciar cada campo de uma tabela. class Field { public: … //inserir as informacoes de um campo void Insert(char *tablename, char *fieldname, char *fieldtype, int fieldsize, int is_pk, int is_fk, char *ref_field, int is_statistic); //inserir as informações de uma tabela/visao void Insert(char *tablename, char *fieldname, char *fieldtype, int fieldsize, int is_pk, int is_fk, char *ref_field, int iscalc, int func, int is_grp_field, int is_statistic); //definir a ordem do campo no registro void SetOrder(int ord); //definir a posicao dos bytes de gravacao no registro (inic:posicao onde inicia o registro; fin:posicao onde termina o registro) void SetBytes(int inic, int fin); … //para mais informacoes sobre os argumentos dos métodos ver a classe catálogo. }; //classe tabela: gerenciar cada tabela do sistema. class Table { public: //metodos … //inserir uma tabela. void Insert(char *tablename, int segtype, char *segfield, int reptype, int tabletype, char tableflag); //inserir uma visao. void Insert(char *viewname, int segtype, char *segfield, int reptype, int viewtype, int refreshtype, int maniptype, char *refreshcycle, char *dependsfrom, double nextrefreshtime, char tableflag); void InsertField(Field *f); //inserir um campo na tabela ListObject *GetFields(); //obter todos os campos. char *GetTableName(); //obter o nome da tabela. Field *FindField(char *fieldname); //devolver o objeto Field se existir da tabela, caso contrario NULL, //pesquisando o fieldname, usar a tabela hash. void Create(char *name,char *modo); //criar. void Open(char *name,char *modo); //abrir. void Close(); //fechar . void InsertRecord(RECORD r); //inserir um registro. void UpDateRecord(RECORD r); //atualizar o registro corrente. void DeleteRecord(RECORD r); //apagar logicamente (colocar uma marca) o registro corrente. void PackRecords(); //apagar fisicamente os registros marcados. void WriteBufferToTable(ListString *buffer); //gravar todos os registros que estao no buffer na tabela. void SeekRecord(int origin); //posicionar o ponteiro da tabela em origin. int GetRecord(RECORD r); //obter o registro corrente. 101 ... private: //atributos … #ifdef EXECUTION_PARALLEL File_MPI *ofile; //ponteiro para arquivo paralelo. #else File_Text *ofile; //ponteiro para arquivo seqüencial. #endif oHash h_fields; //os objetos campos da tabela em um hash. ListObject l_fields; //os objetos campos da tabela em uma lista encadeada (mantém a ordem do catálogo). }; //classe abstrata arquivo: gerenciar o acesso a disco através de classes derivadas. class File { public: //metodos ... virtual void Create(char *nome,char *modo)=0; //criar (nome: nome do arquivo; modo: modo de criação, //ver fopen do C++). virtual void Close()=0; //fechar. virtual void Open(char *nome,char *modo)=0; //abrir (ver Create). virtual int IsOpenFile()=0; //verificar se esta aberto. virtual int long GetOffSet()=0; //obter o offset. virtual char *ReadAll()=0; //devolver todo o conteudo. virtual int ReadRecord(RECORD r)=0; //ler o registro corrente r. virtual void WriteRecord(RECORD r)=0; //gravar um registro r. virtual void UpDateRecord(RECORD r)=0; //atualizar o registro corrente. virtual void DeleteRecord(RECORD r)=0; //apagar logicamente (colocar uma marca) o registro corrente. virtual void PackRecords()=0; //apagar fisicamente os registros marcados. virtual void Seek(long int offset,int origin)=0; //posicionar o ponteiro (ver fseek do C++). virtual int FEof()=0; //verificar se atingiu final do arquivo. … //atributos char path_filename[1000]; //path de leitura/gravacao do arquivo. char mode[10]; //modo de criação/abertura do arquivo. FILE *ptr_f; //ponteiro para arquivos seqüenciais. MPI_File fh; //ponteiro para arquivos paralelos. ... }; //classe arquivo padrao do tipo texto: gerenciar o acesso a disco através de um arquivo do tipo texto. class File_Text : public File { public: … void Create(char *nome,char *modo); void Close(); void Open(char *nome,char *modo); int IsOpenFile() {return(ptr_f!=NULL);}; long GetOffSet(); char *ReadAll(); int ReadRecord(RECORD r); void WriteRecord(RECORD r); void UpDateRecord(RECORD r); void DeleteRecord(RECORD r); void PackRecords(); void Seek(long offset,int origin); 102 int FEof(); … //para mais informacoes ver a classe File }; //classe arquivo do MPI/IO: gerenciar o acesso a disco através das rotinas do MPI/IO. class File_MPI : public File { public: … void Create(char *nome,char *modo); void Close(); void Open(char *nome,char *modo); int IsOpenFile() { return(fh!=NULL); }; int long GetOffSet(); char *ReadAll() { return(NULL); }; int ReadRecord(RECORD r); void WriteRecord(RECORD r); void UpDateRecord(RECORD r); void DeleteRecord(RECORD r); void PackRecords(); void Seek(long int offset,int whence); int FEof(); … //para mais informacoes ver a classe File }; //classe base de dados: classe principal do sistema para gerenciar todos os eventos referentes ao acesso ao banco de //dados entre os processos cliente e servidores de arquivos paralelos. class DbFap { public: DbFap(char *basename); //construtor seqüencial. DbFap(char *basename,Host *host); //construtor paralelo. void Init(); //inicializacoes. ~DbFap(); //destrutor. //inserir um servidor de arquivos paralelos. void AddServer(char *Server); //inserir uma tabela. void AddTable(char *tablename, int segtype, char *segfield, int reptype, int tabletype); //inserir um campo de uma tabela. void AddFieldTable(char *tablename, char *fieldname, char *fieldtype, int fieldsize, int is_pk, int is_fk, char *ref_field, int field_statistic); //inserir uma visao. void AddMatView(char *viewname, int segtype, char *segfield, int reptype, int viewtype, int refreshtype, int maniptype, char *refreshcycle, char *dependsfrom, double nextrefreshtime); //inserir um campo de uma visao. void AddFieldMatView(char *viewname, char *fieldname, char *fieldtype, int fieldsize, int is_pk, int is_fk, char *ref_field, int iscalc, int func, int is_grp_field, int field_statistic); //criar o catalogo no path definitivo. void CreateCatal(); Table *GetTable(char *tablename); //obter uma tabela pelo tablename. void Create(); //criar o banco de dados. void Open(); //abrir o banco de dados. void Close(); //fechar o banco de dados. void InsertRecord(char *tablename,RECORD r); //inserir um registro na tabela tablename. void UpDateRecord(char *tablename,RECORD r); //atualizar o registro corrente na tabela tablename. void DeleteRecord(char *tablename,RECORD r); //apagar logicamente (colocar uma marca) no registro corrente //na tabela tablename. 103 void PackRecords (char *tablename,RECORD r); //apagar fisicamente os registros com marca na tabela tablename. void SeekRecord(char *tablename,int whence); //posicionar o ponteiro a partir de whence para a tabela //tablename (whence: ver fseek do C++). int GetRecord(char *tablename,RECORD r); //obter o registro corrente. void CreateStatistic(); //criar a estatistica das tabelas. void StatisticToMem(); //transfere a estatistica para cada campo de uma tabela. void SetQueryName(char *name); //definir um nome para uma consulta. void Query(char *query); //executar uma consulta sql (query: a consulta em sql). void MatView(char *viewname,char *query);//executar uma visao, materializando em disco (viewname: nome da visao, //query:a consulta). //aplicar ou nao o agrupamento prévio. int ApplyGroupByBeforeJoin_Yes(); //sim. int ApplyGroupByBeforeJoin_No(); //nao. //aplicar ou nao o Filtro Seletivo int ApplyFilterGroupByBeforeJoin_Yes(); //sim. int ApplyFilterGroupByBeforeJoin_No(); //nao. //definir as tecnicas para o processamento das consultas. int SetTechnical_Join_Hash(); //juncao hash. int SetTechnical_Join_LoopAninhate(); //juncao laco aninhado. int SetTechnical_Join_Merge(); //juncao merge (sem sort). int SetTechnical_GroupBy_Hash(); //agrupamento hash. int SetTechnical_GroupBy_Tree(); //agrupamento arvore binaria. int SetTechnical_GroupBy_BPlus(); //agrupamento arvore B+. int SetTechnical_GroupBy_Scan(); //agrupamento com varredura com ordenacao. int SetTechnical_GroupBy_ScanRepeated(); // agrupamento varredura com repeticao int SetTechnical_GroupBy_UnionRepeated(); // agrupamento uniao com repeticao //apoio ao MPI int GetSegment(Table *t, RECORD r); //verifica se o registro a ser gravado pertence ao segmento. void ReplicateCatal(char *name_catal); //enviar o catalogo para os servidores. void RecvCatal(); //Os servidores recebem o catalogo. void SendStatistic(); //Os servidores enviam a sua estatistica para o cliente. void RecvStatistic(); //cliente cria um arquivo de estatistica para cada servidor. void ClearFileLogs(); //apagar todos os arquivos de logs. ... //para mais informacoes sobre os argumentos dos métodos ver a classe catálogo, tabela ou campo. }; //classe sql: gerenciar as consultas escritas em sql para serem executadas no sistema. class Sql { public: //metodos ... void Query(DbFap *db,char *query); //executar a consulta (db: objeto banco de dados; query: consulta em sql). int ParserQuery(); //analisar lexicamente, sintaticamente a consulta void MakePlanInitial(); //construir o plano de consulta inicial void MakePlanExec(); //construir o plano de execucao da consulta void MakePlanAloc(); //construiro plano de alocacao de recursos (servidores de arquivos) void MakeBitVector(); //construir vetores de bits para cada atributo de selecao //aplicar otimizacoes void OptimizationHeuristic(); //otimizacao heuristica void OptimizationGroupByBeforeJoin(); //otimizacao GroupByBeforeJoin void OptimizationEstimCustToGBBJ(); //aplicar estimativa de custo basico para o GBBJ int ApplyFilterGBBJ(Table *pt_table,RECORD r); //aplicar o filtro no GBBJ void Execute(); //executar a consulta //executar a projecao. Table *_ExecutePI(char *tablename_in, //tabela de entrada. int n_fields, //número de campos projetados. char *fields[], //campos projetados. 104 }; char *tablename_out); //tabela de saida. //executar a selecao. Table *_ExecuteSIGMA(char *tablename_in, //tabela de entrada. char *field_1, //primeiro operando. char *field_2, //segundo operando. char *operador, //operador de condicao. char *tablename_out); //tabela de saída. //executar uma junção de laço aninhado. Table *_ExecuteJOIN_LoopAninhate(char *table_1, //primeira tabela de entrada. char* field_1, //campo da primeira tabela de entrada. char *table_2, //segunda tabela de entrada. char* field_2, //campo da segunda tabela de entrada. char *operador, //operador de condicao. char *tablename_out); //tabela de saida. //executar uma junção hash. Table _ExecuteJOIN_Hash(/*ver juncao laço aninhado*/); //executar uma junção merge. Table *_ExecuteJOIN_Merge(/*ver juncao laço aninhado*/); //executar uma agregação Table *_ExecuteGAMA(char *tablename_in, //tabela de entrada. int n_fields, //número de campos de agrupamento. char *fields[], //campos de agrupamento. char *tablename_out, //tabela de saída. LEFT_DEEP_TREE *plan); //plano de execução. //apoio ao MPI char *QueryPlanCod(); //transformar o plano em string para ser enviado aos servidores para ser executado. void CoordExecQuery(int id_query); //coordena as atividades para a execucao da consulta. int ReceptionResults(); //cliente faz a recepcao dos resultados. void SendResult(Table *t); //servidores enviam seus resultados para o cliente. ... //atributos PlanQuery *plan; //o objeto plano de consulta DbFap *db; //o objeto banco de dados ... //classe de vetor bits utilizada no filtro seletivo: gerenciar a construção e verificação do filtro seletivo no sistema. class BitVector { public: BitVector(int size); //construtor (size: capacidade do vetor) ~BitVector(); //destrutor void Insert(int key); //marcar um bit baseado na key int Find(int key); //verificar se o bit esta marcado baseado para a key ... }; 105 Apêndice C. Estrutura de Diretórios do FAP O FAP foi implementado na estrutura de diretórios ilustrada pela Figura C-1. O diretório "fap_db", o diretório padrão, contém o código fonte e o arquivo executável da aplicação. fap_db <basename> catalog performances plans Figura C-1: Estrutura de Diretórios usada pelo FAP O diretório "<basename>" contém as tabelas do banco de dados do sistema e os arquivos com os resultados das consultas. Além disso, é o diretório utilizado para o armazenamento dos arquivos temporário criados durante a execução das consultas. O nome desse diretório será fornecido diretamente pela aplicação durante a criação do objeto da classe DbFap. O diretório "catalog" contém o arquivo de catálogo "CatalogDb" utilizado pela aplicação para consultar e gravar os dados nas tabelas. Neste diretório também são gravadas as estatísticas das tabelas do banco de dados no arquivo "StatisticDb" que serão utilizadas na otimização das consultas. O diretório "performances" contém os arquivos com os resultados de desempenho das consultas executadas no FAP. O diretório "plans" contém os planos de consulta criados durante a otimização das consultas. No processo cliente é construído os seguintes planos: inicial, heurístico, agrupamento prévio, execução e codificado. Os servidores decodificam o plano recebido do processo cliente. 106 Apêndice D. Catálogo do Banco de Dados para o Estudo de Caso Neste apêndice encontram-se as informações do catálogo utilizado durante a execução do estudo de caso. A estrutura do catálogo especificada e detalhada em [Cos01] passou por duas alterações. Uma para possibilitar a realização da estatística dos dados das tabelas e a outra para aplicar o filtro seletivo. A primeira alteração é o acréscimo do campo field_statistic na definição das informações dos campos de uma tabela (cat_t_fields) ou visão materializada (cat_t_views). Os valores possíveis nesse novo campo são 0 e 1 que correspondem respectivamente não realizar ou realizar a estatística para o campo especificado. A segunda alteração corresponde no acréscimo do campo tabletype na definição das informações da tabela (cat_tables) que possui como possíveis valores: 1 que classifica uma tabela como tabela de fatos e 2 como de dimensão. A seguir é apresentada a estrutura do catálogo com esse novo acréscimo. cat_sites sites host_0 host_1 host_2 host_3 cat_tables tablename loja cliente filme locacao //servidores de arquivos paralelos //tabelas segtype segfield 0 null 2 idlojacli 0 null 2 idlojaloc reptyle 1 0 1 0 cat_t_fields //campos das tabelas tablename fieldname fieldtype fieldsize loja idlojalj int 4 loja nomeloja char 15 loja classe char 15 loja rualj char 15 loja numrualj int 6 loja bairrolj char 15 loja ceplj char 10 tabletype 2 2 2 1 is_pk 1 0 0 0 0 0 0 is_fk 0 0 0 0 0 0 0 ref_field null null null null null null null field_statistic 1 0 1 0 0 1 0 107 loja loja loja loja loja cliente cliente cliente cliente cliente cliente cliente cliente cliente cliente filme filme filme filme filme filme filme filme filme locacao locacao locacao locacao locacao locacao locacao locacao locacao zona fonelj cliinic qtcli deletedlj idcli nomecli idlojacli dtnasc respons ruacli numruacli bairrocli fonecli deletedcli idfilmefil nomefilme idgenfil genero censura produtora nfitasloja vrlocdia deletedfil idloc idcliloc idlojaloc idfilmeloc dialoc mesloc anoloc vrlocfita deletedloc char char int int int int char int long int char int char long int int char int char int char int long int long int int int int int int long int 10 10 4 4 1 6 16 4 8 6 16 6 12 8 1 4 16 2 16 4 16 4 5 1 8 6 4 4 2 2 4 3 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 null null null null null null null idlojalj null idcli null null null null null null null null null null null null null null null idcli idlojalj idfilmefil null null null null null cat_views //visão materializada viewname segtype segfield reptyle viewtype refreshtype nextrefrestime locdialjf 2 idlojadlf 0 1 1 cat_v_fields //campos da visão materializada viewname fieldname fieldtype fieldsize is_pk locdialjf idlojadlf int 4 1 locdialjf idfilmedlf int 4 1 locdialjf anodlf int 4 1 locdialjf mesdlf int 2 1 locdialjf diadlf int 2 1 locdialjf nfitdlf int 10 0 locdialjf slocdlf long 10 0 is_fk 1 1 1 1 1 0 0 1 0 0 0 0 1 0 1 1 1 0 0 1 0 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 1 1 0 0 manitype refreshcycle dependsfrom 1 locacao ref_field idlojaloc idfilmeloc anoloc mesloc dialoc null vrlocfita day is_calc 0 0 0 0 0 1 1 func 0 0 0 0 0 2 1 is_grp_field 1 1 1 1 1 0 0 0 field_statistic 1 1 1 1 1 0 0 108 Apêndice E. Código Fonte utilizado no Estudo de Caso Neste apêndice encontra-se o código fonte das principais rotinas utilizadas para executar o estudo de caso no FAP. // =========================================================================================== // SQL DAS CONSULTAS // =========================================================================================== //Define a quantidade de fitas locadas e o valor faturado com locacoes, //por bairro, anualmente, para filmes do genero 'drama'. void Q3(DbFap *db) { Rotinas time; time.SetTimeInit(); db->Query("SELECT loj.bairrolj, loc.anoloc, COUNT(*), SUM(loc.vrlocfita) FROM loja as loj, locacao as loc, filme as film WHERE loj.idlojalj == loc.idlojaloc AND loc.idfilmeloc == film.idfilmefil AND film.genero == 'drama' GROUP_BY loj.bairrolj, loc.anoloc"); printf("\nTempo de execucao:%fs",time.GetTimePassed()); } //Define a quantidade de fitas locadas diariamente, para filmes do genero 'faroeste'. void Q4(DbFap *db) { Rotinas time; time.SetTimeInit(); db->Query("SELECT f.nomefilme, l.anoloc, l.mesloc, l.dialoc, COUNT(*) FROM filme as f, locacao as l WHERE f.genero == 'faroeste' AND f.idfilmefil == l.idfilmeloc GROUP_BY f.nomefilme, l.anoloc, l.mesloc, l.dialoc"); printf("\nTempo de execucao:%fs",time.GetTimePassed()); } //Define a quantidade de fitas locadas anualmente, para filmes do genero 'faroeste'. void Q5(DbFap *db) { Rotinas time; time.SetTimeInit(); db->Query("SELECT f.nomefilme, l.anoloc, COUNT(*) FROM filme as f, locacao as l WHERE f.genero == 'faroeste' AND f.idfilmefil == l.idfilmeloc GROUP_BY f.nomefilme, l.anoloc"); printf("\nTempo de execucao:%fs",time.GetTimePassed()); } //Define a quantidade de fitas locadas diariamente, para filmes do genero 'faroeste'. void Q6(DbFap *db) { Rotinas time; } time.SetTimeInit(); db->Query("SELECT f.nomefilme, l.anoloc, l.mesloc, l.dialoc, COUNT(*) FROM filme as f, locacao as l WHERE f.genero == 'faroeste' AND f.idfilmefil == l.idfilmeloc GROUP_BY f.nomefilme, l.anoloc, l.mesloc, l.dialoc"); printf("\nTempo de execucao:%fs",time.GetTimePassed()); 109 //Define a quantidade de locacoes e o valor faturado com locacoes, diariamente, filme por filme, //para cada loja. void V1(DbFap *db) { db->MatView("locdialjf","SELECT l.idlojaloc, l.idfilmeloc, l.anoloc, l.mesloc, l.dialoc, COUNT(*), SUM(l.vrlocfita) FROM locacao as l GROUP_BY l.idlojaloc, l. idfilmeloc, l.anoloc, l.mesloc, l.dialoc"); db->CreateStatistic(); //criar novas estatisticas db->StatisticToMem(); //atualizar estruturas com as novas estatisticas } //Define a quantidade de fitas locadas e o valor faturado com locacoes, //por bairro, anualmente, para filmes do genero 'drama'. void Q7(DbFap *db) { Rotinas time; time.SetTimeInit(); db->Query("SELECT loj.bairrolj, loc.anodlf, SUM(loc.nfitdlf), SUM(loc.slocdlf) FROM loja as loj, locdialjf as loc, filme as film WHERE loj.idlojalj == loc.idlojadlf AND loc.idfilmedlf == film.idfilmefil AND film.genero == 'drama' GROUP_BY loj.bairrolj, loc.anodlf"); printf("\nTempo de execucao:%fs",time.GetTimePassed()); } //Define o valor faturado com locacoes por genero para loja com idlojaloc=16 void Q8(DbFap *db) { Rotinas time; time.SetTimeInit(); db->Query("SELECT f.genero, SUM(l.vrlocfita) FROM filme as f, locacao as l WHERE f.idfilmefil == l.idfilmeloc AND l.idlojaloc == 16 GROUP_BY f.genero"); printf("\nTempo de execucao:%fs",time.GetTimePassed()); } //Define o valor faturado com locacoes por genero para loja com idlojaloc>=15 void Q9(DbFap *db) { Rotinas time; time.SetTimeInit(); db->Query("SELECT f.genero, SUM(l.vrlocfita) FROM filme as f, locacao as l WHERE f.idfilmefil == l.idfilmeloc AND l.idlojaloc >= 15 GROUP_BY f.genero"); printf("\nTempo de execucao:%fs",time.GetTimePassed()); } //Define o valor faturado com locacoes por genero para loja com idlojaloc>=13 void Q10(DbFap *db) { Rotinas time; time.SetTimeInit(); db->Query("SELECT f.genero, SUM(l.vrlocfita) FROM filme as f, locacao as l WHERE f.idfilmefil == l.idfilmeloc AND l.idlojaloc >= 13 GROUP_BY f.genero"); printf("\nTempo de execucao:%fs",time.GetTimePassed()); } 110 //Define o valor faturado com locacoes por genero para loja com idlojaloc>=9 void Q11(DbFap *db) { Rotinas time; time.SetTimeInit(); db->Query("SELECT f.genero, SUM(l.vrlocfita) FROM filme as f, locacao as l WHERE f.idfilmefil == l.idfilmeloc AND l.idlojaloc >= 9 GROUP_BY f.genero"); printf("\nTempo de execucao:%fs",time.GetTimePassed()); } //Define o valor faturado com locacoes por genero para loja com idlojaloc>=1 void Q12(DbFap *db) { Rotinas time; time.SetTimeInit(); db->Query("SELECT f.genero, SUM(l.vrlocfita) FROM filme as f, locacao as l WHERE f.idfilmefil == l.idfilmeloc AND l.idlojaloc >= 1 GROUP_BY f.genero"); printf("\nTempo de execucao:%fs",time.GetTimePassed()); } // =========================================================================================== // EXECUCAO DAS CONSULTAS // =========================================================================================== void Execute(DbFap *db) { char *ranks[20]; int i, nr_hosts=17; //determinar os hosts for(i=0;i<nr_hosts;i++) { ranks[i]=new char[10]; sprintf(ranks[i],"host_%d",i); } CreateCatalog(db,ranks,nr_hosts); db->Create(); db->Open(); InserirRegistros(db); //criar o catalogo do banco de dados //criar o banco de dados //abrir o banco de dados //inserir os registro na banco de dados db->ClearFileLogs(); //apagar os arquivos de log db->SetTechnical_Join_Hash(); //definir a tecnica HASH para a juncao db->SetTechnical_GroupBy_Hash(); //definir a tecnica HASH para o agrupamento V1(db); //executar a visao materializada 1 db->ApplyGroupByBeforeJoin_No(); db->ApplyFilterGroupByBeforeJoin_No(); db->SetQueryName("Q3NN"); Q3(db); db->SetQueryName("Q4NN"); Q4(db); //aplicar o agrupamento previo? (Nao) //aplicar o filtro seletivo? (Nao) //nomear a consulta //executar a consulta 3 //nomear a consulta //executar a consulta 4 db->ApplyGroupByBeforeJoin_Yes(); db->ApplyFilterGroupByBeforeJoin_No(); //aplicar o agrupamento previo? (Sim) //aplicar o filtro seletivo? (Nao) 111 db->SetQueryName("Q3YN"); Q3(db); db->SetQueryName("Q4YN"); Q4(db); //nomear a consulta //executar a consulta 3 //nomear a consulta //executar a consulta 4 db->ApplyGroupByBeforeJoin_Yes(); db->ApplyFilterGroupByBeforeJoin_No(); db->SetQueryName("Q5YN"); Q5(db); db->SetQueryName("Q6YN"); Q6(db); //aplicar o agrupamento previo? (Sim) //aplicar o filtro seletivo? (Nao) //nomear a consulta //executar a consulta 5 //nomear a consulta //executar a consulta 6 db->ApplyGroupByBeforeJoin_Yes(); db->ApplyFilterGroupByBeforeJoin_Yes(); db->SetQueryName("Q5YY"); Q5(db); db->SetQueryName("Q6YY"); Q6(db); //aplicar o agrupamento previo? (Sim) //aplicar o filtro seletivo? (Sim) //nomear a consulta //executar a consulta 5 //nomear a consulta //executar a consulta 6 db->ApplyGroupByBeforeJoin_No(); db->ApplyFilterGroupByBeforeJoin_No(); db->SetQueryName("Q7NN"); Q7(db); //aplicar o agrupamento previo? (Nao) //aplicar o filtro seletivo? (Nao) //nomear a consulta //executar a consulta 7 db->ApplyGroupByBeforeJoin_Yes(); db->ApplyFilterGroupByBeforeJoin_No(); db->SetQueryName("Q7YN"); Q7(db); //aplicar o agrupamento previo? (Sim) //aplicar o filtro seletivo? (Nao) //nomear a consulta //executar a consulta 7 db->ApplyGroupByBeforeJoin_Yes(); db->ApplyFilterGroupByBeforeJoin_No(); db->SetQueryName("Q8YN"); Q8(db); db->SetQueryName("Q9YN"); Q9(db); db->SetQueryName("Q10YN"); Q10(db); db->SetQueryName("Q11YN"); Q11(db); db->SetQueryName("Q12YN"); Q12(db); //aplicar o agrupamento previo? (Sim) //aplicar o filtro seletivo? (Nao) //nomear a consulta //executar a consulta 8 //nomear a consulta //executar a consulta 9 //nomear a consulta //executar a consulta 10 //nomear a consulta //executar a consulta 11 //nomear a consulta //executar a consulta 12 db->Close(); //fechar o banco de dados //liberar memoria for(i=0;i<nr_hosts;i++) delete ranks[i]; }