Fundamentos para arquitetura de sistemas com boa performance

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
Performance sempre será um atributo de qualidade importante, em qualquer arquitetura. Sistemas sem performance satisfatória têm usability, avialability e scalability comprometidos. Em última instância, sistemas que não atendem expectativas mínimas de boa performance falham em cumprir os objetivos do negócio.
0
Concorda?x

Pela relevância, as expectativas mínimas de boa performance 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 performance 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 a performance geralmente adicionam complexidade e podem prejudicar o evolvability. Performance é fundamental, mas, se perseguida cegamente pode atingir níveis desnecessários que só corroem a manutenabilidade, incrementando os custos.
1
Concorda?x

Definindo performance

De maneira superficial, performance, como atributo de qualidade, trata do tempo que um sistema necessita para completar uma determinada categoria de atividades. De forma mais precisa, entretanto, a performance 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 performance “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.
1
Concorda?x

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 performance 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 a performance

Há quatro aspectos determinantes para a performance 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 boa performance, 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.
1
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 performance 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 da performance, 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 performance 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).

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 a performance

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 performance 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 da performance 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 a performance, 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

Performance 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 sua performance deteriorada frente ao aumento do workload, sem recuperação frente a adição de recursos computacionais, não é, de forma alguma, escalável. 
0
Ponderações?x

Para tornar mais clara a diferença, considere um sistema de e-commerce. Nele, a performance 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 da performance, 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 a performance

A performance de um sistema deve ser avaliada tanto por métricas de tempo, observáveis externamente, quanto pelo consumo de recursos. É correto afirmar que a performance 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 a performance. 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 a performance 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 sua performance? 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 performance 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 performance 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 a performance 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 a performance de um sistema e a capacidade dele de utilizar recursos adequadamente. 

Melhorar a performance 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 boa performance, 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 da performance. 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.

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 a performance

Sistemas com boa performance 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 da performance 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 performance 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 a performance é desacoplar requisições mais complexas em etapas mais simples, usando, então, abordagens assíncronas, melhorando a performance 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 performance. 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 a performance de um sistema comprometida por mecanismos ingênuos de logging e segurança.
0
Considerações?x

Adoção de Rate limiters

A performance de um sistema tem clara relação com as restrições relacionadas aos recursos ao ambiente operacional. O esperado é que a performance 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 performance é 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.

Adotaçã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 performance! 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 performance utilizando melhor infraestrutura. Entretanto, é importante estar atento que “colocar mais lata” (scale up) é uma solução paliativa e raramente sustentável no longo prazo.

// 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 performance 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
7 Comentários
Oldest
Newest Most Voted
Feedbacks interativos
Ver todos os comentários
William Santos
William Santos
3 anos atrás
Feedback no conteúdo deste capítulo É importante, entretanto, não ignorar que iniciativas para melhorar a performance geralmente adicionam complexidade e podem prejudicar o evolvability. Performance é…" Ler mais »

Há casos nos quais a performance pode ser restringida para fins de usabilidade. Um caso prático são sistemas de Home Broker. Frequentemente as cotações de renda variável são atualizadas em intervalos menores aos que o olho humano é capaz de captar. Neste caso, performance não necessariamente é positiva. Para tornar as atualizações perceptíveis, 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.

Castilho
Castilho
3 anos atrás
Feedback no conteúdo deste capítulo Um sistema tem performance "good enough" quando atende as expectativas relativas aos tempos para executar processamentos, sob uma determinada carga,…" Ler mais »

Existem cenários onde só temos o workload. Negócio não tem definido o tempo de execução e nem o recurso computacional. Seria um bom caminho executar um teste de carga em cima do workload informado e alinhar com negócio se o tempo é satisfatório? E se o custo do recurso computacional utilizado está dentro do esperado?

Antonio Nascimento
Antonio Nascimento
3 anos atrás
Feedback no conteúdo deste capítulo Por exemplo, para sistemas Web interativos é interessante saber a quantidade usuários simultâneos, o padrão de comportamento desses usuários, o…" Ler mais »

Uma prática interessante para identificar potenciais problemas de performance é efetuar testes de carga escalonando a quantidade de acessos simultâneos e comparando o response time. Se o response time subir de maneira maior que linear, existem problemas na aplicação.

Raphael Castilho
Raphael Castilho
3 anos atrás

A não utilização ou a utilização de forma inadequada de async await pode causar ThreadPool starvation, gerando problemas de performance em aplicações .Net.

Antonio Nascimento
Antonio Nascimento
3 anos atrás

Uma prática que não está 100% relacionada com performance mas que vale citar é a adoção de Circuit Breaker para a proteção caso algum recurso remoto sofra problemas. Assim se evita o acumulo de requisições a um recurso que sabidamente já está com problemas.

Douglas Picolotto
Douglas Picolotto
3 anos atrás

Um problema bastante comum quando falamos sobre a implementação de componentes em .NET (especialmente no full framework), é a utilização do HttpClient de forma inadequada, criando instâncias e utilizando o Dispose a cada request, acreditando que os recursos utilizados serão liberados. O fato é que o HttpClient não libera os sockets no Dispose, o que pode fazer com que a aplicação sofra com Socket Starvation ao criarmos muitas instâncias (Documentação oficial).

Douglas Picolotto
Douglas Picolotto
3 anos atrás

Alias, a forma como o Dispose é implementado no HttpClient pode ser considerado como uma falha de design, pois ela induz o desenvolvedor ao erro, já que não é aderente ao conceito de descarte fornecido pelo IDisposable.

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

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