Fundamentos para arquitetura de sistemas com bom desempenho

Este capítulo possui uma versão mais recente:

Devemos esquecer as pequenas eficiências, digamos cerca de 97% das vezes: a otimização prematura é a raiz de todos os males. No entanto, não devemos perder nossas oportunidades nesses 3% críticos.
Donald E. Knuth
Desempenho sempre será um atributo de qualidade importante, em qualquer arquitetura. Sistemas com desempenho satisfatória têm usability, avialability e scalability comprometidos. Em última instância, sistemas que não atendem expectativas mínimas de bom desempenho falham em cumprir os objetivos do negócio.
0
Concorda?x

Pela relevância, as expectativas mínimas de bom desempenho devem ser determinadas o mais cedo possível. Depois disso, devem ser consideradas na avaliação das proposições de design arquiteturais candidatas.

Inicialmente, as expectativas de desempenho podem ser expressas pelos tempos percebidos pelos usuários na utilização de features chave. Entretanto, na medida em que decisões de design são realizadas, devem ser especializadas para cada componente.
0
Considerações?x

É importante, entretanto, não ignorar que iniciativas para melhorar o desempenho geralmente adicionam complexidade e podem prejudicar o evolvability. Desempenho é fundamental, mas, se perseguida cegamente pode atingir níveis desnecessários que só corroem a manutenabilidade, incrementando os custos.
0
Concorda?x

 

Usabilidade define desempenho

Desempenho é determinante para melhorar a usabilidade. Entretanto, distante do óbvio, algumas vezes o segredo não é “aumentar a velocidade”, mas diminuir.

William Santos, integrante da primeira turma de mentoria em arquitetura de software compartilha um cenário curioso, relacionado a sistemas de Home Broker. Segundo ele destaca, frequentemente as cotações de renda variável são atualizadas em intervalos menores aos que o olho humano é capaz de captar. Nestes casos, desempenho não necessariamente é positiva. Para tornar as atualizações perceptíveis, em um dos sistemas em que esteve envolvido, foi estabelecido um intervalo mínimo para propagá-las, de modo a descartar o envio da cotações que seriam imperceptíveis. Desta forma, apenas atualizações perceptíveis passaram a ser transmitidas, o que aumentou o conforto do cliente, levando-o a ter mais clareza sobre as mudanças e podendo tomar decisões mais bem pensadas – ao mesmo tempo em que reduziu o consumo de recursos para a transmissão de cotações.

Definindo desempenho

De maneira superficial, desempenho, como atributo de qualidade, trata do tempo que um sistema necessita para completar uma determinada categoria de atividades. De forma mais precisa, entretanto, a desempenho também tem relação direta com a capacidade para suportar workloads – com tamanhos diversos em intervalos com mínimos e máximos bem-definidos – e restrições de recursos computacionais.

Um sistema tem desempenho “good enough” quando atende as expectativas relativas aos tempos para executar processamentos, sob uma determinada carga, sem saturar os recursos computacionais disponíveis. Tudo isso, otimizando a função de avaliação da arquitetura, geralmente o custo.
0
Concorda?x

Quando o 'negócio' não sugere desempenho

O ideal é sempre alinhar expectativas de desempenho antes de fazer qualquer decisão de design. Entretanto, com frequência, as expectativas do negócio são inexistentes ou vagas.

Tem pouca utilidade, por exemplo, determinar que o response time de um determinado endpoint deve ser abaixo de 300 ms se não se conseguir estimar um workload associado.

Quando sistemas são elaborados sem essa “expectativa” clarificada, é útil realizar testes de estresse afim de determinar a realidade atual e validar se esta é suficiente. Eventualmente, essa abordagem, entretanto, pode revelar “tempo perdido” com uma soluções que “ficam devendo”, ou desperdício com soluções que “sobram demais”.

Relação com o custo

A boa arquitetura de software atende os objetivos do negócio, respeitando restrições e atingindo parâmetros expressos nos atributos de qualidade. Para isso, trata do design dos componentes, suas responsabilidades e relacionamentos, tudo isso, de acordo com uma função objetivo, geralmente o custo.
0
Você concorda?x

Melhorar a desempenho sempre impacta o custo, por isso, é importante determinar que abordagem utilizar. As vezes, melhorar a infraestrutura custa menos do que melhorar o código. Outras vezes, melhorar o código compensa a economia em infraestrutura. É importante ter sensibilidade.

Aspectos determinantes para o desempenho

Há quatro aspectos determinantes para o desempenho de um sistema de software e que, por isso, são preocupações arquiteturais: tamanho do workload, restrições para recursos computacionais, estratégias de enfileiramento e qualidade interna de implementação dos componentes.

Tamanho do workload

É fundamental, para o design de sistemas com bom desempenho, conhecer qual será o tamanho do workload que deverá ser suportado. Geralmente, é útil (e até mais fácil) realizar três tipos de previsões: pessimista, otimista e realista.

Por exemplo, para sistemas Web interativos é interessante saber a quantidade usuários simultâneos, o padrão de comportamento desses usuários, o volume de dados será manipulado (tanto em comandos quanto em consultas), etc.
0
Que outras categorias consegue apontar?x

É a partir da previsão do workload que é possível determinar se o dimensionamento da infraestrutura é suficiente.

Restrições para recursos computacionais

O conhecimento das capacidades de processamento e características de desempenho das tecnologias autorizadas é determinante, no design arquitetural, para decisões relacionadas a organização e estrutura dos componentes.

As capacidades de resposta das tecnologias permitem antecipar qual será a realidade do desempenho, inclusive, estratégias para mitigar riscos de saturação.

Hardware faz diferença!

Certa vez, o time de desenvolvimento de um cliente comprometeu semanas de trabalho “otimizando” uma feature complexa que fazia uso intensivo de leituras e escritas em dispositivo persistente. Alguns testes apontavam ganhos de desempenho de 2000%! Entretanto, os ganhos não foram percebidos no ambiente produtivo.

No ambiente de desenvolvimento e testes o dispositivo persistente era um HDD. Em ambiente produtivo, era um SSD…

Estratégias de enfileiramento

Invariavelmente, em qualquer sistema, recursos computacionais poderão não estar disponíveis sempre que uma demada ocorrer – seja por não estarem em estado de prontidão ou por contenção em outra atividade. Sempre que isso acontece, há espera (wait time).

.NET Socket Starvation

.NET tem uma falha conhecida de design na classe HttpClient. Por algumas razões, HttpClient não libera os sockets que utiliza quando o método Dispose é invocado, o que pode fazer com que a aplicação sofra com Socket Starvation ao criarmos muitas instâncias. Eventualmente, isso causa “enfileiramento” de processamentos que precisam de novos sockets causando redução de throughput e, eventualmente, afetando outros recursos, como memória e CPU.

Qualidade interna de implementação dos componentes

De pouco adianta haverem bons recursos computacionais se o código não fazer o uso correto destes recursos. Processadores com múltiplos core e sistemas single-thread. Memória de sobra e processadores potentes, mas interrupções frequentes de execução para coletas de lixo evitáveis. Etc.

Pareto e o desempenho

Empiricamente, é fácil observar que 20% das funcionalidades de um sistema são utilizadas 80% do tempo – trata-se da aplicação da proporção/princípio de Pareto. Geralmente, as exigências de desempenho serão maiores para essas funcionalidades.
0
Concorda?x

Também é comum observar que 80% dos recursos computacionais de um sistema são consumidos por 20% do código. Há uma visão romântica de que a melhoria do desempenho ocorre “escovando bits“, entretanto, na prática, geralmente os ganhos mais percebidos acontecem por adaptações do design arquitetural, por exemplo, pela adição de caching, buscando minimizar o uso de recursos com alto custo computacional, como a rede.

Eventualmente, esgotadas as alternativas arquiteturais para otimização, é comum observar que, dentro da zona crítica de código (20% que consome 80% de recursos), é possível identificar novamente trechos críticos – uma espécie de Pareto2 (6% do código, responsável por 64% dos recursos). Geralmente, a otimização desses trechos é realizada pela escolha apropriada de bons algoritmos e estruturas de dados.
0
Considerações?x

Em sistemas com arquitetura otimizada para o desempenho, que adotam algoritmos certos e estruturas de dados apropriadas, eventualmente, observa-se uma espécie de Pareto3 (0,8% do código, responsável por 51% dos recursos). Nesses casos, e apenas nesses casos, há espaço para implantação de micro otimizações.

Relação com a escalabilidade

Desempenho e escalabilidade são atributos relacionados, porém distintos. A escalabilidade diz respeito a capacidade de um sistema de manter sua performance, em cenários de aumento de workload, mediante a adição de recursos computacionais em proporção favorável.

Um sistema que têm seu desempenho deteriorado frente ao aumento do workload, sem recuperação frente a adição de recursos computacionais, não é, de forma alguma, escalável. 

Para tornar mais clara a diferença, considere um sistema de e-commerce. Nele, o desempenho terá relação, por exemplo, com o tempo necessário para que um número esperado de clientes efetivem o checkout. Enquanto isso, a escalabilidade tem relação com a capacidade de ajustar o sistema para que quantidades maiores de clientes possam ser atendidos, mediante adição razoável de recursos (em proporção igual ou menor ao crescimento do workload).

A degradação do desempenho, frente ao aumento do workload, mesmo com a adição favorável de recursos, é indicador de atenção para problemas escalabilidade. 

Métricas comuns relacionadas o desempenho

O desempenho de um sistema deve ser avaliada tanto por métricas de tempo, observáveis externamente, quanto pelo consumo de recursos. É correto afirmar que o desempenho de um sistema melhora tanto quando os tempos observáveis de processamento são reduzidos, quanto quando há diminuição de demanda por recursos computacionais para cumprir as expectativas de tempo de processamento.
0
Concorda?x

Tendo em vista o cuidado com a utilização de recursos, qualquer métrica de consumo poderá ser associada ao desempenho. Já as métricas de tempo geralmente são response time, turnaround time ou throughput.

Eventualmente, o uso combinado de métricas de tempo e de consumo dão origem a bons indicadores.

Todas as métricas relacionadas ao desempenho são candidatas a composição de fitness functions.
0
Concorda?x

Response time ou Turnaround time, nunca ambos

Considere uma API cujo uma das atribuições é receber arquivos, potencialmente grandes, com grande quantidade de dados para processamento. Como avaliar seu desempenho? Pelo response time, considerando tempo de resposta para o usuário após a requisição, ou pelo turnaround time, considerando o tempo total para processamento do arquivo recebido?

Processar arquivos grandes em uma interface interativa compromete o response time, não só daquela requisição, mas de todas as demais para aquele serviço. Afinal, “segura” recursos importantes e gera enfileiramento.

A abordagem mais recomendada é enfileirar o arquivo grande em um serviço de backend, que será avaliado pelo turnaround time e responder o mais rápido possível, ou seja, com o melhor response time.

Se há dúvidas sobre qual indicador usar, geralmente, a saída é decompor solução em dois componentes, cada um com sua métrica apropriada.

Response time

Em sistemas com interação com usuários, o response time indica o tempo consumido entre uma tarefa (ou ação) ser disparada e uma resposta satisfatória ser gerada. Valores comuns para response time são, geralmente, expressos de frações de segundo.

Quando aplicável, o response time costuma ser a métrica de desempenho mais observada. Afinal, é aquela que tem relação mais próxima com a satisfação dos usuários.
0
Concorda?x

Sistemas com bom projeto de desempenho devem manter response time regular, com pouca ou nenhuma degradação frente ao aumento do workload, desde que não ocorra saturação dos recursos computacionais indicados como restrição.
0
Considerações?x

Turnaround time

Quando há necessidade de avaliar o desempenho do processamento de lotes de dados, como em dinâmicas de consolidação, a métrica de a ser observada é o turnaround time, ou seja, o tempo transcorrido entre o início do processamento do lote e a conclusão. Valores para turnaround time podem ser expressos em segundos, minutos, horas e até dias.

Não é incomum que o negócio relacione o turnaround time com “janelas de processamento”. Ou seja, períodos regulares, bem-definidos, que precisam ser respeitados.

Throughput

O throughput tem relação com a quantidade de operações de um determinado tipo que um sistema consegue executar em um intervalo de tempo definido.

Para fins de comparação, quando os lotes de processamento têm tamanho significativamente variável, ou em operações assíncronas respondendo uma fila, é interessante medir o throughput.

O throughput também é ser utilizado como indicativo de quantas requisições um sistema consegue atender simultaneamente.

Relação com recursos computacionais

Há uma relação forte entre o desempenho de um sistema e a capacidade dele de utilizar recursos adequadamente. 

Melhorar o desempenho de um sistema geralmente implica em: 1) fazer melhor uso dos recursos computacionais ou 2) aumentar a quantidade de recursos computacionais disponíveis. Obviamente, o aumento de recursos computacionais, embora frequentemente efetivo, implica no incremento direto do custo.
0
Considerações?x

A melhor utilização dos recursos computacionais pode acontecer tanto pela utilização de técnicas de otimização de código quanto pela “substituição” do uso de recursos computacionais mais caros (por exemplo, acesso a disco ou a um servidor remoto) por outros mais baratos (memória RAM).

Relação com filas

A utilização adequada de recursos computacionais, essencial para a bom desempenho, depende do entendimento básico da teoria das filas.
0
Concorda? Considerações?x

A maioria dos recursos computacionais – tais como processadores, discos e mecanismos para transferência de dados –  implementam algum suporte para enfileiramento. Em sistemas Web, por exemplo, requisições são enfileiradas sempre que o ritmo de chegada de novas requisições (arrival rate – ex: 5 req/s) for maior do que o ritmo com que os servidores conseguem atender essas requisições (service rate – ex: 3 req/s). O traffic intensity é determinado pela proporção percentual entre arrival rate service rate.

Contenção e Disponibilidade de Recursos

Eventualmente, o enfileiramento não é “suportado” por um recurso em si, mas acontece em decorrência do próprio fluxo de execução do código e concorrência.

Quando um recurso computacional está sob contenção, ou seja, “bloqueado” atendendo uma demanda, nega novas requisições e cabe aos demandantes estabelecer políticas de retentativas, estabelecendo uma espécie de enfileiramento.

Eventualmente, um recurso está indisponível, mesmo que inoperante. Nesses casos, também caberá aos “demandantes” implementar políticas de retentativa.

Quando o traffic intensity permanece acima de 100% por algum tempo, há um aumento na queue length (quantidade de demandas esperando por serem atendidas e sendo atendidas em um determinado momento) e no wait time (tempo total na fila). A consequência direta observável é a degradação do desempenho. Além disso, caso demandas expirem (por time-out) ou o limite de capacidade da fila seja atingido, constata-se saturação do recurso.

Idealmente, recursos muito demandados devem, ao longo do tempo, otimizar o service time (tempo necessário para um recurso atender uma demanda e estar pronto para atender uma próxima) como medida para impactar menos o response time e, também, evitar a saturação.

A forma natural de “aliviar” o traffic intensity é paralelizando o atendimento das demandas em diversas instâncias do tipo de recurso computacional sendo demandado. Dessa forma, fazendo com que o service rate supere o arrival rate.

Em um sistema saudável, onde demandas não são perdidas e o traffic intensity seja menor que 100%, o throughput será igual ao arrival rate.

Filas causam quedas em “efeito dominó”

A imagem abaixo relata comportamento de um sistema durante um incidente de desempenho e demonstra uma característica comum.

A formação de filas, frente a determinados recursos que estão indisponíveis, seja por contenção ou prontidão, acaba estressando outras partes de um sistema, notoriamente CPU, gerando quedas em dominó.

Utilization Law

utilization law aponta que a taxa de utilização de um recurso computacional (eventualmente paralelizado) pode ser determinada pelo produto de seu throughput e a média do service time. Por exemplo, um recurso que trata 3 requisições por segundo, com um service time de 100ms tem utilização de 0.3 (ou, 30%).

Idealmente, a taxa de utilização de um recurso computacional não deve ultrapassar 80%.

Táticas modernas para garantir o desempenho

Sistemas com bom desempenho são aqueles que conseguem, utilizando os recursos computacionais especificados nas restrições, garantir que o tempo médio de atendimento de demandas, combinando processamentos (service times) e esperas (wait times), esteja conforme aos objetivos do negócio. Para isso, devem manter a taxa de utilização desses recursos computacionais abaixo de 80% considerando a carga máxima prevista.

A garantia do desempenho se dá, então, pelo controle rigoroso do arrival rate limitando-o ao especificado no projeto da arquitetura e ao equilíbrio entre o service time e o wait time para garantir que o response time de cada recurso seja o estipulado. Seja pela otimização do recurso em si, ou pelo tuning do ambiente (determinando número de instâncias)

Embora as medidas para melhoria de desempenho dependam de especificidades de cada sistema, há um conjunto comum de táticas frequentemente adotadas, sobretudo sob o ponto de vista arquitetural.

Priorização de requests

Todas as requests são importantes. Mas, algumas são mais importantes que as outras. Eventualmente, um sistema precisará garantir recursos suficientes para atender uma categoria requisições em detrimento de outras.

Uma medida comum é apartar a infraestrutura, dedicando recursos ao atendimento exclusivo de determinadas funcionalidades, separando enfileiramentos. Não raro, é útil segmentar também o sistema de software em componentes, facilitando processos de deploy e manutenção.

Outra boa medida para suportar o desempenho é desacoplar requisições mais complexas em etapas mais simples, usando, então, abordagens assíncronas, melhorando o desempenho percebida com respostas rápidas, enquanto parte do processamento fica postergado.

Desacoplando o aceite e a confirmação de pedidos

Sistemas de e-commerce têm adotado, frequentemente, a separação do aceite de um pedido e a confirmação mediante pagamento.

O aceite do pedido acontece rapidamente, com ótimo response time. Entretanto, este limita-se a “enfileirar” solicitação para confirmação, que é um processo com maior custo computacional.

Aceitar pedidos rapidamente é prioridade frente a confirmar pedidos!

Redução do overhead com chamadas remotas

Rede e GC são duas fontes comuns de problemas de desempenho. O agrupamento de serviços pode reduzir boa parte da pressão sobre a rede e um bocado de intervenções dos mecanismos de GC, demandando menos filas e melhorando o service time.

Componentes cross-cutting também podem se converter, rapidamente, em gargalos de processamento e devem ser adotados com parcimônia. Nada mais infeliz do que ver o desempenho de um sistema comprometida por mecanismos ingênuos de logging e segurança.

Adoção de Rate limiters

A desempenho de um sistema tem clara relação com as restrições relacionadas aos recursos ao ambiente operacional. O esperado é que a desempenho se mantenha previsível enquanto houverem recursos disponíveis, geralmente, entretanto, ela é comprometida quando os limites de recursos são ultrapassados. Por isso, uma das formas de proteger da desempenho é a utilização de rate limiters.

Rate limiters podem ser implementados em client-side, server-side ou em um middleware. Implementações em client-side tem como mérito principal a redução da pressão sobre a rede. Já implementações em server-side ou middleware são mais “confiáveis”, por estarem em ambientes gerenciados, e robustas, visto que permitem regras mais complexas.

De forma prática, a estratégia consistem em limitar o throughput como forma de proteger o response time.

Qualificação no consumo de recursos

Eventualmente, pode ser importante revisar a implementação dos componentes para garantir que recursos estejam sendo utilizados de maneira eficiente. Não raro, por exemplo, memória e CPU são comprometidos desnecessariamente em função de implementações ingênuas, causando aumento do service time. Outros bons exemplos são conexões com banco de dados e uso impróprio do sistema de arquivos que, com frequência, implicam em enfileiramentos.

Adoção de programação paralela e concorrente

Componentes devem aumentar o throughput usando scale out , seja externamente, pela criação de mais instâncias, devidamente balanceadas, seja internamente, pela adoção de threads. Essas medidas reduzem filas de requisições.

Em sistemas modelados para alto nível de paralelismo, é essencial evitar contenção de recursos. Isso é possível tentando criar o mínimo possível de recursos que possam ser utilizados apenas por um único “cliente” por vez.

Implementação de Caching

Caching pode gerar aumento perceptível de desempenho! Seja para evitar consultas complexas para o banco de dados ou para armazenar o resultado de computação de alto custo, caching é uma forma simples de substituir recursos de custo elevado por outros mais baratos, evitando enfileiramentos.

Aumentar a quantidade de recursos

Em muitas condições, pode-se amenizar e até resolver problemas de desempenho utilizando melhor infraestrutura. Entretanto, é importante estar atento que “colocar mais lata” (scale up) é uma solução paliativa e raramente sustentável no longo prazo.

Sumarizando

A desempenho é característica fundamental em qualquer arquitetura. Ela impacta muitos outros atributos de qualidade, sobretudo o custo de um sistema. Para ser bem projetada, depende do alinhamento de expectativas quanto a tempos de processamento e volumes de workload.

Melhorias de desempenho geralmente estão associadas com uso mais adequado de recursos computacionais. Quanto melhor o uso, mais desempenho. Um bom indicativo do uso efetivo dos recursos computacionais está na formação de filas “saudáveis” que garantam throughput equivalente ao arrival rate.

// TODO

Antes de avançar para o próximo capítulo, recomendo a você as seguintes reflexões:

  • Quais são os recursos computacionais que tem causado maiores problemas de desempenho no sistema em que você está trabalhando agora?
  • Quais são as taxas de utilização desses recursos?

Referências bibliográficas

BASS, Len; CLEMENTS, Paul; KASMAN, Rick. Software Architecture in Practice. 3. ed. Old Tappan, Nj: Pearson Education, 2012. (The SEI Series in Software Engineering).

BONDI, André. Foundations of software and system performance engineering: process, performance, modeling, requirements, testing, scalability and practice. Santa Barbara, Ca: Addison Wesley, 2015.

ERDER, Murat; PUREUR, Pierre; WOODS, Eoin. Continuous Architecture in Practice: software architecture in the age of agility and devops. Boston, Ma: Addison-Wesley, 2021. (Vaughn Vernon signature).

GREGG, Brendan. System Performance: enterprise and the cloud. 2. ed. Mountain View, Ca: Addison-Wesley, 2020. (Addison-Wesley Professional Computing Series).

KNUTH, Donald E.. Structured Programming with go to Statements. Acm Computing Surveys, Stanford, Ca, v. 6, n. 4, p. 261-301, dez. 1974. Disponível em: https://dl.acm.org/doi/10.1145/356635.356640. Acesso em: 25 jun. 2021.

LIU, Henry. Software performance and scalability: a quantitative approach. Danvers, Ma: Wiley Publisher, Inc, 2009. (Quantitative Software Engineering

Compartilhe este capítulo:

Compartilhe:

Comentários

Participe da construção deste capítulo deixando seu comentário:

Inscrever-se
Notify of
guest
0 Comentários
Feedbacks interativos
Ver todos os comentários

Fundador e CEO da EximiaCo, atua como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Mentoria

para arquitetos de software

Imersão, em grupo, supervisionada por Elemar Júnior, onde serão discutidos tópicos avançados de arquitetura de software, extraídos de cenários reais, com ênfase em systems design.

Consultoria e Assessoria em

Arquitetura de Software

EximiaCo oferece a alocação de um Arquiteto de Software em sua empresa para orientar seu time no uso das melhores práticas de arquitetura para projetar a evolução consistente de suas aplicações.

ElemarJúnior

Fundador e CEO da EximiaCo, atua como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

+55 51 99942-0609 |  contato@eximia.co

+55 51 99942-0609  contato@eximia.co

0
Quero saber a sua opinião, deixe seu comentáriox
()
x