1. Обзор
В этой практической работе рассматриваются основы Spanner Omni, причины, по которым он служит идеальной основой данных для автономных ИИ-агентов, и приблизительная сквозная реализация развернутого на периферии агентного тактического тренера по пиклболу и симулятора матчей на базе Spanner Omni (Relational + Graph GQL), Python Flask и Gemini 3.8 Flash.
Проблема
Современные интерактивные спортивные платформы и игровые среды реального времени требуют одновременной обработки трех различных рабочих нагрузок:
Операционные транзакции с высокой степенью параллелизма
Бронирование кортов, членство игроков, выставление счетов и расписание турниров, требующие строгой сериализуемости ACID для устранения конфликтов двойного бронирования.
Телеметрия подключенных графов:
Непрерывные последовательности ударов, цепи Маркова для переходов и графики покрытия зон, требующие обхода с низкой задержкой без громоздких конвейеров ETL.
Мгновенный отклик на периферии
Физические симуляции с частотой 60 кадров в секунду, в которых задержка при передаче данных по глобальной облачной сети (100–200 мс) может привести к неприемлемому дрожанию.
Раньше инженеры решали эту проблему, объединяя фрагментированные архитектуры, которые выглядели примерно так: PostgreSQL для транзакционных данных, Neo4j для графов, Redis для пограничного кеширования и конвейеры фоновой синхронизации. Это приводит к архитектурному дрейфу, высокой сложности операций и задержкам в обеспечении согласованности в конечном счете.
Как мы это делаем!
Развернув Google Cloud Spanner Omni на периферии и объединив его с Google Gemini 3.8 Flash, мы создали Spanner Omni Pickleball Game – спортивную игровую платформу на одном движке, которая объединяет реляционную бизнес-логику, нативную телеметрию ударов ISO GQL и тактического ИИ-тренера, работающего в реальном времени.
Spanner Omni
Spanner Omni – это распределенная база данных Google планетарного масштаба с горизонтальной масштабируемостью, транзакциями ACID и мультимодельными возможностями (реляционная, графовая, векторная и аналитическая). Она предназначена для самоуправляемых, локальных, мультиоблачных и изолированных сред. Spanner Omni – это скачиваемая, самостоятельно управляемая версия Google Cloud Spanner. Она предоставляет основные возможности распределенной базы данных Spanner – автоматическое сегментирование, репликацию на основе Paxos и строгую внешнюю согласованность – независимо от того, где вы решите ее запустить:

Что вы узнаете
В этой практической работе вы перейдете от традиционных изолированных архитектур баз данных к современному унифицированному решению для периферийных баз данных. Вы научитесь:
- Развертывайте и оркестрируйте Spanner Omni локально с помощью Docker, чтобы управлять высокопроизводительными реляционными рабочими нагрузками с одним экземпляром.
- Разработайте единую схему реляционного графа и графа ISO GQL, которая сопоставляет транзакционные бизнес-модели с многомерной игровой телеметрией без необходимости синхронизации внешней базы данных.
- Реализуйте строгую сериализуемость ACID в транзакциях чтения-записи Python, чтобы программно предотвратить состояние гонки и устранить конфликты двойного бронирования.
- Создайте нативный конвейер GraphRAG, который использует интегрированные с SQL запросы графов ISO GQL, чтобы передавать телеметрию матча в реальном времени с учетом контекста прямо в Google Gemini для мгновенного тактического коучинга.
Требования
- Стандартный локальный компьютер или виртуальная машина для разработки под управлением Linux (Ubuntu 22.04 или более поздней версии), macOS или Windows.
- Установленное и запущенное приложение Docker Desktop или Docker Engine.
- Установлена версия Python 3.10 или более поздняя.
- Текстовый редактор или интегрированная среда разработки (например, VS Code или PyCharm).
- Активный ключ Gemini API. Примечание. Для локального запуска Spanner Omni не требуется платежный аккаунт Google Cloud, поскольку он работает по бесплатной лицензии разработчика.
2. Развертывание и настройка Spanner Omni
На этом шаге мы запустим локальный экземпляр базы данных Edge. Spanner Omni работает в высокооптимизированном контейнере, предоставляя вам возможности реляционной базы данных планетарного масштаба на вашем локальном ноутбуке разработчика.
- Откройте терминал и создайте постоянный том Docker для хранения схем баз данных и телеметрии игр:
docker volume create spanner_omni_data
- Извлеките и запустите официальный образ контейнера Spanner Omni, настроенный в режиме разработчика с одним сервером:
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
- Убедитесь, что контейнер запущен успешно:
docker ps
🛠️ Быстрое устранение неполадок. Если запуск не удался, потому что порты 9010 или 9020 уже используются фоновыми процессами или устаревшими эмуляторами, выполните в терминале следующий скрипт:
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. Реляционная схема и схема DDL графа ISO GQL
После активации Spanner Omni мы можем создать схему базы данных. Эта схема служит единым источником достоверных данных, объединяя реляционные бизнес-объекты (такие как корты, игроки и бронирования) с быстрыми, связанными схемами телеметрии графов (броски, зоны и переходы).
Чтобы создать таблицы операционных данных и телеметрии, выполните следующие действия:
- Подключитесь к базе данных с помощью Spanner CLI или клиента приложения.
- Выполните следующие инструкции DDL
От вас не требуется никаких действий, поскольку мы устраняем эту проблему в коде приложения.
-- ============================================================================
-- 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
💡 Почему важны временные метки фиксации. Указание OPTIONS (allow_commit_timestamp=true) позволяет упорядочивать данные по монотонно возрастающему времени с точностью до микросекунды во всех локальных репликах узлов. Это критически важно для синхронизации быстро происходящих игровых событий (например, видеозаписи удара по мячу) с соответствующими телеметрическими данными в базе данных.
4. Управление параллелизмом ACID и предотвращение двойного бронирования
На популярных порталах бронирования спортивных площадок пользователи часто соревнуются за одни и те же временные интервалы. Чтобы предотвратить состояние гонки, мы напишем транзакцию чтения-записи на Python в Spanner Omni, чтобы обеспечить абсолютную сериализуемость с защитой от двойного бронирования.
Сейчас от вас не требуется никаких действий, просто посмотрите на код:
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. Физика в реальном времени с частотой 60 кадров/с и настраиваемый бот DUPR
Внешний интерфейс нашего приложения использует симуляцию 2.5D HTML5 Canvas с частотой 60 кадров в секунду. Рендеринг полностью отделен от уровней базы данных, чтобы поддерживать плавную производительность, а телеметрия сбрасывается только после завершения ралли.
В симуляторе есть настраиваемый ИИ-движок OmniBot, который использует матрицу сложности на основе реальных уровней DUPR (Dynamic Universal Pickleball Rating).

Сейчас от вас не требуется никаких действий. Просто ознакомьтесь с информацией.
6. Обработка графика телеметрии ISO GQL Shot
В конце каждого розыгрыша интерфейс отправляет соединенный граф траектории в конечную точку серверной части /api/rally-end. Каждый кадр записывается как реляционный узел, а последовательности динамически связываются через переходные ребра.
Сейчас от вас не требуется никаких действий, просто посмотрите на код:
@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...
Используя встроенный в Spanner механизм запросов к графам ISO GQL, тренер или аналитическая программа могут мгновенно запрашивать сложные структурные шаблоны с глубокими переходами без выполнения громоздких объединений баз данных:
-- 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 и чат с RAG на основе данных
Теперь мы активируем мозг нашего приложения: Google Gemini 3.8 Flash. Наш бэкенд Python использует модель для предоставления трех возможностей ИИ в реальном времени, основанных непосредственно на Spanner Omni.
Для некоторых рабочих процессов ИИ в реальном времени система использует Google Gemini 3.8 Flash:
Анализ розыгрышей в реальном времени и тактический судья
После каждого очка Gemini анализирует цепочку ударов, сложность бота и позиционирование на корте.
Spanner Multi-Table Grounded AI Concierge & Match Analyst:
Пользователи могут запрашивать расписание кортов, тарифы на услуги и спрашивать "Как я сыграл против OmniBot?" на естественном языке. Бэкенд динамически запрашивает Spanner Omni в четырех доменах (Courts, Bookings, GameSessions и Shots), чтобы обосновать Gemini без галлюцинаций.
Real-Time Performance Analytics API (/api/performance):
Агрегирует текущие соотношения побед и поражений, среднюю глубину ралли и недавние игровые сеансы непосредственно из таблицы GameSessions Spanner Omni для панели управления игрока.
Сейчас от вас не требуется никаких действий. Просто ознакомьтесь с информацией.
8. Настройте, запустите и протестируйте
Давайте запустим полное приложение на вашем локальном компьютере, чтобы проверить систему от начала до конца.
- Откройте терминал хоста и клонируйте репозиторий проекта:
git clone https://github.com/AbiramiSukumaran/pickleball
cd pickleball
- Чтобы получить ключ Gemini API, следуйте инструкциям в этой документации:
https://medium.com/google-cloud/get-api-key-from-google-ai-studio-b2d2059a171d
- Откройте файл
.envв редакторе и настройте сведения о среде:
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"
- Установите зависимости Python и запустите приложение:
pip install -r requirements.txt
python app.py
- Откройте браузер и перейдите на страницу
http://localhost:5000, чтобы начать играть. Бронируйте корты на вкладке Reservation (Бронирование), играйте против настраиваемого DUPR OmniBot и консультируйтесь с Gemini Match Analyst прямо на корте.
Откройте браузер или нажмите на ссылку, чтобы запустить приложение на локальном хосте.

9. Поздравляем!
Выполнив эту практическую работу, вы стали свидетелем смены парадигмы в разработке баз данных. В течение многих лет создание адаптивных агентов, работающих в реальном времени на периферии (на заводах, в военных зонах или на спортивных площадках), требовало болезненных компромиссов в проектировании: объединения хрупких конвейеров ETL между изолированными транзакционными системами, отдельными узлами графов и индексами векторного поиска.
Spanner Omni устраняет этот компромисс. Он сочетает в себе распределенную согласованность планетарного масштаба, запросы к графам ISO GQL и реляционную производительность в одном загружаемом локальном контейнере. Вне зависимости от домена Spanner Omni гарантирует, что у ваших периферийных агентов будет надежная фундаментальная истина.