1. Ringkasan
Codelab ini membahas dasar-dasar Spanner Omni, alasan Spanner Omni berfungsi sebagai fondasi data terbaik untuk agen AI otonom, dan implementasi menyeluruh yang kasar dari pelatih taktis dan simulator pertandingan Pickleball agentic yang di-deploy di edge dan didukung oleh Spanner Omni (Relasional + Graph GQL), Python Flask, dan Gemini 3.8 Flash.
Masalah
Platform olahraga interaktif modern dan lingkungan game real-time memerlukan penanganan tiga workload yang berbeda secara bersamaan:
Transaksi Operasional dengan Konkurensi Tinggi:
Pemesanan lapangan, keanggotaan pemain, penagihan, dan jadwal turnamen yang memerlukan serialisasi ACID yang ketat untuk menghilangkan konflik pemesanan ganda.
Telemetri Grafik Terhubung:
Urutan pukulan reli berkelanjutan, rantai Markov transisi, dan grafik cakupan zona yang memerlukan penjelajahan latensi rendah tanpa pipeline ETL yang besar.
Responsivitas Edge Sub-Milidetik:
Simulasi fisika 60 FPS yang serba cepat, di mana perjalanan bolak-balik jaringan cloud area luas (100–200 md) akan menimbulkan jitter yang tidak dapat diterima.
Secara historis, tim engineering mengatasi hal ini dengan menggabungkan arsitektur yang terfragmentasi, yang terlihat seperti ini: PostgreSQL untuk data transaksional, Neo4j untuk grafik pengambilan gambar, Redis untuk edge caching, dan pipeline sinkronisasi latar belakang. Hal ini menyebabkan penyimpangan arsitektur, kompleksitas operasional yang tinggi, dan penundaan konsistensi tertunda.
Cara kami melakukannya
Dengan men-deploy Google Cloud Spanner Omni di edge dan menggabungkannya dengan Google Gemini 3.8 Flash, kami membangun Game Pickleball Spanner Omni — platform game olahraga bermesin tunggal yang menyatukan logika bisnis relasional, telemetri pukulan ISO GQL native, dan pelatihan AI taktis real-time.
Spanner Omni
Spanner Omni menghadirkan mesin database terdistribusi berskala planet dari Google — lengkap dengan skalabilitas horizontal, transaksi ACID, dan kemampuan multimodel (Relasional, Grafik, Vektor, dan Analytics) — ke lingkungan yang dikelola sendiri, lokal, multicloud, dan air-gapped. Spanner Omni adalah edisi Google Cloud Spanner yang dapat didownload dan dikelola sendiri. Spanner memberikan kemampuan database terdistribusi inti Spanner — sharding otomatis, replikasi berbasis Paxos, dan konsistensi eksternal yang kuat — di mana pun Anda memilih untuk menjalankannya:

Yang akan Anda pelajari
Dalam codelab ini, Anda akan beralih dari arsitektur database tradisional yang terpisah-pisah ke solusi database edge terpadu yang modern. Anda akan mempelajari cara:
- Men-deploy dan Mengorkestrasi Spanner Omni secara lokal menggunakan Docker untuk mengelola workload relasional instance tunggal berperforma tinggi.
- Mendesain Skema Graf GQL ISO & Relasional Terpadu yang memetakan model bisnis transaksional bersama telemetri game berdimensi tinggi tanpa memerlukan sinkronisasi database eksternal.
- Terapkan Serializability ACID Ketat di dalam transaksi baca-tulis Python untuk mencegah kondisi race secara terprogram dan menghilangkan konflik pemesanan ganda.
- Bangun Pipeline GraphRAG Native yang memanfaatkan kueri graf ISO GQL yang terintegrasi dengan SQL untuk memasukkan telemetri pertandingan real-time yang didasarkan pada konteks langsung ke Google Gemini untuk pelatihan taktis instan.
Persyaratan
- Mesin lokal standar atau VM pengembangan yang menjalankan Linux (Ubuntu 22.04+), macOS, atau Windows.
- Docker Desktop atau Docker Engine telah diinstal dan berjalan.
- Python 3.10 atau yang lebih baru telah diinstal.
- Editor teks atau IDE (seperti VS Code atau PyCharm).
- Kunci Gemini API Google yang aktif. (Catatan: Akun penagihan Google Cloud tidak diperlukan untuk menjalankan Spanner Omni secara lokal karena berjalan di bawah lisensi evaluasi developer yang dikelola sendiri dan paket gratis).
2. Deployment & Konfigurasi Spanner Omni
Pada langkah ini, kita akan menjalankan instance database edge lokal. Spanner Omni berjalan di dalam container yang sangat dioptimalkan, sehingga memberi Anda kemampuan relasional berskala global di laptop developer lokal Anda.
- Buka terminal Anda dan buat volume Docker persisten untuk menyimpan skema database dan telemetri game Anda:
docker volume create spanner_omni_data
- Ambil dan mulai image container Spanner Omni resmi yang dikonfigurasi dalam mode developer server tunggal:
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
- Pastikan container berhasil diluncurkan:
docker ps
🛠️ Pemecahan Masalah Singkat: Jika peluncuran Anda gagal karena port 9010 atau 9020 sudah digunakan oleh proses latar belakang atau emulator lama, jalankan skrip pembersihan ini di terminal Anda:
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. Desain Skema DDL Graf GQL ISO & Relasional
Dengan Spanner Omni yang aktif, kita dapat membuat skema database. Skema ini berfungsi sebagai satu sumber tepercaya, yang menyatukan entitas bisnis relasional (seperti Lapangan, Pemain, dan Pemesanan) dengan skema telemetri grafik yang cepat dan terhubung (Tembakan, Zona, dan Transisi).
Untuk membuat tabel operasional dan telemetri:
- Menghubungkan ke database menggunakan Spanner CLI atau klien aplikasi Anda
- Jalankan pernyataan DDL berikut
Anda tidak perlu melakukan tindakan apa pun untuk saat ini karena kami sedang menangani masalah ini dalam kode aplikasi:
-- ============================================================================
-- 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
💡 Alasan Stempel Waktu Commit Penting: Menentukan OPTIONS (allow_commit_timestamp=true) memungkinkan pengurutan monotonik yang akurat hingga mikrodetik di semua replika node lokal. Hal ini sangat penting untuk menyinkronkan peristiwa game berkecepatan tinggi (seperti rekaman video line-call) dengan log telemetri pukulan database yang sesuai.
4. Kontrol Konkurensi ACID & Pencegahan Pemesanan Ganda
Di portal pemesanan olahraga yang aktif, beberapa pengguna sering kali berlomba untuk memesan slot waktu lapangan yang sama. Untuk mencegah kondisi race, kita akan menulis transaksi baca-tulis Python di Spanner Omni untuk memastikan serialisasi absolut yang tahan terhadap pemesanan ganda.
Anda tidak perlu melakukan tindakan apa pun sekarang, cukup amati kodenya:
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. Fisika 60 FPS Real-Time & Mesin Bot DUPR yang Dapat Disesuaikan
Frontend aplikasi kami menggunakan rendering simulasi Canvas HTML5 2.5D pada 60 Frame Per Detik (FPS). Rendering ini berjalan sepenuhnya terpisah dari lapisan database untuk mempertahankan performa yang sangat lancar, dan hanya menghapus telemetri saat reli selesai.
Simulator ini dilengkapi mesin AI OmniBot yang dapat disesuaikan dan memanfaatkan matriks tingkat kesulitan berdasarkan level DUPR (Dynamic Universal Pickleball Rating) di dunia nyata.

Anda tidak perlu melakukan tindakan apa pun sekarang, cukup amati detailnya.
6. Pemrosesan Grafik Telemetri ISO GQL Shot
Di akhir setiap reli, frontend akan memposting grafik lintasan yang terhubung ke endpoint backend /api/rally-end. Setiap bidikan direkam sebagai node relasional, dan urutan ditautkan secara dinamis melalui edge transisi.
Anda tidak perlu melakukan tindakan apa pun sekarang, cukup amati kodenya:
@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...
Dengan memanfaatkan mesin kueri grafik ISO GQL native Spanner, pelatih atau program analisis dapat mengkueri pola struktural deep-hop yang kompleks secara instan tanpa menjalankan gabungan database yang besar:
-- 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. Pelatihan Taktis AI Gemini & Chat RAG yang Di-grounding
Sekarang, kita akan mengaktifkan otak aplikasi kita: Google Gemini 3.8 Flash. Backend Python kami menggunakan model ini untuk memberikan tiga kemampuan AI real-time yang didasarkan langsung oleh Spanner Omni.
Sistem ini menggunakan Google Gemini 3.8 Flash untuk beberapa alur kerja AI real-time yang berbeda:
Analisis Reli Real-Time & Wasit Taktis:
Setelah setiap poin, Gemini menganalisis rangkaian pukulan, tingkat kesulitan bot, dan posisi di lapangan.
Spanner Multi-Table Grounded AI Concierge & Match Analyst:
Pengguna dapat mengkueri jadwal lapangan, tarif fasilitas, dan bertanya "Bagaimana performa saya melawan OmniBot?" dalam bahasa alami. Backend secara dinamis mengkueri Spanner Omni di empat domain (Courts, Bookings, GameSessions, dan Shots) untuk melakukan grounding Gemini tanpa halusinasi.
Real-Time Performance Analytics API (/api/performance):
Menggabungkan rasio menang-kalah live, kedalaman reli rata-rata, dan sesi game terbaru langsung dari tabel GameSessions Spanner Omni untuk dasbor pemain.
Anda tidak perlu melakukan tindakan apa pun sekarang, cukup amati detailnya.
8. Konfigurasi, Jalankan, dan Uji!
Mari kita luncurkan aplikasi lengkap di komputer lokal Anda untuk memverifikasi sistem secara menyeluruh.
- Buka terminal host Anda dan clone repositori project:
git clone https://github.com/AbiramiSukumaran/pickleball
cd pickleball
- Dapatkan Kunci Gemini API Anda dengan mengikuti langkah-langkah dalam dokumentasi berikut:
https://medium.com/google-cloud/get-api-key-from-google-ai-studio-b2d2059a171d
- Buka file
.envdi editor dan konfigurasikan detail lingkungan Anda:
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"
- Instal dependensi Python dan jalankan aplikasi:
pip install -r requirements.txt
python app.py
- Buka browser web Anda, lalu buka
http://localhost:5000untuk mulai bermain. Anda dapat memesan waktu lapangan di Tab Reservasi, bermain melawan DUPR OmniBot yang dapat disesuaikan, dan berkonsultasi dengan Analis Pertandingan Gemini real-time di sisi lapangan.
Buka browser web atau klik link ke aplikasi yang berjalan di localhost Anda.

9. Selamat!
Dengan menyelesaikan codelab ini, Anda telah menyaksikan perubahan paradigma dalam rekayasa database. Selama bertahun-tahun, membangun agen real-time yang responsif di edge (di lantai pabrik, di zona militer, atau jaringan olahraga di sisi lapangan) memaksa kompromi desain yang menyakitkan: menyatukan pipeline ETL yang rapuh antara sistem transaksional yang terisolasi, node grafik terpisah, dan indeks penelusuran vektor.
Spanner Omni menghilangkan kompromi ini. Layanan ini menggabungkan konsistensi terdistribusi berskala global, kueri grafik ISO GQL, dan performa relasional ke dalam satu container lokal yang dapat didownload. Apa pun domain Anda, Spanner Omni menjamin agen edge Anda memiliki kebenaran nyata yang tak tergoyahkan.