Breeding scalable web zombies using HBase
Em nosso atual projeto, usamos hadoop e hbase para armazenar centenas de milhares de documentos e conteúdo para todos os nossos usuários. Nós decidimos mudar toda persistência para o HBase . As vantagens sao claras: um modelo de programação mais fácil, o que significa, sucesso mais rápido e menor quantidade de bugs. E nós temos espaço ilimitado para crescimento em produçao: apenas adicionar mais servidores. Adicione uma aplicação web sessionless como uma GUI e você terá uma solução eficiente tanto para grande como para pequenas nuvens. Agora, vamos pular 4 meses de construção da aplicação e pular para onde a historia realmente começa: caçar zumbis.
Socorro! Nós temos um zumbi!
Nosso cluster de desenvolvimento está rodando 2 web containers com um load balancer persistindo em um hadoop/hbase cluster. Nós controlamos nossos recursos dentro da aplicação web instanciando apenas um HTable por thread (armazenados em um ThreadLocal) e tendo certeza que a Hconnection foi divida entre todas as outras threads. Nós estavamos cultivando zumbis e não sabiamos ainda.
Nós encontramos o bug depois de nosso primeiro teste de load: poucas 32 thread continuamente enviando informações para aplicação durante 20 minutos. Foram necessários apenas 3 threads para que a aplicação se tornasse um zumbi: out of native threads. E sim, nos fizemos um RTFM & configuramos ulimit and nproc nas nossas máquinas GNU/Linux. Ainda assim, tivemos uma aplicação que reagia a nada e apenas ficava ali bloqueando recursos.
Por que ?
Um cliente HBase usa (uma central de objectos)(um objecto central) para gerenciar suas conexões com seus servidores. Esse objeto HConnection é basicamente uma queue que serializa acesso a vários sockets TCP/IP e se mantém adicionando threads e sockets da maneira que bem entende. Na nossa configuração, toda thread que acessava um HConnection poderia criar de 2 a 5 sockets entre 2 a 10 threads.
Isso funciona bem para aplicações controladas onde o trabalho é processado em uma queue e worker threads limitadas. Contudo, um web container normalmente gera threads baseado no número que requisições que recebe. Nós estavamos confrontando cum uma aplicação que já tinha 300 threads rodando com 75 sockets abertos e ativos depois de ser inicializada com um pool de 10 threads para servir requisições. Depois de 3 minutos de uploads em nossa aplicação, tivemos 800 sockets abertos e 4500 threads mais ou menos. Hmmmm... Interessante.
Então... Por que eu não posso dizer ao HBase qual threadpool usar? Eu quero ter uma aplicação com uma threadpool de, vamos dizer, 200 threads, e ter o HConnection usando isso. Por que usar essa nova threadpool de sem llimites no construtor de HConnection? Isso cheira a atalho.
Existe uma solução, meio que um hack, chamado HTablePool. Essa solução te dá um controle, mas não completo, sobre os recursos. Você pode limitar o número de objetos HTable que são instanciados, que no nosso caso, não ajudava em nada. Mas, porque callers precisam esperar até que um deles esteja disponível, no final vai estar limitando o número de threads em uso. A conclusão é que você nunca pode prever quantas connections e threads, assim como o número de region servers e zookeeper servers que o HConnection precisa para falar (com o que é logicamente indeterminado) (é logicamente indeterminado).
Sem mais zumbis.
Para falar a verdade, os desenvolvedores do HBase sabem disso e consertaram. HBASE-4805 introduz um construtor que você pode atribuir um executor service a ser usado pelo HConnection. Como nós estamos amarrados a versão que nosso cliente utiliza, claudera hbase 0.90, nós estamos sem essa atualização. Sem solução, tivemos que implementar nossa HtablePool e fazer isso sem ter que rescrever nossos métodos de acesso já implementados. Nós vamos provavelmente alcançar limites novamente, quand o número de regionservers por tabela aumentar. Por hora, nós temos uma solução. Continua...