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

Sinais durante a entrega: comparar versões e fundamentar a promoção

Relaciona revisão e telemetria, compara populações, interpreta distribuições e distingue sucesso técnico de evidência suficiente.

1. Ligar a observação à versão executada

Num rollout de uma aplicação fictícia de fundos, três equipas podem dizer que a entrega está concluída por razões diferentes. A plataforma confirmou o deployment, a aplicação confirmou que o processo arrancou e o suporte encontrou um painel sem alertas. A decisão de alargar tráfego precisa de relacionar esses factos com a revisão que serviu os pedidos e com os resultados produzidos. Antes da janela, define os campos que permitem ligar release, ambiente, serviço, versão e observações. Conserva a referência da consulta e o intervalo usado para que outra pessoa consiga repetir a avaliação. OpenTelemetry fornece service.version para descrever a versão do componente. Mantém service.name como nome lógico e verifica se o backend conserva a versão necessária à comparação. Se o binário novo continua a emitir a versão antiga, a ausência da série candidata não prova ausência de execução. Corrige a origem e confirma novas observações. Evita rebatizar todo o histórico, pois isso também altera a interpretação de pedidos realmente antigos. deployment.environment.name permite descrever o ambiente, mas não seleciona automaticamente apenas produção numa consulta. Define filtros explícitos e verifica os valores observados. Uma legenda convincente não corrige uma população mal selecionada.

2. Comparar populações com o mesmo significado

Se a candidata tem dez erros em duzentos pedidos, a sua taxa é 5%. Dividir esses dez pelos dois mil pedidos de todo o serviço dá 0,5%, mas muda a pergunta. O numerador pertence à candidata e o denominador passa a incluir o controlo. Da mesma forma, somar séries e eliminar revision impede separar posteriormente as versões a partir desse resultado. Decide que dimensões serão necessárias antes de criar regras que conservam apenas agregados. Mesmo com denominadores corretos, a composição pode enganar. No exercício, o controlo tem 90 erros em 900 leituras e um erro em 100 escritas. A candidata tem 20 em 100 leituras e 18 em 900 escritas. Globalmente, o controlo tem 9,1% e a candidata 3,8%. Dentro de leituras, porém, a taxa passa de 10% para 20%; dentro de escritas, de 1% para 2%. A candidata recebe proporcionalmente mais trabalho da operação menos falível. O agregado melhora enquanto ambas as operações pioram. Apresenta a tabela por operação e explica a diferença ao responsável pela promoção. Estes números são inventados para ensinar a comparação; não representam resultados de um serviço real nem demonstram significância estatística.

3. Delimitar o tempo e confirmar atualidade

Uma candidata instalada às 10:20 não deve ser avaliada como se tivesse produzido todos os eventos desde as 09:30. Confirma o intervalo de exposição e o intervalo efetivo de cada cálculo. Um painel pode atualizar agora e continuar a mostrar uma janela que inclui trabalho anterior. Quando os dados chegam atrasados, separa hora do evento e hora de receção. Em Cloud Logging, timestamp descreve quando ocorreu o evento e receiveTimestamp quando foi recebido. Um evento das 10:02 recebido às 10:12 não passa a ser posterior a uma entrega das 10:10, assumindo que a origem registou corretamente o tempo. No laboratório, a regra exige observação nos últimos 120 segundos. Aos 600 segundos, uma observação feita aos 450 está desatualizada, mesmo que o relatório tenha sido reexportado aos 590. Esse limite é didático e deve ser tratado como input explícito. Define também o que fazer quando falta volume ou cobertura. Dois pedidos sem erros não cumprem uma regra que exige cem por operação e versão. Isso não demonstra falha funcional; demonstra que a decisão não tem ainda a evidência exigida. Mantém esse estado distinguível de aprovação e de regressão observada. A equipa seguinte precisa de saber que dados recolher para resolver a incerteza.

4. Ler distribuições sem fabricar percentis

Num histograma clássico, os buckets são cumulativos. Se há 60 observações até 0,1 segundos, 90 até 0,3 e 100 no total, 90% cumprem o limite de 0,3. Somar os dois primeiros buckets conta novamente as observações mais rápidas. Subtrair 60 de 90 responde a outra pergunta: quantas estão acima de 0,1 e até 0,3. Explicita unidades, fronteiras e população antes de interpretar o resultado. Uma diferença de convenção pode transformar uma suposta regressão num erro de cálculo. Para combinar instâncias, verifica a compatibilidade da medição. Se apenas A publica a fronteira 0,3 e B publica outras fronteiras, um numerador selecionado por le=0.3 omite B. Dividir pelo total de ambas não cria a medição em falta. Também não se obtém o p95 global pela média dos p95 exportados por cada réplica. A distribuição conjunta exige dados agregáveis. Em PromQL, a agregação de histogramas clássicos para histogram_quantile conserva le e as dimensões que se pretende separar, como revision. As expressões deste bloco foram revistas pela documentação; não foram executadas contra Prometheus. No trabalho real, confirma tipos, labels, resets e compatibilidade antes de usar uma consulta como critério de promoção.

5. Interpretar o resultado de um job de análise

Cloud Deploy permite jobs de análise ligados a políticas de alerta de Google Cloud Observability. Quando existe verify, analysis corre depois dele e antes de postdeploy. A configuração identifica políticas e pode restringir os alertas considerados por labels. Usa o identificador qualificado da política, sem acrescentar o segmento de uma condição. Se uma política cobre várias aplicações, confirma que o check seleciona a aplicação pretendida. Não reduzas alertas de outro serviço para compensar um âmbito de análise mal definido. Se a duração termina sem alertas detetados, o job pode ter sucesso e o rollout continuar. Essa semântica exige atenção ao desenho da evidência. Num caso fictício, o filtro seleciona uma revisão que nunca recebeu tráfego. O sucesso técnico não cumpre uma regra local que exige observações da candidata. Pede um registo das populações observadas e da atualidade antes de declarar a versão validada. Não alteres retroativamente o critério para justificar a cor do estado. Se for necessária uma exceção, conserva a razão, o responsável e o risco aceite. O responsável de projeto pode coordenar a decisão, mas precisa da equipa técnica para confirmar o que os sinais realmente mediram. Um check bem configurado continua a depender de instrumentação e dados adequados.

6. Usar logs e traces para testar hipóteses

Começa por uma hipótese que a evidência possa contrariar. Se uma consulta usa severity, confirma esse campo na entrada. Um jsonPayload.level com texto ERROR não equivale automaticamente a preencher severity numa chamada direta à API. Quando analisas logs exportados, confirma ainda como o destino trata duplicados: insertId não garante deduplicação na exportação. Contar texto repetido como um único evento também pode apagar falhas legítimas distintas. Documenta a identidade escolhida e conserva a possibilidade de inspecionar exemplos. Nos traces, lê a estrutura e os intervalos antes de somar durações. Um pai de 100 ms pode conter dois filhos de 70 ms que se sobrepõem. A soma de 140 ms não é o tempo de parede do pai. Se um span remoto parece começar antes do envio do cliente, verifica sincronização e instrumentação antes de interpretar diferenças entre relógios como latência. Usa os IDs de parentesco para orientar a investigação. Para correlação por pedido, evita criar uma série de métricas por UUID. Labels ilimitados multiplicam séries; transformar IDs em hashes não limita o número de valores. Mantém dimensões controladas nas métricas e usa logs ou traces com acesso adequado para localizar pedidos concretos.

7. Proteger a decisão contra conclusões incompletas

Uma comparação relativa precisa de contexto operacional. Se candidata e controlo usam a mesma base de dados, a candidata pode degradar ambos. Diferença pequena entre grupos não significa que o serviço esteja saudável. Mantém critérios absolutos adequados, além da comparação. No exemplo, ambos passam de 1% para 8% de erros. Uma regra que aceita diferenças inferiores a um ponto percentual não deteta a degradação comum. A equipa deve investigar a dependência e gerir impacto antes de alargar exposição. Verifica também se a definição do indicador se manteve. Se a nova instrumentação retira timeouts do numerador mas continua a contá-los no total, a taxa baixa sem demonstrar melhoria. Usa a definição de falha acordada e explica qualquer alteração antes de comparar. Na reunião de acompanhamento, apresenta a população, o intervalo, o critério, o resultado e as lacunas conhecidas. Evita reduzir tudo a uma cor. Uma decisão útil indica a próxima ação: recolher dados em falta, corrigir instrumentação, suspender promoção ou apresentar evidência suficiente ao responsável. Guarda a consulta e as contagens usadas. Estas práticas ajudam APS a sustentar uma decisão durante pressão de prazo e permitem ao projeto justificar por que motivo avançou ou ficou suspenso.

8. Laboratório de evidência por operação

O código Python usa uma regra fictícia: leituras e escritas precisam de observações completas, atuais e com pelo menos cem pedidos por versão. Dentro de cada operação, a taxa da candidata não pode exceder a do controlo. O limite de atualidade é 120 segundos, incluindo a fronteira. O programa valida contagens inteiras não negativas e rejeita erros superiores ao número de pedidos. Usa multiplicação cruzada para comparar taxas e Fraction para apresentar valores exatos. Não é um teste de significância estatística nem uma implementação de Cloud Deploy. Executa os oito casos e começa por mixed-population. O agregado favorece a candidata, mas as duas operações apresentam regressão e a decisão é hold. too-few-writes, stale, incomplete e missing-operation ficam inconclusivos. freshness-boundary mostra que uma idade exatamente igual ao limite é aceite pela regra. zero-requests não se transforma em taxa zero. O programa verifica 625 combinações de taxas, dezasseis combinações de qualidade e dez inputs inválidos. Em todos os casos promotionAuthorized permanece falso: eligible-for-review descreve apenas evidência que cumpriu esta regra. Depois altera uma contagem e prevê a decisão antes de executar. Explica qual verificação mudou e que investigação seria necessária num serviço real. O exercício não consulta cloud, não executa PromQL e não autoriza deployments.

"""Original fictional review rule, not Cloud Deploy or a statistical canary test."""
from copy import deepcopy
from fractions import Fraction
import hashlib
import itertools
import json
from pathlib import Path


def integer(value, label):
    if type(value) is not int or value < 0:
        raise ValueError(label + ' must be a nonnegative integer')
    return value


def ratio(errors, requests):
    return str(Fraction(errors, requests)) if requests else None


def evaluate(data, now=600, max_age=120, minimum=100):
    for key, value in [('now', now), ('max_age', max_age), ('minimum', minimum)]:
        integer(value, key)
    if minimum == 0 or not isinstance(data, dict) or set(data) != {'observedAt', 'complete', 'groups'}:
        raise ValueError('invalid rule or evidence shape')
    observed = integer(data['observedAt'], 'observedAt')
    if observed > now or type(data['complete']) is not bool or not isinstance(data['groups'], dict):
        raise ValueError('invalid evidence metadata')
    expected = {'read', 'write'}
    if not set(data['groups']).issubset(expected):
        raise ValueError('unknown operation')
    reasons, comparisons = [], []
    if now - observed > max_age:
        reasons.append('stale')
    if not data['complete']:
        reasons.append('incomplete')
    if set(data['groups']) != expected:
        reasons.append('missing-operation')
    totals = {name: [0, 0] for name in ['control', 'candidate']}
    for operation, group in sorted(data['groups'].items()):
        if not isinstance(group, dict) or set(group) != {'control', 'candidate'}:
            raise ValueError('each operation needs control and candidate')
        for name, values in group.items():
            if not isinstance(values, dict) or set(values) != {'requests', 'errors'}:
                raise ValueError('invalid counts shape')
            n = integer(values['requests'], 'requests')
            errors = integer(values['errors'], 'errors')
            if errors > n:
                raise ValueError('errors exceed requests')
            if n < minimum:
                reasons.append('low-volume:' + operation + ':' + name)
            totals[name][0] += errors
            totals[name][1] += n
        a, b = group['control'], group['candidate']
        regression = None if not a['requests'] or not b['requests'] else b['errors'] * a['requests'] > a['errors'] * b['requests']
        comparisons.append({'operation': operation, 'controlRate': ratio(a['errors'], a['requests']),
            'candidateRate': ratio(b['errors'], b['requests']), 'regression': regression})
    decision = 'inconclusive' if reasons else ('hold' if any(x['regression'] for x in comparisons) else 'eligible-for-review')
    return {'decision': decision, 'reasons': reasons, 'operations': comparisons,
            'aggregateRates': {k: ratio(*v) for k, v in totals.items()},
            'promotionAuthorized': False}


def evidence(read=(1, 100, 1, 100), write=(1, 100, 1, 100)):
    result = {'observedAt': 600, 'complete': True, 'groups': {}}
    for name, (ce, cn, ne, nn) in [('read', read), ('write', write)]:
        result['groups'][name] = {'control': {'errors': ce, 'requests': cn}, 'candidate': {'errors': ne, 'requests': nn}}
    return result


def main():
    base = evidence()
    fixtures = [('equal', deepcopy(base), 'eligible-for-review'),
        ('mixed-population', evidence((90, 900, 20, 100), (1, 100, 18, 900)), 'hold')]
    low = evidence(write=(1, 100, 0, 2)); fixtures.append(('too-few-writes', low, 'inconclusive'))
    stale = deepcopy(base); stale['observedAt'] = 450; fixtures.append(('stale', stale, 'inconclusive'))
    partial = deepcopy(base); partial['complete'] = False; fixtures.append(('incomplete', partial, 'inconclusive'))
    missing = deepcopy(base); del missing['groups']['write']; fixtures.append(('missing-operation', missing, 'inconclusive'))
    boundary = deepcopy(base); boundary['observedAt'] = 480; fixtures.append(('freshness-boundary', boundary, 'eligible-for-review'))
    empty = evidence(write=(0, 100, 0, 0)); fixtures.append(('zero-requests', empty, 'inconclusive'))
    output = []
    for name, data, decision in fixtures:
        before = deepcopy(data)
        result = evaluate(data)
        assert data == before and result['decision'] == decision
        output.append({'id': name, **result})
    assert output[1]['aggregateRates'] == {'control': '91/1000', 'candidate': '19/500'}
    assert all(x['regression'] for x in output[1]['operations'])
    comparisons = 0
    for cr, nr, cw, nw in itertools.product(range(5), repeat=4):
        data = evidence((cr, 100, nr, 100), (cw, 100, nw, 100))
        result = evaluate(data)
        assert result['decision'] == ('hold' if nr > cr or nw > cw else 'eligible-for-review')
        comparisons += 1
    quality_cases = 0
    for fresh, complete, enough, present in itertools.product([False, True], repeat=4):
        data = evidence(write=(1, 100, 0, 100 if enough else 2))
        data['observedAt'] = 600 if fresh else 450
        data['complete'] = complete
        if not present:
            del data['groups']['write']
        result = evaluate(data)
        assert result['decision'] == ('eligible-for-review' if fresh and complete and enough and present else 'inconclusive')
        quality_cases += 1
    invalid = []
    for field, value in [('observedAt', 601), ('observedAt', True), ('observedAt', -1), ('complete', 1)]:
        data = deepcopy(base); data[field] = value; invalid.append(data)
    for field, value in [('requests', -1), ('requests', True), ('requests', 1.5), ('errors', 101)]:
        data = deepcopy(base); data['groups']['read']['candidate'][field] = value; invalid.append(data)
    bad = deepcopy(base); bad['groups']['extra'] = {}; invalid.append(bad)
    bad = deepcopy(base); del bad['groups']['read']['control']; invalid.append(bad)
    for data in invalid:
        try:
            evaluate(data)
        except ValueError:
            pass
        else:
            raise AssertionError('invalid evidence accepted')
    print(json.dumps({'scriptSha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
        'fixtures': output, 'rateComparisons': comparisons, 'qualityCombinations': quality_cases,
        'invalidInputs': len(invalid), 'inputPreserved': True, 'cloudExecuted': False,
        'promqlExecuted': False, 'network': False, 'persistentWrites': False,
        'limitations': 'Fictional deterministic counts rule; no statistical significance, vendor execution, deployment authority or causal proof.'}, indent=2))


if __name__ == '__main__':
    main()
NA PRÁTICA

A candidata tem 3,8% de erros globais contra 9,1% do controlo, mas as taxas por leitura e escrita duplicaram.

Armadilhas comuns

Misturar revisões, usar hora de exportação como observação, calcular média de percentis ou aceitar ausência de alertas sem tráfego.

Tópicos relacionados: Instrumentação e transporte de telemetria · Continuidade e reconciliação de resultados · Desempenho e custo por trabalho concluído

Leva esta ideia contigo

Uma decisão de entrega precisa de dados atribuíveis, comparáveis e atuais; resultados técnicos devem ser interpretados no âmbito do critério acordado.

Criar conta

Referência: Professional Cloud DevOps Engineer exam guide · 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.