Archive for julho, 2006

Melhorando a serializacao de objetos imutaveis

sexta-feira, julho 28th, 2006

Criei um issue no JIRA do JBoss Serialization para melhorar a serialização de objetos imutáveis. Enquanto fazia algumas otimizações em um sistema que utiliza RMI – como protocolo de comunicação com um EJB remoto -, percebi que as chamadas problemáticas eram grandes somente porque continham diversos objetos imutáveis semanticamente iguais.

É muito comum isso ocorrer em função de como geramos instâncias dos tipos básicos na maior parte dos casos. Provavelmente as três maiores fontes de dados hoje são dados entrados pelo usuário, arquivos/documentos de terceiros e dados do banco. Em todos os três casos, as APIs e soluções de parsing utilizadas em quase sua totalidade não fazem cache de instâncias, até porque isso normalmente é desnecessário e sujeito a erros.

Por exemplo, se você estiver usando o Hibernate para ler o bean Produto que, entre outros atributos, possui um atributo BigDecimal que representa a quantidade em estoque e houver 1000 produtos que possuem um estoque de 5.5 unidades 1000 instâncias de BigDecimal 5.5 serão criadas em RAM. Em termos de memória isso não é normalmente relevante, mas quando se tenta serializar mil instâncias ao invés de uma instáncia repetida 1000 vezes, a diferença é brutal:


import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

public class BigDecimalSerializationTest {
   public static void main(String[] args) throws IOException {
      List instances = new ArrayList();

      for (int i = 0; i < 1000; i++) {
         instances.add(new BigDecimal("5.5"));
      }

      imprimeTamanhoSerializado(instances);

      Collections.fill(instances, new BigDecimal("5.5"));

      imprimeTamanhoSerializado(instances);
   }

   private static void imprimeTamanhoSerializado(final Collection instances)
         throws IOException {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      ObjectOutputStream oos = new ObjectOutputStream(baos);

      for (Iterator i = instances.iterator(); i.hasNext();) {
         oos.writeObject(i.next());
      }

      oos.close();

      System.out.println(baos.size());
   }
}

A saída na minha máquina para o teste acima é:


48242
5285

No caso específico que estava otimizando, após considerar o uso de um aspecto, descobri que a maior parte das instâncias era gerado num processo de parsing manual de um arquivo. O simples uso de um Map<String, BigDecimal> e afins para cache das instâncias foi suficiente para eliminar a repetição e trazer um ganho de até 60% em alguns casos.

Contudo, uma otimização assim deveria estar implementada na API de serialização e por isso abri esse issue no JBoss Serialization, que já é superior a API de serialização padrão com respeito ao tamanho em bytes gerado.

Polemica no post “The language barrier”

sexta-feira, julho 28th, 2006

Por essa eu não esperava: a maior parte dos comentários no meu post The language barrier acha que todo mundo tinha mais é que saber inglês e pronto. Para quem não consegue ler inglês, o post fala sobre a dificuldade que o indivíduo com potencial tem de se tornar um bom desenvolvedor pela falta de material em português, especialmente javadocs, livros e material original escrito na nossa língua.

A maioria dos que responderam pensam que é obrigação de quem trabalha com TI saber inglês, que não saber não ajuda o mercado global, que ter um blog no seu idioma nativo é perda de tempo e coisas do tipo. Sinceramente, se é assim, eu acho que a gente devia acabar com todo o material em português sobre TI e ainda tornar o inglês obrigatório para o indivíduo ser aceito na faculdade. Pelo menos assim ele não vai criar falsas esperanças achando que vai ser alguma coisa se não souber o idioma.

Triste, triste, muito triste…

GPL3, draft 2

quinta-feira, julho 27th, 2006

Se voê usa ou cria software open-source, deveria visitar o site da versão 3 da licença GPL. Para aqueles que não sabem, com exceção dos projetos da Apache, do Spring (que usa licença Apache também) e dos projetos da Sun, a grande maioria dos frameworks e ferramentas livres que utilizamos estão licenciados sob a LGPL (JBoss, Hibernate) ou a GPL (MySQL).

O grande objetivo dessa atualização da licença é é cobrir os novos cenários existentes, como uso de tecnologias para proteção de diretos (DRM) ou novos mecanismos de distribuição, como os torrents. Vale a pena ler.

The language barrier

quinta-feira, julho 27th, 2006

Acabo de escrever um novo post no meu blog em inglês sobre a barreira do idioma que impede que muitos desenvolvedores talentos possam mostrar seu verdadeiro potencial. Confiram!

Aceito na JSR 303

terça-feira, julho 18th, 2006

Fui aceito no Expert Group da JSR 303 – Bean Validation. Desta vez, o objetivo é definir anotações e uma API para permitir a validação de JavaBeans, independente da camada em que sejam usados. Este é um assunto em que planejava me ocupar de qualquer forma, já que o Commons Validator, da Apache, possui uma série de limitações e precisava implementar alguma solução melhor para o genesis. Assim que puder, divulgo mais informações…

Resolvendo ORA-01000: maximum open cursors exceeded

terça-feira, julho 4th, 2006

Junto com o clássico OutOfMemoryError, este é um dos erros que mais aparece em sistemas que usam Oracle. Resolver esse problema não é difícil, como vou explicar aqui.

Este erro é causado por Connections, Statements e ResultSets deixados abertos. Normalmente temos algo como:

Connection con = //obtem conexao;
PreparedStatement ps = con.prepareStatement("SELECT ...");
ResultSet rs = ps.executeQuery();

while (rs.next()) {
   // ...
}

rs.close();
ps.close();
con.close();

Note que eu estou sendo otimista, supondo que PreparedStatements sejam usados ao invés de concatenar Strings para executar consultas e que o método close() nas três instâncias esteja pelo menos no código. Se você não fez nem isso, corra atrás. :-)

O bug é que, quando ocorre uma exceção, durante o processamento do select, nenhum dos três objetos é fechado. Então, a primeira solução é colocar o fechamento num bloco try/finally, como mostrado abaixo:

Connection con = null;
PreparedStatement ps = null;
ResultSet rs =null;

try {
   con = //obtem conexao;
   ps = con.prepareStatement("SELECT ...");
   rs = ps.executeQuery();

   while (rs.next()) {
      // ...
   }
} finally {
   if (rs != null) {
      rs.close();
   }

   if (ps != null) {
      ps.close();
   }

   if (con != null) {
      con.close();
   }
}

Tudo certo, né? Errado. Se ocorrer uma exceção ao fechar rs, ps e con nunca serão fechados. Então a melhor solução seria:

Connection con = null;
PreparedStatement ps = null;
ResultSet rs =null;

try {
   con = //obtem conexao;
   ps = con.prepareStatement("SELECT ...");
   rs = ps.executeQuery();

   while (rs.next()) {
      // ...
   }
} finally {
   if (rs != null) {
      try {
          rs.close();
      } catch (SQLException sqe) {
          // logar excecao
      } catch (RuntimeException re) {
          // logar excecao
      }
   }

   if (ps != null) {
      try {
          ps.close();
      } catch (SQLException sqe) {
          // logar excecao
      } catch (RuntimeException re) {
          // logar excecao
      }
   }

   if (con != null) {
      try {
          con.close();
      } catch (SQLException sqe) {
          // logar excecao
      } catch (RuntimeException re) {
          // logar excecao
      }
   }
}

Ok, você vai me dizer, muito legal isso. Mas meu projeto tem milhares de classes e eu não sei onde eu esqueci de fazer isso. Ou, ainda, eu uso um framework O/RM ou JDBC que deveria resolver este problema pra mim. Bem, nesse caso, você tem como descobrir qual a instrução SQL do(s) cursor(es) que está(ão) abertos. Primeiro, você precisa do SID das sessões abertas pela sua aplicação, que você pode obter assim:

select o.sid, osuser, machine,
count(*) num_curs
from v$open_cursor o, v$session s
where o.sid=s.sid
group by o.sid, osuser, machine
order by num_curs;

Se estiver usando connection pool e um máquina só, pode-se usar estes dados para filtrar os resultados:

select o.sid, osuser, machine,
count(*) num_curs
from v$open_cursor o, v$session s
where o.sid=s.sid and
user_name = 'USUARIO_BANCO' and
machine = 'NOME_MAQUINA_APLICACAO'
group by o.sid, osuser, machine
order by  o.sid;

Utilize os SIDs na seguinte query:

select q.sql_text, count(*)
from v$open_cursor o, v$sql q
where q.hash_value=o.hash_value and o.sid IN (<SIDs>)
group by q.sql_text
order by 2;

Se o framework ou servidor de aplicações que você utiliza faz cache de statements (Hibernate e JBoss fazem, por default), é normal que exista um certo número de cursores com o mesmo SQL em aberto. Suspeite daquelas instruções que tenham um número de cursores muito maior que as outras e investigue de onde elas vêm. E se sua aplicação usa PL/SQL, saiba que as instruções contidas nas suas procedures/functions são contadas individualmente como cursores. Espero ter ajudado ;-)