JBoss Seam + DTO + EntityQuery

Outubro 16, 2009 por ranophoenix

Fazer um consulta na base de dados, retornar alguns registros e criar uma tela para pesquisa contendo ordenação e paginação é uma tarefa repetitiva no desenvolvimento de aplicações.

Pensando nisso, o framework JBoss Seam fornece uma classe chamada EntityQuery que, ao ser herdada, fornece todos os métodos necessários para execução e retorno da consulta, ordenação e paginação dos registros. Por exemplo, suponha uma classe persistente chamada EntidadeA, o código abaixo já é mais do que o suficiente para construção de uma tela de exibição e pesquisa:

@Name(“entidadeAList”)

public class EntidadeAList extends EntityQuery<EntidadeA> {

private static final String EJBQL = “select a from EntidadeA a”

private static final String[] RESTRICTIONS = { “lower(a.campoA) like lower(concat(#{entidadeAList.entidade.campoA},’%'))”,}

public EntidadeAList() {

setEjbql(EJBQL);

setRestrictionExpressionStrings(Arrays.asList(RESTRICTIONS));

setMaxResults(10);

}

private EntidadeA entidade A;

//Get e Set para entidadeA

}

Prontinho!!! É só criar sua página de exibição e chamar os métodos disponíveis na classe.

Infelizmente (ou felizmente!?), nem todas as aplicações são tão simples asssim. Desenvolvimento uma dessas aplicações não tão simples, precisei de um DTO para encapsular joins espaciais e outras operações geométricas sobre as entidades persistentes. Segue abaixo o pseudo-código:

@Name(“entidadeDTOList”)

public class EntidadeDTOList extends EntityQuery<EntidadeDTO> {

private static final String EJBQL = “select new meupacote.EntidadeDTO(a.nome, intersection(a.theGeom, b.theGeom)) from EntidadeA a, EntidadeB b”

private static final String[] RESTRICTIONS = {“intersects(a.theGeom, b.theGeom) = #{true}”, “lower(a.campoA) like lower(concat(#{entidadeDTOList.entidade.campoA},’%'))”,}

public EntidadeAList() {

setEjbql(EJBQL);

setRestrictionExpressionStrings(Arrays.asList(RESTRICTIONS));

setMaxResults(10);

}

private EntidadeA entidade A;

//Get e Set para entidadeA

}

Nesse caso, simplesmente herdar de EntityQuery não funcionou. O método de navegação “lastFirstResult” dispara a exceção “invalid select clause for query”.

Investigando mais de perto, abri o código-fonte da super classe Query e observei que o erro estava acontecendo dentro do método getCountEjbql(). O grande problema é que a seguinte expressão regular usada pelo Seam não abrange casos um pouco mais complexos:

SUBJECT_PATTERN = “Pattern.compile(“^select(\\w+((\\s+|\\.)\\w+)*\\s+from”, Pattern.CASE_INSENTIVE)

Como não queria criar um fork do Seam “corrigindo” o pattern, preferi sobrescrever o método na minha classe, que no final ficou parecida com o código abaixo:

@Name(“entidadeDTOList”)

public class EntidadeDTOList extends EntityQuery<EntidadeDTO> {

private static final String EJBQL = “select %s from EntidadeA a, EntidadeB b where intersects(a.theGeom, b.theGeom) = true”

private static final String EJBQL_PROJ = “new meupacote.EntidadeDTO(a.nome, intersection(a.theGeom, b.theGeom))”

private static final String[] RESTRICTIONS = { “lower(a.campoA) like lower(concat(#{entidadeDTOList.entidade.campoA},’%'))”,}

public EntidadeAList() {

setEjbql(String.format(EJBQL, EJBQL_PROJ);

setRestrictionExpressionStrings(Arrays.asList(RESTRICTIONS));

setMaxResults(10);

}

@Override

protected String getCountEjbql() {

String query = getRenderedEjbql();

query = query.replace(EJBQL_PROJ, “count(c)”);

return query;

}

private EntidadeA entidade A;

//Get e Set para entidadeA

}

Agora sim! Funcionando de forma satisfatória, porém não ideal. Acho que o Seam já poderia ter feito uma expressão regular que contemplasse esses casos.

Até a próxima!

Mais uma da série: não perca tempo

Outubro 9, 2009 por ranophoenix

Opa pessoal,

Vou relatar mais uma experiência que pode poupar muito o tempo de vocês quando tiverem trabalhando com a combinação Seam + Eclipse (com JBoss Developer Tools) + JBoss 5.1.

Hoje precisei fazer uma pequena alteração no faces-config.xml de meu projeto e quando dei um seam explode, sem mais nem menos, o JBoss ficava em loop ao tentar fazer o deploy do projeto. Eu fazia um unexplode e quando, para testar, executava um seam deploy, ele conseguia fazer o deploy normalmente. Resolvi investigar e percebi, pela utilização de um controle de versões (Mercurial), que quando eu salvei o projeto utilizando o JBoss Tools XML Editor ele criou outros arquivos:

>hg status
M exploded-archives\sispetroleogas.war\WEB-INF\faces-config.xml
M resources\WEB-INF\faces-config.xml
M test-build\WEB-INF\faces-config.xml
? exploded-archives\sispetroleogas.war\WEB-INF\.faces-config.xml.jsfdia
? resources\WEB-INF\.faces-config.xml.jsfdia
? test-build\WEB-INF\.faces-config.xml.jsfdia

Bingo! Por alguma razão, o arquivo .faces-config.xml.jsfdia criado no WEB-INF do projeto e copiado pelo “seam explode” no processo de build, faz o JBoss se perder e ele fica em loop infinito. Para resolver o problema bastou apagar esses arquivos e para ele não se repetir utilizo agora o XML Editor padrão do Eclipse que não cria nenhum arquivo adicional.

Até a próxima,

Get Feature Info – Novo componente OL4JSF

Outubro 1, 2009 por ranophoenix

Uma típica chamada getfeatureinfo utilizando puramente o OpenLayers é assim:

map.events.register(‘click’, map, function (e) {
document.getElementById(‘nodelist’).innerHTML = “Carregando… aguarde…”;
var params = {
REQUEST: “GetFeatureInfo”,
EXCEPTIONS: “application/vnd.ogc.se_xml”,
BBOX: map.getExtent().toBBOX(),
X: e.xy.x,
Y: e.xy.y,
INFO_FORMAT: ‘text/html’,
QUERY_LAYERS: map.layers[1].params.LAYERS,
FEATURE_COUNT: 50,
Srs: ‘EPSG:4291′,
Layers: ‘topp:campos’,
Styles: ”,
WIDTH: map.size.w,
HEIGHT: map.size.h,
format: ‘image/png’};
OpenLayers.loadURL(“http://localhost:8080/geoserver/wms”, params, this, setHTML, setHTML);
OpenLayers.Event.stop(e);
});

Achou muito código? Que tal utlizar o OL4JSF e deixar o código assim:

<m:getFeatureInfo url=”#{request.contextPath}/JGSProxy/wms” layers=”topp:campos” onCompleteJsFunc=”setHTML” queryLayers=”map.layers[1].params.LAYERS” />

Um código muito mais legível e fácil de manter. ;)

Até a próxima!

Link do projeto:  https://ol4jsf.dev.java.net/

Projeto OL4JSF – Open Layers For JSF

Setembro 8, 2009 por ranophoenix

Olá pessoal,

Tenho o prazer de anunciar a versão inicial do projeto que tem por objetivo criar um conjunto de componentes JSF sobre a biblioteca OpenLayers. Dessa forma, o desenvolvimento da camada de visualização torna-se mais produtivo e menos propício a erros, tendo em vista que é necessário muito menos código javascript na construção das páginas. Além disso, tem um plugin inicial para o Netbeans e o plugin do Eclipse está a caminho.

Site do projeto: https://ol4jsf.dev.java.net/

Abraços,

Pesquisa mostra perfil dos executivos brasileiros

Junho 10, 2009 por ranophoenix

Talvez essa pesquisa seja o reflexo de nosso modelo educacional que é muito mais informativo do que criativo. Em outras palavras, desde pequenos somos muito mais motivados a “decorar” do que realmente pensar e dar a nossa opinião sobre os assuntos, acredito que isso reflita diretamente na nossa capacidade criativa na vida adulta. Isso se confirma até mesmo na universidade, onde percebo que muitos alunos acham as disciplinas onde precisam emitir uma opinião pessoal sobre determinados assuntos muito difíceis. Realmente é uma pena, espero que esse quadro se reverta e que possamos ter profissionais (e pessoas) cada vez mais criativas e com uma maior capacidade de comunicar as suas idéias.

Até mais,

Link para pesquisa: clique aqui

Lançado Grails 1.1

Março 10, 2009 por ranophoenix

Depois de muita expectativa, acaba de ser lançada a nova versão do framework web Grails! Abaixo seguem os links contendo documentação, changelog, etc:

* Changelog:
http://jira.codehaus.org/browse/GRAILS?report=com.atlassian.jira.plugin.system.project:changelog-panel
* Download: http://grails.org/Download .
* Documentação: http://grails.org/doc/1.1
* Notas da versão: http://grails.org/1.1+Release+Notes

Principais características da nova versão:

* Standalone GORM – Use GORM em projetos não Grails
* Suporte ao Ant+Ivy e Maven
* O novo framework de Test (disponível na versão anterior na forma de plugin)
* Plugins melhorados – resolução transitiva de plugin, plugins globais,
plugins locais e mais.
* suporte à JSP Tag Library – GSPs agora podem usar JSP taglibs
* Melhoras no Controller – Controle para evitar submissões repetidas, forwards e
includes e tratamento exceções de forma declarativa
* Suporte Spring Namespace – Use Spring namespaces dentro do BeanBuilder

Tutorial JBoss Seam – Abrir projeto no Eclipse

Janeiro 9, 2009 por ranophoenix

Como algumas pessoas têm entrado em contato comigo com dúvidas de como abrir um projeto seam no Eclipse, segue abaixo um pequeno passo a passo para ter o projeto dentro de usa IDE.

Primeiramente, acesse “File -> Import…”.

Obs: Para aparecer na lista de projetos, você deve selecionar o diretório escolhido para ser o “Java project workspace” (primeira pergunta ao executar o comando seam setup.

Pronto! Agora com o projeto dentro da IDE, torna-se possível utilizar todos os poderosos recursos do Eclipse!

Elipse – JBoss 5

Dezembro 11, 2008 por ranophoenix

Se você é como eu e já fez questão de fazer download do JBoss 5, encontrou alguns problemas no classpath em seu ambiente Eclipse (Ganymede).  Isso aconteceu porque o JBoss mudou a estrutura de diretório contendo diversas bibliotecas.

Como não queria esperar o tempo de fazer o download de uma versão do Eclipse só para corrigir isso, resolvi alterar um dos arquivos de configuração do WTP.

O arquivo é o seguinte:

<DIR INSTALAÇAO DO ECLIPSE>\plugins\org.eclipse.jst.server.generic.jboss_1.5.205.v200805140145\servers\jboss5.serverdef

Segue o  trecho do arquivo com a alteração. A única parte do xml que alterei foi a referente ao classpath (classpath id=”jbossproject”).

<classpath id=”jboss.project” >

<fileset dir=”${serverRootDirectory}”>
<include name=”client/*.jar” />
<include name=”server/default/lib/*.jar” />

<include name=”server/default/deploy/jbossweb.sar/jsf-libs/*.jar” />

<!– This will be ignored if EJB3 is not there 5.0 –>
<include name=”common/lib/*.jar” />
<include name=”lib/*.jar” />
<include name=”server/default/deployers/jbossweb.deployer/*.jar” />
<include name=”server/default/deployers/jboss-aop-jboss5.deployer/*.jar” />
<include name=”server/default/deployers/ejb3.deployer/*.jar” />

<!– for validation to work this library is defined as an archive –>

<exclude name=”common/lib/mail.jar” />

</fileset>

<!– leave one generic jar as an archive so that validation works –>

<archive path=”${serverRootDirectory}/common/lib/mail.jar” />

</classpath>

É issoa aí! Agora é só reiniciar o seu Elipse e adicionar o seu novo servidor em Window -> Preferences -> Server -> Runtime Environments.

Tutorial JBoss Seam

Outubro 21, 2008 por ranophoenix

Olá pessoal!

Resolvi escrever um breve tutorial sobre esse excelente framework Java para desenvolvimento de aplicações JEE. Breve tutorial? É possível fazer alguma coisa em JEE de forma breve?  Sim, com o Seam é possível. Obviamente, não explicarei sobre a arquitetura do framework e, muito menos, sobre a arquitetura JEE.  O nosso objetivo é desenvolver uma aplicação simples para cadastro de fornecedores e produtos. No nosso exemplo, um produto só pode pertencer a um único fornecedor.

DER do nosso pequeno sistema.

DER do nosso pequeno sistema.

Primeiro, você deve baixar o JBoss 4.2.3 GA e o JBoss-seam-2.0.2.SP1  (ver links) e descompactá-los em um diretório de sua escolha. Devo lembrá-lo que é necessário que o JDK 5, ou superior, esteja instalado e configurado no seu PATH.

Entre no diretório onde você descompactou o JBoss e inicie o servidor executando o script run.bat (Windows) ou run.sh (Linux) contido na pasta “Bin”.  Isso aí! Já temos um servidor JEE em execução!

Começaremos o desenvolvimento de nossa aplicação.

Para isso, abra o prompt de comando do seu SO e entre no diretório onde você descompactou o JBoss-seam.  Execute o seguinte comando (digite “seam help” para visualizar todas as opções disponíveis):

> seam setup

Algumas perguntas serão feitas e todas elas têm uma opção default. As respostas são bastante intuitivas. Mas vale ressaltar que o driver jdbc do seu SGBD deve estar no lib do JBoss-seam. Seguem abaixo as respostas que forneci:


Enter your Java project workspace (the directory that contains you Seam projects)
e:/EclipseProjects
Enter your JBoss home directory
c:/jboss-4.2.3.GA
Enter the project name
tutorial_seam
Do you want to use ICEFaces instead of RichFaces [n] (y, [n])
Select a RichFaces skin [blueSky] ([blueSky], classic, ruby, wine, deepMarine, emeraldTown, sakura, DEFAULT)
Is this project deployed as an EAR (with EJB components) or a WAR with no EJB support) [ear] ([ear], war)
Enter the Java package name for your session beans
com.mydomain.sessionbean
Enter the Java package name for your entity beans
com.mydomain.entitybean
Enter the Java package name for your test cases
com.mydomain.test
What kind of database are you using?
postgres
Enter the Hibernate dialect for your database [org.hibernate.dialect.PostgreSQLDialect]
Enter the filesystem path to the JDBC driver jar
../lib/postgresql-8.3-603.jdbc3.jar
Enter JDBC driver class for your database [org.postgresql.Driver]
Enter the JDBC URL for your database
jdbc:postgresql://localhost:5432/tutorial_seam
Enter database username
postgres
Enter database password
*******
Enter the database schema name (it is OK to leave this blank) []
Enter the database catalog name (it is OK to leave this blank) []
Are you working with tables that already exist in the database? [n] (y, [n])
Do you want to drop and recreate the database tables and data in import.sql each time you deploy? [n] (y, [n])

Com isso, temos a configuração necessária para criarmos efetivamente a estrutura de nosso projeto:

> seam create-project

Nesse ponto, já poderíamos abrir esse projeto no Eclipse ou no Netbeans. Mas não iremos abordar isso nesse tutorial.

Em seguida, gere o script da base de dados e importe em algum SGBD relacional de sua escolha. Para o exemplo, estou utilizando o Postgres.

Agora, vamos fazer a engenharia reversa da nossa base de dados e criar todas as páginas, classes, etc, necessárias para nossa aplicação funcionar:

> seam generate-entities

Pronto! Que tal fazermos um deploy de nossa aplicação no nosso servidor JEE? É muito simples:

> seam explode

Abra o seu browser e entre com a URL (O usuário para login é “admin” e a senha pode ser qualquer coisa):

http://localhost:8080/tutorial_seam

Screenshot da aplicação.

Screenshot da aplicação.

Conclusão

Dessa forma, concluímos o nosso projeto com todo o poder da plataforma JEE (transações distribuídas, pool de conexões, internacionalização, etc.) em poucos minutos (sem levar em consideração o tempo que levou para fazer os downloads :) ). Para aplicações do mundo real, esses utilitários são de grande ajuda, pois são o ponto de partida para pularmos tarefas tediosas como: montagem da estrutura inicial do projeto, criação de CRUDs,  mapeamentos ORM, etc. Sendo assim, direcionamos o nosso foco para a criação de regras de negócio e personalização dos artefatos gerados pela ferramenta.
Aconselho você a olhar o código gerado e observar como é relativamente simples, poderoso e robusto. Não se assuste com a quantidade de XMLs gerados, provavelmente, nunca você vai precisar mexer neles. ;)

Links

http://docs.jboss.com/seam/2.0.2.SP1/reference/en-US/html
http://www.seamframework.org/Download
http://www.jboss.org/jbossas/downloads/

Recuperação de Falhas no Postgresql

Outubro 17, 2008 por ranophoenix

Com o objetivo de otimizar o desempenho, os Sistemas Gerenciadores de Banco de Dados (SGBDs) modernos fazem intensivo uso da memória principal. Dessa forma, o disco rígido, por ser muito mais lento que a RAM, não é acessado a todo momento. Por causa disso, surge o seguinte cenário: um usuário inicia uma transação, efetua algumas alterações nos dados, e confirma a transação (commit), porém, antes que o SGBD grave as operações no arquivo de dados, uma falha acontece – falta de energia no servidor, por exemplo. E agora? Qual o mecanismo utilizado pelos SGBDs para garantir a durabilidade das transações nestes cenários? Nesse artigo, conceituaremos duas técnicas baseadas em log que são utilizadas pela maioria dos SGBDs: recuperação adiada e recuperação imediata. E, na seqüência, comentaremos sobre a estratégia escolhida pelo Postgresql para recuperação em caso caso de falhas.

Entendendo o Cenário

Para entendermos melhor as situações nas quais transações podem se encontrar no momento de uma falha, vejamos a Figura 1. Observamos a existência de uma operação denominada CHECKPOINT, que é o processo do SGBD responsável por transferir os dados de transações comitadas ou não (a depender do tipo de recuperação implementada), da memória principal para o arquivo de dados. O Banco pode, basicamente, deparar-se com cinco situações:

·         Transação iniciada e comitada antes do checkpoint (T1);

·         Transação iniciada antes do checkpoint e comitada após o checkpoint, porém antes da falha ocorrer (T2);

·         Transação iniciada antes do checkpoint e não comitada devido à falha (T3);

·         Transação iniciada e comitada após o checkpoint, contudo antes da falha (T4);

·         Transação iniciada após o checkpoint e não comitada devido à falha (T5).

Figura 1.  Cenário de transações em um SGBD.

Figura 1. Cenário de transações em um SGBD.

Quais seriam as transações que o Banco deveria recuperar ao ser iniciado após uma falha? O SGBD deve recuperar todas as transações que foram efetivadas (comitadas) antes do crash, portanto as transações T2 e T4 devem ser restauradas e suas operações efetivadas no arquivo de dados, ou seja, após a restauração o Banco deve forçar um checkpoint. Com a transação T1 não precisamos nos preocupar, pois no momento do checkpoint ela foi persistida. E quanto às transações T3 e T5? Vai depender da estratégia adotada pelo SGBD, como veremos adiante.

O que possibilita ao Banco recuperar as transações é um log criado em disco onde ele registra as operações de cada transação, bem como os checkpoints realizados. Um instante: acabamos de dizer que o SGBD não utiliza muito o disco para ganhar velocidade, então por que o arquivo de log não degrada tanto a performance? A resposta é muito simples: ao contrário do que acontece no arquivo de dados onde o acesso é aleatório, o arquivo de log é sempre seqüencial. Dessa forma, o acesso ao disco se torna muito eficiente, pois o cabeçote de leitura/gravação não precisa ficar se deslocando para áreas não contíguas.

Portanto, sempre mantenha o arquivo de log em um disco que não seja o utilizado pelo arquivo de dados. Percebam que isso não é uma imposição dos SGBDs, mas é altamente recomendado para que o Banco consiga ser mais eficiente.

A recuperação adiada e a imediata diferem entre si pela estratégia utilizada durante o checkpoint e, por conseqüência, em como esses dados são restaurados no caso de falha.

Recuperação Adiada (REDO)

Nessa estratégia, o checkpoint só grava no arquivo de dados as transações que estão marcadas como comitadas na memória principal. As operações de outras transações são somente feitas no buffer de banco de dados e registradas no arquivo de log. Assim sendo, transações não comitadas têm suas operações ignoradas no momento da recuperação (veja isso como um rollback do sistema), enquanto as transações que foram efetivadas devem ser refeitas e persistidas no arquivo de dados.

A implementação dessa estratégia é relativamente simples, pois não precisamos nos preocupar com as transações que não foram efetivadas. É necessário somente que as transações iniciadas e comitadas antes da falha sejam refeitas (REDO). Dessa forma, no arquivo de log não é necessário que o Banco armazene os valores antigos dos dados, porque nada será desfeito.

Vamos fazer uma análise do cenário das transações (Figura 1) de acordo com essa recuperação:

* Nada é preciso ser feito para T1, pois no momento do checkpoint ela foi persistida no arquivo de dados;
* A T2 precisa ser totalmente refeita, ou seja, partindo das informações registradas no arquivo de log, todas as operações de T2 são reprocessadas;
* A T3 é ignorada (rollback). Dessa forma, todas as operações são totalmente descartadas, tendo em vista que não foram comitadas até o momento da falha;
* Assim como T2, a T4 precisa ser totalmente refeita;
* Pelo mesma razão de T3, a T5 é ignorada (rollback).

Para transações curtas essa é uma estratégia interessante devido a sua simplicidade de implementação. Contudo, longas transações consomem mais buffers de banco de dados, pois todas as operações não comitadas são mantidas na RAM até que sejam efetivadas e o processo de checkpoint faça a transferência para o arquivo de dados.

Em seguida, demonstramos o algoritmo para a recuperação adiada.

1. Processar o arquivo de log de trás para frente até encontrar o checkpoint preenchendo duas listas: transações comitadas (LTC) e transações iniciadas (LTI);
2. Buscar ponto de partida da restauração:
3. Se a LTC estiver contida na LTI, o ponto de partida é o checkpoint;
4. Se existir alguma transação na LTC que não esteja na LTI, continuar fazendo a leitura do arquivo de log até LTI = LTC;
5. Processar o arquivo de log seqüencialmente refazendo (REDO) as operações das transações contidas na LTC;
6. Forçar um chekpoint.

Recuperação Imediata (UNDO/REDO)

Na recuperação imediata, todos os dados que foram alterados por transações comitadas ou não, são persistidas no arquivo de dados. Por essa razão, algumas transações precisam ser desfeitas (UNDO) e outras refeitas (REDO) no momento da recuperação.

Com a necessidade do UNDO, alguns controles adicionais precisam ser adicionados ao arquivo de log. O SGBD precisa conhecer, além dos valores alterados, os valores antes da modificação.

Baseado nessas diferenças, analisaremos o cenário das transações de acordo com essa estratégia:

* Nada é preciso ser feito para T1, pois no momento do checkpoint ela foi persistida no arquivo de dados;
* Como na recuperação adiada, a T2 precisa ser refeita a partir do checkpoint;
* A T3 precisa ser desfeita até o momento do checkpoint, portanto, todas as alterações feitas no arquivo de dados devem retornar ao seu estado anterior;
* A T4 precisa ser totalmente refeita;
* A T5 é descartada (rollback);

A implementação dessa abordagem é mais complexa do que a anterior, porém a retenção de memória tende a ser muito menor.

Vejamos o algoritmo para recuperação imediata:

1. Processar o arquivo de log na ordem reversa até encontrar o checkpoint montando uma lista de transações comitadas (LTC);
2. A lista de transações ativas (LTA) pode ser preenchida por dados de controle contidos no próprio checkpoint;
3. Desfazer (UNDO) todas as operações de gravação das transações contidas na LTA, começando do checkpoint ao início do log;
4. Refazer (REDO) todas as operações de gravação das transações contidas na LTC, do checkpoint ao final do arquivo;
5. Forçar um checkpoint.

Write-Ahead Logging (WAL)

Finalmente, depois de toda essa parte introdutória, entramos no universo Postgresql. Write-Ahead Logging nada mais é do que um sinônimo para o sistema de recuperação adiada (REDO). Dessa forma, através dos conceitos observados nas seções anteriores, já conhecemos as principais implicações dessa estratégia. Obviamente, o Postgresql tem algumas particularidades em sua implementação.

WAL torna possível técnicas de backup mais flexíveis, indo além de um simples dump da base de dados ou uma cópia física das estruturas de diretório. Podemos pensar em uma situação onde um crash aconteceu em nosso servidor de banco numa sexta-feira, porém o último backup físico que temos é o da segunda. E Agora? Basta que tenhamos o log WAL desse período para que o Postgresql refaça todas as operações efetivadas entre o último backup e o crash, levando o arquivo de dados para o último estado consistente antes da falha ocorrer. Além disso, temos a possibilidade de realizar backups on-line através do arquivamento do log e restaurar o estado da base para qualquer ponto no tempo. Esse recurso é conhecido como Point-In-Time Recovery (PITR) e tem material suficiente para um artigo dedicado só a ele.

Lembram-se quando falamos anteriormente que é de fundamental importância colocar o arquivo de log em um disco separado? No Postgresql, estes arquivos ficam no diretório pg_xlog, dentro do cluster gerado pelo comando initdb. Portanto, devemos movê-lo e criar um link simbólico no local original.

O Postgresql salva a posição do último checkpoint executado no arquivo pg_control. Em caso de falha, o SGBD faz a leitura desse arquivo e depois reprocessa as transações comitadas contidas nos arquivos de log. Portanto, cuide muito bem desse arquivo, pois caso ele fique corrompido não será possível refazer as operações. Contudo, poderíamos implementar o algoritmo da recuperação adiada, processando o arquivo de log de trás para frente e resolveríamos o problema. Que tal? Essa é uma excelente oportunidade para deixar a sua contribuição para a comunidade de software livre. Inclusive, isso já está na lista de coisas a fazer (http://www.postgresql.org/docs/faqs. TODO.html).

Parâmetros de Configuração

O Postgresql é um SGBD altamente parametrizado e com o WAL não é diferente. Podemos fazer diversos ajustes relevantes para adequar o SGBD a nossas necessidades. Cada ajuste pode ser feito diretamente no arquivo postgresql.conf (carregado no momento da inicialização do serviço) ou através do comando SQL: SET <nome do parâmetro> <valor> (altera o valor do parâmetro para a sessão atual).

O primeiro ponto a salientar é que o WAL é automaticamente habilitado, porém podemos configurar, dentre outras coisas, em que momento o processo server executará o checkpoint. Na Listagem 1 encontramos o trecho do arquivo postgresql.conf responsável pela configuração dos parâmetros WAL.

Listagem 1. postgresql.conf.

#——————————————————————————

# WRITE AHEAD LOG

#——————————————————————————

# – Settings -

#fsync = on                 # turns forced synchronization on or off

#synchronous_commit = on         # immediate fsync at commit

#wal_sync_method = fsync         # the default is the first option

# supported by the operating system:

#   open_datasync

#   fdatasync

#   fsync

#   fsync_writethrough

#   open_sync

#full_page_writes = on           # recover from partial page writes

#wal_buffers = 64kB              # min 32kB

# (change requires restart)

#wal_writer_delay = 200ms        # 1-10000 milliseconds

#commit_delay = 0           # range 0-100000, in microseconds

#commit_siblings = 5             # range 1-1000

# – Checkpoints -

#checkpoint_segments = 3         # in logfile segments, min 1, 16MB each

#checkpoint_timeout = 5min       # range 30s-1h

#checkpoint_completion_target = 0.5   # checkpoint target duration, 0.0 – 1.0

#checkpoint_warning = 30s        # 0 is off

# – Archiving -

#archive_mode = off         # allows archiving to be done

# (change requires restart)

#archive_command = ”       # command to use to archive a logfile segment

#archive_timeout = 0        # force a logfile segment switch after this

# time; 0 is off

Para o objetivo do artigo, nos concentraremos nos parâmetros de configuração do checkpoint: checkpoint_segments, checkpoint_timeout, checkpoint_completion_target e checkpoint_warning.

Na configuração padrão, o servidor executa um checkpoint a cada 3 segmentos do arquivo de log (3 * 16MB) ou a cada cinco minutos. Também podemos executar o checkpoint a qualquer momento através da instrução SQL CHECKPOINT.

Na configuração dos parâmetros temos que ter em mente duas características que devemos balancear: tempo de recuperação após um crash e desempenho do servidor quando em execução. Infelizmente, essas características são inversamente proporcionais.

Diminuindo os valores dos parâmetros checkpoint_segments e checkpoint_timeout, fazemos com que os checkpoints sejam executados com maior freqüência, portanto reduzindo o tempo de recuperação após uma falha. Porém, por ser um processo muito custoso, a execução de tantos checkpoints consecutivos degrada a performance do SGBD. Caso dois checkpoints sejam executados em um intervalo menor do que checkpoint_warning, uma mensagem é adicionada ao log do banco informando para aumentar o valor de checkpoint_segments.

Por outro lado, adiando a execução de checkpoints estaremos aumentando a retenção de memória e, em caso de falha, o tempo de recuperação. Contudo, o desempenho do SGBD será otimizado, pois a quantidade de operações de I/O em disco será reduzida. Se você tem um hardware altamente robusto e confiável essa é a opção ideal. Nesse sentido, a vantagem obtida com o ganho de desempenho compensa o custo no aumento do tempo de recuperação em virtude de um crash esporádico.

Com o objetivo de reduzir o I/O durante o processo de checkpoint, o Postgresql calcula a taxa de transferência necessária para que o processo termine em um tempo determinado. Na configuração padrão, o SGBD espera acabar cada checkpoint aproximadamente na metade do tempo antes que outro inicie (checkpoint_completion_target = 0.5). Desse modo, checkpoint_completion_target é uma fração de checkpoint_segments ou checkpoint_timeout, o que ocorrer primeiro. Para servidores que já estão trabalhando no limite de I/O é aconselhável aumentar o valor desse parâmetro, pois assim se reduz taxa de transferência do processo de chekpoint e, com isso, libera recursos para as transações operacionais.

Testando as Configurações

Nem sempre temos uma base com um volume de dados suficientemente grande para testarmos os efeitos dos nossos ajustes. Para suprir essa necessidade, o Postgresql vem com o utilitário pgbench.

Em primeiro lugar, vamos criar um database denominado dbtests, conforme Listagem 2.

Listagem 2. Criando database.

> createdb dbtests

Não é obrigatório criar um database só para os testes que iremos fazer, porém é uma boa prática não misturar dados de testes com dados reais de nossas aplicações.

Agora podemos executar o comando que cria as tabelas com os dados necessários para os testes (Listagem 3).

Listagem 3. Criação de tabelas e dados.

>pgbench –U postgres –h localhost –i dbtests

O comando da Listagem 3 cria as tabelas: accounts (100.000 linhas), branches (1 linha), history (nenhuma linha) e tellers (10 linhas). Caso se queira fazer testes com uma massa de dados maior, podemos adicionar o parâmetro –s <fator escalar> (multiplica o número de linhas geradas pelo fator escalar). Por exemplo, a Listagem 4 faz com que a tabela accounts seja criada com 1.000.000 de linhas.

Listagem 4. Usando fator escalar.

>pgbench –U postgres –h localhost –s 10 –i dbtests

E, finalmente, na Listagem 5 executamos o nosso benchmark. O nosso teste simula 10 clientes, cada um executando 10 transações, onde cada transação executa os comandos da Listagem 6.  Observamos na Listagem 7 o resultado do bechmark executado em um servidor hipotético. O atributo tps nos informa quantas transações por segundo o banco conseguiu atender.

O utilitário pgbench é muito flexível. Além de outros parâmetros de linha de comando, temos a possibilidade de criar os nossos próprios scripts de teste.

Dessa forma, é interessante executarmos pelos menos um benchmark para cada configuração que alterarmos no postgresql.conf. Podendo ainda, disparar o pgbench de vários hosts. O tps, juntamente com outras métricas obtidas através de outras ferramentas (top, iostat, etc) , nos fornece informações extremamente relevantes para uma utilização eficiente dos recursos oferecidos pelo Postgresql.

Listagem 5. Executando benchmark.

>pgbench –U postgres –h localhost –c 10 –t 10 dbtests

Listagem 6. Operações executadas por cada transação.

BEGIN;

UPDATE accounts SET abalance = abalance + :delta WHERE aid = :aid;

SELECT abalance FROM accounts WHERE aid = :aid;

UPDATE tellers SET tbalance = tbalance + :delta WHERE tid = :tid;

UPDATE branches SET bbalance = bbalance + :delta WHERE bid = :bid;

INSERT INTO history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);

END;

Listagem 7. Resultado do benchmark.

transaction type: TPC-B (sort of)

scaling factor: 10

number of clients: 10

number of transactions per client: 1000

number of transactions actually processed: 10000/10000

tps = 63.123853 (including connections establishing)

tps = 65.235782 (excluding connections establishing)

Conclusões

Nesse artigo tratamos dos conceitos fundamentais dos sistemas de recuperação de falha baseados em log e qual a abordagem adotada pelo Postgresql para garantir a durabilidade das transações. Como DBAs, é de fundamental importância entender o modo como se processa a recuperação de transações no nosso SGBD, pois podemos otimizar parâmetros de acordo com situações encontradas em ambientes reais de produção.

Notas:

Durabilidade: A Durabilidade é uma das quatro características de uma transação. Essa propriedade garante que os efeitos de uma transação comitada sejam permanentes, inclusive após uma falha. Representa a quarta letra da sigla ACID (Atomicidade, Consistência, Isolamento e Durabilidade).