SENADO FEDERAL Secretaria Especial do Interlegis - SINTER Subsecretaria de Tecnologia da Informação - SSTIN RELATÓRIO DE PROJETO versão 1.0 Documento de Projeto do Sistema 1.Termo de Referência Esse relatório diz respeito ao edital número 46, “OBJ-REL - Camada de Persistência Objeto-Relacional”, publicado entre os dias 04 a 11 de maio de 2008, a ser realizado no período de maio/2008 a outubro/2008. 2.Introdução De acordo com o descrito no termo de referência desse projeto, os frameworks utilizados pelo Interlegis permitem o armazenamento de informações tanto em banco de dados relacionais (PostgreSQL e MySQL) quanto em banco de dados orientados a objetos (ZODB). De forma geral, quando trata-se de representar informações dentro do Plone (utilizando-se o framework Archetypes), a persistência dos dados costuma ser orientada a objetos (OO) e realizada diretamente no banco de dados orientado a objetos ZODB. Há algumas soluções do Interlegis como, por exemplo, o SAPL (Sistema de Apoio ao Processo Legislativo), que podem ser consideradas soluções “híbridas”, armazenando informações em diferentes modelos (parte das informações em banco de dados relacional e parte em banco de dados orientado a objetos). No entanto, o SAPL não utiliza o framework Archetypes e, por sua vez, não está relacionado com esse projeto. Esse relatório tratará apenas das considerações relevantes ao primeiro caso, o mapeamento de informações objeto-relacional. Nesse caso, as informações que normalmente seriam armazenadas em bancos de dados OO precisam ser mapeadas para um banco de dados relacional. 3.Considerações Gerais Apesar de bancos de dados puramente OO serem tecnicamente excelentes, sabe-se que a adoção de bancos de dados relacionais tem sido a regra geral. Isso se deve a muitos motivos, mas principalmente ao fato dos bancos de dados relacionais serem o reflexo de mais de 20 anos de pesquisas, terem uma forte fundamentação teórica e acima de tudo, a disponibilidade de muitos fabricantes de bancos de dados relacionais com excelentes ferramentas para as mais diversas plataformas. Esse cenário “relacional” como “padrão de fato” acaba sempre se refletindo na necessidade de mapear a representação de informações OO para modelos relacionais. O framework Archetypes (sobre o qual o Plone é definido) adota como padrão uma representação de informações OO e persistência direta no ZODB. No entanto, ainda em versões bastante antigas do framework Archetypes foi implementado um mapeamento objeto-relacional conhecido como SQLStorage. Essa implementação mapeava conteúdos do Plone apenas para o banco de dados PostgreSQL e teve adoção relativamente restrita, limitada a poucos desenvolvedores. Apesar dessa implementação ser considerada até hoje como a implementação oficial de mapeamento objeto-relacional, é sabido que ela sofre de um problema crônico de performance. 1 SENADO FEDERAL Secretaria Especial do Interlegis - SINTER Subsecretaria de Tecnologia da Informação - SSTIN Para compreender o restante desse relatório e as decisões tomadas durante o projeto é importante esclarecer alguns conceitos. A principal consideração a ser feita e que impacta diretamente na escalabilidade e performance dos sistemas ditos “transacionais” (entenda-se aqui por “sistemas transacionais” aqueles sistemas gerenciais mais comuns, como folha de pagamento, contabilidade, financeiro, etc, onde existe um volume considerável de dados sendo processados de forma transacional) é que esse tipo de sistema nunca foi o alvo principal de plataformas de servidor de aplicação web como o Zope (servidor de aplicação sobre o qual o Plone é escrito). Apesar de ser possível desenvolver sistemas transacionais sob a plataforma Zope, em sistemas assim existe uma quantidade considerável de transações de escrita nos banco de dados e essa quantidade costuma ser bastante superior a que encontraríamos em uma aplicação web padrão (como um portal de uma Câmara Municipal) e para o qual o Plone foi planejado. Em uma aplicação web característica existe certamente um relação maior de “leituras x escritas” se comparada a aplicações transacionais. Nesse sentido, o ZODB, camada de persistência OO do Zope, tem características em sua arquitetura que o fazem superior quando implementado em aplicações web, mas que acabam por prejudica-lo quando o mesmo é implementado em aplicações transacionais. Dentre essas características de projeto do ZODB, sabe-se que o banco de dados como um todo foi planejado para ter melhor performance quando submetido a grandes volumes de leituras e poucas escritas. O mecanismo de tratamento de conflitos de transações implementado pelo ZODB é dito otimista e baseado em timestamps (em versões mais recentes adota “Multiversion Concurrency Control MVCC [1]”). Bancos de dados relacionais geralmente adotam decisões de projetos voltadas para ambientes com alto nível de transações concorrentes e conflitantes. É comum a utilização de tratamento de conflitos de transações pessimistas, através de bloqueios. Essa diferença essencial de arquitetura claramente impacta na performance das soluções escritas com cada tipo de banco de dados. Conhecer as vantagens de cada arquitetura de banco de dados tem sua importância. A performance do SQLStorage é ruim pois desconsidera essas questões e é “agnóstico” em sua implementação. Em teoria, essa implementação poderia ser melhorada adicionando-se uma camada de cache intermediário ao mapeador objeto-relacional. No entanto, como o código do SQLStorage é bastante antigo e por vezes incompleto, não suportando o mapeamento de diversas classes de objetos, optou-se por atender aos requisitos descritos no edital desenvolvendo uma nova camada de mapeamento objeto relacional para o Archetypes. Essa camada será baseada em frameworks específicos de mapeamento objeto-relacional; frameworks esses já existentes e comprovadamente de qualidade. Assim, ao invés de desenvolver todo o mapeador objeto-relacional, esse trabalho estará focado na integração de mapeadores conhecidos com a arquitetura do Archetypes. 4.Limitações Antes de descrever a especificação do mapeador objeto-relacional é importante ressaltar as limitações que a arquitetura Zope impõe a esse tipo de solução. Além das implicações relacionadas com o mecanismo de controle de transações concorrentes, há ainda 2 aspectos importantes a serem considerados. O primeiro aspecto diz respeito a escalabilidade do Plone quando integrado a bancos de dados relacionais, ou de forma mais ampla, o Zope integrado a serviços remotos. O Plone é executado sobre o servidor de aplicações Zope e esse possui um número fixo de threads responsáveis pelo atendimento de requisições. Aceita-se que todas as aplicações desenvolvidas para o Zope e, por sua vez, para o Plone, devem executar tão rápido quanto possível (cada requisição deve ser processada 2 SENADO FEDERAL Secretaria Especial do Interlegis - SINTER Subsecretaria de Tecnologia da Informação - SSTIN em frações de segundo). Isso é indicado não somente pela óbvia necessidade de respostas imediatas por parte do usuário final, mas pelo fato de que requisições que demoram muito para serem processadas mantém as threads do servidor Zope alocadas. Se essa alocação for feita durante alguns segundos, bastariam 4 usuários simultâneos para que tivéssemos a impressão de que o Plone “travou” durante esses poucos segundos. Na verdade não existe realmente um travamento, mas sim uma condição de corrida, onde o browser aguarda que o servidor web responda, mas esse está com todos os seus recursos ocupados. Em nosso caso específico, esses recursos seriam as threads do Zope esperando que o banco de dados responda a uma consulta SQL. Esse tipo de situação pode ser evitada mantendo-se o servidor de banco de dados relacional do qual o Zope depende em um servidor rápido, com carga controlada e acessível através de um conexão de alta velocidade (preferencialmente no mesmo segmento de rede do servidor de aplicações Zope). Outra limitação importante é que ainda é difícil persistir as informações representadas pelo framework Archetypes apenas em banco de dados relacional. Isso se deve a uma limitação do próprio Zope 2, onde as implementações internas da camada da persistência, dos mecanismos de segurança e de herança de conteúdo (conhecida como aquisição) impedem a clara separação do conteúdo e das permissões a ele associadas, assim como dos demais conteúdos acessados durante o “traverse” (acesso ao conteúdo no seu contexto de aquisição). Isso significa que, persistido as informações contidas no “schema” de um objeto Archetypes, essas informações podem ficar armazenadas apenas no banco de dados relacional, mas ainda assim será necessário armazenar um objeto no ZODB. Esse objeto será responsável por manter consistente os mecanismos de segurança e “traverse” do Zope. Para mais informações sobre “acquisition” consulte [2]. Durante o processo de análise de requisitos desse projeto tentou-se investigar sobre a possibilidade de contornar essa limitação e fazer o mapeamento objeto-relacional utilizando apenas o banco relacional, sem a necessidade de persistir informações no ZODB. Essa alternativa mostrou-se ainda incipiente pois, na melhor das hipóteses, isso implicaria em modificações consideráveis no código fonte dos Produtos do Interlegis (algo não desejável e explicitamente descrito no termo de referência onde temos “ser compatível com códigos Archetypes legados”) ou na adoção de código fonte muito instável. 5.Detalhamento do Produto Segundo o termo de referência, temos como objetivo “tornar mais eficiente o mecanismo de armazenamento do Archetypes permitindo considerável melhoria na performance de transações de escrita e leitura no banco de dados”. Ainda segundo o termo de referência, para isso deve-se “projetar e desenvolver uma camada que faça o mapeamento de objetos do banco de dados ZODB, gerados pelo framework Archetypes, para SGBDs relacionais tais como o PostgreSQL e MySQL, utilizando ORMs como o SQLAlchemy ou SQLObject, fazendo com que os objetos gerados possam, arbitrariamente, ser armazenados no ZODB ou em um SGBD relacional ou em ambos”. Como descrito nas considerações gerais, o SQLStorage, implementação padrão de mapeamento objeto-relacional do Archetypes, sofre de um problema crítico de performance. Nesse sentido optou-se pela adoção do ORM SQLAlchemy [3] para mitigar esse problema. O SQLAlchemy implementa internamente um padrão de projeto (“design pattern”) Unidade de Trabalho (“Unit of Work”). Na prática esse será o grande diferencial quando comparado à implementação padrão do SQLStorage pois as operações de escritas passarão a feitas em “batch”. 3 SENADO FEDERAL Secretaria Especial do Interlegis - SINTER Subsecretaria de Tecnologia da Informação - SSTIN No que diz respeito as operações SQL realizadas durante o mapeamento, a implementação interna do SQLStorage realiza para cada operação de escrita (resultado da invocação de qualquer “design pattern setter”) pelo menos 1 select e 1 update (ou 1 insert). Para uma classe Archetypes com 20 campos (“field”), seriam executados pelo menos 40 consultas SQL (e na prática costumam ser mais do que 40, pois não existe otimização interna nas chamadas de métodos “getter e setter”). Usando-se o SQLAlchemy a quantidade de operações será teoricamente diminuída para apenas 1 ou 2 instruções SQL. O ganho de performance desejado será obtido justamente na diminuição de operações SQL realizadas pois, para cada SQL executado o Zope tem que enviar a consulta para o servidor de banco de dados via rede e, mesmo que o servidor de banco de dados responda muito rapidamente, isso certamente será mais lento do que processar apenas algumas operações. Um menor número de operação SQL significa uma economia de tempo na latência de rede e na alocação estática de threads do Zope. O SQLAlchemy também atende ao requisito de permitir que esse mapeamento seja feito para diversos bancos de dados relacionais diferentes. Segundo [4], o SQLAlchemy suporta dialetos para o SQLite, Postgres, MySQL, Oracle, MS-SQL, Firebird, MaxDB, MS Access, Sybase, Informix e DB2 (desde que exista um driver que implemente a DB-API 2.0). Ainda segundo o termo de referência, temos que a camada de mapeamento objetorelacional deverá ser compatível com todos os tipos de dados nativos do Archetypes. A próxima seção especifica como serão feitos esses mapeamentos. 6.Especificação O mapeamento dos diversos tipos de implementado de acordo com a tabela 1. Archetypes dados nativos do Archetypes será SQLAlchemy Observações 1 StringField Text Mapeamento direto. 2 TextField Text Mapeamento direto. 3 LinesField Text Mapeamento direto. 4 BooleanField Boolean Mapeamento direto. 5 DateTimeField Datetime Mapeamento direto. 6 IntegerField Numeric Mapeamento direto. 7 FixedPointField Numeric Mapeamento direto. 8 FloatField Float Mapeamento direto. 9 ComputedField - Campos do tipo ComputedField não precisam ser mapeados pois não são persistidos. O valor de um campo ComputedField é “computado” no momento em que o método “getter” é acessado. 10 ReferenceField - Vide observação #1 e #2. 11 FileField - Vide observação #1 e #3. 12 ImageField - Vide observação #1 e #3. 13 CMFObjectField - “Deprecated”. Recomenda-se o uso de FileField. 14 PhotoField - “Deprecated”. Recomenda-se o uso de ImageField. 15 ObjectField - ObjectField é uma classe base e não deve ser 4 SENADO FEDERAL Secretaria Especial do Interlegis - SINTER Subsecretaria de Tecnologia da Informação - SSTIN utilizada na descrição de “schemas”. Tabela 1. Mapeamento de tipos Observação 1: A implementação padrão de mapeamento objeto-relacional do Archetypes (SQLStorage) não suporta o mapeamento de referências, arquivos (FileField) e imagens (ImageField). Observação 2: A ausência de suporte ao mapeamento de referências se deve a uma modificação na implementação da API de referências realizada depois da implementação do SQLStorage. Devido a essa alteração, os ReferenceField não passam mais através da Interface de “storage”, definida em Archetypes.Storage.StorageLayer. A implementação atual segue a API definida na classe base Archetypes.Referenceable.Referenceable, que terá que sofrer “monkey patches”. Todas as referências serão mapeadas para uma tabela auxiliar chamada references que conterá 3 colunas: source_uid e target_uid, relationship (o campo relationship pode ser nulo). Observação 3: Não é conveniente persistir campos FileField e ImageField em um banco de dados relacional. Campos FileField podem ser arquivos binários grandes e servi-los através do Zope conectado a um banco de dados, apesar de possível seria uma péssima escolha. O mesmo se aplica a campos ImageField, com o agravante que esse tipo de campo armazena imagens em seu tamanho original e diversas cópias da mesma em tamanhos diferentes. A alternativa a ser adotada para o mapeamento desse tipo de informação será a adoção do FileSystemStorage [5]. Persistindo os arquivos e imagens no sistema de arquivos abre-se a possibilidade de servir esses arquivos utilizando um servidor web padrão (solução mais simples e rápida, especialmente para arquivos grandes). O mapeamento objeto-relacional será feito criando uma tabela que descreva o “schema” de cada classe Archetypes. Como regra geral, essa tabela terá tantos campos quanto a classe original Archetypes tinha, de acordo com a Tabela 1, exceto os campos que não precisam ser mapeados e os que tem mapeamento especial (linhas 10 a 12 da Tabela 1). Nessa tabela ainda constarão pelo menos duas colunas adicionais: uid e parent_uid, identificando o UID do objeto e o UID do objeto “aq_parent”, respectivamente (a coluna uid poderá ser “unique”). Além disso, fica convencionado a existência de 1 banco de dados diferente para cada instancia de objeto Plone Site. Por fim, para atender ao requisito definido no termo de referência como “permitir a escolha arbitrária do armazenamento dos dados somente no ZODB, somente no SGBD relacional ou em ambos”, fica convencionado que a configuração referente a onde a persistência será realizada é feita através de um parâmetro de configuração no código fonte do Plone Product responsável pelo mapeamento. 7.Infra-estrutura A implementação a ser desenvolvida deverá ser compatível e devidamente testada com as seguintes versões: Python 2.4.4 Zope 2.9.8 Plone 2.5.5 Archetypes 1.4.6 MySQL 5.0 - Community Server - Generally Available (GA) Release 5 SENADO FEDERAL Secretaria Especial do Interlegis - SINTER Subsecretaria de Tecnologia da Informação - SSTIN PostgreSQL 8.3 Acredita-se que a implementação terá compatibilidade com versões posteriores do Plone e suas dependências (3.0.x e 3.1.x), mas essa compatibilidade só será garantida para as versões listadas acima. Versões antigas do Plone (2.1.x e anteriores) não serão suportadas. 8.Referências [1] Multiversion Concurrency Control – MVCC http://en.wikipedia.org/wiki/Multiversion_concurrency_control [2] Zope Acquisition http://www.zope.org/Documentation/Books/ZDG/current/Acquisition.stx [3] SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper http://www.sqlalchemy.org/ [4] SQLAlchemy – Key Features of SQLAlchemy http://www.sqlalchemy.org/features.html [5] File System Storage http://ingeniweb.sourceforge.net/Products/FileSystemStorage 6