依 autoflow-agent workspace v2 設計把 PRD / 設計 / 架構 / 交付類 共享文件從個人層 .autoflow/(ignored)搬到 docs/autoflow/(進 git), 讓團隊可共享產品與架構文件,個人層只留 progress / review / testing 等 per-branch 筆記。 - 02-prd/ 21 個檔(PRD、features、market-analysis 等) - 03-design/ 18 個檔(design-spec、wireframes、flows 等) - 04-architecture/ 31 個檔(TDD、design-doc、ADR×14、API 規格等) - 07-delivery/ 3 個檔(project-summary、phase-0.6-handover、stage-deployment-setup) 合計 73 檔。原檔已從 .autoflow/ 移除(migration 工具執行 git mv, 但因 .autoflow/ 在 .gitignore 中、git 將此操作視為新增、無 rename history)。
5.8 KiB
5.8 KiB
ADR-004:模型儲存採用 S3-Compatible 介面
狀態
Accepted — 2026-04-21
背景 (Context)
visionA Cloud 需要儲存:
- 使用者上傳的模型檔案(
.nef、.onnx等,幾 MB ~ 幾百 MB) - 模型 metadata(JSON,幾 KB)
- (未來)轉檔產物:kneron_model_converter 產生的
.nef - (未來)推論紀錄、snapshots、截圖
POC(edge-ai-platform)用本地檔案系統 + in-memory registry。local-tool 也一樣。這在單機環境 OK,雲端不行:
| 問題 | 本地檔案系統 | S3-compatible |
|---|---|---|
| 多節點共用 | ❌ 各節點看不到彼此 | ✅ 共享 bucket |
| 持久性 | 單機磁碟壞 = 資料沒 | ✅ 11 個 9 的設計 |
| 容量彈性 | 單機磁碟有上限 | ✅ 幾乎無上限 |
| 成本效益 | 計算節點帶容量 = 貴 | 冷熱分層、便宜 |
| 直接給前端下載 | 要透過應用伺服器 | ✅ Presigned URL 直給 |
| Multipart upload | 自己實作 | ✅ 標準 |
同時,雛形階段不想立刻接真實 S3 / MinIO(需架設、需 credentials、增加 setup 成本)。
決策 (Decision)
在 internal/storage 定義 Store interface,以 S3 的語義為基礎:
package storage
import (
"context"
"io"
"time"
)
// Store 是模型 / 檔案儲存的抽象層。語義對齊 S3。
type Store interface {
// Put 上傳一個物件。size <0 表示未知大小(streaming)。
Put(ctx context.Context, key string, r io.Reader, size int64, meta map[string]string) error
// Get 下載一個物件,回傳 reader(caller 要負責 Close)。
Get(ctx context.Context, key string) (io.ReadCloser, *ObjectInfo, error)
// Stat 取得物件資訊但不下載 body。
Stat(ctx context.Context, key string) (*ObjectInfo, error)
// Delete 刪除一個物件。
Delete(ctx context.Context, key string) error
// List 列出 prefix 下的物件。
List(ctx context.Context, prefix string) ([]*ObjectInfo, error)
// PresignedGetURL 產生讓使用者直接下載的簽名 URL。
PresignedGetURL(ctx context.Context, key string, ttl time.Duration) (string, error)
// PresignedPutURL 產生讓使用者直接上傳的簽名 URL。
PresignedPutURL(ctx context.Context, key string, ttl time.Duration) (string, error)
}
type ObjectInfo struct {
Key string
Size int64
ContentType string
LastModified time.Time
ETag string
Metadata map[string]string
}
雛形提供兩個實作
-
LocalFSStore:把物件存在本地data/storage/下- Key 對應檔案路徑(
models/user-123/yolov5.nef→data/storage/models/user-123/yolov5.nef) - Metadata 存在同名
.meta.json PresignedGetURL/PresignedPutURL回http://localhost:PORT/storage/...這種由 api-server 代理的 URL(雛形的「假簽名」)- 啟動最快,開發者零成本
- Key 對應檔案路徑(
-
S3Store(可選,雛形後期加):用aws-sdk-go-v2- 連真實 AWS S3、MinIO、Cloudflare R2、Backblaze B2 等
PresignedGetURL/PresignedPutURL是真的 S3 presigned URL- 需要
S3_ENDPOINT、S3_BUCKET、S3_REGION、S3_ACCESS_KEY、S3_SECRET_KEYenv
選擇由 config 決定
storage:
backend: localfs # or "s3"
localfs:
root: ./data/storage
base_url: http://localhost:3001/storage # api-server 代理路徑
s3:
endpoint: "" # 空字串用 AWS 原生;填 MinIO / R2 等用自訂
bucket: visiona-models
region: us-east-1
access_key: ${S3_ACCESS_KEY}
secret_key: ${S3_SECRET_KEY}
考慮過的替代方案
| 方案 | 優點 | 缺點 | 排除原因 |
|---|---|---|---|
| 直接用 AWS SDK 呼叫 S3 | 簡單 | 綁定 AWS;MinIO / R2 要 workaround | 綁定單一雲 |
| 用 MinIO SDK | 輕量 | MinIO SDK 寫的 code 去接 AWS S3 要微調;綁定特定 SDK | 換雲成本 |
| 自己做儲存服務 | 完全掌控 | 重新發明輪子;費時;達不到 S3 等級的持久性 | 不值得 |
| 資料庫存 blob | 交易一致性 | DB 不適合儲存大檔;昂貴 | 技術錯配 |
| 只用 LocalFS 不抽象 | 簡單 | 未來替換 = 重寫所有 model handler | 違反抽象原則 |
後果 (Consequences)
正面影響
- 雛形零 setup:
docker-compose up直接跑,不需 S3 / MinIO - 切雲端零改動:換 config 的
backend: s3即可 - 支援多家雲:endpoint 可指向 AWS / Cloudflare R2 / Backblaze B2 / MinIO / 自建 Ceph 等
- 介面 S3-native:未來加 multipart upload、server-side encryption 等 S3 進階特性無需變介面
負面影響(接受的取捨)
PresignedGetURL在 LocalFS 是假的:需 api-server 提供/storage/...代理端點。這段代理程式碼要寫,但不複雜- LocalFS 不支援多節點:雛形階段只有單節點,可接受;Phase 1 遷到 S3 後自動解決
- Interface 比純 SDK 包裝更抽象:略多一層呼叫,效能無感(本地 disk IO 遠大於 interface method dispatch)
風險
- LocalFS 的 metadata 靠 sidecar
.meta.json:不是原子的;雛形忽略;Phase 1 改 S3 自然原子 - PresignedPutURL 在 LocalFS 實作要處理 CSRF / auth:雛形可暫時接受裸上傳;Phase 1 上 S3 後由 S3 presigned signature 保護
- 大檔上傳 OOM:
Put使用io.Reader已是 streaming,LocalFS 實作用io.Copy避免 buffer 整個 body;需要 code review 把關
合規性
- Architect 確認
- 雛形測試:確保
LocalFSStore通過 interface conformance tests - Phase 1:確保
S3Store通過相同 tests - 加入
.gitignore:data/storage/不進 Git
相關文件
- Design Doc §3.3(外部依賴)
- TDD §8(儲存層介面)
- 相關 ADR:ADR-005(雛形不接 DB / Auth)