domingo, maio 24, 2015
Testando ZeroMQ - PUSH PULL Clean Shutdown
A ideia do projeto é só incorporar coisas conforme a necessidade. No caso, estou precisando da algo melhor para paralelizar workers. Seguindo a sugestão do Mereghost, dei uma olhanda no ZeroMQ a fiquei agradevelmente impressionado.
Ao invés de um sistema "faz tudo" de mensageria, encontrei uma biblioteca que se propõe a ser apenas um socket on steroids para facilitar a comunicação inter-processos (e inter-threads). Para o que eu precisava, é perfeito (e rápido que dá medo)!
O maior problema é que precisava de algo que avisasse os workers que o trabalho acabou, mas que não os matasse no meio do processamento. Procurei aqui e ali, mas não achei um modelo que servisse. Mãos à massa e comecei a experimentar coisas, cheguei ao resultado aí embaixo.
A coisa toda ainda precisa de mais testes e acho que a ideia também carece de um certo amadurecimento, mas achei que ficou legal, e ainda por cima, funciona! :D
Esse é o cara que vai receber as mensagens. Ele verifica se a mensagem recebida é uma string "EOF", indicando que o sender já enviou tudo. Se sim, ele pára e avisa o sender que já terminou (usando outro canal).
Esse é o cara que envia as mensagens. Depois de mandar tudo ele envia uma string "EOF" para cada worker que disse que trabalharia com ele. Como não sabe quem é quem, ele manda um "EOF" e espera alguém responder, quando recebe a resposta ele desmarca esse worker da lista e manda outro "EOF". Assim continua até a lista de workers estar vazia.
Funciona bastante bem, a coisa só não é melhor porque o ZeroMQ manda um bolo da mensagens de cada vez, então, se por algum motivo um dos workers está mais lento que o resto, ele empaca todo mundo até terminar sua cota de mensagens.
Pelo que testei, quanto maior a mensagem, menor esse problema pois o "bolo" que ele manda contém menos mensagens. Mas, convenhamos, esse não é um grande problema.
O ponto mais grave é que se um worker morrer (ou for interrompido), as mensagens que estavam com ele vão pras cucuias.
Acredito que tem como contornar essas coisas, mas por enquanto, ainda não achei jeito :)
terça-feira, maio 12, 2015
Acentos em expressões regulares (Ruby)
Já tentou fazer expressões regulares para dar match em letras acentuadas?
Desagradável...
É comum chegar em algo do tipo:
/[\wáéíóúâêôç...]+/
Mas não se desespere, pequeno gafanhoto. Existe um meio MUITO mais fácil! \o/
/[[:word:]]+/
Não é tão elegante quanto um simples "\w" (que não pega caracteres acentuados), mas é muito mais curto ^_^
Testa aí no IRB, e depois procura por POSIX bracket expressions na documentação do Regex do Ruby, tem mais coisas úteis lá (gosto muito do [[:upper:]]).
terça-feira, agosto 13, 2013
Ruby method parameters
Encontrei essa ontem: desde a versão 1.9.2-180 existe uma introspecção que retorna quais são os parâmetros de um método.
Confuso? Vai um pouco de código, então:
Ele retorna um array com arrays de 2 elementos, o primeiro symbol é o tipo e o segundo o nome do parâmetro:
- :req - parâmetro obrigatório
- :rest - parâmetros variáveis (usando "*" splat)
- :key - parâmetro nomeado (novo no Ruby 2.0)
sábado, agosto 10, 2013
VIM - "Power of g" + "normal" (massacrando logs)
Essa página aqui me deu a idéia de mostrar uma das coisas que mais gosto no VIM: massacrar logs (e outros arquivos maiores do que minha preguiça permite trabalhar manualmente).
Aqui nesse site aqui tem alguns exemplos de logs legais para brincar: http://www.monitorware.com/en/logsamples. Eu peguei os do PostFix (e-mail) para usar de exemplo.
Copiar todas as linhas com "reject:" para o final do arquivo
:g/reject:/t$
":g" executa comandos em todas as linhas em que a expressão regular der match. O comando "t" copia a linha para um certo endereço. "$" é o endereço para "fim do arquivo".
Se você quiser mover a linha ao invés de copiá-la, troque o "t" por "m".
Copiar todas as linhas com "reject:" para o registrador "a" (para colar onde quiser depois com "ap)
qaq (isso limpa o registrador "a")
:g/reject:/yA ("y" copia a linha e "A" adiciona ao registrador "a")
ou
:g/reject:/normal "Ayy
Tudo o que vier a seguir do comando "normal" é tratado como instruções do VIM. Ou seja, no exemplo acima "Ayy adiciona a linha no registrador "a" da mesma maneira que você faria na edição normal do vi. "normal" é o comando mais cabeludamente ninja do VIM, pois permite que vocẽ execute macros nas linhas que deram match!.
Aí sim!!!!
Copiar as linhas com "Domain not found" para o final e ficar apenas com o endereço de destino
:g/Domain not found/t$ | s/.*to=<// | s/>.*//
Dá para separar comandos com "|" (pipe). No caso acima, dupliquei para o fim do arquivo e já removi começo e fim de linha que não interessavam. Quando as operações são mais complicadas, o pipe é indispensável ;)
É isso! Eu gosto de pensar no VIM não é um editor de textos, mas como um editor de código com uma linguagem de programação interativa (e suscinta) embutida :D
quinta-feira, junho 13, 2013
Sobrevivendo ao Arch Linux - Atualizando Repositórios do PACMAN
Depois de alguns anos feliz e contente usando o Ubuntu, resolvi me aventurar no Arch Linux (mérito de convencimento do Marcello (vulgo "Cabello") e Marcel (vulgo "Barba"), seus vagabundos!)
**AVISO AOS TRANSEUNTES INCAUTOS**
O Arch Linux não é para os desavisados, a premissa dele é que você precisa entender o sistema para usá-lo. O que significa um bocado de leitura e pesquisa na internet. Use ele se quiser acelerar seu aprendizado, não para ter resultados rápidos e instantâneos sem esforço.
Dado o aviso, vamos ao que interessa: Pequenas dicas para tornar a vida mais fácil.
Atualizando Repositórios
Você tem sua boa e velha lista de repositórios do Arch Linux, mas quando vai instalar algum programa usando o "pacman" ou o "yaourt", percebe que alguns estão lentos, ou pior, alguns pacotes não são encontrados. O que fazer?
> sudo reflector -l 10 --sort rate --save /etc/pacman.d/mirrorlist > sudo pacman -Syy
O reflector é um pacote que você pode instalar via o próprio pacman (ou yaourt):
Rode esse comando uma vez a cada mês que você vai estar sempre bem.
terça-feira, junho 04, 2013
Do the simplest thing that could possibly work
Ontem na empresa surgiu uma excelente discussão sobre o ponto.
Pelo que andei fuçando, existe um grande debate (até hoje) sobre o que significa "Simplest". IMHO:
"Simplest" ≠ "Easiest" ≠ "Quickest"
Essa frase aqui do Ward Cunningham é boa para ilustrar:
We choose simple solutions first so that we can maintain focus on the customer's problem. Simple means easy to reason about. Easy serves as a synonym for simple so long as we remember that it is the thinking that should be easy. -- WardCunningham
(ênfases minhas)
Em termos de arquitetura, minha tradução para "mais simples" se resume a pensar em 4 aspectos:
- Metáforas (mais fácil de explicar)
- Simetria (mesma coisa o tempo todo = menos coisas para entender)
- Componentes pequenos (menor = mais fácil de entender)
- Protocolos (não preciso entender o "como")
sábado, fevereiro 12, 2011
Arquitetura de Software: Performance
As pessoas da área de exatas normalmente se esquecem, mas a comunicação verbal não é uma coisa lá muito certinha, grande parte das palavras não possui definição exata e depende muito do contexto. Então, antes de começar, vamos evitar a confusão e a baderna e vamos definir direitinho o que é "Performance" no contexto desse artigo.
Quando digo "Perfomance" estou falando do tempo que leva entre o usuário disparar uma operação e o sistema retornar o resultado desejado. Quanto menor o tempo, mais "performance" [SAIP].
Outro conceito que quero deixar claro é o de "Tempo de Resposta", que defino aqui como o tempo que leva entre o usuário disparar uma operação e o sistema responder algo de volta. Quanto menor o tempo, menos "tempo de resposta". [WKRT]
Repare na importante diferença: "retornar o resultado" vs "responder algo".
Não é a mesma coisa? Não, não é. Eu posso ter uma operação extremamente lenta (baixa performance), mas se ela for respondendo de tempos em tempos "estou trabalhando" (responder algo), o usuário não vai ter a impressão que meu sistema está travado. O exemplo mais típico é o de cópia de arquivos, sem aquela barra de progresso, qualquer usuário acharia que a máquina travou.
Por que essa diferença é tão importante? Porque, dependendo do sistema que você está envolvido, "performance" não é o que você quer, e sim "tempo de resposta". Um dos casos mais típicos é o da aplicação web que demora para responder, o usuário pensa que travou e clica no botão "refresh", disparando a operação uma segunda vez. Encontre um usuário com o dedo nervoso e você terá dezenas de operações iguais concorrendo no servidor e um usuário cada vez mais frustrado do outro lado.
Então, antes de fazer a pergunta "como garantir que meu software tenha Performance", você deve responder "preciso de Performance ou de Tempo de Resposta?"
Um boa "thumb rule" é que sistemas para o público em geral devem perseguir primeiro Tempo de Resposta, já sistemas que são suporte a outros sistemas (APIs, Frameworks e Linguagens de Programação, por exemplo), devem perseguir Performance antes de qualquer coisa.
Esclarecido o ponto, vamos ver o que podemos fazer para garantir Tempo de Resposta e o que podemos fazer para garantir Performance.
Ambas são qualidades de software que podem ser garantidas com "táticas arquiteturais" [SAIP]. Uma "tática" é uma decisão de design, ou seja, uma diretiva de como o software vai ser feito.
Táticas para Tempo de Resposta
O importante do tempo de resposta passar para o usuário percepção de que o sistema não travou e continua trabalhando.
Segundo Jakob Nielsen [JNRT], o comportamento do usuário varia com o tempo de resposta:
- até 0,1 segundos;
- entre 0,1 e 1 segundo;
- entre 1 e 10 segundos;
- acima de 10 segundos.
Um tempo de resposta de até 0,1 segundo é o que o usuário espera de coisas em que ele está no controle, tais como bater uma tecla e ver a letra aparecer na tela.
De 0,1 até 1,0 segundo é o tempo que o usuário espera que o sistema esteja "fazendo algo". Operações como salvar ou apagar devem ter mais de 0,1 segundo, caso contrário o usuário vai pensar que nada aconteceu e tentar novamente. Em algumas operações AJAX, por exemplo, a resposta pode ser imediata (salvar em background ao clicar, por exemplo), então, uma tática interessante é adicionar um pequeno delay nessas operações se elas não ultrapassarem 0,1 segundo.
De 1 até 10 segundo, o usuário começa a perder o foco que você precisa indicar que está trabalhando. Grande parte das páginas web possui carregamento entre 1 e 3 segundos [MTBC], em sistemas web, esse tempo tende a ser maior devido a operações mais complexas. Ou seja, se você está trabalhando em sistemas web, conte com o "loading" do browser para ajudar, mas se você está usando AJAX, a tática recomendada é sempre colocar alguma dica visual ("loading" animado, por exemplo) de que o sistema está trabalhando, pois é quase certo que várias requisições levarão mais de 1 segundo para responder.
Acima de 10 segundos, o usuário pára de esperar a operação finalizar e começa a fazer outra coisa. A tática recomendada nesse caso é apresentar uma barra de progresso ou ao menos um "loading" animado. Uma tática interessante apresentada por Matt Kelly, é colocar algo divertido, como uma pequena animação não convencional ou algo que o usuário possa interagir enquanto espera.
Se a operação puder levar minutos para completar, a tática recomendada é dispará-la em background e avisar ao usuário para buscar o resultado mais tarde. Em tempos de internet, isso pode significar algo do tipo "mandaremos um e-mail (sms/twitter/IM) com o link para os resultados quando o processo estiver concluído". Essa é tática usada pela Pragmatic Programmer Bookshelf, por exemplo, quando se pede para baixar um e-book e ele ainda não foi gerado.
A apresentação de Matt Kelly da ZURB na jQuery Conference de Boston possui outras táticas muito interessantes de como lidar com esses tempos de resposta em sistemas web [MTBC]. Só tenha certeza de pegar os slides para assistir junto com o vídeo.
Táticas para Performance
"We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil" - Donald Knuth
Um bom tempo de resposta pode não bastar para as operações mais frequentes no sistema. Como elas são muito utilizadas, o usuário pode ficar irritado se ficar esperando sempre até que a operação seja concluída. Em casos como esse, não há outra alternativa senão tentar melhorar a performance.
Performance ruim é frequentemente causada não pelo sistema como um todo, mas sim por pequenos trechos e operações. Esses são os famigerados "gargalos". O grande trabalho para a arquitetura de software não é evitar esses gargalos, mas sim providenciar maneiras de identificá-los rapidamente.
As táticas, nesse caso, serão focadas em sistemas web para o público em geral, e não APIs ou Frameworks.
Em primeiro lugar, uma boa parte dos sistemas web costumam seguir o modelo arquitetural de repositório [SAIN]. Ou seja, um banco de dados (o repositório) é o centro do sistema e a maior interação é entre banco e a página web (a parte) ao invés das páginas interagirem fortemente entre si.
Esse é um excelente modelo, pois é bastante desacoplado. É possível trocar a partes com relativa facilidade, desde que não se modifique muito o repositório. Ele possui um problema com o rastreio das informações, pois é difícil descobrir qual parte alterou um dado que outra parte precisa, ou seja, existe uma dependência indireta via dados.
Nesse tipo de sistema, os maiores gargalos de performance inevitavelmente se concentram ao se movimentar grandes massas de dados no repositório, seja criando, recuperando ou alterando. Em miúdos: o gargalo de performance está normalmente em operações pesadas de banco de dados.
Uma tática NÃO recomendada é tentar aliviar o banco de dados passando o processamento para a aplicação. Isso normalmente diminui a performance, pois toda a operação que seria feita diretamente agora precisa ser passada pela rede até a aplicação, carregada em memória (novamente, pois se ela foi recuperada do banco, teve que ser carregada na memória do banco também), processada e novamente serializada e enviada de volta para o banco de dados.
Outra tática NÃO recomendada é tentar otimizar o sistema enquanto não se sabe onde no sistema a performance é pior [DKSP]. Essa "tática" é conhecida como "otimização prematura" e aumenta a complexidade do sistema sem garantias de resultado.
A melhor das abordagens para problemas de performance é monitorar a execução do sistema para identificar quais operações são mais lentas e então remover o gargalo.
A primeira tática recomendada é monitorar o tempo de todas operações de banco de dados da aplicação, isso pode ser feito facilmente, bastando logar seu tempo de execução. Para isso, é preciso que todas as operações de bancos de dados (consultas, inserções, alterações), sejam executadas por um único mecanismo, dessa maneira, registrar o tempo de execução não custa mais quem meia dúzia de linhas de código.
Além de monitorar o tempo de execução da operações de banco de dados, é interessante monitor o tempo de execução das páginas (tempo entre a "request" recebida e "response" enviada). Assim, mesmo quando o gargalo não está no banco, ainda é possível identificá-lo. Ao se monitorar os ambos, é possível saber não somente em que parte do sistema está o gargalo, mas se ele é causado pela aplicação ou pelo banco de dados.
Uma prática controversa, mas que me deu excelentes resultados, foi logar a pilha de execução (stack trace) junto com o tempo de execução do banco de dados. Dessa forma, consegui identificar rapidamente em que parte do sistema o SQL estava sendo gerado, por vezes, o sistema gerava partes do SQL em lugares diferentes. Recomendo essa prática para os primeiros meses de vida do sistema e principalmente se seu sistema possuir muitas consultas dinâmicas.
Outra tática é usar "timeouts", para garantir que as operações de banco de dados não ultrapassem um limite tolerável [MNRI][SAIP]. Apesar dessa ser uma tática recomendada para "Estabilidade", ela é muito útil para impedir que uma operação pesada deixe o sistema lento para todos os outros usuários.
Ambas as táticas devem ser usadas não somente com o banco de dados, mas também com qualquer dependência que o software tenha com sistemas externos, tais como web APIs.
Uma tática extra ao se lidar com sistemas externos é o uso do padrão "Circuit Break"[MNRI][TRCB]. Apesar de não estar diretamente relacionado com performance, ele garante que o software não sofra com instabilidade de sistemas externos.
Uma vez identificado o gargalo, basta aplicar uma série de técnicas para tentar removê-lo. Coisas como "memoization", "buffers", "índices e planos de execução forçados" e principalmente melhoria de algoritmos. Infelizmente essas técnicas estão além do escopo desse (já longo) artigo. Por outro lado, recomendo muito que a equipe tenha pessoas bem versadas nessas técnicas para que os gargalos sejam removidos rapidamente assim que identificados.
Espero que tanto texto tenha sido de alguma utilidade!
[SAIP] - Software Architecture In Practice - http://www.amazon.com/Software-Architecture-Practice-2nd-Bass/dp/0321154959
[JNRT] - http://www.useit.com/papers/responsetime.html
[MTBC] - http://events.jquery.org/2010/boston/video/video.php?talk=matt-kelly
[SAIN] - http://www.cs.cmu.edu/afs/cs/project/vit/ftp/pdf/intro_softarch.pdf
[DKSP] - http://pplab.snu.ac.kr/courses/adv_pl05/papers/p261-knuth.pdf