1. Panoramica
Questo codelab illustra i concetti fondamentali di Spanner Omni, il motivo per cui funge da base di dati definitiva per gli agenti AI autonomi e un'implementazione end-to-end approssimativa di un simulatore di partite e un allenatore tattico di pickleball basato su agenti, di cui è stato eseguito il deployment su edge, basato su Spanner Omni (Relational + Graph GQL), Python Flask e Gemini 3.8 Flash.
Problema
Le moderne piattaforme sportive interattive e gli ambienti di gioco in tempo reale richiedono la gestione simultanea di tre workload diversi:
Transazioni operative ad alta contemporaneità:
Prenotazioni dei campi, abbonamenti dei giocatori, fatturazione e programmi dei tornei che richiedono una rigorosa serializzabilità ACID per eliminare i conflitti di doppia prenotazione.
Telemetria del grafico connesso:
Sequenze di tiri di rally continui, catene di Markov di transizione e grafici di copertura delle zone che richiedono un attraversamento a bassa latenza senza pipeline ETL ingombranti.
Reattività edge inferiore al millisecondo:
Simulazioni di fisica frenetiche a 60 FPS in cui i round trip della rete cloud ad ampia area (100-200 ms) introdurrebbero un jitter inaccettabile.
In passato, i team di ingegneria risolvevano questo problema unendo architetture frammentate che assomigliavano a questo: PostgreSQL per i dati transazionali, Neo4j per i grafi di ripresa, Redis per l'edge caching e pipeline di sincronizzazione in background. Ciò introduce deviazioni architetturali, un'elevata complessità operativa e ritardi nella coerenza finale.
How we do it!
Eseguendo il deployment di Google Cloud Spanner Omni sull'edge e accoppiandolo con Google Gemini 3.8 Flash, abbiamo creato Spanner Omni Pickleball Game, una piattaforma di gioco sportivo con un unico motore che unifica la logica di business relazionale, la telemetria dei tiri ISO GQL nativa e il coaching AI tattico in tempo reale.
Spanner Omni
Spanner Omni porta il motore del database distribuito su scala globale di Google, completo di scalabilità orizzontale, transazioni ACID e funzionalità multi-modello (relazionale, grafico, vettoriale e analisi), in ambienti on-premise, multi-cloud e con air gap autogestiti. Spanner Omni è una versione scaricabile e autogestita di Google Cloud Spanner. Offre le funzionalità principali del database distribuito di Spanner, ovvero sharding automatico, replica basata su Paxos e forte coerenza esterna, ovunque tu scelga di eseguirlo:

Obiettivi didattici
In questo codelab, passerai dalle tradizionali architetture di database isolate a una soluzione di database perimetrale moderna e unificata. Imparerai a:
- Esegui il deployment e l'orchestrazione di Spanner Omni in locale utilizzando Docker per gestire workload relazionali a istanza singola ad alte prestazioni.
- Progetta uno schema di grafi relazionali e ISO GQL unificato che mappa i modelli di business transazionali insieme alla telemetria di gioco ad alta dimensionalità senza richiedere la sincronizzazione di database esterni.
- Implementa la serializzabilità ACID rigorosa all'interno delle transazioni di lettura/scrittura Python per prevenire in modo programmatico le race condition ed eliminare i conflitti di doppia prenotazione.
- Crea una pipeline GraphRAG nativa che sfrutta le query del grafo ISO GQL integrate con SQL per fornire telemetria delle partite in tempo reale e con un contesto preciso direttamente a Google Gemini per un coaching tattico istantaneo.
Requisiti
- Una macchina locale standard o una VM di sviluppo con Linux (Ubuntu 22.04 o versioni successive), macOS o Windows.
- Docker Desktop o Docker Engine installato e in esecuzione.
- Python 3.10 o versioni successive installato.
- Un editor di testo o un IDE (come VS Code o PyCharm).
- Una chiave API Gemini di Google attiva. (Nota: non è richiesto un account di fatturazione Google Cloud per eseguire Spanner Omni localmente, poiché viene eseguito con una licenza di valutazione per sviluppatori autogestita di Livello senza costi).
2. Deployment e configurazione di Spanner Omni
In questo passaggio, avvieremo l'istanza del database perimetrale locale. Spanner Omni viene eseguito all'interno di un container altamente ottimizzato, offrendoti funzionalità relazionali su scala globale sul tuo laptop di sviluppo locale.
- Apri il terminale e crea un volume Docker permanente per archiviare gli schemi del database e la telemetria del gioco:
docker volume create spanner_omni_data
- Esegui il pull e avvia l'immagine container ufficiale di Spanner Omni configurata in modalità sviluppatore per un singolo server:
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
- Assicurati che il container sia stato avviato correttamente:
docker ps
🛠️ Risoluzione rapida dei problemi in una riga: se l'avvio non va a buon fine perché le porte 9010 o 9020 sono già in uso da processi in background o esecuzioni di emulatori legacy, esegui questo script di pulizia nel terminale:
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. Progettazione dello schema DDL del grafico relazionale e ISO GQL
Con Spanner Omni attivo, possiamo stabilire lo schema del nostro database. Questo schema funge da Single Source Of Truth, unificando le organizzazioni aziendali relazionali (come campi, giocatori e prenotazioni) con schemi di telemetria dei grafici rapidi e connessi (tiri, zone e transizioni).
Per creare le tabelle operative e di telemetria:
- Connettiti al database utilizzando l'interfaccia a riga di comando di Spanner o il client dell'applicazione
- Esegui le seguenti istruzioni DDL
Al momento non è richiesta alcuna azione da parte tua, poiché stiamo risolvendo il problema nel codice dell'applicazione:
-- ============================================================================
-- 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
💡 Perché i timestamp di commit sono importanti: specificare OPTIONS (allow_commit_timestamp=true) consente un ordinamento monotonico e preciso al microsecondo in tutte le repliche dei nodi locali. Questo è fondamentale per sincronizzare gli eventi di gioco ad alta velocità (come i filmati video delle chiamate di linea) con i log di telemetria dei tiri del database corrispondenti.
4. Controllo della concorrenza ACID e prevenzione delle doppie prenotazioni
Nei portali di prenotazione di sport attivi, più utenti spesso competono per prenotare gli stessi intervalli di tempo per i campi. Per evitare race condition, scriveremo una transazione di lettura/scrittura Python in Spanner Omni per garantire una serializzabilità assoluta e a prova di doppia prenotazione.
Non è necessario alcun intervento da parte tua, osserva semplicemente il codice:
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. Fisica in tempo reale a 60 FPS e motore bot DUPR regolabile
Il frontend della nostra applicazione utilizza un rendering di simulazione HTML5 Canvas 2.5D a 60 frame al secondo (FPS). Questo rendering viene eseguito in modo completamente disaccoppiato dai livelli del database per mantenere prestazioni fluide, scaricando la telemetria solo al termine dei rally.
Il simulatore è dotato di un motore AI OmniBot regolabile che sfrutta una matrice di difficoltà basata sui livelli DUPR (Dynamic Universal Pickleball Rating) reali.

Al momento non è richiesta alcuna azione da parte tua, basta osservare i dettagli.
6. ISO GQL Shot Telemetry Graph Processing
Al termine di ogni rally, il frontend pubblica il grafico della traiettoria connessa nel nostro endpoint di backend /api/rally-end. Ogni inquadratura viene registrata come nodo relazionale e le sequenze sono collegate dinamicamente tramite archi di transizione.
Non è richiesta alcuna azione da parte tua, osserva solo il codice:
@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...
Sfruttando il motore di query dei grafi ISO GQL nativo di Spanner, un allenatore o un programma di analisi può eseguire query su pattern strutturali complessi e con hop profondi istantaneamente senza eseguire join di database ingombranti:
-- 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. Gemini AI Tactical Coaching & Grounded RAG Chat
Ora attiveremo il cervello della nostra applicazione: Google Gemini 3.8 Flash. Il nostro backend Python utilizza il modello per fornire tre funzionalità di AI in tempo reale basate direttamente su Spanner Omni.
Il sistema utilizza Google Gemini 3.8 Flash per alcuni workflow di AI in tempo reale distinti:
Analisi degli scambi in tempo reale e arbitro tattico:
Dopo ogni punto, Gemini analizza la sequenza di colpi, la difficoltà del bot e la posizione in campo.
Spanner Multi-Table Grounded AI Concierge & Match Analyst:
Gli utenti possono eseguire query su orari dei campi, tariffe delle strutture e chiedere "Come ho giocato contro OmniBot?" in linguaggio naturale. Il backend esegue query dinamiche su Spanner Omni in quattro domini (Courts, Bookings, GameSessions e Shots) per fornire il grounding a Gemini senza allucinazioni.
API Real-Time Performance Analytics (/api/performance):
Aggrega i rapporti vincite-perdite in tempo reale, la profondità media degli scambi e le sessioni di gioco recenti direttamente dalla tabella GameSessions di Spanner Omni per la dashboard del giocatore.
Al momento non è richiesta alcuna azione da parte tua, basta osservare i dettagli.
8. Configura, esegui e testa.
Avviamo l'applicazione completa sul tuo computer locale per verificare il sistema end-to-end.
- Apri il terminale host e clona il repository del progetto:
git clone https://github.com/AbiramiSukumaran/pickleball
cd pickleball
- Ottieni la chiave API Gemini seguendo i passaggi descritti nella seguente documentazione:
https://medium.com/google-cloud/get-api-key-from-google-ai-studio-b2d2059a171d
- Apri il file
.envnell'editor e configura i dettagli dell'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"
- Installa le dipendenze Python ed esegui l'applicazione:
pip install -r requirements.txt
python app.py
- Apri il browser web e vai alla pagina
http://localhost:5000per iniziare a giocare. Puoi prenotare i campi nella scheda Prenotazione, giocare contro l'avversario regolabile DUPR OmniBot e consultare l'analista di partite Gemini in tempo reale a bordo campo.
Apri il browser web o fai clic sul link all'app in esecuzione nel tuo localhost.

9. Complimenti!
Completando questo codelab, hai assistito a un cambio di paradigma nell'ingegneria dei database. Per anni, la creazione di agenti reattivi in tempo reale nell'edge (nelle fabbriche, nelle zone militari o nelle reti sportive a bordo campo) ha imposto dolorosi compromessi di progettazione: l'unione di pipeline ETL fragili tra sistemi transazionali isolati, nodi di grafi separati e indici di ricerca vettoriale.
Spanner Omni elimina questo compromesso. Combina la coerenza distribuita su scala globale, le query sui grafi ISO GQL e le prestazioni relazionali in un unico container locale scaricabile. Indipendentemente dal tuo dominio, Spanner Omni garantisce che i tuoi agenti perimetrali abbiano un dato di fatto incrollabile.