1. نظرة عامة
يتناول هذا الدرس التطبيقي حول الترميز أساسيات Spanner Omni، وسبب كونه قاعدة البيانات الأساسية لوكلاء الذكاء الاصطناعي المستقلين، وتنفيذًا تقريبيًا شاملاً لمدرب تكتيكي ومحاكي مباريات Pickleball مستند إلى الوكلاء ومنشور على الأجهزة الطرفية، ويعمل من خلال Spanner Omni (Relational + Graph GQL) وPython Flask وGemini 3.8 Flash.
المشكلة
تتطلّب المنصات الرياضية التفاعلية الحديثة وبيئات الألعاب في الوقت الفعلي التعامل مع ثلاثة أعباء عمل متباينة في الوقت نفسه:
العمليات التشغيلية ذات التزامن العالي:
حجوزات الملاعب واشتراكات اللاعبين والفوترة وجداول البطولات التي تتطلّب تسلسلاً صارمًا لضمان اتساق البيانات (ACID) من أجل تجنُّب تعارضات الحجوزات المزدوجة.
Connected Graph Telemetry:
تسلسلات لقطات الرالي المستمرة، وسلاسل ماركوف الانتقالية، ورسوم بيانية لتغطية المنطقة تتطلب اجتيازًا بزمن انتقال منخفض بدون مسارات ETL ضخمة.
استجابة Edge في أقل من جزء من الثانية:
محاكاة فيزيائية سريعة الوتيرة بمعدل 60 لقطة في الثانية، حيث تؤدي عمليات نقل البيانات ذهابًا وإيابًا في شبكة السحابة الإلكترونية الواسعة النطاق (100-200 ملي ثانية) إلى حدوث تذبذب غير مقبول.
في السابق، كانت الفِرق الهندسية تحلّ هذه المشكلة من خلال دمج بنى مجزّأة تبدو كالتالي: PostgreSQL لبيانات المعاملات، وNeo4j للرسومات البيانية للّقطات، وRedis للتخزين المؤقت على الأجهزة الطرفية، ومسارات المزامنة في الخلفية. ويؤدي ذلك إلى حدوث انحراف في البنية، وتعقيد تشغيلي كبير، وتأخيرات في الاتساق النهائي.
طريقتنا في النجاح
من خلال نشر Google Cloud Spanner Omni على الأجهزة الطرفية وربطه بنموذج Google Gemini 3.8 Flash، أنشأنا لعبة Spanner Omni Pickleball، وهي منصة ألعاب رياضية بمحرك واحد توحّد منطق الأعمال العلائقي، وبيانات القياس عن بُعد للّقطات المتوافقة مع معيار ISO GQL، والتدريب التكتيكي بالذكاء الاصطناعي في الوقت الفعلي.
Spanner Omni
يوفّر Spanner Omni محرّك قاعدة البيانات الموزّعة على مستوى العالم من Google، مع إمكانية التوسّع الأفقي ومعاملات ACID وإمكانات النماذج المتعدّدة (العلائقية والرسومية والمتّجهة والتحليلات)، في بيئات مُدارة ذاتيًا ومحلية ومتعدّدة السحابة الإلكترونية ومعزولة عن الشبكة. Spanner Omni هو إصدار مُدار ذاتيًا وقابل للتنزيل من Google Cloud Spanner. وتوفّر إمكانات قاعدة البيانات الموزّعة الأساسية في Spanner، مثل التقسيم التلقائي للبيانات والنسخ المتماثل المستند إلى Paxos والاتساق الخارجي القوي، أينما اخترت تشغيلها:

أهداف الدورة التعليمية
في هذا الدرس التطبيقي حول الترميز، ستنتقل من بنى قواعد البيانات التقليدية المنعزلة إلى حلّ حديث وموحّد لقواعد البيانات الطرفية. ستتعلّم كيفية:
- يمكنك نشر Spanner Omni وتنظيمه محليًا باستخدام Docker لإدارة أعباء العمل ذات الأداء العالي في قواعد البيانات العلائقية ذات المثيل الواحد.
- تصميم مخطط رسم بياني موحّد يجمع بين قواعد البيانات العلائقية ولغة ISO GQL، بحيث يربط بين نماذج الأعمال الخاصة بالمعاملات وبيانات القياس عن بُعد العالية الأبعاد في الألعاب، وذلك بدون الحاجة إلى مزامنة قواعد البيانات الخارجية.
- يمكنك تنفيذ ميزة "التسلسل الصارم لـ ACID" داخل معاملات القراءة والكتابة في Python لمنع حالات السباق برمجيًا والتخلّص من تعارضات الحجز المزدوج.
- أنشئ مسار معالجة Native GraphRAG يستفيد من طلبات بحث ISO GQL للرسوم البيانية المدمجة مع SQL لتغذية بيانات القياس عن بُعد للمباريات في الوقت الفعلي وبشكل مستند إلى السياق مباشرةً في Google Gemini لتقديم تدريب تكتيكي فوري.
المتطلبات
- جهاز عادي على الجهاز أو جهاز افتراضي للتطوير يعمل بنظام التشغيل Linux (الإصدار Ubuntu 22.04 أو إصدار أحدث) أو macOS أو Windows.
- تثبيت Docker Desktop أو Docker Engine وتشغيلهما
- تثبيت الإصدار 3.10 من Python أو إصدار أحدث
- محرِّر نصوص أو بيئة تطوير متكاملة (مثل VS Code أو PyCharm)
- مفتاح Google Gemini API نشط (ملاحظة: لا يلزم توفّر حساب فوترة على Google Cloud لتشغيل Spanner Omni محليًا لأنّه يعمل بموجب ترخيص تقييم مطوّر مُدار ذاتيًا من الفئة المجانية).
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 للرسم البياني في GQL العلائقي والمتوافق مع ISO
بعد تفعيل Spanner Omni، يمكننا إنشاء مخطّط قاعدة البيانات. يعمل هذا المخطط كمصدر وحيد للحقيقة، حيث يوحّد الكيانات التجارية العلائقية (مثل الملاعب واللاعبين والحجوزات) مع مخططات قياس عن بُعد للرسومات البيانية السريعة والمتصلة (التسديدات والمناطق والانتقالات).
لإنشاء جداول العمليات والقياس عن بُعد:
- الاتصال بقاعدة البيانات باستخدام واجهة سطر الأوامر في Spanner أو برنامج التطبيق
- نفِّذ عبارات 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- محرّك DUPR Bot قابل للتعديل مع تأثيرات حركية في الوقت الفعلي بمعدّل 60 لقطة في الثانية
تستخدم الواجهة الأمامية لتطبيقنا محاكاة HTML5 Canvas ثنائية الأبعاد ونصف، ويتم عرضها بمعدل 60 لقطة في الثانية (FPS). يتم تشغيل هذا العرض بشكل منفصل تمامًا عن طبقات قاعدة البيانات للحفاظ على أداء سلس للغاية، ولا يتم إرسال بيانات القياس عن بُعد إلا عند انتهاء السباقات.
تتميز هذه اللعبة بمحاكاة محرّك OmniBot AI القابل للتعديل، والذي يستفيد من مصفوفة صعوبة تستند إلى مستويات DUPR (التقييم العالمي الديناميكي للعبة البيكل بول) في العالم الحقيقي.

ليس عليك اتّخاذ أي إجراء الآن، بل ما عليك سوى الاطّلاع على التفاصيل.
6. معالجة الرسم البياني للقياس عن بُعد في ISO GQL Shot
At the conclusion of each rally, the frontend posts the connected trajectory graph to our backend endpoint /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...
من خلال الاستفادة من محرّك طلبات البحث في الرسم البياني ISO GQL الأصلي في Spanner، يمكن للمدرّب أو برنامج الإحصاءات البحث في أنماط هيكلية معقّدة وعميقة على الفور بدون إجراء عمليات دمج ضخمة لقواعد البيانات:
-- 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 في أربعة نطاقات (الملاعب والحجوزات وجلسات اللعب واللقطات) لتزويد 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 الهشة بين أنظمة المعاملات المعزولة، وعُقد الرسوم البيانية المنفصلة، وفهارس بحث Google.
يحلّ Spanner Omni هذه المشكلة، فهو يجمع بين الاتساق الموزَّع على مستوى العالم، وطلبات البحث في الرسم البياني ISO GQL، والأداء العلائقي في حاوية محلية واحدة قابلة للتنزيل. بغض النظر عن مجال عملك، يضمن Spanner Omni حصول وكلاء Edge على معلومات فعلية موثوقة.