Uma etapa muito comum em processos seletivos para engenheiros de software, especialmente nessa era pós pandemia, é a entrevista de system design. Se você é um profissional de nível sênior ou superior, é quase certo que enfrentará um desafio desse tipo, e dada a sua importância, gostaria de compartilhar algumas dicas sobre um dos primeiros passos cruciais, para essas entrevistas: back of the envelope estimations!
OBS: Caso não faça ideia do que seja system design, deixo aqui indicação de dois sites muito legais para estudar mais / entender sobre o assunto:
O que é back of the envelope estimations?
Segundo Jeff Dean, Google Senior Fellow, em tradução livre: "back-of-the-envelope estimations são estimativas que você cria usando uma combinação de hipóteses suas e números de performance comuns para ter uma boa noção de quais designs atenderão aos seus requisitos”.
Basicamente, antes de iniciarmos a construção / arquitetura do sistema, é ideal estimarmos alguns requisitos de capacidade e performance. Isso nos permite tomar decisões mais acertadas e adequadas para o cenário em questão. A essas estimativas damos o nome informal de "back of the envelope estimations".
Potência de 2
No mundo da computação trabalhamos com bits, isso é o básico. Quando estamos estimando algo como armazenamento de dados, precisamos trabalhar com potência de 2, pois estamos lidando com sequências de bits (normalmente sequências de bytes, que são 8 bits). Ter uma tabela de potências de 2 na ponta da língua pode ajudar muito:
Potência | Valor aproximado | Nome completo | Abreviação |
---|---|---|---|
10 | 1 mil | 1 Kilobyte | 1 KB |
20 | 1 milhão | 1 Megabyte | 1 MB |
30 | 1 bilhão | 1 Gigabyte | 1 GB |
40 | 1 trilhão | 1 Terabyte | 1 TB |
50 | 1 quadrilhão | 1 Petabyte | 1 PB |
Além dessa tabela, é útil ter em mente os tamanhos dos tipos de dados elementares para estimar requisitos de armazenamento:
Tipo de dado | Tamanho (byte) |
---|---|
int | 4 |
float | 8 |
boolean | 1 |
UTF-8 character | 1 |
UNIX timestamp | 4 |
Números de latência que todo programador deveria saber
No mundo da computação, latência geralmente se refere ao tempo que uma determinada ação leva para ser executada, ou seja, num contexto de rede, latência pode se referir ao tempo que um pacote demora pra ir de um nó A até o nó B, no contexto de armazenamento, latência pode ser referir ao tempo que o disco demora para gravar uma informação X ou ler a mesma informação. No contexto de banco de dados, latência pode ser referir ao tempo que uma query demora pra ser executada e retornar seu resultado, e por ai vai.
Dentro deste contexto de latência, o Dr. Jeff Dean lista alguns números importantes pra termos na cabeça:
Nome da operação | Latência |
---|---|
L1 cache reference | 0.5 ns |
Branch mispredict | 5 ns |
L2 cache reference | 7 ns |
Mutex lock/unlock | 100 ns |
Main memory reference | 100 ns |
Compress 1K bytes with Zippy | 10,000 ns = 10 µs |
Send 2K bytes over 1 Gbps network | 20,000 ns = 20 µs |
Read 1 MB sequentially from memory | 250,000 ns = 250 µs |
Round trip within the same datacenter | 500,000 ns = 500 µs |
Disk seek | 10,000,000 ns = 10 ms |
Read 1 MB sequentially from the network | 10,000,000 ns = 10 ms |
Read 1 MB sequentially from disk | 30,000,000 ns = 30 ms |
Send packet CA (California) ->Netherlands->CA | 150,000,000 ns = 150 ms |
Um primeiro disclaimer sobre esta tabela é que os números definitivamente estão obsoletos, uma vez que os computadores evoluem e se tornam mais rápidos e poderosos constantemente, porém, não temos nenhum ônus em utilizarmos esta tabela para back of the envelope estimations, pois estamos trabalhando com estimativas, bem rudimentares e aproximadas, que serão utilizadas em entrevistas de system design.
A ideia é termos noção de ordem de grandeza e não de números exatos.
Interpretações dos números
A partir desses dados, podemos tirar algumas conclusões valiosas para nossos designs:
- Há uma diferença de magnitude significativa entre diferentes operações (exemplo ler um dado da rede / disco vs ler um dado da memória)
- Datacenters costumam estar localizados muito longe um dos outros, tornando custoso o tráfego de dados entre eles.
- Utilizando um algoritmo simples de compressão conseguimos economizar muito em largura de banda no tráfego de rede
- Escritas são 40x mais lentas do que leituras
- Dados globais compartilhados são caros, isto é uma limitação fundamental de sistemas distribuídos, a contenção de locks em objetos que são muito escritos acaba matando a performance do sistema, uma vez que precisamos executar transações seriais (uma de cada vez)
- Desenhe sua arquitetura para escalar escritas, pois são as operações mais custosas
- Otimize seu sistema para minimizar locks de escrita, garantindo que dados possam ser escritos simultaneamente sem um lock
- Otimize seu sistema para paralelismo, ou seja, garanta que escritas possam ser executadas de maneira paralela sempre que possível
Números de disponibilidade
Disponibilidade é a medida que indica o tempo em que um sistema está online e responsivo dentro de um determinado período. Normalmente, disponibilidade é expressa em porcentagem, onde 100% significa que o sistema esteve disponível todo o tempo analisado. A maioria dos sistemas considera aceitável uma disponibilidade entre 99% e 100%.
Um termo frequentemente usado no contexto de disponibilidade é SLA (Service Level Agreement), que é um valor acordado em contrato sobre a disponibilidade de um serviço. SLAs são frequentemente expressos em "números de 9", que indicam a quantidade de 9s após a vírgula (decimais).
A tabela a seguir expressa os valores aproximados de SLA para diferentes períodos de tempo:
Disponibilidade % | Downtime por dia | Downtime por semana | Downtime por mês | Downtime por ano |
---|---|---|---|---|
99% | 14.40 minutos | 1.68 horas | 7.31 horas | 3.65 dias |
99.99% | 8.64 segundos | 1.01 minutos | 4.38 minutos | 52.60 minutos |
99.999% | 864.00 milissegundos | 6.05 segundos | 26.30 segundos | 5.26 minutos |
99.9999% | 86.40 milissegundos | 604.80 milissegundos | 2.63 segundos | 31.56 segundos |
Exemplo prático de análise
Imagine que você precisa gerar uma página de resultados de uma busca com 30 thumbnails (imagens), como seria a análise de performance deste requisito?
Proposta 1 - Design Serial
Nesta proposta iremos ler as imagens do disco sequencialmente (uma após a outra).
A conta de performance seria:
30 X ( 10 ms / leitura de disco + ( 0,256 MB * 30 ms / MB ) ) = 530 ms
Explicando a conta:
Como são 30 thumbnails, multiplicamos por 30 a quantidade de tempo de leitura de cada thumbnail.
Cada thumbnail gasta o tempo da leitura em disco mais o tempo necessário para transferir esses dados do disco pra memória, ambos os valores estão nas tabelas acima, mas pra referência seriam 10 ms / leitura de disco e 30 ms / MB transferido.
Além dos números da tabela, estimamos que cada thumbnail tenha 256 KB de tamanho (ou 0.256 MB).
Resolvendo a equação, chegamos ao número de 530 ms como estimativa para gerar a página, levando em conta apenas a leitura dos thumbnails.
Proposta 2 - Design Paralelo
Nesta proposta, as leituras em disco seriam feitas em paralelo, usando, por exemplo, diferentes threads para ler simultaneamente do disco.
A conta ficaria:
10 ms / leitura de disco + 0,256 MB * 30 ms / MB = 18 ms
Explicando a conta:
Aqui, a conta é similar à do design sequencial, mas não multiplicamos o resultado por 30, já que as leituras são feitas em paralelo.
Para tornar o cenário mais realista, podemos adicionar alguns milissegundos para sincronização de threads e variação nas leituras de disco, onde teríamos algo entre 30 e 60 ms, ainda assim muito mais rápido que o design serial, mostrando o poder do paralelismo!
Analisando as propostas
Qual cenário é melhor?
A resposta pra essa pergunta vai depender de cada caso e de seus requisitos, entretanto, tendo as estimativas de cada design, podemos compara-los de maneira mais assertiva e optar por aquele que entrega melhor performance e garante que os requisitos estejam sendo satisfeitos.
O importante é que agora você tem uma base sólida para comparação, permitindo responder de forma mais precisa a perguntas que possam surgir durante uma reunião de whiteboarding ou uma entrevista de system design, usando números concretos para embasar suas decisões.
Concluindo
Back-of-the-envelope estimations é uma técnica que ajuda a termos uma visão mais holística e baseada em números de diferentes designs e soluções, sendo uma ferramenta importante pra quando estamos tentando desenhar algum sistema.
Tenha em mãos números básicos de performance e armazenamento de partes relevantes utilizadas em seus sistemas, saber de antemão estes números, antes das reuniões de whiteboarding / entrevistas de system design, ajuda a fazer estimativas mais precisas, uma vez que é impossível estimar corretamente sem uma base de informações.
Além disso, monitorar e medir periodicamente seus sistemas garante que essas estimativas se tornem cada vez mais precisas, pois você estará utilizando dados reais e específicos dos componentes do seu sistema. Isso não só melhora a qualidade das estimativas, mas também fortalece a sua capacidade de tomar decisões informadas e eficazes.
Top comments (0)