跳转到内容

架构

Tuist 缓存服务是一个独立服务,为构建产物提供内容寻址存储(CAS)和缓存元数据的键值存储。

该服务使用双层存储架构加上本地 SQLite 元数据:

  • 本地磁盘:低延迟缓存命中的主要存储
  • S3:持久化产物的耐用存储,允许在逐出后恢复
  • SQLite:用于产物访问追踪、孤立文件清理、后台作业和键值缓存数据的本地元数据
flowchart LR
CLI[Tuist CLI] --> NGINX[Nginx]
NGINX --> APP[缓存服务]
NGINX -->|X-Accel-Redirect| DISK[(本地磁盘)]
APP --> S3[(S3)]
APP -->|认证| SERVER[Tuist 服务器]

Nginx 作为入口点,使用 X-Accel-Redirect 处理高效文件传输:

  • 下载:缓存服务验证认证后,返回 X-Accel-Redirect 头。Nginx 直接从磁盘服务文件或从 S3 代理。
  • 上传:Nginx 将请求代理到缓存服务,数据流式写入磁盘。

产物存储在分片的目录结构的本地磁盘上:

  • 路径{account}/{project}/cas/{shard1}/{shard2}/{artifact_id}
  • 分片:使用产物 ID 的前四个字符创建两级分片(例如 ABCD1234AB/CD/ABCD1234

缓存服务使用两个 SQLite 数据库:

  • 主元数据数据库:存储 cache_artifacts、孤立扫描游标、Oban 作业和其他服务元数据。
  • 键值数据库:在专用 SQLite 文件中存储 key_value_entrieskey_value_entry_hashes

键值存储独立为数据库,以便使用 SQLite 增量自动 vacuum 而不影响产物元数据和孤立文件清理状态。

S3 提供耐用存储:

  • 后台上传:写入磁盘后,产物通过每分钟运行一次的后台工作器排队上传到 S3
  • 按需激活:当本地产物缺失时,通过预签名 S3 URL 立即服务请求,同时将产物排队后台下载到本地磁盘

该服务使用多个后台进程管理磁盘空间:

  • CAS 磁盘逐出使用 LRU 语义,以 cache_artifacts 为后端
  • 当磁盘使用率超过 85% 时,删除最旧的产物直到使用率降至 70%
  • 产物在本地逐出后仍保留在 S3 中
  • KV 逐出通过保留策略删除旧的键值条目,当专用 KV 数据库增长超过配置的 size budget 时也可以缩小它

该服务还运行磁盘产物的孤立文件清理工作器:

  • 它扫描存储树,查找存在于磁盘但没有相应 cache_artifacts 行的文件。
  • 这可能发生在文件写入磁盘但元数据写入在 SQLite 缓冲区刷新完成前丢失的情况下。
  • 忽略安全窗口内的新文件以避免与正在进行的上传竞争。
  • 如果孤立的文件被删除,之后再次被请求,下次缓存未命中会导致它再次上传,因此系统会自我修复。

缓存服务通过调用 /api/projects 端点将认证委托给 Tuist 服务器,并缓存结果(成功 10 分钟,失败 3 秒)。

sequenceDiagram
participant CLI as Tuist CLI
participant N as Nginx
participant A as 缓存服务
participant D as 磁盘
participant S as S3
CLI->>N: GET /api/cache/cas/:id
N->>A: 代理认证
A-->>N: X-Accel-Redirect
alt 在磁盘上
N->>D: 服务文件
else 不在磁盘上
N->>S: 从 S3 代理
end
N-->>CLI: 文件字节
sequenceDiagram
participant CLI as Tuist CLI
participant N as Nginx
participant A as 缓存服务
participant D as 磁盘
participant S as S3
CLI->>N: POST /api/cache/cas/:id
N->>A: 代理上传
A->>D: 流式写入磁盘
A-->>CLI: 201 Created
A->>S: 后台上传
端点方法描述
/upGET健康检查
/metricsGETPrometheus 指标
/api/cache/cas/:idGET下载 CAS 产物
/api/cache/cas/:idPOST上传 CAS 产物
/api/cache/keyvalue/:cas_idGET获取键值条目
/api/cache/keyvaluePUT存储键值条目
/api/cache/module/:idHEAD检查模块产物是否存在
/api/cache/module/:idGET下载模块产物
/api/cache/module/startPOST开始分片上传
/api/cache/module/partPOST上传分片
/api/cache/module/completePOST完成分片上传