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
[MNRI] - Release It! - http://pragprog.com/titles/mnee/release-it