visionA/docs/autoflow/04-architecture/adr/adr-004-storage-interface.md
jim800121chen fb7da5d180 chore(autoflow): migrate .autoflow/ 共享層文件至 docs/autoflow/
依 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)。
2026-05-04 16:55:55 +08:00

5.8 KiB
Raw Blame History

ADR-004模型儲存採用 S3-Compatible 介面

狀態

Accepted — 2026-04-21

背景 (Context)

visionA Cloud 需要儲存:

  1. 使用者上傳的模型檔案.nef.onnx 等,幾 MB 幾百 MB
  2. 模型 metadataJSON幾 KB
  3. (未來)轉檔產物kneron_model_converter 產生的 .nef
  4. 未來推論紀錄、snapshots、截圖

POCedge-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 下載一個物件,回傳 readercaller 要負責 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
}

雛形提供兩個實作

  1. LocalFSStore:把物件存在本地 data/storage/

    • Key 對應檔案路徑(models/user-123/yolov5.nefdata/storage/models/user-123/yolov5.nef
    • Metadata 存在同名 .meta.json
    • PresignedGetURL / PresignedPutURLhttp://localhost:PORT/storage/... 這種由 api-server 代理的 URL雛形的「假簽名」
    • 啟動最快,開發者零成本
  2. S3Store(可選,雛形後期加):用 aws-sdk-go-v2

    • 連真實 AWS S3、MinIO、Cloudflare R2、Backblaze B2 等
    • PresignedGetURL / PresignedPutURL 是真的 S3 presigned URL
    • 需要 S3_ENDPOINTS3_BUCKETS3_REGIONS3_ACCESS_KEYS3_SECRET_KEY env

選擇由 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 簡單 綁定 AWSMinIO / R2 要 workaround 綁定單一雲
用 MinIO SDK 輕量 MinIO SDK 寫的 code 去接 AWS S3 要微調;綁定特定 SDK 換雲成本
自己做儲存服務 完全掌控 重新發明輪子;費時;達不到 S3 等級的持久性 不值得
資料庫存 blob 交易一致性 DB 不適合儲存大檔;昂貴 技術錯配
只用 LocalFS 不抽象 簡單 未來替換 = 重寫所有 model handler 違反抽象原則

後果 (Consequences)

正面影響

  • 雛形零 setupdocker-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 保護
  • 大檔上傳 OOMPut 使用 io.Reader 已是 streamingLocalFS 實作用 io.Copy 避免 buffer 整個 body需要 code review 把關

合規性

  • Architect 確認
  • 雛形測試:確保 LocalFSStore 通過 interface conformance tests
  • Phase 1確保 S3Store 通過相同 tests
  • 加入 .gitignoredata/storage/ 不進 Git

相關文件

  • Design Doc §3.3(外部依賴)
  • TDD §8儲存層介面
  • 相關 ADRADR-005雛形不接 DB / Auth