Архитектура БД

Архитектура базы данных

Техническая документация схемы данных Rodnoy Files — многопользовательское семейное фотохранилище с ИИ-функциями

1 Обзор архитектуры

Rodnoy Files использует трёхуровневую архитектуру хранения данных: PostgreSQL 16 как основную реляционную БД для структурированных данных (пользователи, файлы, альбомы, права доступа), Redis для сессий, кэша и очередей задач, и S3-совместимое объектное хранилище (MinIO / Selectel) для самих медиафайлов. Такой подход обеспечивает надёжность транзакций для метаданных и масштабируемость для больших объёмов медиа.

PostgreSQL 16
Метаданные, пользователи, отношения, биллинг, журналы
Redis 7
Сессии, кэш дашборда, очереди AI-задач, rate limiting
S3 (MinIO)
Оригиналы фото, превью, шифрованные файлы

2 Основные сущности

Ниже описаны ключевые таблицы БД. Каждая таблица имеет колонку user_id для изоляции данных (row-level security). Все ID используют UUID v7 для монотонной сортировки по времени.

users — Пользователи

Поле Тип Описание
idUUID PKУникальный идентификатор
emailVARCHAR(255) UNIQUEЛогин, также для восстановления доступа
password_hashVARCHAR(255)argon2id хеш пароля
nameVARCHAR(100)Отображаемое имя
avatar_file_idUUID FKСсылка на фото-аватар
plan_idINT FKТекущий тарифный план
storage_used_bytesBIGINTЗанятое место (байт), обновляется при загрузке/удалении
encryption_key_encTEXTЗашифрованный мастер-ключ E2E (AES-256-GCM)
two_factor_secretVARCHAR(255)TOTP секрет для 2FA (опционально)
created_atTIMESTAMPTZДата регистрации

files — Фотографии и медиа

Поле Тип Описание
idUUID PKУникальный ID файла
user_idUUID FKВладелец файла
original_nameVARCHAR(500)Оригинальное имя файла (DSC_0847.HEIC)
storage_pathTEXTПуть в S3: users/{user_id}/{uuid}.{ext}
thumbnail_pathTEXTПуть к превью (800px, WebP)
mime_typeVARCHAR(50)image/jpeg, image/heic, video/mp4
width / heightINTРазрешение оригинала
size_bytesBIGINTРазмер файла в байтах
checksum_sha256VARCHAR(64)Для определения дубликатов
exif_dataJSONBEXIF: GPS, дата съёмки, камера, ориентация
taken_atTIMESTAMPTZДата съёмки (из EXIF или загрузки)
is_favoriteBOOLEANДобавлено в избранное
ai_embeddingvector(512)pgvector эмбеддинг для AI-поиска (CLIP)
uploaded_atTIMESTAMPTZДата загрузки на сервер

albums / album_files — Альбомы

Поле Тип Описание
id / user_id / nameUUID / FK / VARCHARИдентификатор, владелец, название альбома
cover_file_idUUID FKОбложка альбома
is_ai_generatedBOOLEANСоздан ИИ автоматически
ai_categoryVARCHAR(50)Категория: nature, people, travel, food...
album_filesJOIN таблицаalbum_id + file_id + position (ORDER BY)

persons / faces — Распознавание людей

Поле Тип Описание
persons.id / user_id / nameUUID / FK / VARCHARID человека, владелец, имя
persons.avatar_file_idUUID FKФото-аватар человека (среднее или выбранное)
persons.photo_countINTДенормализованный счётчик (обновляется триггером)
persons.is_hiddenBOOLEANСкрыт из раздела "Люди"
faces.file_id / person_idFK / FKСвязь лицо-фото-человек
faces.bbox / confidenceJSONB / FLOATКоординаты bounding box [x,y,w,h], уверенность ИИ 0-1

shares — Общие ссылки

Поле Тип Описание
id / user_idUUID / FKID ссылки, кто поделился
share_typeENUMfile / album / folder
target_idUUIDID файла или альбома
tokenVARCHAR(32) UNIQUEСекретный токен для URL (crypto_random)
password_hashVARCHAR(255)Пароль на ссылку (опционально)
expires_atTIMESTAMPTZСрок действия ссылки (NULL = бесконечно)
max_views / view_countINT / INTЛимит и счётчик просмотров
is_activeBOOLEANАктивна ли ссылка

family_groups / family_members — Семейные аккаунты

Поле Тип Описание
family_groups.id / owner_id / nameUUID / FK / VARCHARГруппа, создатель, название
family_members.group_id / user_idFK / FKСвязь участник-группа
family_members.roleENUMowner / admin / member / viewer
family_members.can_upload / can_deleteBOOLEANПрава на загрузку и удаление

plans / subscriptions — Тарифы и подписки

Поле Тип Описание
plans.id / name / price_monthlyINT / VARCHAR / INTБазовый(0), Премиум(299), Семейный(599)
plans.storage_limit_bytesBIGINTЛимит хранилища (5 ГБ, 100 ГБ, 1 ТБ)
plans.featuresJSONBСписок доступных функций (E2E, AI, family)
subscriptions.user_id / plan_idFK / FKТекущая подписка пользователя
subscriptions.expires_atTIMESTAMPTZДата окончания (NULL = бессрочно)
subscriptions.payment_provider_idVARCHARID в платёжной системе (ЮKassa)

3 Многопользовательский режим

Изоляция данных — каждая таблица содержит user_id как обязательную колонку. Все SQL-запросы на уровне бэкенда включают фильтр WHERE user_id = $current_user. Дополнительно используется PostgreSQL Row-Level Security (RLS) как последний рубеж защиты на случай ошибки в прикладном коде.

Конкурентные загрузки — при массовой загрузке пользователь может отправлять до 10 файлов параллельно. Каждый файл проходит через очередь Redis (Celery). Сервер сначала записывает запись в БД со статусом uploading, затем файл попадает в S3, после чего статус обновляется на ready и запускается фоновая задача генерации превью и AI-обработки.

Rate limiting — Redis хранит счётчики запросов по ключу ratelimit:{user_id}:{endpoint} с TTL 60 секунд. Лимиты: 100 запросов/мин для API, 30 загрузок/мин, 5 AI-запросов/мин для бесплатных пользователей.

Квоты хранилища — при каждой загрузке проверяется users.storage_used_bytes + file_size <= plans.storage_limit_bytes. При превышении возвращается ошибка 429. Общий занятый объём обновляется через триггер БД при вставке/удалении файлов для защиты от race conditions.

4 Безопасность данных

E2E шифрование (AES-256-GCM) — при включённом шифровании файлы шифруются на клиенте ДО отправки на сервер. Схема: мастер-ключ пользователя хранится в зашифрованном виде в БД (users.encryption_key_enc). Мастер-ключ расшифровывается паролем пользователя (argon2id KDF). Для каждого файла генерируется уникальный Data Encryption Key (DEK), который шифруется мастер-ключом и хранится рядом с файлом в S3-метаданных.

Сессии — JWT access token (15 мин) + refresh token (30 дней) хранятся в Redis. При каждом запросе проверяется валидность токена через Redis lookup. При смене пароля все сессии пользователя инвалидируются массовым удалением ключей session:{user_id}:*.

Ссылки-шеры — токены генерируются через crypto.random(24) (192 бит энтропии). При наличии пароля — хеш хранится через argon2id. При каждом доступе проверяются: is_active, expires_at, max_views. Логи доступов (IP, User-Agent) записываются в отдельную таблицу share_access_logs.

5 Производительность

Индексы — на каждой таблице созданы: первичный ключ (UUID), B-tree индекс на user_id, композитные индексы на частые запросы (user_id + uploaded_at для галереи, user_id + name для поиска людей). Для AI-поиска используется расширение pgvector с IVFFlat индексом на колонку ai_embedding (ANN-поиск, top-50 похожих за <50ms).

Партиционирование — таблица files партиционируется по RANGE на uploaded_at (ежемесячные партиции). При достижении 100 млн записей — дальнейшее партиционирование по user_id (hash). Таблица faces партиционируется по user_id (hash, 32 партиции).

Redis-кэш — дашборд-статистика (кол-во фото, альбомов, людей, занятое место) кэшируется на 5 минут с инвалидацией при загрузке/удалении. Обложки альбомов кэшируются на 1 час. Список людей кэшируется до следующего обновления через AI.

Фоновые задачи (Celery) — после загрузки файла ставятся задачи: генерация превью (WebP 800px), извлечение EXIF, определение дубликатов (perceptual hash), распознавание лиц (InsightFace), генерация AI-эмбеддинга (CLIP). Задачи ставятся в очередь с приоритетом: превью > EXIF > дубликаты > лица > эмбеддинг.

6 Масштабирование

Начальная конфигурация — один сервер (VPS 8 vCPU, 32 ГБ RAM) с PostgreSQL, Redis, Celery worker, Nginx. Достаточно для 1 000 пользователей с 10 000 фото каждый. S3-хранилище (Selectel/MinIO) на отдельном сервере или облачном сервисе.

Рост до 10 000+ пользователей — добавление read replica PostgreSQL (hot standby) для тяжелых аналитических запросов (AI-поиск, статистика). Вертикальное масштабирование Celery workers. CDN (Cloudflare/Bunny) для раздачи превью и публичных шар.

Рост до 100 000+ пользователей — миграция на управляемый PostgreSQL (Supabase/Neon) с автоматическим масштабированием. Citus для горизонтального шардирования по user_id. Отдельные очереди Celery для AI-задач (GPU-воркеры). S3 с версионированием и lifecycle-правилами для автоматического удаления старых превью.

7 ER-диаграмма

Структура связей между основными таблицами. Каждая таблица изолирована по user_id (не показано для чистоты диаграммы).

users
1 — N
files
Основная таблица
albums
1 — N (album_files) — N
persons
1 — N (faces) — N (files)
shares
N — 1 (users), N — 1 (files/albums)
family_groups
N — N (users через family_members)
Связи: users → files (1:N), users → albums (1:N), albums ↔ files (M:N через album_files), users → persons (1:N), persons ↔ files (M:N через faces), files → shares (1:N), users → shares (1:N), users → subscriptions (1:1), plans → subscriptions (1:N), family_groups ↔ users (M:N через family_members)

8 Этапы внедрения

Этап 1: Ядро (MVP)
Срок: 2-3 недели

Таблицы: users, files, albums, album_files. Регистрация/авторизация через JWT. Загрузка файлов в S3. Генерация превью. CRUD альбомов. Базовая галерея с пагинацией. Индексы на user_id + uploaded_at. Подключение pgvector (пока без эмбеддингов).

Этап 2: AI-функции
Срок: 3-4 недели

Таблицы: persons, faces, cleanup_suggestions. Celery workers для фоновых задач. Распознавание лиц (InsightFace). Определение дубликатов (perceptual hash pHash + dHash). Определение размытых/плохих фото. Автоматическая категоризация альбомов. AI-поиск через CLIP эмбеддинги + pgvector ANN.

Этап 3: Шары и Семья
Срок: 2 недели

Таблицы: shares, share_access_logs, family_groups, family_members. Генерация публичных ссылок с токенами. Пароль на ссылки. Срок действия. Семейные группы с ролевой моделью. Семейное хранилище (shared vault — файлы видны всем участникам группы).

Этап 4: Биллинг и E2E
Срок: 2-3 недели

Таблицы: plans, subscriptions, payment_history. Интеграция ЮKassa. Управление подписками. E2E шифрование: генерация мастер-ключа на клиенте, шифрование файлов перед загрузкой, хранение DEK в S3-метаданных. 2FA через TOTP. Вебхуки для платежных уведомлений.