Arquivo

Archive for the ‘Desenvolvimento’ Category

Twitter-Twoofer

Não que eu seja um ótimo desenvolvedor. Ainda escorrego bastante. Até demais, eu diria.

O caso é que vejo críticas diárias de colegas desenvolvedores no Twitter e pela aleatoriedade e quase total ausência de contexto que o Twitter proporciona, fica difícil absorver o valor dela e o embasamento que levou a pessoa a criticar.

Nesse ritmo de eterna aprendizagem que o mundo do desenvolvimento é, não ajuda muito ler comentários esparsos, superficiais e sem linearidade. Eu prefiro que o comentário seja somente um convite à descoberta da crítica seguido de um link de um artigo em que lá haja começo, meio, fim, argumentos e uma verbalização mais detalhada da visão e dos questionamentos que motivaram a crítica.

Cito desenvolvimento e desenvolvedores porque isso faz parte da minha vida. Mas obviamente vale para qualquer assunto e profissão.

(e eu também preciso aprender a me embasar melhor antes de falar)

Categorias:Desenvolvimento

Apresentando: HaavokIPC

Então você pega um projeto de sistema feito em PHP 4 (um dos ~7% de sites que ainda usam essa versão) e precisa usar algum componente que só funciona no PHP 5. Você pensou em um web service local em uma segunda instância do Apache rodando PHP 5, mas dadas as características do ambiente onde a aplicação roda e da dificuldade de fazer o deploy do segundo servidor, você foi proibido pelo chefe de fazer isso. Você pode também executar código entre diferentes versões de PHP ou mesmo na mesma versão, mantendo uma parte da sua aplicação em uma sandbox.

Para isso há várias soluções, dependendo dos requisitos. O HaavokIPC dá conta destes: chamadas síncronas a funções ou métodos de objetos/classes; uso dos dados retornados através de callbacks; uso em parte da aplicação que não exige alta performance. Ele não usa servidores ou conexões, ele executa o binário do PHP e se comunica com ele através de um arquivo serializado ou persistência. Os objetos responsáveis pela persistência e serialização são plugáveis e novos objetos podem ser desenvolvidos sem perda de tempo, apenas respeitando a interface. Por enquanto estão disponíveis os seguintes objetos de persistência, chamados de Drivers: File, Memcache e Shm, que usam, respectivamente, um arquivo, um servidor do memcache ou shared memory de ambientes *nix. Para a serialização, estão disponívels os Serializers Default, que usa as funções serialize e unserialize nativas do PHP e Msgpack, que usa a extensão msgpack.

Ele foi desenvolvido para ser compatível com boa parte das versões de PHP entre 4 e 5.

A documentação ainda não está madura e há muito a se fazer no código em termos de performance e arquitetura, como todo bom projeto experimental.

O código está disponível no GitHub -> HaavokIPC/PHP

Update 25/11/2011
Estou portando o HaavokIPC para Ruby e isso me obrigou a fazer mudanças na arquitetura, a fim de tornar a configuração e a conversa entre front end e back end mais fácil, seja de PHP para PHP, PHP para Ruby ou de Ruby para PHP. Criei um novo repositório para este port, disponível em HaavokIPC/Ruby. Mais pra frente espero portá-lo para outras linguagens.

Update 30/11/2011
O desenvolvimento do HaavokIPC em Ruby está congelado enquanto trabalho no refactoring da versão em PHP, com alterações mais profundas do que simplesmente tornar sua configuração mais flexível.

Por uma web mais rápida: compressão radical de JavaScript – parte 2

No post anterior, descrevi o cenário em que um código em JS deveria ser entregue com um nível de compressão excepcional, visto que ele seria bastante requisitado. Expliquei um pouco do funcionamento do Packer para JavaScript e dei algumas dicas para otimizar o uso dele de forma que não pese no lado do cliente.

Neste ponto, um código de 36 KB (XRegExp + parte da aplicação) já está razoávelmente comprimido, mas não o suficiente para um ambiente com muitas requests: advertising.

A solução encontrada foi compressão pelo servidor com GZip. Otimização, aliás, recomendada no post do Sérgio Lopes e não só por ele!

É uma solução muito eficaz para entrega de conteúdo estático. Se utilizada com cache, então, fica lindo. Mas essa opção não se adequava ao caso, já que o JS era “eventualmente estático” e qualquer alteração das regras impactava no conteúdo do JS, seja modificando o tamanho para mais ou menos, mas, sempre modificando o arquivo. Daí a necessidade de não cachear esses arquivos e garantir que toda alteração feita pelo usuário da plataforma seria imediatamente refletida no script.

Então foi decidido: compressão máxima pelo servidor onde os arquivos JS (um para cada site, cada cliente com vários sites) seriam hospedados. Packer  + Gzip nível 9 – compressão de 36 para menos de 4 KB. Incrível! (~4 KB * clientes * sites * milhões) já parece ser um número bem melhor!

Nesse ponto, fiquei incomodado – mesmo sem ter feito qualquer benchmark – sobre como o servidor responderia tendo que efetuar tantas compressões on-the-fly, sem caching e acabei chegando a uma solução muito, mas muito simples e eficaz:

Para conteúdos estáticos e muito requisitados, não deixe o servidor comprimir durante a requisição. Entregue comprimido.

Sim, isso! Armazenar o arquivo já comprimido! O backend já era responsável por obter as regras, concatenar a XRegExp, as regras e o código que faz tudo funcionar; agora também é responsável por comprimir o JS com GZip. Gastar algum tempo de processamento comprimindo 36 KB (ou mais) em 4 KB? Nunca na requisição, só no momento em que uma regra for modificada ou em algum cron job na madrugada executado por garantia.

E o servidor?

A não ser que ele seja instruído a não fazer isso, e manter a compressão habilitada para tudo, ele vai comprimir o que já foi comprimido. Então, aqui está o maior truque disso tudo:  configurar o servidor para caso uma request peça por um arquivo armazenado em determinado diretório, deixe a compressão e envie uma header Content-Encoding: gzip.

E aquele browser que não tem suporte a gzip?

Não pode entregar como gzip. Excepcionalmente, o backend também trata de gerar os scripts somente com a compressão do Packer. Nesse caso, usar cache é importante, porque uma parcela de usuários receberá um arquivo um pouco maior que os outros e é importante não deixar a performance do servidor ser impactada por isso.

Usamos  o Lighttpd e foi muito fácil configurá-lo para agir assim. Para caching, o escolhido foi o Varnish, mas como não fui responsável pela configuração, não posso dar detalhes.

O conceito mais importante dessa parte do post não se aplica ao JS ou ao comportamento da aplicação, mas do servidor. A idéia de entregar conteúdo já comprimido ao invés de utizar compressão on-the-fly se aplica a qualquer tipo de conteúdo que não seja interessante, do ponto de vista da performance, de ser entregue através de métodos específicos de caching. E a conclusão é de que isso não é mais do que um método alternativo de caching.

Considerei a abordagem de usar caching mais forte, separando a XRegExp, com um tempo de cache maior, enquanto as regras e o código ficariam com um tempo menor de cache. Mas aí não seria tão “emocionante” e se por algum motivo a XRegExp não fosse carregada, a aplicação não funcionaria.

Concluído o projeto, me desliguei da empresa com uma proposta mais interessante, deixando uma documentação bastante forte sobre essas idéias. Preciso dar uma olhada nos sites dos clientes para ver se eles já estão utilizando tal aplicação!

Por uma web mais rápida: compressão radical de JavaScript – parte 1

Post inspirado pela palestra do Sérgio Lopes sobre otimização de sites, que foi apresentada na #QConSP. Gostaria de ter demonstrado esse caso no momento, como sugestão para todos da área, mas eu sou tímido. 😛

Em uma empresa onde trabalhei no ano passado, tive que desenvolver um código JS que usa uma biblioteca adicional, XRegExp, para named capture em expressões regulares. Juntando essa biblioteca e meu código, o tamanho total ficava em cerca de 36 KB – no mínimo, já que o código era modificado por variáveis que o cliente da plataforma poderia configurar. A projeção de hits no servidor solicitando um JS era da ordem de milhões por mês, dado que é uma plataforma de advertising. Considerando que cada site deve incluir esse JS e um cliente tem vários sites: (+36 KB * clientes * sites * milhões): não resulta em um número muito bom.

* Detalhe importante: o JS não é totalmente estático. Caso o cliente da plataforma modificasse uma regra, ela teria que ser imediatamente aplicada ao JS, logo, não poderia ficar muito tempo em cache do usuário. O jeito encontrado foi servir sem cache e com o máximo possível de compressão.

Há 3 anos eu conheci o Packer, um compactador de JavaScript que é bastante utilizado por frameworks JS e módulos de pipelining em frameworks web – detalhe que na época eu o conheci através de um código malicioso e como eu queria saber o que o código estava fazendo, então tive que estudar a forma como ele era compactado e o que significava aquela function(p,a,c,k,e,r) no começo do arquivo. Ele tem muito valor e utilidade em ambientes onde você precisa fazer entrega de conteúdo com eficiência.

Ele não é tão simples como outros códigos que eliminam comentários, espaços, tabs, pontos-e-vírgulas duplicados, quebras de linha, deixando a estrutura básica da linguagem para que o código seja funcional. Ele faz bem mais que isso. Explico agora:

– O Packer parseia seu código JS, fazendo tudo o que eu descrevi ali em cima e também separa cada palavra-chave ou valor, montando um array* de strings.
– Cada palavra armazenada no array possui um índice numérico e esse índice é usado como referência no código que o Packer gera.
– O Packer cria um wrapper, que é a função anônima (p,a,c,k,e,r), responsável por descompactar o código gerado, buscar as referências no array e remontar o código, substituindo as referências pelos valores respectivos, terminando com um eval() para interpretar o código remontado.
– Como as referências são únicas e as strings usadas para descrevê-las são pequenas, muitas vezes usando números de base 62 – dependendo da configuração, o código gerado é bastante econômico e sucinto.

* Este array de strings é na verdade uma string só, com seus valores delimitados por pipes

Vou dar um exemplo bem simples de como o packer vai montar um o código compactado:

function stack_messages(strings) {
var i = 0;
for (i = 0; i < strings.length(); i++) {
var message = “Annoying message #” + i + “: ” + strings[i]”;
alert(message);
}
}

Esse é o “array” de palavras úteis que o Packer gerou após fazer o parsing:

‘|i|var|function|stack_messages|for|length|Annoying|message|alert||’

E gerar o seguinte código:

‘3 4(a){2 1=0;5(1=0;1<a.6();1++){2 b=”7 8 #”+1+”: “+a[1]”;9(b)}}’

Não parece, mas esta é a função stack_messages. Ignorando chaves, pontos-e-vírgulas, parênteses e outros símbolos que obviamente não podem ser reduzidos, é possível entender que que: a referência 3 corresponde ao índice 3 no “array”, que quer dizer “function”. A referência de número 4 é “stack_messages”. Isso é só pra começar.

Note algo interessante: palavras que foram utilizadas várias vezes no código, como var, message e i só aparecem uma vez no array. Elas possuem as referências 2, 8 e 1, respectivamente.

‘3 4(a){2 1=0;5(1=0;1<a.6();1++){2 b=”7 8 #”+1+”: “+a[1]”;9(b)}}’

Trocando de 1 a 7 caracteres por 1 só. Inteligente demais, não?

* Não entendi o porque da palavra message ter duas referências, 8 e b, mas assim que descobrir, atualizo o post.

Não vou me aprofundar nos detalhes do algoritmo de descompressão do Packer, mas por cima é possível notar que:

– È um algoritmo iterativo: o único código que roda imediatamente é ele e o código desenvolvido por você não vai rodar até que ele tenha iterado o array de palavras úteis e transformado o código traduzido em código válido para ser interpretado com eval(). A velocidade com a qual ele roda depende bastante da velocidade da máquina e do browser de seu cliente.
– Não é bom para códigos pequenos. O overhead causado pela função de descompressão não compensa o trabalho da compressão.
– Concatene todos os arquivos JS que sua página necessita através de um pipeliner antes de comprimir com o Packer. Isso faz com que o tamanho do array de palavras úteis seja menor e centralizado. No caso de frameworks e bibliotecas de terceiros, escolha as versões non-minified e non-packed. O ideal é não judiar do forçar o browser do seu cliente a executar o descompactador do Packer mais de uma vez, seja em cada arquivo, seja em “compressão em cima de compressão”, como um código packed passando novamente pelo Packer.

Nesse ponto, meu código (XRegExp + minha aplicação) já está “minified” com o Packer, mas ainda está pesado para o cenário de milhões de requests. Vou explicar na parte 2 como o servidor foi otimizado para servir arquivos com compressão.

%d blogueiros gostam disto: