← Professional Cloud Architect: arquitetura e operação
21 / 25 · 120 MIN

Retenção, consistência e semântica de consultas

Desenhar contratos de dados verificáveis e encontrar falhas escondidas por expiração assíncrona, réplicas, índices e consultas SQL.

Começar pelo contrato que o utilizador observa

Desenhar armazenamento exige descrever o que uma pessoa pode concluir de uma leitura. Num serviço fictício de operações de fundos, escrever uma correção e mostrar de imediato o valor anterior causa uma decisão errada, mesmo quando a infraestrutura não tem um incidente. Regista quatro fronteiras: quando uma escrita é confirmada, onde será lida, durante quanto tempo é válida e qual a unidade que tem de mudar em conjunto. Usa dois exemplos no workshop: atualizar uma preferência individual e confirmar duas partes inseparáveis de uma decisão. Não imponhas automaticamente a mesma solução a ambos. O responsável funcional deve explicar as consequências de observar estado antigo ou parcial; o arquiteto traduz essas consequências em critérios verificáveis. No ensaio, guarda identificador da operação, destino da escrita, destino da leitura e resultado esperado. Uma captura de ecrã com um valor correto é evidência de um caso, não demonstra todos os caminhos de failover. O documento de arquitetura deve tornar explícitas as condições em que a promessa ao utilizador se mantém.

Separar elegibilidade, visibilidade e eliminação

Uma política de limpeza e uma regra funcional respondem a perguntas diferentes. Em Bigtable, uma célula elegível para garbage collection pode continuar legível; filtra a leitura segundo o intervalo que o consumidor aceita. Em Firestore, TTL não é uma autorização com expiração pontual e não elimina subcoleções por eliminar o pai. Transforma estas diferenças num exercício: uma sessão termina às 16:00, existe às 16:01 e tem eventos associados. O backend deve recusar a ação expirada, enquanto o plano de dados trata separadamente os eventos. Pede à equipa que enumere todos os caminhos de acesso, incluindo chamadas diretas que ignoram a interface. Para uma política Bigtable com duas condições, desenha uma tabela de verdade antes da configuração. Interseção exige ambas; união admite qualquer uma. Depois, verifica se essa escolha corresponde à intenção do responsável pelos dados. Não declares uma obrigação legal de eliminação satisfeita apenas porque configuraste um TTL; define a evidência operacional exigida com os responsáveis adequados.

Escolher o local de leitura e a fronteira de mudança

A replicação não deve ser tratada como uma caixa que remove qualquer problema de consistência. Em Bigtable, encaminhar as leituras e escritas relevantes para o mesmo cluster suporta leitura das próprias escritas; a passagem a outro cluster exige considerar alterações ainda não replicadas. Uma read replica Cloud SQL pode igualmente estar atrasada. Para uma confirmação sensível, uma leitura no primário pode ser uma escolha simples, mantendo relatórios tolerantes a atraso na réplica. Documenta o custo e a carga que essa decisão cria. Atomicidade é outra dimensão: o contrato MutateRows de Bigtable é por entrada, não por batch completo. Se o requisito abranger duas linhas, não o declares satisfeito com base num único pedido de rede. Já um pequeno conjunto de escritas Firestore conhecidas pode usar um batch atómico dentro dos limites aplicáveis. Num exercício de revisão, o aluno deve marcar a fronteira de cada operação e explicar uma falha parcial possível. A escolha final deve resultar do requisito observado, não da familiaridade com uma API.

Preservar a população e o estado de referência

Uma alteração aparentemente visual pode mudar a população de dados. Acrescentar orderBy(priority) em Firestore exclui documentos onde o campo não existe. Numa fila fictícia com 120 tarefas, mostrar 90 não prova que as outras 30 foram concluídas. Decide se deve existir um valor obrigatório, uma consulta complementar ou outra representação aprovada; nunca inventes uma prioridade operacional só para a contagem passar. Retenção também precisa de uma referência temporal explícita. Numa tabela BigQuery particionada por data de negócio, inserir hoje uma linha antiga não reinicia a expiração daquela partição. Para guardar uma referência consultável sem alterações, considera um snapshot com expiração adequada; para ensaiar alterações numa cópia independente, considera um clone. Identifica proprietário, permissões, prazo e forma de validar cada cópia. O aluno deve explicar por que guardar apenas uma query não fixa os dados que ela encontrará no futuro. O exercício termina quando consegue escolher uma opção e indicar a evidência que invalidaria essa escolha.

Executar um contraexemplo antes de confiar numa query

O exercício Python desta aula cria uma base SQLite apenas em memória. Antes de o executar, prevê três resultados: candidates [21,22] contra blocked [21,NULL] com NOT IN; duas ocorrências (X,40) comparadas com uma através de EXCEPT; e totais de três eventos com dois valores de ordenação iguais. A execução mostra a condição desconhecida, a perda de multiplicidade escondida e a diferença entre fronteiras RANGE e ROWS. SQLite usa EXCEPT sem a palavra DISTINCT nesta demonstração. O código compara ainda 81 combinações de contagens com Counter de Python, construído separadamente. Estes ensaios validam os resultados locais apresentados, não emulam BigQuery, custos, IAM ou escalabilidade. Consulta as referências GoogleSQL para o comportamento ensinado nas perguntas cloud. Acrescenta depois um candidato NULL: NOT EXISTS com igualdade não o bloqueia automaticamente, pelo que a política para IDs ausentes precisa de ser explícita. A discussão deve terminar com uma frase verificável sobre o contrato, não apenas com a substituição mecânica de um operador por outro.

Definir a ordem e resolver múltiplas correspondências

Uma regra para escolher a revisão vencedora deve existir antes de otimizar a query. ROW_NUMBER com um timestamp empatado não define sozinho qual das revisões deve vencer. Se o contrato estabelece revision_seq único por trade, usa esse critério como desempate; ordenar o resultado final depois de selecionar a linha não corrige a escolha anterior. Num MERGE GoogleSQL com UPDATE, duas linhas de origem a corresponder à mesma linha de destino exigem resolver a multiplicidade, em vez de esperar uma escolha automática. Propõe ao aluno dois contextos: revisões que se substituem e ocorrências que têm de ser todas preservadas. Deduplicar pode ser adequado no primeiro se houver uma regra; no segundo destrói informação. O caso prático do arquivo usa deliberadamente linhas iguais que representam ocorrências diferentes. Compara frequências por tuplo, especifica o tratamento de NULL e mantém uma referência da origem. Uma query que termina sem erros é apenas uma execução bem sucedida; a aceitação exige demonstrar o significado pedido.

Medir o caminho que se pretende dimensionar

Um benchmark deve representar o trabalho cuja capacidade vais comprar. Uma query BigQuery com cacheHit=true não mediu a computação de dados novos; ensaia sem reutilização de resultados e regista plano, volume, duração e contexto de carga. Identifica também o projeto do job: a atribuição de reserva não passa para o projeto dos dados apenas por a query ler uma tabela desse projeto. Num modelo on-demand, uma estimativa superior numa tabela clustered pode exceder maximum_bytes_billed antes de executar; isso não prova o consumo efetivo previsto pelo sponsor. Revê filtros e o limite autorizado em vez de remover o controlo sem discussão. Noutro serviço, um índice Spanner com STORING pode evitar procurar uma coluna adicional na tabela, com espaço adicional. E um timestamp Firestore indexado mas nunca consultado merece avaliação para isenção de índice. Para cada proposta, escreve hipótese, resultado esperado, custo introduzido e condição de reversão. O objetivo é justificar uma mudança mensurável no workload real, não acumular funcionalidades ativadas.

Fechar a decisão com provas e responsabilidades

Num comité de migração, apresenta a promessa funcional, as observações e as lacunas separadamente. A promoção normal de uma read replica Cloud SQL termina a replicação anterior; não deixes escritores no primário antigo contando com sincronização contínua. Uma transferência de filesystem para Cloud Storage precisa dos agentes e acessos desse caminho. Desativar Object Versioning não remove as versões não atuais já existentes. Estes três exemplos mostram dependências que podem ficar fora do cronograma se o plano listar apenas botões da consola. Usa os casos desta aula para ensaiar uma reunião: alguém apresenta um teste verde, outra pessoa identifica o que esse teste realmente prova e o responsável decide com critérios definidos. O resumo é um contrato de leitura explícito, uma fronteira de atomicidade adequada, retenção com âmbito claro e validação SQL sensível a NULL, ordem e multiplicidade. Relaciona estas decisões com reconciliação de migrações, segurança da aplicação e recuperação funcional. Regista questões em aberto e quem as resolve antes de aceitar o serviço.

"""Original, local SQL teaching fixture. No cloud connection or persistent database."""
import json
import platform
import sqlite3
from collections import Counter
from itertools import product


def run():
    db = sqlite3.connect(':memory:')
    checks = []

    def check(name, actual, expected):
        if actual != expected:
            raise AssertionError((name, actual, expected))
        checks.append(name)

    def query(sql, args=()):
        return db.execute(sql, args).fetchall()

    try:
        db.executescript('''
        CREATE TABLE candidates(id INTEGER NOT NULL);
        CREATE TABLE blocked(id INTEGER);
        INSERT INTO candidates VALUES(21),(22);
        INSERT INTO blocked VALUES(21),(NULL);
        CREATE TABLE original(k TEXT NOT NULL, amount INTEGER NOT NULL);
        CREATE TABLE migrated(k TEXT NOT NULL, amount INTEGER NOT NULL);
        INSERT INTO original VALUES('X',40),('X',40),('Y',20);
        INSERT INTO migrated VALUES('X',40),('Y',20);
        CREATE TABLE events(id TEXT PRIMARY KEY, bucket INTEGER, amount INTEGER);
        INSERT INTO events VALUES('A',1,10),('B',1,20),('C',2,5);
        CREATE TABLE revisions(trade TEXT, stamp INTEGER, seq INTEGER, value TEXT,
          UNIQUE(trade,seq));
        INSERT INTO revisions VALUES('T',100,1,'old'),('T',100,2,'new');
        ''')
        check('matching NOT IN is false', query('SELECT 21 NOT IN (SELECT id FROM blocked)'), [(0,)])
        check('nonmatching NOT IN with NULL is unknown', query('SELECT 22 NOT IN (SELECT id FROM blocked)'), [(None,)])
        check('WHERE drops false and unknown', query('SELECT id FROM candidates WHERE id NOT IN (SELECT id FROM blocked)'), [])
        check('explicit nonnull blocklist finds 22', query('SELECT id FROM candidates WHERE id NOT IN (SELECT id FROM blocked WHERE id IS NOT NULL)'), [(22,)])
        check('NOT EXISTS equality finds 22', query('SELECT c.id FROM candidates c WHERE NOT EXISTS (SELECT 1 FROM blocked b WHERE b.id=c.id)'), [(22,)])
        check('NULL candidate excluded by explicit policy', query('SELECT NULL WHERE NULL IS NOT NULL AND NOT EXISTS (SELECT 1 FROM blocked WHERE id=NULL)'), [])
        check('NULL is not automatically equality matched', query('SELECT NULL WHERE NOT EXISTS (SELECT 1 FROM blocked WHERE id=NULL)'), [(None,)])
        forward = query('SELECT k,amount FROM original EXCEPT SELECT k,amount FROM migrated')
        reverse = query('SELECT k,amount FROM migrated EXCEPT SELECT k,amount FROM original')
        check('set comparison hides lost duplicate forward', forward, [])
        check('set comparison hides lost duplicate reverse', reverse, [])
        check('source count is three', query('SELECT COUNT(*) FROM original'), [(3,)])
        check('target count is two', query('SELECT COUNT(*) FROM migrated'), [(2,)])
        source_counts = query('SELECT k,amount,COUNT(*) FROM original GROUP BY k,amount ORDER BY k,amount')
        target_counts = query('SELECT k,amount,COUNT(*) FROM migrated GROUP BY k,amount ORDER BY k,amount')
        check('source frequencies retain occurrences', source_counts, [('X',40,2),('Y',20,1)])
        check('target frequencies expose lost occurrence', target_counts, [('X',40,1),('Y',20,1)])
        ranges = query('SELECT id,SUM(amount) OVER(ORDER BY bucket RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM events ORDER BY id')
        rows = query('SELECT id,SUM(amount) OVER(ORDER BY bucket,id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM events ORDER BY id')
        check('RANGE groups peers at current boundary', ranges, [('A',30),('B',30),('C',35)])
        check('ROWS with total order accumulates by event', rows, [('A',10),('B',30),('C',35)])
        check('explicit tie break selects business revision', query('SELECT value FROM (SELECT value,ROW_NUMBER() OVER(PARTITION BY trade ORDER BY stamp DESC,seq DESC) AS rn FROM revisions) WHERE rn=1'), [('new',)])
        check('empty blocklist admits nonnull candidate', query('SELECT 22 WHERE 22 NOT IN (SELECT id FROM blocked WHERE 0)'), [(22,)])
        check('no NULL and no match gives true', query('SELECT 22 NOT IN (SELECT id FROM blocked WHERE id IS NOT NULL)'), [(1,)])
        # Compare SQL frequencies against independently constructed Python multisets.
        # All 3^4 count assignments for X/Y in source/target; repeated rows are meaningful.
        properties = 0
        hidden_losses = 0
        for sx, sy, tx, ty in product(range(3), repeat=4):
            left = [('X',40)] * sx + [('Y',20)] * sy
            right = [('X',40)] * tx + [('Y',20)] * ty
            db.execute('DELETE FROM original')
            db.execute('DELETE FROM migrated')
            db.executemany('INSERT INTO original VALUES(?,?)', left)
            db.executemany('INSERT INTO migrated VALUES(?,?)', right)
            lf = query('SELECT k,amount,COUNT(*) FROM original GROUP BY k,amount ORDER BY k,amount')
            rf = query('SELECT k,amount,COUNT(*) FROM migrated GROUP BY k,amount ORDER BY k,amount')
            expected = Counter(left) == Counter(right)
            if (lf == rf) != expected:
                raise AssertionError(('frequency comparison', sx, sy, tx, ty))
            set_same = not query('SELECT k,amount FROM original EXCEPT SELECT k,amount FROM migrated') and not query('SELECT k,amount FROM migrated EXCEPT SELECT k,amount FROM original')
            if set_same != (set(left) == set(right)):
                raise AssertionError(('set comparison', sx, sy, tx, ty))
            hidden_losses += bool(set_same and not expected)
            properties += 1
        check('all finite multiplicity assignments checked', properties, 81)
        check('set comparisons can hide multiplicity differences', hidden_losses > 0, True)
        return dict(passed=len(checks), checks=checks, propertyCases=properties,
                    hiddenMultiplicityMismatches=hidden_losses,
                    example=dict(sourceCounts=source_counts,targetCounts=target_counts,rangeTotals=ranges,rowTotals=rows),
                    python=platform.python_version(),sqlite=sqlite3.sqlite_version,
                    vendorExecution=False,network=False,persistentWrites=False)
    finally:
        db.close()


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

Uma comparação de conjuntos passa mesmo perdendo uma ocorrência; uma exclusão com NULL também esconde uma diferença que a equipa esperava encontrar.

Armadilhas comuns

Confundir limpeza com autorização, batch com atomicidade entre linhas, cache com capacidade medida e conjuntos iguais com ocorrências preservadas.

Tópicos relacionados: Migração, reconciliação e resultados analíticos · Dados, consistência e publicação de eventos · Observabilidade e recuperação funcional

Leva esta ideia contigo

Especifica população, validade, consistência e multiplicidade; usa contraexemplos e mede o caminho que realmente sustenta o requisito.

Criar conta

Referência: Bigtable garbage collection overview · Current linked standard 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.