# Архитектура кэша {#cache-architecture}

> [!NOTE]
> На этой странице представлен технический обзор архитектуры сервиса кэширования Tuist. Она предназначена в первую очередь для **пользователей, использующих самостоятельное размещение**, и **участников проекта**, которым необходимо понять внутреннее устройство сервиса. Обычным пользователям, которые хотят только использовать кэш, читать её не нужно.


Сервис кэширования Tuist — это отдельный сервис, который предоставляет хранилище с адресацией по содержимому (CAS) для артефактов сборки и хранилище ключ-значение для метаданных кэша.

## Обзор {#overview}

Сервис использует двухуровневую архитектуру хранения плюс локальные метаданные SQLite:

- **Локальный диск**: основное хранилище для быстрых попаданий в кэш
- **S3**: надёжное хранилище, которое сохраняет артефакты и позволяет восстанавливать их после удаления с диска
- **SQLite**: локальные метаданные для отслеживания доступа к артефактам, очистки orphan-файлов, фоновых задач и данных кэша ключ-значение

```mermaid
flowchart LR
    CLI[Tuist CLI] --> NGINX[Nginx]
    NGINX --> APP[Cache service]
    NGINX -->|X-Accel-Redirect| DISK[(Local Disk)]
    APP --> S3[(S3)]
    APP -->|auth| SERVER[Tuist Server]
```

## Компоненты {#components}

### Nginx {#nginx}

Nginx выступает точкой входа и обеспечивает эффективную передачу файлов с помощью `X-Accel-Redirect`:

- **Загрузки**: сервис кэша проверяет аутентификацию, затем возвращает заголовок `X-Accel-Redirect`. Nginx отдаёт файл напрямую с диска или проксирует из S3.
- **Выгрузки**: Nginx проксирует запросы к сервису кэша, который потоково записывает данные на диск.

### Хранилище с адресацией по содержимому {#cas}

Артефакты хранятся на локальном диске в шардированной структуре каталогов:

- **Путь**: `{account}/{project}/cas/{shard1}/{shard2}/{artifact_id}`
- **Шардирование**: первые четыре символа идентификатора артефакта образуют двухуровневый шард (например, `ABCD1234` → `AB/CD/ABCD1234`)

### Метаданные SQLite {#sqlite}

Сервис кэша использует две базы данных SQLite:

- **Основная БД метаданных**: хранит `cache_artifacts`, курсоры сканирования orphan-файлов, задачи Oban и другие служебные метаданные.
- **БД ключ-значение**: хранит `key_value_entries` и `key_value_entry_hashes` в выделенном файле SQLite.

Хранилище ключ-значение вынесено в отдельную базу данных, чтобы можно было использовать инкрементальное автоматическое освобождение места в SQLite без влияния на метаданные артефактов и состояние очистки orphan-файлов.

### Интеграция с S3 {#s3}

S3 обеспечивает надёжное хранение:

- **Фоновые выгрузки**: после записи на диск артефакты ставятся в очередь на загрузку в S3 через фоновый воркер, который запускается каждую минуту
- **Гидратация по запросу**: если локальный артефакт отсутствует, запрос сразу обслуживается через предподписанный URL S3, пока артефакт ставится в очередь на фоновую загрузку на локальный диск

### Удаление с диска {#eviction}

Сервис управляет дисковым пространством с помощью нескольких фоновых процессов:

- **Удаление артефактов CAS с диска** использует семантику LRU на основе `cache_artifacts`
- Когда использование диска превышает 85%, самые старые артефакты удаляются, пока использование не снизится до 70%
- Артефакты остаются в S3 после удаления с локального диска
- **Удаление записей KV** удаляет старые записи ключ-значение по сроку хранения и может также уменьшать размер выделенной БД KV, когда она превышает заданный лимит размера

### Очистка orphan-файлов {#orphan-cleanup}

Сервис также запускает воркер очистки orphan-файлов для артефактов на диске:

- Он сканирует дерево хранилища в поисках файлов, которые существуют на диске, но не имеют соответствующей строки в `cache_artifacts`.
- Это может произойти, если файл записан на диск, но запись метаданных потеряна до завершения сброса буфера SQLite.
- Файлы, созданные позже безопасного интервала, игнорируются, чтобы избежать конфликтов с выполняющимися загрузками.
- Если orphan-файл удалён и позже запрошен снова, следующий промах кэша приведёт к его повторной загрузке, поэтому система самовосстанавливается.

### Аутентификация {#authentication}

Кэш делегирует аутентификацию серверу Tuist, вызывая конечную точку `/api/projects` и кэшируя результаты (10 минут при успехе, 3 секунды при ошибке).

## Потоки запросов {#request-flows}

### Загрузка {#download-flow}

```mermaid
sequenceDiagram
    participant CLI as Tuist CLI
    participant N as Nginx
    participant A as Cache service
    participant D as Disk
    participant S as S3

    CLI->>N: GET /api/cache/cas/:id
    N->>A: Proxy for auth
    A-->>N: X-Accel-Redirect
    alt On disk
        N->>D: Serve file
    else Not on disk
        N->>S: Proxy from S3
    end
    N-->>CLI: File bytes
```

### Выгрузка {#upload-flow}

```mermaid
sequenceDiagram
    participant CLI as Tuist CLI
    participant N as Nginx
    participant A as Cache service
    participant D as Disk
    participant S as S3

    CLI->>N: POST /api/cache/cas/:id
    N->>A: Proxy upload
    A->>D: Stream to disk
    A-->>CLI: 201 Created
    A->>S: Background upload
```

## Конечные точки API {#api-endpoints}

| Конечная точка | Метод | Описание |
|----------|--------|-------------|
| `/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 | Завершить многокомпонентную загрузку |
