Academia.eduAcademia.edu

JDBC (Java DB Connectivity) Concorrente

Departamento de Universidade de Aveiro Electrónica, Telecomunicações e Informática 2011 Wilson Bertino Lopes dos Santos JDBC (Java DB Connectivity) Concorrente Departamento de Universidade de Aveiro Electrónica, Telecomunicações e Informática 2011 Wilson Bertino JDBC (Java DB Connectivity) Concorrente Lopes dos Santos Dissertação apresentada à Universidade de Aveiro para cumprimento dos requisitos necessários à obtenção do grau de Mestre em Engenharia de Computadores e Telemática, realizada sob a orientação cientı́fica do Dr. Diogo Nuno Pereira Gomes, Assistente Convidado do Departamento de Electrónica, Telecomunicações e Informática da Universidade de Aveiro e do Mestre Óscar Narciso Mortágua Pereira, Assistente Convidado do Departamento de Electrónica, Telecomunicações e Informática da Universidade de Aveiro o júri / the jury presidente / president Prof. Dr. Rui Luı́s Andrade Aguiar Professor Associado da Universidade de Aveiro vogais / examiners committee Prof. Dra. Maribel Yasmina Campos Alves Santos Professora Auxiliar do Dep. de Sistemas de Informação da Universidade do Minho (arguente principal) Dr. Diogo Nuno Pereira Gomes Assistente Convidado da Universidade de Aveiro (orientador) Mestre Óscar Narciso Mortágua Pereira Assistente Convidado da Universidade de Aveiro (co-orientador) agradecimentos / Os meus agradecimentos vão para os meus pais, cujo esforço e dedicação acknowledgements permitiu-me atingir e superar esta etapa. Resumo A API JDBC permite aos programas Java manipularem dados de uma base de dados. No entanto, a definição da API não prevê uma utilização concorrente dos seus serviços, não é por isso possı́vel partilhar objectos JDBC em segurança entre threads. Neste documento é descrita uma implementação concorrente da interface ResultSet. Esta interface é utilizada para ler ou modificar linhas do resultado da execução de uma instrução SQL. O driver JDBC foi criado para SQL Server 2008. De modo a avaliar o desempenho da solução desenvolvida foram realizados testes de desempenho comparando-a com a implementação do driver da Microsoft, em que se criou um ResultSet por thread. Os resultados mostraram que a ideia desenvolvida produz um aumento de desempenho em ambientes multithreaded. Abstract The JDBC API allows Java programs to access data stored on a data base. However, the API specification doesn’t provide a solution for concurrent access to its interfaces, so it isn’t safe to shared the same JDBC object between threads. This document describes the concurrent implementation of the Result Set interface. This interface is used to read or modify lines in the result of executing a SQL statement. The JDBC driver was created for SQL Server 2008. In order to assess its performance, the developed solution was benchmarked against the situation where it is created one ResultSet per thread using Microsoft’s implementation of the JDBC driver. Results show that the solution increases performance on a multithreaded environment. Conteúdo Conteúdo iii Lista de tabelas v Lista de figuras viii 1 Introdução 1 1.1 Integração de Linguagens de programação e Bases de dados . . . . . . . . . . 2 1.2 O que é JDBC? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.3 O que é um JDBC driver? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.4 Breve tutorial JDBC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.4.1 Instalação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.4.2 Criar uma ligação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.4.3 Executar uma query . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Motivação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 1.5 2 Implementação de um driver JDBC 2.1 2.2 13 Camada TDS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.1.1 TDSMessage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.1.2 ITDSResultSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 Camada JDBC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3 Arquitectura do ResultSet Concorrente 3.1 3.2 3.3 19 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.1.1 Problema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 3.1.2 Ideia base da solução . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 ResultSet Wrapper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.2.1 Utilização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Cursor Concorrente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3.3.1 Anatomia de um ResultSet . . . . . . . . . . . . . . . . . . . . . . . . 24 3.3.2 Cache individual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 3.3.3 Cache partilhado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3.3.4 Utilização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 i 4 Benchmark 31 4.1 Benchmark principal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 4.2 Benchmark com atrasos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 4.3 Cache individual vs Cache partilhado . . . . . . . . . . . . . . . . . . . . . . . 38 5 Resultados 39 5.1 Plataforma de teste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 5.2 Benchmark principal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 5.2.1 Comparação com MSJDBC . . . . . . . . . . . . . . . . . . . . . . . . 40 5.2.2 Comparação com WJDBC . . . . . . . . . . . . . . . . . . . . . . . . . 48 5.2.3 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 Benchmark com atrasos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 5.3.1 Atraso entre colunas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 5.3.2 Atraso entre linhas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 5.3.3 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 Cache individual vs Cache partilhado . . . . . . . . . . . . . . . . . . . . . . . 69 5.4.1 Fetch size 10% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 5.4.2 Fetch size 20% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 5.4.3 Fetch size 50% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 5.4.4 Fetch size 75% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 5.4.5 Fetch size 100% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 5.4.6 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 5.3 5.4 6 Discussão 6.1 75 Análise de resultados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 6.1.1 Comparação com JDBC . . . . . . . . . . . . . . . . . . . . . . . . . . 75 6.1.2 Comparação com WJDBC . . . . . . . . . . . . . . . . . . . . . . . . . 78 6.1.3 Comparação com atrasos . . . . . . . . . . . . . . . . . . . . . . . . . 79 6.1.4 Cache individual vs Cache partilhado . . . . . . . . . . . . . . . . . . 80 6.2 Conclusão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 6.3 Trabalho relacionado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 6.4 Trabalho Futuro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 Glossário 84 Acrónimos 85 Bibliografia 87 A Estudo do SQLServerResultSet 93 A.1 Cursores no SQL Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 A.1.1 Fetching e Scrolling . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 A.1.2 Concorrência . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 ii A.1.3 Tipos de cursor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 A.2 Tipos de cursor por result set . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 A.3 Adaptive Buffering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 B Tabular Data Stream 101 B.1 Mensagens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 B.2 Pacotes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 B.2.1 Cabeçalho . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 B.2.2 Zona de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 B.3 Tokenless Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 B.3.1 Pre-Login . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 B.3.2 Login . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 B.3.3 SQLBatch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 B.4 Token Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 C Cursor Stored Procedures C.1 sp cursor 107 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 C.1.1 Sintaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 C.1.2 Argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 C.2 sp cursoropen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 C.2.1 Sintaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 C.2.2 Argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 C.3 sp cursorfetch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 C.3.1 Sintaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 C.3.2 Argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 C.4 sp cursorclose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 C.4.1 Sintaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 C.4.2 Argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 D Funcionalidade implementada 113 D.1 Implementação JDBC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 D.2 Implementação TDS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 iii iv Lista de Tabelas 1.1 Serviços do ResultSet que permitem mover o cursor. . . . . . . . . . . . . . . 8 1.2 Serviços do ResultSet que permitem modificar os dados do dataset. . . . . . . 9 2.1 Descrição dos serviços da classe TDSMessage . . . . . . . . . . . . . . . . . . 15 2.2 Descrição dos serviços da interface ITDSResultSet . . . . . . . . . . . . . . . 16 2.3 Interfaces da API JDBC implementadas. . . . . . . . . . . . . . . . . . . . . . 17 3.1 Descrição dos atributos da implementação do ResultSet. . . . . . . . . . . . . 24 3.2 Métodos reimplementados pela classe CursorIndividualCache. . . . . . . . . . 27 3.3 Métodos reimplementados pela classe CursorSharedCache . . . . . . . . . . . 28 A.1 Tipos de cursor suportados pelo driver . . . . . . . . . . . . . . . . . . . . . . 96 A.1 Tipos de cursor suportados pelo driver . . . . . . . . . . . . . . . . . . . . . . 97 A.1 Tipos de cursor suportados pelo driver . . . . . . . . . . . . . . . . . . . . . . 98 B.1 Campos do cabeçalho TDS . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 B.2 Indicação das mensagens que usam tokens . . . . . . . . . . . . . . . . . . . . 104 B.3 Opções da mensagem de Pre-Login . . . . . . . . . . . . . . . . . . . . . . . . 105 B.4 Packet Data Token Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 C.1 Stored prodecures do sistema relevantes para a implementação do driver JDBC. 107 D.1 Métodos implementados da interface Driver . . . . . . . . . . . . . . . . . . . 113 D.2 Métodos implementados da interface Statement . . . . . . . . . . . . . . . . . 113 D.3 Métodos implementados da interface ResultSet . . . . . . . . . . . . . . . . . 114 D.4 Tipos SQL suportados pelo driver. . . . . . . . . . . . . . . . . . . . . . . . . 115 v vi Lista de Figuras 1.1 Arquitectura JDBC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.1 Arquitectura de um driver JDBC do tipo 4 para SQL Server 2008. . . . . . . 14 2.2 Classe TDSMessage. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.3 Interface ITDSResultSet. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 3.1 Representação da relação entre o ResultSet, o cursor servidor e o dataset . . . 19 3.2 Relação de muitos para um entre o ResultSet e o cursor do servidor. . . . . . 20 3.3 Diagrama de classes da solução ResultSet Wrapper. . . . . . . . . . . . . . . . 21 3.4 Diagrama das classes envolvidas na utilização das solução Cursor Wrapper. . 23 3.5 Visão geral da implementação do ResultSet . . . . . . . . . . . . . . . . . . . 24 3.6 Diagrama de classes do cursor com cache individual. . . . . . . . . . . . . . . 26 3.7 Diagrama de classes da implementação do cursor com cache partilhado. . . . 28 3.8 Classes principais na utilização da solução Cursor. . . . . . . . . . . . . . . . 30 4.1 Tabela utilizada no benchmark. . . . . . . . . . . . . . . . . . . . . . . . . . . 34 4.2 Medição do tempo de preparação . . . . . . . . . . . . . . . . . . . . . . . . . 34 4.3 Medição do tempo de execução . . . . . . . . . . . . . . . . . . . . . . . . . . 35 5.1 M SJDBC/CJDBCI , no contexto Actualização. . . . . . . . . . . . . . . . 40 5.2 M SJDBC/CJDBCS , no contexto Actualização. . . . . . . . . . . . . . . . 41 5.3 M SJDBC/W JDBC, no contexto Actualização. . . . . . . . . . . . . . . . 42 5.4 M SJDBC/CJDBCI , no contexto Leitura. . . . . . . . . . . . . . . . . . . 42 5.5 M SJDBC/CJDBCS , no contexto Leitura. . . . . . . . . . . . . . . . . . . 43 5.6 M SJDBC/W JDBC, no contexto Leitura. . . . . . . . . . . . . . . . . . . . 44 5.7 M SJDBC/CJDBCI , no contexto Inserção. . . . . . . . . . . . . . . . . . . 44 5.8 M SJDBC/CJDBCS , no contexto Inserção. . . . . . . . . . . . . . . . . . . 45 5.9 M SJDBC/W JDBC, no contexto Inserção. . . . . . . . . . . . . . . . . . . 46 5.10 M SJDBC/CJDBCI , no contexto Remoção. . . . . . . . . . . . . . . . . . 46 5.11 M SJDBC/CJDBCS , no contexto Remoção. . . . . . . . . . . . . . . . . . 47 5.12 M SJDBC/W JDBC, no contexto Remoção. . . . . . . . . . . . . . . . . . 48 5.13 W JDBC/CJDBCI , no contexto Actualização. . . . . . . . . . . . . . . . . 49 5.14 W JDBC/CJDBCS , no contexto Actualização. . . . . . . . . . . . . . . . . 50 5.15 W JDBC/CJDBCI , no contexto Leitura. . . . . . . . . . . . . . . . . . . . 50 vii 5.16 W JDBC/CJDBCS , no contexto Leitura. . . . . . . . . . . . . . . . . . . . 51 5.17 W JDBC/CJDBCI , no contexto Inserção. . . . . . . . . . . . . . . . . . . . 51 5.18 W JDBC/CJDBCS , no contexto Inserção. . . . . . . . . . . . . . . . . . . 52 5.19 W JDBC/CJDBCI , no contexto Remoção. . . . . . . . . . . . . . . . . . . 53 5.20 W JDBC/CJDBCS , no contexto Remoção. . . . . . . . . . . . . . . . . . . 53 5.21 M SJDBC/CJDBCI , efeito do atraso no contexto Actualização. . . . . . . 55 5.22 M SJDBC/CJDBCS , efeito do atraso no contexto Actualização. . . . . . . 56 5.23 M SJDBC/W JDBC, efeito do atraso no contexto Actualização. . . . . . . 56 5.24 M SJDBC/CJDBCI , efeito do atraso no contexto Leitura. . . . . . . . . . 57 5.25 M SJDBC/CJDBCS , efeito do atraso no contexto Leitura. . . . . . . . . . 58 5.26 M SJDBC/W JDBC, efeito do atraso no contexto Leitura. . . . . . . . . . 58 5.27 W JDBC/CJDBCI , efeito do atraso no contexto Actualização. . . . . . . . 59 5.28 W JDBC/CJDBCS , efeito do atraso no contexto Actualização. . . . . . . . 60 5.29 W JDBC/CJDBCI , efeito do atraso no contexto Leitura. . . . . . . . . . . 61 5.30 W JDBC/CJDBCS , efeito do atraso no contexto Leitura. . . . . . . . . . . 61 5.31 M SJDBC/CJDBCI , efeito do atraso no contexto Actualização. . . . . . . 62 5.32 M SJDBC/CJDBCS , efeito do atraso no contexto Actualização. . . . . . . 63 5.33 M SJDBC/W JDBC, efeito do atraso no contexto Actualização. . . . . . . 64 5.34 M SJDBC/CJDBCI , efeito do atraso no contexto Leitura. . . . . . . . . . 64 5.35 M SJDBC/CJDBCS , efeito do atraso no contexto Leitura. . . . . . . . . . 65 5.36 M SJDBC/W JDBC, efeito do atraso no contexto Leitura. . . . . . . . . . 66 5.37 W JDBC/CJDBCI , efeito do atraso no contexto Actualização. . . . . . . . 67 5.38 W JDBC/CJDBCS , efeito do atraso no contexto Actualização. . . . . . . . 67 5.39 W JDBC/CJDBCI , efeito do atraso no contexto Leitura. . . . . . . . . . . 68 5.40 W JDBC/CJDBCS , efeito do atraso no contexto Leitura. . . . . . . . . . . 69 5.41 CJDBCI /CJDBCS M , no contexto 10 . . . . . . . . . . . . . . . . . . . . . 70 5.42 CJDBCI /CJDBCS M , no contexto 20. . . . . . . . . . . . . . . . . . . . . . 71 5.43 CJDBCI /CJDBCS M , no contexto 50. . . . . . . . . . . . . . . . . . . . . . 71 5.44 CJDBCI /CJDBCS M , no contexto 75. . . . . . . . . . . . . . . . . . . . . . 72 5.45 CJDBCI /CJDBCS M , no contexto 100. . . . . . . . . . . . . . . . . . . . . 73 5.46 CJDBCI /CJDBCS , no contexto 100. . . . . . . . . . . . . . . . . . . . . . . 73 6.1 Comparação entre CJDBC e M SJDBC, no contexto Leitura . . . . . . . . . . 77 6.2 Comparação entre CJDBC e M SJDBC, no contexto Actualização . . . . . . . 77 viii Capı́tulo 1 Introdução O objectivo deste trabalho é realizar uma implementação concorrente de um driver JDBC para SQL Server 2008. Este primeiro capı́tulo começa por referir o problema da integração de linguagens de programação e bases de dados, explicando a razão da escolha do JDBC como solução. É fornecida uma descrição mais detalhada do que consiste o JDBC, incluindo um pequeno tutorial exemplificando como se pode utilizar a API JDBC para aceder a dados numa base de dados. Este capı́tulo explica também o que é um driver JDBC. Por fim são identificados os problemas que a API JDBC apresenta no âmbito da execução de código concorrente, e que estão na base da motivação para a realização deste trabalho. No capı́tulo 2 é explicada a arquitectura da implementação do driver JDBC para SQL Server 2008. No capı́tulo 3 são apresentadas as soluções para o problema da partilha de objectos JDBC e é explorado em maior profundidade o desenvolvimento do driver JDBC, no que se refere à implementação do ResultSet. No capı́tulo 4 é descrito o método experimental utilizado para avaliar o desempenho das soluções desenvolvidas. No capı́tulo 5 apresentam-se os resultados obtidos. No capı́tulo 6 analisam-se e discutem-se os resultados. São apresentadas as conclusões, é analisado o trabalho relacionado e são indicadas sugestões para trabalhos futuros. No apêndice A é apresentado um estudo da implementação de um ResultSet para SQL Server. Este apêndice revela conceitos importantes relacionados com o que envolve implementar um ResultSet, e reunindo o que se aprendeu através do estudo do driver da Microsoft. No apêndice B é descrito o protocolo de comunicação entre aplicações cliente e o SQL Server, conhecido como Tabular Data Stream. No apêndice C são apresentados os stored procedures que existem no SQL Server e que são utilizados pelo ResultSet para manipular os dados do dataset. No apêndice D é listada a funcionalidade da API JDBC que foi implementada neste trabalho. 1 1.1 Integração de Linguagens de programação e Bases de dados A integração de bases de dados e linguagens de programação é um problema que existe quase há 50 anos [8] e que é conhecido por impedance mismatch [8, 62, 2]. Uma das principais razões da existência deste problema é o facto de estas duas entidades terem sido desenvolvidas independentemente, durante muitos anos [3]. E como consequência surgiram incompatibilidades na interoperabilidade entre a interface das linguagens procedimentais e a interface das linguagens query das bases de dados. Exemplos dessas incompatibilidades são programas imperativos versus queries declarativas, optimização ao nı́vel da compilação versus optimização ao nı́vel da query, algoritmos e estruturas de dados versus relações e ı́ndices, threads versus transações, ponteiros nulos versus nulo como ausência de dados [8]. Desde sempre, a generalidade dos programas necessitou de alguma forma de dados permanentes. Alguns programas implementam sistemas de armazenamento especı́ficos, mas a verdade é que existem diversos sistemas cuja principal função é a gestão eficiente dos dados. Os Relational Database Management Systems (RBMS) constituem soluções aceites e bem estabelecidas para essa função, e que apesar de surgirem soluções no âmbito de bases de dados orientadas a objectos, crê-se que os sistemas de base de dados relacionais continuem a existir por muitos mais anos [7]. Dois exemplos de popularidade são a Oracle Database e o SQL Server da Microsoft [74]. Existe assim, a necessidade de encontrar uma solução para a integração de linguagens de programação e bases de dados. Têm sido realizados vários esforços para integrar linguagens de programação e bases de dados. Exemplos são a exploração de linguagens de programação especializadas em base de dados, persistência ortogonal, bases de dados orientadas a objectos, modelos de transação, bibliotecas de acesso a dados, embedded queries, e mapeamento objecto-relacional [8]. A persistência ortogonal consiste em estender a existência de um objecto para além do tempo de duração da execução de um programa [5]. Um dos problemas da persistência ortogonal é que não dá espaço a optimizações [8]. PJama [45] e OPJ [4, 70] são exemplos de persistência ortogonal. Como alternativa à persistência ortogonal existe a execução explı́cita de queries. A Call Level Interface (CLI) [79] é um mecanismo predominante na execução explı́cita de queries, e permite à linguagem de programação o acesso ao database engine a partir de uma API estandardizada. Um dos principais problemas da CLI é a ausência de tipagem estática, o que causa a detecção de erros apenas em runtime. No entanto, permite melhorar o desempenho geral através da redução da latência na comunicação [8]. ODBC e JDBC são dois exemplos. As embedded queries constituem outro mecanismo de execução explı́cita de queries. Neste mecanismo as instruções (statements SQL) são escritas directamente no código fonte da linguagem de programação. Este mecanismo está a deixar de ser suportado por diversos sistemas, como por exemplo o Microsoft SQL Server [22] e Sybase [78]. Apesar das inúmeras soluções que surgiram e continuam a surgir, ainda não se chegou a um consenso na escolha de uma solução definitiva para o problema da integração. Na 2 escolha da utilização de uma das soluções, existem alguns factores a considerar tais como a portabilidade e o desempenho. A linguagem Java permite escrever aplicações independentes da plataforma, para sistemas computacionais fixos ou móveis, tendo por isso um elevado nı́vel de portabilidade. A optimização ao nı́vel da Java Virtual Machine, torna a linguagem uma solução forte também no âmbito do desempenho [1]. Para além disso, sendo o JDBC uma CLI, permite o acesso directo ao database engine, dando flexibilidade para outras opções de desempenho. A generalização da utilização da linguagem Java e a existência de vários Relational Database Management Systems com raı́zes profundas, faz do JDBC um objecto de alvo estudo. 1.2 O que é JDBC? A JDBC (Java Database Connectivity API ) é uma Application Programming Interface (API) para acesso a bases de dados. A JDBC é uma SQL-level API [77] o que significa que é possı́vel construir statements SQL e introduzi-las em chamadas a código Java. Isso faz com que se esteja praticamente a utilizar SQL e ao mesmo tempo tem-se acesso ao mundo orientado a objectos em que os resultados dos pedidos à base de dados são objectos Java e os problemas de acesso são resolvidos com a gestão de excepções. O objectivo principal do JDBC é funcionar de modo simples e flexı́vel. A JDBC não foi a primeira tentativa de uma solução para o acesso universal a bases de dados. Uma das outras soluções que se destacam é a Open DataBase Connectivity (ODBC) [69, 30], que também tem como principal objectivo fornecer uma interface uniforme de acesso a bases de dados. No entanto sofre de um pouco de excesso de complexidade [77]. O desenvolvimento da JDBC foi influenciado pelas APIs já existentes, tais como a ODBC e a X/Open SQL Call Level Interface (CLI), e foi tido o cuidado de reutilizar as principais abstracções existentes nessas APIs, com o intuito de melhorar a aceitação por parte dos fabricantes de base de dados, e aproveitar o conhecimento já existente dos utilizadores de ODBC e SQL CLI [77]. A API JDBC é definida em dois pacotes Java [53, 75]: • java.sql[54] fornece a API de acesso aos dados (normalmente guardados numa base de dados relacional). É neste pacote que se encontram as classes mais usadas: Connection, ResultSet, Statement e PreparedStatement. • javax.sql[55] fornece a API de acesso aos serviços do servidor. Este pacote fornece serviços para J2EE, tais como DataSouce e RowSet. Entre as vantagens da JDBC destacam-se a possibilidade de utilizar os sistemas de base de dados já existentes, a facilidade de aprendizagem, simplicidade na instalação e de manutenção barata. Não há a necessidade de configurações de rede. O URL JDBC contém toda a informação necessária para estabelecer a ligação [52]. 3 1.3 O que é um JDBC driver? Um JDBC driver é um componente de software que implementa a API JDBC, permitindo assim às aplicações Java interagirem com uma base de dados. A aplicação que utiliza JDBC para aceder a uma base de dados é alheia ao modo como são implementadas as interfaces que utiliza, isso é inteira responsabilidade do driver. A Figura 1.1 mostra a arquitectura tı́pica do JDBC, e vem reforçar o último ponto: uma aplicação Java utiliza o conjunto de interfaces disponibilizadas pelo JDBC, essas interfaces são implementadas pelo driver que também se encarrega de comunicar com o sistema da base de dados, seja ele qual for (Oracle, SQL Sever, MySQL, etc.). Figura 1.1: Arquitectura JDBC Cada driver JDBC é construı́do para um Database Management System (DBMS) especı́fico implementando o protocolo de comunicação de queries e resultados entre o cliente e a base de dados. Existem quatro categorias de driver[48, 65]: 1. JDBC-ODBC Utiliza um driver Open Database Connectivity fazendo uma ponte para estabelecer a comunicação. O driver JDBC converte as invocações da API JDBC em invocações a funções ODBC. Uma vez que o ODBC depende de bibliotecas nativas do sistema operativo em que a JVM esta a correr, este tipo de driver é dependente da plataforma. 2. Native-API Converte os pedidos em chamadas a uma biblioteca cliente. O driver JDBC converte as invocações a métodos da API JDBC em invocações nativas da API da base de dados. Este tipo de driver é dependente da plataforma, e necessita que as bibliotecas estejam instaladas no cliente. 3. Network-Protocol Utiliza uma camada intermédia (middleware) cuja função é converter os pedidos na linguagem (protocolo) da base de dados. A camada intermédia converte as invocações 4 a métodos da API JDBC em mensagem do protocolo da base de dados. Este tipo de driver é escrito totalmente em código Java e é independente da plataforma porque a camada intermédia encarrega-se das especificidades do sistema. 4. Native-Protocol Converte os pedidos directamente no protocolo utilizado pelo Database Management System. O seu desempenho é melhor do que o desempenho dos drivers do tipo 1 e 2 porque não existe a conversão em chamadas ODBC ou em chamadas da API da base de dados. Este tipo de driver é escrito totalmente em código Java e é independente da plataforma. A diferença deste tipo para o tipo 3 é que a conversão no protocolo da base de dados é realizada no cliente, enquanto que no driver do tipo 3 a conversão é realizada na camada intermédia. Um driver da categoria JDBC-ODBC é conhecido como driver JDBC do Tipo 1. Um driver da categoria Native-API é conhecido como driver JDBC do Tipo 2. Um driver da categoria Network-Protocol é conhecido como driver JDBC do Tipo 3. Um driver da categoria Native-Protocol é conhecido como driver JDBC do Tipo 4. 1.4 Breve tutorial JDBC Nesta secção serão apresentados e explicados alguns exemplos de utilização do JDBC para aceder aos conteúdos de uma base de dados relacional. 1.4.1 Instalação A instalação de um driver JDBC é tão simples como incluir um ficheiro jar no classpath: java -cp driver.jar:... programa Em que driver.jar é o ficheiro jar do driver que se pretende utilizar, e programa é o nome da classe Java principal. Depois no programa, antes do driver poder ser utilizado é necessário criar uma nova instância do driver utilizando: Class.forName(nome); Em que nome é uma String que possui o nome da classe que implementa a interface java.sql.Driver. Este nome pode ser obtido consultando a documentação que acompanha o driver. Se o driver implementar a versão 4.0 da API JDBC, este último passo pode ser omitido, porque a versão 4.0 introduz o carregamento automático das classes que implementam java.sql.Driver e que estão presentes no classpath, através do mecanismo Java SE Service Provider [46]. 5 1.4.2 Criar uma ligação Para criar uma ligação ao servidor apenas utiliza-se o método: Driver.getConnection(url);. O parâmetro url é uma string no formato (note-se que o url está no formato para SQL Server 2008)[14]: jdbc:sqlserver://[serverName[\instanceName][:portNumber]] [;property=value[;property=value]] Em que: • jdbc:sqlserver:// é o sub-protocolo, é constante e obrigatório. • serverName é o endereço do servidor. • instanceName é uma instância no servidor. • portNumber é o número da porta do serviço no servidor. • property é uma propriedade da ligação. As propriedades para SQL Server 2008 podem ser consultadas em [34], e dois exemplos são o nome de utilizador e a password. A Listagem 1.1 apresenta o código Java para a criação de uma ligação à base de dados utilizando JDBC. Listagem 1.1: Criação de um objecto da ligação à base de dados. 1 3 // O valor de url deve ser alterado de acordo com a configuração do sistema. String url = " jdbc : sqlserver :// localhost :1433; database = AdventureWorks " + " ; username = admin ; password = admin " ; Connection con = java . sql . DriverManager . getConnection ( url ); 1.4.3 Executar uma query O resultado da execução de uma statement SQL denomina-se por result set ou dataset e é obtido invocando o método executeQuery da interface Statement. O resultado desse método é um objecto ResultSet que fornece serviços para operar sobre as linhas do dataset, linha a linha. Para criar um result set, temos portanto, de estabelecer uma ligação com o servidor, criar uma statement1 e invocar o método executeQuery da statement, tal como demonstrado na Listagem 1.2. Listagem 1.2: Criação de um objecto result set. 2 String sql = " SELECT column1 , column2 FROM mytable " ; Statement stmt = con . createStatement (); // con é o objecto da ligação ao servidor ResultSet rs = stmt . executeQuery ( sql ); 1 Instância de uma classe que implementa a interface Statement. 6 As operações disponı́veis pelo objecto result set2 dependem do seu tipo. A criação da statement (linha 2 da listagem anterior) determina o tipo de result set que é criado. Existem três versões do método createStatement[51]: • createStatement() • createStatement(int resultSetType, int resultSetConcurrency) • createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) Os parâmetros válidos para estes métodos pertencem à interface ResultSet e são descritos a seguir. resultSetType pode ter um dos seguintes valores: • TYPE FORWARD ONLY O result set só pode ser percorrido numa direcção, da primeira para a última linha, e uma linha de cada vez. • TYPE SCROLLABLE SENSITIVE O result set pode ser percorrido em qualquer direcção, e pode-se aceder a qualquer linha; as modificações externas são visı́veis. • TYPE SCROLLABLE INSENSITIVE O result set pode ser percorrido em qualquer direcção, e pode-se aceder a qualquer linha; as modificações externas não são visı́veis. resultSetConcurrency pode ter um dos seguintes valores: • CONCUR READ ONLY O result set só suporta operações de leitura. • CONCUR UPDATABLE O result set também suporta operações de modificação. resultSetHoldability pode ter um dos seguintes valores: • HOLD CURSORS OVER COMMIT Os cursores continuam abertos após a instrução de commit. • CLOSE CURSORS AT COMMIT Os cursores são fechados após a instrução de commit. O método createStatement sem argumentos cria por pré-definição um result set do tipo TYPE FORWARD ONLY/CONCUR UPDATABLE. Por exemplo para criar um result set scrollable3 actualizável e sensı́vel a actualizações externas criamos uma statement como demonstrado na Listagem 1.3. Listagem 1.3: Criação de um result set scrollable e actualizável. 1 Statement stmt = con . createStatement ( ResultSet . TYPE_SCROLLABLE_SENSIBLE , ResultSet . CONCUR_UPDATABLE ); 2 Instância de uma classe que implementa a interface ResultSet. Result set do tipo TYPE SCROLLABLE SENSITIVE ou TYPE SCROLLABLE INSENSITIVE, permitindo o acesso aleatório às suas linhas. 3 7 1.4.3.1 Ler os valores do result set O processamento do resultado é realizado linha a linha, e acede-se a uma linha movendo o cursor4 para essa linha. A Tabela 1.1 descreve os serviços da interface ResultSet que permitem mover o cursor. Tabela 1.1: Serviços do ResultSet que permitem mover o cursor. Método Descrição next move para a próxima linha. previous move para a linha anterior. absolute(n) move para a linha n. relative(n) move n linhas a partir da linha actual. first move para a primeira linha. last move para a última linha. beforeFirst move para a posição anterior à primeira linha. afterLast move para a posição posterior à última linha. Quando o result set é criado o cursor encontra-se antes da primeira linha. A Listagem 1.4 exemplifica a leitura dos dados das colunas da primeira linha do result set. Listagem 1.4: Ler a primeira linha do result set. 2 rs . next (); // mover o cursor para a próxima linha rs . getInt (1); // ler o inteiro da primeira coluna rs . getString (2); // ler a string da segunda coluna Se o result set for do tipo TYPE FORWARD ONLY apenas o serviço next se encontra disponı́vel, se um dos outros for invocado será apresentado um erro. 1.4.3.2 Modificar os valores de um result set A interface ResultSet também permite actualizar os dados de um result set, mas a Statement que lhe dá origem tem que ser criada com o parâmetro resultSetConcurrency igual a CONCUR UPDATABLE. A Tabela 1.2 descreve os serviços da interface ResultSet que permitem modificar os dados do dataset. 4 O interface ResultSet fornece o mesmo tipo de funcionalidade que um cursor[19, 20], por isso diz-que que se está a mover o cursor quando se invoca uma operação que altera a linha em que o ResultSet se encontra. 8 Tabela 1.2: Serviços do ResultSet que permitem modificar os dados do dataset. Método Descrição updateXXX(n, val) Actualiza o valor da coluna n com o valor val. Existe um método update para cada tipo de dados, por isso XXX deve ser substituı́do por Int, String, Date, etc. updateRow Envia para o servidor as modificações realizadas à linha. moveToInsertRow Move o cursor para uma linha especial que depois pode ser enviada ao servidor para ser inserida no dataset. insertRow Envia para o servidor uma linha que deve ser acrescentada ao dataset. moveToCurrentRow Cancela a inserção da linha e move o cursor para a linha actual. deleteRow Remove uma linha do dataset. As listagens seguintes exemplificam uma operação de actualização, inserção e remoção, respectivamente. Listagem 1.5: Actualização de uma linha do result set. 1 3 // Criação do result set. Statement stmt = con . createStatement ( TYPE_SCROLL_SENSITIVE , CONCUR_UPDATABLE ); ResultSet rs = stmt . executeQuery ( sql ); 5 7 9 // Actualização do result set. rs . absolute (5); // mover para a linha 5, será actualizada. rs . updateInt (1 , val1 ); // actualizar a primeira coluna com o valor ”val1” rs . updateString (2 , val2 ); // actualizar a segunda coluna com o valor ”val2” rs . updateRow (); // enviar as modificações para o servidor Listagem 1.6: Inserção de uma linha no result set. // Criação igual ao exemplo anterior (...) 2 4 6 8 // Inserção de uma nova linha. rs . moveToInsertRow (); // iniciar uma inserção. rs . updateInt (1 , val1 ); rs . updateString (2 , val2 ); rs . insertRow (); // enviar as modificações para o servidor. rs . moveToCurrentrow (); // mover o cursor para linha actual. Listagem 1.7: Remoção de uma linha do result set. // Criação igual ao exemplo anterior (...) 2 // Remoção de uma linha. 9 4 rs . last (); // mover para a última linha, que será removida. rs . deleteRow (); // enviar as modificações para o servidor 1.5 Motivação A motivação para o desenvolvimento deste trabalho surge do facto que o JDBC não inclui mecanismos para tirar partido de um ambiente multihreaded. As operações dos objectos dos pacotes java.sql e javax.sql devem ser thread-safe [58], isto é, devem operar correctamente numa situação em que existem diversos threads a aceder a um objecto. No entanto, apenas garantir o invariante de um objecto não é tirar partido de um ambiente multithreaded, e há mesmo situações em que um objecto não deve ser partilhado entre threads. Vejamos a situação representada na Listagem 1.8, que mostra o fio de execução de dois threads que se encontram a trabalhar em paralelo sobre o mesmo objecto (rs é uma referência para uma instância de um ResultSet). Listagem 1.8: Dois threads a operar em simultâneo sobre o mesmo result set. 1 3 // Thread A rs . absolute (4); int id = rs . getInt (1); // Thread B rs . next (); int id = rs . getInt (1); String name = rs . getString (2); Como não há certezas em relação à ordem em que as operações serão executadas, os resultados são imprevisı́veis. Por exemplo, a linha 2 do Thread A pode ser executada, e antes que a sua linha 3 seja executada, a linha 2 do Thread B é executada, e a seguir o Thread A lê o valor da linha a seguir à pretendida. Neste exemplo existe também o problema que quando a linha 2 do Thread B foi executada pretendia-se mover para a próxima linha (a linha anterior à chamada absolute(4)), e afinal moveu-se para a linha 5. Vejamos só mais um problema, representado na Listagem 1.9. Listagem 1.9: Dois threads a operar em simultâneo sobre o mesmo result set. 2 4 // Thread A rs . updateInt (1 , ival ); rs . updateString (2 , sval ); rs . updateRow (); // Thread B rs . relative (5); O Thread A está a tentar actualizar os valores de uma linha. O Thread B quer mover o result set para uma linha que se encontra 5 linhas à frente da linha actual. O código do Thread B pode executar antes que a chamada updateRow() seja realizada, tendo como consequência a perda dos novos valores do Thread A. Estas são apenas duas situações problemáticas de várias que podem surgir quando se partilha o mesmo objecto de um ResultSet entre threads. 10 Como se resolvem estes problemas? Bem, uma solução poderia passar por criar um result set para cada thread. Mas isso implica a criação e execução de uma statement para cada thread. É facilmente perceptı́vel que esta situação cria desperdı́cio de recursos. Outras soluções envolvem a criação de mecanismos especiais ao nı́vel da aplicação, de modo a providenciar um ambiente realmente concorrente, ou então ser o próprio driver fornecer tais mecanismos. Estas soluções serão discutidas no próximo capı́tulo. 11 12 Capı́tulo 2 Implementação de um driver JDBC Este capı́tulo apresenta as linhas gerais da implementação do driver JDBC do tipo 4 para o Database Management System Microsoft SQL Server 2008 [9]. A informação aqui contida refere-se particularmente ao driver desenvolvido neste trabalho. Apesar do JDBC ser uma Client Level Interface, e dar por isso a possibilidade de se interacção directa com o database engine, este trabalho não contempla optimizações que têm como origem a utilização de funcionalidades próprias do DBMS utilizado. Pretende-se antes encontrar um modelo de optimização no que se refere ao acesso concorrente de objectos JDBC partilhados. Neste caso em particular, o objectivo centra-se em partilhar um objecto ResultSet. Encontrando um modelo de acesso que prova ser eficiente, independentemente do database engine sobre o qual se está a trabalhar, criam-se condições para que esse modelo seja mais largamente aceite, e possa ser utilizado na implementação de drivers JDBC para os diversos sistemas de base de dados existentes. O desenho e implementação deste driver é focalizado na componente cliente que o constitui, deixando a componente servidor o mais abstracta possı́vel. A única componente especı́fica referente à componente servidor, consiste na interface de comunicação com este. Mais propriamente utiliza-se o protocolo de comunicação com o SQL Server (protocolo Tabular Data Stream (TDS)), e utiliza-se a interface programática existente para efectuar diversas operações com um cursor do servidor, tais como a sua declaração ou a obtenção de uma linha especı́fica do result set (conjunto de linhas seleccionadas pela statement SQL). Esta especialização da componente servidor era inevitável para se conseguir criar um driver JDBC funcional, que possibilitasse testar o seu desempenho. O driver implementa duas camadas: JDBC e TDS. A camada JDBC refere-se ao conjunto de classes que implementam as interfaces definidas na API JDBC [46]. A camada TDS referese ao conjunto de classes e interfaces que implementam o protocolo de comunicação com SQL Server, o Tabular Data Stream [11, 63]. A arquitectura geral de um driver JDBC do tipo 4 para SQL Server 2008 é apresentada na Figura 2.1. O driver JDBC converte os pedidos de acesso à API JDBC em invocações à camada TDS, que conhece o modo como os pedidos devem ser realizados ao SQL Server e 13 converte esses pedidos em mensagens do protocolo TDS. Figura 2.1: Arquitectura de um driver JDBC do tipo 4 para SQL Server 2008. 2.1 Camada TDS A camada TDS do driver JDBC constitui a implementação do protocolo Tabular Data Stream, que consiste no protocolo de transporte de informação entre uma aplicação cliente e o SQL Server. Esta secção apenas descreve a interface que esta camada disponibiliza. No apêndice B é fornecida uma resumida descrição do protocolo. Informação mais detalhada sobre o protocolo pode ser obtida nas páginas MSDN dedicadas ao TDS [11] ou na especificação da Sybase [63]. A camada TDS fornece duas entidades que permitem à camada superior interagir com o SQL Server sem conhecer as especificidades do protocolo: TDSMessage e ITDSResultSet. 2.1.1 TDSMessage A classe TDSMessage implementa toda a comunicação com o servidor. A comunicação é realizada utilizando um socket TCP. Os serviços disponibilizados pela classe TDSMessage consistem em invocações de serviços do SQL Server. A classe converte os pedidos da camada superior na linguagem do SQL Server. A Figura 2.2 apresenta os serviços disponibilizados pela classe. 14 Figura 2.2: Classe TDSMessage. A classe UpdateValue que é utilizada nos métodos cursorUpdate, cursorUpdateAbsolute, cursorDelete, cursorDeleteAbsolute e cursorInsert, tem como objectivo converter os valores das colunas de uma linha no formato do protocolo. Converte o valor de um tipo de dados Java num conjunto de bytes formatados de acordo com o TDS, o resultado é depois transferido para o SQL Server na mensagem (actualização, remoção ou inserção). A Tabela 2.1 apresenta uma sucinta descrição dos serviços da classe TDSMessage. Tabela 2.1: Descrição dos serviços da classe TDSMessage Nome close login executeSQLBatch cursorClose cursorOpen cursorUpdate cursorUpdateAbsolute cursorDelete cursorDeleteAbsolute cursorInsert cursorFetch cursorRefresh cursorRows Descrição Fecha os streams de comunicação com o servidor. Envia um pedido de login ao servidor. Envia um pedido de execução de um batch de comandos SQL ao servidor. Envia um pedido ao servidor para fechar e desalocar um cursor. Envia um pedido ao servidor para alocar e abrir um cursor. Envia um pedido ao servidor de actualização de valores das colunas de uma linha, considerando uma posição relativa. Envia um pedido ao servidor de actualização de valores das colunas de uma linha, considerando uma posição absoluta. Envia um pedido ao servidor de remoção de uma linha, considerando uma posição relativa. Envia um pedido ao servidor de remoção de uma linha, considerando uma posição absoluta. Envia um pedido ao servidor de inserção de uma linha. Envia um pedido ao servidor de carregamento de linhas do result set. Envia um pedido ao servidor de recarregamento de linhas do result set. Envia um pedido ao servidor de informação relativamente ao número total de linhas do result set do cursor. 15 2.1.2 ITDSResultSet Quando o servidor envia ao cliente um result set1 , este vem formatado de acordo com o protocolo. A interface ITDSResultSet fornece serviços para extrair a informação do result set, garantindo acesso aos dados sem a necessidade do conhecimento dos detalhes. A Figura 2.3 apresenta os serviços fornecidos pela interface ITDSResultSet. Figura 2.3: Interface ITDSResultSet. A descrição dos serviços da interface ITDSResultSet é apresentada na Tabela 2.2. Tabela 2.2: Descrição dos serviços da interface ITDSResultSet Nome getCursor columnCount rowCount isEmpty getXXX wasNull insertRow updateRow getColumnName getUpdateRowCount 1 Descrição Obtém o identificador do cursor do servidor. Obtém a quantidade de colunas que cada linha do result set possui. Obtém a quantidade de linhas que foram enviadas para o cliente. Verifica se a quantidade de linhas é zero. Obtém o valor de uma coluna, dado o seu ı́ndice. Existe um método get para cada um dos tipos de dados Java suportados: int, double, String e Date. Verifica se o valor da coluna lido é SQL NULL. Insere uma linha na cópia do result set que existe no cliente. Actualiza uma linha da cópia do result set que existe no cliente. Obtém o nome de uma coluna, dado o seu ı́ndice. Obtém o número de linhas que foram alteradas na base de dados como resultado da execução de um comando DML. Resultado da execução de uma statement SQL 16 2.2 Camada JDBC Esta camada é constituı́da por um conjunto de classes que implementam as interfaces definidas na API JDBC. Neste trabalho procedeu-se apenas a uma implementação parcelar da API. Um dos objectivos principais para o driver JDBC era suportar a execução de statements SQL e obter acesso a um result set. A Tabela 2.3 lista as interfaces que foram implementadas e identifica as classes que as implementam. As interfaces implementadas pertencem todas ao pacote java.sql. No capı́tulo 3 é apresentada uma explicação mais detalhada da implementação da interface ResultSet. Tabela 2.3: Interfaces da API JDBC implementadas. Interface Driver Classe CDriver Statement CStatement ResultSet CResultSet Descrição Esta interface tem que ser obrigatoriamente implementada por um driver. A classe java.sql.DriverManager encarrega-se de carregar os drivers, que mais tarde serão usados para criar uma ligação à base de dados. Esta interface permite executar statements SQL e obter o respectivo result set. Esta interface permite obter acesso ao resultado da execução de uma statement SQL. Existem serviços para leitura e modificação do result set. 17 18 Capı́tulo 3 Arquitectura do ResultSet Concorrente 3.1 Introdução O ResultSet é uma interface que fornece uma abstracção ao paradigma Orientado a Objectos para um conceito do paradigma Relacional: o cursor. O comportamento e terminologia envolvidos na criação do objecto result set, a sua manipulação, até à sua remoção são os mesmos que se encontram associados a um cursor do servidor (também conhecido como cursor de base de dados). A implementação da interface ResultSet cria um cursor no SQL Server1 , e na sua operação interage com o cursor do servidor de modo a obter ou actualizar os dados do dataset 2 associado ao cursor. Este dataset corresponde ao conjunto de linhas que satisfazem a condição da statement SQL executada usando o método executeQuery da interface Statement. A relação entre estas entidades encontra-se representada na Figura 3.1. Figura 3.1: Representação da relação entre o ResultSet, o cursor servidor e o dataset 1 Normalmente é feita uma para excepção para o ResultSet do tipo FORWARD ONLY/READ ONLY em que o ResultSet é obtido através da execução de um batch. 2 Os termos dataset e result set são equivalentes quando se referem aos dados seleccionados por uma statement SQL. O termo result set também é usado no documento para se referir a uma instância da interface ResultSet. 19 Devido a esta relação ResultSet/cursor podemos dizer que criar uma instância do ResultSet corresponde a criar um cursor cliente. 3.1.1 Problema A relação de 1 para 1 entre ResultSet e cursor, implica que para cada ResultSet criado num programa Java, irá ser alocado e aberto um novo cursor no servidor. Como a API JDBC não define um mecanismo que permita tirar partido de um ambiente multihreaded, para se poder trabalhar concorrentemente sobre um dataset é necessário criar um ResultSet/cursor para cada entidade concorrente, o que desde logo implica uma maior alocação de recursos. A maior alocação de recursos traduz-se em desperdı́cio, porque está claro que vai haver replicação várias vezes da mesma informação. E este é um problema que se verifica tanto no lado do cliente como do servidor. Do lado do cliente existe a instanciação de mais objectos. Do lado do servidor existem mais cursores alocados e abertos. 3.1.2 Ideia base da solução As boas práticas do acesso a informação em base de dados ensinam que se deve evitar a utilização de cursores pois normalmente eles utilizam muitos recursos e reduzem o desempenho e a escalabilidade das aplicações [71], e que se devem utilizar alternativas, como por exemplo: • Ciclos while • Tabelas temporárias • Tabelas derivadas • Múltiplas queries Uma vez que o modo de operação de um ResultSet é linha a linha, temos de continuar a utilizar cursores do servidor. Mas o que se pode fazer é diminuir o número de cursores utilizados. Por isso a ideia base consiste em transformar a relação de 1 para 1 numa relação de muitos para um, isto é, permitir que várias instâncias do ResultSet utilizem o mesmo cursor do servidor. Esta alteração é assinalada na Figura 3.2. Figura 3.2: Relação de muitos para um entre o ResultSet e o cursor do servidor. 20 A implementação encontra-se, deste modo, preparada para operar num ambiente concorrente, garantindo uma cooperação correcta entre as entidades concorrentes. A cada entidade é atribuı́da uma referência para uma instância do ResultSet que por sua vez opera concorrentemente com as outras instâncias sobre o mesmo cursor, permitindo o desejado acesso concorrente ao dataset. Agora que já se sabe qual é a ideia base da solução, nas duas próximas secções serão descritas duas possı́veis soluções concretas; uma implementando a concorrência ao alto nı́vel e outra ao baixo nı́vel. 3.2 ResultSet Wrapper Esta primeira solução é independente da implementação do driver, isto é, funciona para qualquer driver que existe. A ideia base consiste em criar um mecanismo que controle os acessos concorrentes a um objecto result set partilhado, e providencie uma implementação dos serviços garantindo o acesso correcto ao result set, resolvendo assim os problemas identificados anteriormente (secção 1.5). O acesso correcto ao result set implica que para cada entidade que lhe acede seja guardado o número da linha do result set em que ele se encontra a trabalhar, e quando voltar a ser a entidade activa o result set volta a encontrar-se nessa linha. Para tal é necessário que a linha do result set em que cada entidade se encontra a trabalhar seja guardada quando existe troca de entidade activa, e seja restaurada a linha quando voltar a ser a entidade activa. A operação que guarda o número da linha do result set denomina-se por salvaguarda de contexto e a operação que move o result set para a linha guardada denomina-se por restauração de contexto. A Figura 3.3 apresenta o diagrama de classes da solução ResultSet Wrapper. Figura 3.3: Diagrama de classes da solução ResultSet Wrapper. A solução consiste em criar cursores cliente que encapsulam (wrap) e partilham a mesma instância de um ResultSet. A cada thread que pretende trabalhar sobre o result set é lhe atribuı́do um cursor cliente. O processo de salvaguarda/restauro de contexto é realizado pelo 21 cursor cliente, e é totalmente transparente para o utilizador. Isto significa que o modo de utilização desta solução é rigorosamente igual à utilização da interface ResultSet. Existem quatro tipos de cursores: CursorForwardReadOnly Wrapper para um result set FORWARD ONLY/READ ONLY. CursorForwardUpdate Wrapper para um result set FORWARD ONLY/UPDATABLE. CursorScrollReadOnly Wrapper para um result set SCROLLABLE SENSITIVE/READ ONLY. CursorScrollUpdate Wrapper para um result set SCROLLABLE SENSITIVE/UPDATABLE. Cada tipo de cursor implementa os métodos da interface ResultSet que fazem sentido, lançando uma excepção dizendo que a operação não é suportada no caso contrário. Por exemplo, o CursorScrollReadOnly implementa os métodos relacionados com o movimento do cursor, e lança excepção se se tentar invocar um método de update, enquanto que o CursorForwardUpdate suporta os métodos de update e lança uma excepção se se tentar mover o cursor para uma linha especı́fica (método absolute). A gestão dos contextos dos cursores cliente é realizada pelos próprios cursores. Cada um tem a noção da linha do result set onde se encontra acedendo à variável currentRow da própria classe. Assim, quando é o cursor cliente activo, se for necessário recupera o seu contexto, movendo o result set para a linha onde se encontrava a trabalhar. A detecção da mudança do cursor activo é realizada com o auxı́lio da variável currentCursor que tem visibilidade ao nı́vel da classe AbstractCursor e que guarda o identificador do cursor activo. Quando um cursor entra em execução, o primeiro procedimento é verificar se o valor de currentCursor é igual ao seu identificador (id), se for igual então não é necessário mover o result set, porque ele foi o último cursor a trabalhar com o result set, caso contrário invoca o método do result set que o posiciona na linha onde se encontrava a trabalhar anteriormente. O trabalho sobre o result set tem que ser realizado num regime de acesso exclusivo, para o obter esse acesso exclusivo (lock) é utilizada a variável lock que é uma instância da classe ReentrantLock. Esta classe providencia o mesmo comportamento e semântica que o monitor explı́cito acedido usando métodos e blocos synchronized, mas com funcionalidades extra [50]. 3.2.1 Utilização A criação de um result set wrapper é realizada por intermédio da interface ICursorFactory. Esta interface fornece um serviço para construir cada um dos tipos de cursor (CursorForwardReadOnly, CursorForwardUpdate, CursorScrollReadOnly e CursorScrollUpdate). A interface da fábrica de cursores é implementada pela classe CursorFactoryImpl, que recebe 22 no seu construtor o objecto result set que será mais tarde partilhado pelos cursores. No acto de construção de um cursor é verificado se o result set encapsulado é compatı́vel com o tipo de cursor que se está a pedir, lançando-se uma excepção numa situação de incompatibilidade. Por exemplo, se o método createForwardRead é invocado e o result set é do tipo SCROLLABLE SENSITIVE/READ ONLY uma excepção a explicar a situação é criada e lançada. A Figura 3.4 apresenta o diagrama de classes com a fábrica de cursores. Figura 3.4: Diagrama das classes envolvidas na utilização das solução Cursor Wrapper. Para utilizar esta solução primeiro cria-se um objecto ResultSet, como se criaria habitualmente utilizando o método executeQuery da interface Statement. Em seguida cria-se uma instância da fábrica de cursores passando como argumento o result set ao seu construtor. O último passo consiste em utilizar um dos serviços disponibilizados pela interface da fábrica para criar o wrapper do result set desejado. A Listagem 3.1 exemplifica a utilização da solução ResultSet Wrapper para um result set do tipo TYPE FORWARD ONLY/CONCUR UPDATABLE. Listagem 3.1: Criação de um ResultSet Wrapper. 1 3 5 // Criação do result set. ResultSet rs = stmt . executeQuery ( sql ); // Criação da fábrica de cursores. ICursorFactory factory = new CursorFactoryImpl ( rs ); // Criação do wrapper (cursor). ResultSet cursor = factory . createForwardUpdate (); O objecto stmt é uma instância de Statement e foi criado com o tipo adequado ao tipo de wrapper criado na listagem. O argumento sql é uma String que contém a statement SQL que dá origem ao result set. 3.3 Cursor Concorrente O ResultSet Wrapper parece ser uma solução válida, no entanto, ao colocar o controlo de acesso à região crı́tica num nı́vel tão alto, diminui-se a concorrência porque existe mais código que poderia ser executado em paralelo e que passa a ser executado sequencialmente. Como consequência perde-se a oportunidade de explorar sistemas com multiprocessador e diminui-se também o throughput[61, 73]. 23 Usando um driver JDBC existente implica que o melhor que se consegue é mesmo ficar nesse nı́vel alto de concorrência. Mas implementando um driver próprio tem-se acesso ao funcionamento interno, e por isso consegue-se analisar e descobrir os pontos que são totalmente concorrentes (isto é, que podem ser executados em paralelo), e também descobrir os pontos em que se acede a recursos partilhados e tem que haver por isso acesso sequencial e exclusivo. Nesta secção é apresentada uma solução que começou pela implementação de um driver JDBC do tipo 4 (Native-Protocol Driver). A seguir à implementação do driver procedeu-se à inclusão do mecanismo apresentado na secção 3.1.2 na implementação do driver. 3.3.1 Anatomia de um ResultSet Nesta secção pretende-se, sem entrar em grandes detalhes, dar a entender a estrutura geral de uma implementação do ResultSet, fazendo assim a cobertura de alguns conceitos e terminologias que ajudam a perceber as subsequentes secções. A classe CResultSet implementa a interface ResultSet, comunicando com camada que implementa o protocolo Tabular Data Stream (TDS) para executar as operações sobre o result set. A Figura 3.5 apresenta o diagrama de classes simplificado da implementação do ResultSet. Figura 3.5: Visão geral da implementação do ResultSet A Tabela 3.1 possui uma descrição dos atributos da classe CResultSet. Tabela 3.1: Descrição dos atributos da implementação do ResultSet. Atributo Descrição cursor Identificador do cursor do servidor. É obtido na resposta do servidor ao pedido de criação do cursor (ver secção C.2). currentRow Número da linha actual do cursor cliente. rsIndex Índice para aceder ao cache. tdsResultSet Cache do result set. Apesar de o ResultSet operar linha a linha interagindo com o cursor do servidor, cada instância de uma implementação do ResultSet possui em memória um conjunto de linhas, isto é, possui um cache, de modo a diminuir o número de mensagens trocadas entre o cliente e o servidor. Tal como na arquitectura de computadores em que os processadores usam o 24 princı́pio da localidade de referência[76, 72] e transferem para a memória de mais alto nı́vel não só os dados pedidos, mas um bloco de dados adjacentes; no acesso a uma linha de um result set existe uma grande probabilidade de uma linha próxima ser também utilizada (pelo princı́pio), é por isso uma boa estratégia, sempre que existe o pedido de uma linha que não se encontra na cache, transferir um bloco de linhas adjacentes. A classe CResultSet possui um objecto que implementa a interface ITDSResultSet. Esse objecto corresponde ao cache referido anteriormente e possui o result set formatado de acordo com o protocolo TDS. O cache essencialmente é constituı́do por uma lista com as linhas do result set e possui ainda a descrição das colunas que formam uma linha, nessa descrição está presente, por exemplo, o tipo de dados da coluna e o respectivo nome. O cache é indexado usando a variável rsIndex, cuja gama de valores vai desde zero até ao tamanho da cache. O tamanho da cache é definido pelo método setFetchSize da interface ResultSet. A variável rsIndex para além de ser utilizada para ler o conteúdo de uma linha do result set, pode também ser utilizada nas operações de actualização e remoção (se o tipo de result set as suportar). O cursor servidor actualiza o result set utilizando um ı́ndice relativo ao seu buffer (ver apêndice C, particularmente a secção C.1), esse ı́ndice corresponde exactamente ao valor de rsIndex. A operação de inserção ignora este valor, porque as inserções são realizadas após a última linha do result set. O número da linha actual do result set é guardado pela variável currentRow, cujo valor varia na gama 1 até ao número total de linhas seleccionadas pela execução da statement SQL. O seu valor pode ser obtido a partir do método getRow da interface ResultSet, e tem particular importância para o funcionamento interno de CResultSet na verificação da necessidade de efectuar um fetch de um novo conjunto de linhas. Se o valor de currentRow não estiver contido na gama de linhas que o cache possui, então é preciso pedir novas linhas ao cursor do servidor. 3.3.2 Cache individual Uma vez que diminuindo a disputa pelo acesso a uma memória partilhada melhora-se a eficiência de uma aplicação [61], a ideia desta solução é tentar diminuir ao máximo os pontos em que é necessário proceder ao lock para a obter acesso exclusivo. Daı́ surgiu a implementação de um ResultSet que para cada instância criasse uma cópia (normalmente parcial) do result set. O que isto significa é que cada cursor cliente possui cache próprio. O cache individual possui as seguintes vantagens: 1. O acesso ao cache, tanto para leitura como para escrita, é realizado sem restrições, isto é, não há a necessidade obter lock sobre o objecto do cache. 2. Numa situação de cópia parcial do result set diminui-se o número de vezes que se interage com o servidor. O segundo ponto carece de uma explicação. Imaginemos um cache com capacidade de uma linha, partilhado por alguns threads. Um thread T1 está a trabalhar na linha 5 do 25 result set, entretanto T1 deixa de ser o thread em execução, que passa a ser o thread T2 cuja linha do result set é a 10 e que agora terá de ser pedida ao servidor. A troca de thread em execução provoca uma grande quantidade de mensagens trocadas entre o cliente e o servidor, pois cada thread precisa de pedir as linhas em que está a trabalhar. E apesar de se ter utilizado um exemplo simples em que o cache tem capacidade de apenas uma linha, caches com capacidades maiores sofrem ainda mais de perda de desempenho (a demonstração e explicação deste fenómeno encontram-se nas secções 5.4 e 6.1.4, respectivamente). Com um cache individual o carregamento do conteúdo do cache é realizado consoante as necessidades individuais do cursor cliente, pelo que se resolve o problema do maior volume de mensagens trocadas entre o cliente e o servidor, porque o cursor cliente tem em cache as linhas em que estava a trabalhar. O cache individual possui as seguintes desvantagens: 1. Replicação da informação; podem existir caches cujo conteúdo seja o mesmo. 2. A actualização de um dos caches não reflete as alterações nos restantes caches, sendo necessário os cursores pedirem ao servidor os novos dados. A Figura 3.6 apresenta o diagrama de classes da solução com cache individual. Note-se a relação de 1 para 1 entre CursorIndividualCache e ITDSResultSet, que determina o cache como único para cada cursor cliente. Figura 3.6: Diagrama de classes do cursor com cache individual. As classes CursorForwardOnlyReadOnly, CursorForwardOnlyUpdatable, CursorScrollableReadOnly e CursorScrollableUpdatable implementam um result set do tipo FORWARDONLY/READ ONLY, FORWARD ONLY/UPDATABLE, SCROLLABLE SENSITIVE /READ ONLY e SCROLLABLE UPDATABLE, respectivamente. Estas classes reimplementam os métodos que o respectivo result set não suporta, indicando um erro se esses métodos forem invocados. A classe CursorIndividualCache tem como função adequar a implementação do ResultSet realizada pelo CResultSet à situação de ter um cache individual. A Tabela 3.2 descreve os métodos que têm de ser reimplementados (override) pela classe CursorIndividualCache. 26 Tabela 3.2: Métodos reimplementados pela classe CursorIndividualCache. Método next update delete 3.3.3 Explicação Cada cursor cliente pode mover o cursor do servidor, assim quando um cursor cliente pede ao servidor que mova o seu cursor para o próxima linha ou próximo buffer, a posição inicial pode não ser a correcta para o cursor cliente que faz o pedido. Por isso, o método next é reimplementado para executar um FETCH ABSOLUTE em vez de um FETCH NEXT (a secção A.1.1 apresenta uma explicação destes termos). Aqui também existe o problema do cursor do servidor poder estar noutra linha, por isso o RPC sp cursor tem que ser invocado com um optype igual a ABSOLUTE|UPDATE (conjunção de duas opções usando o operador OR) em vez de UPDATE (ver secção C.1). Ao efectuar esta alteração do valor de optype o cursor do servidor passa a considerar o argumento rownum como sendo o número da linha contando desde o inı́cio do result set em vez de contar desde o inı́cio do buffer. A situação é semelhante ao update, mas o valor DELETE do argumento optype é substituı́do por ABSOLUTE|DELETE. Cache partilhado Como foi visto na secção anterior o cursor com cache individual será vantajoso numa situação em os threads trabalham sobre gamas de linhas dı́spares e distantes, no entanto provoca a replicação de informação transmitida e guardada no cliente. Esta secção apresenta a implementação do cursor cliente, em que as várias instâncias do cursor partilham o mesmo cache. O cache partilhado tem as seguintes vantagens: 1. Só existe uma cópia dos dados do result set. 2. A actualização do cache realizada por um cursor cliente é visı́vel pelos restantes cursores cliente. O cache partilhado tem as seguintes desvantagens: 1. Todo o acesso ao cache, seja para leitura ou escrita, tem que ser precedido de um lock no objecto que representa o cache. 2. O número de mensagens trocadas com o servidor pode aumentar, diminuindo o desempenho da aplicação, principalmente numa situação em que os cursores cliente trabalhem em gamas de linhas distantes entre si. Se o cache for partilhado nas operações de leitura (getInt, getDate, ...) é necessário recuperar o contexto, isto é, verificar se a linha para a qual se moveu o cursor cliente ainda está presente no cache, e efectuar um fetch caso não esteja em cache. Num ambiente multithreaded é muito frequente outro thread entrar em execução e alterar as linhas que estão presentes no cache. Isto provoca uma maior troca de mensagens entre o cliente e o servidor porque é necessário alterar várias vezes o conteúdo do cache. Tentando resolver este problema, na 27 implementação do cursor com cache partilhado são seleccionadas e guardadas em cache todas as linhas do dataset. Isto transforma o cache hit ratio em 100%. A Figura 3.7 apresenta o diagrama de classes da solução com cache partilhado. Podemos ver que a relação entre os cursores cliente e o cache é de muitos para 1; a mesma memória cache (ITDSResultSet) é utilizada por vários cursores cliente. Figura 3.7: Diagrama de classes da implementação do cursor com cache partilhado. Os atributos e os métodos de CursorSharedCache não são apresentados, porque essencialmente o que a classe faz é reimplementar (override) os métodos da interface ResultSet, adequando-os ao facto de agora estar a operar com um cache partilhado. A Tabela 3.3 apresenta os métodos que têm de ser reimplementados pela classe CursorSharedCache. Tabela 3.3: Métodos reimplementados pela classe CursorSharedCache Método afterLast beforeFirst getInt getString getDouble getDate updateRow insertRow scroll Explicação A implementação normal deste método invoca o RPC sp cursorfetch com o optype FETCH AFTER (A.1.1), o que faz com que o cursor do servidor seja movido para uma posição a seguir à última linha e o buffer fique vazio. Com cache partilhado não há a necessidade de mover o cursor do servidor, apenas o cursor cliente. Também não é desejável que o cache seja esvaziado. A situação é semelhante ao afterLast, mas o optype é FETCH BEFORE (A.1.1), e o cursor do servidor é movido para uma posição anterior à primeira linha do result set. O acesso ao cache para leitura só pode ocorrer depois da obtenção do lock. O acesso ao cache para leitura só pode ocorrer depois da obtenção do lock. O acesso ao cache para leitura só pode ocorrer depois da obtenção do lock. O acesso ao cache para leitura só pode ocorrer depois da obtenção do lock. O acesso ao cache para actualizar uma linha só pode ocorrer depois da obtenção do lock. O acesso ao cache para inserir uma linha só pode ocorrer depois da obtenção do lock. O método scroll serve de boilerplate para os métodos de navegação no result set. Como no cache partilhado todas as linhas do result set já se encontram no cache, não há a necessidade de verificar se uma linha se encontra em cache e em caso negativo pedi-la ao servidor. 28 A partilha do cache implica a utilização de um mecanismo de sincronização. O cursor cliente com cache partilhado não só tem de garantir acesso exclusivo ao canal de comunicação com o servidor, como tem também de garantir o acesso exclusivo ao cache. Este aspecto parece constituir uma desvantagem em relação à implementação com cache individual, sendo um potencial factor de perda desempenho. No entanto, com o cache partilhado não existe o overhead da construção de vários caches. Esta discussão ficará para a secção de análise dos resultados (secção 6.1) apresentados no capı́tulo 5. As classes CursorForwardOnlyReadOnly, CursorForwardOnlyUpdatable, CursorScrollableReadOnly e CursorScrollableUpdatable implementam um result set do tipo FORWARDONLY/READ ONLY, FORWARD ONLY/UPDATABLE, SCROLLABLE SENSITIVE /READ ONLY e SCROLLABLE UPDATABLE, respectivamente. Estas classes reimplementam os métodos que o respectivo result set não suporta, indicando um erro se esses métodos forem invocados. 3.3.4 Utilização O driver implementado é utilizado tal como outro driver JDBC. A versão 4.0 da API JDBC introduziu a funcionalidade de carregamento de um java.sql.Driver [46], através da utilização do mecanismo Java SE Service Provider. Isto significa que já não é necessário invocar Class.forName. O driver suporta esta funcionalidade, por isso para obter uma ligação à base de dados basta utilizar o código apresentado na Listagem 3.2. O jar do driver tem que se encontrar no classpath. Listagem 3.2: Obtenção de uma ligação. 2 String url = ... Connection con = DriverManager . getConnection ( url ); Em que url é uma String no formato descrito na secção 1.4.2. O que é novo é a possibilidade de criar vários objectos result set (cursores cliente) que utilizam o mesmo cursor do servidor, operando assim sobre o mesmo dataset. Para tal é necessário obter uma instância da interface ICStatement. Esta interface fornece o serviço getCursor() que devolve um cursor cliente. A interface ICStatement estende a interface Statement. A classe que as implementa devolve um cursor com cache partilhado (3.3.3) quando o valor de fetch size é igual a zero, e devolve um cursor com cache individual (3.3.2) quando o valor de fetch size é superior a zero (um valor inferior a zero é inválido). Uma vez que por pré-definição o valor de fetch size é superior a zero, se o método setFetchSize da interface ResultSet não for explicitamente invocado então será criado um cursor com cache individual. Será então boa prática utilizar o método setFetchSize explicitamente, para evitar confusões. 29 Figura 3.8: Classes principais na utilização da solução Cursor. As listagens 3.3 e 3.4 exemplificam a criação de um cursor cliente com cache partilhado e de um cursor cliente com cache individual, respectivamente. Listagem 3.3: Criação de um cursor cliente com cache partilhado. 4 stmt = con . createStatement (); // Com fetchSize igual a zero todas as linhas do result set irão ser colocadas em cache. stmt . setFetchSize (0); stmt . executeQuery ( sqlQuery ); 6 ResultSet cursor = (( ICStatement ) stmt ). getCursor (); 2 Listagem 3.4: Criação de um cursor cliente com cache individual. 1 3 stmt = con . createStatement (); // Cache individual com capacidade igual a 25 linhas. stmt . setFetchSize (25); stmt . executeQuery ( sqlQuery ); 5 ResultSet cursor = (( ICStatement ) stmt ). getCursor (); Com a excepção da última linha de cada listagem, a criação é igual para ambos e deve ser bastante familiar e intuitiva para quem já utiliza a API JDBC. A partir da criação, cada cursor cliente pode ser utilizado como um ResultSet, tal como está definido na documentação da API [47]. 30 Capı́tulo 4 Benchmark Uma vez implementadas as soluções, é necessário realizar uma avaliação do seu desempenho. Para tal foi criado um Domain-Specific Benchmark [59], que para ser útil tem que ser: Relevante: Tem que medir o desempenho máximo que um sistema apresenta durante uma operação tı́pica. Portável: Deve ser fácil de realizar em diferentes sistemas e arquitecturas. Escalável: Deve ser aplicável a pequenos e a grandes sistemas. Simples: Tem que ser facilmente compreensı́vel. Foi com estas caracterı́sticas em mente que o benchmark foi construı́do. 4.1 Benchmark principal O benchmark consiste em criar um determinado número de threads e medir o tempo total que eles levam a executar a sua tarefa. A tarefa é atribuı́da no momento da criação dos threads e corresponde à execução de um contexto. Foram realizados testes nos contextos: leitura, actualização, inserção e remoção. O código executado para cada contexto encontra-se nas listagens 4.1 até 4.4. Estes contextos foram testados num cenário que tenta representar uma situação de utilização de um result set por vários threads. O cenário consiste em dividir logicamente um result set e atribuir uma gama de linhas a cada thread. Na sua execução, cada thread realiza a sua tarefa sobre as linhas lhe são destinadas. De notar que este é um cenário de vários possı́veis. Listagem 4.1: Contexto de leitura. 1 int firstLine = counter . getAndAdd ( linesPerThread ); int lastLine = firstLine + linesPerThread - 1; 3 31 5 7 9 11 13 15 rs . absolute ( firstLine - 1); for ( int i = firstLine ; i <= lastLine ; ++ i ) { rs . next (); val1 = rs . getInt (1); val2 = rs . getString (2); val3 = rs . getString (3); val4 = rs . getDate (4); val5 = rs . getDouble (5); val6 = rs . getInt (6); val7 = rs . getDate (7); val8 = rs . getString (8); } Listagem 4.2: Contexto de actualização. 2 initUpdateValues (); int firstLine = counter . getAndAdd ( linesPerThread ); int lastLine = firstLine + linesPerThread - 1; 4 6 8 10 12 14 16 rs . absolute ( firstLine - 1); for ( int i = firstLine ; i <= lastLine ; ++ i ) { rs . next (); rs . updateInt (1 , val1 ); rs . updateString (2 , val2 ); rs . updateString (3 , val3 ); rs . updateDate (4 , val4 ); rs . updateDouble (5 , val5 ); rs . updateInt (6 , val6 ); rs . updateDate (7 , val7 ); rs . updateString (8 , val8 ); rs . updateRow (); } Listagem 4.3: Contexto de inserção. 1 3 5 7 9 11 13 initUpdateValues (); for ( int i = 0; i < linesPerThread ; ++ i ) { rs . moveToInsertRow (); rs . updateInt (1 , val1 ); rs . updateString (2 , val2 ); rs . updateString (3 , val3 ); rs . updateDate (4 , val4 ); rs . updateDouble (5 , val5 ); rs . updateInt (6 , val6 ); rs . updateDate (7 , val7 ); rs . updateString (8 , val8 ); rs . insertRow (); rs . moveToCurrentRow (); } 32 Listagem 4.4: Contexto de remoção. 1 int firstLine = counter . getAndAdd ( linesPerThread ); int lastLine = firstLine + linesPerThread - 1; 3 5 7 rs . absolute ( firstLine - 1); for ( int i = firstLine ; i <= lastLine ; ++ i ) { rs . next (); rs . deleteRow (); } A variável inteira linesPerThread possui o número de linhas atribuı́das a cada thread, e o seu valor é atribuı́do directamente como parâmetro de teste. O número de linhas da tabela é igual ao número de linhas por thread multiplicado pelo número de threads. A variável counter é uma instância da classe AtomicInteger [49], e é-lhe atribuı́do o valor 1 no inı́cio de cada teste. A variável counter auxilia o processo de divisão do result set em gamas. Cada thread obtém o valor da variável, que corresponde ao número da primeira linha da gama, e calcula o valor do inı́cio da próxima gama. O valor do número da última linha da gama é calculado adicionando o número de linhas por thread ao valor da primeira linha da gama. Foram realizados benchmarks com o driver da Microsoft (disponı́vel em [10]), com a solução ResulSet Wrapper e a com solução Cursor. De modo a apresentar e discutir os resultados de modo mais sintético, utilizaram-se os seguintes nomes para identificar cada solução: • M SJDBC: Teste usando o driver da Microsoft; • CJDBCI : Teste usando o driver implementado, na versão com cache individual; • CJDBCS : Teste usando o driver implementado, na versão com cache partilhado; • W JDBC: Teste usando o driver da Microsoft, na versão Wrapper ; O benchmark do M SJDBC cria um result set para cada thread, porque o result set não pode ser correctamente partilhado pelas entidades concorrentes. Para o benchmark do W JDBC é criado um result set do driver da Microsoft que depois é partilhado entre os threads, e cada thread acede ao result set através de um wrapper. Para os benchmarks do CJDBCI e CJDBCS é criado um cursor cliente do result set para cada thread. O tipo de result set dos testes será sempre TYPE SCROLLABLE SENSITIVE. Uma vez que um cursor forward-only só permite percorrer o dataset numa direcção, não é possı́vel recuperar o contexto, nem existe a possibilidade de atribuir uma porção do dataset a cada thread, pois este não pode mover o result set para uma linha especı́fica (a operação absolute não está disponı́vel). A solução ResultSet Wrapper tem como pedra basilar o restauro do contexto, e como já foi dito não é possı́vel restaurar o contexto a não ser que o result set seja scrollable; por estes motivos não são realizados testes com o tipo de result set TYPE FORWARD ONLY. No contexto da Leitura o tipo de concorrência do result set é CONCUR READ ONLY e nos contextos da Actualização, Inserção e Remoção a concorrência é do tipo CONCUR UPDATABLE. 33 Antes da execução propriamente dita do benchmark é necessário proceder à preparação da base de dados, pois alguns testes de desempenho necessitam que existam dados na tabela de testes. Todas as statement SQL utilizadas nos testes de desempenho para criar os result set foram realizadas sobre a tabela representada na Figura 4.1. Figura 4.1: Tabela utilizada no benchmark. As listagens 4.5 e 4.6 apresentam os passos que são efectuados na preparação da base de dados dos testes de desempenho para os diferentes contextos. Os contextos de Leitura, Actualização e Remoção limpam os dados da tabela, e introduzem novos dados antes de executar o benchmark. O contexto de Inserção só limpa os dados da tabela antes de executar o benchmark, já que a própria execução consiste em introduzir dados na tabela. Listagem 4.5: Preparação para executar os contextos de Leitura, Actualização e Remoção. 1 3 Limpar a tabela de testes Introduzir novos valores na tabela de testes Executar o benchmark Listagem 4.6: Preparação para executar o contexto de Inserção. 1 Limpar a tabela de testes Executar o benchmark Na execução do benchmark são medidos dois tempos: tempo de preparação (fig. 4.2), que corresponde ao tempo necessário para executar as queries e criar os threads, e tempo de execução (fig. 4.3), que corresponde ao tempo total que os threads criados levam a terminar a sua tarefa. Figura 4.2: Medição do tempo de preparação 34 Figura 4.3: Medição do tempo de execução Na inicialização de variáveis são criados os result set apropriados a cada situação e definidos parâmetros tais como o número de linhas por thread. Para o M SJDBC neste passo não é criado algum result set, pois cada thread é que se encarrega de criar o seu próprio result set. Para o W JDBC este passo também incluir criar a fábrica de cursores. Na criação dos threads é atribuı́da uma tarefa (Runnable) a cada thread. A tarefa está relacionada com o contexto. Os threads no M SJDBC criam agora o seu result set e os threads das restantes soluções obtêm o cursor cliente para o result set criado no passo anterior. Os resultados deste benchmark são apresentados nas secções 5.2 e 5.2.2, e discutidos nas secções 6.1.1 e 6.1.2. Para além do benchmark descrito até aqui, foram ainda criados dois benchmarks adicionais que testam situações particulares, e que serão descritos nas secções seguintes. 4.2 Benchmark com atrasos A motivação para realizar este benchmark é que o cenário do benchmark principal pode ser considerado irrealista; normalmente existe algum processamento associado à leitura e escrita de linhas, e admite-se que a introdução desse processamento possa vir a alterar os resultados. Por isso resolveu-se introduzir a realização de uma tarefa entre operações de leitura/escrita, que provocará um atraso entre as invocações sucessivas de operações do ResultSet. A introdução do atraso é realizada em dois locais: entre colunas e entre linhas; ver-se-á assim se algum dos dois tem algum efeito nos resultados finais do benchmark. O resultados para os dois locais foram obtidos em dois benchmarks separados. Neste benchmark apenas o contexto de leitura e de actualização foram testados. O contexto de inserção do ponto de vista operacional é semelhante ao contexto de actualização, pelo que os seus resultados seriam redundantes. O contexto de remoção não realiza processamento dos valores das colunas, e é por isso excluı́do. As listagens 4.7, 4.8, 4.9 e 4.10 apresentam o código executado por cada thread para os diferentes contextos. Listagem 4.7: Contexto de leitura com atraso entre colunas. 2 4 6 int firstLine = counter . getAndAdd ( linesPerThread ); int lastLine = firstLine + linesPerThread - 1; rs . absolute ( firstLine - 1); for ( int i = firstLine ; i <= lastLine ; ++ i ) { rs . next (); val1 = rs . getInt (1); 35 ResultSetRunnable . delayfunc (); val2 = rs . getString (2); ResultSetRunnable . delayfunc (); val3 = rs . getString (3); ResultSetRunnable . delayfunc (); val4 = rs . getDate (4); ResultSetRunnable . delayfunc (); val5 = rs . getDouble (5); ResultSetRunnable . delayfunc (); val6 = rs . getInt (6); ResultSetRunnable . delayfunc (); val7 = rs . getDate (7); ResultSetRunnable . delayfunc (); val8 = rs . getString (8); ResultSetRunnable . delayfunc (); 8 10 12 14 16 18 20 22 } Listagem 4.8: Contexto de leitura com atraso entre linhas. 1 3 5 7 9 11 13 int firstLine = counter . getAndAdd ( linesPerThread ); int lastLine = firstLine + linesPerThread - 1; rs . absolute ( firstLine - 1); for ( int i = firstLine ; i <= lastLine ; ++ i ) { rs . next (); val1 = rs . getInt (1); val2 = rs . getString (2); val3 = rs . getString (3); val4 = rs . getDate (4); val5 = rs . getDouble (5); val6 = rs . getInt (6); val7 = rs . getDate (7); val8 = rs . getString (8); ResultSetRunnable . delayfunc (); 15 } Listagem 4.9: Contexto de actualização com atraso entre colunas. 1 3 5 7 9 11 13 initUpdateValues (); int firstLine = counter . getAndAdd ( linesPerThread ); int lastLine = firstLine + linesPerThread - 1; rs . absolute ( firstLine - 1); for ( int i = firstLine ; i <= lastLine ; ++ i ) { rs . next (); ResultSetRunnable . delayfunc (); rs . updateString (2 , val2 ); ResultSetRunnable . delayfunc (); rs . updateString (3 , val3 ); ResultSetRunnable . delayfunc (); rs . updateDate (4 , val4 ); ResultSetRunnable . delayfunc (); 36 rs . updateDouble (5 , val5 ); ResultSetRunnable . delayfunc (); rs . updateInt (6 , val6 ); ResultSetRunnable . delayfunc (); rs . updateDate (7 , val7 ); ResultSetRunnable . delayfunc (); rs . updateString (8 , val8 ); ResultSetRunnable . delayfunc (); rs . updateRow (); 15 17 19 21 23 } Listagem 4.10: Contexto de actualização com a atraso entre linhas. 2 4 6 initUpdateValues (); int firstLine = counter . getAndAdd ( linesPerThread ); int lastLine = firstLine + linesPerThread - 1; rs . absolute ( firstLine - 1); for ( int i = firstLine ; i <= lastLine ; ++ i ) { rs . next (); ResultSetRunnable . delayfunc (); 8 16 rs . updateString (2 , val2 ); rs . updateString (3 , val3 ); rs . updateDate (4 , val4 ); rs . updateDouble (5 , val5 ); rs . updateInt (6 , val6 ); rs . updateDate (7 , val7 ); rs . updateString (8 , val8 ); 18 rs . updateRow (); 10 12 14 } O código dos contextos deste benchmark é muito semelhante ao código do benchmark principal, a diferença fundamental está na invocação do método delayfunc() da classe ResultSetRunnable entre cada operação sobre uma coluna para introduzir atraso entre colunas, ou entre cada linha lida para introduzir atraso entre linhas. O código executado por este método encontra-se na listagem 4.11. Realizando diversos testes, verificou-se que o método introduz o atraso de aproximadamente delayms. A variável delayms pertence à classe ResultSetRunnable, e o seu valor pode ser alterado usando o método setDelay dessa mesma classe. Listagem 4.11: Método de atraso. 1 3 5 final static Object = new Object (); public static void delayfunc () { synchronized ( obj ) { obj . wait ( delayms ); } } 37 Os resultados deste benchmark são apresentados na secção 5.3, e discutidos na secção 6.1.3. 4.3 Cache individual vs Cache partilhado Foi realizado um benchmark suplementar comparando a implementação do cursor cliente com cache individual com a implementação do cursor cliente com cache partilhado. Este benchmark tem o intuito de justificar a existência de ambos como solução ao problema, e ainda justificar a decisão de utilizar um fetch size de 100% para o cache partilhado. O benchmark contemplou apenas o contexto de leitura, pelo que o código executado pelos threads no processo é o mesmo que foi apresentado na listagem 4.1. Decidiu-se assim porque o contexto de leitura é o que mais directamente depende da implementação do cache, e por isso é também o mais relevante para testar. A ideia principal deste benchmark é verificar o efeito que vários valores de fetch size têm em cada implementação do cache. Na medida em que a implementação do cache partilhado por pré-definição carrega todas as linhas da tabela para o cache, isto é, tem um fetch size de 100%, foi necessário realizar uma modificação à implementação, de modo a ser possı́vel trabalhar com um cache partilhado que não escolhe todas as linhas; esta implementação é denominada por CJDBCS M , a implementação do cache individual é denominada por CJDBCI e a implementação normal do cache partilhado é denominada por CJDBCS . O procedimento passou por calcular o valor de linhas para o fetch size aplicando uma percentagem ao número total de linhas presentes na tabela. As várias percentagens definem os contextos, e são as seguintes: fetch size 10%, fetch size 20%, fetch size 50%, fetch size 75% e fetch size 100%. No entanto, nos resultados e nas discussões utiliza-se a notação mais compacta: 10, 20, 50, 75 e 100 para designar os contextos. O cenário de teste é o mesmo que foi utilizado no benchmark principal: a cada thread é atribuı́da uma gama de linhas sobre a qual ele deve realizar a tarefa que lhe foi atribuı́da. Os resultados deste benchmark são apresentados na secção 5.4, e discutidos na secção 6.1.4. 38 Capı́tulo 5 Resultados Neste capı́tulo, primeiro são apresentadas as caracterı́sticas do sistema computacional utilizado para realizar os benchmarks, e a seguir são apresentados os resultados para os benchmarks descritos no capı́tulo 4. Os gráficos apresentados refletem os resultados comparando as soluções aos pares efectuando um rácio. Isto permite perceber qual a solução que melhor responde à variação dos parâmetros do benchmark. A variável independente é a quantidade de threads, a variável dependente é o rácio do tempos obtidos para cada solução e cada série representa o número de linhas que é atribuı́do a cada thread. Cada thread executou a tarefa associada a um determinado contexto sobre esse número de linhas. São apresentados os resultados obtidos para três tempos: execução (ExecT), preparação (SetupT) e total (Total). 5.1 Plataforma de teste Os resultados apresentados foram obtidos usando a seguinte plataforma: • Cliente: Intel R CoreTM 2 Duo P8600 @ 2.40GHz, 4GB DDR2 • Servidor: Inter R PentiumTM SU4100 @ 1.30GHz, 4GB DDR3, Disco Rı́gido SATA II 7200 RPM • SQL Server 2008 versão 10.0.1600 • Java 1.6.0 17 • Microsoft SQL Server JDBC Driver 3.0 (sqljdbc4 - versão 3.0.1301.101, Abril de 2010) Foram registadas 30 amostras para cada teste. Para dar maior valor estatı́stico ao resultados as 10 melhores amostras e as 10 piores foram ignoradas, e os resultados reflectem portanto a média das restantes amostras. A razão para não ter em conta algumas amostras deve-se ao facto que podem acontecerem condições favoráveis ou desfavoráveis, externas ao 39 ambiente de teste, que alteram o comportamento habitual. Assim, descartando estes resultados anómalos, obtém-se uma melhor estimativa. De notar que a opção por este processo surge de observação empı́rica. Entre o registo de cada amostra foi executado o garbage collector com o intuito de reciclar os objectos anteriores que já não estejam a ser utilizados, libertando memória [56, 60]. Foi criada uma base de dados no SQL Server para registar os resultados e fornecer dados para a execução dos cenários de teste, com um modelo de recuperação simples e com a opção de estatı́sticas automáticas desactivada. Estas opções servem para diminuir o impacto que o SQL Server possa ter nos resultados. 5.2 Benchmark principal 5.2.1 Comparação com MSJDBC Esta secção apresenta os resultados do benchmark principal (secção 4.1). Os resultados são apresentados sob a forma de uma comparação entre o desempenho registado para o M SJDBC e as restantes soluções. A comparação é realizada através de um rácio dos tempos registados. 5.2.1.1 Actualização 5.2.1.1.1 CJDBCI A Figura 5.1 apresenta os resultados da comparação do desempenho de M SJDBC e de CJDBCI , no contexto Actualização. Figura 5.1: Comparação entre M SJDBC e CJDBCI , no contexto Actualização. (b) SetupT (a) ExecT MSJDBC/CJDBCI 1.06 MSJDBC/CJDBCI 1.4 90 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.04 (c) Total MSJDBC/CJDBCI 35 linhas/thread 65 linhas/thread 90 linhas/thread 80 1.02 70 1 60 0.98 50 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.35 1.3 1.25 1.2 0.96 40 1.15 0.94 30 1.1 0.92 20 1.05 0.9 10 0.88 10 30 50 quantidade de threads 70 90 0 1 10 30 50 quantidade de threads 70 90 10 30 50 quantidade de threads 70 90 Em relação ao tempo de execução, o CJDBCI apresenta um desempenho um pouco inferior ao de JDBC, principalmente para os valores do número de linhas por thread maiores. 40 O aumento da quantidade de threads penaliza mais o desempenho da solução CJDBCI . Com a ajuda do tempo de preparação, o CJDBCI consegue ser sempre mais rápido do que JDBC no que se refere ao tempo total. Neste caso o aumento da quantidade de threads beneficia o desempenho do CJDBCI , e para quantidades de threads baixas os resultados dos valores do número de linhas por thread são aproximados, diferenciando-se um pouco para as quantidades de threads mais altas, em que os valores de linhas mais altos penalizam mais o desempenho do CJDBCI . 5.2.1.1.2 CJDBCS A Figura 5.2 apresenta os resultados da comparação do desempenho de M SJDBC e de CJDBCS , no contexto Actualização. Figura 5.2: Comparação entre M SJDBC e CJDBCS , no contexto Actualização. (b) SetupT (a) ExecT MSJDBC/CJDBCS 1.5 MSJDBC/CJDBCS 2 30 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.45 (c) Total MSJDBC/CJDBCS 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.9 25 1.4 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.8 1.35 1.7 20 1.3 1.6 1.25 1.5 15 1.2 1.4 10 1.15 1.3 1.1 1.2 5 1.05 1.1 1 10 30 50 quantidade de threads 70 90 0 1 10 30 50 quantidade de threads 70 90 10 30 50 quantidade de threads 70 90 Aqui o desempenho do CJDBCS é sempre superior, conseguindo bons resultados totais, pois para as quantidades de threads maiores consegue ser quase duas vezes melhor do que JDBC. Quanto maior é a quantidade de threads melhor é o desempenho de CJDBCS em relação a JDBC. Diferentes números de linhas por thread não implicam alterações significativas ao comportamento geral. O tempo de preparação tem alguma importância, pois vem aumentar a superioridade do CJDBCS em relação ao JDBC para os tempos totais. 5.2.1.1.3 W JDBC A Figura 5.3 apresenta os resultados da comparação do desempenho de M SJDBC e de W JDBC, no contexto Actualização. 41 Figura 5.3: Comparação entre M SJDBC e W JDBC, no contexto Actualização. (b) SetupT (a) ExecT MSJDBC/WJDBC 1.45 MSJDBC/WJDBC 1.9 90 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.4 (c) Total MSJDBC/WJDBC 35 linhas/thread 65 linhas/thread 90 linhas/thread 80 1.8 1.35 70 1.7 1.3 60 1.6 1.25 50 1.5 1.2 40 1.4 30 1.3 20 1.2 10 1.1 1.15 1.1 1.05 1 10 30 50 quantidade de threads 70 90 0 35 linhas/thread 65 linhas/thread 90 linhas/thread 1 10 30 50 quantidade de threads 70 90 10 30 50 70 90 quantidade de threads O desempenho do W JDBC é sempre melhor, destacando-se para quantidades de threads maiores. A alteração do valor de linhas por thread não provoca diferenças dignas de registo. O tempo de preparação acentua a vantagem que o W JDBC tem sobre o JDBC no tempo total. 5.2.1.2 Leitura 5.2.1.2.1 CJDBCI A Figura 5.4 apresenta os resultados da comparação do desempenho de M SJDBC e de CJDBCI , no contexto Leitura. Figura 5.4: Comparação entre M SJDBC e CJDBCI , no contexto Leitura. (b) SetupT (a) ExecT MSJDBC/CJDBCI 0.98 MSJDBC/CJDBCI 1.75 80 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.96 (c) Total MSJDBC/CJDBCI 35 linhas/thread 65 linhas/thread 90 linhas/thread 70 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.7 0.94 60 1.65 0.9 50 1.6 0.88 40 1.55 0.92 0.86 1.5 30 0.84 1.45 20 0.82 0.8 1.4 10 0.78 10 30 50 quantidade de threads 70 90 0 1.35 10 30 50 quantidade de threads 70 90 10 30 50 quantidade de threads 70 90 O desempenho do CJDBCI é superior ao de JDBC para o tempo total, já no tempo de 42 execução verifica-se o contrário, embora a desvantagem não seja grande. Nota-se uma descida do desempenho do CJDBCI em relação ao JDBC para quantidades de threads maiores. Os valores de número de linhas por thread maiores prejudicam um pouco o desempenho do CJDBCI em relação ao desempenho do JDBC. 5.2.1.2.2 CJDBCS A Figura 5.5 apresenta os resultados da comparação do desempenho de M SJDBC e de CJDBCS , no contexto Leitura. Figura 5.5: Comparação entre M SJDBC e CJDBCS , no contexto Leitura. (a) ExecT (b) SetupT MSJDBC/CJDBCS 20 22 35 linhas/thread 65 linhas/thread 90 linhas/thread 18 (c) Total MSJDBC/CJDBCS MSJDBC/CJDBCS 20 35 linhas/thread 65 linhas/thread 90 linhas/thread 20 18 18 16 35 linhas/thread 65 linhas/thread 90 linhas/thread 16 16 14 14 14 12 12 12 10 10 10 8 8 8 6 6 6 4 4 4 2 2 10 30 50 quantidade de threads 70 90 2 10 30 50 quantidade de threads 70 90 10 30 50 quantidade de threads 70 90 Basicamente, o desempenho do CJDBCS é muito bom comparativamente ao de JDBC sendo, para o tempo total, no mı́nimo 4 vezes melhor e no máximo consegue ser quase 20 vezes melhor. Os valores do número de linhas por thread maiores penalizam mais o desempenho do CJDBCS , embora não gravosamente. O aumento da quantidade threads beneficia o desempenho do CJDBCS . 5.2.1.2.3 W JDBC A Figura 5.6 apresenta os resultados da comparação do desempenho de M SJDBC e de W JDBC, no contexto Leitura. O desempenho do W JDBC é sempre muito melhor do que o desempenho do JDBC, chegando a ser 10 vezes melhor do tempo total. As quantidades de threads maiores beneficiam o desempenho do W JDBC. O aumento do número de linhas por thread começa a dar vantagem ao desempenho de W JDBC à medida que o número de threads também aumenta. 43 Figura 5.6: Comparação entre M SJDBC e W JDBC, no contexto Leitura. (b) SetupT (a) ExecT 70 7 (c) Total MSJDBC/WJDBC MSJDBC/WJDBC MSJDBC/WJDBC 11 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 10 60 6 9 5 50 4 40 3 30 2 20 8 7 6 5 4 3 10 1 2 0 0 10 30 50 quantidade de threads 70 10 90 30 50 quantidade de threads 70 90 1 10 30 50 quantidade de threads 70 90 O tempo de preparação realça mais a vantagem do W JDBC no tempo total. 5.2.1.3 Inserção 5.2.1.3.1 CJDBCI A Figura 5.7 apresenta os resultados da comparação do desempenho de M SJDBC e de CJDBCI , no contexto Inserção. Figura 5.7: Comparação entre M SJDBC e CJDBCI , no contexto Inserção. (a) ExecT (b) SetupT MSJDBC/CJDBCI 1.06 (c) Total MSJDBC/CJDBCI MSJDBC/CJDBCI 1.12 40 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.1 1.04 30 1.08 25 1.06 20 1.04 1.02 1 1.02 15 0.98 1 10 0.96 0.98 5 0.94 10 30 50 quantidade de threads 70 90 0 0.96 10 30 50 quantidade de threads 70 90 10 30 50 70 90 quantidade de threads Praticamente sempre, o desempenho do CJDBCI é ligeiramente superior ao de JDBC. A atribuição de um maior número de linhas por thread, não tem grande peso para o desempenho, e o facto da quantidade de threads aumentar dá uma pequena vantagem ao CJDBCI . 44 Os resultados dos tempos de execução e total são semelhantes, pelo que se pode dizer que o tempo de preparação tem pouca influência para o resultado final. 5.2.1.3.2 CJDBCS A Figura 5.8 apresenta os resultados da comparação do desempenho de M SJDBC e de CJDBCS , no contexto Inserção. Figura 5.8: Comparação entre M SJDBC e CJDBCS , no contexto Inserção. (b) SetupT (a) ExecT MSJDBC/CJDBCS 1.04 MSJDBC/CJDBCS 1.1 5.5 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.02 (c) Total MSJDBC/CJDBCS 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 5 1 1.05 4.5 0.98 0.96 4 0.94 3.5 1 0.92 0.95 3 0.9 2.5 0.88 0.9 2 0.86 0.84 10 30 50 quantidade de threads 70 901.5 0.85 10 30 50 70 90 10 quantidade de threads 30 50 70 90 quantidade de threads No geral o desempenho do CJDBCS é marginalmente melhor do que o desempenho do JDBC. Para um número de threads maior a vantagem de CJDBCS sobre JDBC acentua-se. Os valores do número de linhas por thread menores apresentam um melhor desempenho comparativo em favor de CJDBCS , do que valores maiores. 5.2.1.3.3 W JDBC A Figura 5.9 apresenta os resultados da comparação do desempenho de M SJDBC e de W JDBC, no contexto Inserção. O desempenho do W JDBC é melhor para os valores do número de linhas por thread menores, enquanto que para valores menores a vantagem é do JDBC. O aumento da quantidade de threads não influencia com grande peso os resultados. O tempo de preparação tem pouca influência no tempo total. 5.2.1.4 5.2.1.4.1 Remoção CJDBCI 45 Figura 5.9: Comparação entre M SJDBC e W JDBC, no contexto Inserção. (b) SetupT (a) ExecT MSJDBC/WJDBC 1.03 MSJDBC/WJDBC 1.08 60 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.02 (c) Total MSJDBC/WJDBC 35 linhas/thread 65 linhas/thread 90 linhas/thread 55 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.07 50 1.06 45 1.05 40 1.04 35 1.03 1.01 1 0.99 0.98 30 0.97 25 1.02 1.01 1 20 0.96 0.99 15 0.95 0.98 10 0.94 10 30 50 70 90 5 quantidade de threads 0.97 10 30 50 quantidade de threads 70 10 90 30 50 70 90 quantidade de threads A Figura 5.10 apresenta os resultados da comparação do desempenho de M SJDBC e de CJDBCI , no contexto Remoção. Figura 5.10: Comparação entre M SJDBC e CJDBCI , no contexto Remoção. (b) SetupT (a) ExecT MSJDBC/CJDBCI 1.1 (c) Total MSJDBC/CJDBCI MSJDBC/CJDBCI 1.35 90 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 80 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.3 1.05 1.25 70 1.2 1 60 1.15 50 0.95 1.1 40 0.9 0.85 1.05 30 1 20 0.95 0.9 10 0.8 10 30 50 quantidade de threads 70 90 0 0.85 10 30 50 quantidade de threads 70 90 10 30 50 70 90 quantidade de threads Para o tempo de execução o desempenho do CJDBCI fica um pouco abaixo do desempenho do JDBC. Embora a influencia seja pequena, o aumento da quantidade de threads penaliza um pouco o desempenho do CJDBCI , verificando-se o mesmo para os valores maiores do número de linhas por thread. O tempo de preparação é relevante para o resultado final; para o tempo do total o CJDBCI apresenta melhor desempenho, sendo praticamente sempre superior a JDBC. O aumento da quantidade de threads dá vantagem a CJDBCI e o aumento do valor do número de linhas por thread penaliza um pouco o seu desempenho face ao desempenho do JDBC. 46 5.2.1.4.2 CJDBCS A Figura 5.11 apresenta os resultados da comparação do desempenho de M SJDBC e de CJDBCS , no contexto Remoção. Figura 5.11: Comparação entre M SJDBC e CJDBCS , no contexto Remoção. (b) SetupT (a) ExecT MSJDBC/CJDBCS 1.4 MSJDBC/CJDBCS 1.9 35 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.35 (c) Total MSJDBC/CJDBCS 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.8 30 1.3 1.7 25 1.25 1.6 20 1.2 1.5 1.15 1.4 15 1.1 1.3 10 1.05 1.2 5 1 0.95 10 30 50 70 90 0 quantidade de threads 1.1 1 10 30 50 quantidade de threads 70 90 10 30 50 quantidade de threads 70 90 O desempenho do CJDBCS é superior ao de JDBC. Para quantidades de threads menores o desempenho do CJDBCS é melhor mas a vantagem é moderada, para quantidades maiores o desempenho comparativo dispara atingindo valores muito bons. A quantidade de linhas por thread, não incute diferenças significativas. O tempo de preparação tem influência no resultado, acentuando ainda mais a vantagem do CJDBCS sobre o JDBC. 5.2.1.4.3 W JDBC A Figura 5.12 apresenta os resultados da comparação do desempenho de M SJDBC e de W JDBC, no contexto Remoção. O desempenho do W JDBC é superior ao de JDBC. O aumento da quantidade de threads beneficia o desempenho do W JDBC, embora seja mais notório no tempo total. Os valores de número de linhas por thread mais altos beneficiam mais o desempenho do JDBC. Os bons tempos de preparação do W JDBC dão-lhe ainda mais vantagem nos tempos totais sobre o JDBC. 5.2.1.5 Resumo Resumindo os resultados podemos dizer que em geral: 47 Figura 5.12: Comparação entre M SJDBC e W JDBC, no contexto Remoção. (b) SetupT (a) ExecT MSJDBC/WJDBC 1.35 MSJDBC/WJDBC 1.9 90 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.3 (c) Total MSJDBC/WJDBC 35 linhas/thread 65 linhas/thread 90 linhas/thread 80 1.8 1.25 70 1.7 1.2 60 1.6 1.15 50 1.5 40 1.4 30 1.3 20 1.2 10 1.1 1.1 1.05 1 0.95 0.9 10 30 50 70 quantidade de threads 90 0 35 linhas/thread 65 linhas/thread 90 linhas/thread 1 10 30 50 quantidade de threads 70 90 10 30 50 70 90 quantidade de threads • O desempenho de CJDBC 1 e de W JDBC é superior ao desempenho de M SJDBC; • O desempenho das soluções CJDBC e W JDBC melhora em relação ao desempenho de M SJDBC com o aumento da quantidade de threads; • O tempo de preparação de M SJDBC é muito superior, sendo por inúmeras vezes 80 vezes mais alto; • Para o contexto Actualização as soluções CJDBCS e W JDBC apresentam o melhor desempenho; • Para o contexto Inserção a solução CJDBCI apresenta o melhor desempenho; • Para o contexto Leitura a solução CJDBCS apresenta o melhor desempenho; • Para o contexto Remoção as soluções CJDBCS e W JDBC apresentam o melhor desempenho. 5.2.2 Comparação com WJDBC Esta secção apresenta os resultados do benchmark principal (secção 4.1). Os resultados são apresentados sob a forma de uma comparação entre o desempenho registado para o W JDBC e as soluções CJDBCI e CJDBCS . A comparação é realizada através de um rácio dos tempos registados. 5.2.2.1 5.2.2.1.1 1 Actualização CJDBCI CJDBC refere-se às soluções CJDBCI e CJDBCS 48 A Figura 5.13 apresenta os resultados da comparação do desempenho de W JDBC e de CJDBCI , no contexto Actualização. Figura 5.13: Comparação entre W JDBC e CJDBCI , no contexto Actualização. (b) SetupT (a) ExecT WJDBC/CJDBCI 1 (c) Total WJDBC/CJDBCI 0.98 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.96 0.95 WJDBC/CJDBCI 1 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.95 0.94 0.9 0.9 0.92 0.85 0.9 0.85 0.8 0.88 0.8 0.86 0.75 0.75 0.84 0.7 0.7 0.82 0.65 0.8 10 30 50 70 90 0.65 10 quantidade de threads 30 50 70 90 10 quantidade de threads 30 50 70 90 quantidade de threads O desempenho do W JDBC é sempre melhor do que o desempenho do CJDBCI . Para os tempos de execução e total o aumento da quantidade de threads penaliza mais o desempenho do CJDBCI , enquanto que para o tempo de preparação a mesma situação beneficia o desempenho do CJDBCI . Um número maior de linhas por thread não implica alterações importantes na comparação dos desempenhos. O tempo de preparação tem pouco impacto no tempo total. 5.2.2.1.2 CJDBCS A Figura 5.14 apresenta os resultados da comparação do desempenho de W JDBC e de CJDBCS , no contexto Actualização. Ambos têm desempenhos semelhantes, mas no tempo de preparação a vantagem está claramente do lado do W JDBC, aumentando com o crescimento da quantidade de threads. Porém este tempo não altera muito o total. O número de threads ou de linhas por thread não afecta significativamente os resultados. 5.2.2.2 5.2.2.2.1 Leitura CJDBCI A Figura 5.15 apresenta os resultados da comparação do desempenho de W JDBC e de CJDBCI , no contexto Leitura. O desempenho do CJDBCI é superior ao de W JDBC para a quantidade de threads menor e número de linhas por thread maior. O W JDBC é bastante superior para as restantes 49 Figura 5.14: Comparação entre W JDBC e CJDBCS , no contexto Actualização. (b) SetupT (a) ExecT WJDBC/CJDBCS 1.045 (c) Total WJDBC/CJDBCS WJDBC/CJDBCS 1.035 0.42 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.41 1.04 1.03 0.4 1.025 0.39 1.02 0.38 1.015 0.37 1.01 0.36 1.005 0.35 1 1.035 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.03 1.025 1.02 1.015 1.01 10 30 50 70 90 0.995 0.34 10 30 quantidade de threads 50 70 10 90 30 quantidade de threads 50 quantidade de threads 70 90 70 90 Figura 5.15: Comparação entre W JDBC e CJDBCI , no contexto Leitura. (b) SetupT (a) ExecT 1.1 1.4 (c) Total WJDBC/CJDBCI WJDBC/CJDBCI WJDBC/CJDBCI 1.4 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.2 1.05 1.2 1 1 1 0.8 0.95 0.8 0.6 0.9 0.6 0.4 0.85 0.4 0.8 0.2 0.2 0.75 0 10 30 50 70 90 quantidade de threads 35 linhas/thread 65 linhas/thread 90 linhas/thread 10 30 50 quantidade de threads 70 90 0 10 30 50 quantidade de threads situações. Uma quantidade de thread maior afecta negativamente o desempenho de CJDBCI . O número de linhas por thread tem pouco efeito para as quantidades de threads maiores. O tempo de preparação é semelhante, havendo uma ligeira vantagem para o W JDBC, e tem pouca preponderância no resultado total. 5.2.2.2.2 CJDBCS A Figura 5.16 apresenta os resultados da comparação do desempenho de W JDBC e de CJDBCS , no contexto Leitura. O CJDBCS começa por apresentar um desempenho bastante superior, mas que se degrada rapidamente com o aumento do número de threads. Porém o desempenho de CJDBCS 50 Figura 5.16: Comparação entre W JDBC e CJDBCS , no contexto Leitura. (b) SetupT (a) ExecT 0.4 8 (c) Total WJDBC/CJDBCS WJDBC/CJDBCS WJDBC/CJDBCS 3.5 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.38 7 3 0.36 6 2.5 0.34 5 0.32 4 2 0.3 3 1.5 0.28 2 0.26 10 1 10 30 50 70 30 50 70 90 1 quantidade de threads 90 10 30 quantidade de threads 50 quantidade de threads 70 90 mantém-se superior ao de W JDBC. Os números de linhas por thread maiores favorecem o desempenho do CJDBCS nos tempos de execução e total, e prejudicam-no no tempo de preparação. O mau tempo de preparação do CJDBCS faz com que a grande vantagem que tinha no tempo de execução seja reduzida quase para metade no tempo total. 5.2.2.3 Inserção 5.2.2.3.1 CJDBCI A Figura 5.17 apresenta os resultados da comparação do desempenho de W JDBC e de CJDBCI , no contexto Inserção. Figura 5.17: Comparação entre W JDBC e CJDBCI , no contexto Inserção. (b) SetupT (a) ExecT WJDBC/CJDBCI 1.08 0.8 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.06 (c) Total WJDBC/CJDBCI 0.75 1.04 WJDBC/CJDBCI 1.06 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.04 0.7 1.02 0.65 1 0.6 0.98 0.55 0.96 0.5 0.94 0.45 0.92 1.02 1 0.98 0.96 0.94 0.92 0.9 0.4 10 30 50 quantidade de threads 70 90 0.9 10 30 50 quantidade de threads 51 70 90 10 30 50 quantidade de threads 70 90 O desempenho do W JDBC é superior ao de CJDBCI para quantidades de threads mais baixas, enquanto que é o CJDBCI que ganha para as quantidades mais altas, mas com uma ligeira vantagem. O tempo de preparação do W JDBC é significativamente melhor, principalmente para os valores de número de linhas mais baixos. No geral o número de linhas por thread não afecta significativamente a comparação. 5.2.2.3.2 CJDBCS A Figura 5.18 apresenta os resultados da comparação do desempenho de W JDBC e de CJDBCS , no contexto Inserção. Figura 5.18: Comparação entre W JDBC e CJDBCS , no contexto Inserção. (a) ExecT (b) SetupT WJDBC/CJDBCS 1.06 0.35 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.04 (c) Total WJDBC/CJDBCS WJDBC/CJDBCS 1.06 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.04 0.3 1.02 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.02 1 1 0.25 0.98 0.98 0.96 0.96 0.2 0.94 0.94 0.92 0.92 0.15 0.9 0.9 0.88 0.88 0.1 0.86 0.86 0.84 0.05 10 30 50 70 90 0.84 10 quantidade de threads 30 50 70 90 10 30 quantidade de threads 50 70 90 quantidade de threads O desempenho do W JDBC é superior ao de CJDBCS para as quantidades de threads menores, e o desempenho do CJDBCS é superior para as quantidades maiores. O tempo de preparação do CJDBCS é muito pior do que o tempo do W JDBC, e vai-se degradando com uma quantidade de threads maior. O tempo de preparação tem um impacto pequeno no resultado final. O número de linhas por thread não influencia significativamente os resultados. 5.2.2.4 5.2.2.4.1 Remoção CJDBCI A Figura 5.19 apresenta os resultados da comparação do desempenho de W JDBC e de CJDBCI , no contexto Remoção. O desempenho do W JDBC é melhor do que o desempenho do CJDBCI , cujo desempenho se degrada com o aumento da quantidade de threads. O número de linhas por thread não afecta significativamente os resultados. 52 Figura 5.19: Comparação entre W JDBC e CJDBCI , no contexto Remoção. (b) SetupT (a) ExecT WJDBC/CJDBCI 0.95 (c) Total WJDBC/CJDBCI 1 35 linhas/thread 65 linhas/thread 90 linhas/thread WJDBC/CJDBCI 0.95 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.95 0.9 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.9 0.9 0.85 0.85 0.85 0.8 0.8 0.75 0.8 0.7 0.75 0.75 0.65 0.6 0.7 0.7 0.55 0.65 0.5 10 30 50 70 90 0.65 10 30 quantidade de threads 50 70 90 10 30 quantidade de threads 50 70 90 quantidade de threads O tempo de preparação é semelhante para ambos, tendo pouco impacto no resultado total. 5.2.2.4.2 CJDBCS A Figura 5.20 apresenta os resultados da comparação do desempenho de W JDBC e de CJDBCS , no contexto Remoção. Figura 5.20: Comparação entre W JDBC e CJDBCS , no contexto Remoção. (a) ExecT (b) SetupT WJDBC/CJDBCS 1.08 0.41 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.06 (c) Total WJDBC/CJDBCS WJDBC/CJDBCS 1.08 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.06 0.4 1.04 1.04 1.02 0.39 1.02 1 1 0.38 0.98 0.98 0.96 0.37 0.96 0.94 0.94 0.36 0.92 0.92 0.9 0.35 0.9 0.88 0.88 0.34 10 30 50 quantidade de threads 70 90 0.86 10 30 50 quantidade de threads 70 90 10 30 50 70 90 quantidade de threads Nos tempos de execução e total a vantagem vai para o desempenho do CJDBCS , embora seja muito pequena. O aumento da quantidade de threads não influencia significativamente os resultados e o número de linhas por thread maior é melhor para o desempenho do W JDBC. O tempo de preparação do W JDBC é bastante superior ao de CJDBCS , e o aumento de quantidade de threads melhora a vantagem. 53 O tempo de preparação não tem muito peso no tempo total, pelo que os resultados da comparação dos tempos de execução e total são muito parecidos. 5.2.3 Resumo Resumindo os resultados podemos dizer que em geral: • O desempenho de CJDBCI é pior do que o de W JDBC, principalmente para quantidades de threads maiores. A excepção regista-se no contexto da Inserção em que CJDBCI é ligeiramente melhor; • O desempenho de CJDBCS é melhor do que o de W JDBC, principalmente para quantidades de threads maiores. 5.3 Benchmark com atrasos Nesta secção são apresentados os resultados do benchmark que introduz a simulação de processamento entre colunas ou linhas. Este benchmark foi apresentado na secção 4.2. Uma vez que o único tempo medido que é directamente afectado pela introdução de atrasos é o tempo de execução, este será o único cujos valores serão apresentados. Para ser mais cómodo, os gráficos dos resultados com atraso e sem atraso são agrupados por contexto. Os valores dos atrasos entre colunas e linhas foram escolhidos de forma a poderem produzir resultados que sejam comparáveis. A tabela de testes (fig. 4.1) possui oito colunas, assim no atraso entre colunas é introduzido um atraso total de 8 × atrasoC , em que atrasoC é valor do atraso introduzido entre cada coluna. O atraso entre linhas (atrasoL ), para ser comparável, terá então o valor de 8 × atrasoC . Por exemplo, atraso de 0.1ms entre colunas versus atraso de 0.8ms entre linhas. Os detalhes do funcionamento deste benchmark podem ser encontrados na secção 4.2. 5.3.1 Atraso entre colunas 5.3.1.1 Comparação com MSJDBC Esta secção apresenta os resultados do benchmark com atrasos (secção 4.2). Os resultados reflectem o efeito do atraso entre colunas, e são apresentados sob a forma de uma comparação entre o desempenho registado para o M SJDBC e as restantes soluções. A comparação é realizada através de um rácio dos tempos registados. 5.3.1.1.1 Actualização 5.3.1.1.1.1 CJDBCI A Figura 5.21 apresenta os resultados da comparação do desempenho de M SJDBC e de CJDBCI , no contexto Actualização. 54 Figura 5.21: Efeito do atraso na comparação entre M SJDBC e CJDBCI , no contexto Actualização. (b) Atraso de 1ms (a) Sem atraso MSJDBC/CJDBCI 1.06 MSJDBC/CJDBCI 1.06 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.04 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.04 1.02 1.02 1 1 0.98 0.98 0.96 0.96 0.94 0.94 0.92 0.92 0.9 0.9 0.88 0.88 0.86 10 30 50 quantidade de threads 70 90 10 30 50 quantidade de threads 70 90 A introdução afectou ligeiramente o desempenho de CJDBCI , diminuindo o desempenho em relação ao M SJDBC. Os números de linhas por thread mais baixos denotam um maior efeito do atraso no desempenho de CJDBCI ; Os números de linhas por thread mais altos refletem menos impacto no desempenho. No geral a introdução de atraso entre colunas na actualização não tem muita influência nos resultados. 5.3.1.1.1.2 CJDBCS A Figura 5.22 apresenta os resultados da comparação do desempenho de M SJDBC e de CJDBCS , no contexto Actualização. A introdução de atraso prejudica um pouco o desempenho do CJDBCS . Onde se nota um maior impacto da introdução de atraso é no número de linhas mais baixo, e para quantidades de threads maiores. No geral a introdução de atraso entre colunas na actualização não tem muita influência nos resultados, e é ainda menos influenciante do que no caso do CJDBCI . 5.3.1.1.1.3 W JDBC A Figura 5.23 apresenta os resultados da comparação do desempenho de M SJDBC e de W JDBC, no contexto Actualização. A introdução de atraso tem uma enorme influência nos resultados, prejudicando gravemente o desempenho do W JDBC. 55 Figura 5.22: Efeito do atraso na comparação entre M SJDBC e CJDBCS , no contexto Actualização. (b) Atraso de 1ms (a) Sem atraso MSJDBC/CJDBCS 1.5 MSJDBC/CJDBCS 1.35 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.45 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.3 1.4 1.25 1.35 1.3 1.2 1.25 1.15 1.2 1.15 1.1 1.1 1.05 1.05 1 1 10 30 50 70 90 10 30 quantidade de threads 50 70 90 quantidade de threads Figura 5.23: Efeito do atraso na comparação entre M SJDBC e W JDBC, no contexto Actualização. (a) Sem atraso (b) Atraso de 1ms MSJDBC/WJDBC MSJDBC/WJDBC 0.22 1.45 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.4 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.215 0.21 1.35 0.205 1.3 0.2 1.25 0.195 0.19 1.2 0.185 1.15 0.18 1.1 0.175 1.05 0.17 0.165 1 10 30 50 quantidade de threads 70 90 10 30 50 70 90 quantidade de threads Na ausência de atraso o desempenho do W JDBC é superior ao de M SJDBC, mas com a introdução de atraso o desempenho do W JDBC é bastante pior (cerca 8 vezes). Porém comportamento em resposta à variação dos parâmetros é semelhante: os números de linhas por thread mais baixos e quantidades de threads maiores favorecem o desempenho do W JDBC. 5.3.1.1.2 Leitura 56 5.3.1.1.2.1 CJDBCI A Figura 5.24 apresenta os resultados da comparação do desempenho de M SJDBC e de CJDBCI , no contexto Leitura. Figura 5.24: Efeito do atraso na comparação entre M SJDBC e CJDBCI , no contexto Leitura. (b) Atraso de 1ms (a) Sem atraso MSJDBC/CJDBCI 0.98 MSJDBC/CJDBCI 1 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.96 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.98 0.94 0.96 0.92 0.94 0.9 0.92 0.88 0.9 0.86 0.88 0.84 0.86 0.82 0.84 0.8 0.82 0.78 0.8 10 30 50 70 90 quantidade de threads 10 30 50 70 90 quantidade de threads A introdução de atraso beneficia ligeiramente o desempenho do CJDBCI em relação ao M SJDBC. São as quantidades de linhas por thread maiores que mais ganham com a introdução de atraso. Porém o desempenho do CJDBCI degrada-se com o aumento da quantidade de threads. 5.3.1.1.2.2 CJDBCS A Figura 5.25 apresenta os resultados da comparação do desempenho de M SJDBC e de CJDBCS , no contexto Leitura. O desempenho do CJDBCS é gravemente prejudicado pela introdução de atraso. Ainda existe superioridade do desempenho do CJDBCS em relação ao M SJDBC, mas essa superioridade é cerca de 4.5 vezes mais pequena em relação à ausência de atraso. O CJDBCS continua a beneficiar do aumento da quantidade de threads. O número de linhas por thread varia em quase nada os resultados, pois para todos os valores os resultados são aproximados. 5.3.1.1.2.3 W JDBC A Figura 5.26 apresenta os resultados da comparação do desempenho de M SJDBC e de W JDBC, no contexto Leitura. 57 Figura 5.25: Efeito do atraso na comparação entre M SJDBC e CJDBCS , no contexto Leitura. (b) Atraso de 1ms (a) Sem atraso MSJDBC/CJDBCS MSJDBC/CJDBCS 4 20 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 18 3.5 16 3 14 12 2.5 10 2 8 6 1.5 4 1 2 10 30 50 quantidade de threads 70 10 90 30 50 quantidade de threads 70 90 Figura 5.26: Efeito do atraso na comparação entre M SJDBC e W JDBC, no contexto Leitura. (a) Sem atraso (b) Atraso de 1ms MSJDBC/WJDBC MSJDBC/WJDBC 0.6 7 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 6 0.5 5 0.4 4 0.3 3 0.2 2 0.1 1 0 0 10 30 50 quantidade de threads 70 90 10 30 50 quantidade de threads 70 90 O desempenho de W JDBC em relação ao de M SJDBC diminuiu drasticamente com a introdução de atraso. Para as quantidades de threads menores a tendência é o desempenho de W JDBC diminui em relação ao de M SJDBC. Para as quantidades maiores a relação entre as duas soluções mantém-se quase constante. 58 5.3.1.2 Comparação com WJDBC Esta secção apresenta os resultados do benchmark com atrasos (secção 4.2). Os resultados reflectem o efeito do atraso entre colunas, e são apresentados sob a forma de uma comparação entre o desempenho registado para o W JDBC e as restantes soluções. A comparação é realizada através de um rácio dos tempos registados. 5.3.1.2.1 Actualização 5.3.1.2.1.1 CJDBCI A Figura 5.27 apresenta os resultados da comparação do desempenho de W JDBC e de CJDBCI , no contexto Actualização. Figura 5.27: Efeito do atraso na comparação entre W JDBC e CJDBCI , no contexto Actualização. (b) Atraso de 1ms (a) Sem atraso WJDBC/CJDBCI 1 WJDBC/CJDBCI 6 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 5.8 0.95 5.6 0.9 5.4 5.2 0.85 5 0.8 4.8 0.75 4.6 4.4 0.7 4.2 0.65 10 30 50 quantidade de threads 70 90 4 10 30 50 quantidade de threads 70 90 A introdução de atraso dá uma enorme vantagem ao CJDBCI . Sem o atraso o desempenho do CJDBCI é no melhor caso aproximadamente igual ao de W JDBC e no pior caso é cerca de 0.3 vezes pior, porém com a introdução de atraso no melhor caso é 6 vezes melhor e no pior caso 4 vezes melhor. O CJDBCI continua a perder desempenho face ao W JDBC para quantidades de threads maiores, e os números de linhas por thread mais altos também continuam a beneficiar o CJDBCI . 5.3.1.2.1.2 CJDBCS A Figura 5.28 apresenta os resultados da comparação do desempenho de W JDBC e de CJDBCS , no contexto Actualização. 59 Figura 5.28: Efeito do atraso na comparação entre W JDBC e CJDBCS , no contexto Actualização. (b) Atraso de 1ms (a) Sem atraso WJDBC/CJDBCS 1.045 WJDBC/CJDBCS 6.6 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 6.5 1.04 6.4 1.035 6.3 1.03 6.2 1.025 6.1 1.02 6 1.015 5.9 1.01 10 30 50 quantidade de threads 70 905.8 10 30 50 quantidade de threads 70 90 O desempenho do CJDBCS é bastante beneficiado pela introdução de atraso, em relação ao W JDBC. Sem atraso, o desempenho de ambos é próximo. Com atraso, o desempenho de CJDBCS é mais de 6 vezes melhor. A variação os parâmetros do benchmark continuam a não influenciar significativamente os resultados. 5.3.1.2.2 Leitura 5.3.1.2.2.1 CJDBCI A Figura 5.29 apresenta os resultados da comparação do desempenho de W JDBC e de CJDBCI , no contexto Leitura. O impacto da introdução de atraso é enorme, dando uma grande vantagem ao desempenho do CJDBCI sobre o W JDBC. É maior a vantagem que o CJDBCI tem sobre o W JDBC com atraso, do que a vantagem que existe do W JDBC na ausência de atraso. Sem atraso o desempenho do W JDBC chega a ser 1.4 vezes melhor, mas com atraso o desempenho do CJDBCI chega a ser cerda de 19 vezes melhor. A tendência é de melhoria do desempenho de CJDBCI com o aumento de quantidade de threads até se verificar uma certa estabilização para quantidades maiores. Quanto maior é o número de linhas por thread, maior é a vantagem do CJDBCI sobre W JDBC. 60 Figura 5.29: Efeito do atraso na comparação entre W JDBC e CJDBCI , no contexto Leitura. (b) Atraso de 1ms (a) Sem atraso WJDBC/CJDBCI 1.4 WJDBC/CJDBCI 20 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 18 1.2 16 1 14 12 0.8 10 0.6 8 6 0.4 4 0.2 2 0 10 5.3.1.2.2.2 30 50 quantidade de threads 70 90 0 10 30 50 quantidade de threads 70 90 CJDBCS A Figura 5.30 apresenta os resultados da comparação do desempenho de W JDBC e de CJDBCS , no contexto Leitura. Figura 5.30: Efeito do atraso na comparação entre W JDBC e CJDBCS , no contexto Leitura. (b) Atraso de 1ms (a) Sem atraso WJDBC/CJDBCS WJDBC/CJDBCS 70 8 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 7 60 6 50 5 40 4 30 3 20 10 2 0 1 10 30 50 70 90 quantidade de threads 10 30 50 70 90 quantidade de threads Verifica-se um resultado semelhante ao de CJDBCI , mas a vantagem de CJDBCS sobre W JDBC é ainda mais esmagadora. O desempenho de CJDBCS já era melhor do que o desempenho de W JDBC na ausência de atraso, por isso a vantagem foi ainda maior com atraso. 61 A tendência de perda de desempenho do CJDBCS com o aumento da quantidade de threads foi invertida. 5.3.2 Atraso entre linhas 5.3.2.1 Comparação com MSJDBC Esta secção apresenta os resultados do benchmark com atrasos (secção 4.2). Os resultados reflectem o efeito do atraso entre linhas, e são apresentados sob a forma de uma comparação entre o desempenho registado para o M SJDBC e as restantes soluções. A comparação é realizada através de um rácio dos tempos registados. 5.3.2.1.1 Actualização 5.3.2.1.1.1 CJDBCI A Figura 5.31 apresenta os resultados da comparação do desempenho de M SJDBC e de CJDBCI , no contexto Actualização. Figura 5.31: Efeito do atraso na comparação entre M SJDBC e CJDBCI , no contexto Actualização. (a) Sem atraso (b) Atraso de 8ms MSJDBC/CJDBCI 1.06 MSJDBC/CJDBCI 1.02 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.04 35 linhas/thread 65 linhas/thread 90 linhas/thread 1 1.02 0.98 1 0.96 0.98 0.94 0.96 0.92 0.94 0.9 0.92 0.88 0.9 0.86 0.88 0.84 10 30 50 quantidade de threads 70 90 10 30 50 quantidade de threads 70 90 Os resultados do desempenho do CJDBCI com atraso são ligeiramente piores do que os resultados do M SJDBC. A quantidade de linhas por thread mais baixa é a que revela maior perda de desempenho por parte do CJDBCI . Tirando a ligeira descida do desempenho do CJDBCI , o efeito do atraso é insignificante. 62 5.3.2.1.1.2 CJDBCS A Figura 5.32 apresenta os resultados da comparação do desempenho de M SJDBC e de CJDBCS , no contexto Actualização. Figura 5.32: Efeito do atraso na comparação entre M SJDBC e CJDBCS , no contexto Actualização. (a) Sem atraso (b) Atraso de 8ms MSJDBC/CJDBCS 1.5 MSJDBC/CJDBCS 1.3 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.45 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.25 1.4 1.35 1.2 1.3 1.25 1.15 1.2 1.1 1.15 1.1 1.05 1.05 1 1 10 30 50 quantidade de threads 70 90 10 30 50 quantidade de threads 70 90 O desempenho do CDJBCS é prejudicado ligeiramente com introdução de atraso. É no número de linhas por thread mais baixo que se nota a maior diferença. No geral o efeito da introdução de atraso é insignificante. 5.3.2.1.1.3 W JDBC A Figura 5.33 apresenta os resultados da comparação do desempenho de M SJDBC e de W JDBC, no contexto Actualização. O desempenho do W JDBC sofre uma perda significativa em relação ao desempenho do M SJDBC. O crescimento da vantagem em relação ao M SJDBC quando o número de threads aumenta já não se verifica. Os valores do desempenho do W JDBC para os números de linhas por thread maiores perdem mais, ficando mesmo abaixo do desempenho do M SJDBC. 5.3.2.1.2 Leitura 5.3.2.1.2.1 CJDBCI 63 Figura 5.33: Efeito do atraso na comparação entre M SJDBC e W JDBC, no contexto Actualização. (b) Atraso de 8ms (a) Sem atraso MSJDBC/WJDBC MSJDBC/WJDBC 1.45 1.15 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.4 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.1 1.35 1.05 1.3 1 1.25 0.95 1.2 0.9 1.15 0.85 1.1 0.8 1.05 0.75 1 10 30 50 70 10 90 30 quantidade de threads 50 quantidade de threads 70 90 A Figura 5.34 apresenta os resultados da comparação do desempenho de M SJDBC e de CJDBCI , no contexto Leitura. Figura 5.34: Efeito do atraso na comparação entre M SJDBC e CJDBCI , no contexto Leitura. (a) Sem atraso (b) Atraso de 8ms MSJDBC/CJDBCI 0.98 MSJDBC/CJDBCI 1 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.96 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.98 0.96 0.94 0.94 0.92 0.92 0.9 0.9 0.88 0.88 0.86 0.86 0.84 0.84 0.82 0.82 0.8 0.8 0.78 0.78 10 30 50 70 90 quantidade de threads 10 30 50 70 90 quantidade de threads A introdução de atraso favorece o desempenho do CJDBCI em relação ao desempenho do M SJDBC. É nos valores de linhas por thread mais altos que se verifica a vantagem da introdução de atraso de CJDBCI em relação a M SJDBC, no entanto o desempenho do M SJDBC ainda é superior ao de CJDBCI . Os diferentes valores de linhas por thread apresentam resultados próximos entre si. 64 As quantidades maiores de threads penalizam o desempenho do CJDBCI . 5.3.2.1.2.2 CJDBCS A Figura 5.35 apresenta os resultados da comparação do desempenho de M SJDBC e de CJDBCS , no contexto Leitura. Figura 5.35: Efeito do atraso na comparação entre M SJDBC e CJDBCS , no contexto Leitura. (a) Sem atraso (b) Atraso de 8ms MSJDBC/CJDBCS MSJDBC/CJDBCS 6 20 35 linhas/thread 65 linhas/thread 90 linhas/thread 18 35 linhas/thread 65 linhas/thread 90 linhas/thread 5.5 5 16 4.5 14 4 12 3.5 10 3 8 2.5 6 2 4 1.5 1 2 10 30 50 70 90 10 30 quantidade de threads 50 quantidade de threads 70 90 O efeito da introdução do atraso é penalizador para o desempenho do CJDBCS . Embora o CJDBCS ainda mantenha um desempenho claramente superior ao desempenho de M SJDBC, a vantagem é aproximadamente 3.5 vezes menor. A diferença entre os resultados para os vários valores de linhas por thread diminuiu. O aumento da quantidade de threads continua a beneficiar o desempenho do CJDBCS . 5.3.2.1.2.3 W JDBC A Figura 5.36 apresenta os resultados da comparação do desempenho de M SJDBC e de W JDBC, no contexto Leitura. A introdução de atraso é muito penalizador para o desempenho do W JDBC. O desempenho do W JDBC deixou de ser superior ao de M SJDBC, e passou a ser bastante inferior. W JDBC perdeu mais desempenho para os valores de linhas por thread maiores. O aumento da quantidade de thread começa por penalizar o desempenho do W JDBC até que estabiliza, permanecendo relativamente constante. 65 Figura 5.36: Efeito do atraso na comparação entre M SJDBC e W JDBC, no contexto Leitura. (b) Atraso de 8ms (a) Sem atraso MSJDBC/WJDBC MSJDBC/WJDBC 1.1 7 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 1 6 0.9 5 0.8 4 0.7 0.6 3 0.5 2 0.4 1 0.3 0.2 0 10 5.3.2.2 30 50 quantidade de threads 70 90 10 30 50 quantidade de threads 70 90 Comparação com WJDBC Esta secção apresenta os resultados do benchmark com atrasos (secção 4.2). Os resultados reflectem o efeito do atraso entre linhas, e são apresentados sob a forma de uma comparação entre o desempenho registado para o W JDBC e as restantes soluções. A comparação é realizada através de um rácio dos tempos registados. 5.3.2.2.1 Actualização 5.3.2.2.1.1 CJDBCI A Figura 5.37 apresenta os resultados da comparação do desempenho de W JDBC e de CJDBCI , no contexto Actualização. A introdução de atraso beneficia o desempenho do CJDBCI em relação ao desempenho do W JDBC, principalmente para os valores de linhas por thread maiores. O valor de linhas por thread mais baixo sofreu poucas alterações. Tirando uma fase inicial em que o aumento da quantidade de threads beneficia o desempenho do CJDBCI , o aumento da quantidade de thread penaliza o desempenho do CJDBCI em relação ao desempenho do W JDBC. 5.3.2.2.1.2 CJDBCS A Figura 5.38 apresenta os resultados da comparação do desempenho de W JDBC e de CJDBCS , no contexto Actualização. 66 Figura 5.37: Efeito do atraso na comparação entre W JDBC e CJDBCI , no contexto Actualização. (b) Atraso de 8ms (a) Sem atraso WJDBC/CJDBCI 1 WJDBC/CJDBCI 1.25 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.2 0.95 1.15 0.9 1.1 1.05 0.85 1 0.8 0.95 0.9 0.75 0.85 0.7 0.8 0.65 0.75 10 30 50 quantidade de threads 70 90 10 30 50 quantidade de threads 70 90 Figura 5.38: Efeito do atraso na comparação entre W JDBC e CJDBCS , no contexto Actualização. (a) Sem atraso (b) Atraso de 8ms WJDBC/CJDBCS 1.045 WJDBC/CJDBCS 1.45 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.4 1.04 1.35 1.035 1.3 1.03 1.25 1.2 1.025 1.15 1.02 1.1 1.015 1.05 1.01 10 30 50 quantidade de threads 70 90 1 10 30 50 quantidade de threads 70 90 A introdução de atraso beneficia com algum significância o desempenho do CJDBCS face ao desempenho do W JDBC, principalmente para os valores de linhas por thread maiores. Inicialmente o aumento da quantidade threads dá alguma vantagem ao desempenho do CJDBCS , até estabilizar e manter-se relativamente constante para as quantidades maiores. 5.3.2.2.2 Leitura 67 5.3.2.2.2.1 CJDBCI A Figura 5.39 apresenta os resultados da comparação do desempenho de W JDBC e de CJDBCI , no contexto Leitura. Figura 5.39: Efeito do atraso na comparação entre W JDBC e CJDBCI , no contexto Leitura. (a) Sem atraso (b) Atraso de 8ms WJDBC/CJDBCI 1.4 WJDBC/CJDBCI 3.5 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.2 35 linhas/thread 65 linhas/thread 90 linhas/thread 3 1 2.5 0.8 2 0.6 1.5 0.4 1 0.2 0 10 30 50 quantidade de threads 70 0.5 90 10 30 50 quantidade de threads 70 90 Com a introdução de atraso, o desempenho do CJDBCI passa a ser sempre superior ao de W JDBC; a vantagem que CJDBCI tem agora em relação ao W JDBC é maior do que a vantagem que o W JDBC tinha na ausência de atraso. Os valores do número de linhas por thread maiores dão maior vantagem ao desempenho do CJDBCI . O aumento do quantidade de threads dá alguma vantagem ao desempenho do CJDBCI , para quantidades menores. 5.3.2.2.2.2 CJDBCS A Figura 5.40 apresenta os resultados da comparação do desempenho de W JDBC e de CJDBCS , no contexto Leitura. O desempenho do CJDBCS em relação ao W JDBC beneficia bastante da introdução de atraso. Para além de o desempenho do CJDBCS face ao de W JDBC ser ainda maior do que o que se verificava na ausência de atraso, o aumento da quantidade de threads deixa de ser penalizador para o desempenho do CJDBCS e passa a dar vantagem. 68 Figura 5.40: Efeito do atraso na comparação entre W JDBC e CJDBCS , no contexto Leitura. (b) Atraso de 8ms (a) Sem atraso WJDBC/CJDBCS WJDBC/CJDBCS 18 8 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 16 7 14 6 12 5 10 8 4 6 3 4 2 2 0 1 10 30 50 70 90 10 quantidade de threads 5.3.3 30 50 quantidade de threads 70 90 Resumo O atraso introduzido entre colunas provocou mais efeito nos resultados do que o atraso entre linhas, mas produzem conclusões semelhantes, por isso o seguinte é válido para ambos (salvo diferenças referidas explicitamente): • A introdução de atraso afecta o M SJDBC e o CJDBCI da mesma forma, pelo que os resultados com e sem atraso são semelhantes; • A introdução de atraso não afectou significativamente os resultados da comparação de CJDBCS com M SJDBC na Actualização, no entanto para a Leitura o desempenho de CJDBCS sofreu um grande decréscimo; • O desempenho da solução W JDBC diminuiu muito com a introdução de atraso. Por exemplo, comparando com a ausência de atraso entre colunas, na Leitura foi cerca de 10 vezes pior e na Actualização foi cerca de 6 vezes pior. 5.4 Cache individual vs Cache partilhado Esta secção apresenta os resultados para o benchmark do Cache individual vs Cache partilhado (secção 4.3). Para cada tamanho do fetch size é realizada a comparação entre os resultados registados para o cache individual e o cache partilhado, através de um rácio. Relembro que para poder ser possı́vel a realização deste benchmark teve de ser criada uma versão especial do cache partilhado, denominada de CJDBCS M , que permite definir diferentes tamanhos para a capacidade do cache. Para se ter uma noção da diferença de desempenho entre as versões oficiais de cada tipo de cache, é realizada uma comparação adicional 69 entre CJDBCI e CJDBCS , mas que obviamente só foi executada no contexto de fetch size 100%. Relembro ainda que se utilizou a forma mais compacta 10, 20, etc, para designar os contextos fetch size 10%, fetch size 20%, etc. 5.4.1 Fetch size 10% 5.4.1.1 CJDBCS M A Figura 5.41 apresenta os resultados da comparação do desempenho de CJDBCI e de CJDBCS M , no contexto Fetch size 10%. Figura 5.41: Comparação entre CJDBCI e CJDBCS M , no contexto 10 . (a) ExecT (b) SetupT 1.35 0.7 (c) Total CJDBCI/CJDBCSM CJDBCI/CJDBCSM 0.6 1.3 0.5 1.25 0.4 1.2 0.3 1.15 0.2 1.1 CJDBCI/CJDBCSM 0.8 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.7 0.6 0.5 0.4 0.3 0.2 1.05 0.1 0.1 1 0 10 30 50 70 90 10 30 quantidade de threads 50 quantidade de threads 70 90 0 10 30 50 70 90 quantidade de threads O desempenho do CJDBCS M é bastante inferior ao desempenho do CJDBCI . O valor do tempo de preparação tem pouco impacto, pelo que os valores do desempenho comparativo para os tempos de execução e total são essencialmente os mesmos. 5.4.2 5.4.2.1 Fetch size 20% CJDBCS M A Figura 5.42 apresenta os resultados da comparação do desempenho de CJDBCI e de CJDBCS M , no contexto Fetch size 20%. O desempenho do CJDBCS M é sempre bastante inferior ao de CJDBCI . O aumento do número de linhas por thread penaliza significativamente o desempenho do CJDBCS M face 70 Figura 5.42: Comparação entre CJDBCI e CJDBCS M , no contexto 20. (b) SetupT (a) ExecT CJDBCI/CJDBCSM 0.26 1.25 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.24 (c) Total CJDBCI/CJDBCSM CJDBCI/CJDBCSM 0.3 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.2 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.25 0.22 1.15 0.2 1.1 0.2 0.18 1.05 0.16 0.15 1 0.14 0.95 0.12 0.1 0.9 0.1 0.08 0.85 10 30 50 70 90 0.05 10 30 50 quantidade de threads 70 90 10 30 quantidade de threads 50 70 90 quantidade de threads ao CJDBCI . Embora o aumento da quantidade de threads também penalize o desempenho do CJDBCS M , o peso é menor. O tempo de preparação tem pouca influência no resultado total. 5.4.3 Fetch size 50% 5.4.3.1 CJDBCS M A Figura 5.43 apresenta os resultados da comparação do desempenho de CJDBCI e de CJDBCS M , no contexto Fetch size 50%. Figura 5.43: Comparação entre CJDBCI e CJDBCS M , no contexto 50. (b) SetupT (a) ExecT CJDBCI/CJDBCSM 0.35 (c) Total CJDBCI/CJDBCSM 1.16 35 linhas/thread 65 linhas/thread 90 linhas/thread CJDBCI/CJDBCSM 0.4 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.14 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.35 0.3 1.12 0.3 0.25 1.1 1.08 0.25 1.06 0.2 0.2 0.15 1.04 0.15 1.02 0.1 0.1 1 0.05 0.98 10 30 50 quantidade de threads 70 90 0.05 10 30 50 quantidade de threads 70 90 10 30 50 70 90 quantidade de threads A comparação de desempenho dos dois mantém-se semelhante aos contextos fetch size 71 10% e 20%, pelo que o que foi dito para esses contextos aplica-se a este. Porém nota-se uma muito ligeira melhoria em relação ao contexto fetch size 20%. 5.4.4 Fetch size 75% 5.4.4.1 CJDBCS M A Figura 5.44 apresenta os resultados da comparação do desempenho de CJDBCI e de CJDBCS M , no contexto Fetch size 75%. Figura 5.44: Comparação entre CJDBCI e CJDBCS M , no contexto 75. (b) SetupT (a) ExecT CJDBCI/CJDBCSM 0.35 (c) Total CJDBCI/CJDBCSM 1.15 35 linhas/thread 65 linhas/thread 90 linhas/thread CJDBCI/CJDBCSM 0.4 35 linhas/thread 65 linhas/thread 90 linhas/thread 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.35 0.3 1.1 0.3 0.25 1.05 0.25 0.2 0.2 1 0.15 0.15 0.95 0.1 0.1 0.05 0.9 10 30 50 70 90 0.05 10 quantidade de threads 30 50 70 90 10 30 quantidade de threads 50 70 90 quantidade de threads Também aqui os resultados são semelhantes ao contextos anteriores. Nota-se, porém, uma ligeira melhoria do desempenho do CJDBCS M para os valores de número de linhas por thread maiores. 5.4.5 5.4.5.1 Fetch size 100% CJDBCS M A Figura 5.45 apresenta os resultados da comparação do desempenho de CJDBCI e de CJDBCS M , no contexto Fetch size 100%. O seguinte aplica-se a todos aos tempos de execução e total; o CJDBCS M para o número de linhas por thread mais baixo, praticamente iguala o desempenho de CJDBCI , começando a perder terreno à medida que os valores vão aumentando. Em relação ao tempo de preparação, para o valores mais altos de linhas existe alguma vantagem para o CJDBCI , e para o restante valor o desempenho é aproximadamente igual para ambos. 72 Figura 5.45: Comparação entre CJDBCI e CJDBCS M , no contexto 100. (b) SetupT (a) ExecT CJDBCI/CJDBCSM 1.15 CJDBCI/CJDBCSM 1.15 1.9 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.1 (c) Total CJDBCI/CJDBCSM 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.8 1.1 1.7 1.05 1.05 1.6 1 35 linhas/thread 65 linhas/thread 90 linhas/thread 1 1.5 0.95 0.95 1.4 0.9 0.9 1.3 0.85 0.85 1.2 0.8 0.8 1.1 0.75 0.75 1 0.7 10 30 50 70 900.9 quantidade de threads 5.4.5.2 0.7 10 30 50 quantidade de threads 70 10 90 30 50 70 90 quantidade de threads CJDBCS A Figura 5.46 apresenta os resultados da comparação do desempenho de CJDBCI e de CJDBCS , no contexto Fetch size 100%. Figura 5.46: Comparação entre CJDBCI e CJDBCS , no contexto 100. (a) ExecT (b) SetupT 0.75 20 35 linhas/thread 65 linhas/thread 90 linhas/thread 18 (c) Total CJDBCI/CJDBCS CJDBCI/CJDBCS CJDBCI/CJDBCS 12 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.7 35 linhas/thread 65 linhas/thread 90 linhas/thread 11 0.65 10 16 0.6 9 14 0.55 8 12 0.5 7 0.45 6 10 0.4 8 5 0.35 4 0.3 6 3 0.25 4 10 30 50 quantidade de threads 70 90 10 30 50 quantidade de threads 70 90 2 10 30 50 70 90 quantidade de threads O desempenho geral de CJDBCS é muito superior ao de CJDBCI . Verifica-se que um número de linhas por thread mais baixo, faz com que o crescimento do desempenho relativo ao tempo de execução de CJDBCS face ao de CJDBCI seja mais acentuado, enquanto que para um número de linhas maior o crescimento é quase horizontal. O tempo de preparação do CJDBCI é bem melhor do que o de CJDBCS . Apesar disso, no tempo total o CJDBCS tem muito melhor desempenho. 73 5.4.6 Resumo Resumindo os resultados apresentados: • O CJDBCI apresenta muito melhor desempenho do que CJDBCS M para todos os contextos à excepção de 100, em que se encontram mais próximos, mas mantendo-se superior; • O CJDBCS é no mı́nimo 2 vezes mais rápido do que CJDBCI para o contexto 100, e chega a ser 11.5 vezes mais rápido. 74 Capı́tulo 6 Discussão 6.1 Análise de resultados Nas subsecções seguintes são discutidos e analisados os resultados obtidos e apresentados no capı́tulo anterior. 6.1.1 Comparação com JDBC Esta subsecção discute e analisa os resultados apresentados na secção 5.2. Como já foi mencionado no capı́tulo dos resultados, todas as soluções apresentadas demonstraram um tempo de preparação muito melhor do que a solução M SJDBC. Este era o resultado esperado, na medida em que nesta solução tem que se criar uma statement e um result set para para thread, enquanto que as restantes soluções possibilitam a partilha do mesmo objecto result set. Isto significa que num ambiente multithreaded com n threads em execução e em que o tempo de criação de um result set é dado por cRS , o tempo total do tempo de preparação para M SJDBC será de n × cRS , enquanto que em CJDBC e em W JDBC será de apenas cRS . Os resultados práticos comprovam esta teoria na medida em que o rácio entre os tempos de preparação do M SJDBC e as outras soluções é numericamente aproximado à quantidade de threads utilizado para efectuar a medição (veja-se por exemplo a Figura 5.12). Esta relação não se verifica para a solução CJDBCS porque esta é a única que na preparação carrega os dados do result set para o cache. As outras soluções apenas criam o result set (declaração e abertura do cursor do servidor). A vantagem do melhor tempo de preparação revelou-se importante em diversas situações, porque onde por vezes existia uma ligeira vantagem do M SJDBC, esta foi anulada com a ajuda do tempo de preparação. Por exemplo na comparação entre M SJDBC e CJDBCI para a Actualização (fig. 5.1), que para quantidades de threads e valores de número de linhas maiores o M SJDBC era cerca de 10% mais rápido, no tempo total a vantagem vai para o CJDBCI sendo cerca de 30% mais rápido do que M SJDBC. A solução CJDBCI revelou-se mais eficiente do que o M SJDBC em todos os contextos, com destaque para os contextos de modificação (Actualização, Inserção e Remoção), 75 em que a tendência é haver mais vantagem para o CJDBCI quantos mais threads estiverem em execução. Já no contexto da Leitura, embora exista uma clara vantagem para o CJDBCI , a tendência não se verifica pois existe um ligeiro declı́nio em favor do desempenho do M SJDBC. Esta situação justifica-se pelo facto de o cursor com cache individual, do ponto de vista do cliente, ter um peso semelhante ao M SJDBC pois cada thread possui em cache a totalidade do dataset. Depois como se pode ver no tempo de execução da Leitura (fig. 5.4), a implementação da Microsoft é simplesmente mais eficiente do que a realizada neste trabalho. No entanto é importante ressalvar que mesmo com a tendência para diminuir o desempenho do CJDBCI face ao M SJDBC com o aumento da quantidade de threads, é na Leitura que o CJDBCI demonstra maior superioridade sendo cerca de 35% a 70% mais rápido do que M SJDBC. No contexto da inserção verificou-se muito equilı́brio, principalmente nos tempos de execução em que os resultados de M SJDBC e CJDBCI são muito próximos. No tempo total consegue-se notar uma pequena superioridade de CJDBCI devido ao bom desempenho do tempo de preparação. Uma outra nota vai para o facto de os resultados para os contextos da Actualização e da Remoção serem semelhantes. É compreensı́vel que tal se suceda pois tirando o diferente valor para optype do RPC sp cursor (ver C.1), a implementação dos métodos de actualização e remoção semelhantes. Ainda assim no contexto da remoção não há actualização de valores das colunas pelo que o desempenho do CJDBCI neste contexto é melhor do que na actualização. A solução CJDBCS também apresenta um desempenho claramente superior ao desempenho de M SJDBC, melhor até porque apesar de não apresentar um tempo de preparação tão bom, ao nı́vel da execução é mais eficiente. O tempo de preparação não é tão bom, porque ao contrário do M SJDBC e do CJDBCI , o CJDBCS constrói o cache no momento da criação dos cursores, refletindo-se o peso dessa operação no tempo de preparação em vez do tempo de execução. É também por esta razão que apresenta melhores resultados para a execução, pois uma vez que já tem os dados em cache não necessita de requisitar as linhas do dataset, operação essa que se revela bastante penosa para o desempenho. Este aspecto é claramente visı́vel para o contexto da Leitura em que o CJDBCS consegue ser no mı́nimo 4 vezes mais rápido do que M SJDBC e no máximo quase 18 vezes, e para além disso o desempenho comparativo melhora à medida que a quantidade de threads aumenta, ao contrário do CJDBCI cujo desempenho comparativo diminui nessa situação (ver Figura 6.1). Queria aqui referir um aspecto em relação ao contexto de Actualização. Como se pode ver na Figura 6.2 o desempenho comparativo do CJDBCS em relação ao M SJDBC é melhor do que o do CJDBCI . Mas porquê? Até se poderia pensar que aconteceria precisamente o contrário, pois o cursor de cache partilhado tem de obter acesso exclusivo ao cache para o actualizar, enquanto que o cache individual pode actualizar o seu cache sem essa necessidade. A razão surge do facto que o cache individual tem a necessidade de mover o cursor do servidor antes de efectuar a actualização, já o mesmo não acontece com o cursor com cache partilhado. A actualização de um result set no SQL Server a partir de um cursor só é possı́vel ao fim de se carregar (fetch) algumas linhas no buffer do cursor1 , e é mesmo lançado um erro caso o 76 Figura 6.1: Comparação entre CJDBC e M SJDBC, no contexto Leitura MSJDBC/CJDBCI 0.98 MSJDBC/CJDBCS 20 35 linhas/thread 65 linhas/thread 90 linhas/thread 0.96 35 linhas/thread 65 linhas/thread 90 linhas/thread 18 0.94 16 0.92 14 0.9 12 0.88 10 0.86 0.84 8 0.82 6 0.8 4 0.78 10 30 50 70 90 2 quantidade de threads 10 30 50 quantidade de threads 70 90 Figura 6.2: Comparação entre CJDBC e M SJDBC, no contexto Actualização MSJDBC/CJDBCI 1.06 MSJDBC/CJDBCS 1.5 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.04 35 linhas/thread 65 linhas/thread 90 linhas/thread 1.45 1.4 1.02 1.35 1 1.3 0.98 1.25 0.96 1.2 0.94 1.15 0.92 1.1 0.9 1.05 0.88 10 30 50 70 90 quantidade de threads 1 10 30 50 quantidade de threads 70 90 buffer esteja vazio. Na operação de fetch o servidor envia as linhas do buffer ao cliente, que depois cria o seu próprio cache. O ı́ndice para actualizar uma linha do result set é relativa a esse buffer e não ao result set. Isso quer dizer que o cliente pode pedir para actualizar, por exemplo, a linha 3 e estar a actualizar a linha 60 do result set. O cursor com cache individual não pode assumir que tem todas as linhas do result set, logo assume que a sua noção de cache é diferente da noção do servidor, e por essa razão move o cursor do servidor antes de actualizar, através de um UPDATE ABSOLUTE em vez de um UPDATE normal. A diferença é que no UPDATE, o cursor utiliza o seu buffer para actualizar os dados da tabela, enquanto que no UPDATE ABSOLUTE a modificação é realizada nas tabelas. Já no cursor com cache partilhado os ı́ndices utilizados para identificar as linhas são os mesmos nos dois lados e inalteráveis, pelo que se pode actualizar directamente usando o cursor servidor. Tal como se havia verificado com CJDBCI , o desempenho de CJDBCS e M SJDBC 1 Na documentação do protocolo é utilizada a designação de buffer em vez de cache. 77 para a Inserção, são próximos. A solução WJDBC também revelou melhor desempenho do que M SJDBC em todos os contextos, voltando a revelar uma relação directa entre o número de threads e o ganho de desempenho para o tempo de preparação, tal como se verificou com CJDBCI . Ao nı́vel da preparação as soluções W JDBC e CJDBCI são semelhantes na medida em que ambas essencialmente o que fazem é abrir e alocar um cursor do servidor que por sua vez cria um result set. Nos contextos da Actualização e da Remoção os resultados foram semelhantes, porque tal como foi explicado para CJDBCI as duas operações têm uma implementação semelhante. No entanto, W JDBC tem maior vantagem sobre M SJDBC do que tem CJDBCI , mas isto é um assunto a discutir na comparação com W JDBC na secção 6.1.2. A operação em que W JDBC mais se destacou foi a Leitura. Para uma quantidade reduzida de threads a vantagem é pequena, mas para quantidades maiores o desempenho de W JDBC é muito superior ao de M SJDBC, chegando a ser 10 mais rápido. A razão principal para este resultado tem a ver com as n cópias do result set que o M SJDBC tem que efectuar do result set. Na preparação o M SJDBC tem que criar n vezes mais cursores, e como é na execução que as linhas são realmente carregadas em cache, na execução têm que ser carregados n vezes mais result sets. O resultado final é a grande superioridade de desempenho do W JDBC sobre o M SJDBC. Mais uma vez verifica-se que para o contexto da Inserção existe um maior equilı́brio nos resultados não existindo uma vantagem clara para alguma das soluções. 6.1.2 Comparação com WJDBC No geral a solução CJDBCI tem um desempenho inferior ao demonstrado pela solução W JDBC. Também na generalidade, para quantidades de threads maiores a solução CJDBCI perde desempenho em relação a W JDBC. Embora a solução CJDBCI seja menos restritivo em relação ao lock e permita que mais código seja executado concorrentemente, a solução é mais pesada quanto à utilização de recursos do lado do cliente. A solução W JDBC partilha o mesmo objecto result set utilizando cursores cujo peso é insignificante. Um cursor no W JDBC não é muito mais do que uma classe que possui uma referência para um ResultSet e um inteiro que guarda a linha actual do result set. Já a solução CJDBCI constrói um cache por cursor. Com a excepção da alocação de um cursor do servidor, isso é semelhante ao M SJDBC em que se constrói n result sets para n threads. À medida em que a quantidade de threads aumenta é cada vez mais penoso criar um cache para cada cursor de cada thread, o que se reflete numa perda de desempenho. É na Leitura que se nota mais o peso de criar os vários caches. A situação onde existe mais equilı́brio é na inserção, existindo até uma ligeira vantagem para CJDBCI que ronda os 2 a 6% para quantidades de threads maiores. Na inserção a 78 interacção com o servidor é mı́nima, não existe o fetch do result set, apenas são inseridos dados do cliente no servidor. O que se verifica no W JDBC para quantidades de threads maiores é que existe menos código concorrente a ser executado, pois a solução W JDBC bloqueia o acesso ao objecto result set quando inicia o processo de inserção. Daı́ surgir a vantagem para o CJDBCI . A solução CJDBCS e a W JDBC em geral têm um desempenho semelhante, o que até era espectável pois o princı́pio de funcionamento de ambos é o mesmo: existe um objecto utilizado para operar sobre o dataset e que é partilhado pelos threads. A diferença encontra-se no local onde é realizado o acesso exclusivo ao objecto partilhado: num nı́vel mais alto para o W JDBC, e num nı́vel mais baixo para o CJDBCS . Obtendo o lock num nı́vel mais alto não há tanta sensibilidade para o distinguir o código que é concorrente do que não é concorrente; todo o código executado depois do lock é executado não concorrentemente, e por isso pode acontecer estar-se a diminuir o desempenho. Obtendo o lock num nı́vel mais baixo há a possibilidade de se identificarem as zonas do código que têm de ser executadas com acesso exclusivo e que não podem ser executadas concorrentemente (zona crı́tica), efectuando apenas o lock nessas zonas existe mais código concorrente. E com mais código concorrente obtemos um aumento de desempenho. Apesar da proximidade de desempenho de ambas as soluções, nota-se uma vantagem em favor da solução CJDBCS . E é no contexto da Leitura que se verifica uma clara superioridade de CJDBCS em relação a W JDBC. A razão está relacionada com o nı́vel mais baixo de implementação dos locks e com a construção especial desta solução, em que se sabe que o cache tem todas as linhas do dataset, e por isso comunica menos com o servidor, aumentando o desempenho. O tempo de preparação do CJDBCS é mais alto porque esta solução cria o cache partilhado na instanciação, e o W JDBC só pede as linhas do dataset quando precisa delas, ou seja, em execução (o que também reduz o desempenho de W JDBC na execução, principalmente no contexto da Leitura). 6.1.3 Comparação com atrasos O efeito que a introdução de atraso provocou na comparação dos resultados entre as soluções M SJDBC e CJDBC é quase inexistente, pelo que se pode dizer que a introdução de atraso afecta as soluções na mesma proporção, levando à obtenção de resultados semelhantes à situação de ausência de atraso. Em relação à solução W JDBC notou-se uma perda desempenho enorme, resultado que já era esperado principalmente na operação de actualização (e inserção também). O W JDBC bloqueia o acesso ao objecto do result set quando começa o processo de actualização de uma linha, e só o desbloqueia quando o processo termina. Com a introdução de atraso os threads mantêm o lock durante mais tempo diminuindo a concorrência, e com o resultado prático de 79 diminuir muito o desempenho. Ao contrário do que se pensava inicialmente, afinal a soma das partes é maior do que o todo. Embora se introduza 8 vezes um atraso entre colunas, o atraso total verificado entre linhas usando o valor 8 × atrasoC é bastante menor. A tı́tulo de curiosidade o tempo total da realização do benchmark do atraso entre colunas foi de mais de 12h30 e o tempo total da realização do benchmark do atraso entre linhas foi de cerca de 6h30. O facto de a introdução de atraso entre colunas provocar mais atraso, também evidenciou mais diferenças entre os resultados na ausência de atrasos e os resultados com atraso. 6.1.4 Cache individual vs Cache partilhado Desde o inı́cio do trabalho previu-se que a implementação de cache partilhado viria sofrer num contexto com múltiplos threads, pois numa situação em que os threads trabalhem em linhas diferentes o cache estaria continuamente a ser alterado. Este benchmark veio a confirmar esse raciocı́nio. O CJDBCS M tem um desempenho inferior ao de CJDBCI . Do fetch size de 10% para 20% nota-se uma perda de desempenho do CJDBCS . Isto acontece porque com um cache maior, a sua actualização torna-se numa operação mais pesada, e o maior número de linhas não chega para compensar a sua actualização. Para o fetch size 50% e 75% nota-se uma muito ligeira recuperação do CJDBCS M , e para 100% ambos (CJDBCI e CJDBCS M ) têm desempenhos próximos para quantidades de threads mais baixas. Este dado vem contribuir para a justificação da assunção de que a implementação do cache partilhado deveria guardar todas as linhas no cache. Existe mais um aspecto importante a referir. Embora para o fetch size a 100% o CJDBCS M se tenha aproximando do CJDBCI , ainda ficou aquém. Mas pelo que se tinha visto na comparação com o M SJDBC, o CJDBCS tinha melhor desempenho do que o CJDBCI . Isto aconteceu porque teve que ser criada uma nova versão do CJDBCS que suportasse diferentes tamanhos de fetch size. A versão oficial da implementação do cache partilhado está optimizada para tirar partido do facto de o cache conter todas as linhas, e não ser necessário verificar se uma linha requisitada se encontra em cache. Nos resultados da comparação da comparação entre CJDBCI e CJDBCS verifica-se uma grande superioridade do CJDBCS , o que comprova que o cache partilhado deve conter a totalidade das linhas do result set em cache. 6.2 Conclusão Este trabalho provou que existem soluções que permitem um acesso concorrente aos serviços da API JDBC, nomeadamente para acesso ao ResultSet. As soluções encontradas não só fornecem um mecanismo que garante um estado correcto do ResultSet num ambiente com múltiplos threads, como reduz a utilização e desperdı́cio de recursos no cliente e no servidor. O desempenho das soluções construı́das a pensar numa execução concorrente superou o 80 desempenho da solução que cria um result set para cada entidade concorrente (thread). Face aos resultados de CJDBCS , CJDBCI e W JDBC conclui-se que se houver memória disponı́vel no cliente para conter todo o result set, deve-se utilizar a solução CJDBCS , diminuindo assim a quantidade de tráfego de rede e diminuindo também a dependência sobre os cursores do servidor, que se sabe serem menos eficientes do que utilizar um result set sem cursor [16, 71]. Caso não seja possı́vel guardar na memória do cliente todo o result set, a escolha da solução recai para o W JDBC se se pretender o máximo de desempenho. Porém, a solução CJDBCI também garante um desempenho superior a JDBC. Se tivermos em conta os resultados do benchmark que simula alguma actividade entre operações sobre o ResultSet, então conclui-se que a solução W JDBC deve ser evitada pois o seu desempenho sofreu um decréscimo enorme, apresentando um desempenho pior do que JDBC. 6.3 Trabalho relacionado O jTDS é um driver JDBC 3.0 open-source do tipo 4 para Microsoft SQL Server (6.5, 7, 2000, 2005 e 2008) e Sybase (10, 11, 12, 15) [68]. É baseado no projecto FreeTDS (implementação em C do protocolo TDS [6]) e implementa quase a totalidade da especificação 3.0 da JDBC [67]. Quanto à concorrência suporta somente a execução concorrente de Statements. A solução ResultSet Wrapper (W JDBC) teve como inspiração o trabalho realizado por Óscar Narciso Mortágua Pereira, Rui Luı́s Andrade Aguiar e Maribel Yasmina Campos Alves Santos, apresentado no artigo ”Assessment of a Enhanced ResultSet Component for Accessing Relational Databases”[82]. A ideia principal é a mesma: encapsular um result set, controlando o acesso concorrente a ele, através da criação de cursores cliente. Na solução desse artigo a salvaguarda do contexto é realizada centralmente na entidade EResultSet. Basicamente ela possui uma memória que é indexada pelo identificador do cursor, e que para cada posição tem guardada a linha do result set para esse cursor. Se se verificar que o cursor activo foi alterado, é realizada a salvaguarda do contexto do cursor anterior e restaurado o contexto do novo. O W JDBC é diferente no sentido que a gestão do contexto é realizado de modo distribuı́do, cabendo a cada cursor guardar o seu contexto, apenas a verificação de alteração do cursor cliente é realizada centralmente. O W JDBC é assim uma implementação mais flexı́vel e escalável, pois no EResultSet tem que ser definido um tamanho para a memória que guarda os contextos. 6.4 Trabalho Futuro Foi apenas implementada uma pequena porção da API JDBC, daı́ a utilização do driver produzido tem que acontecer com restrições (por exemplo, só alguns tipos de dados SQL são suportados). Por isso, de modo a permitir a utilização do driver num ambiente de produção, devem ser adicionadas mais funcionalidades. 81 O trabalho apresentado neste documento está essencialmente centrado na implementação concorrente da interface ResultSet. No entanto, a API JDBC possui muitas outras que podem beneficiar de um estudo que leve a uma implementação concorrente. Por exemplo as interfaces Statement e PreparedStatement. Um outro aspecto em que se pode trabalhar no futuro é em melhorar a implementação do TDS, e para isso seria importante saber as zonas crı́ticas ao nı́vel do desempenho. Para descobrir essas zonas crı́ticas colocar-se-iam pontos de benchmark no funcionamento interno do driver. Assim em vez de se saber quanto tempo demora a realizar uma tarefa complexa, passa-se a ter a noção do tempo que cada unidade que a constitui leva a completar a sua função. A avaliação das unidades permitiria melhorar a construção das que se revelassem menos eficientes. 82 Glossário ambiente multihreaded Aplicação que executa várias tarefas simultaneamente utilizando threads separados para cada tarefa. 10, 20 Application Programming Interface (API) Conjunto de regras e especificações que estabelecem o modo como um software disponibiliza as suas funcionalidades. 1, 3 batch Conjunto de uma ou mais statements Transact-SQL enviadas ao SQL Server para execução. 15, 19 boilerplate Este termo quando aplicado a código-fonte refere-se a código que pode ser reutilizado sem sofrer alterações. 28 bulk insert Método eficiente de preenchimento de uma tabela, invocado por um cliente num servidor. 101 cache hit ratio Percentagem de acesso à cache em que o elemento procurado é lá encontrado. 28 classpath Lista com os directórios e ficheiros jar, utilizada pela Java Virtual Machine para encontrar classes e pacotes Java. 5, 29 Data Manipulation Language Linguagem que define comandos para actualizar, inserir e remover informação num base de dados. 16 database engine Serviço principal responsável pelas tarefas de armazenamento, gestão e segurança dos dados [39]. 2, 3, 13 Database Management System (DBMS) Sistema que permite criar, gerir e utilizar uma base de dados. 4, 5, 13 dataset Conjunto de dados, normalmente apresentados numa forma tabular. 1, 6, 28, 29, 33, 76, 79 fetch Pedido e carregamento de linhas de um dataset. 25, 27, 76, 79 garbage collector Thread que corre em background numa aplicação Java e que liberta a memória de objecto que já não estejam a ser utilizados. 40 83 Java Virtual Machine Máquina virtual capaz de executar bytecode Java. 3, 4 lock Bloqueio do acesso a um objecto partilhado, permitindo acesso exclusivo à entidade que mantêm o bloqueio. 22, 25, 27, 78, 79 Open Database Connectivity (ODBC) Interface de software standard para aceder a um DBMS. 4 overhead Processamento adicional requirido para executar uma determinada tarefa. 29 override Reimplementação de um método de uma superclasse realizada por uma das suas subclasses. 26, 28 query Pedido de informação a uma base de dados ou a um sistema de informação. 4 Relational Database Management System (DBMS) Sistema que permite criar, gerir e utilizar uma base de dados relacional. 2, 3 Remote Procedure Call (RPC) Procedimento executado num sistema remoto. No âmbito das bases de dados significa a invocação de um stored procedure. 28, 76 ResultSet Interface Java do pacote java.sql que permite operar sobre o resultado da execução de uma statement SQL. 13 statement SQL String com uma expressão numa linguagem que o servidor entende. 2, 6, 13, 16, 17, 19, 23, 25, 34 stored procedure Sub-rotina constituı́da por comandos T-SQL, disponı́vel num sistema de base de dados relacional. 1, 101, 107 Tabular Data Stream Protocolo utilizado na comunicação entre a aplicação cliente e o SQL Server. 1, 13, 14, 24, 115 User Defined Function (UDF) Função criada pelo utilizador que pode ser utilizada em instruções SQL. 101 84 Acrónimos API Application Programming Interface CJDBC Designação genérica para a implementação concorrente do ResultSet CJDBCI Designação para a solução concorrente do ResultSet com cache individual CJDBCS Designação para a solução concorrente do ResultSet com cache partilhado CLI Client Level Interface DBMS Database Management System JVM Java Virtual Machine M SJDBC Designação para a solução que cria um ResultSet por thread ODBC Open Database Connectivity RBMS Relational Database Management System RPC Remote Procedure Call SQL Structured Query Language TDS Tabular Data Stream UDF User Defined Function W JDBC Designação para a solução ResultSet Wrapper 85 86 Bibliografia [1] Inc. Advanced Micro Devices. chine environment. Optimizing java performance in a virtual ma- http://developer.amd.com/documentation/articles/pages/ optimizingjavainvmenvironment.aspx, 2009. [2] Scott W. Ambler. The object-relational impedance mismatch. http://www.agiledata. org/essays/impedanceMismatch.html, 2009. [3] Malcolm P. Atkinson and O. Peter Buneman. Types and persistence in database programming languages. ACM Computing Surveys, 19:105–190, 1988. [4] Malcolm P. Atkinson, Laurent Daynès, Mick J. Jordan, Tony Printezis, and Susan Spence. An orthogonally persistent java, 1996. [5] Malcolm P. Atkinson and Ronald Morrison. Orthogonally persistent object systems. The Vldb Journal, 4:319–401, 1995. [6] Brian Bruns. Freetds. http://www.freetds.org/, 2011. [Online; accessed May-2011]. [7] Jian Chen and Qiming Huang. Eliminating the impedance mismatch between relational systems and object-oriented programming languages. In in Proce. the 6th International Hong Kong Database Workshop, 1995. [8] William R. Cook and Ali H. Ibrahim. Integrating programming languages & databases: What’s the problem? 2005. [9] Microsoft Corporation. Microsoft R SQL Server R 2008. http://www.microsoft.com/ sqlserver/2008/en/us/. [10] Microsoft Corporation. Microsoft SQL Server JDBC Driver 3.0. http://www.microsoft.com/downloads/en/details.aspx?FamilyID= a737000d-68d0-4531-b65d-da0f2a735707, April 2010. [Online; accessed September2010]. [11] Microsoft Corporation. Tabular Data Stream Protocol Specification. http://msdn. microsoft.com/en-us/library/dd304523(PROT.13).aspx, 2010. November-2010]. 87 [Online; accessed [12] Microsoft Corporation. All headers rule definition. http://msdn.microsoft.com/ en-us/library/cc448573.aspx, 2011. [Online; accessed May-2011]. [13] Microsoft Corporation. Browse mode. http://msdn.microsoft.com/en-us/library/ aa936959(SQL.80).aspx, 2011. [Online; accessed May-2011]. [14] Microsoft Corporation. Building the connection url: Sql server 2008. http://msdn. microsoft.com/pt-pt/library/ms378428.aspx, 2011. [Online; accessed May-2011]. [15] Microsoft Corporation. Cursor Concurrency (Database Engine). http://msdn. microsoft.com/en-us/library/ms191493.aspx, 2011. [Online; accessed May-2011]. [16] Microsoft Corporation. Cursor implementations. http://msdn.microsoft.com/en-us/ library/ms189546(v=SQL.100).aspx, 2011. [Online; accessed May-2011]. [17] Microsoft Corporation. Cursor stored procedures. http://msdn.microsoft.com/ en-us/library/ms187801.aspx, 2011. [Online; accessed May-2011]. [18] Microsoft Corporation. Cursor types (database engine). http://msdn.microsoft.com/ en-us/library/ms188644(v=SQL.100).aspx, 2011. [Online; accessed May-2011]. [19] Microsoft Corporation. Cursors (Database Engine). http://msdn.microsoft.com/ en-us/library/ms191179(v=SQL.100).aspx, 2011. [Online; accessed May-2011]. [20] Microsoft Corporation. Cursors (Transact-SQL). http://msdn.microsoft.com/en-us/ library/ms181441(v=SQL.100).aspx, 2011. [Online; accessed May-2011]. [21] Microsoft Corporation. Data types (transact-sql). http://msdn.microsoft.com/ en-us/library/ms187752.aspx, 2011. [Online; accessed May-2011]. [22] Microsoft Corporation. Deprecated database engine features in sql server 2008 r2. http: //msdn.microsoft.com/en-us/library/ms143729.aspx, 2011. [23] Microsoft Corporation. Dynamic Cursors (Database Engine). http://msdn.microsoft. com/en-us/library/ms189099(v=SQL.100).aspx, 2011. [Online; accessed May-2011]. [24] Microsoft Corporation. Fast Forward-only Cursors (Database Engine). http://msdn. microsoft.com/en-us/library/ms187502(v=SQL.100).aspx, 2011. [Online; accessed May-2011]. [25] Microsoft Corporation. Fetching and Scrolling. http://msdn.microsoft.com/en-us/ library/ms187881(v=SQL.100).aspx, 2011. [Online; accessed May-2011]. [26] Microsoft Corporation. Forward-only Cursors (Database Engine). http://msdn. microsoft.com/en-us/library/ms178033(v=SQL.100).aspx, 2011. [Online; accessed May-2011]. 88 [27] Microsoft Corporation. Keyset-driven Cursors (Database Engine). http://msdn. microsoft.com/en-us/library/ms179409.aspx, 2011. [Online; accessed May-2011]. [28] Microsoft Corporation. Login7. http://msdn.microsoft.com/en-us/library/ dd304019(v=PROT.13).aspx, 2011. [Online; accessed May-2011]. [29] Microsoft Corporation. Nbcrow. http://msdn.microsoft.com/en-us/library/ dd304783(v=PROT.13).aspx, 2011. [Online; accessed May-2011]. [30] Microsoft Corporation. Odbc–open database connectivity overview. http://support. microsoft.com/kb/110093/en-us, 2011. [Online; accessed May-2011]. [31] Microsoft Corporation. Packet data token stream definition. http://msdn.microsoft. com/en-us/library/dd340794(v=PROT.13).aspx, 2011. [Online; accessed May-2011]. [32] Microsoft Corporation. Packet header: Type. http://msdn.microsoft.com/en-us/ library/dd304214(v=PROT.13).aspx, 2011. [Online; accessed May-2011]. [33] Microsoft Corporation. setresponsebuffering method (sqlserverstatement). http:// msdn.microsoft.com/en-us/library/bb879939(v=SQL.100).aspx, 2011. [Online; accessed May-2011]. [34] Microsoft Corporation. Setting the connection properties: Sql server 2008. http:// msdn.microsoft.com/pt-pt/library/ms378988.aspx, 2011. [Online; accessed May2011]. [35] Microsoft Corporation. sp cursor (transact-sql). http://msdn.microsoft.com/en-us/ library/ff848759.aspx, 2011. [Online; accessed May-2011]. [36] Microsoft Corporation. sp cursorclose (transact-sql). http://msdn.microsoft.com/ en-us/library/ff848800.aspx, 2011. [Online; accessed May-2011]. [37] Microsoft Corporation. sp cursorfetch (transact-sql). http://msdn.microsoft.com/ en-us/library/ff848736.aspx, 2011. [Online; accessed May-2011]. [38] Microsoft Corporation. sp cursoropen (transact-sql). http://msdn.microsoft.com/ en-us/library/ff848737.aspx, 2011. [Online; accessed May-2011]. [39] Microsoft Corporation. Sql server database engine (sql server 2008). http://msdn. microsoft.com/en-us/library/ms187875(v=SQL.100).aspx, 2011. [40] Microsoft Corporation. SQLServerResultSet Members. http://msdn.microsoft.com/ en-us/library/ms378188(v=SQL.100).aspx, 2011. [Online; accessed May-2011]. [41] Microsoft Corporation. Sqlserverstatement class. http://msdn.microsoft.com/en-us/ library/ms378995(v=SQL.100).aspx, 2011. [Online; accessed May-2011]. 89 [42] Microsoft Corporation. Static cursors (database engine). http://msdn.microsoft.com/ en-us/library/ms191286.aspx, 2011. [Online; accessed May-2011]. [43] Microsoft Corporation. Understanding Cursor Types. http://msdn.microsoft.com/ en-us/library/ms378405(v=SQL.100).aspx, 2011. [Online; accessed May-2011]. [44] Microsoft Corporation. Using adaptive buffering. http://msdn.microsoft.com/en-us/ library/bb879937(v=SQL.100).aspx, 2011. [Online; accessed May-2011]. [45] Oracle Corporation. Pjama. http://labs.oracle.com/forest/opj.main.html, 2000. [46] Oracle Corporation. JSR-000221 JDBC 4.0. http://jcp.org/aboutJava/ communityprocess/final/jsr221/index.html, 2006. [Online; accessed September2010]. [47] Oracle Corporation. Java Platform Standard Ed. 6 - Package java.sql. http:// download.oracle.com/javase/6/docs/api/java/sql/package-summary.html, 2010. [Online; accessed April-2011]. [48] Oracle Corporation. Types of JDBC technology drivers. http://java.sun.com/ products/jdbc/driverdesc.html, 2010. [Online; accessed November-2010]. [49] Oracle Corporation. Class AtomicInteger. http://download.oracle.com/javase/1. 5.0/docs/api/java/util/concurrent/atomic/AtomicInteger.html, 2011. [Online; accessed April-2011]. [50] Oracle Corporation. Class reentrantlock. http://download.oracle.com/javase/6/ docs/api/java/util/concurrent/locks/ReentrantLock.html, 2011. [Online; ac- cessed May-2011]. [51] Oracle Corporation. Interface connection. http://download.oracle.com/javase/6/ docs/api/java/sql/Connection.html, 2011. [Online; accessed May-2011]. [52] Oracle Corporation. Jdbc overview. http://www.oracle.com/technetwork/java/ overview-141217.html, 2011. [Online; accessed May-2011]. [53] Oracle Corporation. veloper guides. Jdk 6 java database connectivity (jdbc)-related apis & de- http://download.oracle.com/javase/6/docs/technotes/guides/ jdbc/, 2011. [Online; accessed May-2011]. [54] Oracle Corporation. Package java.sql. http://download.oracle.com/javase/6/docs/ api/java/sql/package-summary.html, 2011. [Online; accessed May-2011]. [55] Oracle Corporation. Package javax.sql. http://download.oracle.com/javase/6/ docs/api/javax/sql/package-summary.html, 2011. [Online; accessed May-2011]. 90 [56] Oracle Corporation. System.gc() (java platform se 6). http://download.oracle. com/javase/6/docs/api/java/lang/System.html#gc(), 2011. [Online; accessed May2011]. [57] D. Crocker and P. Overell. [RFC] Augmented BNF for Syntax Specifications: ABNF. http://www.ietf.org/rfc/rfc4234.txt, 2005. [Online; accessed November-2010]. [58] Maydene Fisher, Jon Ellis, and Jonathan Bruce. JDBC API Tutorial and Reference (Third Edition). Addison Wesley, 2003. [59] Jim Gray, editor. The Benchmark Handbook for Database and Transaction Systems (2nd Edition). Morgan Kaufmann, 1993. [60] Roedy Green. Garbage collection: Java glossary. http://mindprod.com/jgloss/ garbagecollection.html, 2011. [Online; accessed May-2011]. [61] Brian Göetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, and Doug Lea. Java Concurrency In Practice. Addison-Wesley Professional, 2006. [62] Qiming Huang and Jian Chen. Eliminating the impedance mismatch between relational systems and object-oriented programming language. 1995. [63] Sybase Inc. TDS 5.0 Functional Specification. http://www.sybase.com/content/ 1040983/Sybase-tds38-102306.pdf, 2006. [Online; accessed November-2010]. [64] Wikimedia Foundation Inc. Tabular Data Stream. http://www.enotes.com/topic/ Tabular_Data_Stream, 2010. [Online; accessed November-2010]. [65] Wikipedia Foundation Inc. JDBC Driver. http://en.wikipedia.org/wiki/JDBC_ driver, 2010. [Online; accessed November-2010]. [66] The jTDS Project. System stored procedures (jtds documentation). http://jtds. sourceforge.net/apiCursors.html. [Online; accessed December-2010]. [67] The jTDS Project. jtds feature matrix. http://jtds.sourceforge.net/features. html, 2011. [Online; accessed May-2011]. [68] The jTDS Project. jTDS JDBC Driver. http://jtds.sourceforge.net/, 2011. [Online; accessed May-2011]. [69] Easysoft Limited. What is odbc? http://www.easysoft.com/developer/interfaces/ odbc/linux.html#what_is_odbc, 2011. [Online; accessed May-2011]. [70] Alonso Marquez, Stephen Blackburn, Gavin Mercer, and John N. Zigman. Implementing orthogonally persistent java. In Workshop on Persistent Object Systems, pages 247–261, 2000. 91 [71] Brad McGehee. Performance tuning sql server sql-server-performance.com/tips/cursors_p1.aspx, cursors. http://www. January 2007. [Online; accessed May-2011]. [72] Linda Null and Julia Lobur. The Essentials of Computer Organization and Architecture. Jones and Bartlett Publishers, 2003. [73] Scott Oaks and Henry Wong. Java Threads, Third Edition. O’Reilly Media, 2004. [74] Onstrategies.com. tional databases. Evans data rates popularity of rela- http://www.onstrategies.com/CURRENT-NEWS/ Evans-Data-Rates-Popularity-of-Relational-Databases.html, 2008. [75] Mahmoud Parsian. JDBC Recipes: A Problem-Solution Approach (Problem-Solution Approach). Apress, Berkely, CA, USA, 2005. [76] David A. Patterson and John L. Hennessy. Computer Organization and Design: The Hardware/Software Interface (Third Edition). Morgan Kaufmann, 2007. [77] George Reese. Database Programming with JDBC & Java, Second Edition. O’Reilly Media, 2001. [78] Inc. Sybase. Embeddedsql. http://www.sybase.com/products/archivedproducts/ embeddedsql, 2011. [79] ISO/IEC. Information technology. Database languages - sql - part 3: Call-level interface (sql/cli). technical report 9075-3:1995. ISO/IEC, 1995. [80] Inc. Unicode. The unicode consortium. http://unicode.org, 2011. [Online; accessed December-2010]. [81] Robert Vieira. Professional SQL Server 2005 Programming. Wrox, 2006. [82] Óscar Pereira, Rui Aguiar, and Maribel Santos. Assessment of a enhanced resultset component for accessing relational databases. 2010. 92 Apêndice A Estudo do SQLServerResultSet Aquando da criação do objecto statement é definido o tipo de result set que é criado. Esse tipo determina o modo como os dados são carregados do servidor; podem ser usados cursores de servidor ou não, podem ser carregados todos os dados de uma só vez ou podem ser carregados conforme a aplicação vai os vai requisitando. Nesta secção é apresentado um estudo da classe SQLServerResultSet, que corresponde à classe do driver da Microsoft que implementa a interface java.sql.ResultSet. O estudo concentra-se no modo de interacção do ResultSet com o SQL Server. A.1 Cursores no SQL Server As operações numa base de dados relacional actuam sobre um conjunto de linhas que satisfazem a cláusula WHERE de uma statement, no entanto muitas aplicações precisam de trabalhar com blocos mais pequenos ou até com uma linha de cada vez. Os cursores fornecem esse mecanismo, permitindo [19]: • Posicionamento numa linha especı́fica. • Acesso a uma linha ou um bloco de linhas a partir da localização actual no result set. • Modificar (actualizar, remover) linhas. • Diferentes nı́veis de visibilidade às modificações realizadas por outros no result set. • Acesso ao result set a partir de Transact-SQL em scripts, stored procedures e triggers. A.1.1 Fetching e Scrolling A operação de obter uma linha a partir do cursor designa-se por fetch. Um cursor é classificado quanto ao tipo de fetch que suporta [25]: • Forward-only As linhas são obtidas sequencialmente da primeira até à última. 93 • Scrollable Qualquer linha pode ser obtida em qualquer direcção. Um cursor Forward-only suporta a seguinte operação de fetch: • FETCH NEXT Obtém a próxima linha. Um cursor Scrollalble suporta as seguintes operações de fetch: • FETCH NEXT Obtém a próxima linha. • FETCH FIRST Obtém a primeira linha. • FETCH PRIOR Obtém a linha anterior. • FETCH LAST Obtém a última linha. • FETCH ABSOLUTE n Obtém a linha n a partir da primeira linha. • FETCH RELATIVE n Obtém a linha n a partir da linha actual. A.1.2 Concorrência O SQL Server suporta 4 tipos de concorrência [15]: • READ ONLY A actualização usando o cursor não é permitida e não são obtidos locks nas linhas do result set. • OPTIMISTIC WITH VALUES Não são obtidos locks nas linhas. Quando uma actualização ocorre os valores actuais das colunas da linha são comparados com os valores anteriormente carregados, se forem iguais procede-se à actualização, caso contrário é lançado um erro. • OPTIMISTIC WITH ROW VERSIONING A tabela a actualizar tem que possuir uma coluna do tipo timestamp. Numa actualização valores de timestamp são comparados para determinar se a linha foi alterada ou não por outros, e em caso negativo os novos valores são guardados. 94 • SCROLL LOCKS O cursor lê a linha obtendo um update lock. Se o cursor for aberto no decorrer de uma transacção o lock mantém-se até ocorrer um commit ou um roll-back. Se o cursor for aberto fora de uma transacção o lock da linha é liberto quando for obtida uma outra linha. A.1.3 Tipos de cursor O SQL Server suporta os seguintes tipos de cursor [18]: Forward-only [26] Não suporta scrolling, as linhas são obtidas sequencialmente da primeira para a última. As linhas só são carregadas quando são pedidas. As modificações provenientes das operações de inserção, actualização e remoção, realizadas pelo próprio ou por outros são visı́veis. O SQL Server implementa uma versão designada por Fast Forward-only, com optimização de desempenho [24]. Static [42] Quando o cursor é aberto é criada uma cópia do result set na base de dados tempdb. Uma vez que trabalha com uma cópia, através deste tipo de cursor as modificações não são visı́veis. Também é conhecido como cursor insensitive e cursor snapshot. Keyset-driven [27] São usadas chaves para aceder às linhas da tabela. A tabela tem que possuir uma ou mais linhas que permitam identificar unicamente uma linha. O keyset (conjunto de chaves) é criado como uma tabela na base de dados tempdb. As modificações, próprias ou externas, nas colunas que não são chave são visı́veis, mas as inserções externas não são visı́veis. Dynamic [23] Todas as modificações são visı́veis. Os valores e a ordem das linhas pode ser alterada em cada fetch. Este tipo de cursor é o que tem mais baixo desempenho, principalmente para maiores quantidades de dados, e têm também problemas de concorrência porque em cada fetch o result set é reconstruı́do, e por isso em geral deve-se evitar a sua utilização [81]. No entanto, para quantidades de dados pequenas o result set trabalha a partir da RAM, senso nesta situação mais rápido do que o keyset, que trabalha a partir do disco (este utiliza uma tabela temporária na tempdb) [81]. A.2 Tipos de cursor por result set As caracterı́sticas requisitadas ao result set determinam a interacção que o SQLServerResultSet tem com o servidor. A Tabela A.1 mostra que tipo de cursor de servidor é criado para cada caracterı́stica do result set [43]. A API JDBC permite definir os requisitos do result set 95 quanto à navegabilidade (forward-only ou scrollable) e à concorrência (actualizável ou só de leitura), mas o SQLServerResultSet suporta ainda mais uma caracterı́stica, buffering, que é indicada na coluna com o mesmo nome na Tabela A.1 e explicada na secção A.3. Tabela A.1: Tipos de cursor suportados pelo driver Tipo Cursor N/A Caracterı́stica Forward-only read-only Buffering full TYPE FORWARD ONLY / CONCUR READ ONLY N/A Forward-only read-only adaptive TYPE FORWARD ONLY / CONCUR READ ONLY Fast Forward Forward-only read-only N/A TYPE FORWARD ONLY / CONCUR UPDATABLE Dynamic Forwardonly Forward-only updatable N/A TYPE SCROLL INSENSITIVE Static Scrollable read-only N/A TYPE SCROLL SENSITIVE / CONCUR READ ONLY Keyset Scrollable read-only N/A TYPE SCROLL SENSITIVE CONCUR UPDATABLE / CONCUR SS SCROLL LOCKS CONCUR SS OPTIMISTIC CC CONCUR SS OPTIMISTIC CCVAL Keyset Scrollable updatable. N/A TYPE FORWARD ONLY / CONCUR READ ONLY 96 Descrição Permite apenas uma passagem, da primeira até à última linha, pelo result set. Este é o comportamento por pré-definição. O driver lê todo o result set para a memória quando a statement é executada. Permite apenas uma passagem, da primeira até à última linha, pelo result set. O driver lê as linhas do result set conforme vão sendo pedidas, minimizando a memória gasta pelo cliente. Permite apenas uma passagem, da primeira até à última linha, pelo result set usando o cursor do servidor. As linhas são carregadas em blocos com o tamanho fetch size. Permite apenas uma passagem, da primeira até à última linha, pelo result set, permitindo também a actualização das linhas. As linhas são carregadas em blocos com o tamanho fetch size. O result set não é modificável, e as modificações externas não são visı́veis. As linhas são carregadas em blocos com o tamanho fetch size. As actualizações externas são visı́veis, as remoções aparecem como dados inexistentes e as inserções externas não são visı́veis. As linhas são carregadas em blocos com o tamanho fetch size. As actualizações internas e externas são visı́veis, as remoções aparecem como dados inexistentes e as inserções externas não são visı́veis. As linhas são carregadas em blocos com o tamanho fetch size. Tabela A.1: Tipos de cursor suportados pelo driver Tipo Cursor N/A Caracterı́stica Forward-only read-only Buffering full or adaptive TYPE SS SERVER CURSOR FORWARD ONLY Fast Forward Forward-only N/A TYPE SS SCROLL STATIC Static As actualizações externas não são reflectivdas. N/A TYPE SS SCROLL KEYSET / CONCUR READ ONLY Keyset Scrollable read-only N/A TYPE SS SCROLL KEYSET / CONCUR UPDATABLE CONCUR SS SCROLL LOCKS CONCUR SS OPTIMISTIC CC CONCUR SS OPTIMISTIC CCVAL Keyset Scrollable updatable. N/A TYPE SS SCROLL DYNAMIC / CONCUR READ ONLY Dynamic Scrollable read-only N/A TYPE SS DIRECT FORWARD ONLY 97 Descrição Fornece um cursor no cliente que não permite modificações e cujos dados do result set podem ser todos carregados na execução da statement. Não é criado uma cursor no servidor. Acede rapidamente todos os dados usando um cursor no servidor. Permite modificações de for usado com CONCUR UPDATABLE. As linhas são carregadas em blocos com o tamanho fetch size. É possı́vel usar adaptive buffering se o método setResponseBuffering da classe SQLServerStatement for explicitamente invocado com o argumento ”adaptive”. Esta opção é equivalente a TYPE SCROLL INSENSITIVE. As linhas são carregadas em blocos com o tamanho fetch size. As actualizações externas são visı́veis, as remoções aparecem como dados inexistentes e as inserções externas não são visı́veis. Esta opção é equivalente a TYPE SCROLL SENSITIVE. As linhas são carregadas em blocos com o tamanho fetch size. As actualizações internas e externas são visı́veis, as remoções aparecem como dados inexistentes e as inserções não são visı́veis. Esta opção é equivalente a TYPE SCROLL SENSITIVE. As linhas são carregadas em blocos com o tamanho fetch size. As actualizações e inserções externas são visı́veis, e as remoções aparecem como dados inexistentes. As linhas são carregadas em blocos com o tamanho fetch size. Tabela A.1: Tipos de cursor suportados pelo driver Tipo TYPE SS SCROLL DYNAMIC / CONCUR UPDATABLE CONCUR SS SCROLL LOCKS CONCUR SS OPTIMISTIC CC CONCUR SS OPTIMISTIC CCVAL Cursor Dynamic Caracterı́stica Scrollable updatable Buffering N/A Descrição As actualizações e inserções internas e externas são visı́veis, e as remoções aparecem como dados inexistentes. As linhas são carregadas em blocos com o tamanho fetch size. O SQLServerResultSet para além de suportar os tipos de result set definidos pela interface ResultSet, adiciona alguns tipos que permitem requisitar explicitamente tipos especı́ficos que existem no SQL Server, tanto ao nı́vel da navegação (forward-only, scrollable), como ao nı́vel do tipo de concorrência [40]: • CONCUR SS OPTIMISTIC CC Leitura e escrita com concorrência optimı́stica (row versioning) e sem locks de linha. • CONCUR SS OPTIMISTIC CCVAL Leitura e escrita com concorrência optimı́stica (values) e sem locks de linha. • CONCUR SS SCROLL LOCKS Leitura e escrita com concorrência optimı́stica e com locks de linha. • TYPE SS DIRECT FORWARD ONLY Cursor do tipo fast forward-only, só de leitura. • TYPE SS SCROLL DYNAMIC Cursor do tipo dynamic. • TYPE SS SCROLL KEYSET Cursor do tipo keyset. • TYPE SS SCROLL STATIC Cursor do tipo static. • TYPE SS SERVER CURSOR FORWARD ONLY Cursor do tipo fast forward-only, só de leitura. A.3 Adaptive Buffering O adaptive buffering é uma funcionalidade introduzida no Microsoft SQL Server 2005 JDBC Driver versão 1.2, que tem como finalidade o carregamento de grandes quantidades de dados sem a necessidade de utilizar cursores do servidor [44]. O que isto significa é que existem dois modos de carregamento de dados: adaptive e full. No modo adaptive é carregada a menor quantidade possı́vel de dados, enquanto que no modo full todo o result set é lido do servidor em run time. 98 O acesso a esta funcionalidade é realiza pela utilização do método setResponseBuffering [33] da classe SQLServerStatement [41], que recebe uma String (full ou adaptive) que indica o modo desejado. A classe SQLServerStatement é a implementação da interface java.sql.Statement. A principal motivação é diminuir a quantidade de memória utilizada pela aplicação, e até evitar situações em que a memória esgota e é lançado o erro OutOfMemoryError, quando a aplicação JDBC trabalha com queries que produzem resultados muito grandes. 99 100 Apêndice B Tabular Data Stream O Tabular Data Stream (TDS) é um protocolo da camada aplicação utilizado para transferir informação entre um servidor de base de dados e um cliente [11, 63]. Foi desenhado e desenvolvido em 1984 pela Sybase Inc. para ser utilizado no servidor SQL da empresa, e mais tarde foi também desenvolvido pela Microsoft para ser utilizado no Microsoft SQL Server [64]. B.1 Mensagens Como qualquer protocolo de rede, o TDS efectua a comunicação usando troca de mensagens. Existem duas categorias de mensagens: mensagens do cliente e mensagens do servidor. Resumidamente as mensagens do cliente são: Pre-login Handshake que tem de ocorrer antes do login, e que configura alguns parâmetros tais como a encriptação. Login Mensagem que inicia o estabelecimento da comunicação com o servidor. Como resposta, o servidor informa o cliente se aceitou ou rejeitou o pedido de comunicação. SQL Command Mensagem que na zona de dados contém um comando SQL ou batch de comandos SQL, representado numa String codificada em Unicode [80]. SQL Command com Dados Binários Mensagem que faz um pedido de execução de uma operação bulk insert usando um comando SQL seguido de dados binários. O comando também é representado numa String codificada em Unicode. Remote Procedure Call (RPC) Mensagem que faz um pedido de execução de um stored procedure ou uma UDF. A mensagem contém o nome, opções e parâmetros do RPC. 101 Attention signal Mensagem que cancela a execução de um comando. Resumidamente as mensagens do servidor são: Pre-login response Resposta a uma mensagem de pre-login. Login response Resposta a uma mensagem de login. Contém informação sobre as caracterı́sticas do servidor, informação opcional e mensagens de erro. Row data Resposta com os dados devolvidos pela execução de um comando. Esta mensagem é precedida por uma descrição dos nomes das colunas e dos tipos de dados. Return status Resposta com o valor do estado de um RPC. Também é usada para enviar o estado do resultado da execução de uma instrução T-SQL. Return parameters Resposta com os valores dos parâmetros de saı́da de um RPC. Response completion Resposta que indica o fim de um conjunto de resultados. Error e Info Resposta que transmite mensagens de erro ou mensagens informativas. Attention Acknowledgment Resposta que confirma a recepção de um cancelamento de execução de um comando. B.2 Pacotes Cada mensagem é constituı́da por um ou mais pacotes. Cada pacote tem um tamanho máximo cujo valor é determinado na mensagem de login. Todos os pacotes da mensagem excepto o último têm de ter um tamanho igual ao valor do tamanho máximo negociado. Cada pacote é constituı́do por um cabeçalho (packet header ) e por uma zona de dados (packet data). B.2.1 Cabeçalho 102 Corresponde aos primeiros 8 bytes do pacote. Os campos do cabeçalho estão representados na seguinte tabela. O valor em cima de cada campo indica o número de bytes desse campo. Tabela B.1: Campos do cabeçalho TDS 1 1 2 2 1 1 Type Status Length SPID PacketId Window Descrição dos campos do cabeçalho: Type Define o tipo de mensagem. Status Indica o estado da mensagem (por exemplo, indica se a mensagem terminou). Length Indica o tamanho do pacote (incluindo o tamanho do cabeçalho). SPID Identifica o ID do processo no servidor correspondente à ligação actual. Este campo tem carácter opcional, pelo que nesta implementação será sempre enviado o valor 0x0000. PacketID Indica o número do pacote. Cada pacote enviado incrementa este valor em uma unidade. Window Actualmente não é utilizado, por isso tem sempre o valor 0x00. NOTA: Todos os valores são representados em network byte order (big-endian) e são valores sem sinal. B.2.2 Zona de dados Todos os tipos de mensagens, exceptuando a Attention signal, a seguir ao cabeçalho têm uma zona de dados [32]. NOTA: A zona de dados também pode ser denominada de data stream ou apenas stream. Os pacotes tem um tamanho máximo, cujo valor é determinado no login. O tamanho do pacote inclui o tamanho do cabeçalho. Se uma mensagem produzir um pacote que ultrapasse o tamanho definido, terá de ser dividida por múltiplos pacotes. Cada um desses pacotes terá um cabeçalho semelhante, exceptuando os campos Status e Length. O campo Status terá o valor 0x0 se houverem mais pacotes da mensagem e terá o valor 0x1 se o pacote é o último da mensagem. O campo 103 Length terá um valor igual ao tamanho definido, para todos os pacotes excepto para o último da mensagem. Existem dois tipos de zonas de dados: Token Stream e Tokenless Stream. Um token stream é constituı́do por um ou mais tokens, em que cada um deles é seguidos pelos dados relativos ao token. Um tokenless stream contém directamente os dados da mensagem, sem recorrer a tokens para os descrever. Na tabela a seguir temos um resumo das mensagens que usam tokens e as que não usam. Tabela B.2: Indicação das mensagens que usam tokens Mensagem Origem Token Pre-Login Cliente Não Login Cliente Não SQL Batch Cliente Não Bulk Load Cliente Sim Remote Prodecure Call Cliente Sim Attention Cliente Não Transaction Manager Request Cliente Não Pre-Login Response Servidor Não Login Response Servidor Sim Row Data Servidor Sim Return Status Servidor Sim Return Parameters Servidor Sim Response Completion Servidor Sim Error and Info Messages Servidor Sim Attention Acknowledgment Servidor Não A definição da gramática dos streams (token e tokenless) é especificada usando Augmented Backus-Naur Form [57]. B.3 Tokenless Streams Um tokenless stream contém directamente os dados da mensagem, sem recorrer a tokens para os descrever. A seguir é descrito o formato da zona de dados, das mensagens com tokenless streams. B.3.1 Pre-Login O stream desta mensagem é constituı́do por uma sequência de opções seguidas dos dados relativos a essas opções. Cada opção tem três campos: Type, Position e Length. Type 104 identifica a opção, Position indica a posição que a opção ocupa nos dados e Length indica o número de bytes que opção ocupa nos dados. Tabela B.3: Opções da mensagem de Pre-Login Opção Valor Descrição VERSION 0x00 Versão do remetente. Normalmente usado para debugging. ENCRYPTION 0x01 Negociar encriptação. INSTOPT 0x02 Nome da instância do SQL Server. THREADID 0x03 Id do thread da aplicação cliente. Usado para debugging. TERMINATOR 0xFF Assinala o fim da mensagem de Pre-Login. Do que se conseguiu apurar apenas VERSION e ENCRYPTION são obrigatórios, e uma vez que as restantes opções de momento são irrelevantes, os pacotes de Pre-Login do driver só irão conter estas duas opções. Para além disso, ainda não será considerada a utilização de encriptação. B.3.2 Login Este stream define as regras de autenticação entre o cliente e o servidor. O seu tamanho não deve ultrapassar os 128-1 bytes. A definição deste stream possui várias regras que podem ser consultadas em [28], das quais se destacam a OffsetLength e a Data. Estas duas regras definem os parâmetros concretos do login, tais como a base de dados a utilizar ou o nome de utilizador. A regra Data possui os bytes que representam os dados dos parâmetros e a regra OffsetLength define a posição e comprimento de cada parâmetro. B.3.3 SQLBatch Este stream define o formato de uma mensagem SQL Batch. A definição deste stream é composta por uma regra ALL HEADERS1 seguida de um stream em Unicode que contém o comando SQL. B.4 Token Streams As mensagens mais complexas (por exemplo, os dados do result set) são construı́das usando tokens. Um token consiste num byte que funciona como identificador, seguido de dados especı́ficos ao token. Existem os seguintes tokens [31]: 1 Alguns streams TDS podem ser precedidos de vários cabeçalhos. A regra ALL HEADERS é utilizada para especificar esses cabeçalhos[12]. 105 Tabela B.4: Packet Data Token Streams Nome ALTMETADATA ALTROW COLINFO COLMETADATA DONE DONEINPROC DONEPROC ENVCHANGE ERROR INFO LOGINACK NBCROW OFFSET ORDER RETURNSTATUS RETURNVALUE ROW SSPI TABNAME TVP ROW Descrição Descreve o tipo de dados, tamanho e nome da coluna que resulta de uma SQL Statement que gera totais. Usado para enviar uma linha com totais, cujo formato é descrito pelo token ALTMETADATA. Descreve a informação da coluna em Browse Mode [13], sp cursoropen e sp cursorfetch. Descreve o result set para interpretação dos tokens ROW que lhe seguem. Indica que uma SQL Statement foi terminada. Indica que uma SQL Statement de um stored procedure foi terminada. Indica que um stored procedure terminou. Notificação de uma alteração de ambiente (por exemplo, base de dados, lı́ngua). Usado para enviar uma mensagem de erro ao cliente. Usado para enviar uma mensagem de informação ao cliente. Usado para enviar ao cliente a resposta a um pedido de login. A ausência deste token numa resposta de login significa que o login no servidor não foi realizado com sucesso. Usado para enviar ao cliente uma linha definida pelo token COLMETADATA com compressão null bitmap (mais informações em [29]. Usado para informar o cliente da posição onde uma palavra-chave ocorre num SQL text buffer do próprio cliente. Este token foi removido no TDS 7.2. Usado para informar o cliente que colunas determinam a ordem dos dados. Usado para enviar ao cliente o valor do estado de um RPC. Usado para enviar ao cliente o valor de retorno de um RPC. Usado para enviar ao cliente uma linha completa, que foi anteriormente definida por um token COLMETADATA. Token SSPI devolvido durante o processo de login. Usado para enviar ao cliente o nome da tabela quando sp cursoropen é utilizado ou quando em browser mode. Usado para enviar uma linha table value parameter (TVP), do cliente para o servidor. 106 Apêndice C Cursor Stored Procedures Existem instalados no SQL Server um conjunto de stored procedures que permitem a operação de um cursor sobre um dataset [17, 66]. Este é um tema que não está directamente relacionado com a descrição do protocolo TDS, mas do ponto de vista da implementação de um driver JDBC, estes dois temas estão intimamente ligados. A mensagem de rpc request do TDS possui um campo em que se pode indicar um número de um stored procedure, esse stored procedure é um dos que se podem encontrar em [17] e cujo número identificador se pode encontrar na respectiva documentação. Estes stored procedures são o elemento fundamental na implementação de um result set scrollable e/ou updatable. Na Tabela C.1 são apresentados os stored procedures importantes para a implementação do driver. A coluna procId corresponde ao identificador do stored procedure. Tabela C.1: Stored prodecures do sistema relevantes para a implementação do driver JDBC. procId Nome Descrição 1 sp cursor Permite efectuar actualização, inserção ou remoção de uma ou mais linhas do fetch buffer do cursor. 2 sp cursoropen Abre um cursor definindo a statement SQL a ele associada e suas opções. 7 sp cursorfetch Carrega uma ou mais linhas para o buffer do cursor. Este buffer designa-se de fetch buffer. 9 sp cursorclose Fecha e liberta os recursos associados ao cursor. A seguir será apresentada e explicada a sintaxe de cada um dos stored procedures aqui enunciados. C.1 sp cursor Mais informações sobre este stored procedure podem ser encontradas em [35]. 107 C.1.1 Sintaxe sp_cursor cursor, optype, rownum, table [ , value [...n] ] ] C.1.2 Argumentos cursor Identificador do cursor gerado pelo SQL Server na execução do sp cursor. optype Identifica a operação a executar: Valor Operação Descrição 0x0001 UPDATE Actualiza uma ou mais linhas indicadas por rownum. 0x0002 DELETE Remove uma ou mais linhas indicadas por rownum. 0x0004 INSERT Insere dados. 0x0008 REFRESH Volta a preencher o fetch buffer com os dados das tabelas. 0x10 LOCK Provoca a aquisição de um SQL Server U-Lock nas páginas que contêm a linhas especificadas. 0x20 SETPOSITION Pode ser usado numa cláusula OR com REFRESH, UPDATE, DELETE ou LOCK para mudar a posição do cursor para a última linha modificada. 0x40 ABSOLUTE Pode ser usado numa cláusula OR com UPDATE ou DELETE para modificar a linha indicada por rownum, e cujo valor é referente ao data set criado pela SQL statement em vez de se referir ao fetch buffer. rownum Especifica a linha do fetch buffer sobre a qual se irá realizar a operação. table Nome da tabela sobre a qual será realizada a operação. Relevante quando a statement SQL involve um join. value String em Unicode que indica os valores de actualização/inserção. C.2 sp cursoropen Mais informações sobre este stored procedure podem ser encontradas em [38]. 108 C.2.1 Sintaxe sp_cursoropen cursor OUTPUT, stmt [, scrollopt [ OUTPUT ] [ , ccopt [ OUTPUT ] [ ,rowcount OUTPUT [ ,boundparam] [,...n] ] ] ] ] C.2.2 Argumentos cursor Identificador do cursor gerado pelo SQL Server na execução do sp cursor. stmt SQL statement que define o result set do cursor. scrollopt Indica o tipo de cursor criado: Valor Descrição 0x0001 KEYSET 0x0002 DYNAMIC 0x0004 FORWARD ONLY 0x0008 STATIC 0x10 FAST FORWARD 0x1000 PARAMETERIZED STMT 0x2000 AUTO FETCH 0x4000 AUTO CLOSE 0x8000 CHECK ACCEPTED TYPES 0x10000 KEYSET ACCEPTABLE 0x20000 DYNAMIC ACCEPTABLE 0x40000 FORWARD ONLY ACCEPTABLE 0x80000 STATIC ACCEPTABLE 0x100000 FAST FORWARD ACCEPTABLE ccopt Indica o tipo de concorrência do cursor criado: 109 Valor Descrição 0x0001 READ ONLY 0x0002 SCROLL LOCKS 0x0004 OPTIMISTIC 0x0008 OPTIMISTIC 0x2000 ALLOW DIRECT 0x4000 UPDT IN PLACE 0x8000 CHECK ACCEPTED OPTS 0x10000 READ ONLY ACCEPTABLE 0x20000 SCROLL LOCKS ACCEPTABLE 0x40000 OPTIMISTIC ACCEPTABLE 0x80000 OPTIMISITC ACCEPTABLE rowcount Número de linhas do fetch buffer a ser usado pelo AUTO FETCH. boundparam Significa o uso de parâmetros adicionais. sp cursorfetch C.3 Mais informações sobre este stored procedure podem ser encontradas em [37]. C.3.1 Sintaxe sp_cursorfetch cursor [ , fetchtype [ , rownum [ , nrows ] ] ] C.3.2 Argumentos cursor Identificador do cursor gerado pelo SQL Server na execução do sp cursor. fetchtype Especifica que o buffer que deve ser carregado: 110 Valor Nome Descrição 0x0001 FIRST Carrega o primeiro buffer de nrows linhas. 0x0002 NEXT Carrega o próximo buffer de nrows linhas. 0x0004 PREV Carrega o antecessor buffer de nrows linhas. 0x0008 LAST Carrega o último buffer de nrows linhas. 0x10 ABSOLUTE Carrega nrows linhas a partir da linha rownum. 0x20 RELATIVE Carrega nrows linhas começando na linha rownum. 0x80 REFRESH Recarrega o buffer com os dados das tabelas. 0x100 INFO Obtém informação acerca do cursor. 0x200 PREV NOADJUST Usado como PREV, mas ao contrário de PREV, esta opção não preenche o buffer com linhas que se encontram na actual posição ou depois da actual posição do cursor. 0x400 SKIP UPDT CNCY Quando usado os valores timestamp das colunas não são escritos na tabela keyset quando uma linhas é (re)carregada. Tem que ser utilizado em conjunção com umas das outras opções à excepção de INFO. rownum Usado para especificar a linha de ABSOLUTE ou RELATIVE. nrow Especifica o número de linhas que devem ser carregadas pela operação. Por pré-definição tem o valor 20. sp cursorclose C.4 Mais informações sobre este stored procedure podem ser encontradas em [36]. C.4.1 Sintaxe sp_cursorclose cursor C.4.2 Argumentos cursor Identificador do cursor gerado pelo SQL Server na execução do sp cursoropen. 111 112 Apêndice D Funcionalidade implementada Este anexo apresenta uma lista da funcionalidade da API JDBC implementada pelo driver. Fica assim uma referência para a utilização do driver. D.1 Implementação JDBC As Tabelas D.1, D.2 e D.3 apresentam as interfaces do JDBC implementadas. Tabela D.1: Métodos implementados da interface Driver Nome boolean acceptsURL(String url) Connection connect(String url, Properties info) int getMajorVersion() int getMinorVersion() boolean jdbcCompliant() Tabela D.2: Métodos implementados da interface Statement Nome void addBatch(String sql) void clearBatch() void close() int[] executeBatch() ResultSet executeQuery(String sql) int executeUpdate(String sql) 113 Tabela D.3: Métodos implementados da interface ResultSet Nome boolean absolute(int row) void afterLast() void beforeFirst() void cancelRowUpdates() void close() void deleteRow() boolean first() Date getDate(int columnIndex) double getDouble(int columnIndex) int getFetchSize() int getInt(int columnIndex) int getRow() String getString(int columnIndex) void insertRow() boolean isAfterLast() boolean isBeforeFirst() boolean isClosed() boolean isFirst() boolean isLast() boolean last() void moveToCurrentRow() void moveToInsertRow() boolean next() boolean previous() void refreshRow() boolean relative(int rows) void setFetchSize(int rows) void updateDate(int columnIndex, Date x) void updateDouble(int columnIndex, double x) void updateInt(int columnIndex, int x) void updateRow() void updateString(int columnIndex, String x) boolean wasNull() 114 D.2 Implementação TDS O TDS especifica o formato como o SQL Server recebe e envia um valor, por isso o driver tem de conhecer esse formato para conseguir comunicar com sucesso com o servidor. Como existem vários tipos suportados pelo SQL Server [21] e o driver implementado apenas suporta alguns, a Tabela D.4 apresenta a referência dos tipos suportados. Como o facto de uma coluna permitir ou não valores nulos pode alterar o formato, a segunda coluna da Tabela D.4 indica o suporte relativo a esta caracterı́stica. Tabela D.4: Tipos SQL suportados pelo driver. Nome Null/Not Null INT S/S FLOAT S/S DATETIME S/S NVARCHAR S/S 115