Skip to content
Merged
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
4 changes: 3 additions & 1 deletion config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ bizDB:
maxIdleConn: 10

scheduler:
# ModelScope 私有仓库凭据;不配置时匿名访问,不复用 HF token。
modelscopeToken: ""
port: 19091
persistRepo:
enabled: true
Expand Down Expand Up @@ -75,4 +77,4 @@ aidc:
xn-03: xn-03
xn-zl: xn-zl
hd-05: hd-05
xb-01: xb-01
xb-01: xb-01
62 changes: 62 additions & 0 deletions docs/modelscope-parity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# ModelScope 远端仓库管理补齐

2026-09-23。范围为 Scheduler 的远端下载管理;不修改上传库存、仓库范本或节点文件,不迁移数据库。

## 行为

- 未指定版本的仓库发现按来源取默认分支:HF 为 `main`,ModelScope 为 `master`。
- 缓存任务完成后使用任务保存的 commit 获取元数据;返回 commit 不一致或清单缺失时拒绝登记。
- 完整性核验逐一匹配节点、来源、仓库类型、仓库、路径、内容摘要和大小,不再以文件总数代替清单。缺少内容身份的 HF 元数据通过固定 commit 的递归 tree 补齐;Speed 负责上游分页。不会通过文件 GET 下载内容。
- 下载完成与仓库登记分别表达:登记失败保留任务完成状态,`errorMsg` 标明 `repository registration pending`,RPC 返回错误,进入 Speed 既有持久化通知重试。成功重试清除错误。
- 同一节点仓库更新原记录并替换标签,保留数据库 ID、挂载状态与节点关系;重复回报不重复插入。旧任务迟到回报不会覆盖编号更新的已完成任务。
- 不再因为 Scheduler 存在同仓库历史任务而拦截创建;由 Speed 根据当前 commit 判断运行中、已缓存或创建新任务,Scheduler 透传结果。

## 凭据

```yaml
scheduler:
modelscopeToken: "" # 私有仓库需要配置所属 ModelScope 账号的凭据
```

ModelScope 的创建、恢复、仓库登记及默认挂载使用该配置。空值表示匿名请求,不回退到 HF token。HF 保持原有 `hf_token` 数据库选择方式。挂载请求显式提供的 `token` 仍优先使用。停止和实时进度查询不发送上游凭据。

配置修改需在后续部署或服务重启时生效;本次没有修改运行环境或配置真实凭据。

## 查询兼容

仓库列表 `/api/v1/repositories` 和任务列表 `/api/v1/cacheJob/list` 增加可选参数 `namespace=modelscope` 或 `namespace=huggingface`,也支持具体上传 namespace。过滤发生在数据库分页和计数之前;省略参数保持混合列表行为。

普通仓库列表/详情与任务列表新增统一身份字段:

```json
{
"namespace": "modelscope",
"fullRepo": "owner/demo",
"repositoryId": "modelscope/owner/demo"
}
```

`repositoryId` 是带 namespace 的业务标识,仍需结合 `repoType/datatype` 区分类型;数据库数字 `id` 不变。为兼容已有调用方,不改旧 `org/repo/orgRepo` 含义。任务列表分页改为标准 OFFSET/LIMIT,并使用 ID 作为相同创建时间的排序补充。

`POST /api/persistRepo` 可在明确指定仓库时提供 `commit`,用于人工重试固定版本登记。没有指定 commit 的待登记仓库优先使用最新已完成任务的 commit,没有完成任务时才按来源默认分支发现。旧 `offVerify` 显式选项继续保留;任务完成通知不再开启它。

## 验证

新增 DAO 与 service 测试使用临时 SQLite 数据库及本机临时 HTTP 端口,覆盖:

- ModelScope 默认 `master`,逐路径校验而非数量比较。
- 元数据服务失败后补报,任务完成事实及登记错误回读。
- commit 不匹配、旧内容冒充新内容时拒绝登记。
- 重试幂等、新版本原位更新、旧通知不回退新版本。
- HF/ModelScope 凭据隔离及 ModelScope 匿名访问。
- 两来源同名仓库的筛选、分页、计数及统一身份字段。
- 已存在任务仍转交 Speed 决定复用,并透传 disposition。
- HF 固定版本 tree 的 LFS 内容身份解析。

使用只读源码挂载、禁用外网的 Linux Go 1.24 容器执行 `go test ./...`。本机默认 Go 1.26 与 sonic 不兼容;Windows Go 1.23.1 还受既有 Linux `syscall.Stat_t` 代码限制,因此未改项目依赖或平台代码来绕过。

本次验证为真实 DAO/HTTP 调用及隔离数据库测试,上游和 Speed 响应使用受控 fixture;未连接真实 ModelScope 私有仓库,未进行真实多节点下载或浏览器验收。

## 尚未扩展的展示信息

当前 Speed 的 ModelScope 元数据适配只提供仓库版本、文件清单和容量,未提供点赞、下载数、标签、任务分类及组织头像。本次不把缺失信息伪造成上游统计,也不根据 HF 标签体系猜测 ModelScope 分类;这些展示字段仍需确认实际上游接口及映射后单独补充。
13 changes: 13 additions & 0 deletions docs/node-discovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# 节点自动发现接口

2026-09-22:Register 请求新增 managementUrl(字段 5)与 downloadUrl(字段 6)。
可选 HTTP(S) URL 随注册保存至附加表 node_endpoints,以 Scheduler 数字节点 ID 为主键。
服务启动自动创建该表,不修改模型文件、远端库存或官方版本。旧客户端不发送字段仍可注册。

`GET /api/v1/nodes/health?endpoints=true&after=0&limit=200` 在原健康视图上附加
managementUrl/downloadUrl,继续使用 nextAfter 游标。端点尚未上报的旧节点返回空字符串。
communication 表示注册通信有效性;onlineMode 表示节点运行模式,不能当成在线状态。
端点存储或健康查询失败返回 503,不返回假成功的空列表。

ModelFleet 使用唯一 Scheduler 的 HTTP 地址发现节点,用 gRPC 地址配置 Speed。
只读发现无需管理令牌,不返回任何节点凭据。
33 changes: 33 additions & 0 deletions docs/node-repositories.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 节点仓库观察

打开 `/node-health`,在节点卡片点击“查看仓库”。仓库区域展示选中实例的仓库总数、已记录容量以及仓库来源、namespace、名称、类型、commit、挂载状态和源仓库修改时间;挂载错误文本随记录展示。

当前页每 10 秒从数据库重新读取,刷新保留选中节点和页码。查询失败会清除旧表格并显示错误,可手动重试。每页 20 条,支持上一页/下一页。大表格在自身区域内滚动,小屏可横向滚动查看其余列。

## 数据口径

只读原有 `dingospeed` 和 `repository` 表,以节点 ID 查询 instance_id,再严格限定 `repository.instance_id`。不调用 Speed、不触发持久化或回源下载,不修改任何表结构或旧记录。

这里展示数据库中的仓库投影,不是磁盘扫描结果;未发布或未入库的缓存不在列表中,上传仓库沿用 main 投影。容量为 `used_storage` 的逻辑大小合计,不代表物理磁盘用量或去重后大小。节点断连时,仍能读取数据库记录,但这不证明当前磁盘内容完整。若在线/离线节点共用同一个 instance_id,现有表只能提供该实例的共同记录。

旧 HF `org=Qwen,repo=demo` 显示为 `namespace=huggingface,repo=Qwen/demo`;上传 `org=dingo-local/alice,repo=team/demo` 显示为 `namespace=alice,repo=team/demo`。无法按新身份规则解码的历史记录仍展示原始值,并标为“未识别身份”。

## 只读接口

`GET /api/v1/nodes/:id/repositories?after=0&limit=20`

- id:已注册节点的正整数 ID。
- after:非负 int64 仓库 ID 游标,默认 0。
- limit:1..100,默认 20。按仓库 ID 升序,用额外一行判断下一页。
- 返回:`nodeId`、`instanceId`、`items`、`total`、`usedStorage`、`nextAfter`、`observedAt`。
- 每条 item:`id`、`namespace`、`repo`、`datatype`、`identityValid`、`commit`、`usedStorage`、`mountStatus`、`errorMessage`、`lastModified`、`updatedAt`。
- 仓库 `id` 和 `nextAfter` 是十进制字符串,避免浏览器损失 int64 精度;`nextAfter="0"` 表示无下一页。
- 参数错误 400,节点不存在 404,数据库不可用 503。成功结果禁用 HTTP 缓存,数据库查询超时为 5 秒。

总数与列表分别读取,持续入库或删除期间可能出现短暂差异,下一次轮询会更新。查询严格使用绑定参数,数据库文本通过 textContent 渲染。

## 本地验证

全量 Go 测试与编译通过。隔离 MySQL + 正式 Scheduler + Chromium 验证脚本为工作空间 `integration/verify_node_repositories.py`,覆盖身份解码、大整数游标、跨节点隔离、分页、参数校验、数据库变更自动同步、空数据、浏览器错误恢复、文本转义与移动端布局。该脚本仅对专用本地夹具插入和删除测试行,不可用于生产库。

本次没有部署到 10.220.70.213,也没有向该环境写入数据。
46 changes: 46 additions & 0 deletions docs/upload-inventory-schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
-- First-phase uploaded inventory only. These additive tables intentionally do
-- not alter repository/model_file_record/model_file_process (remote business).
CREATE TABLE IF NOT EXISTS upload_inventory_state (
instance_id VARCHAR(191) PRIMARY KEY,
epoch VARCHAR(64) NOT NULL,
epoch_started_at DATETIME(3) NOT NULL,
last_sequence BIGINT UNSIGNED NOT NULL,
inventory_complete BOOLEAN NOT NULL,
last_attempt_at DATETIME(3) NOT NULL,
last_confirmed_at DATETIME(3) NULL,
error_message TEXT NULL
);
CREATE TABLE IF NOT EXISTS upload_inventory_file (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
identity_hash CHAR(64) NOT NULL UNIQUE,
namespace VARCHAR(255) NOT NULL,
repo_type VARCHAR(32) NOT NULL,
repo VARCHAR(1024) NOT NULL,
path VARCHAR(1000) NOT NULL,
sha256 CHAR(64) NOT NULL,
size BIGINT NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
INDEX idx_upload_repo(namespace, repo_type), INDEX idx_upload_sha(sha256)
);
CREATE TABLE IF NOT EXISTS upload_inventory_holding (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
file_id BIGINT NOT NULL,
instance_id VARCHAR(191) NOT NULL,
sequence BIGINT UNSIGNED NOT NULL,
confirmed_at DATETIME(3) NOT NULL,
UNIQUE KEY uk_upload_holding(file_id, instance_id),
INDEX idx_upload_holding_instance(instance_id), INDEX idx_upload_holding_file(file_id)
);

-- v2 fencing and repository watermarks; independent from holdings lifetime.
CREATE TABLE IF NOT EXISTS upload_report_nodes (
instance_id VARCHAR(191) PRIMARY KEY,
epoch VARCHAR(64), pending_epoch VARCHAR(64), baseline_digest VARCHAR(64),
status VARCHAR(32), error TEXT, updated_at DATETIME(3)
);
CREATE TABLE IF NOT EXISTS upload_report_repos (
repo_hash VARCHAR(64) PRIMARY KEY,
instance_id VARCHAR(191), epoch VARCHAR(64), sequence BIGINT UNSIGNED,
digest VARCHAR(64), INDEX idx_upload_report_node(instance_id)
);
54 changes: 54 additions & 0 deletions docs/upload-inventory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# 上传库存:持久队列主动推送(协议 v2)

只记录 Speed 已生效、可提供的上传文件。远端下载进度、清理上报、仓库范本与节点间同步保持独立。

## 正常运行

Speed 在 `<repos>/.upload-inventory/outbox.json` 保存变化序号、按 namespace/repoType/完整仓库名合并的待处理记录、操作恢复日志及固定待确认报告。该目录不在任何仓库目录内,整仓删除不会删除待报告墓碑。

本地有效元数据变更前先落盘操作意图,操作完成后清除意图;未完成意图可按确定目标重放。发布、删除和恢复经过同一套记录机制。暂存上传不触发有效库存变更。网络不参与本地提交,日志收尾失败也不会将已生效操作改判为网络失败。

工作线程汇总变化仓库所有有效本地 revision 的文件并主动 POST;同路径不同 SHA256 保留,同内容不同路径保留,多个 revision 的相同引用只计一次。扫描绕过下载服务的元数据缓存,读取不完整或内容不可提供时报错,保留队列与最后确认库存。

报告生成后序号、文件列表、删除标记固定并持久化。请求失败、超时或响应丢失重发同一报告;收到匹配 epoch/sequence/digest 的确认后,仅清除该报告覆盖的记录。发送期间的更新继续保留。

连续变化首次延迟 300ms 合并,后续变化不推迟首个截止时间。失败采用有随机抖动的指数退避,最大约 257 秒。注册/重连唤醒待处理队列;空队列不枚举仓库、不发送库存。没有 30 秒周期盘点,心跳不触发盘点。

## 事务与序号

Scheduler 复用 `upload_inventory_file`、`upload_inventory_holding` 和节点查询状态表,新增:

- `upload_report_nodes`:每节点当前代次、待对账代次、基线摘要、任务状态。
- `upload_report_repos`:每节点每仓库接收水位及固定报告摘要。删除持有关系后保留该水位。

一次事务中锁定对应节点状态、比较代次和仓库序号、替换对应节点与仓库的持有关系并保存水位,提交后才确认。相同序号不同内容拒绝;重复报告确认;旧序号不覆盖新状态;旧代次拒绝。节点间没有共享上报互斥锁。无持有关系的文件身份可以保留,但库存目录查询不会显示它,避免跨节点报告与身份清理产生竞争。

## 初次基线与人为对账

初次接入 v2 时由 Scheduler 分配基线代次。仅初次接入与用户明确对账才全量盘点;普通重启继续持久队列。由 v1 升级到 v2 会建立一次新基线,此后旧全节点回拉报告不再被接受。

人为对账:Spinfield 节点配置 → **重新对账上传库存** → Scheduler 持久化任务并通知目标 Speed → Speed 固定完整清单并校验内容 SHA256 → POST → Scheduler 原子替换该节点全量上传持有关系 → Speed 收到确认后归零。

成功时切换新 epoch,清空被基线覆盖的旧队列,序号重置为 0。盘点固定之后发生的新变化保留,并从新代次的 1 开始重新编号;它们已产生时,观察到的序号可以立即大于 0。其他节点的队列、序号和代次不变。重复基线不会擦除其后已应用的仓库更新。

任务失败保持原库存和原代次。已固定报告的发送失败只重发原报告,不重复扫描;本地基线构建最多自动尝试三次,达到上限后持久保存停止状态并上报 `needs_attention` 和错误,不再自动全量扫盘。普通重启、重连、重复投递和新的仓库变更不会重置次数。修复原因后再次显式对账,由 Scheduler 分配新代次重新执行;进行中的非停止任务仍合并重复请求。停止期间普通仓库变化继续持久入队,待完整基线确认后接续,不能用部分基线覆盖库存。任务投递有独立重试;Scheduler 重启恢复未投递任务,Speed 重连也取得待执行任务。页面 GET 和刷新只读取任务状态。

Speed 丢失序号但 Scheduler 已有代次时不会擅自从 1 上报,必须显式对账。队列 JSON 损坏时显式请求会先保存 `.damaged-*` 副本再建立重建状态;权限或其他普通 IO 失败不会被当成可清空的损坏文件。直接改磁盘不产生事件,可由显式对账发现。

## 接口

- `POST /api/v1/upload-inventory/reports`:v2 报告,单请求上限 128 MiB。
- `POST /api/v1/upload-inventory/nodes/:instanceId/session`:首次基线和重连控制状态,不自行扫描磁盘。
- `POST /api/v1/upload-inventory/nodes/:instanceId/reconcile`:显式创建/重试投递对账任务。
- `GET /api/v1/upload-inventory/nodes/:instanceId/reconcile`:只读任务状态。
- `POST /api/v1/upload-inventory/nodes/:instanceId/reconcile-progress`:扫描/重试/需要处理状态,不修改持有关系。
- Speed 管理口 `POST /api/upload-inventory/reconcile`:持久化对账请求并唤醒处理。
- Spinfield `GET/POST /api/v1/node-settings/:nodeId/inventory-reconcile`:经节点管理权限校验后访问 Scheduler。

原上传库存 repositories/files/nodes 文件查询接口保持不变。节点离线仅影响可用性,保留最后持有关系。原远端下载接口不变。

## 配置与限制

Speed 新增 `scheduler.httpUrl`(注册配置为 `httpUrl`),指向 Scheduler HTTP 根地址,不从 gRPC 端口推断。Spinfield 接管注册的节点自动使用全局 Scheduler HTTP 地址;外部管理的节点需在 Speed 配置中提供。先升级 Scheduler,再升级 Speed;失败期间本地操作继续。

此版本使用原子文件状态存储,不引入外部消息系统。仓库快照及基线在本地有效元数据变更屏障内生成,网络发送不持有屏障。大仓库/大基线需要相应内存与扫描时间,超过 128 MiB 请求限制会明确失败并保留待处理状态,尚未实现分块清单传输。部署沿用现有内部管理网络和权限边界。
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/andybalholm/brotli v1.1.1
github.com/avast/retry-go v3.0.0+incompatible
github.com/bytedance/sonic v1.13.2
github.com/glebarez/sqlite v1.11.0
github.com/go-playground/validator/v10 v10.26.0
github.com/go-sql-driver/mysql v1.9.3
github.com/gocolly/colly v1.2.0
Expand Down Expand Up @@ -43,7 +44,9 @@ require (
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/gobwas/glob v0.2.3 // indirect
Expand All @@ -62,6 +65,7 @@ require (
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/temoto/robotstxt v1.1.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
Expand All @@ -76,4 +80,8 @@ require (
golang.org/x/time v0.8.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
)
Loading