1. Definir trabalho útil antes de otimizar
Uma aplicação fictícia de fundos termina um batch com menos despesa cloud. O relatório destaca também mais tentativas por segundo. Antes de apresentar uma otimização, pergunta quantas operações distintas produziram resultados válidos dentro do prazo acordado. Tentativas podem incluir retries, falhas e repetições. Uma configuração pode parecer mais barata porque deixa trabalho por concluir. Define a unidade com negócio, aplicação e FinOps antes do ensaio: neste bloco, conta uma operação esperada apenas quando existe um resultado válido até ao prazo. Regista o conjunto esperado e o período de custos. Usa o mesmo âmbito nas duas configurações, incluindo componentes partilhadas que o acordo atribui ao serviço. Explica se o valor é observado, estimado ou ainda parcial. A definição do exercício não pretende representar um procedimento de um banco específico. Serve para tornar verificável a decisão. O gestor de projeto coordena critérios e responsáveis; APS confirma o comportamento operacional; FinOps ajuda a assegurar que as parcelas financeiras são comparáveis. Se o requisito for alterado, conserva a alteração explicitamente. Retirar o prazo do denominador só para melhorar o indicador muda o significado do resultado e deixa de responder à pergunta inicial.
2. Encontrar o limite efetivo do contentor
CPU livre no nó não significa que qualquer contentor possa consumi-la. Num nó Linux, um limite de CPU configurado pode provocar throttling quando a aplicação tenta ultrapassá-lo. No exemplo, o contentor tem 500m, existe margem no nó e o sinal de throttling aumenta. O próximo ensaio deve relacionar o limite com throughput e latência, em vez de assumir que mais disco resolve a restrição. Requests e limits têm funções diferentes; reduzir o request não aumenta automaticamente o teto de CPU. Para memória, escreve as parcelas do modelo antes de aumentar concorrência. Com 512 MiB de base e 80 MiB adicionais por pedido simultâneo, vinte pedidos exigem 2 112 MiB. Num orçamento de 2 048 MiB, faltam 64 MiB. Esta conta não prevê o instante de um OOM nem substitui medição real; usa hipóteses explícitas de custo igual e ausência de outras utilizações. Num serviço real, buffers, caches, runtime e distribuição dos pedidos podem mudar o consumo. Ensaiar valores menores de concorrência pode proteger memória mas aumentar espera. Compara os efeitos com os requisitos, conserva margem e define como regressar à configuração anterior se a latência ou a capacidade útil piorar.
3. Tratar a dependência que impede progresso
Mais workers não garantem mais conclusões. Uma base de dados pode estar limitada por ligações ou por trabalho que espera uma transação. Em PostgreSQL 18, wait_event_type=Lock com wait_event=transactionid indica espera pelo fim de uma transação. Identifica a cadeia de bloqueio e o responsável antes de decidir uma intervenção. CPU baixa é compatível com sessões à espera; aumentar CPU não termina automaticamente o bloqueador. Não transformes o diagnóstico em cancelamento indiscriminado de sessões, pois é preciso compreender o trabalho em curso e o processo autorizado de recuperação. Calcula também o máximo de ligações que a aplicação pode abrir durante escala. No exercício, a base permite 180 ligações, 30 reservadas para outros consumidores. Cada instância pode abrir dez, incluindo overflow. O limite compatível com esse orçamento é quinze instâncias. Um pool que normalmente utiliza poucas ligações pode atingir o máximo durante pressão. Inclui processos paralelos, instâncias antigas ainda ativas no rollout e outras aplicações no raciocínio operacional. O número fornecido é fictício e não um limite padrão de Cloud SQL. Confirma configurações reais e mede espera por aquisição, ligações em uso e conclusões antes de aumentar pools. Uma fila maior à entrada da base pode apenas deslocar o gargalo.
4. Ensaiar a carga que se pretende suportar
Num modelo fechado de teste, cada utilizador espera que uma iteração termine antes de iniciar a seguinte. Se a aplicação abranda, a taxa de novas iterações pode cair. Isso é adequado para algumas perguntas, mas não demonstra comportamento sob chegadas externas constantes. Quando o objetivo exige essa carga, considera um modelo aberto e confirma o que o gerador efetivamente conseguiu iniciar. A configuração desejada e a carga observada podem divergir se faltarem recursos de geração. Regista ambas, juntamente com falhas e duração do ensaio. Controla as condições que mudam entre versões. Medir A com cache vazia e B com pedidos já em cache não isola uma melhoria de código. Usa datasets, composição de operações, estado de cache e períodos de aquecimento definidos. Nem todos os ensaios devem ter cache quente: arranque, novos dados e recuperação podem exigir condições diferentes. Define quais representam requisitos relevantes e reporta resultados separadamente. Evita corrigir números à mão com um desconto arbitrário para a cache. Se a comparação não foi equivalente, repete o ensaio e conserva a limitação do anterior. Estes exercícios discutem desenho de testes; não executam k6 nem medem a capacidade de uma aplicação real.
5. Calcular custo por resultado, com unidades coerentes
A custa 12 000 cêntimos e entrega mil operações válidas distintas. B custa 9 000 e entrega seiscentas. O custo por resultado útil é doze cêntimos em A e quinze em B. A despesa total baixou, mas a eficiência medida por esta unidade piorou. Usar o volume de A no denominador de B produziria nove cêntimos e uma conclusão sem base. Mantém custo e volume ligados ao mesmo plano, período e âmbito. Todas as quantias deste bloco são fictícias; não são preços de fornecedores. A qualidade e o prazo também mudam o denominador. Com 850 operações válidas no prazo, cem válidas tardias e cinquenta inválidas, a regra do exercício conta apenas 850. Para 17 000 cêntimos, o custo unitário é vinte. Relata os outros conjuntos separadamente, para permitir investigação e recuperação. Se não existir qualquer resultado válido, a divisão não tem valor definido. Mostra a despesa e zero resultados em vez de apresentar custo unitário zero. Repetições do mesmo ID não criam unidades úteis adicionais. Contar IDs distintos ajuda a reconciliação, mas não prova que uma operação financeira não foi executada duas vezes. Mantém repetições assinaladas para análise.
6. Confirmar âmbito financeiro e parcelas residuais
Retirar workers não elimina necessariamente todos os custos associados a um serviço. No modelo contratual do exercício, há 400 unidades diárias fixas, 200 variáveis de workers e cem de retenção. A alteração elimina apenas as 200 variáveis. A poupança é 200 e ficam 500 por dia. Não atribuas à mudança a eliminação de uma parcela que continua contratada ou necessária. Se o objetivo incluir decommission, identifica explicitamente cada componente, dependência, responsável e evidência de cessação de custo. O momento da observação financeira também interessa. Cloud Billing exporta dados para BigQuery sem garantia de latência; serviços podem reportar consumo em intervalos diferentes. Logo após uma migração, uma linha ausente não demonstra custo zero. Marca o conjunto como parcial e planeia reconciliação quando chegarem dados adicionais. Se for preciso decidir antes, separa estimativa e valor observado, com hipóteses e incerteza visíveis. Não inventes um custo substituto copiando o serviço mais barato do projeto. O laboratório recusa apresentar um custo unitário definitivo quando costsComplete é falso. Essa regra torna explícita a limitação do exercício; não pretende descobrir automaticamente se uma exportação real está completa.
7. Rever recomendações face aos períodos críticos
Uma recomendação de machine type é evidência para análise, com uma janela e pressupostos. A documentação consultada do Compute Engine descreve recomendações baseadas nos últimos oito dias e limitações de médias que podem não captar picos curtos. Se o processamento mensal crítico não ocorreu nesse período, uma recomendação para reduzir a VM não demonstra capacidade suficiente para o próximo fecho. Procura observações desse processamento ou ensaia uma carga representativa antes de transformar uma poupança estimada em compromisso operacional. Na reunião, liga proposta, hipótese, ensaio e critério. Por exemplo: reduzir capacidade pode diminuir custo nominal, mas deve manter o prazo do batch, resultados válidos e capacidade de recuperação acordada. Define uma janela controlada, sinais para interromper e responsável pela reversão. Não assumes que qualquer recomendação exige implementação imediata, nem que deva ser ignorada. Usa-a para formular uma hipótese verificável. Conserva resultados e limites do ensaio, incluindo condições que não foram reproduzidas. APS precisa de saber operar a configuração escolhida depois do projeto; FinOps precisa de acompanhar a poupança realmente observada. Uma decisão completa distingue benefício previsto, benefício realizado e requisitos ainda por comprovar.
8. Laboratório de custos e identidades do lote
O código Python recebe custo em cêntimos, IDs esperados, registos de tentativa e prazo. Um ID conta quando pertence ao lote, tem resultado marcado válido e terminou até ao prazo, incluindo a fronteira. Repetições contam uma vez no denominador e ficam assinaladas. IDs fora do lote são reportados separadamente. A validade é fornecida pelo exercício, não calculada por inspeção de uma transação real. O programa não repara duplicações financeiras nem demonstra idempotência da aplicação. Compara plan-a e plan-b. A faz 1 200 tentativas, entrega mil resultados úteis e custa quinze cêntimos por resultado. B faz 1 500 tentativas, entrega seiscentos e custa vinte. Observa duplicate-row, zero-useful, late-or-invalid e incomplete-cost. A fronteira de prazo é incluída e um custo parcial não produz um valor unitário definitivo. Há oito casos, 4 096 combinações de identidades e repetições e dez inputs inválidos verificados. O cálculo usa Fraction para conservar razões exatas e não modifica os inputs. optimizationApproved permanece falso: o relatório apoia uma discussão, não autoriza alterações. Executa localmente, altera um prazo ou resultado e explica por que mudou o denominador. Não há chamadas cloud, API de faturação ou preços reais.
"""Original closed-batch accounting exercise, not billing or transaction software."""
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 summarize(cost_cents, expected, attempts, deadline, costs_complete=True):
integer(cost_cents, 'cost_cents'); integer(deadline, 'deadline')
if type(costs_complete) is not bool:
raise ValueError('costs_complete must be boolean')
if not isinstance(expected, list) or not expected or any(type(x) is not str or not x for x in expected) or len(set(expected)) != len(expected):
raise ValueError('expected IDs must be unique nonempty strings')
if not isinstance(attempts, list):
raise ValueError('attempts must be a list')
wanted, useful, outside, occurrences = set(expected), set(), set(), {}
for row in attempts:
if not isinstance(row, dict) or set(row) != {'operation', 'valid', 'completedAt'}:
raise ValueError('invalid attempt shape')
if type(row['operation']) is not str or not row['operation'] or type(row['valid']) is not bool:
raise ValueError('invalid attempt identity or validity')
integer(row['completedAt'], 'completedAt')
key = row['operation']
if key not in wanted:
outside.add(key)
elif row['valid'] and row['completedAt'] <= deadline:
useful.add(key)
occurrences[key] = occurrences.get(key, 0) + 1
return {'recordedCostCents': cost_cents, 'costsComplete': costs_complete,
'attemptRows': len(attempts), 'usefulCount': len(useful),
'costPerUsefulCents': str(Fraction(cost_cents, len(useful))) if useful and costs_complete else None,
'usefulIds': sorted(useful), 'missingIds': sorted(wanted - useful),
'outsideScopeIds': sorted(outside),
'repeatedUsefulIds': sorted(k for k, n in occurrences.items() if n > 1),
'meetsRequiredVolume': useful == wanted, 'optimizationApproved': False}
def row(key, valid=True, time=100):
return {'operation': key, 'valid': valid, 'completedAt': time}
def main():
expected = ['op-' + str(n) for n in range(1000)]
a = [row(k) for k in expected] + [row(expected[i], False) for i in range(200)]
b = [row(k) for k in expected[:600]] + [row(expected[i % 600], False) for i in range(900)]
definitions = [
('plan-a', 15000, expected, a, 100, True, 1000, '15'),
('plan-b', 12000, expected, b, 100, True, 600, '20'),
('duplicate-row', 120, ['a', 'b'], [row('a'), row('a'), row('b')], 100, True, 2, '60'),
('zero-useful', 5000, ['a'], [row('a', False)], 100, True, 0, None),
('late-or-invalid', 200, ['a', 'b', 'c'], [row('a'), row('b', True, 101), row('c', False)], 100, True, 1, '200'),
('outside-scope', 200, ['a', 'b'], [row('a'), row('x')], 100, True, 1, '200'),
('incomplete-cost', 100, ['a'], [row('a')], 100, False, 1, None),
('deadline-boundary', 123, ['a'], [row('a', True, 100)], 100, True, 1, '123'),
]
fixtures = []
for name, cost, wanted, attempts, deadline, complete, count, unit in definitions:
before = deepcopy((wanted, attempts))
result = summarize(cost, wanted, attempts, deadline, complete)
assert result['usefulCount'] == count and result['costPerUsefulCents'] == unit
assert (wanted, attempts) == before
# Keep evidence compact; full IDs remain available from summarize().
compact = {k: v for k, v in result.items() if k not in ['usefulIds', 'missingIds']}
compact['missingCount'] = len(result['missingIds'])
fixtures.append({'id': name, **compact})
checks = 0
small = ['a', 'b', 'c', 'd', 'e', 'f']
for mask, duplicates in itertools.product(range(64), repeat=2):
attempts = [row(k) for i, k in enumerate(small) if mask & (1 << i)]
attempts += [row(k) for i, k in enumerate(small) if mask & duplicates & (1 << i)]
result = summarize(120, small, attempts, 100)
count = mask.bit_count()
assert result['usefulCount'] == count
assert result['costPerUsefulCents'] == (str(Fraction(120, count)) if count else None)
assert len(result['repeatedUsefulIds']) == (mask & duplicates).bit_count()
assert len(result['missingIds']) == 6 - count
checks += 1
invalid = [(-1, ['a'], [], 100), (True, ['a'], [], 100), (1, [], [], 100),
(1, ['a', 'a'], [], 100), (1, [''], [], 100), (1, ['a'], {}, 100),
(1, ['a'], [{}], 100), (1, ['a'], [row('a', 1)], 100),
(1, ['a'], [row('a', True, -1)], 100), (1, ['a'], [], 1.5)]
for args in invalid:
try:
summarize(*args)
except ValueError:
pass
else:
raise AssertionError('invalid input accepted')
print(json.dumps({'scriptSha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
'fixtures': fixtures, 'identityCombinations': checks, 'invalidInputs': len(invalid),
'inputPreserved': True, 'cloudExecuted': False, 'billingApiCalled': False,
'network': False, 'persistentWrites': False,
'limitations': 'Fictional complete-scope costs and supplied validity; repeated rows flagged, no proof or repair of financial effects, no optimization authorization.'}, indent=2))
if __name__ == '__main__':
main()
Um plano com menos despesa pode passar de 15 para 20 cêntimos por resultado útil e falhar o volume exigido.
Armadilhas comuns
Contar retries como resultados, ignorar limites por contentor, multiplicar pools sem orçamento ou tratar exportações recentes incompletas como custo zero.
Tópicos relacionados: Recuperação de filas e reconciliação · Sinais de promoção e populações comparáveis · Compromissos, reservas e âmbito de custos
Uma otimização precisa de melhorar resultados dentro dos requisitos, com custos comparáveis e limites operacionais demonstrados.
Referência: Professional Cloud DevOps Engineer exam guide · Current linked guide; edition date unconfirmed (2026-09-30 inspection)