Este sábado, dia 28 de Abril estarei presente no Flisol, Festival Latino-Americano de Instalação de Software livre, conversando um pouco sobre como nginx e algumas técnicas de otimização para web. Serão duas palestras que envolvem o mesmo tema:
Na primeira debaterei com o público sobre algumas ferramentas utilizadas no Musica.com.br para tornar o portal mais dinâmico e rápido, sem sobrecarregar o servidor. Bibliotecas como Head.js, backbone.js e localStorage serão abordadas.
A segunda falaremos sobre como escalar seu site com Nginx. Mostrarei alguns problemas que tivemos e como solucionamos eles em diversos produtos da Globo.com.
A programação completa do Flisol Salvador você confere aqui: http://softwarelivre.org/flisol-ssa/programacao.
Portanto, se estiver por salvador neste feriadão, não deixe de participar deste evento e puxar um papo comigo.
segunda-feira, 23 de abril de 2012
terça-feira, 22 de março de 2011
Corrigindo o enconding retornado pelo Net::HTTP do ruby 1.9
A String retornada pelo Net::HTTP.get no ruby 1.9 sempre é definida com encoding "ASCII-8BIT". Acontece que muitas vezes esse não é o encoding correto da String, e portanto, erros esquisitos podem acontecer. Um exemplo pode ser visto abaixo quando utilizei essa String em um erb:
Ou o seguinte, que acontece ao tentar encodar essa String para UTF-8:
A solução para este problema é antes de encodar, definir o encoding correto da String. Para isso podemos utilizar o header "content-type" retornado na requisição. Se por exemplo ele retornasse 'text/xml; charset=ISO-8859-1', podemos converter a String da seguinte forma:
Para não ter que fazer isso a cada requisição HTTP, criei uma lib que altera o metodo body do HttpResponse. O código pode ser pego aqui:
http://gist.github.com/882465
Eu até pensei em alterar o metodo force_encoding da String para aceitar o valor do content-type, mas achei que isso seria dar muita responsabilidade para a String. Como o valor de content-type está apenas no ambito do HTTP, faz sentido colocar na classe Net::HttpResponse.
invalid byte sequence in UTF-8
Ou o seguinte, que acontece ao tentar encodar essa String para UTF-8:
> minha_string.encode("UTF-8")
Encoding::UndefinedConversionError: "\xC3" from ASCII-8BIT to UTF-8A solução para este problema é antes de encodar, definir o encoding correto da String. Para isso podemos utilizar o header "content-type" retornado na requisição. Se por exemplo ele retornasse 'text/xml; charset=ISO-8859-1', podemos converter a String da seguinte forma:
> minha_string.force_encoding("ISO-8859-1").encode("UTF-8")Para não ter que fazer isso a cada requisição HTTP, criei uma lib que altera o metodo body do HttpResponse. O código pode ser pego aqui:
http://gist.github.com/882465
Eu até pensei em alterar o metodo force_encoding da String para aceitar o valor do content-type, mas achei que isso seria dar muita responsabilidade para a String. Como o valor de content-type está apenas no ambito do HTTP, faz sentido colocar na classe Net::HttpResponse.
quarta-feira, 3 de novembro de 2010
Gem brazilian-rails em versões de rails antigas
Se você estiver usando uma versão antiga do brazilian-rails é bem provável que ao tentar instalar esta gem ela comece a instalar também versões superiores do rails. Isso porque esta gem possui um conjunto de dependências definidas somente com o operador ">=".
Ou seja, se você tentar instalar a versão 2.1.13 por exemplo, que funciona com o rails 2.3.2, ele tentará baixar a dependência da gem brdinheiro ">= 2.1.13", acabando por baixar a versão 3.0.0, que depende do activerecord 3.0.0, do rails 3. Nem preciso dizer que isso acabará dando merda.
A solução é baixar todos os componentes independentemente sem suas dependências da seguinte forma:
Ou seja, se você tentar instalar a versão 2.1.13 por exemplo, que funciona com o rails 2.3.2, ele tentará baixar a dependência da gem brdinheiro ">= 2.1.13", acabando por baixar a versão 3.0.0, que depende do activerecord 3.0.0, do rails 3. Nem preciso dizer que isso acabará dando merda.
A solução é baixar todos os componentes independentemente sem suas dependências da seguinte forma:
sudo gem install brnumeros --version=2.1.13 --ignore-dependencies
sudo gem install brdinheiro --version=2.1.13 --ignore-dependencies
sudo gem install brcep --version=2.1.13 --ignore-dependencies
sudo gem install brdata --version=2.1.13 --ignore-dependencies
sudo gem install brhelper --version=2.1.13 --ignore-dependencies
sudo gem install brstring --version=2.1.13 --ignore-dependencies
sudo gem install brcpfcnpj --version=2.1.13 --ignore-dependencies
sudo gem install brI18n --version=2.1.13 --ignore-dependencies
sudo gem install brazilian-rails --version=2.1.13 --ignore-dependencies
quinta-feira, 2 de setembro de 2010
Solucionando IO bloqueante do mysql no ruby
Fui a uma palestra muito interessante sobre performance na Oscon. A palestra No Callbacks, No Threads: Async & Cooperative Web Servers with Ruby 1.9, tratava do problema de IO bloqueante do driver do mysql para ruby, e como solução era proposto o uso de recursos como Event Machine e Fibers do ruby 1.9. Contudo, a solução não ficava nada elegante, tornando a manutenção do código muito complicada. Embora hoje haja um esforço para tornar esse trabalho transparente, consegui obter o mesmo resultado em performance basicamente aumentando o número de processos a atenderem as requisições.
Mas antes de explicar a solução é preciso demonstrar o problema. O caso é que embora tenhamos threads no ruby, alguns drivers como o do mysql são bloqueantes, ou seja, quando estão em uma operação de IO eles bloqueiam o processo inteiro, inclusive todas suas threads. Veja por exemplo o seguinte código:
Teoricamente ao fazermos a requisição a este controller de teste, a requisição não deveria durar os dois segundos de espera pelo retorno do insert ao mysql. Mas não é isso que acontece. As requisições acabam sendo enfileiradas pois o processo inteiro fica bloqueado a cada execução de query no banco. Isso pode ser comprovado utilizando o Apache Benchmark.
É bom deixar claro que o bloqueio ocorre somente ao usar o método execute do driver, que é responsável por fazer atualizações no banco. Fazendo somente consultas, o bloqueio não ocorre.
Na palestra em questão, foi demonstrado que utilizando Event Machine e Fibers é possivel desbloquear o processo utilizando callbacks. No final o tempo total foi reduzido para 2 segundos e alguns milésimos. Esse mesmo resultado eu obtive configurando um nginx com passenger configurado com 10 forks. Uma solução bem mais limpa. São apenas dois parametros, um do nginx, e outro do passenger:
E o resultado do teste:
É claro que ainda sim a solução proposta na palestra é mais performática, até porque nela o consumo de memória é bem menor. Resta saber se essa economia vale a pena quando se pesa na balança o custo de manter um código mais complicado e os problemas que a concorrência podem trazer ao seu projeto. E para demonstrar o quão escalável é dividir as requisições em processos, fiz ainda um ultimo teste, com 1000 requisições, sendo 200 simultâneas, configurando o nginx e o passenger para trabalhar com 200 forks:
Ou seja, o tempo total de teste manteve-se estável. Levando- em conta que dificilmente alguém fará um insert no banco com sleep, acredito que esssa seja uma boa solução para o problema de IO bloqueante. Ao invés de threads, utilizar processos.
Mas antes de explicar a solução é preciso demonstrar o problema. O caso é que embora tenhamos threads no ruby, alguns drivers como o do mysql são bloqueantes, ou seja, quando estão em uma operação de IO eles bloqueiam o processo inteiro, inclusive todas suas threads. Veja por exemplo o seguinte código:
class TestesController < ApplicationController
def index
Thread.new { Teste.connection.execute("insert into testes (id) select sleep(2)") }
render :text => 'ok'
end
end
Teoricamente ao fazermos a requisição a este controller de teste, a requisição não deveria durar os dois segundos de espera pelo retorno do insert ao mysql. Mas não é isso que acontece. As requisições acabam sendo enfileiradas pois o processo inteiro fica bloqueado a cada execução de query no banco. Isso pode ser comprovado utilizando o Apache Benchmark.
> ab -c 10 -n 10 http://localhost:3000/testes
...
Concurrency Level: 10
Time taken for tests: 20.514 seconds
Complete requests: 10
...
É bom deixar claro que o bloqueio ocorre somente ao usar o método execute do driver, que é responsável por fazer atualizações no banco. Fazendo somente consultas, o bloqueio não ocorre.
Na palestra em questão, foi demonstrado que utilizando Event Machine e Fibers é possivel desbloquear o processo utilizando callbacks. No final o tempo total foi reduzido para 2 segundos e alguns milésimos. Esse mesmo resultado eu obtive configurando um nginx com passenger configurado com 10 forks. Uma solução bem mais limpa. São apenas dois parametros, um do nginx, e outro do passenger:
worker_processes 10;
#...
http {
passenger_max_pool_size 10;
}
E o resultado do teste:
> ab -c 10 -n 10 http://localhost/testes
Concurrency Level: 10
Time taken for tests: 2.424 seconds
Complete requests: 10
É claro que ainda sim a solução proposta na palestra é mais performática, até porque nela o consumo de memória é bem menor. Resta saber se essa economia vale a pena quando se pesa na balança o custo de manter um código mais complicado e os problemas que a concorrência podem trazer ao seu projeto. E para demonstrar o quão escalável é dividir as requisições em processos, fiz ainda um ultimo teste, com 1000 requisições, sendo 200 simultâneas, configurando o nginx e o passenger para trabalhar com 200 forks:
> ab -c 200 -n 1000 http://localhost/testes
Concurrency Level: 200
Time taken for tests: 13.043 seconds
Complete requests: 1000
Ou seja, o tempo total de teste manteve-se estável. Levando- em conta que dificilmente alguém fará um insert no banco com sleep, acredito que esssa seja uma boa solução para o problema de IO bloqueante. Ao invés de threads, utilizar processos.
segunda-feira, 9 de agosto de 2010
Aumento de produtividade por ponto de complexidade
Tenho ouvido ultimamente muitos amigos da área comentando sobre pressão por aumento de produtividade baseada em pontos de complexidade. Isso me deixa bastante preocupado. Embora seja nobre o desejo de aumentar as entregas da área de desenvolvimento, quantificar isso usando os pontos de complexidade das histórias não quer dizer muita coisa.
Contudo antes de simplesmente reclamar contra a pressão, é preciso analisar as possiveis causas de produtividade baixa que possam estimular esse tipo de pressão. Pensando bastante cheguei a três possibilidades, e com elas, possiveis soluções, que seriam mais eficazes do que estimular aumento desse tipo de numero.
1- Não há confiança de que o time esteja trabalhando em seu máximo. Ou seja, o agente gerador de pressão acredita que os integrantes estão fazendo corpo mole ou gastando o dia com amenidades ao invés de se focar na entrega do projeto. A pressão por aumento de pontos de complexidade pode até resolver esse problema temporariamente, mas também pode ser mascarado por integrantes do time que se sentem coagidos a fazer horas extras para cobrir as entregas esperadas. Ou seja, o problema só se resolve mesmo com conversas francas e com uma presença mais ativa do interessado.
2- O time percebendo folga na iteração aproveita para melhorar a qualidade da entrega ainda mais. Esse tipo de preciosismo costuma acontecer com bastante frequência. Muitas vezes o desenvolvedor por ter mais tempo para pensar aproveita para implementar testes e fluxos mais rebuscados evitando bugs que no futuro tomariam o triplo do tempo, e o designer aproveita para criar e rebuscar as interfaces e assim encantar ainda mais o cliente. Neste caso a pressão pelo aumento de entrega de pontos de complexidade apenas estimula a diminuição da qualidade. Ou seja, embora haja um aumento imediato na velocidade, em pouco tempo ela cairá por causa das correções de bugs e dos ajustes visuais.
3- O time é inexperiente ou não conhece a tecnologia adotada. Neste caso pressionar pelo aumento de entrega de pontos de complexidade de nada adianta. De certa forma, com o tempo, naturalmente as entregas serão maiores, ou em muitos casos menores pois o time começará a estimar com menos pontos, demonstrando assim a ineficácia desse numeros para medir produtividade. Para resolver este problema existem diversas opções como, organizar dojos, estimular programação em par, indicar livros e treinamentos, estimular a experimentação com tempo para projetos pessoais.
Todas três possibilidades acima eu vi acontecer de perto nos times em que trabalhei. E quase sempre o problema foi resolvido sem utilizar os pontos de complexidade como parâmetro de medição. E vocês lembram de alguma outra causa de produtividade baixa? Tem idéia de como solucionar? Qual sua opinião sobre o assunto?
Contudo antes de simplesmente reclamar contra a pressão, é preciso analisar as possiveis causas de produtividade baixa que possam estimular esse tipo de pressão. Pensando bastante cheguei a três possibilidades, e com elas, possiveis soluções, que seriam mais eficazes do que estimular aumento desse tipo de numero.
1- Não há confiança de que o time esteja trabalhando em seu máximo. Ou seja, o agente gerador de pressão acredita que os integrantes estão fazendo corpo mole ou gastando o dia com amenidades ao invés de se focar na entrega do projeto. A pressão por aumento de pontos de complexidade pode até resolver esse problema temporariamente, mas também pode ser mascarado por integrantes do time que se sentem coagidos a fazer horas extras para cobrir as entregas esperadas. Ou seja, o problema só se resolve mesmo com conversas francas e com uma presença mais ativa do interessado.
2- O time percebendo folga na iteração aproveita para melhorar a qualidade da entrega ainda mais. Esse tipo de preciosismo costuma acontecer com bastante frequência. Muitas vezes o desenvolvedor por ter mais tempo para pensar aproveita para implementar testes e fluxos mais rebuscados evitando bugs que no futuro tomariam o triplo do tempo, e o designer aproveita para criar e rebuscar as interfaces e assim encantar ainda mais o cliente. Neste caso a pressão pelo aumento de entrega de pontos de complexidade apenas estimula a diminuição da qualidade. Ou seja, embora haja um aumento imediato na velocidade, em pouco tempo ela cairá por causa das correções de bugs e dos ajustes visuais.
3- O time é inexperiente ou não conhece a tecnologia adotada. Neste caso pressionar pelo aumento de entrega de pontos de complexidade de nada adianta. De certa forma, com o tempo, naturalmente as entregas serão maiores, ou em muitos casos menores pois o time começará a estimar com menos pontos, demonstrando assim a ineficácia desse numeros para medir produtividade. Para resolver este problema existem diversas opções como, organizar dojos, estimular programação em par, indicar livros e treinamentos, estimular a experimentação com tempo para projetos pessoais.
Todas três possibilidades acima eu vi acontecer de perto nos times em que trabalhei. E quase sempre o problema foi resolvido sem utilizar os pontos de complexidade como parâmetro de medição. E vocês lembram de alguma outra causa de produtividade baixa? Tem idéia de como solucionar? Qual sua opinião sobre o assunto?
segunda-feira, 17 de maio de 2010
Site de Passione estréia nova plataforma de novelas da Rede Globo
Entrou no ar hoje a tarde o novo site de Passione, a nova novela das oito da Rede Globo. Mas por trás deste site está muito mais que um site. É o ínicio de uma nova experiência de consumo da dramaturgia da Rede Globo na internet.
Para quem não sabe, depois de quatro anos na equipe de videos da globo.com, me engajei logo no inicio deste projeto no time dedicado a ele. Apesar de ser a mesma empresa, é um mundo totalmente novo. Novas tecnologias, novas formas de gestão, novas pessoas. Tudo novo. Foi e está sendo muito bom.
E agora, com a entrega no prazo, e o feedback de dezenas de pessoas no twitter elogiando nosso trabalho, só tenho a agradecer ao meu time que me acolheu tão bem, e a todos as equipes que nos ajudaram, seja nos dando suporte, seja nos dando dicas, seja nos incentivando. Sem vocês o resultado não seria igual. Parabéns a todos nós!
Para quem não sabe, depois de quatro anos na equipe de videos da globo.com, me engajei logo no inicio deste projeto no time dedicado a ele. Apesar de ser a mesma empresa, é um mundo totalmente novo. Novas tecnologias, novas formas de gestão, novas pessoas. Tudo novo. Foi e está sendo muito bom.
E agora, com a entrega no prazo, e o feedback de dezenas de pessoas no twitter elogiando nosso trabalho, só tenho a agradecer ao meu time que me acolheu tão bem, e a todos as equipes que nos ajudaram, seja nos dando suporte, seja nos dando dicas, seja nos incentivando. Sem vocês o resultado não seria igual. Parabéns a todos nós!
quarta-feira, 3 de março de 2010
Objetos fake em diversas linguagens para divertir minha vó
Quase todas as linguagens que trabalhei possuem ferramentas para criar objetos fakes e assim auxiliar na construção de testes. Mas e se essas ferramentas não existissem? Estaríamos perdidos? Claro que não! Mocks e stubs podem ser criados de diversas formas diferentes nas diversas linguagens.
Esse é um ótimo assunto pra se discutir em um jantar de família. Quer coisa mais divertida que explicar pra sua avó como construir objetos fake em diversas linguagens? Por exemplo um simples stub que conta quantas vezes um método que notifica visualização de um filme em um serviço externo é chamado. Algo similar ao que o código abaixo faz. Diversão garantida!
Em Ruby
Em ruby podemos simplesmente criar uma nova classe em tempo de execução ou adicionar um método a um objeto qualquer. É a maneira mais simples de garantir um sorrisão da sua vó tamanha facilidade. No caso do exemplo abaixo resolvi adicionar métodos a um objeto qualquer e ao final executo o teste utilizando o rspec.
Em Python
Em Python é possivel alterar métodos de instancias em tempo de execução, mas não é possivel adicionar métodos a um objeto da classe object. Neste momento minha vozinha fica decepcionada. Para fazer algo semelhante ao que fizemos em ruby teríamos então que instanciar um objeto da classe original e só depois modificar o método. Ficaria mais ou menos assim:
Esta opção passa a ser ruim quando o construtor da classe original executa algumas tarefas que são custosas para o nosso teste. Por isso, acho que em python o melhor para o este caso é criar uma classe em tempo de execução, deixando minha vó um pouco mais alegre, conforme exemplo abaixo:
Em Java
Uma forma "simples" de fazer em Java é criando uma nova classe para herdar a original e sobrescrever somente o método desejado. Mas aí cairíamos no mesmo problema que discutimos sobre um possível construtor com código muito custoso na superclasse. E minha vó, que apesar de repetir muitas vezes as mesmas histórias, não gosta de ouvir as nossas repetidas.
Uma alternativa que temos é extrair uma interface da classe original para que em nosso teste a gente possa implementar essa interface da maneira que quisermos. Ficaria mais ou menos assim:
Então em nosso teste a gente cria uma classe implementando a interface recém criada:
E depois, mesmo que neste ponto minha vó já esteje dormindo, a gente utiliza a classe fake criada no teste:
Parece que minha avó não gostou da história. Ela começou tão dinâmica e foi ficando cadas vez mais devagar. Sem dúvida eu deveria ter contato ao contrário.
Esse é um ótimo assunto pra se discutir em um jantar de família. Quer coisa mais divertida que explicar pra sua avó como construir objetos fake em diversas linguagens? Por exemplo um simples stub que conta quantas vezes um método que notifica visualização de um filme em um serviço externo é chamado. Algo similar ao que o código abaixo faz. Diversão garantida!
servico.should_receive(:notificar_visualizacao!).with(filme).once
Em Ruby
Em ruby podemos simplesmente criar uma nova classe em tempo de execução ou adicionar um método a um objeto qualquer. É a maneira mais simples de garantir um sorrisão da sua vó tamanha facilidade. No caso do exemplo abaixo resolvi adicionar métodos a um objeto qualquer e ao final executo o teste utilizando o rspec.
servico_fake = Object.new
servico_fake.instance_eval do
def notificar_visualizacao!(filme)
@filme_visualizado = filme
@quantidade_visualizacoes = quantidade_visualizacoes + 1
end
def filme_visualizado
@filme_visualizado
end
def quantidade_visualizacoes
@quantidade or 0
end
end
filme = Filme.new
filme.servico = servico_fake
filme.visualizar!
servico_fake.filme.should be_equal(filme)
servico_fake.quantidade.should == 1
Em Python
Em Python é possivel alterar métodos de instancias em tempo de execução, mas não é possivel adicionar métodos a um objeto da classe object. Neste momento minha vozinha fica decepcionada. Para fazer algo semelhante ao que fizemos em ruby teríamos então que instanciar um objeto da classe original e só depois modificar o método. Ficaria mais ou menos assim:
filme_visualizado = None
quantidade_visualizacoes = 0
def notificar_visualizacao_fake(filme):
global filme_visualizado, filme_visualizado
filme_visualizado = filme
quantidade_visualizacoes += 1
servico_fake = ServicoExterno()
servico_fake.notificar_visualizacao = notificar_visualizacao_fake
Esta opção passa a ser ruim quando o construtor da classe original executa algumas tarefas que são custosas para o nosso teste. Por isso, acho que em python o melhor para o este caso é criar uma classe em tempo de execução, deixando minha vó um pouco mais alegre, conforme exemplo abaixo:
class ServicoFake(object):
def __init__(self):
self.filme_visualizado = None
self.quantidade_visualizacoes = 0
def notificar_visualizacao(self,filme):
self.filme_visualizado = filme
self.quantidade_visualizacoes += 1
servico_fake = ServicoFake()
filme = Filme()
filme.servico = servico_fake
filme.visualizar()
assert servico_fake.filme_visualizado is filme
assert servico_fake.quantidade_visualizacoes == 1
Em Java
Uma forma "simples" de fazer em Java é criando uma nova classe para herdar a original e sobrescrever somente o método desejado. Mas aí cairíamos no mesmo problema que discutimos sobre um possível construtor com código muito custoso na superclasse. E minha vó, que apesar de repetir muitas vezes as mesmas histórias, não gosta de ouvir as nossas repetidas.
Uma alternativa que temos é extrair uma interface da classe original para que em nosso teste a gente possa implementar essa interface da maneira que quisermos. Ficaria mais ou menos assim:
//ServicoExterno.java
public interface ServicoExterno {
void notificarVisualizacao(Filme filme);
}
//ServicoExternoHTTP.java
public class ServicoExternoHTTP implements ServicoExterno {
public ServicoExternoHTTP() {
//Faz um monte de coisas
}
public void notificarVisualizacao(Filme filme) {
//Faz mais coisas ainda
}
}
Então em nosso teste a gente cria uma classe implementando a interface recém criada:
public class ServicoExternoFake implements ServicoExterno {
public int quantidade_visualizacoes = 0;
public Filme filme_visualizado = null;
public void notificarVisualizacao(Filme filme) {
filme_visualizado = null;
quantidade_visualizacoes++;
}
}
E depois, mesmo que neste ponto minha vó já esteje dormindo, a gente utiliza a classe fake criada no teste:
ServicoExternoFake servicoFake = new ServicoExternoFake();
Filme filme = new Filme();
filme.setServico(servicoFake);
filme.visualizar();
assertSame(filme,servicoFake.filme_visualizado);
assertEquals(1,servicoFake.quantidade_visualizacoes);
Parece que minha avó não gostou da história. Ela começou tão dinâmica e foi ficando cadas vez mais devagar. Sem dúvida eu deveria ter contato ao contrário.
Assinar:
Postagens (Atom)