Agents de données et GraphRAG on-device avec Spanner Omni

1. Présentation

Cet atelier de programmation présente les principes fondamentaux de Spanner Omni, explique pourquoi il s'agit de la base de données ultime pour les agents IA autonomes, et propose une implémentation approximative de bout en bout d'un coach tactique et d'un simulateur de match de pickleball agentique déployé en périphérie, optimisé par Spanner Omni (relationnel + Graph GQL), Python Flask et Gemini 3.8 Flash.

Problème

Les plates-formes de sport interactives modernes et les environnements de jeu en temps réel nécessitent de gérer simultanément trois charges de travail disparates :

Transactions opérationnelles à forte simultanéité :

Réservations de courts, abonnements des joueurs, facturation et calendriers des tournois nécessitant une sérialisabilité ACID stricte pour éliminer les conflits de double réservation.

Télémétrie du graphique connecté :

Séquences de tirs de rallye continues, chaînes de Markov de transition et graphiques de couverture de zone nécessitant un parcours à faible latence sans pipelines ETL volumineux.

Réactivité de l'Edge en moins d'une milliseconde :

Simulations physiques rapides à 60 FPS, où les allers-retours sur un réseau cloud étendu (100 à 200 ms) introduiraient une instabilité inacceptable.

Par le passé, les équipes d'ingénierie résolvaient ce problème en assemblant des architectures fragmentées qui ressemblaient à ceci : PostgreSQL pour les données transactionnelles, Neo4j pour les graphiques de plans, Redis pour la mise en cache périphérique et des pipelines de synchronisation en arrière-plan. Cela entraîne une dérive architecturale, une grande complexité opérationnelle et des retards de cohérence à terme.

Comment nous procédons

En déployant Google Cloud Spanner Omni en périphérie et en l'associant à Google Gemini 3.8 Flash, nous avons créé le jeu Spanner Omni Pickleball, une plate-forme de jeu de sport à moteur unique qui unifie la logique métier relationnelle, la télémétrie de tir ISO GQL native et le coaching tactique par IA en temps réel.

Spanner Omni

Spanner Omni intègre le moteur de base de données distribuée à l'échelle planétaire de Google, avec une évolutivité horizontale, des transactions ACID et des fonctionnalités multimodèles (relationnel, graphe, vectoriel et analytique), dans des environnements autogérés, sur site, multicloud et sous air gap. Spanner Omni est une édition téléchargeable et autogérée de Google Cloud Spanner. Il offre les principales fonctionnalités de base de données distribuée de Spanner (segmentation automatique, réplication basée sur Paxos et cohérence externe forte) où que vous choisissiez de l'exécuter :

acc871a470f63b29.png

Points abordés

Dans cet atelier de programmation, vous allez passer d'architectures de bases de données cloisonnées traditionnelles à une solution de base de données de périphérie moderne et unifiée. Vous allez apprendre à effectuer les tâches suivantes :

  1. Déployez et orchestrez Spanner Omni localement à l'aide de Docker pour gérer des charges de travail relationnelles hautes performances à instance unique.
  2. Concevez un schéma de graphe relationnel et ISO GQL unifié qui mappe les modèles économiques transactionnels avec la télémétrie de jeu à haute dimension, sans nécessiter de synchronisation de base de données externe.
  3. Implémentez la sérialisabilité ACID stricte dans les transactions en lecture-écriture Python pour éviter de manière programmatique les conditions de concurrence et éliminer les conflits de double réservation.
  4. Créez un pipeline GraphRAG natif qui exploite des requêtes de graphe ISO GQL intégrées à SQL pour fournir des données de télémétrie de match en temps réel et contextualisées directement à Google Gemini pour un coaching tactique instantané.

Conditions requises

  • Une machine locale standard ou une VM de développement exécutant Linux (Ubuntu 22.04 ou version ultérieure), macOS ou Windows.
  • Docker Desktop ou Docker Engine installé et en cours d'exécution.
  • Python 3.10 ou version ultérieure installé.
  • Un éditeur de texte ou un IDE (comme VS Code ou PyCharm).
  • Une clé API Gemini Google active. (Remarque : Aucun compte de facturation Google Cloud n'est requis pour exécuter Spanner Omni localement, car il s'exécute sous une licence d'évaluation pour les développeurs autogérée et sans frais.)

2. Déploiement et configuration de Spanner Omni

Dans cette étape, nous allons lancer notre instance de base de données Edge locale. Spanner Omni s'exécute dans un conteneur hautement optimisé, ce qui vous permet de bénéficier de fonctionnalités relationnelles à l'échelle planétaire sur votre ordinateur portable de développement local.

  1. Ouvrez votre terminal et créez un volume Docker persistant pour stocker les schémas de votre base de données et la télémétrie du jeu :
docker volume create spanner_omni_data
  1. Extrayez et démarrez l'image de conteneur Spanner Omni officielle configurée en mode développeur sur un seul serveur :
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
  1. Vérifiez que le conteneur a bien été lancé :
docker ps

🛠️ Dépannage rapide en une ligne : si le lancement échoue, car les ports 9010 ou 9020 sont déjà utilisés par des processus en arrière-plan ou des exécutions d'émulateur anciennes, exécutez ce script de nettoyage dans votre 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. Conception de schémas LDD relationnels et de graphes ISO GQL

Une fois Spanner Omni activé, nous pouvons établir notre schéma de base de données. Ce schéma sert de source unique de vérité, unifiant les entités commerciales relationnelles (telles que les terrains, les joueurs et les réservations) avec des schémas de télémétrie de graphes rapides et connectés (tirs, zones et transitions).

Pour créer les tables opérationnelles et de télémétrie :

  1. Connectez-vous à votre base de données à l'aide de la CLI Spanner ou de votre client d'application.
  2. Exécutez les instructions LDD suivantes :

Aucune action n'est requise de votre part à ce stade, car nous gérons cela dans le code de l'application :

-- ============================================================================
-- 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

💡 Pourquoi les codes temporels de commit sont-ils importants ? Spécifier OPTIONS (allow_commit_timestamp=true) permet un classement monotone et précis à la microseconde près sur toutes les instances répliquées de nœuds locaux. C'est essentiel pour synchroniser les événements de jeu à grande vitesse (comme les séquences vidéo de l'arbitrage) avec les journaux de télémétrie des tirs correspondants dans la base de données.

4. Contrôle de simultanéité ACID et prévention des réservations en double

Sur les portails de réservation de terrains de sport, plusieurs utilisateurs se disputent souvent les mêmes créneaux horaires. Pour éviter les conditions de concurrence, nous allons écrire une transaction de lecture-écriture Python dans Spanner Omni afin de garantir une sérialisabilité absolue et d'éviter les réservations en double.

Aucune action n'est requise de votre part pour le moment. Observez simplement le code :

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. Moteur physique 60 FPS en temps réel et moteur de bot DUPR ajustable

L'interface de notre application utilise un rendu de simulation HTML5 Canvas 2,5D à 60 images par seconde (FPS). Ce rendu s'exécute de manière totalement dissociée des couches de la base de données pour maintenir des performances d'une fluidité exceptionnelle, en n'envoyant la télémétrie qu'à la fin des rallyes.

Le simulateur est doté d'un moteur d'IA OmniBot ajustable qui utilise une matrice de difficulté basée sur les niveaux DUPR (Dynamic Universal Pickleball Rating) réels.

1640056a824b47e6.png

Aucune action requise de votre part pour le moment. Il vous suffit d'examiner les détails.

6. Traitement graphique de la télémétrie des clichés ISO GQL

À la fin de chaque rallye, l'interface publie le graphique de trajectoire connecté sur notre point de terminaison de backend /api/rally-end. Chaque plan est enregistré en tant que nœud relationnel, et les séquences sont liées de manière dynamique via des arêtes de transition.

Aucune action requise de votre part pour le moment. Observez simplement le code :

@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...

En exploitant le moteur de requêtes de graphes ISO GQL natif de Spanner, un entraîneur ou un programme d'analyse peut interroger instantanément des modèles structurels complexes et profonds sans exécuter de jointures de base de données volumineuses :

-- 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 tactique avec l'IA Gemini et chat RAG ancré

Nous allons maintenant activer le cerveau de notre application : Google Gemini 3.8 Flash. Notre backend Python utilise le modèle pour fournir trois fonctionnalités d'IA en temps réel ancrées directement par Spanner Omni.

Le système utilise Google Gemini 3.8 Flash pour certains workflows d'IA distincts en temps réel :

Analyse des échanges en temps réel et arbitre tactique :

Après chaque point, Gemini analyse la chaîne de coups, la difficulté du bot et le positionnement sur le court.

Concierge IA et analyste de match ancrés sur plusieurs tables Spanner :

Les utilisateurs peuvent consulter les horaires des courts, les tarifs des installations et demander "Comment ai-je joué contre OmniBot ?" en langage naturel. Le backend interroge dynamiquement Spanner Omni sur quatre domaines (Courts, Bookings, GameSessions et Shots) pour ancrer Gemini sans aucune hallucination.

API Analytics des performances en temps réel (/api/performance) :

Il agrège les ratios victoires/défaites en direct, la profondeur moyenne des échanges et les sessions de jeu récentes directement à partir de la table GameSessions de Spanner Omni pour le tableau de bord du joueur.

Aucune action requise de votre part pour le moment. Il vous suffit d'examiner les détails.

8. Configurer, exécuter et tester

Lancez l'application complète sur votre ordinateur local pour vérifier le système de bout en bout.

  1. Ouvrez le terminal de votre hôte et clonez le dépôt du projet :
git clone https://github.com/AbiramiSukumaran/pickleball

cd pickleball
  1. Obtenez votre clé API Gemini en suivant les étapes de la documentation suivante :

https://medium.com/google-cloud/get-api-key-from-google-ai-studio-b2d2059a171d

  1. Ouvrez le fichier .env dans votre éditeur et configurez les détails de votre environnement :
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"
  1. Installez les dépendances Python et exécutez l'application :
pip install -r requirements.txt

python app.py
  1. Ouvrez votre navigateur Web et accédez à http://localhost:5000 pour commencer à jouer. Vous pouvez réserver des créneaux sur le terrain dans l'onglet Reservation (Réservation), jouer contre le DUPR OmniBot ajustable et consulter votre Gemini Match Analyst en temps réel sur le terrain.

Ouvrez le navigateur Web ou cliquez sur le lien vers l'application exécutée sur votre hôte local.

a2d18f8288607c66.png

9. Félicitations !

En suivant cet atelier de programmation, vous avez assisté à un changement de paradigme dans l'ingénierie des bases de données. Pendant des années, la création d'agents réactifs en temps réel en périphérie (dans les usines, les zones militaires ou les réseaux sportifs) a imposé des compromis de conception douloureux : assembler des pipelines ETL fragiles entre des systèmes transactionnels isolés, des nœuds de graphe distincts et des index de recherche vectorielle.

Spanner Omni élimine ce compromis. Il combine une cohérence distribuée à l'échelle planétaire, des requêtes de graphes ISO GQL et des performances relationnelles dans un seul conteneur local téléchargeable. Quel que soit votre domaine, Spanner Omni garantit que vos agents de périphérie disposent d'une vérité terrain inébranlable.