架构
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 将请求代理到缓存服务,数据流式写入磁盘。
内容寻址存储
Section titled “内容寻址存储”产物存储在分片的目录结构的本地磁盘上:
- 路径:
{account}/{project}/cas/{shard1}/{shard2}/{artifact_id} - 分片:使用产物 ID 的前四个字符创建两级分片(例如
ABCD1234→AB/CD/ABCD1234)
SQLite 元数据
Section titled “SQLite 元数据”缓存服务使用两个 SQLite 数据库:
- 主元数据数据库:存储
cache_artifacts、孤立扫描游标、Oban 作业和其他服务元数据。 - 键值数据库:在专用 SQLite 文件中存储
key_value_entries和key_value_entry_hashes。
键值存储独立为数据库,以便使用 SQLite 增量自动 vacuum 而不影响产物元数据和孤立文件清理状态。
S3 提供耐用存储:
- 后台上传:写入磁盘后,产物通过每分钟运行一次的后台工作器排队上传到 S3
- 按需激活:当本地产物缺失时,通过预签名 S3 URL 立即服务请求,同时将产物排队后台下载到本地磁盘
该服务使用多个后台进程管理磁盘空间:
- CAS 磁盘逐出使用 LRU 语义,以
cache_artifacts为后端 - 当磁盘使用率超过 85% 时,删除最旧的产物直到使用率降至 70%
- 产物在本地逐出后仍保留在 S3 中
- KV 逐出通过保留策略删除旧的键值条目,当专用 KV 数据库增长超过配置的 size budget 时也可以缩小它
孤立文件清理
Section titled “孤立文件清理”该服务还运行磁盘产物的孤立文件清理工作器:
- 它扫描存储树,查找存在于磁盘但没有相应
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: 后台上传API 端点
Section titled “API 端点”| 端点 | 方法 | 描述 |
|---|---|---|
/up | GET | 健康检查 |
/metrics | GET | Prometheus 指标 |
/api/cache/cas/:id | GET | 下载 CAS 产物 |
/api/cache/cas/:id | POST | 上传 CAS 产物 |
/api/cache/keyvalue/:cas_id | GET | 获取键值条目 |
/api/cache/keyvalue | PUT | 存储键值条目 |
/api/cache/module/:id | HEAD | 检查模块产物是否存在 |
/api/cache/module/:id | GET | 下载模块产物 |
/api/cache/module/start | POST | 开始分片上传 |
/api/cache/module/part | POST | 上传分片 |
/api/cache/module/complete | POST | 完成分片上传 |