1. Visão geral
Este codelab aborda os fundamentos do Spanner Omni, por que ele serve como a base de dados definitiva para agentes de IA autônomos e uma implementação aproximada de ponta a ponta de um treinador tático de pickleball e simulador de partidas implantado na borda, com tecnologia do Spanner Omni (Relacional + Graph GQL), Python Flask e Gemini 3.8 Flash.
Problema
As plataformas esportivas interativas modernas e os ambientes de jogos em tempo real exigem o processamento simultâneo de três cargas de trabalho distintas:
Transações operacionais de alta simultaneidade:
Reservas de quadras, assinaturas de jogadores, faturamento e cronogramas de torneios que exigem serialização ACID estrita para eliminar conflitos de reservas duplicadas.
Telemetria de gráfico conectado:
Sequências contínuas de jogadas, cadeias de Markov de transição e gráficos de cobertura de zona que exigem travessia de baixa latência sem pipelines de ETL volumosos.
Capacidade de resposta de borda em menos de um milissegundo:
Simulações de física de 60 QPS em ritmo acelerado, em que as viagens de ida e volta da rede em nuvem de área ampla (100 a 200 ms) introduziriam instabilidade inaceitável.
No passado, as equipes de engenharia resolviam isso unindo arquiteturas fragmentadas que se pareciam com isto: PostgreSQL para dados transacionais, Neo4j para gráficos de fotos, Redis para armazenamento em cache de borda e pipelines de sincronização em segundo plano. Isso introduz desvios de arquitetura, alta complexidade operacional e atrasos na consistência posterior.
Como fazemos isso!
Ao implantar o Google Cloud Spanner Omni na borda e combiná-lo com o Google Gemini 3.8 Flash, criamos o Spanner Omni Pickleball Game, uma plataforma de jogos esportivos de mecanismo único que unifica a lógica de negócios relacional, a telemetria de jogadas nativa do ISO GQL e o treinamento tático de IA em tempo real.
Spanner Omni
O Spanner Omni leva o mecanismo de banco de dados distribuído em escala planetária do Google, com escalonabilidade horizontal, transações ACID e recursos de vários modelos (relacional, gráfico, vetorial e de análise), para ambientes autogerenciados, locais, multicloud e com isolamento físico. O Spanner Omni é uma edição para download e autogerenciada do Google Cloud Spanner. Ele oferece os principais recursos de banco de dados distribuído do Spanner (fragmentação automática, replicação baseada em Paxos e consistência externa forte) onde quer que você escolha executá-lo:

O que você vai aprender
Neste codelab, você vai fazer a transição de arquiteturas de banco de dados isoladas tradicionais para uma solução moderna e unificada de banco de dados de borda. Você vai aprender a:
- Implantar e orquestrar o Spanner Omni localmente usando o Docker para gerenciar cargas de trabalho relacionais de instância única e alto desempenho.
- Projete um esquema de gráfico unificado relacional e ISO GQL que mapeie modelos de negócios transacionais com telemetria de jogos de alta dimensão sem exigir sincronização de banco de dados externo.
- Implementar a serialização ACID estrita em transações de leitura e gravação do Python para evitar disputas de forma programática e eliminar conflitos de reserva dupla.
- Crie um pipeline nativo de GraphRAG que use consultas de gráfico ISO GQL integradas ao SQL para alimentar a telemetria de correspondência em tempo real e com base no contexto diretamente no Google Gemini para treinamento tático instantâneo.
Requisitos
- Uma máquina local padrão ou uma VM de desenvolvimento com Linux (Ubuntu 22.04+), macOS ou Windows.
- Docker Desktop ou Docker Engine instalado e em execução.
- Python 3.10 ou mais recente instalado.
- Um editor de texto ou ambiente de desenvolvimento integrado (como VS Code ou PyCharm).
- Uma chave de API Gemini ativa do Google. Observação: não é necessário ter uma conta de faturamento do Google Cloud para executar o Spanner Omni localmente, já que ele é executado com uma licença de avaliação de desenvolvedor autogerenciada e sem custo financeiro.
2. Implantação e configuração do Spanner Omni
Nesta etapa, vamos criar nossa instância local do banco de dados de borda. O Spanner Omni é executado em um contêiner altamente otimizado, oferecendo recursos relacionais em escala global no seu laptop de desenvolvedor local.
- Abra o terminal e crie um volume persistente do Docker para armazenar os esquemas do banco de dados e a telemetria do jogo:
docker volume create spanner_omni_data
- Extraia e inicie a imagem oficial do contêiner do Spanner Omni configurada no modo de desenvolvedor de servidor único:
docker run -d --name spanner-omni \
-p 9010:9010 -p 9020:9020 \
-v spanner_omni_data:/spanner \
us-docker.pkg.dev/spanner-omni/images/spanner-omni:2026.r2.1-beta \
start-single-server
- Verifique se o contêiner foi iniciado:
docker ps
🛠️ Solução rápida de problemas em uma linha:se a inicialização falhar porque as portas 9010 ou 9020 já estão sendo usadas por processos em segundo plano ou execuções de emuladores legados, execute este script de limpeza no terminal:
pkill -f "spanner" || true
fuser -k 9010/tcp 9020/tcp 2>/dev/null || true
docker rm -f spanner-local spanner-omni spanner-omni-local 2>/dev/null || true
3. Design de esquema DDL de gráfico relacional e ISO GQL
Com o Spanner Omni ativo, podemos estabelecer nosso esquema de banco de dados. Esse esquema funciona como nossa única fonte de verdade, unificando entidades empresariais relacionais (como quadras, jogadores e reservas) com esquemas de telemetria de gráficos rápidos e conectados (arremessos, zonas e transições).
Para criar as tabelas operacionais e de telemetria:
- Conectar-se ao banco de dados usando a CLI do Spanner ou o cliente do aplicativo
- Execute as seguintes instruções DDL:
Nenhuma ação necessária de sua parte neste momento, pois estamos abordando isso no código do aplicativo:
-- ============================================================================
-- 1. OPERATIONAL & FACILITY MANAGEMENT (RELATIONAL ACID)
-- ============================================================================
CREATE TABLE Courts (
court_id INT64 NOT NULL,
name STRING(100) NOT NULL,
location STRING(100) NOT NULL,
surface_type STRING(50) NOT NULL,
is_indoor BOOL NOT NULL,
hourly_rate FLOAT64 NOT NULL,
has_lighting BOOL NOT NULL,
created_at TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)
) PRIMARY KEY (court_id);
CREATE TABLE Players (
player_id INT64 NOT NULL,
name STRING(100) NOT NULL,
email STRING(100) NOT NULL,
skill_level STRING(20) NOT NULL,
phone STRING(50),
created_at TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)
) PRIMARY KEY (player_id);
CREATE TABLE Bookings (
booking_id INT64 NOT NULL,
court_id INT64 NOT NULL,
player_id INT64 NOT NULL,
booking_date DATE NOT NULL,
start_hour INT64 NOT NULL,
end_hour INT64 NOT NULL,
player_name STRING(100) NOT NULL,
court_name STRING(100) NOT NULL,
status STRING(20) NOT NULL,
notes STRING(MAX),
created_at TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)
) PRIMARY KEY (booking_id);
CREATE TABLE MatchRequests (
match_id INT64 NOT NULL,
player_id INT64 NOT NULL,
player_name STRING(100) NOT NULL,
court_id INT64,
court_name STRING(100),
play_date DATE NOT NULL,
start_hour INT64 NOT NULL,
skill_required STRING(20) NOT NULL,
match_type STRING(20) NOT NULL,
spots_open INT64 NOT NULL,
status STRING(20) NOT NULL,
notes STRING(MAX),
created_at TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)
) PRIMARY KEY (match_id);
-- ============================================================================
-- 2. TACTICAL GAME TELEMETRY & PROPERTY GRAPH (ISO GQL)
-- ============================================================================
CREATE TABLE CourtZones (
zone_id STRING(50) NOT NULL,
name STRING(100) NOT NULL,
is_kitchen BOOL NOT NULL,
side STRING(50) NOT NULL
) PRIMARY KEY (zone_id);
CREATE TABLE Shots (
shot_id STRING(50) NOT NULL,
player_id STRING(50) NOT NULL,
shot_type STRING(100) NOT NULL,
speed_mph FLOAT64,
spin_type STRING(50),
outcome STRING(50),
from_zone_id STRING(50),
to_zone_id STRING(50)
) PRIMARY KEY (shot_id);
CREATE TABLE ShotTransitions (
transition_id STRING(50) NOT NULL,
from_shot_id STRING(50) NOT NULL,
to_shot_id STRING(50) NOT NULL,
rally_depth INT64,
pressure_level STRING(20)
) PRIMARY KEY (transition_id);
-- ============================================================================
-- 3. MATCH SESSIONS & AI COACHING HISTORY
-- ============================================================================
CREATE TABLE GameSessions (
session_id STRING(50) NOT NULL,
player_id INT64 NOT NULL,
player_score INT64 NOT NULL,
bot_score INT64 NOT NULL,
bot_difficulty STRING(20) NOT NULL,
winner STRING(20) NOT NULL,
rally_length INT64 NOT NULL,
fault_type STRING(100),
coaching_summary STRING(MAX),
created_at TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)
) PRIMARY KEY (session_id);
cat << 'EOF' > Dockerfile
FROM ollama/ollama:latest
ENV OLLAMA_HOST=0.0.0.0:8080
ENV OLLAMA_MODELS=/root/.ollama
ENV MODEL=gemma4:e2b
RUN ollama serve & sleep 5 && ollama pull $MODEL
ENTRYPOINT ["ollama", "serve"]
EOF
💡 Por que os carimbos de data/hora de confirmação são importantes:especificar OPTIONS (allow_commit_timestamp=true) permite uma ordenação monotônica e precisa em microssegundos em todas as réplicas de nós locais. Isso é fundamental para sincronizar eventos de jogo de alta velocidade (como imagens de vídeo de chamada de linha) com os registros de telemetria de tiro do banco de dados correspondentes.
4. Controle de simultaneidade ACID e prevenção de reservas duplicadas
Em portais de reserva de esportes ativos, vários usuários costumam disputar para reservar os mesmos horários de quadra. Para evitar disputas, vamos escrever uma transação de leitura e gravação em Python no Spanner Omni para garantir uma serialização absoluta e à prova de reservas duplicadas.
Nenhuma ação necessária de sua parte agora, apenas observe o código:
from google.cloud import spanner
def create_booking_transaction(transaction, booking_data):
# 1. Query for existing overlapping confirmed bookings on the same court
query = """
SELECT booking_id, player_name, start_hour, end_hour
FROM Bookings
WHERE court_id = @court_id
AND booking_date = @booking_date
AND status = 'CONFIRMED'
AND NOT (end_hour <= @start_hour OR start_hour >= @end_hour)
"""
params = {
"court_id": booking_data["court_id"],
"booking_date": booking_data["booking_date"],
"start_hour": booking_data["start_hour"],
"end_hour": booking_data["end_hour"],
}
param_types = {
"court_id": spanner.param_types.INT64,
"booking_date": spanner.param_types.DATE,
"start_hour": spanner.param_types.INT64,
"end_hour": spanner.param_types.INT64,
}
results = list(transaction.execute_sql(query, params=params, param_types=param_types))
if results:
conflict = results[0]
raise ValueError(
f"Court conflict: Already booked by {conflict[1]} ({conflict[2]}:00-{conflict[3]}:00)"
)
# 2. Insert new confirmed booking with Spanner commit timestamp
insert_stmt = """
INSERT INTO Bookings (
booking_id, court_id, player_id, booking_date, start_hour, end_hour,
player_name, court_name, status, notes, created_at
) VALUES (
@booking_id, @court_id, @player_id, @booking_date, @start_hour, @end_hour,
@player_name, @court_name, 'CONFIRMED', @notes, PENDING_COMMIT_TIMESTAMP()
)
"""
# execute_update statement writing to Spanner Omni...
transaction.execute_update(insert_stmt, params=booking_data, param_types=booking_types)
5. Física em tempo real a 60 FPS e mecanismo de bot DUPR ajustável
O front-end do nosso aplicativo usa uma renderização de simulação de tela HTML5 2.5D a 60 quadros por segundo (FPS). Essa renderização é executada de forma totalmente dissociada das camadas do banco de dados para manter um desempenho impecável, liberando a telemetria apenas quando as corridas terminam.
O simulador conta com um mecanismo de IA OmniBot ajustável que usa uma matriz de dificuldade baseada nos níveis reais de DUPR (Dynamic Universal Pickleball Rating).

Você não precisa fazer nada agora, apenas observar os detalhes.
6. Processamento de gráficos de telemetria de fotos ISO GQL
No final de cada rali, o front-end publica o gráfico de trajetória conectado no endpoint de back-end /api/rally-end. Cada cena é registrada como um nó relacional, e as sequências são vinculadas dinamicamente por arestas de transição.
Nenhuma ação necessária de sua parte agora, apenas observe o código:
@app.route("/api/execute-shot", methods=["POST"])
def execute_shot():
data = request.json or {}
shot_id = f"shot_{int(time.time()*1000)}"
# 1. Insert the Shot Node
insert_shot_sql = """
INSERT INTO Shots (shot_id, player_id, shot_type, speed_mph, spin_type, outcome, from_zone_id, to_zone_id)
VALUES (@shot_id, @player_id, @shot_type, @speed, @spin, @outcome, @from_zone, @to_zone)
"""
# 2. If part of a continuous rally, link with a ShotTransition Edge
if data.get("prev_shot_id"):
transition_id = f"trans_{int(time.time()*1000)}"
insert_trans_sql = """
INSERT INTO ShotTransitions (transition_id, from_shot_id, to_shot_id, rally_depth, pressure_level)
VALUES (@trans_id, @from_shot, @to_shot, @depth, @pressure)
"""
# Executed within a single, unified Spanner Omni transaction...
Ao usar o mecanismo de consulta de gráficos ISO GQL nativo do Spanner, um treinador ou programa de análise pode consultar padrões estruturais complexos e profundos instantaneamente, sem executar junções de banco de dados volumosas:
-- Find highest-frequency transition sequences leading to player unforced errors
GRAPH PickleballTelemetry
MATCH (s1:Shots)-[t:ShotTransitions]->(s2:Shots)
WHERE s1.shot_type = 'Speedup' AND s2.outcome = 'FAULT_NET'
RETURN s1.speed_mph, s2.from_zone_id, COUNT(*) AS occurrences
ORDER BY occurrences DESC
LIMIT 5;
7. Coaching tático da IA do Gemini e chat de RAG embasado
Agora, vamos ativar o cérebro do nosso aplicativo: o Google Gemini 3.8 Flash. Nosso back-end em Python usa o modelo para oferecer três recursos de IA em tempo real, fundamentados diretamente pelo Spanner Omni.
O sistema usa o Google Gemini 3.8 Flash para alguns fluxos de trabalho de IA em tempo real:
Análise de rali em tempo real e árbitro tático:
Depois de cada ponto, o Gemini analisa a sequência de jogadas, a dificuldade do bot e o posicionamento na quadra.
Concierge de IA com embasamento em várias tabelas do Spanner e analista de correspondência:
Os usuários podem consultar os horários das quadras, as taxas das instalações e perguntar "Como foi meu desempenho contra o OmniBot?" em linguagem natural. O back-end consulta dinamicamente o Spanner Omni em quatro domínios (Courts, Bookings, GameSessions e Shots) para embasar o Gemini sem alucinações.
API Real-Time Performance Analytics (/api/performance):
Agrega proporções de vitórias e derrotas em tempo real, profundidade média de ralis e sessões de jogo recentes diretamente da tabela GameSessions do Spanner Omni para o painel do jogador.
Você não precisa fazer nada agora, apenas observar os detalhes.
8. Configurar, executar e testar!
Vamos iniciar o aplicativo completo na sua máquina local para verificar o sistema de ponta a ponta.
- Abra o terminal do host e clone o repositório do projeto:
git clone https://github.com/AbiramiSukumaran/pickleball
cd pickleball
- Para receber sua chave de API Gemini, siga as etapas na documentação abaixo:
https://medium.com/google-cloud/get-api-key-from-google-ai-studio-b2d2059a171d
- Abra o arquivo
.envno editor e configure os detalhes do ambiente:
SPANNER_EMULATOR_HOST="localhost:9010"
SPANNER_PROJECT="PROJECT_ID"
SPANNER_INSTANCE="omni-instance"
SPANNER_DATABASE="pickleball-db"
GEMINI_API_KEY="REPLACE_WITH_YOUR_ACTUAL_GEMINI_KEY"
- Instale as dependências do Python e execute o aplicativo:
pip install -r requirements.txt
python app.py
- Abra o navegador da Web e acesse
http://localhost:5000para começar a jogar. Você pode reservar horários na quadra na guia de reservas, jogar contra o DUPR OmniBot ajustável e consultar o Gemini Match Analyst em tempo real na quadra.
Abra o navegador da Web ou clique no link para o app em execução no host local.

9. Parabéns!
Ao concluir este codelab, você testemunhou uma mudança de paradigma na engenharia de bancos de dados. Por anos, criar agentes responsivos em tempo real na borda (em fábricas, zonas militares ou redes esportivas) exigia concessões de design dolorosas: juntar pipelines de ETL frágeis entre sistemas transacionais isolados, nós de grafo separados e índices da Pesquisa vetorial.
O Spanner Omni elimina essa desvantagem. Ele combina consistência distribuída em escala global, consultas de gráfico ISO GQL e desempenho relacional em um único contêiner local para download. Não importa o domínio, o Spanner Omni garante que seus agentes de borda tenham informações empíricas inabaláveis.