Cloud Spanner é o banco de dados relacional distribuído do GCP. Combina transações ACID com consistência externa e escalabilidade horizontal ilimitada, algo que o teorema CAP dizia ser impossível, viabilizado pelo uso de relógios atômicos (TrueTime).

É a implementação comercial do paper “Spanner: Google’s Globally-Distributed Database” (2012).

Quando usar Spanner

Use SpannerNão use
ACID + escala horizontal (> 2 TB ou > 2000 QPS de escrita)Cargas analíticas (use gcp-bigquery)
Distribuição global com consistência garantidaDataset pequeno ou previsível (use gcp-cloud-sql)
SLA 99.999% em multi-regiãoDados de documento hierárquico (use gcp-firestore)
Migração de bancos relacionais que não cabem mais em um nóCache ou lookups por chave simples (use gcp-memorystore)

Hierarquia de objetos

Instance (configuração regional/global)
  └── Database
        ├── Tables
        ├── Views
        ├── Indexes
        └── Change Streams

Configurações de instância

TipoExemplo de configLatência de escritaSLA
Regionalregional-southamerica-east1~5ms99.99%
Multi-regional USnam4~10ms99.999%
Multi-regional EUeur3~10ms99.999%
Globalnam-eur-asia1~20-30ms99.999%

Capacidade de compute

Compute é provisionado em Processing Units (PUs) ou nodes (1 node = 1000 PUs):

# Criar instância com 1 node (produção)
gcloud spanner instances create minha-instancia \
  --config=regional-southamerica-east1 \
  --nodes=1 \
  --description="Produção"
 
# Instância menor para dev/staging (100 PUs = ~$65/mês)
gcloud spanner instances create dev-instancia \
  --config=regional-southamerica-east1 \
  --processing-units=100

Schema e DDL

Spanner suporta GoogleSQL (padrão) e PostgreSQL dialect (modo de compatibilidade):

-- Tabela principal
CREATE TABLE Pedidos (
  PedidoId   STRING(36)  NOT NULL,
  ClienteId  STRING(36)  NOT NULL,
  Status     STRING(20),
  Total      NUMERIC,
  CriadoEm  TIMESTAMP   NOT NULL OPTIONS (allow_commit_timestamp=true)
) PRIMARY KEY (PedidoId);
 
-- Índice secundário
CREATE INDEX PedidosPorCliente ON Pedidos(ClienteId);
 
-- Índice com STORING (copia colunas para evitar lookup na tabela base)
CREATE INDEX PedidosPorStatus ON Pedidos(Status)
STORING (CriadoEm, ClienteId, Total);

Interleaving (co-localização de dados)

Tabelas interleaved têm suas linhas armazenadas fisicamente junto com as linhas pai. Elimina round-trips de rede em JOINs pai-filho frequentes:

CREATE TABLE ItensPedido (
  PedidoId   STRING(36) NOT NULL,
  ItemId     STRING(36) NOT NULL,
  Produto    STRING(100),
  Quantidade INT64,
  Preco      NUMERIC
) PRIMARY KEY (PedidoId, ItemId),
INTERLEAVE IN PARENT Pedidos ON DELETE CASCADE;

Design de row key

O primary key determina onde os dados ficam armazenados. Hotspots surgem quando muitas escritas vão para o mesmo “split” (fragmento):

-- Ruim: timestamp crescente como prefixo concentra escritas no split mais recente
PRIMARY KEY (CriadoEm, EventoId)
 
-- Ruim: auto-increment sequencial concentra escritas no maior valor
PRIMARY KEY (Id INT64 GENERATED BY DEFAULT AS IDENTITY)
 
-- Bom: UUID v4 distribui escritas uniformemente entre splits
PRIMARY KEY (PedidoId)  -- STRING(36) com UUID
 
-- Alternativa: hash como prefixo para IDs sequenciais
PRIMARY KEY (MOD(FARM_FINGERPRINT(CAST(id AS STRING)), 20), id, ts)

Transações

Read-write transaction

from google.cloud import spanner
 
client = spanner.Client(project="meu-projeto")
instance = client.instance("minha-instancia")
database = instance.database("meu-banco")
 
def transferir_saldo(transaction, origem_id, destino_id, valor):
    # Leitura consistente dentro da transação
    resultados = transaction.read(
        "Contas",
        columns=["ContaId", "Saldo"],
        keyset=spanner.KeySet(keys=[[origem_id], [destino_id]])
    )
    contas = {r[0]: r[1] for r in resultados}
 
    if contas[origem_id] < valor:
        raise ValueError("Saldo insuficiente")
 
    transaction.update("Contas", columns=["ContaId", "Saldo"], values=[
        [origem_id, contas[origem_id] - valor],
        [destino_id, contas[destino_id] + valor],
    ])
 
database.run_in_transaction(transferir_saldo, "conta-A", "conta-B", Decimal("100.00"))

Mutations API

Mais eficiente que DML para escritas em massa (sem verificação de constraints intermediária):

with database.batch() as batch:
    batch.insert(
        table="Pedidos",
        columns=["PedidoId", "ClienteId", "Status", "CriadoEm"],
        values=[
            ["uuid-1", "cliente-A", "PENDENTE", spanner.COMMIT_TIMESTAMP],
            ["uuid-2", "cliente-B", "PENDENTE", spanner.COMMIT_TIMESTAMP],
        ]
    )
    batch.delete("CarrinhoTemp", spanner.KeySet(keys=[["uuid-1"], ["uuid-2"]]))

Stale reads

Para queries que toleram dados ligeiramente desatualizados, com latência menor (não precisa contatar o líder da região):

import datetime
 
with database.snapshot(exact_staleness=datetime.timedelta(seconds=15)) as snapshot:
    rows = snapshot.execute_sql(
        "SELECT PedidoId, Status FROM Pedidos WHERE ClienteId = @cliente",
        params={"cliente": "cliente-A"},
        param_types={"cliente": spanner.param_types.STRING}
    )
    for row in rows:
        print(row)

DML

-- INSERT, UPDATE, DELETE padrão
INSERT INTO Pedidos (PedidoId, ClienteId, Status)
VALUES ('uuid-novo', 'cliente-X', 'RASCUNHO');
 
UPDATE Pedidos
SET Status = 'CONFIRMADO'
WHERE PedidoId = 'uuid-novo';
 
-- Partitioned DML: para operações em massa sem manter uma transação longa
-- (pode processar milhões de linhas em paralelo)
UPDATE Pedidos SET Status = 'ARQUIVADO'
WHERE CriadoEm < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 365 DAY);

Change Streams

Captura mudanças nas tabelas em tempo real para CDC (Change Data Capture):

CREATE CHANGE STREAM MudancasPedidos
  FOR Pedidos(Status, ClienteId, Total);

Pode ser consumido pelo gcp-dataflow para propagar mudanças ao gcp-bigquery, gcp-pubsub ou outros sistemas.

Conexão via gcloud

# Executar query diretamente
gcloud spanner databases execute-sql meu-banco \
  --instance=minha-instancia \
  --sql="SELECT COUNT(*) FROM Pedidos WHERE Status = 'PENDENTE'"
 
# Spanner Studio (interface web) está disponível no console GCP

Monitoramento

  • Spanner Studio: interface no console para queries ad-hoc e DDL
  • Query insights: planos de execução, CPU por query, queries mais lentas
  • Cloud Monitoring: spanner/instance/cpu/utilization, spanner/query/execution_count
  • Lock insights: identifica contenção de bloqueio em transações concorrentes

Ver também: gcp | gcp-cloud-sql | gcp-alloydb | gcp-bigquery | gcp-dataflow | gcp-boas-praticas | db-tipos-de-bancos-de-dados