Módulos do Nginx

30/9/2009 | Tags:, , , , | Escrito por: Dirceu Pauka Jr.
nginx-logo

Nginx é um web server e proxy de alta performance capaz de servir milhares de requisições simultâneas.

Tendo usado desde a época da boo-box, recentemente em dois sites que hospedo e também no BuzzVolume, eu posso dizer algo sobre o famoso “nginx bem configurado”: funciona e me faz dormir tranquilo.

O nginx é um web server. Responde as requisições na porta configurada, faz redirecionamentos, retorna arquivos estáticos e aceita módulos:

A comunidade está contribuindo bastante e é sobre alguns módulos “3th party” que vou falar.

Obs: Por “backend” entenda que é a aplicação Ruby, PHP, Python, “escolha sua linguagem”, que estiver rodando.

NginxHttpUpstreamFairModule

O upstream_fair faz com que as requisições batam no backend que estiver com menos carga. Sem esse módulo as requisições são distribuídas para qualquer backend, mesmo que esse estiver ocupado.

NginxHttpEmptyGifModule

O ngx_http_empty_gif_module mantém na memória um GIF de 1×1 pixels e o entrega diretamente, sem buscar no disco.
Eu considero esse o módulo mais curioso. Ele mostra a preocupação com performance e a facilidade de fazer algo assim com o nginx.
Esse gif de 1×1 pode servir como favicon.ico em sites que não usam icone, evitando que o arquivo (que não existe) seja procurado e retorne um erro. Também pode ser usado como “beacon” para rastreamento de impressões de ads.

NginxHttpLimitReqModule

Construindo o próximo Twitter? Mais um encurtador de URL? O módulo NginxHttpLimitReqModule permite impor limite de requisições por cliente nas suas páginas. Acabe com os engraçadinhos que abusam da API no seu URL-Short.

nginx-ey-balancer

Com o nginx-ey-balancer é possivel limitar o número de requisições simultâneas passadas ao backend. Ao invés de todas requisições cairem na aplicação elas ficam em fila no nginx aguardando um processo disponível. Segurar a requisição na aplicação precisa de mais memória e faz a performance da aplicação cair consideravelmente. Ótima solução desenvolvida pela EngineYard.

NginxHttpMemcachedModule

Memcached é um software desenvolvido para diminuir a carga da base de dados. Ele faz cache de objetos na memória e permite acesso pela interface rede. O NginxHttpMemcachedModule dá ao nginx o poder de procurar por um objeto no memcached e caso encontrar retorna o seu conteúdo para o navegador.

NginxHttpImageFilterModule

NginxHttpImageFilterModule é outro software interessante. Esse desenvolvido pelo criador do nginx, muda o tamanho da imagem na hora de entregar para o cliente evitando armazenar vários “thumbnails” do mesmo arquivo.

Estão surgindo cada vez mais módulos para o nginx. Confira outros “oficiais” e “3th party“.



Espancando um Web Server com Ruby

19/10/2008 | Tags:, , , , , , | Escrito por: Dirceu Pauka Jr.

Eu resolvi fazer alguns testes com Threads e Timeouts no Ruby hoje e para tal resolvi lidar com algo real: chamadas a APIs.

Problema

O problema todo da escalabilidade acontece por um simples motivo: uma chamada que demora para responder gera uma bola de neve no sistema. E esse problema da bola de neve se repete em várias partes do sistema todo. É isso que ocorre com as querys longas no MySQL ou upload de arquivo que trava uma instância do servidor e deixa todas próximas requisições lentas.

Por isso que é muito mais importante diminuir o tempo das querys longas no MySQL do que qualquer outra coisa. Você provavelmente consegue tirar mais tempo das querys mais longas, certo? Pense no lance de proporção, certo?

Problemas em escalar leitura em MySQL são comuns e por isso as soluções são amplamente conhecidas. Apesar de existir lock para leitura e outras características que machucam muito o trabalho de quem tem que escalar base de dados relacionais, caras como o Flickr fazem um bom uso do MySQL até hoje (sempre com ajuda de cache, lembre-se disso).

Mas e se a fonte de dados da aplicação é algo muito mais lento que um MySQL? E se a fonte de dados é digamos um WebService? Ou no pior dos casos: Web Scraping?

Um WebService ou uma API RESTfull não tem latência controlada como uma query rodando no MySQL local (ou em um cluster de) pode ter. Para começar a falar sobre WebServices eu penso logo em 200ms de latência só para atingir o servidor. E acredite, na maioria das vezes isso é pouco e de qualquer forma é o tempo de resposta que uma query comum no MySQL deve levar.

Além da lentidão você tem que pensar que a coisa toda pode não funcionar. Você pode fazer a requisição HTTP e ela não voltar. É da natureza do HTTP não retornar algumas requisições.

Agora você pensa em centenas de requisições entrando na fila a cada segundo em um sistema que busca os dados em uma API de tempo de resposta médio de 2 segundos. Esqueça a moleza do MySQL. 2 segundos aqui (no meu exemplo de API) é o tempo de resposta mínimo! Ferrou.

Eu já passei por isso. A maneira básica de lidar é cache. Você sabe. Todo mundo sabe…

Mas ainda acontece que as requisições são muito lentas! Muito! O cache não pode ser para sempre e as vezes acontecem coisas que lhe obrigam a limpar todo o cache.

Não importa quantos processos Ruby você abrir (ou quantas requisições/segundo seu Apache sirva em uma maquina de 16 bits), sua aplicação não escala e a culpa é da sua fonte de dados que para piorar é externa e você não pode fazer nada.

Solução

Eu estou planejando cortar muito processos Ruby de uma aplicação em Merb que bate 2MM req/dia com uma solução parecida com a experiência de hoje (que alias se encontra no final do post).

Por enquanto, para manter o serviço no ar a solução foi usar o que havia de melhor na infra: Nginx, Memcached e Thin. Ainda assim eu preciso de 75 instancias do Thin para que tudo funcione (lembre-se: cache não é eterno e requisições sem cache são extremamente lentas).

Como irei mudar o cenário? Quase da mesma forma como espanquei um WebServer a poucas horas com somente um processo Ruby aberto e muito menos banda do que um VPS costuma ter… “Threads”.

Usei Threads de Ruby aqui para a experiência que vou passar, mas para produção recomendo o uso de bibliotecas que irão gerenciar o trabalho. Nanite leva a carga de conhecimento em engenharia que a EngineYard possui, e apesar de não ter uma solução baseada especificamente na questão de threads pode servir para distribuir instâncias do servidor entre várias maquinas numa rede local (lógico que não é – e nem pretende ser – uma implementação do MapReduce). Ainda preciso fazer um bom teste com Nanite e conhecer soluções como NeverBlock. De qualquer forma, essas são boas soluções para o problema de escalabilidade. Pode confiar :)

Abaixo o código (experimental) que derrubou um IIS (inclusive expondo pedaços da aplicação). Ele mostra como eu usei o máximo da banda disponível para fazer tantas requisições em um servidor remoto até ele cair. Isso com somente um processo Ruby.

Em casos comuns o gargalo seria do script Ruby que faz as requisições no outro servidor, no caso desse script eu tranformei o gargalo em banda e na capacidade do outro servidor de me responder. Situação bem melhor do que gargalo na requisição em si.

Ouvi dizer que essa não é a maneira mais fácil de lidar com o problema, certamente no Ruby NeverBlock é uma solução legal. Como eu disse, preciso olhar com atenção.

Boa parte do código serve para estimar o tempo necessário para conclusão da tarefa e para exibir quanto tempo cada Thread está levando.
O ideal é achar um bom número para colocar no método join da Thread. Tente deixar esse número um pouco acima do tempo médio que o servidor remoto (ou serviço externo) responda tranquilo suas requisições sem cair no mesmo problema de bola de neve. O grande segredo do código está nesse valor e você precisa ajustar ele para sua realidade.

Notas da brincadeira:

  • Prefira utilizar Ruby 1.8.6. A documentação do 1.8.7 não está muito real. Principalmente com bibliotecas, onde enfrentei um probleminha com as classes Date e Time no 1.8.7.
  • Cuidado com comparação de Floats (principalmente se tratando de datas), Floats são números muito mais complexos do que parecem.
  • Hadoop