← Professional Cloud DevOps Engineer: entrega e fiabilidade
13 / 20 · 135 MIN

SRE: medição, capacidade e recuperação

Definir SLIs, interpretar orçamentos e coordenar decisões de capacidade, recuperação e retirada de serviços.

1. Escrever o contrato de medição

Uma equipa fictícia suporta uma API usada para consultar o estado de instruções de processamento. Antes de escolher uma percentagem, escreve quem utiliza a API, que operações contam, quando começa a espera e qual o resultado útil. Neste exercício, contam pedidos reais válidos, incluindo os que falham dentro do serviço. Testes sintéticos e pedidos inválidos ficam fora, segundo uma regra aprovada antes da medição. Uma falha de processamento não transforma um pedido válido num pedido excluído. Guarda a definição com versão, responsável, janela e origem dos dados, para que APS e desenvolvimento consigam reproduzir o relatório. Num lote com 1200 registos, excluir 200 testes e 100 pedidos inválidos distintos deixa 900 pedidos elegíveis. Se 18 falharam, o indicador de sucesso é 882/900, ou 98%. Para um processo noturno, escolhe uma unidade que represente a entrega: ficheiros únicos válidos disponíveis até à hora acordada. Se 95 de 100 chegaram até às 06:00 e cinco só às 06:12, o resultado dentro do prazo é 95%, mesmo que todos os processos terminem com exit 0. Estes números e regras são exemplos originais, sem relação com políticas de qualquer banco. No handover, pede ao responsável funcional que confirme a unidade e o prazo antes de discutir alertas.

2. Calcular sem perder a população

Dois intervalos apresentam 90 sucessos em 100 pedidos e 891 em 900. Somar as contagens produz 981/1000, ou 98,1%. A média simples de 90% e 99% produz 94,5% porque atribui o mesmo peso a intervalos com volumes diferentes. Um indicador baseado em janelas responde a outra pergunta: quantas janelas cumpriram a condição definida? Não troques entre as duas unidades no mesmo relatório. Em Cloud Monitoring, consulta a definição efetiva do SLO e distingue requestBased de windowsBased antes de interpretar o painel. Agora define um indicador interno em que cada pedido bom tem de ter êxito e terminar em até 250 ms. Entre 10 000 pedidos, 80 falharam, 150 foram lentos e 30 pertencem aos dois grupos. Há 80 + 150 − 30 = 200 pedidos maus. A sobreposição é retirada uma vez, pois cada pedido só ocupa uma posição no denominador. Este cálculo composto é um modelo de aprendizagem, não uma configuração pronta de BasicSli. Antes de o implementar, identifica métricas ou eventos capazes de representar a interseção. Dois totais independentes, sem informação sobre sobreposição, não permitem reconstruir exatamente este resultado. Pede uma amostra de eventos e verifica os casos na fronteira de 250 ms.

3. Tratar lacunas e governar o orçamento

O registo de entrada identifica 400 pedidos elegíveis, mas o coletor conserva apenas 360 resultados, todos com êxito. Os 40 restantes têm resultado desconhecido. Publicar 100% para toda a população esconde a falha de recolha; publicar 90% como falha comprovada também excede a evidência. Regista a cobertura incompleta, investiga os identificadores em falta e aplica apenas a regra de dados desconhecidos que tenha sido acordada. Uma política conservadora pode existir, mas precisa de estar explícita. Em reuniões, comunica separadamente o que aconteceu ao serviço e o que se consegue medir. O orçamento também tem unidades. Com 800 000 pedidos e alvo de 99,95%, são permitidos 400 pedidos maus. Não existe conversão única para minutos sem saber a carga afetada em cada intervalo. Num período móvel de 28 dias, reparar hoje não apaga o impacto de ontem. Se a política inclui falhas de fornecedores, excluí-las retroativamente para libertar uma release altera o acordo. Mantém o cálculo e encaminha a decisão para quem pode autorizar exceções. As políticas devem definir responsáveis, ações e exceções; o exemplo publicado pela Google não é uma obrigação universal nem a política interna da tua organização.

4. Exercício: reconciliar antes de concluir

Guarda o código desta aula como run.py e executa python3 run.py com Python 3.13. O exercício usa apenas a biblioteca padrão e imprime JSON. A população fictícia contém 900 pedidos elegíveis, 200 sintéticos e 100 inválidos. Entre os elegíveis, 18 falham, 36 excedem 250 ms e nove pertencem aos dois grupos. Prevê primeiro os resultados: 45 maus, 855 bons, SLI de 19/20 e orçamento de nove para o alvo didático de 99%. O saldo é −36 pedidos; não o arredondes para zero, pois isso esconderia a dimensão do excesso. Lê depois missing e unexpected. Remover o resultado r899 ou introduzir um identificador desconhecido deixa a cobertura por reconciliar; sli e slo_met ficam null. O conjunto vazio também não prova sucesso. Compara latency_boundary, budget_equality e fractional_budget para perceber a inclusão de 250 ms, a igualdade exata ao alvo e um orçamento inferior a um pedido. O programa percorre ainda 24 combinações de classificação e rejeita sete entradas inválidas. Estes ensaios verificam o modelo local. A lista esperada é fornecida como verdade do exercício: não há consulta a ingress real, validação de integridade de logs ou chamadas à cloud. Para produção, terias de demonstrar também como se obtém e protege essa lista.

5. Validar capacidade antes do failover

A equipa prevê deslocar 18 000 pedidos/s para uma região cuja capacidade ensaiada é 12 000/s dentro da latência acordada. Sem expansão imediata, faltam pelo menos 6000/s de capacidade. Decide antecipadamente que tráfego pode esperar e quem autoriza a prioridade. Limita a admissão à capacidade sustentável e acompanha a idade da fila e o trabalho efetivamente concluído. Uma fila sem limite apenas transfere o problema para memória, prazos e recuperação posterior. Acrescenta ao plano o tempo necessário para escoar o acumulado depois da reposição. Quota disponível permite alocação dentro de limites, mas não garante capacidade física. Uma reserva de VMs noutra zona não cobre automaticamente a zona escolhida; verifica correspondência de zona e propriedades. Em Cloud Run, uma aplicação single-threaded com quatro vCPUs pode saturar um thread enquanto a média de CPU ronda 25%. Revê concorrência, latência e CPU em conjunto, em vez de concluir que existe capacidade útil por ocupar. Num HPA Kubernetes baseado apenas em percentagem de CPU, requests efetivos em falta impedem definir a utilização relevante. Confirma requests e condições do HPA antes de mudar limites de réplicas. Cada mecanismo exige evidência própria; o ensaio de carga continua a precisar de uma carga e dependências representativas.

6. Conter amplificação e terminar trabalho

Repetir uma operação pode ajudar numa falha transitória, mas três camadas com três tentativas totais cada podem gerar 27 chamadas ao backend para um pedido inicial, se todas falharem e esgotarem as tentativas. Desenha onde cada repetição acontece e define um orçamento global, além de espera e dispersão das tentativas. Confirma também se a operação pode ser repetida sem duplicar efeitos. No cenário da aula, a carga já excede a capacidade: repetir imediatamente todos os pedidos recusados piora a pressão sobre a mesma dependência. Propaga o prazo restante. Se uma leitura tem 800 ms de orçamento total e já gastou 620 ms, restam no máximo 180 ms, ignorando aqui margens de transporte. Reiniciar 800 ms em cada serviço permite continuar trabalho depois de o cliente desistir. Na retirada de uma VM de um unmanaged instance group usado por um Application Load Balancer, coordena o draining com o ciclo de vida do processo. Configurar 90 segundos no balanceador não mantém vivo um processo que a tua automação termina ao fim de cinco. Observa pedidos em curso e critérios de terminação; ligações reutilizadas e outros tipos de balanceador têm particularidades que exigem consultar a configuração concreta. O exercício não executa draining nem comprova ausência de perda de pedidos.

7. Provar recuperação e preparar retirada

Num ensaio fictício, o incidente começa às 11:00, os dados recuperados chegam às 10:42 e o serviço funcional volta às 11:30. Com RPO de 15 minutos e RTO de 45, o tempo de reposição de 30 minutos cumpre o objetivo, mas o ponto recuperado perde 18 minutos e falha o RPO. Regista os dois resultados. Um endpoint acessível não demonstra por si só que os dados estão utilizáveis: acrescenta reconciliação, operações de negócio e dependências à aceitação. Conserva as horas e critérios usados, para que outra equipa possa repetir o raciocínio. Depois de migrar, sete dias sem tráfego não resolvem a dúvida sobre um consumidor mensal. Confirma o calendário, o proprietário e o destino desse consumidor antes de desativar a origem. Revê também backups, percursos de recuperação e dependências que ficaram fora da migração. Prepara uma decisão com evidências, responsáveis e prazo de observação adequado ao serviço. Atualiza runbooks, diagramas, monitorização e procedimentos de manutenção após a decisão. O ganho de custo só é sustentável se a retirada não eliminar uma capacidade ainda necessária; inclui o trabalho de desativação e validação no plano, em vez de o deixar como tarefa sem responsável depois da entrega.

8. Coordenar a decisão e entregar ao RUN

No caso final, uma região falha, a capacidade restante está limitada e parte da telemetria desaparece. Uma equipa quer desviar tráfego enquanto outra prepara o regresso ao percurso anterior. Antes de executar alterações incompatíveis, estabelece coordenação única, um responsável pelas operações e alguém que mantenha a comunicação. Regista a hipótese, a ação autorizada, o resultado esperado e a hora de reavaliação. Limitar tráfego segundo a prioridade aprovada pode ser uma mitigação útil enquanto se recupera capacidade. A ausência de erros nos poucos resultados observados não justifica anunciar recuperação total. Prepara uma passagem de turno em que outro colega consiga continuar sem reconstruir o incidente. Inclui capacidade medida, pedidos admitidos e adiados, idade do acumulado, cobertura da telemetria, última alteração e próximo critério de decisão. Na comunicação em inglês, pratica uma frase concreta: “Service is partially restored; lower-priority work remains deferred and telemetry reconciliation is still in progress.” Indica a próxima atualização sem prometer uma hora de recuperação que a evidência não suporta. O resumo da aula é uma sequência de trabalho: definir a população, reconciliar resultados, calcular na unidade certa, limitar carga, provar recuperação e confirmar dependências antes da retirada. Retoma estes critérios nos tópicos de alertas, FinOps e aceitação de produção.

"""Original SLI teaching model: fixed window, fictional ingress inventory."""
from fractions import Fraction
from hashlib import sha256
from itertools import product
from pathlib import Path
import json
import platform


def assess(records, expected_eligible_ids, target=Fraction(99, 100)):
    if not isinstance(target, Fraction) or not 0 < target < 1:
        raise ValueError('target must be an exact fraction strictly between zero and one')
    seen, eligible = set(), []
    for row in records:
        if not {'id', 'synthetic', 'valid', 'success', 'duration_ms'} <= row.keys():
            raise ValueError('missing field')
        if not isinstance(row['id'], str) or not row['id'] or row['id'] in seen:
            raise ValueError('empty or duplicate event identifier')
        if any(type(row[k]) is not bool for k in ['synthetic', 'valid', 'success']):
            raise ValueError('classification flags must be booleans')
        if type(row['duration_ms']) is not int or row['duration_ms'] < 0:
            raise ValueError('duration must be a nonnegative integer')
        seen.add(row['id'])
        if not row['synthetic'] and row['valid']:
            eligible.append(row)
    ids = {r['id'] for r in eligible}
    expected = set(expected_eligible_ids)
    complete = ids == expected
    good = sum(r['success'] and r['duration_ms'] <= 250 for r in eligible)
    failed = sum(not r['success'] for r in eligible)
    slow = sum(r['duration_ms'] > 250 for r in eligible)
    both = sum(not r['success'] and r['duration_ms'] > 250 for r in eligible)
    bad = len(eligible)-good
    assert bad == failed+slow-both
    evaluable = complete and bool(expected)
    budget = len(eligible)*(1-target) if evaluable else None
    return {'records': len(records), 'eligible': len(eligible), 'good': good,
            'bad': bad, 'failed': failed, 'slow': slow, 'both': both,
            'missing': sorted(expected-ids), 'unexpected': sorted(ids-expected),
            'coverage_complete': complete,
            'sli': str(Fraction(good, len(eligible))) if evaluable else None,
            'allowed_bad': str(budget) if evaluable else None,
            'remaining_bad': str(budget-bad) if evaluable else None,
            'slo_met': bad <= budget if evaluable else None}


def event(name, synthetic=False, valid=True, success=True, duration=250):
    return dict(id=name, synthetic=synthetic, valid=valid, success=success, duration_ms=duration)


def run():
    expected = {f'r{i}' for i in range(900)}
    records = [event(f'r{i}', success=i >= 18, duration=400 if i < 9 or 18 <= i < 45 else 250) for i in range(900)]
    records += [event(f's{i}', synthetic=True) for i in range(200)]
    records += [event(f'i{i}', valid=False) for i in range(100)]
    complete = assess(records, expected)
    assert (complete['eligible'], complete['good'], complete['bad']) == (900, 855, 45)
    assert (complete['failed'], complete['slow'], complete['both']) == (18, 36, 9)
    assert complete['sli'] == '19/20' and complete['allowed_bad'] == '9'
    assert complete['remaining_bad'] == '-36' and complete['slo_met'] is False
    missing = assess([r for r in records if r['id'] != 'r899'], expected)
    assert missing['sli'] is None and missing['missing'] == ['r899'] and missing['slo_met'] is None
    extra = assess(records+[event('unknown')], expected)
    assert extra['sli'] is None and extra['unexpected'] == ['unknown']
    empty = assess([], set())
    assert empty['sli'] is None and empty['slo_met'] is None
    boundary = assess([event('at-250'), event('at-251', duration=251)], {'at-250','at-251'})
    assert boundary['good'] == 1 and boundary['bad'] == 1
    equality = assess([event(f'e{i}', success=i > 0) for i in range(100)], {f'e{i}' for i in range(100)})
    assert equality['remaining_bad'] == '0' and equality['slo_met'] is True
    fractional = assess([event('tiny')], {'tiny'})
    assert fractional['allowed_bad'] == '1/100'
    assert Fraction(90+891, 100+900) == Fraction(981,1000)
    assert 80+150-30 == 200
    assert Fraction(800000)*(1-Fraction(9995,10000)) == 400
    assert 3**3 == 27 and 800-620 == 180
    assert 18000-12000 == 6000
    truth_cases = 0
    for synthetic, valid, success, duration in product([False,True], [False,True], [False,True], [249,250,251]):
        row = event('t', synthetic, valid, success, duration)
        chosen = not synthetic and valid
        result = assess([row], {'t'} if chosen else set())
        assert result['eligible'] == int(chosen)
        assert result['good'] == int(chosen and success and duration <= 250)
        truth_cases += 1
    malformed = [event('bad', duration=-1),event('bad', duration=True),event('bad', duration=1.5),event('bad', success='true'),event(''),{'id':'missing'}]
    invalid = 0
    for row in malformed:
        try:
            assess([row], {'bad'})
        except ValueError:
            invalid += 1
    try:
        assess([event('duplicate'),event('duplicate')], {'duplicate'})
    except ValueError:
        invalid += 1
    assert invalid == 7
    return {'scriptSha256':sha256(Path(__file__).read_bytes()).hexdigest(),
            'pythonVersion':platform.python_version(),
            'fixtures':{'complete':complete,'missing':missing,'unexpected':extra,'empty':empty,'latency_boundary':boundary,'budget_equality':equality,'fractional_budget':fractional},
            'truthCases':truth_cases,'invalidInputCases':invalid,
            'calculationChecks':6,'network':False,'persistentWrites':False,
            'vendorExecution':False,'independentVerification':False,
            'limitations':'Fixed-window trusted synthetic inventory; no actual ingress reconciliation, histogram query, collector, cloud API, or production acceptance decision.'}


if __name__ == '__main__':
    print(json.dumps(run(), ensure_ascii=False, indent=2))
NA PRÁTICA

Um failover deixa 6000 pedidos/s acima da capacidade e a recolha incompleta impede declarar recuperação total.

Armadilhas comuns

Excluir falhas do denominador, apresentar dados desconhecidos como sucesso, confundir quota com capacidade e retirar dependências mensais.

Tópicos relacionados: Alertas e cobertura de telemetria · FinOps e custo de capacidade resiliente · Aceitação de produção e retirada de aplicações

Leva esta ideia contigo

Uma decisão de fiabilidade precisa de população conhecida, capacidade demonstrada e resultados de recuperação verificáveis.

Criar conta

Referência: Implementing SLOs · Current linked guide; edition date unconfirmed (2026-09-30 inspection)

Google Cloud é uma marca comercial de Google LLC. A dr.pt é uma plataforma de preparação independente e não está afiliada, associada, patrocinada, autorizada nem aprovada por Google. Os conteúdos e as perguntas são originais, não são perguntas oficiais de exame, e concluir os nossos testes não atribui nem garante qualquer certificação. Os nomes são usados apenas para identificar o tema. Todas as outras marcas pertencem aos respetivos titulares.