• Introdução ao ZODB

last modified April 4, 2011 by davilima6

Introdução ao ZODB

Uma visão geral do ZODB perante bases relacionais, transações, escalabilidade e melhores práticas.


Comparação com outras bases de dados

Bases de dados relacionais são excelentes para manipular grandes quantidades de dados homogêneos. Se você está desenvolvendo um sistema de contabilidade, um banco de dados relacional é uma ótima escolha. No entanto, bancos relacionais suportam estruturas de dados hierárquicas até um determinado ponto. A utilização de relacionamentos de chaves estrangeiras deve fazer referência a uma tabela única, então somente um único tipo pode ser armazenado.

Bases de dados hierárquicas (como por exemplo um LDAP ou um sistema de arquivos) são muito mais adequadas para modelar as flexíveis hierarquias de contimento necessárias para aplicações de gerência de conteúdo. Porém esses sistemas não suportam verdadeiramente semânticas transacionais.

ORMs como o SQLAlchemy permitem trabalhar com bancos relacionais de uma maneira muito mais prazerosa, orientada a objetos. No entanto, não superam as restrições inerentes ao modelo relacional.

O ZODB é um sistema (quase) transparente para persistência de objetos Python, fortemente influenciado pelo Smalltalk. Como uma base de dados orientada a objetos, ele permite a flexibilidade de construir um modelo de dados compatível com a sua aplicação. Na maior parte do tempo, você não precisará se preocupar com a persistência das informações - você apenas trabalhará com objetos Python e a persistência simplesmente funcionará.

Claro que tanto poder vem com um preço. Embora alterar os métodos das suas classes não seja um problema, alterar atributos pode obrigar a escrita de um script de migração, como você faria no caso de uma mudança de um esquema relacional. No entanto, tais migrações de esquema não são impostas explicitamente para objetos do ZODB, o que pode trazer problemas mais tarde.

 

Transações

O ZODB é um sistema transacional em seu núcleo. As transações fornecem controle de concorrência e atomicidade.

As transações são executadas como se tivessem acesso exclusivo aos dados, de modo que o desenvolvedor de uma aplicação não precise se preocupar com threading. Naturalmente, não existe nada que previna duas solicitações simultâneas e conflitantes, então são feitas verificações no momento do commit da transação a fim de garantir consistência.

Desde o Zope 2.8, o ZODB implementa controle de concorrência multi-versionado. Isso significa o fim de erros do tipo ReadConflictErrors, pois cada transação tem a garantia de ser capaz de carregar qualquer objeto como ele era quando a transação iniciou.

Você pode ainda ver alguns erros do tipo ConflictErrors (de escrita). Esses podem ser minimizados utilizando estruturas de dados que suportem resolução de conflitos, primariamente B-Trees provenientes da biblioteca BTrees. Essas estruturas de dados escaláveis são usadas em Pastas Gigantes do Plone e muitas partes do Zope. Uma desvantagem é a falta de suporte para ordenamento definido pelo usuário. (N.T.: isso mudou no Plone 4)

Os lugares de maior probabilidde para ConflictErrors são os índices do catálogo. Alguns dos índices não suportam resolução de conflitos e você verá ConflictErrors durante cargas intensas de operações de escrita. Uma solução é adiar as atualizações do catálogo utilizando o QueueCatalog (PloneQueueCatalog no collective), que permite que operações de indexação sejam serializadas utilizando um cliente ZEO separado. Isso pode trazer grandes ganhos de performance à medida que novas tentativas de solicitações são reduzidas, mas a desvantagem é que atualizações de índices deixam de ser imediatamente refletidas na aplicação.

Isso nos traz a Atomicidade, a outra característica chave das transações do ZODB. Uma transação resultará sempre em sucesso ou falha, seus dados nunca são deixados em um estado inconsistente se um erro ocorre. Isso faz do Zope um sistema permissivo para se trabalhar.

No entanto, você deve ser cuidadoso com interações com sistemas externos. Se um ConflictError ocorre, o Zope tentará reexecutar uma transação até 3 vezes. Interações com um sistema externo devem ser feitas através de um Data Manager que participa na transação. Se você estiver conversando com uma base de dados, use um Zope DA ou um invólucro para o SQLAlchemy, por exemplo o collective.lead.

Infelizmente a implementação padrão do MailHost utilizada pelo Plone não conhece nada sobre transações. Com ela você pode ver emails duplicados sendo enviados. Caso isso se torne um problema, utilize TransactionalMailHost.

 

Escalabilidade

O Python é limitado a uma única CPU pelo Global Interpreter Lock, mas tudo bem, pois o ZEO permite rodar múltiplos servidores de aplicações que compartilham uma única base de dados. Você deve rodar um cliente Zope para cada processador no seu servidor. O ZEO permite ainda conectar uma sessão de debug à sua base de dados concomitantemente ao seu servidor web Zope, funcionalidade inestimável para depuração de erros.

O ZEO tende a ser IO bound, de forma que o GIL não faz diferença.

O ZODB também oferece suporte a particionamento, permitindo distribuir suas informações ao longo de múltiplos armazenamentos. No entanto, você deve ser cuidadoso em relação a referências cruzadas entre as bases de dados (especialmente ao copiar e colar entre duas bases de dados) porque elas podem ser problemáticas.

Outro motivo comum para usar particionamento é porque as configurações de cache de memória do ZODB são feitas por base de dados. Separar o catálogo em outro armazenamento permite configurar um maior tamanho de cache de destino para os objetos do catálogo do que para os objetos de conteúdo. Como a interface do Plone gira bastante em torno do catálogo, essa técnica pode trazer um benefício de performance significante, especialmente em sites de grande porte.

A typical ZEO setup on a four core server

 

Tipos de armazenamento

O armazenamento em arquivo, conhecido como FileStorage, é o padrão. Tudo fica em um grande arquivo Data.fs, que é essencialmente um registro das transações. Utilize-o a não ser que você tenha um motivo muito bom.

O DirectoryStorage produz um arquivo para cada revisão de objeto. Não necessita que o Data.fs.index seja reconstruído no caso de um desligamento sujo (o que pode levar um bom tempo em bases de dados grandes). Funciona melhor para um número pequeno de usuários.

O PGStorage é novo e interessante, já que elimina a necessidade de usar o ZEO. Embora a camada de rede do PostgreSQL é provavelmente mais rápida do que o ZEO, o PGStorage move a resolução de conflitos para o servidor de aplicações, o que pode ser ruim para a performance de piores casos. Não há muitos relatos de utilização em servidores em produção.

BDBStorage, OracleStorage e APE são outras alternativas mais ou menos deixadas de lado pela comunidade.

 

Outras características

Savepoints (anteriormente conhecidos como sub-transações) permitem o controle de erros granularizado e a coleta objetos como lixo durante um transação, economizando assim memória.

Versões está fora de uso (deprecated). A camada de aplicação é responsável pelo versionamento, por exemplo, CMFEditions ou ZopeVersionControl.

Undo, não se apóie nele! No caso de objetos indexados, pode ser impossível desfazer a transação (independentemente) se uma transação posterior atualizou o mesmo índice. O undo é somente executado em uma única base de dados, então se você separou seu catalogo, ele ficará fora de sincronia. Funciona bem para desfazer ações dentro de  portal_skins/custom, no entanto.

BLOBs são novidades do ZODB 3.8 / Zope 2.11 que trazem suporte eficiente a grandes arquivos. Excelente para aplicações de gerenciamento de documentos.

Compactar (packing) é o ato de remover revisões antigas dos objetos. É similar ao VACUUM do PostgreSQL.

 

Algumas melhores práticas

  • Não escreva durante leituras. Seu Data.fs não deve crescer durante uma leitura. Fique atento ao método setDefault e evite migrações inplace.
  • Mantenha seu código no sistema de arquivos. Apesar de ser bastante conveniente para cobrir prazos, armazenar objetos demais na pasta custom não trará mais do que sofrimento durante a longa vida de um projeto.
  • Utilize estruturas de dados escaláveis como por exemplo as BTrees.
  • Mantenha seus objetos de conteúdo simples, adicionando funcionalidades por meio de adaptadores e views.
 

Mais documentação