1. Escolher a partir das decisões do consumidor
Começa pelo acesso que o produto de dados precisa de suportar. Uma consulta de histórico de uma carteira, uma atualização transacional de dois registos e uma análise sobre meses de movimentos têm padrões diferentes. Regista granularidade, volume, frequência, latência tolerada, concorrência, consistência e forma de recuperação. Acrescenta o que o consumidor considera um resultado válido. Um serviço pode responder rapidamente e devolver dados que ainda não passaram reconciliação. Separa a decisão sobre armazenamento da decisão sobre quando publicar o resultado para utilização. Num projeto APS fictício, a aplicação operacional atualiza instruções e o reporting calcula posições consolidadas. Não escolhas um único produto só para reduzir o número de nomes no diagrama. Compara candidatos com os acessos concretos. Spanner merece avaliação quando é necessário um modelo relacional transacional distribuído; Bigtable merece avaliação para acessos adequados ao desenho de chaves; BigQuery serve análise e Cloud Storage guarda objetos. Isto não dispensa medir latência, custo e limitações. Para arquivos, inclui leituras e recuperação no custo total. Se um ficheiro passa de acesso raro para reconciliação diária, a classe economicamente adequada pode mudar. A aprovação inicial deve indicar que hipóteses justificam a escolha e quando precisam de ser revistas.
2. Desenhar chaves sem perder os acessos necessários
Uma chave física influencia onde os dados ficam próximos e como são encontrados. Em Bigtable, a ordem lexicográfica permite explorar prefixos e intervalos. Um timestamp crescente no início da row key pode concentrar escritas recentes. Um prefixo com distribuição adequada pode melhorar esse padrão, mas a decisão precisa de considerar a consulta. Não basta espalhar dados se a leitura dominante passar a exigir procurar em todo o conjunto. O ensaio deve incluir contas pequenas e grandes, rajadas de escrita e consultas simultâneas ao histórico recente. Imagina telemetria de várias aplicações de fundos. Uma chave aplicação#tempo pode conservar proximidade por aplicação, mas uma aplicação dominante ainda pode concentrar carga. Regista a distribuição observada, não apenas a contagem de aplicações. Se propuseres vários segmentos por aplicação, documenta quantos intervalos cada leitura terá de combinar e como a equipa diagnostica uma omissão. Aplicar hash à chave inteira remove a ordenação útil do valor original. Essa troca pode exigir um caminho de acesso adicional. No comité técnico, compara desenho, distribuição e consultas com os mesmos dados. Um aumento do número de nós não prova que um hotspot de chave foi resolvido. A decisão deve explicar tanto a escrita como a leitura resultantes.
3. Relacionar a partição com o período de negócio
Data de ocorrência e data de ingestão são campos com significados diferentes. Uma tabela particionada por ingestão organiza fisicamente a chegada dos dados. Um relatório por data de negócio precisa de incluir movimentos tardios que podem estar noutras partições. Antes de limitar a leitura, define o conjunto lógico esperado. Só depois procura uma seleção física que o conserve. Uma margem de alguns dias pode passar num ensaio e continuar incorreta se não houver limite de atraso garantido. Nesse caso, revê o desenho, a captura de correções ou a estratégia de reconciliação. Para BigQuery, usa predicados que permitam determinar as partições relevantes e verifica a consulta concreta. Um filtro que compara a coluna de partição com outra coluna variável pode impedir pruning. Limitar o número de linhas devolvidas não equivale a limitar os dados lidos. O controlo de filtro obrigatório ajuda a evitar consultas sem restrição adequada, mas não verifica se escolheste o mês certo. Regista separadamente o requisito funcional, o predicado físico e a evidência de execução. Compara IDs, contagens e medidas por período. Um total igual pode esconder uma linha de valor zero em falta, duas omissões que se compensam ou atribuições à carteira errada. A otimização deve manter o contrato do resultado.
4. Controlar granularidade e organização interna
Depois de selecionar partições, o clustering pode reduzir os blocos relevantes para certos filtros. A ordem das colunas importa. Se o filtro dominante é account_id, um desenho que começa por outro campo merece comparação com uma alternativa orientada a esse acesso. Não prometas a mesma melhoria para todas as consultas. Recolhe métricas numa amostra representativa e mantém constantes os critérios de correção. O desenho físico deve acompanhar o workload real, incluindo consultas de fecho e de investigação de incidentes. Na modelação, considera relações hierárquicas frequentemente consultadas em conjunto. Uma instrução com um número limitado de componentes pode usar ARRAY de STRUCT. Isso conserva a relação dentro da linha, mas o consumo precisa de respeitar a granularidade. Após UNNEST, o total do pai aparece em várias linhas. Somá-lo por componente multiplica a medida. Um DISTINCT aplicado apenas ao montante também pode fundir instruções legítimas que têm o mesmo valor. Define a chave da medida antes de agregar. Faz um ensaio com zero, um e vários componentes e com dois pais de montante igual. A desnormalização não é uma regra universal: um modelo estrela já adequado pode não beneficiar de maior aninhamento. Mede e documenta a razão da escolha.
5. Governar ficheiros e acessos como um produto
Um lake utilizável precisa de descoberta, significado, autorização e operação. O catálogo ajuda a encontrar ativos, proprietários e relações, mas a presença de uma entrada não prova acesso aos dados nem aprovação de cada alteração. Em modelos federados, os domínios mantêm responsabilidade pelos seus produtos e acordam contratos comuns. Define quem aceita alterações de esquema, qual o significado do período, que consumidor depende do produto e como são comunicadas incompatibilidades. Na documentação consultada, Dataplex Universal Catalog evoluiu para Knowledge Catalog; o guia do exame mantém a terminologia Dataplex. Conserva essa relação nas referências sem inventar uma versão nova do exame. Em BigLake, a delegação permite separar permissões da tabela e do armazenamento subjacente. Inspeciona também caminhos diretos para os objetos, pois uma concessão independente pode contornar o controlo aplicado na tabela. Para o ciclo de vida, distingue elegibilidade, execução e recuperação. Ações lifecycle são assíncronas. Uma política de retenção bloqueada tem consequências irreversíveis: não pode ser removida nem reduzida. O responsável pelo produto deve aceitar essas condições antes do lock. Regista períodos, exceções, custos e evidência de restauro. Não uses a existência de uma regra como prova de que todos os objetos já foram tratados.
6. Laboratório: fidelidade antes do tamanho do scan
O exercício original content/labs/pde-partition-fidelity/run.py compara uma seleção física com o resultado lógico de referência fornecido. Corre python3 content/labs/pde-partition-fidelity/run.py < content/labs/pde-partition-fidelity/case.json na raiz do projeto. Cada linha tem id, eventDate, ingestDate, account, amountCents e declaredBytes. Neste contrato fictício, os montantes são cêntimos de EUR; não há conversão cambial. As datas são dias explícitos no formato YYYY-MM-DD, sem cálculo de fusos horários. IDs têm de ser únicos. O input aceita no máximo 10 000 linhas e rejeita campos inesperados, datas inválidas, duplicados e números fora dos limites. partitionBy escolhe eventDate ou ingestDate. scan define um intervalo físico semiaberto, ou null para ler todas as linhas. query define o intervalo lógico por eventDate e uma conta, ou null para todas as contas. O modelo filtra primeiro as partições e depois aplica o pedido lógico. Em paralelo calcula a referência sobre todas as linhas fornecidas. Mostra IDs, montantes, partições lidas, linhas e bytes declarados. matchesProvidedReference só passa se não faltar nenhuma identidade elegível. O programa não executa SQL, não simula o otimizador BigQuery e não estima faturação. Os bytes são um valor fictício fornecido pelo aluno, útil apenas para comparar os casos deste modelo.
7. Comparar alternativas com contraexemplos
O ficheiro de exemplo tem quatro movimentos. A e B pertencem a 1 de outubro e à conta F1, com 1000 e 2000 cêntimos. A chegou no próprio dia; B chegou a 3 de outubro. C pertence a 2 de outubro e D a 3. Cada linha declara 100 bytes. O pedido lógico procura F1 no intervalo [2026-10-01,2026-10-02). Com partições por ingestDate e scan no mesmo intervalo, só A é lido: 100 bytes, total 1000 e missingIds=[B]. A referência contém A e B, total 3000. O resultado mais pequeno está incorreto para o pedido. Muda partitionBy para eventDate: o mesmo intervalo lê A e B, 200 bytes, sem omissões. Usa scan=null: lê 400 bytes e também conserva o resultado. Agora muda B para zero. O scan estreito volta a produzir o mesmo total da referência, mas continua a falhar por identidade em falta. Por fim, alarga o intervalo físico até 4 de outubro. A comparação passa para estes dados, sem provar um limite universal para atrasos futuros. Escreve as conclusões numa tabela com fidelidade, âmbito e bytes declarados. Separa o que o ensaio demonstra das hipóteses que precisariam de observação do sistema real.
8. Aceitar e operar o desenho escolhido
Uma decisão de armazenamento termina com critérios de operação. Entrega o padrão de consulta esperado, limites de crescimento, responsável pelo esquema, política de retenção, forma de recuperação e métricas que indicam mudança de comportamento. Para tabelas analíticas, mede custo e latência de resultados reconciliados. Para acessos por chave, acompanha distribuição e concentração. Para ficheiros, acompanha volume, classes, acessos e ações de ciclo de vida. Uma redução do valor faturado pode resultar de um consumidor ter deixado de receber dados; compara sempre a métrica financeira com o produto entregue. No caso APS, prepara uma decisão para o comité: modelo anterior, alternativa, requisitos conservados, diferenças conhecidas e plano de retorno. Executa consultas de fecho, investigações ad hoc e leitura de eventos tardios com dados representativos. Se um consumidor continua a usar a granularidade antiga, trata a compatibilidade antes de promover. Define quem pode aceitar uma versão provisória e quem deve ser avisado de correções. O resumo é simples: escolhe o armazenamento pelos acessos, conserva significado ao reorganizar dados e valida as garantias no âmbito certo. O exercício local ensina a construir um contraexemplo; a aceitação real precisa ainda de evidência sobre origem, segurança, execução, capacidade e custos do ambiente concreto.
"""Original local scan model. Declared bytes are not BigQuery billing estimates."""
from datetime import date
import json
import re
import sys
def exact(value, keys):
if type(value) is not dict or set(value) != set(keys):
raise ValueError('unexpected or missing fields')
def day(value):
if type(value) is not str or not re.fullmatch(r'[0-9]{4}-[0-9]{2}-[0-9]{2}', value):
raise ValueError('date must be YYYY-MM-DD')
date.fromisoformat(value)
return value
def bounds(value):
exact(value, ['from', 'to'])
start, end = day(value['from']), day(value['to'])
if start >= end:
raise ValueError('range must be nonempty and increasing')
return start, end
def integer(value, low, high):
if type(value) is not int or not low <= value <= high:
raise ValueError('integer outside local contract')
def identifier(value):
if type(value) is not str or not re.fullmatch(r'[A-Za-z0-9_-]{1,64}', value):
raise ValueError('invalid identifier')
def evaluate(document):
exact(document, ['rows', 'partitionBy', 'scan', 'query'])
rows = document['rows']
if type(rows) is not list or len(rows) > 10000:
raise ValueError('rows must be a list with at most 10000 entries')
layout = document['partitionBy']
if layout not in ('eventDate', 'ingestDate'):
raise ValueError('unknown partition field')
exact(document['query'], ['from', 'to', 'account'])
query = document['query']
start, end = bounds({'from': query['from'], 'to': query['to']})
if query['account'] is not None:
identifier(query['account'])
scan = bounds(document['scan']) if document['scan'] is not None else None
seen = set()
for row in rows:
exact(row, ['id', 'eventDate', 'ingestDate', 'account', 'amountCents', 'declaredBytes'])
identifier(row['id'])
identifier(row['account'])
day(row['eventDate'])
day(row['ingestDate'])
integer(row['amountCents'], -10**12, 10**12)
integer(row['declaredBytes'], 0, 10**9)
if row['id'] in seen:
raise ValueError('duplicate row id')
seen.add(row['id'])
def eligible(row):
return start <= row['eventDate'] < end and (query['account'] is None or row['account'] == query['account'])
scanned = [r for r in rows if scan is None or scan[0] <= r[layout] < scan[1]]
reference = sorted((r for r in rows if eligible(r)), key=lambda r: r['id'])
result = sorted((r for r in scanned if eligible(r)), key=lambda r: r['id'])
found = {r['id'] for r in result}
missing = [r['id'] for r in reference if r['id'] not in found]
return {'partitionBy': layout, 'scannedPartitions': sorted({r[layout] for r in scanned}),
'scannedRowCount': len(scanned), 'scannedDeclaredBytes': sum(r['declaredBytes'] for r in scanned),
'referenceIds': [r['id'] for r in reference], 'resultIds': [r['id'] for r in result],
'referenceAmountCents': sum(r['amountCents'] for r in reference),
'resultAmountCents': sum(r['amountCents'] for r in result),
'missingIds': missing, 'matchesProvidedReference': not missing,
'sourceCompletenessProven': False, 'cloudPruningMeasured': False, 'billingEstimate': False}
def unique_object(pairs):
result = {}
for key, value in pairs:
if key in result:
raise ValueError('duplicate JSON field')
result[key] = value
return result
if __name__ == '__main__':
try:
raw = sys.stdin.read(3_000_001)
if len(raw) > 3_000_000:
raise ValueError('input exceeds local limit')
print(json.dumps(evaluate(json.loads(raw, object_pairs_hook=unique_object)), sort_keys=True))
except (ValueError, TypeError, RecursionError) as error:
print(json.dumps({'error': str(error)}), file=sys.stderr)
sys.exit(2)
Ler 100 bytes omite B; ler 200 por eventDate conserva A e B. Os bytes são fictícios.
Armadilhas comuns
Confundir ingestão com ocorrência; somar totais do pai após UNNEST; usar metadata como autorização; inferir faturação do modelo local.
Tópicos relacionados: Eventos tardios e replay · Preparação para análise · Custos e recuperação
Uma otimização é candidata a aceitação quando conserva o resultado e tem benefício medido no workload real.
Referência: Professional Data Engineer standard exam guide · Current linked standard guide (document title v4.2); edition date unconfirmed (2026-09-30 inspection)