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()
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
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.
Referência: Professional Cloud DevOps Engineer exam guide · Current linked guide; edition date unconfirmed (2026-09-30 inspection)