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

Continuidade de serviço: recuperar trabalho e confirmar resultados

Calcula recuperação com entradas contínuas e limites a jusante; prepara replay, reconciliação e passagem de turno.

1. Definir o resultado da recuperação

Considera um serviço fictício que recebe instruções para um lote de fundos. Após um incidente, a aplicação volta a responder e a fila diminui. O responsável de negócio continua a precisar de saber que instruções produziram um resultado válido. Define três observações separadas: trabalho aceite, trabalho ainda pendente e resultados confirmados. Uma tentativa de processamento pode falhar; um reconhecimento pode ser emitido antes da escrita correta por um defeito; um encaminhamento para quarentena pode retirar a mensagem da fila ativa. Nenhum desses eventos, isoladamente, demonstra que o lote está concluído. Antes da intervenção, escreve o critério de aceitação. Neste exercício, cada identificador esperado deve ter exatamente um resultado válido ou uma exceção explicitamente aprovada pelo responsável. Não é uma regra atribuída a um banco ou um procedimento universal. É uma condição didática que permite avaliar decisões. Define também quem reconcilia resultados, quem pode aceitar exceções e quem mantém trabalho pendente após a janela. A equipa de infraestrutura confirma capacidade e disponibilidade; a aplicação explica os efeitos produzidos; o responsável do lote decide a aceitação com base na evidência acordada. Um estado verde no dashboard não substitui estas responsabilidades.

2. Calcular a margem que reduz o atraso

No modelo mais simples, todas as operações custam o mesmo, não há falhas e as taxas são constantes. Se entram 80 operações por segundo e se concluem 120, a fila perde 40 por segundo. Um atraso de 12 000 demora 300 segundos a desaparecer. Dividir apenas por 120 daria 100 segundos e ignoraria trabalho que continua a chegar. Com 90 entradas e 90 conclusões por segundo, um atraso existente mantém-se. Recuperar a capacidade normal pode estabilizar o serviço sem recuperar o trabalho acumulado. Calcula com conclusões efetivas. Se os workers suportam 200 por segundo mas a base de dados limita conclusões a 110, a capacidade útil deste modelo é 110. Com entradas de 80 e 9 000 pendentes, são necessários cinco minutos. Duplicar workers sem alterar o limite a jusante não modifica a conta. Na prática, mede intervalos e acompanha mudanças no custo das operações. Uma migração pode introduzir consultas mais caras e um replay pode ter composição diferente do tráfego corrente. Apresenta a previsão com as hipóteses usadas, o instante de observação e a próxima revisão. A fórmula serve para raciocinar sobre um cenário controlado; não transforma capacidade nominal numa promessa de prazo.

3. Preparar replay com fronteiras explícitas

Para repetir mensagens já reconhecidas por timestamp em Pub/Sub, confirma retenção aplicável no tópico ou na subscrição. Verifica também que o intervalo necessário ainda está abrangido. Um seek bem-sucedido não constitui uma fronteira instantânea de todas as entregas. A transição tem consistência eventual. Trata o conjunto recuperado com critérios de negócio e não assumes que a posição da fila corresponde ao estado da base de dados. Seek não desfaz escritas externas. No exercício, uma versão defeituosa reconheceu 100 instruções mas só confirmou 70 resultados. Primeiro preserva evidência e identifica os 100 IDs esperados. Depois separa resultados confirmados, resultados ausentes e resultados ambíguos. Uma escrita cujo cliente recebeu timeout precisa de investigação: timeout não prova que a transação falhou. Corrige o consumidor antes de o voltar a expor ao lote. Ensaiar com um subconjunto permite comparar efeitos sem lançar imediatamente todo o volume contra a dependência. Define condições de paragem e quem pode retomar. Regista ainda a versão corrigida, o âmbito temporal e a origem dos IDs. Se o âmbito aumentar durante a recuperação, essa alteração deve ficar visível ao responsável, porque muda volume, risco e prazo.

4. Separar identidade de entrega e de operação

Exactly-once em Pub/Sub é uma funcionalidade de subscrições pull, incluindo StreamingPull, com garantia regional. Não acrescenta esse suporte a uma subscrição push. Mesmo com a funcionalidade, duas publicações podem corresponder à mesma intenção de negócio. Um identificador de mensagem diferente não prova que foram pedidas duas operações. A aplicação precisa de relacionar a identidade de transporte com a identidade que usa para reconhecer trabalho de negócio. Num exemplo fictício, a instrução F-42 é publicada duas vezes após uma resposta incerta. Um consumidor encontra dois IDs de mensagem e dois registos com o mesmo valor. Investiga a origem da instrução, os resultados e a regra que distingue uma correção legítima de uma repetição. Não elimines um registo só porque os valores coincidem: duas operações distintas também podem ter o mesmo montante. Define uma identidade estável para a intenção e trata divergência de payload como uma condição explícita. O exercício anterior sobre recuperação de hooks mostra uma associação em memória; uma implementação real precisa de tratar persistência e concorrência. Aqui o objetivo é escolher a evidência que permite decidir, sem assumir que uma opção de transporte resolve todas as duplicações financeiras.

5. Proteger sequência e capacidade do consumidor

A ordenação de callbacks por chave não controla automaticamente trabalho assíncrono que continua depois de o callback terminar. Se as revisões 11 e 12 iniciam escritas em paralelo, a 11 pode terminar depois e substituir estado mais recente. A aplicação deve preservar a sequência onde os efeitos ocorrem. Uma chave global pode também concentrar trabalho independente. Se cada carteira exige ordem interna mas carteiras diferentes são independentes, uma chave estável por carteira é uma opção a avaliar. A transição precisa de considerar mensagens já em curso. Faz primeiro a pergunta de negócio: que operações podem realmente trocar de ordem? Um aumento aparente de throughput pode ser inválido se altera a sequência exigida. No ensaio, compara o resultado final de cada carteira e inclui operações com tempos de execução diferentes. Uma distribuição aleatória de chaves pode esconder a fila enquanto produz resultados incorretos. A equipa de projeto deve reservar tempo para confirmar estas invariantes com a aplicação; o suporte deve conseguir localizar a chave ou operação afetada. Quando o problema está antes da publicação, distingue-o do consumo: flow control no publisher limita mensagens e bytes pendentes no cliente. Esse controlo não comprova que o lote a jusante foi recuperado.

6. Dar seguimento à quarentena e às tentativas

O encaminhamento para dead-letter é best-effort e o número configurado de tentativas é aproximado. Confirma a configuração e as permissões da identidade de serviço; o acesso do operador ao tópico não substitui essas permissões. Uma mensagem encaminhada necessita de um caminho de análise e recuperação. Define o responsável pelo conjunto em quarentena e os critérios para corrigir, repetir ou aceitar uma exceção. Evita usar o esvaziamento da fila original como critério único de sucesso. Num lote fechado de 1 000 operações, 980 concluídas e 20 em quarentena continuam a representar 20 resultados por resolver. Não reduzas o denominador para apresentar 100% sem um acordo explícito que altere a aceitação. Retry também tem âmbito próprio: backoff atua por mensagem e não estabelece uma pausa global da subscrição. Durante manutenção de uma dependência, a equipa precisa de um plano explícito para o trabalho novo e o trabalho já entregue. Descreve onde cada conjunto fica conservado, como será retomado e quem confirma a recuperação. Uma pausa mal definida pode apenas deslocar acumulação para a memória de outro processo. Observa a fronteira onde o trabalho realmente espera antes de alterar limites.

7. Ensaiar decisões e comunicar o que falta

Prepara três variantes do mesmo incidente: entrada estável, entrada crescente e capacidade a jusante reduzida. Pede ao participante uma previsão e a condição que a invalida. No caso de 9 000 pendentes, 80 entradas/s e 110 conclusões/s, quatro minutos são insuficientes. A decisão pode exigir controlar a admissão sem perder instruções, obter capacidade efetiva ou renegociar o prazo. Nenhuma opção deve aparecer como garantia sem confirmar dependências e autorização do responsável pelo serviço. Ao comunicar, separa serviço disponível, atraso estimado e aceitação do lote. Uma mensagem útil identifica o instante da medição, o conjunto afetado, a previsão condicional, as exceções e a próxima atualização. Na passagem de turno, entrega IDs ou referências pesquisáveis, responsáveis e critérios de fecho. Uma lista de servidores saudáveis não informa que doze operações continuam sem resultado. Antes de encerrar, compara conjuntos de identidades. Se se esperam A,B,C,D mas o diário contém A,A,B,C, quatro linhas não demonstram quatro resultados distintos. Há uma repetição a investigar e D continua em falta. Guardar essa diferença torna a próxima ação concreta e evita que indicadores agregados escondam trabalho incompleto.

8. Laboratório local de conservação do trabalho

Executa o código Python apresentado abaixo. O modelo usa segundos inteiros: primeiro acrescenta entradas, depois conclui o mínimo entre trabalho disponível, capacidade dos workers e capacidade a jusante. As operações têm custo igual. Pode retirar do conjunto inicial um número explícito para quarentena, que nunca conta como concluído. A cada passo confirma aceite = concluído + pendente + quarentena. Não há chamadas cloud, persistência, retries, expiração ou simulação das garantias de Pub/Sub. Compara net-capacity e balanced. Depois compara downstream-cap, que mantém 1 800 pendentes após 240 segundos, com downstream-drained, que chega a zero aos 300. Em load-change, a fila passa de 1 000 para 600 e acaba em 700. Em quarantine, a fila chega a zero mas apenas 980 das 1 000 operações foram concluídas. Explica também empty-then-regrows: firstEmptySecond regista a primeira passagem por zero, não uma promessa de recuperação permanente. O modelo verifica 1 600 combinações constantes contra uma fórmula independente e rejeita inputs negativos, booleanos e estruturas incompletas. Altera uma fase, prevê o resultado à mão e só depois executa. A evidência valida a aritmética deste exercício, não desempenho real, ordenação de mensagens ou cumprimento de um SLA.

"""Original teaching model. No Pub/Sub client, real-time scheduler or cloud calls."""
import hashlib
import itertools
import json
from pathlib import Path


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


def model(initial, phases, quarantined=0):
    """Each second admits arrivals, then completes min(queue, worker, downstream).
    Initial quarantined work leaves the active queue but is never completed.
    All units are equally costly. No failures, retries, latency or expiry.
    """
    integer(initial, 'initial')
    integer(quarantined, 'quarantined')
    if quarantined > initial:
        raise ValueError('quarantine exceeds initial work')
    if not isinstance(phases, list):
        raise ValueError('phases must be a list')
    checked = []
    for phase in phases:
        if not isinstance(phase, dict) or set(phase) != {'seconds', 'arrivals', 'worker', 'downstream'}:
            raise ValueError('phase fields must match the model')
        checked.append({k: integer(v, k) for k, v in phase.items()})
    queue, accepted, completed, elapsed = initial - quarantined, initial, 0, 0
    first_empty = 0 if queue == 0 else None
    points = []
    for phase in checked:
        capacity = min(phase['worker'], phase['downstream'])
        for _ in range(phase['seconds']):
            queue += phase['arrivals']
            accepted += phase['arrivals']
            done = min(queue, capacity)
            queue -= done
            completed += done
            elapsed += 1
            if queue == 0 and first_empty is None:
                first_empty = elapsed
            assert accepted == completed + queue + quarantined
        points.append({'second': elapsed, 'pending': queue, 'completed': completed})
    return {'accepted': accepted, 'completed': completed, 'pending': queue,
            'quarantined': quarantined, 'firstEmptySecond': first_empty,
            'allAcceptedCompleted': queue == 0 and quarantined == 0,
            'phaseEnds': points}


def phase(seconds, arrivals, worker, downstream):
    return dict(seconds=seconds, arrivals=arrivals, worker=worker, downstream=downstream)


def main():
    scenarios = [
        ('net-capacity', 12000, [phase(300, 80, 120, 120)], 0, 0, 300),
        ('balanced', 4000, [phase(300, 90, 90, 90)], 0, 4000, None),
        ('downstream-cap', 9000, [phase(240, 80, 200, 110)], 0, 1800, None),
        ('downstream-drained', 9000, [phase(300, 80, 200, 110)], 0, 0, 300),
        ('load-change', 1000, [phase(10, 20, 60, 60), phase(10, 70, 60, 60)], 0, 700, None),
        ('quarantine', 1000, [phase(10, 0, 100, 100)], 20, 0, 10),
        ('empty-then-regrows', 10, [phase(1, 0, 10, 10), phase(2, 20, 10, 10)], 0, 20, 1),
        ('no-capacity', 4, [phase(3, 2, 0, 10)], 0, 10, None),
    ]
    fixtures = []
    for name, initial, phases, quarantine, pending, first_empty in scenarios:
        before = json.dumps(phases, sort_keys=True)
        result = model(initial, phases, quarantine)
        assert result['pending'] == pending and result['firstEmptySecond'] == first_empty
        assert json.dumps(phases, sort_keys=True) == before
        fixtures.append({'id': name, **result})
    assert fixtures[5]['completed'] == 980 and not fixtures[5]['allAcceptedCompleted']
    assert fixtures[6]['firstEmptySecond'] == 1 and fixtures[6]['pending'] == 20
    combinations = 0
    for initial, arrivals, worker, downstream, seconds in itertools.product(range(5), range(4), range(4), range(4), range(5)):
        result = model(initial, [phase(seconds, arrivals, worker, downstream)])
        expected = max(0, initial + seconds * (arrivals - min(worker, downstream)))
        assert result['pending'] == expected
        assert result['accepted'] == initial + seconds * arrivals
        assert result['completed'] == result['accepted'] - expected
        combinations += 1
    invalid = [(-1, [], 0), (True, [], 0), (1.5, [], 0), (1, [], -1), (1, [], 2),
               (1, {}, 0), (1, [{}], 0), (1, [phase(1, -1, 1, 1)], 0),
               (1, [phase(True, 1, 1, 1)], 0), (1, [phase(1, 1, '2', 1)], 0)]
    for args in invalid:
        try:
            model(*args)
        except ValueError:
            pass
        else:
            raise AssertionError('invalid input accepted')
    print(json.dumps({'scriptSha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
        'fixtures': fixtures, 'constantRateCombinations': combinations, 'invalidInputs': len(invalid),
        'inputPreserved': True, 'cloudExecuted': False, 'network': False, 'persistentWrites': False,
        'limitations': 'Integer equal-cost work model; no vendor execution, deadlines, retries, expiry, per-message order or delivery guarantees.'}, indent=2))


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

9 000 pendentes, 80 entradas/s e limite efetivo de 110 conclusões/s exigem cinco minutos no modelo constante.

Armadilhas comuns

Ignorar entradas, contar tentativas como conclusões, confundir seek com rollback de dados e esconder quarentena numa fila vazia.

Tópicos relacionados: SLOs e capacidade de serviço · Recuperação de hooks e identidade de intenção · Telemetria durante rollout

Leva esta ideia contigo

Recuperação exige capacidade líquida e evidência dos resultados esperados, com exceções acompanhadas até à aceitação.

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.