Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions cmd/mo-testplan/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Copyright 2024 Matrix Origin
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// mo-testplan analyzes PR test coverage against MO's 6 test types
// (BVT, stability, chaos, big data, PITR, snapshot) using AI.
//
// Usage:
//
// mo-testplan --pr 24088
// mo-testplan --pr 24088 --json
// mo-testplan --pr 24088 --write
// mo-testplan --pr 24088 --write --create-pr
package main

import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/matrixorigin/matrixone/pkg/testinfra/analyzer"
"github.com/matrixorigin/matrixone/pkg/testinfra/writer"
)

const (
defaultAPIURL = "https://models.github.ai/inference/chat/completions"
defaultModel = "openai/gpt-4o-mini"
)

func main() {
var (
prNumber int
repo string
apiURL string
apiKey string
model string
jsonOut bool
write bool
createPR bool
)

flag.IntVar(&prNumber, "pr", 0, "PR number to analyze")
flag.StringVar(&repo, "repo", "matrixorigin/matrixone", "GitHub repository (owner/name)")
flag.StringVar(&apiURL, "api-url", defaultAPIURL, "LLM API endpoint")
flag.StringVar(&apiKey, "api-key", "", "API key (default: $GITHUB_TOKEN or gh auth token)")
flag.StringVar(&model, "model", defaultModel, "LLM model name")
flag.BoolVar(&jsonOut, "json", false, "output raw JSON instead of formatted report")
flag.BoolVar(&write, "write", false, "write suggested BVT cases to test/distributed/cases/")
flag.BoolVar(&createPR, "create-pr", false, "create a PR with written cases (implies --write)")
flag.Parse()

if prNumber == 0 {
fmt.Fprintf(os.Stderr, "Usage: mo-testplan --pr <number> [--repo owner/name] [--json]\n")
fmt.Fprintf(os.Stderr, "\nFlags:\n")
flag.PrintDefaults()
os.Exit(1)
}

if apiKey == "" {
apiKey = resolveAPIKey()
}
if apiKey == "" {
fmt.Fprintf(os.Stderr, "Error: no API key found.\n")
fmt.Fprintf(os.Stderr, "Set --api-key, $GITHUB_TOKEN, or login with: gh auth login\n")
os.Exit(1)
}

repoRoot := detectRepoRoot()
if repoRoot == "" {
fmt.Fprintf(os.Stderr, "Error: cannot detect repo root (no go.mod found).\n")
os.Exit(1)
}

cfg := analyzer.Config{
Repo: repo,
RepoRoot: repoRoot,
APIURL: apiURL,
APIKey: apiKey,
Model: model,
}

a := analyzer.New(cfg)
report, err := a.Analyze(context.Background(), prNumber)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}

if jsonOut {
data, err := json.MarshalIndent(report, "", " ")
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
os.Stdout.Write(data)
os.Stdout.WriteString("\n")
} else {
os.Stdout.WriteString(analyzer.FormatReport(report))
}

// --create-pr implies --write
if createPR {
write = true
}

if write && len(report.SuggestedCases) > 0 {
written, err := writer.WriteCases(repoRoot, report.SuggestedCases)
if err != nil {
fmt.Fprintf(os.Stderr, "Error writing cases: %v\n", err)
os.Exit(1)
}
if len(written) > 0 {
os.Stdout.WriteString("\n### 已写入文件\n")
for _, f := range written {
os.Stdout.WriteString("- ")
os.Stdout.WriteString(f)
os.Stdout.WriteString("\n")
}

if createPR {
prURL, err := writer.CreatePR(repoRoot, repo, prNumber, written)
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating PR: %v\n", err)
os.Exit(1)
}
os.Stdout.WriteString("\n### 已创建 PR\n")
os.Stdout.WriteString(strings.TrimSpace(prURL))
os.Stdout.WriteString("\n")
}
} else {
os.Stdout.WriteString("\n无新增文件(已有 case 或建议为空)\n")
}
}
}

func resolveAPIKey() string {
if key := os.Getenv("GITHUB_TOKEN"); key != "" {
return key
}
out, err := exec.Command("gh", "auth", "token").Output()
if err == nil {
return strings.TrimSpace(string(out))
}
return ""
}

func detectRepoRoot() string {
dir, err := os.Getwd()
if err != nil {
return ""
}
for {
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
return dir
}
parent := filepath.Dir(dir)
if parent == dir {
return ""
}
dir = parent
}
}
47 changes: 47 additions & 0 deletions docs/ai-skills/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# MO 整体架构

## 核心组件

MO 采用存算分离架构,由 4 个核心服务组成:

| 组件 | 包路径 | 职责 |
|------|--------|------|
| **CN (Compute Node)** | `pkg/cnservice/` | SQL 解析、计划生成、编译执行。无状态,可水平扩展 |
| **TN (Transaction Node)** | `pkg/tnservice/` | 事务处理、数据持久化、WAL 管理 |
| **LogService** | `pkg/logservice/` | 分布式日志(基于 Dragonboat/Raft),提供数据一致性保证 |
| **Proxy** | `pkg/proxy/` | 连接路由、负载均衡,将客户端请求分发到不同 CN |

## 辅助组件

| 组件 | 包路径 | 职责 |
|------|--------|------|
| **HAKeeper** | `pkg/hakeeper/` | 集群编排与健康检查,基于 Raft 状态机 |
| **ClusterService** | `pkg/clusterservice/` | 集群拓扑管理、节点发现 |
| **Gossip** | `pkg/gossip/` | 节点间元数据传播(基于 memberlist) |
| **TaskService** | `pkg/taskservice/` | 后台任务调度(CDC、自增 ID 等) |
| **Bootstrap** | `pkg/bootstrap/` | 集群初始化、版本管理 |

## 启动流程

入口:`cmd/mo-service/main.go` → `launch.go`

```
启动 → 读配置 → 启动 LogService → 启动 TN → 启动 CN → 启动 Proxy
```

配置文件位于 `etc/launch/`:
- `launch.toml` — 集群协调配置
- `cn.toml` — CN 配置(端口、引擎类型)
- `tn.toml` — TN 配置
- `log.toml` — LogService 配置

## 数据流

```
客户端 → Proxy → CN (SQL解析/计划/编译/执行)
DisttaE (分布式引擎) ←→ TN (事务/存储)
↕ ↕
FileService LogService
(对象存储) (WAL/Raft)
```
58 changes: 58 additions & 0 deletions docs/ai-skills/backup-restore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# 备份恢复(PITR / Snapshot)

## 概述

MO 支持两种备份恢复机制:
- **PITR (Point-In-Time Recovery)** — 恢复到任意时间点
- **Snapshot** — 恢复到指定快照

## PITR

### 原理
- 基于 Logtail 的增量日志回放
- 记录事务提交时间戳,恢复时回放到指定时间点
- 依赖 TN 的 checkpoint + WAL

### 关键路径
- `pkg/backup/` — 备份配置与元数据
- `pkg/frontend/` — 前端 PITR SQL 命令处理
- `pkg/vm/engine/tae/` — 存储层 checkpoint 管理
- `pkg/logservice/` — WAL 日志持久化

### SQL 语法
```sql
CREATE PITR pitr_name FOR ACCOUNT account_name RANGE value unit;
ALTER PITR pitr_name ...;
DROP PITR pitr_name;
RESTORE ACCOUNT account_name FROM PITR pitr_name TIMESTAMP '2024-01-01 00:00:00';
```

## Snapshot

### 原理
- 创建数据库/表的一致性快照
- 基于 MVCC 时间戳实现
- 快照元数据存储在系统表中

### SQL 语法
```sql
CREATE SNAPSHOT snapshot_name FOR ACCOUNT account_name;
RESTORE ACCOUNT account_name FROM SNAPSHOT snapshot_name;
DROP SNAPSHOT snapshot_name;
```

## Backup 包(`pkg/backup/`)

- `BackupType` — 备份类型
- `BackupTs` — 备份时间戳
- `BackupObject` (objectio) — 备份对象元数据

## 与测试的关联

| 变更范围 | 影响的测试 |
|---------|----------|
| PITR 核心逻辑 | PITR 测试; BVT: pitr |
| Snapshot 核心逻辑 | Snapshot 测试; BVT: snapshot |
| Logtail/Checkpoint | PITR + Snapshot 都受影响 |
| 备份元数据 | BVT: pitr, snapshot |
| 多租户备份 | BVT: tenant; PITR/Snapshot 多租户场景 |
47 changes: 47 additions & 0 deletions docs/ai-skills/cdc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# CDC(Change Data Capture)

## 概述

MO 的 CDC 模块基于 Logtail 捕获数据变更,将变更同步到下游系统。

## 核心组件(`pkg/cdc/`)

| 组件 | 职责 |
|------|------|
| `CDCStateManager` | 每个 publication 的状态跟踪 |
| `CDCStatementBuilder` | 为下游生成复制 SQL |
| `WatermarkUpdater` | 同步进度(水位线)管理 |
| Table Scanner | 扫描表变更 |

## 工作流程

```
表数据变更 → Logtail 捕获 → CDC 模块消费 → 生成下游 SQL → 同步到下游
WatermarkUpdater 更新水位线
```

## 后台任务类型

- `JT_CDC_GetOrAddCommittedWM` — 获取/添加已提交水位线
- `JT_CDC_CommittingWM` — 提交中的水位线
- `JT_CDC_UpdateWMErrMsg` — 更新水位线错误信息
- `JT_CDC_RemoveCachedWM` — 清除缓存水位线

## SQL 语法

```sql
CREATE CDC cdc_name ...;
ALTER CDC cdc_name ...;
DROP CDC cdc_name;
SHOW CDC;
```

## 与测试的关联

| 变更范围 | 影响的测试 |
|---------|----------|
| CDC 状态管理 | BVT: cdc |
| 水位线逻辑 | BVT: cdc; 稳定性: 长时间 CDC 同步 |
| Logtail 消费 | CDC + PITR + Snapshot |
| 杀节点后 CDC 恢复 | Chaos: 杀 CN/TN 后 CDC 恢复 |
43 changes: 43 additions & 0 deletions docs/ai-skills/fileservice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# FileService

## 概述

FileService 是 MO 的对象存储抽象层,屏蔽底层存储差异(本地磁盘 / S3 / MinIO)。

## 核心接口(`pkg/fileservice/`)

| 接口 | 职责 |
|------|------|
| `FileService` | 基础读写操作 |
| `MutableFileService` | 追加/删除操作 |
| `ReaderWriterFileService` | 流式 I/O |
| `CacheDataAllocator` | 内存管理 |
| `FileCache` | 本地缓存层 |
| `ETLFileService` | ETL 专用操作 |

## 存储后端

- **本地文件系统** — 开发/测试环境
- **S3 / MinIO** — 生产环境对象存储
- **HTTP** — 远程文件访问

## 配置

```toml
# etc/launch/tn.toml
[fileservice.s3]
bucket = "my-bucket"
key-prefix = "mo-data"
endpoint = "http://minio:9000"
```

本地存储模式:数据存储在 `mo-data/` 目录下。

## 与测试的关联

| 变更范围 | 影响的测试 |
|---------|----------|
| 读写逻辑 | BVT: load_data, stage |
| 缓存层 | 稳定性: tpch(大量读取)|
| S3 对接 | 大数据量测试(云端 load)|
| ETL 功能 | BVT: load_data |
Loading
Loading