74 lines
5.9 KiB
Markdown
74 lines
5.9 KiB
Markdown
# Design
|
|
|
|
## Context
|
|
|
|
См. `proposal.md` для мотивации. Технические факты, формирующие подход:
|
|
|
|
- Генерация вариантов — `photos/pipeline.go:buildVariants`: `thumb = Fit(200,200)`,
|
|
`preview = Fit(1200,1200)`, WebP q82. Оба кропа вписывают по длинной стороне.
|
|
- Метаданные имеют две реализации (Postgres и SQLite) с общей контрактной проверкой
|
|
`internal/repo/contract`; SQL дублируется (`photos/store.go`, `photos/sqlite_store.go`).
|
|
- Роли заданы жёстко: `photo_objects.role CHECK (role IN ('original','thumb','preview'))`
|
|
(`migrations/000008`, `migrations/sqlite/000001`).
|
|
- Варианты генерирует фоновый воркер из очереди `variant_jobs`; лишние объекты удаляются
|
|
сборщиком мусора по отсутствию ссылок (`photos/worker.go`).
|
|
- Фронтенд-сетка берёт только `thumb_url` (`PhotoGrid.svelte:78`); обложки — только
|
|
`cover_thumb_url` (`albums/store.go` джойнит `role='thumb'`).
|
|
|
|
## Goals / Non-Goals
|
|
|
|
**Goals:**
|
|
|
|
- Сетка фото и обзор альбомов показывают изображение без апскейла при любом выбранном размере.
|
|
- Единое поведение на Postgres и SQLite; контрактные тесты покрывают новую роль.
|
|
- Бэкфилл существующих фото без ручных операций.
|
|
|
|
**Non-Goals:**
|
|
|
|
- Динамоческое ресайзирование на лету / image-proxy.
|
|
- Иные форматы (AVIF) или согласование форматов.
|
|
- Изменение `preview` (просмотрщик) и оригинала.
|
|
- Замер точной плотности DPR-пикселей и «идеальный» выбор варианта — допускается запас.
|
|
|
|
## Decisions
|
|
|
|
- **Набор вариантов.** `thumb = Fill(200,200)` (квадрат), `medium = Fill(800,800)` (квадрат),
|
|
`preview = Fit(1200,1200)`. Причина: плитка сетки квадратная (`aspect-square object-cover`),
|
|
квадратный кроп исключает лишний апскейл короткой стороны; просмотрщик не квадратный, поэтому
|
|
`preview` остаётся вписанным. Альтернатива — оставить `thumb` вписанным: отклонена, короткая
|
|
сторона выходит < 200 и мылит уже мелкие плитки.
|
|
- **Роль `medium` в `photo_objects`.** Роли маппятся 1:1 на поля API и на привязки; расширяем
|
|
ограничение значением `medium`. Альтернатива — отдельная таблица вариантов: избыточна для
|
|
одного размера.
|
|
- **`srcset` + `sizes`.** Ширины `200w/800w/1200w`; `sizes` берётся из фактической ширины сетки
|
|
(`bind:clientWidth` на `<ul>`, делённой на число колонок). Альтернатива — оценка по `100vw`:
|
|
неточна из-за рейла и правой панели, ведёт к перекачке 1200.
|
|
- **Бэкфилл через очередь.** Миграция делает `INSERT ... SELECT id FROM photos` в
|
|
`variant_jobs`; воркер пересоздаёт роли и перепривязывает, старые объекты уходят в GC по
|
|
refcount. Альтернатива — воркер, пропускающий уже готовые роли: требует версионирования
|
|
вариантов; альтернатива — отдельный `cmd/backfill`: лишний код. Выбран самый простой путь.
|
|
- **Обложка.** Джойн medium-варианта обложки с откатом на `thumb`, если medium ещё не готов;
|
|
отдельного поля «preview обложки» не вводим.
|
|
|
|
## Risks / Trade-offs
|
|
|
|
- [Перегенерация `preview` впустую при бэкфилле] → воркер работает батчами и параллельно
|
|
(настраивается); лишние объекты удаляются сборщиком; разовая стоимость.
|
|
- [Рост хранилища на один объект 800px на фото] → WebP q82 скромного размера; приемлемо.
|
|
- [Неточный `sizes`] → запас в пользу резкости; при отсутствии `medium_url` вариант просто не
|
|
попадает в `srcset`, сетка падает на доступные (thumb/preview).
|
|
- [SQLite не умеет `ALTER CHECK`] → миграция пересоздаёт `photo_objects`; на больших таблицах
|
|
копирование строк. Приемлемо для текущего масштаба, выполняется в окне обслуживания.
|
|
|
|
## Migration Plan
|
|
|
|
1. Применить миграцию: расширить `CHECK` роли, налить `variant_jobs` для существующих фото.
|
|
2. Выкатить код: воркер генерирует `medium` и квадратный `thumb`; API отдаёт новые поля.
|
|
3. Воркер разбирает очередь; сетка показывает `srcset` с откатом на доступные варианты.
|
|
4. Откат: сначала код (API перестаёт отдавать medium, воркер не генерирует); затем down-миграция
|
|
восстанавливает `CHECK`, предварительно удалив привязки `medium`.
|
|
|
|
## Open Questions
|
|
|
|
Нет.
|