• Nenhum resultado encontrado

Tipos do Hibernate

No documento Java 122 (páginas 47-49)

Dentre os campos armazenados na clas- se CacheKey (veja a Figura 3 da parte 1),

podemos destacar o campo type, que é atribuído a uma implementação de uma interface de mesmo nome. A interface org.

hibernate.type.Type define os contratos

de mapeamento entre o mundo orientado a objetos e o banco dados.

A API do Hibernate apresenta dezenas de implementações de Type e toda enti- dade é invariavelmente associada a um conjunto dos mesmos, de acordo com o tipo de seus campos e relacionamentos, o que permite ao Hibernate gerar corre- tamente as instruções SQL nas operações de CRUD. O Type armazenado em Ca-

cheKey corresponde ao tipo do campo

utilizado como identificador da entidade.

Por exemplo, se o id for um campo do tipo

Long, uma CacheKey para uma instância

da entidade irá referenciar uma instância de org.hibernate.type.LongType.

O que é interessante observar é que todas as instâncias dessas classes de mapeamento são de fato metadados e em sua grande maioria são definidos como singletons pelo Hibernate. Dentre os tipos que não são implementados como singletons, estão os tipos que devem ser parametrizados em tempo de execução (durante o “parsing” das entidadesque ocorre na inicialização da SessionFactory que irá gerenciá-las), como por exemplo, o tipo ManyToOneType, que marca qual

entidade é referenciada por uma anotação

@ManyToOne.

Independentemente de serem ou não sin- gletons, as implementações de um deter- minado Type são mantidas em memória e devem ser de natureza imutável, isto é, uma vez que a SessionFactory é cons- truída, os tipos não devem ser alterados. A estrutura responsável por manter os tipos associados a uma determinada en- tidade é a classe org.hibernate.metadata.

ClassMetadata (CMD), que pode ser pen-

sada como sendo a versão do Hibernate de um descritor de tipo do Java (OSC).

Infelizmente a API de C2N do Hibernate não se aproveita do fato de tipos serem singletons (ou mantidos em memória pela

SessionFactory) quando o assunto é seria-

lização, pois trata seus metadados como objetos comuns. Este ‘descuido’ faz com que o benefício da utilização do design pat-

tern desapareça quando um elemento do C2N é lido a partir do disco, considerando que novos objetos são recriados durante o processo de deserialização. Dentre as im- plicações associadas a essa negligência por parte da API de C2N, podemos destacar dois efeitos colaterais:

• O custo de serialização de tipos é eleva- do. Serializar um LongType requer 883 bytes;

• Quando uma CacheKey migra do disco para a memória, ela fica com uma cópia de um tipo do Hibernate em memória. Para entender por que isto acontece, consideremos um cache que armazena alguns objetos em memória e um número muito maior em disco. Podemos imagi- nar o layout inicial da memória como o representado na Figura 7. Nesta figura, as instâncias de CacheKey guardam valores inteiros e consequentemente referenciam o tipo IntegerType do Hibernate. Os valores associados às chaves com identificadores 1 e 2 são mantidos em memória e os outros valores são mantidos em disco.

Quando se submeter uma leitura do identificador de número 3, o valor cor- respondente que estava em disco migrará para a memória e, se o número máximo de elementos em memória for atingido, al- gum elemento da memória poderá migrar

Figura 6. Garbage Collection: estruturas padrão (esq) e otimizadas (dir).

para o disco. Nesse processo, ilustrado na

Figura 8, a chave que foi deserializada do

disco substituirá a chave em memória, e esta nova instância estará associada a uma também nova instância de IntegerType, que deixará de ser um singleton por estar duplicado na memória.

Assim, se você dimensionou seu cache para manter até um milhão de objetos em memória, eventualmente haverá um des- perdício devido às cópias de tipos. Cada instância dos tipos “simples” (LongType,

IntegerType, etc.) custa cerca de 64 bytes

no Heap. Portanto, se houver um milhão de cópias, estamos falando de cerca de 62MB de desperdício.

Queries

Consultas sofrem os mesmos problemas que discutimos anteriormente e podem ser ainda mais agravados dependendo do número e tipos dos parâmetros utilizados, além do tamanho da String de consulta em si.

No primeiro artigo da série verificamos o custo associado ao se adotar Criteria no lugar de NamedQueries, para o C2N em memória. Enquanto que em memória fica evidenciado um ganho da ordem de 200% ao se empregar NamedQueries com C2N em memória, em disco o ganho é muito menos pronunciado (~20%) devido ao fato das consultas serem serializadas e perder- mos o benefício do cálculo de equals como uma operação de tempo constante – O(1) – quando uma String migra do disco para a memória (ver Figuras 7 e 8). Pelas mesmas razões, a redução de memória que obtemos pela escolha de NamedQueries ao invés de Criteria também é anulada quando o C2N é utilizado em disco.

As Tabelas 2 e 3 mostram o resultado da execução do mesmo teste apresentado na seção “Prefira NamedQueries à Criteria” da parte 1, utilizando no entanto, o C2N em Disco. Nesse caso as otimizações apre- sentam um ganho da ordem de 60%.

otimizando as estruturas do C2n

Conforme observamos nas seções ante- riores, os objetos armazenados no C2N não possuem uma estrutura compacta quando serializados. Em poucas palavras

Figura 8. Layout da Memória pós-migração

HDD Sem Cache Cache em Memória Cache em Disco Cache em Disco, otimizado

Toshiba, mecânico, 5400rpm 6867s 140ms 3115 1984 Corsair Neutron GTX, SSD 6605ms 137ms 3080ms 1957ms

Tabela 2. Microbenchmarks de consultas com NamedQuery utilizando diferentes camadas de armazenamento

HDD Sem Cache Cache em Memória Cache em Disco Cache em Disco, otimizado

Toshiba, mecânico, 5400rpm 6001s 412ms 3715 2285 Corsair Neutron GTX, SSD 5795ms 416ms 3635ms 2260ms

Tabela 3. Microbenchmarks de consultas com Criteria utilizando diferentes camadas de armazenamento

poderíamos dizer que “há muita informa- ção estática sendo armazenada de forma desnecessária”. Como a API do Hibernate não é “plugável” com respeito às classes que são utilizadas, se desejarmos otimizá- las temos três possibilidades:

1. Otimizar o mecanismo de serialização do EhCache. Embora possível, uma solu- ção genérica não teria conhecimento das características específicas das classes do C2N do Hibernate;

2. Criar as classes otimizadas, que não violam os contratos do Hibernate, compilá-las e reempacotá-las no JAR do Hibernate;

3. Utilizar um Java Agent, que intercepta classes do C2N no momento em que são carregadas e as transforma em suas ver- sões otimizadas.

Neste artigo optamos pela terceira opção por ser menos intrusiva e nos permitir

testar diferentes formatos sem ter todo o trabalho de reconstruir um JAR. Antes de discutir como vamos utilizar agente, primeiramente vamos mostrar as transfor- mações que desejamos fazer com relação às classes do C2N. Essas transformações essencialmente envolvem implementar o padrão Flyweight de modo a otimizar o acesso a tipos do Hibernate e descritores de classe. A otimização de descritores é comumente empregada em frameworks de caches distribuídos como o próprio EhCache (quando conectado ao Terracotta) e o Oracle Coherence.

No documento Java 122 (páginas 47-49)

Documentos relacionados