feat(photos): separate object storage from metadata layer
Move physical file facts out of the photos table into an objects ledger:
- objects / photo_objects / variant_jobs tables (migration 000008)
- drop storage_key/thumb_key/preview_key/size/width/height/mime_type from
photos (migration 000009); they are derived via joins to the original object
- album-free storage keys: objects/{id}/{role}.{ext}
- explicit GC via pending/ready/deleting states; objects deleted only when no
photo references them
- background worker drives variant generation (durable variant_jobs) and GC
- copy/move are metadata-only; storage objects are shared between photos
- remove unused storage.DeletePrefix
Non-destructive to the external JSON API: size/dimensions/mime and URLs are
still returned, just sourced from the object layer.
OpenSpec change: media-object-layer
This commit is contained in:
+26
-8
@@ -328,23 +328,41 @@ colors: {
|
||||
- **albums** (id, owner_id, title, description, cover_photo_id, privacy, created_at)
|
||||
- **album_members** (album_id, user_id, role, invited_by, joined_at)
|
||||
- **invites** (id, album_id, token, created_by, expires_at, max_uses, uses)
|
||||
- **photos** (id, album_id, uploader_id, storage_key, thumb_key, preview_key,
|
||||
original_filename, size, width, height, mime_type,
|
||||
taken_at, latitude, longitude, caption, created_at)
|
||||
- **photos** (id, album_id, uploader_id, original_filename,
|
||||
taken_at, latitude, longitude, caption, exif, deleted_at, created_at)
|
||||
- **objects** (id, storage_key, mime_type, size, width, height, checksum, state, created_at)
|
||||
- **photo_objects** (photo_id, object_id, role)
|
||||
- **variant_jobs** (photo_id, attempts, next_attempt_at, last_error)
|
||||
- **comments** (id, photo_id, user_id, text, created_at, updated_at)
|
||||
- **likes** (photo_id, user_id, created_at)
|
||||
- **notifications** (id, user_id, type, payload_json, read_at, created_at)
|
||||
|
||||
## 10. Структура в S3 (Garage)
|
||||
## 10. Слой хранения и слой метаданных
|
||||
|
||||
**S3 (Garage) — непрозрачное объектное хранилище.** Ключи не содержат сущностей
|
||||
метаданных (альбома, фото). Каждый физический файл адресуется своим `object_id`:
|
||||
|
||||
```
|
||||
{bucket}/
|
||||
└── albums/{album_id}/
|
||||
├── original/{photo_id}.{ext}
|
||||
├── preview/{photo_id}.webp
|
||||
└── thumb/{photo_id}.webp
|
||||
└── objects/{object_id}/
|
||||
├── original.{ext}
|
||||
├── thumb.webp
|
||||
└── preview.webp
|
||||
```
|
||||
|
||||
**Postgres — журнал истины.** Таблица `objects` описывает каждый блоб (ключ, mime,
|
||||
размер, габариты, checksum, состояние `pending|ready|deleting`), таблица
|
||||
`photo_objects` связывает фото с объектами по ролям (`original|thumb|preview`),
|
||||
таблица `variant_jobs` — очередь генерации вариантов. Альбом, принадлежность и
|
||||
представление живут только в `photos`/`albums`.
|
||||
|
||||
Следствия:
|
||||
- **Копирование** между альбомами — только новые строки `photos`+`photo_objects`,
|
||||
ссылающиеся на те же объекты; операции с S3 не выполняются.
|
||||
- **Перенос** — только `UPDATE photos.album_id`; объекты не трогаются.
|
||||
- **Удаление** фото удаляет строки; фоновый воркер удаляет блоб лишь когда на
|
||||
объект не осталось ни одной ссылки (сборка мусора по ledger-состояниям).
|
||||
|
||||
Доступ: **presigned URL** со сроком 1 час (бакет приватный).
|
||||
Garage поддерживает presigned URL через SigV4 query-string signatures.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user