Tác nhân dữ liệu và GraphRAG trên thiết bị bằng Spanner Omni

1. Tổng quan

Lớp học lập trình này trình bày các kiến thức cơ bản về Spanner Omni, lý do Spanner Omni đóng vai trò là nền tảng dữ liệu tối ưu cho các tác nhân AI tự động, cũng như cách triển khai toàn diện một huấn luyện viên chiến thuật Pickleball và trình mô phỏng trận đấu được triển khai ở biên, dựa trên tác nhân, hoạt động trên nền tảng Spanner Omni (Quan hệ + Đồ thị GQL), Python Flask và Gemini 3.8 Flash.

Vấn đề

Các nền tảng thể thao tương tác hiện đại và môi trường trò chơi theo thời gian thực cần xử lý đồng thời ba khối lượng công việc riêng biệt:

Giao dịch hoạt động có tính đồng thời cao:

Việc đặt sân, gói thành viên của người chơi, thông tin thanh toán và lịch thi đấu đòi hỏi khả năng tuần tự hoá ACID nghiêm ngặt để loại bỏ xung đột do đặt trước trùng lặp.

Dữ liệu đo từ xa của biểu đồ đã kết nối:

Chuỗi cú đánh liên tục, chuỗi Markov chuyển đổi và biểu đồ phạm vi bao phủ khu vực yêu cầu truyền tải có độ trễ thấp mà không cần quy trình ETL cồng kềnh.

Phản hồi nhanh chóng trong chưa đầy một mili giây:

Mô phỏng vật lý 60 khung hình/giây tốc độ cao, trong đó thời gian khứ hồi của mạng đám mây diện rộng (100–200 mili giây) sẽ gây ra hiện tượng giật không thể chấp nhận được.

Trước đây, các nhóm kỹ thuật đã giải quyết vấn đề này bằng cách kết hợp các cấu trúc phân mảnh như sau: PostgreSQL cho dữ liệu giao dịch, Neo4j cho biểu đồ ảnh chụp, Redis cho bộ nhớ đệm biên và quy trình đồng bộ hoá trong nền. Điều này dẫn đến sự sai lệch về cấu trúc, độ phức tạp cao trong vận hành và độ trễ về tính nhất quán cuối cùng.

Cách chúng tôi thực hiện!

Bằng cách triển khai Google Cloud Spanner Omni ở biên và kết hợp với Google Gemini 3.8 Flash, chúng tôi đã xây dựng Trò chơi Pickleball Spanner Omni — một nền tảng trò chơi thể thao một công cụ, hợp nhất logic kinh doanh quan hệ, phép đo từ xa cú đánh ISO GQL gốc và huấn luyện viên AI chiến thuật theo thời gian thực.

Spanner Omni

Spanner Omni mang công cụ cơ sở dữ liệu phân tán quy mô toàn cầu của Google (hoàn chỉnh với khả năng mở rộng theo chiều ngang, giao dịch ACID và các chức năng đa mô hình (Quan hệ, Biểu đồ, Vectơ và Phân tích)) vào các môi trường tự quản lý, tại chỗ, đa đám mây và tách biệt. Spanner Omni là một phiên bản có thể tải xuống và tự quản lý của Google Cloud Spanner. Dịch vụ này cung cấp các chức năng cơ sở dữ liệu phân tán cốt lõi của Spanner (phân đoạn tự động, sao chép dựa trên Paxos và tính nhất quán mạnh mẽ bên ngoài) ở bất cứ nơi nào bạn chọn để chạy dịch vụ:

acc871a470f63b29.png

Kiến thức bạn sẽ học được

Trong lớp học lập trình này, bạn sẽ chuyển đổi từ kiến trúc cơ sở dữ liệu riêng biệt truyền thống sang giải pháp cơ sở dữ liệu biên hiện đại và hợp nhất. Bạn sẽ tìm hiểu cách:

  1. Triển khai và điều phối Spanner Omni trên máy bằng Docker để quản lý khối lượng công việc quan hệ hiệu suất cao, một thực thể.
  2. Thiết kế một giản đồ đồ thị GQL ISO và quan hệ hợp nhất, giúp liên kết các mô hình kinh doanh giao dịch với dữ liệu đo từ xa nhiều chiều của trò chơi mà không cần đồng bộ hoá cơ sở dữ liệu bên ngoài.
  3. Triển khai Khả năng chuyển đổi tuần tự ACID nghiêm ngặt trong các giao dịch đọc-ghi bằng Python để ngăn chặn tình huống tương tranh theo phương thức lập trình và loại bỏ xung đột đặt trước trùng lặp.
  4. Xây dựng quy trình GraphRAG gốc tận dụng các truy vấn đồ thị ISO GQL tích hợp SQL để cung cấp dữ liệu đo từ xa về trận đấu theo thời gian thực, có căn cứ theo ngữ cảnh trực tiếp vào Google Gemini để huấn luyện chiến thuật tức thì.

Yêu cầu

  • Một máy cục bộ tiêu chuẩn hoặc máy ảo phát triển chạy Linux (Ubuntu 22.04 trở lên), macOS hoặc Windows.
  • Docker Desktop hoặc Docker Engine đã được cài đặt và đang chạy.
  • Đã cài đặt Python 3.10 trở lên.
  • Trình chỉnh sửa văn bản hoặc IDE (chẳng hạn như VS Code hoặc PyCharm).
  • Khoá Gemini API đang hoạt động của Google. (Lưu ý: Bạn không cần có tài khoản thanh toán trên Google Cloud để chạy Spanner Omni trên máy vì dịch vụ này chạy theo giấy phép đánh giá dành cho nhà phát triển tự quản lý, cấp miễn phí).

2. Triển khai và định cấu hình Spanner Omni

Trong bước này, chúng ta sẽ khởi động phiên bản cơ sở dữ liệu biên cục bộ. Spanner Omni chạy bên trong một vùng chứa được tối ưu hoá cao, mang đến cho bạn các chức năng quan hệ quy mô toàn cầu trên máy tính xách tay của nhà phát triển tại địa phương.

  1. Mở cửa sổ dòng lệnh rồi tạo một ổ đĩa Docker cố định để lưu trữ giản đồ cơ sở dữ liệu và dữ liệu đo từ xa của trò chơi:
docker volume create spanner_omni_data
  1. Kéo và khởi động hình ảnh vùng chứa Spanner Omni chính thức được định cấu hình ở chế độ nhà phát triển một máy chủ:
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. Đảm bảo vùng chứa đã khởi chạy thành công:
docker ps

🛠️ Khắc phục sự cố nhanh bằng một dòng lệnh: Nếu bạn không khởi chạy được vì cổng 9010 hoặc 9020 đã được các tiến trình nền hoặc trình mô phỏng cũ sử dụng, hãy thực thi tập lệnh dọn dẹp này trong dòng lệnh:

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. Thiết kế giản đồ DDL đồ thị GQL ISO và quan hệ

Khi Spanner Omni đang hoạt động, chúng ta có thể thiết lập giản đồ cơ sở dữ liệu. Giản đồ này đóng vai trò là nguồn đáng tin cậy duy nhất, hợp nhất các thực thể kinh doanh có liên quan (chẳng hạn như Sân, Người chơi và Lượt đặt trước) với giản đồ đo từ xa dạng biểu đồ nhanh và được kết nối (Cú đánh, Khu vực và Chuyển đổi).

Cách tạo bảng hoạt động và đo từ xa:

  1. Kết nối với cơ sở dữ liệu bằng Spanner CLI hoặc ứng dụng khách của bạn
  2. Chạy các câu lệnh DDL sau

Bạn không cần làm gì cả vì chúng tôi đang giải quyết vấn đề này trong mã xử lý ứng dụng:

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

💡 Tại sao dấu thời gian cam kết lại quan trọng: Việc chỉ định OPTIONS (allow_commit_timestamp=true) cho phép sắp xếp thứ tự đơn điệu, chính xác đến từng micro giây trên tất cả bản sao nút cục bộ. Điều này rất quan trọng để đồng bộ hoá các sự kiện trò chơi tốc độ cao (như cảnh quay video gọi đường dây) với nhật ký đo từ xa tương ứng của cơ sở dữ liệu.

4. Kiểm soát đồng thời ACID và ngăn chặn đặt trước trùng lặp

Trong các cổng đặt chỗ thể thao đang hoạt động, nhiều người dùng thường tranh nhau đặt cùng một khung giờ sân. Để ngăn chặn tình huống tương tranh, chúng tôi sẽ viết một giao dịch đọc-ghi bằng Python trong Spanner Omni để đảm bảo khả năng tuần tự hoá tuyệt đối, chống đặt chỗ trùng lặp.

Bạn không cần làm gì cả, chỉ cần quan sát đoạn mã:

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. Công cụ bot DUPR có thể điều chỉnh và vật lý 60 khung hình/giây theo thời gian thực

Giao diện người dùng của ứng dụng sử dụng kết xuất mô phỏng HTML5 Canvas 2.5D ở tốc độ 60 khung hình/giây (FPS). Quá trình kết xuất này chạy hoàn toàn tách biệt với các lớp cơ sở dữ liệu để duy trì hiệu suất mượt mà, chỉ xả dữ liệu đo từ xa khi các cuộc đua kết thúc.

Trò chơi mô phỏng này có công cụ AI OmniBot có thể điều chỉnh, sử dụng ma trận độ khó dựa trên các cấp độ DUPR (Hệ thống xếp hạng Pickleball linh hoạt toàn cầu) trong thế giới thực.

1640056a824b47e6.png

Bạn không cần làm gì cả, chỉ cần xem thông tin chi tiết.

6. Xử lý đồ thị đo từ xa về ảnh chụp GQL theo tiêu chuẩn ISO

Khi kết thúc mỗi loạt đánh qua lại, giao diện người dùng sẽ đăng biểu đồ quỹ đạo đã kết nối lên điểm cuối phụ trợ /api/rally-end. Mỗi cảnh quay được ghi lại dưới dạng một nút quan hệ và các trình tự được liên kết linh động thông qua các cạnh chuyển cảnh.

Bạn không cần làm gì cả, chỉ cần quan sát mã:

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

Bằng cách tận dụng công cụ truy vấn đồ thị ISO GQL gốc của Spanner, huấn luyện viên hoặc chương trình phân tích có thể truy vấn các mẫu cấu trúc phức tạp, sâu sắc ngay lập tức mà không cần chạy các phép nối cơ sở dữ liệu cồng kềnh:

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

Bây giờ, chúng ta sẽ kích hoạt bộ não của ứng dụng: Google Gemini 3.8 Flash. Phần phụ trợ Python của chúng tôi sử dụng mô hình này để cung cấp ba chức năng AI theo thời gian thực dựa trên Spanner Omni.

Hệ thống sử dụng Google Gemini 3.8 Flash cho một số quy trình AI riêng biệt theo thời gian thực:

Phân tích pha bóng theo thời gian thực và trọng tài chiến thuật:

Sau mỗi điểm, Gemini sẽ phân tích chuỗi cú đánh, độ khó của bot và vị trí trên sân.

Trợ lý AI và nhà phân tích trận đấu dựa trên nhiều bảng trong Spanner:

Người dùng có thể truy vấn lịch thi đấu, giá thuê sân và hỏi "Tôi đã thi đấu thế nào với OmniBot?" bằng ngôn ngữ tự nhiên. Phần phụ trợ truy vấn động Spanner Omni trên bốn miền (Sân, Lượt đặt chỗ, Phiên chơi và Cú đánh) để cung cấp thông tin chính xác cho Gemini mà không có ảo tưởng.

Real-Time Performance Analytics API (/api/performance):

Tổng hợp tỷ lệ thắng-thua trực tiếp, độ sâu trung bình của loạt đánh và các phiên chơi gần đây ngay từ bảng GameSessions của Spanner Omni cho trang tổng quan của người chơi.

Bạn không cần làm gì cả, chỉ cần xem thông tin chi tiết.

8. Định cấu hình, chạy và kiểm thử!

Hãy chạy ứng dụng hoàn chỉnh trên máy cục bộ để xác minh hệ thống từ đầu đến cuối.

  1. Mở thiết bị đầu cuối của máy chủ lưu trữ và sao chép kho lưu trữ dự án:
git clone https://github.com/AbiramiSukumaran/pickleball

cd pickleball
  1. Lấy Khoá Gemini API bằng cách làm theo các bước trong tài liệu sau:

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

  1. Mở tệp .env trong trình chỉnh sửa và định cấu hình thông tin chi tiết về môi trường:
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. Cài đặt các phần phụ thuộc Python và chạy ứng dụng:
pip install -r requirements.txt

python app.py
  1. Mở trình duyệt web rồi truy cập vào http://localhost:5000 để bắt đầu chơi! Bạn có thể đặt giờ chơi trên thẻ Đặt chỗ, chơi với DUPR OmniBot có thể điều chỉnh và tham khảo ý kiến của Nhà phân tích trận đấu Gemini theo thời gian thực ngay trên sân.

Mở trình duyệt web hoặc nhấp vào đường liên kết đến ứng dụng đang chạy trong máy chủ cục bộ của bạn!

a2d18f8288607c66.png

9. Xin chúc mừng!

Khi hoàn thành lớp học lập trình này, bạn đã chứng kiến một sự thay đổi mô hình trong kỹ thuật cơ sở dữ liệu. Trong nhiều năm, việc xây dựng các tác nhân phản hồi nhanh, theo thời gian thực ở biên (trên sàn nhà máy, trong khu vực quân sự hoặc mạng thể thao bên lề) đã buộc phải có những thoả hiệp thiết kế khó khăn: kết hợp các quy trình ETL dễ hỏng giữa các hệ thống giao dịch riêng biệt, các nút đồ thị riêng biệt và chỉ mục của Tìm kiếm vectơ.

Spanner Omni loại bỏ sự đánh đổi này. Công cụ này kết hợp tính nhất quán phân tán trên quy mô toàn cầu, khả năng truy vấn đồ thị ISO GQL và hiệu suất quan hệ vào một vùng chứa cục bộ duy nhất, có thể tải xuống. Dù bạn hoạt động trong lĩnh vực nào, Spanner Omni cũng đảm bảo các tác nhân biên của bạn có thông tin thực tế đáng tin cậy.