Tropic Host

Установка Immich на VPS в Docker: собственная замена Google Photos с AI-распознаванием лиц, pgvector и сайзингом NVMe

38 мин чтения
Tropic

Краткий вывод: Для стабильного развертывания Immich с фоновым AI-инференсом лиц и векторным поиском pgvector требуется KVM-сервер конфигурации от 4 vCPU (с поддержкой инструкций AVX2 для ONNX Runtime), 6 ГБ RAM для защиты от OOM Killer и NVMe-накопитель под базу данных и превью. Продакшен-стек разворачивается через Docker Compose с изоляцией микросервисов и кэша Redis, а внешний трафик защищается обратным прокси (Nginx/Caddy) по протоколам TLS 1.3 и HTTP/2 с директивой client_max_body_size 0 для бесшовной передачи «тяжелых» медиафайлов с мобильных клиентов.


Содержание

  1. Аппаратные требования и сайзинг KVM VPS под стек Immich
  2. Подготовка операционной системы Linux и тюнинг параметров ядра
  3. Развертывание Immich через Docker Compose: архитектура и переменные окружения
  4. Тюнинг Machine Learning: распознавание лиц, CLIP-поиск и аппаратное ускорение
  5. Публикация в сеть: настройка реверс-прокси Caddy с автоматическим TLS и защита доступа
  6. Синхронизация мобильных устройств: настройка клиентов iOS и Android
  7. Регламент Disaster Recovery: автоматическое резервное копирование и верификация восстановления
  8. Диагностика и устранение типовых сбоев при эксплуатации Immich
  9. Часто задаваемые вопросы (FAQ)

Аппаратные требования и сайзинг KVM VPS под стек Immich

Развертывание стека Immich на VPS сопряжено с выраженным асимметричным профилем нагрузки: в моменты пакетного импорта сотен гигабайт медиафайлов пиковая утилизация вычислительных ядер и дисковой подсистемы возрастает на порядок по сравнению с режимом редкого чтения. Архитектура приложения декомпозирована на несколько тесно связанных сервисов (immich-server, фоновые воркеры микросервисов, кэширующий брокер Redis, реляционная база PostgreSQL с векторным расширением pgvector и выделенный микросервис immich-machine-learning). Некорректный сайзинг любого из этих узлов приводит к лавинообразным задержкам синхронизации, исчерпанию пула соединений и зависанию очередей задач. Для надежной изоляции стека требуется аппаратная KVM-виртуализация, поскольку в контейнерных средах OpenVZ/LXC заблокированы тонкие настройки планировщика ядра Linux, гранулярные лимиты памяти через cgroups v2 и управление системными вызовами ввода-вывода.

Профиль нагрузки: параллелизм микросервисов и инференс нейросетей

Каждый компонент стека накладывает специфические требования на аппаратные ресурсы хоста:

  1. immich-server и фоновые очереди (Redis + BullMQ): Node.js runtime обрабатывает входящие REST/WebSocket-соединения мобильных клиентов, осуществляет аудит сессий и маршрутизирует очереди задач. Redis удерживает в оперативной памяти графы фоновых джобов (извлечение метаданных, генерация превью, трансформация цветов). Нехватка RAM вызывает принудительный сброс очередей или аварийную остановку воркеров по OOM.
  2. PostgreSQL с расширением pgvector: Векторная база хранит 512- или 768-мерные эмбеддинги CLIP для семантического визуального поиска и векторные дескрипторы лиц. Построение и обход графовых индексов HNSW (Hierarchical Navigable Small World) критически зависят от объема памяти: если индекс не помещается в shared_buffers и дисковый кэш ОС, задержка поиска (p99 latency) возрастает с 4–8 мс до сотен миллисекунд из-за постоянного чтения страниц с диска.
  3. immich-machine-learning: Python-контейнер на базе ONNX Runtime выполняет три ресурсоемкие задачи: детекцию лиц, распознавание объектов и генерацию текстово-визуальных векторов. При пакетном импорте инференс утилизирует доступные vCPU на 100%, активно задействуя инструкции векторных расширений AVX2/AVX-512.

Вычислительные ядра: почему критичен параметр CPU Steal Time (%st = 0.0%)

В моменты импорта медиаархива микросервис Machine Learning и транскодер видео (FFmpeg) вызывают непрерывную 100% загрузку процессорных потоков. Если хостинг-провайдер использует агрессивный оверселлинг CPU, гипервизор начинает принудительно отбирать процессорные кванты в пользу соседей по физической ноде.

В выводе утилит top или vmstat 1 это фиксируется метрикой %st (CPU Steal Time):

avg-cpu:  %user   %system   %idle  %iowait  %steal
          82.40     15.20    0.00     2.40    0.00

При значении %st > 3–5% процесс инференса нейросетей начинает троттлить: время распознавания одного кадра возрастает с 80–120 мс до 1.5–2 секунд. Это приводит к разрастанию очереди BullMQ, деградации Node.js Event Loop и потере соединений с мобильными клиентами по таймауту.

Для стабильной работы Immich на виртуальном сервере необходимы изолированные vCPU на базе высокочастотных процессоров AMD EPYC или AMD Ryzen 9 (с базовой частотой от 3.5 ГГц) с честным закреплением ресурсов. На инфраструктуре облачного провайдера tropic.host виртуализация KVM развернута с жестким контролем плотности нод, что гарантирует CPU Steal Time %st = 0.0% даже при длительной компиляции превью и безостановочном инференсе нейросетевых моделей.

Требования к дисковой подсистеме: случайный доступ 4K QD1

Специфика фотоархива заключается в доминировании сотен тысяч мелких файлов. Для каждой оригинальной фотографии Immich генерирует несколько деривативов: * Крошечный размытый плейсхолдер (BlurHash/WebP, 2–4 КБ). * Сжатое мини-превью для сетки таймлайна (WebP, 250px, 15–30 КБ). * Полноразмерное превью для экрана просмотра (WebP/JPEG, 1080p–4K, 200–800 КБ).

Когда пользователь прокручивает галерею на смартфоне, клиентский запрос запрашивает от 50 до 200 мини-превью в секунду. В этот момент дисковая подсистема испытывает экстремальную нагрузку на произвольное чтение блоками 4 КБ с глубиной очереди 1 (4K QD1).

Классические HDD и бюджетные SATA SSD с задержками доступа 5–15 мс вызывают зависание ленты и долгую подгрузку превью. Стек требует исключительно серверные накопители NVMe PCIe 4.0, демонстрирующие скорость произвольного чтения 4K QD1 свыше 50 000 IOPS и p99 latency менее 0.8 мс. В пулах хранения платформы tropic.host используются корпоративные NVMe-диски PCIe 4.0 с защитой от теплового троттлинга, что исключает деградацию дискового ввода-вывода при одновременной фоновой записи входящего потока данных и отдаче превью в веб-интерфейс.

Сетевая инфраструктура и пропускная способность аплинка

Первичная настройка синхронизации мобильного устройства часто сопровождается единовременной выгрузкой от 100 ГБ до 1–2 ТБ несжатых данных (форматы Apple ProRAW, HEIC, 4K 60fps ProRes). На стандартном канале 100 Мбит/с трансфер 500 ГБ занимает более 11 часов, создавая постоянную нагрузку на сетевой стек.

Симметричный аплинк с пропускной способностью от 1 до 10 Гбит/с сокращает время первичной заливки до десятков минут. Для предотвращения потери пакетов и буферблоата при передаче данных через трансграничные маршруты ядро Linux должно использовать современный алгоритм контроля перегрузок TCP BBR:

# Проверка и активация BBR в sysctl
sysctl -w net.core.default_qdisc=fq
sysctl -w net.ipv4.tcp_congestion_control=bbr

Прямой BGP-пиринг нод tropic.host в крупнейших европейских и региональных узлах обмена трафиком (Франкфурт, Амстердам, Стамбул) в сочетании с активным TCP BBR обеспечивает стабильный минимальный RTT и утилизацию полосы пропускания без сброса TCP-окон при заливке больших видеофайлов.


Матрица аппаратного сайзинга KVM VPS под Immich

В таблице представлены валидированные спецификации инстансов под разные масштабы медиабиблиотеки с учетом резервирования ресурсов под кэш PostgreSQL и инференс ONNX:

Масштаб архива / Профиль нагрузки vCPU (AMD EPYC / Ryzen 9) RAM (ECC) Дисковое пространство (NVMe PCIe 4.0) Полоса аплинка Референсный профиль tropic.host Сценарий эксплуатации и ограничения
Starter
До 50 000 фото / 1 пользователь
2 ядра
(Честный KVM, %st = 0%)
4 ГБ 80–120 ГБ
(4K QD1 > 40k IOPS)
1 Гбит/с
(BBR включен)
KVM NVMe Starter Индивидуальный архив. Инференс CLIP поочередный (IMMICH_WORKERS=1), отключено одновременное кодирование H.265.
Production
50k–300k фото / 2–5 пользователей
4 ядра
(Базовая частота 3.5+ ГГц)
8 ГБ 250–500 ГБ
(4K QD1 > 55k IOPS)
1–2.5 Гбит/с KVM NVMe Pro Семейный сервер. Одновременный поиск по лицам, фоновый транскодинг видео в 1080p, HNSW-индекс pgvector целиком в RAM.
Advanced Pro
300k–1M фото / до 10 пользователей
6–8 ядер
(Высокочастотные vCPU)
16 ГБ 1–2 ТБ
(Enterprise NVMe)
2.5–5 Гбит/с KVM NVMe Business Профессиональная галерея (RAW/DNG). Параллельный ML-инференс в 2–4 потока, аппаратный оффлоад, shared_buffers = 4GB.
Enterprise / Studio
Более 1 млн медиафайлов / Студия
12–16 ядер
(Dedicated Cores)
32–64 ГБ 2–4+ ТБ
(NVMe массив с резервом)
10 Гбит/с KVM High-Frequency Dedicated Высоконагруженный архив студии. Мгновенная кластеризация векторов, многопоточный транскодинг 4K, отсутствие очередей на бэкап.

При сайзинге дискового пространства важно закладывать дополнительный запас: служебные директории Immich (миниатюры, кэш транскодирования, транзакционные логи PostgreSQL WAL и дамп векторов pgvector) требуют дополнительно 25–35% объема сверх «чистого» размера оригинальных файлов. Выбор предсказуемой KVM-ноды на платформе tropic.host исключает риск падения сервиса из-за деградации I/O или нехватки ресурсов процессора в моменты пикового инжеста.

Подготовка операционной системы Linux и тюнинг параметров ядра

Эксплуатация распределенного мультимедийного бекенда требует адаптации стандартной конфигурации ядра Linux под профиль смешанной дисковой и сетевой I/O-нагрузки: одновременный потоковый аплоад несжатых 4K-видеоконтейнеров, параллельный ML-инференс и транзакционная запись векторных эмбеддингов. Полноценная модификация подсистемы виртуальной памяти и сетевого стека возможна исключительно на аппаратной виртуализации KVM, так как контейнерные среды OpenVZ/LXC блокируют системные вызовы к пространству /proc/sys и изолируют доступ к контроллерам cgroups v2.

Развертывая Immich на VPS, базовой платформой следует выбирать свежие дистрибутивы с ядром ветки 6.x — Ubuntu 24.04 LTS или Debian 12. На серверных инстансах tropic.host гипервизор предоставляет чистые вычислительные ядра AMD EPYC / Ryzen 9 с нулевым временем ожидания процессора (%st = 0.0%), что исключает спонтанные задержки планировщика CFS при пакетной обработке очередей микросервисов.

Оптимизация подсистемы виртуальной памяти и файловых дескрипторов

Служба распознавания лиц и семантического поиска задействует расширение pgvector для PostgreSQL и ONNX-модели машинного обучения. Этим процессам требуются непрерывные пулы отображаемой памяти и увеличенные лимиты системных дескрипторов inotify для фонового сканирования внешних библиотек.

Стандартные значения дистрибутивов вызывают отказ в аллокации буферов (ENOMEM) и падение воркеров экспорта с ошибкой too many open files. Создайте конфигурационный файл /etc/sysctl.d/99-immich.conf (переопределяющий параметры sysctl.conf) со следующими параметрами:

cat <<'EOF' > /etc/sysctl.d/99-immich.conf
# Увеличение пула областей виртуальной памяти для pgvector и ONNX Runtime
vm.max_map_count = 524288

# Снижение агрессивности вытеснения страниц кэша в swap для защиты NVMe от износа
vm.swappiness = 10

# Контроль грязных страниц для сглаживания I/O-пиков при записи видеопотоков
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10

# Лимиты системных файловых дескрипторов и подсистемы inotify
fs.file-max = 2097152
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 1024

# Очереди сокетов и сетевые буферы под трансляцию тяжелых медиафайлов
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 8192
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# Активация алгоритма управления перегрузками TCP BBR и планировщика fq
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF

Примените изменения ядра без перезагрузки ноды:

sysctl --system

Параметр vm.max_map_count = 524288 выделяет достаточный лимит областей VMA (vm_area_struct) под генерацию графов HNSW в pgvector, предотвращая нехватку сегментов адресации. Значение vm.swappiness = 10 удерживает файловый кэш СУБД в оперативной памяти, обращаясь к разделу подкачки только при критическом давлении на RAM.

Для снятия пользовательских лимитов процессов на уровне PAM-аутентификации сконфигурируйте файл /etc/security/limits.d/99-immich.conf:

cat <<'EOF' > /etc/security/limits.d/99-immich.conf
* soft nofile 65535
* hard nofile 1048576
* soft nproc 65535
* hard nproc 65535
EOF

Активация алгоритма TCP BBR для ускорения мобильного инжеста

Традиционный алгоритм Reno/Cubic трактует единичные потери пакетов (packet drop) в сетях LTE/5G как сигнал перегрузки канала, мгновенно снижая размер окна перегрузки (CWND) в два раза. Это приводит к длительной буферизации и обрывам при автоматической выгрузке 4K-видеопотоков со смартфонов в бэкенд галереи.

Алгоритм TCP BBR (Bottleneck Bandwidth and RTT), разработанный Google, базируется на динамическом моделировании фактической пропускной способности узкого места канала и минимального времени круговой задержки (RTprop). В связке с планировщиком очередей fq (Fair Queuing) BBR нивелирует влияние буферблоата (Bufferbloat) и передает медиаконтент на максимальной физической скорости аплинка даже при 2–5% случайных сетевых потерь. На инфраструктуре tropic.host порты серверов подключены к симметричным магистральным каналам 1–10 Гбит/с с прямой маршрутизацией к узлам обмена трафиком в Амстердаме, Франкфурте и Стамбуле, что позволяет утилизировать всю полосу мобильного клиента при входящем бэкапе.

Убедитесь, что модуль BBR загружен и активен:

lsmod | grep bbr || modprobe tcp_bbr
sysctl net.ipv4.tcp_congestion_control
# Ожидаемый вывод: net.ipv4.tcp_congestion_control = bbr

Развертывание Docker CE и плагина Compose v2

Штатные пакеты docker.io и docker-compose из стандартного репозитория дистрибутива содержат устаревшие сборки и устаревший Python-рантайм Compose v1, несовместимый с современными спецификациями Immich. Необходима установка актуального Docker CE (Community Edition) и CLI-плагина docker-compose-plugin напрямую из официального репозитория Docker Inc.

Перед установкой удалите конфликтующие пакеты ОС:

for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do 
  apt-get remove -y $pkg 2>/dev/null
done

Сконфигурируйте официальный репозиторий и установите стабильный стек рантайма:

apt-get update && apt-get install -y ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  tee /etc/apt/sources.list.d/docker.list > /dev/null

apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

В дистрибутиве Ubuntu 24.04 LTS по умолчанию задействована унифицированная иерархия cgroups v2. Проверьте тип смонтированной файловой системы контрольных групп:

stat -fc %T /sys/fs/cgroup/
# Результат должен возвращать: cgroup2fs

Наличие cgroups v2 обязательно для изоляции микросервисов: это позволяет демону Docker корректно применять ограничения systemd по лимитам оперативной памяти (memory.max, memory.high), защищая ноду от аварийного падения демона при переполнении буферов видеокодера ffmpeg.

Для предотвращения переполнения диска логами контейнеров настройте ротацию в /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "3"
  },
  "live-restore": true,
  "exec-opts": ["native.cgroupdriver=systemd"]
}

Перезапустите демон Docker для применения настроек:

systemctl restart docker && systemctl enable docker

Монтирование файловых систем и структура рабочих каталогов

Транскодирование видео и генерация миниатюр создают непрерывный поток мелких операций случайной записи (4K random write). Если хранилище смонтировано на отдельный блочный NVMe-диск, файловую систему ext4 необходимо размечать с отключением обновления меток времени доступа к файлам (noatime), что экономит до 15–20% дисковых транзакций журнала (jbd2).

В /etc/fstab точка монтирования целевого хранилища должна содержать следующие опции:

UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /mnt/storage ext4 defaults,noatime,nodiratime,commit=60 0 2

Опция commit=60 задерживает сброс метаданных журнала на диск до 60 секунд, агрегируя служебные операции в памяти и снижая нагрузку на контроллер накопителя при массовом импорте фотосессий.

Сформируйте изолированную иерархию директорий для раздельного хранения сырых исходников, временного кэша и базы данных:

# Системная директория для конфигураций и compose-файлов
mkdir -p /opt/immich && cd /opt/immich

# Директории для постоянного хранения данных (Persistent Storage)
mkdir -p /mnt/storage/immich/library          # Исходные оригиналы (RAW, DNG, MOV, MP4)
mkdir -p /mnt/storage/immich/upload           # Временный каталог для поступающих чанков
mkdir -p /mnt/storage/immich/thumbs           # Сгенерированные превью и миниатюры WebP
mkdir -p /mnt/storage/immich/encoded-video    # Сконвертированные H.264/H.265 потоки
mkdir -p /mnt/storage/immich/profile          # Аватары и профильные изображения
mkdir -p /mnt/storage/immich/postgres         # Таблицы, WAL и pgvector индексы

# Назначение прав доступа
# Контейнеры Immich по умолчанию работают от UID:GID 1000:1000
chown -R 1000:1000 /mnt/storage/immich
chmod -R 775 /mnt/storage/immich

# Директория PostgreSQL требует строгого владения UID СУБД (999 в официальном контейнере)
chown -R 999:999 /mnt/storage/immich/postgres
chmod 700 /mnt/storage/immich/postgres

Разделение директорий на уровне путей позволяет в дальнейшем выносить каталог миниатюр (thumbs) или транскодированных видео (encoded-video) на более быстрые scratch-накопители, а основной архив (library) держать на емких массивах без необходимости перенастройки внутренней архитектуры контейнеров.

Развертывание Immich через Docker Compose: архитектура и переменные окружения

После подготовки файловой системы и разграничения прав доступа переходим к формированию конфигурации сервисов в директории /opt/immich. Архитектура приложения декомпозирована на четыре автономных компонента, взаимодействующих внутри изолированного сетевого пространства:

  1. immich-server — ядро системы, объединяющее REST API, WebSocket-шлюз и воркеры фоновых очередей на базе BullMQ. Сервер принимает входящий трафик, управляет метаданными и нарезает асинхронные задачи (генерация превью, извлечение EXIF, транскодирование).
  2. immich-machine-learning — микросервис инференса нейросетевых моделей на Python/ONNX Runtime. Выполняет распознавание лиц, классификацию сцен и генерацию эмбеддингов для семантического поиска.
  3. database (PostgreSQL с расширением tensorchord/pgvecto-rs) — гибридное хранилище реляционных сущностей и векторных представлений. Расширение pgvecto.rs написано на Rust и использует векторные индексы HNSW/IVFFlat, выполняя быстрый поиск по косинусному расстоянию без необходимости развертывать отдельную векторную СУБД вроде Milvus или Qdrant.
  4. redis — in-memory хранилище состояний и брокер очередей BullMQ с субмиллисекундным временем отклика, предотвращающий деградацию базы данных при лавинообразном поступлении мелких системных задач.

Стабильность такой многокомпонентной системы при развертывании Immich на VPS напрямую зависит от аппаратного слоя гипервизора. В отличие от контейнерных сред OpenVZ/LXC, аппаратная виртуализация KVM гарантирует полную изоляцию ресурсов ядра. Например, на облачных KVM-инстансах платформы tropic.host метрика CPU Steal Time строго зафиксирована на уровне %st = 0.0%. Отсутствие оверселлинга процессоров AMD EPYC / Ryzen 9 исключает задержки в обработке очередей BullMQ и предотвращает троттлинг векторных вычислений при индексации многотысячных медиатек.


Генерация криптографических секретов и конфигурация .env

Файл .env хранит системные константы, параметры подключения к СУБД и локальные пути монтирования накопителей. Использование значений по умолчанию в production-среде недопустимо: слабый пароль к СУБД создает прямую уязвимость базы векторов и метаданных.

Сгенерируйте криптостойкий пароль для PostgreSQL с помощью системного генератора псевдослучайных чисел:

cd /opt/immich
openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32 > /tmp/db_pass.txt

Сформируйте конфигурационный файл .env:

cat <<EOF > /opt/immich/.env
# Версия контейнеров Immich
IMMICH_VERSION=release

# Корневой путь постоянного хранилища (Persistent Storage)
UPLOAD_LOCATION=/mnt/storage/immich

# Каталог расположения данных PostgreSQL и pgvecto-rs индексов
DB_DATA_LOCATION=/mnt/storage/immich/postgres

# Сетевые параметры подключения к СУБД
DB_DATABASE_NAME=immich
DB_USERNAME=postgres
DB_PASSWORD=$(cat /tmp/db_pass.txt)
DB_PORT=5432

# Таймзона инстанса для корректной синхронизации таймстемпов EXIF
TZ=UTC
EOF

# Удаление временного файла пароля и ограничение прав доступа к .env
rm -f /tmp/db_pass.txt
chmod 600 /opt/immich/.env

Параметр UPLOAD_LOCATION задает базовую точку монтирования для внутренних каталогов Immich. Переменная DB_PASSWORD считывается контейнерами СУБД и бэкенда на этапе запуска для прохождения взаимной аутентификации в приватной Docker-сети.


Конфигурация оркестрации: docker-compose.yml

Создайте манифест docker-compose.yml, объединяющий микросервисы в единый контур с контролем зависимостей через механизм healthcheck:

cat <<'EOF' > /opt/immich/docker-compose.yml
services:
  immich-server:
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
    env_file:
      - .env
    ports:
      - 127.0.0.1:2283:2283
    depends_on:
      redis:
        condition: service_healthy
      database:
        condition: service_healthy
    restart: always
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://127.0.0.1:2283/api/server/version || exit 1"]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 30s
    deploy:
      resources:
        limits:
          memory: 4096M

  immich-machine-learning:
    container_name: immich_machine_learning
    image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
    volumes:
      - /mnt/storage/immich/model-cache:/cache
    env_file:
      - .env
    restart: always
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://127.0.0.1:3003/ping || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 3072M

  redis:
    container_name: immich_redis
    image: docker.io/redis:7.2-alpine
    restart: always
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 512M

  database:
    container_name: immich_postgres
    image: tensorchord/pgvecto-rs:pg16-v0.2.1
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_DB: ${DB_DATABASE_NAME}
      POSTGRES_INITDB_ARGS: "--data-checksums"
    volumes:
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
    restart: always
    command: [
      "postgres",
      "-c", "shared_buffers=1024MB",
      "-c", "work_mem=32MB",
      "-c", "maintenance_work_mem=256MB",
      "-c", "effective_cache_size=2048MB",
      "-c", "random_page_cost=1.1",
      "-c", "checkpoint_completion_target=0.9",
      "-c", "wal_buffers=16MB",
      "-c", "max_connections=100",
      "-c", "shared_preload_libraries=vectors.so"
    ]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE_NAME}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s
    deploy:
      resources:
        limits:
          memory: 2048M

networks:
  default:
    name: immich_network
    driver: bridge
EOF

Технические особенности спецификации:

  • Инжекция tensorchord/pgvecto-rs: Параметр -c shared_preload_libraries=vectors.so загружает динамическую библиотеку расширения в адресное пространство PostgreSQL при инициализации postmaster. Директива -c random_page_cost=1.1 оптимизирует планировщик запросов под характеристики серверных NVMe PCIe 4.0 накопителей (в отличие от стандартного для HDD значения 4.0), снижая latency p99 при выполнении семантического поиска по визуальному контексту.
  • Сетевая изоляция: Порт 2283 сервиса immich-server привязан к локальному интерфейсу 127.0.0.1. Прямой доступ из внешней сети закрыт; весь входящий HTTP/HTTPS трафик должен проксироваться через защищенный обратный прокси (Nginx или Traefik) с поддержкой TLS 1.3 и HSTS.
  • Cgroups-лимиты: Разделы deploy.resources.limits фиксируют потолок потребления RAM каждым процессом. Это исключает неконтролируемое срабатывание Linux OOM Killer, предотвращая аварийное завершение демона PostgreSQL при пиковой загрузке во время пакетной векторизации медиафайлов.

Запуск стека и валидация инициализации

Перед запуском убедитесь, что поддиректория кэша моделей машинного обучения создана и имеет корректные права владения:

mkdir -p /mnt/storage/immich/model-cache
chown -R 1000:1000 /mnt/storage/immich/model-cache

Выполните развертывание контейнеров в фоновом режиме:

docker compose up -d

Проверьте текущий статус сервисов и прохождение тестов живучести (healthchecks):

docker compose ps

Вывод команды должен подтвердить переход каждого сервиса в состояние healthy:

NAME                      IMAGE                                                 COMMAND                  SERVICE                   CREATED          STATUS                    PORTS
immich_machine_learning   ghcr.io/immich-app/immich-machine-learning:release   "tini -- ./start.sh"     immich-machine-learning   45 seconds ago   Up 44 seconds (healthy)   
immich_postgres           tensorchord/pgvecto-rs:pg16-v0.2.1                    "docker-entrypoint.s…"   database                  45 seconds ago   Up 44 seconds (healthy)   5432/tcp
immich_redis              docker.io/redis:7.2-alpine                            "docker-entrypoint.s…"   redis                     45 seconds ago   Up 44 seconds (healthy)   6379/tcp
immich_server             ghcr.io/immich-app/immich-server:release              "tini -- ./start.sh"     immich-server             45 seconds ago   Up 44 seconds (healthy)   127.0.0.1:2283->2283/tcp

Проверьте лог инициализации базы данных на предмет успешного включения расширения векторизации и отсутствия ошибок контрольных сумм:

docker compose logs database | grep -E "database system is ready to accept connections|vectors"

В потоке stdout должны присутствовать маркеры готовности СУБД:

immich_postgres  | [1] LOG:  loaded library "vectors.so"
immich_postgres  | [1] LOG:  database system was shut down at 2026-10-04 09:12:01 UTC
immich_postgres  | [1] LOG:  database system is ready to accept connections

Валидируйте выполнение миграций структуры таблиц и подключение воркеров BullMQ в логах основного приложения:

docker compose logs --tail=50 immich-server | grep -E "Connected to PostgreSQL|Initialized database|BullMQ"

Присутствие записей об успешном выполнении миграций подтверждает корректность созданной структуры таблиц, доступность очередей Redis и готовность стека к обработке сетевых запросов.

Тюнинг Machine Learning: распознавание лиц, CLIP-поиск и аппаратное ускорение

После успешной инициализации микросервисов основной вычислительный удар при импорте медиатеки принимает на себя контейнер immich-machine-learning. По умолчанию сервис запускает ресурсоемкие пайплайны компьютерного зрения без жестких лимитов на параллелизм, что при неконтролируемом сканировании сотен гигабайт фотографий приводит к мгновенной утилизации всех доступных vCPU, росту CPU load average выше критических отметок и вызову OOM Killer ядром Linux. Грамотная настройка инференса позволяет сохранить отзывчивость веб-интерфейса и фоновых очередей даже на виртуальных мощностях начального уровня.

Механика семантического поиска и векторизации через CLIP

Поиск по естественному языку («собака на пляже на закате», «красный автомобиль», «документы с печатью») реализован в Immich через архитектуру мультимодальных трансформеров CLIP (Contrastive Language-Image Pre-Training). Векторизация выполняется в два независимых этапа:

  1. Индексация медиафайлов: При добавлении изображения визуальный энкодер (по умолчанию модель семейства ViT-B-32 — Vision Transformer с размером патча $32 \times 32$ пикселя) сжимает картинку до размерности $224 \times 224$ и генерирует нормализованный 512-мерный вектор признаков (эмбеддинг). Этот вектор записывается в PostgreSQL с расширением pgvecto-rs в колонку типа vector(512).
  2. Обработка текстового запроса: Когда пользователь вводит поисковую фразу в клиентском интерфейсе, текстовый энкодер CLIP преобразует строку в аналогичный 512-мерный вектор в общем латентном пространстве признаков.
  3. K-NN поиск: База данных вычисляет косинусное сходство (Cosine Distance) между вектором запроса и векторами изображений через индекс HNSW (Hierarchical Navigable Small World): $$\text{similarity} = \cos(\theta) = \frac{\mathbf{u} \cdot \mathbf{v}}{|\mathbf{u}| |\mathbf{v}|}$$ Выборка наиболее релевантных совпадений возвращается клиенту с задержкой $p99 < 40\text{ мс}$ при условии нахождения поискового индекса целиком в оперативной памяти.

Использование архитектуры ViT-B-32 обеспечивает оптимальное соотношение точности zero-shot классификации и вычислительной сложности, однако требует строгого контроля за потреблением памяти при матричном умножении в слоях Self-Attention.

Управление параллелизмом инференса и защита от OOM

Микросервис машинного обучения Immich построен на базе Python-фреймворка FastAPI с движком ONNX Runtime. В стандартной поставке каждый рабочий процесс (воркер) загружает в оперативную память собственную копию моделей CLIP и распознавания лиц. Если запуск контейнера выполняется на сервере с 4–8 vCPU, рантайм пытается задействовать доступные ядра под каждый воркер, что вызывает взрывной рост RSS (Resident Set Size).

Базовая модель CLIP ViT-B-32 в формате FP32 требует порядка 1.2 ГБ оперативной памяти, а пайплайн Facial Recognition (детекция SCRFD + эмбеддинги ArcFace из пакета InsightFace) — еще около 800 МБ. Четыре параллельных воркера потребуют свыше 8 ГБ RAM исключительно под веса моделей и промежуточные тензоры, без учета буферов базы данных.

Для предотвращения нехватки памяти скорректируйте блок окружения контейнера immich-machine-learning в файле docker-compose.yml:

  immich-machine-learning:
    image: ghcr.io/immich-app/immich-machine-learning:release
    container_name: immich_machine_learning
    restart: always
    environment:
      # Ограничение количества параллельных воркеров FastAPI/Uvicorn
      - IMMICH_MACHINE_LEARNING_WORKERS=1
      # Лимит потоков вычислений на уровне математических библиотек (OpenMP / BLAS)
      - OMP_NUM_THREADS=2
      - OPENBLAS_NUM_THREADS=2
      # Глубина очереди запросов к модели инференса
      - MACHINE_LEARNING_CONCURRENCY=2
      # Каталог кэширования загруженных моделей
      - MACHINE_LEARNING_CACHE_FOLDER=/cache
    volumes:
      - ./model-cache:/cache
    deploy:
      resources:
        limits:
          cpus: '3.5'
          memory: 4096M
        reservations:
          memory: 2048M

Параметр IMMICH_MACHINE_LEARNING_WORKERS=1 фиксирует единственный рабочий процесс, разделяющий загруженные в RAM веса моделей между входящими задачами очередей Redis/BullMQ. Регулировка потоков ядра через OMP_NUM_THREADS и аппаратных очередей Concurrency Threads внутри контейнера гарантирует, что движок матричных вычислений не займет 100% процессорного бюджета хоста, оставляя ресурс под операции ввода-вывода и веб-сервер.

При развертывании Immich на VPS критическую роль играет предсказуемость таймингов CPU. На виртуальных машинах хостинг-провайдера tropic.host архитектурно исключен оверселлинг вычислительных узлов: показатель CPU Steal Time строго равен нулю (%st = 0.0%). Инстансы работают на изолированных физических ядрах AMD EPYC и Ryzen 9 с частотой до 4.5–5.0 ГГц, что исключает просадку скорости инференса нейросетей из-за конкурирующих соседних нагрузок на гипервизоре KVM.

Выбор и замена базовых моделей на квантованные версии

Штатные модели распознавания лиц и CLIP поставляются в 32-битной точности с плавающей запятой (FP32). Замена их на 8-битные квантованные версии (INT8) сокращает потребление оперативной памяти на 60–70% и ускоряет математический инференс на процессорах с векторными инструкциями AVX-512 VNNI (Vector Neural Network Instructions) в 2.5–3.5 раза без потери точности сопоставления лиц.

Переопределение моделей выполняется через административную панель Immich в разделе Administration -> Machine Learning Settings:

  1. CLIP (Smart Search):
  2. Вместо ViT-B-32__openai (FP32, ~600 МБ) укажите квантованный вариант: ViT-B-32__openai-quantized или оптимизированную мультиязычную модель XLM-Roberta-Large-Vit-B-16Plus-multilingual__quantized, если требуется поддержка сложных поисковых запросов на русском языке.
  3. Facial Recognition (Распознавание лиц):
  4. Базовый пайплайн buffalo_l базируется на тяжелой нейросети ResNet-50. Замените модель распознавания на легковесный пакет buffalo_s (MobileNet-backbone) или активируйте buffalo_l-quantized. Это снижает время детекции одного лица с 85 мс до 18–22 мс на процессорное ядро EPYC Zen 4.
  5. Execution Provider:
  6. По умолчанию движок инициализирует CPUExecutionProvider библиотеки ONNX Runtime.
  7. Для процессоров Intel доступна интеграция бэкенда OpenVINO, ускоряющая обработку сверточных графов за счет специфических микрокомандных оптимизаций конвейера. На платформах AMD EPYC со встроенными векторными расширениями AVX-512 наилучшую производительность демонстрирует нативный движок ONNX Runtime с квантованием INT8/FP16.

Сравнительный анализ моделей машинного обучения для инференса на CPU

Задача / Пайплайн Базовая модель (Default) Оптимизированная модель (Recommended) Execution Provider RAM на воркер (FP32 vs INT8) Latency на ядро (Zen 4, ms) Рекомендуемый профиль инстанса
CLIP Smart Search ViT-B-32__openai ViT-B-32__openai-quantized ONNX Runtime 1250 МБ $\to$ 420 МБ 145 мс $\to$ 48 мс 2 vCPU / 4 GB RAM
CLIP Multilingual xlm-roberta-base-ViT-B-32 nomic-ai/nomic-embed-vision-v1.5 ONNX Runtime 1800 МБ $\to$ 610 МБ 210 мс $\to$ 65 мс 4 vCPU / 8 GB RAM
Facial Recognition buffalo_l (ResNet-50) buffalo_s (MobileNet) ONNX Runtime / OpenVINO 850 МБ $\to$ 260 МБ 85 мс $\to$ 19 мс 2 vCPU / 4 GB RAM
Facial Recognition (HQ) antelopev2 buffalo_l-quantized ONNX Runtime 1400 МБ $\to$ 480 МБ 180 мс $\to$ 52 мс 4 vCPU / 8 GB RAM

Очереди транскодирования видео через FFmpeg

Транскодирование видеофайлов в веб-совместимые форматы выполняется процессами FFmpeg, инициируемыми контейнером immich-server. Без четко заданных лимитов одновременное кодирование видеопотоков высокого разрешения способно полностью заблокировать дисковую подсистему и вызвать скачки задержек при чтении базы данных.

Настройки кодирования задаются в веб-панели управления (Administration -> Settings -> Video Transcoding):

  • Выбор целевого кодека:
  • H.264 (libx264): Рекомендуемый выбор при эксплуатации Immich на VPS без выделенного аппаратного GPU (QuickSync/NVENC). Кодек обеспечивает универсальную совместимость со всеми браузерами и мобильными клиентами при минимальной нагрузке на vCPU.
  • VP9 (libvpx-vp9): Обеспечивает на 30–35% более высокую степень сжатия по сравнению с H.264 при том же визуальном качестве, но создает в 3–4 раза более высокую нагрузку на процессор при программном рендеринге. Использование VP9 на VPS без дискретной видеокарты оправдано исключительно в условиях жесткого дефицита дискового пространства.
  • Параметры сжатия и битрейта:
  • Preset: Установите значение faster или veryfast. Использование пресетов slow или veryslow дает выигрыш в размере файла не более 5–8%, увеличивая время удержания процессора на сотни процентов.
  • Constant Rate Factor (CRF): Оптимальный диапазон — 23–26. Шаг в сторону 28 дает заметную деградацию на темных градиентах, шаг к 18 раздувает объем итогового MP4 контейнера.
  • Разрешение: Ограничьте масштабирование параметром 1080p (1920x1080). Генерация 4K-превью для мобильных экранов нецелесообразна с точки зрения расхода NVMe-пространства.
  • Concurrency: Установите параметр Concurrency в значение 1 (максимум 2 для тарифов от 4–6 vCPU). Параллельное кодирование более чем одного видеопотока вызовет троттлинг фоновых очередей BullMQ.
# Пример аргументов компиляции конвейера FFmpeg, применяемых Immich в фоновом режиме:
ffmpeg -y -i input_4k.mov \
  -c:v libx264 \
  -preset faster \
  -crf 23 \
  -vf "scale=trunc(min(max(iw\,ih*dar)\,1920)/2)*2:trunc(ow/dar/2)*2" \
  -c:a aac \
  -b:a 128k \
  -movflags +faststart \
  output_transcoded.mp4

Флаг -movflags +faststart перемещает атомы метаданных moov в начало файла контейнера MP4, что позволяет браузеру начать потоковое воспроизведение видео сразу после получения первых килобайт данных по протоколу HTTP Range Requests, не дожидаясь полной загрузки файла из хранилища.

Высокая интенсивность операций записи временных чанков при кодировании видео создает серьезную нагрузку на дисковый ввод-вывод. Дисковая подсистема серверов tropic.host на базе серверных накопителей NVMe корпоративного класса с интерфейсом PCIe 4.0 обеспечивает производительность на случайных операциях 4K QD1 свыше 50 000 IOPS. Это исключает появление системных очередей iowait, гарантируя параллельную работу транскодирования, векторного поиска в pgvecto-rs и моментальную отдачу тяжелых оригиналов фото по симметричным BBR-каналам пропускной способностью до 1–10 Гбит/с.

Публикация в сеть: настройка реверс-прокси Caddy с автоматическим TLS и защита доступа

Для безопасной публикации стека Immich на VPS в открытый интернет категорически не рекомендуется выставлять порт веб-интерфейса контейнера напрямую во внешнюю сеть. Все входящие пользовательские сессии должны маршрутизироваться через высокопроизводительный обратный прокси-сервер, обеспечивающий аппаратную терминацию TLS-соединений, поддержку протоколов HTTP/2 и HTTP/3, а также изоляцию внутренних сервисов хранения данных. Использование современного веб-сервера Caddy исключает необходимость в эксплуатации внешних cron-скриптов и утилит certbot, полностью автоматизируя жизненный цикл сертификатов.

Привязка DNS и валидация домена

Перед развертыванием ingress-шлюза необходимо делегировать доменное имя на сетевой адрес хоста. В панели управления DNS-провайдера создается стандартная DNS A-record, связывающая целевой поддомен (например, photos.yourdomain.com) и публичный Static IPv4 инстанса. Чистые статические IP-адреса виртуальных серверов tropic.host с проверенной сетевой репутацией не содержатся в черных списках спам-фильтров и базах блокировок, что гарантирует мгновенное прохождение валидации по протоколу ACME со стороны удостоверяющего центра Let's Encrypt без задержек на обработку TLS-ALPN-01 или HTTP-01 проверок.

Конфигурация Caddyfile: потоковая передача и тюнинг тайм-аутов

В отличие от Nginx, где для передачи крупных файлов требуется вручную конфигурировать параметр client_max_body_size (по умолчанию ограниченный 1 МБ) и отключать дисковую буферизацию тела запроса (proxy_request_buffering off), веб-сервер Caddy изначально спроектирован на базе потоковой архитектуры (streaming architecture). Caddy транслирует входящие чанки медиафайлов напрямую в upstream без предварительного сохранения на локальный накопитель, исключая пиковое потребление оперативной памяти и износ NVMe при загрузке 4K-видео или RAW-исходников.

Тем не менее, передача архивов объемом 10–50 ГБ по нестабильным или медленным каналам связи неизбежно приводит к разрывам сессий при стандартных тайм-аутах ожидания ответа сокетов. Создайте файл конфигурации /etc/caddy/Caddyfile:

photos.yourdomain.com {
    # Сжатие текстовых ассетов (CSS/JS/JSON) алгоритмами Zstandard и Gzip
    encode zstd gzip

    # Глобальные тайм-ауты чтения и записи для предотвращения обрыва тяжелых медиапотоков
    timeouts {
        read_body 120m
        read_header 30s
        write 120m
        idle 5m
    }

    # Терминация TLS с использованием актуальных версий протоколов
    tls [email protected] {
        protocols tls1.2 tls1.3
    }

    # Маршрутизация на внутренний интерфейс Immich
    reverse_proxy 127.0.0.1:2283 {
        # Тюнинг HTTP-транспорта под длительные I/O операции
        transport http {
            read_timeout 120m
            write_timeout 120m
            response_header_timeout 60m
            keepalive 120s
            keepalive_idle_conns 100
        }

        # Буферизация ответа отключается для мгновенной отдачи потокового видео
        flush_interval -1
    }

    # Логирование запросов в структурированном JSON-формате
    log {
        output file /var/log/caddy/immich_access.log {
            roll_size 100mb
            roll_keep 7
            roll_keep_for 720h
        }
    }
}

Директива reverse_proxy в Caddy прозрачно обрабатывает протокол WebSockets: веб-сервер автоматически транслирует заголовки Connection: Upgrade и Upgrade: websocket. Это обеспечивает непрерывную синхронизацию фоновых очередей, передачу прогресса распознавания лиц и обновление таймлайна в мобильных клиентах Immich без ручной модификации HTTP-заголовков.

Благодаря включенным по умолчанию протоколам HTTP/2 и HTTP/3 (QUIC поверх транспортного протокола UDP) мобильное приложение сохраняет устойчивость соединения при переключении между сотовыми вышками LTE/5G и локальными сетями Wi-Fi. Отсутствие блокировки начала очереди (Head-of-Line Blocking) на транспортном уровне в сочетании с оптимизацией BBR-маршрутизации сетевого стека tropic.host гарантирует минимальный джиттер и максимальную скорость прокачки медиаконтента даже на каналах с трансграничной латентностью.

Примените конфигурацию и запустите сервис Caddy:

# Валидация синтаксиса файла конфигурации
caddy validate --config /etc/caddy/Caddyfile

# Перезапуск службы системным менеджером
sudo systemctl reload caddy

Изоляция сетевого периметра и защита внутренних портов

Критическая уязвимость развертывания контейнерных стеков в Linux заключается в поведении Docker Engine: по умолчанию Docker модифицирует цепочки PREROUTING в iptables, пробрасывая порты в обход правил пользовательского брандмауэра UFW. Если в docker-compose.yml указана запись ports: - "2283:2283", сервис будет доступен отовсюду по публичному IP-адресу в обход Caddy и шифрования TLS.

Для обеспечения изоляции в файле docker-compose.yml привязка внешних портов должна быть жестко ограничена локальным интерфейсом петли обратной связи (loopback):

services:
  immich-server:
    ports:
      # Привязка исключительно к локальному интерфейсу 127.0.0.1
      - "127.0.0.1:2283:2283"
    networks:
      - immich_network

  database:
    # База данных PostgreSQL не должна публиковать порты на хост-системе вовсе
    expose:
      - "5432"
    networks:
      - immich_network

  redis:
    # Redis изолирован внутри межконтейнерной виртуальной сети bridge
    expose:
      - "6379"
    networks:
      - immich_network

networks:
  immich_network:
    driver: bridge

Защитите периметр операционной системы с помощью межсетевого экрана UFW, закрыв прямой доступ ко всем технологическим портам и оставив открытыми только необходимые шлюзы:

# Установка базовой политики: блокировка всех входящих, разрешение исходящих
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Разрешение порта SSH (рекомендуется использовать нестандартный порт)
sudo ufw allow 22/tcp comment 'SSH Port'

# Разрешение входящего веб-трафика для Caddy (HTTP и HTTPS)
sudo ufw allow 80/tcp comment 'Caddy ACME HTTP'
sudo ufw allow 443/tcp comment 'Caddy HTTPS TLS'

# Обязательное открытие UDP-порта 443 для работы протокола HTTP/3 (QUIC)
sudo ufw allow 443/udp comment 'Caddy HTTP/3 QUIC'

# Активация брандмауэра
sudo ufw enable

Проверьте статус привязки сетевых сокетов, убедившись, что порты PostgreSQL (5432), Redis (6379) и порт внутреннего сервера Immich (2283) не прослушивают интерфейс 0.0.0.0:

# Аудит открытых сетевых сокетов ядра
sudo ss -tulpn | grep -E ':(2283|5432|6379|80|443)'

Вывод команды должен подтверждать, что порт 2283 слушает исключительно адрес 127.0.0.1, порты СУБД полностью изолированы внутри сетевых пространств имен Docker, а внешние интерфейсы сервера принимают подключения только по портам 80 и 443 (TCP/UDP) под управлением процесса Caddy.

Синхронизация мобильных устройств: настройка клиентов iOS и Android

После изоляции сокетов и маршрутизации защищенного HTTPS/QUIC-трафика через реверс-прокси первым шагом в веб-интерфейсе администратора блокируется открытая регистрация. По умолчанию инстанс позволяет регистрироваться любому пользователю, перешедшему по URL хоста:

  1. Перейдите в раздел Administration -> Settings -> User Settings.
  2. В секции Login & Registration переведите переключатель Allow new users to sign up в состояние Disabled.
  3. Сохраните изменения. Теперь доступ к серверу возможен только по учетным записям, явно созданным администратором (Administration -> Users).

Для мобильной синхронизации используется официальный клиент Immich Mobile App (доступен в App Store и Google Play). Авторизация приложения выполняется по публичному доменному адресу инстанса с обязательным указанием префикса API (https://photos.yourdomain.com/api). Для служебных скриптов пакетного импорта, резервного копирования и интеграции со сторонними сервисами используются постоянные токены доступа: в профиле пользователя (Account Settings -> API Keys) генерируются изолированные API Keys с гранулярными правами на чтение и запись, тогда как мобильный клиент удерживает сессию через отзывной JWT-токен устройства.

┌────────────────────────────────────────────────────────┐
│                   Immich Mobile App                    │
│    (iOS: BGAppRefreshTask / Android: ForegroundSvc)    │
└───────────────────────────┬────────────────────────────┘
                            │ Chunked Multipart Upload
                            │ TLS 1.3 / HTTP/3 (QUIC)
                            ▼
┌────────────────────────────────────────────────────────┐
│              Caddy Ingress (tropic.host)               │
│          BGP Anycast / TCP BBR / 1-10 Gbps             │
└───────────────────────────┬────────────────────────────┘
                            │ Reverse Proxy (127.0.0.1:2283)
                            ▼
┌────────────────────────────────────────────────────────┐
│                 Immich Server Core                     │
│    Master Storage (Bit-for-Bit RAW / HEIC / ProRes)    │
│      NVMe PCIe 4.0 (>50k IOPS) / KVM %st = 0.0%        │
└───────────────────────────┬────────────────────────────┘
                            │ Push Job Metadata
                            ▼
┌────────────────────────────────────────────────────────┐
│            Redis BullMQ -> Microservices               │
│       Async Transcoding (AVIF / WebP / H.265)          │
└────────────────────────────────────────────────────────┘

Особенности фоновой синхронизации на iOS

Песочница iOS жестко ограничивает время непрерывного выполнения фоновых процессов. Системный планировщик задач ядра Darwin распределяет кванты процессорного времени через интерфейсы BGAppRefreshTask и BGProcessingTask, опираясь на эвристику пользовательского поведения, остаток заряда аккумулятора и тип сетевого интерфейса.

Для обеспечения работы механизма Background Backup на устройствах Apple требуется принудительная конфигурация системных политик:

  • Разрешение фонового обновления: В системных настройках iOS перейдите в Настройки -> Immich и активируйте тумблер Обновление контента (Background App Refresh).
  • Доступ к медиатеке: Предоставьте приложению уровень прав Полный доступ к фото (ограниченный доступ исключает фоновое чтение новых ассетов).
  • Политика энергосбережения: Отключите системный «Режим низкого энергопотребления» (Low Power Mode). При активном энергосбережении iOS полностью блокирует запуск фоновых задач планировщика и замораживает открытые сетевые сокеты приложений в стеке TCP FIN.
  • Фоновые триггеры геолокации: В настройках резервного копирования Immich Mobile App активируйте опцию Background Backup и разрешите доступ к службам геолокации в режиме «Всегда». Immich использует системный триггер значительной смены координат (Significant Location Change API) — переключение между базовыми станциями сотовой связи или смена Wi-Fi BSSID будит процесс в памяти на 10–30 секунд, чего достаточно для проверки очереди и отправки пачки новых кадров.

Для первичной синхронизации объемных медиатек (от 50 ГБ) обход ограничений iOS сводится к удержанию приложения в активном состоянии: подключите смартфон к зарядному устройству, откройте Immich и активируйте встроенный экран удержания сессии (Keep screen awake), предотвращающий переход экрана в спящий режим до завершения очереди выгрузки.

Тонкая настройка на Android: отключение Doze Mode

В операционной системе Android фоновые демоны подавляются механизмом Doze Mode, введенным для оптимизации потребления аккумулятора. При выключенном экране и неподвижном устройстве Doze Mode отключает сетевой стек для неприоритетных процессов, блокирует системные таймеры AlarmManager и удерживает частичные блокировки сна (Partial WakeLocks).

Чтобы процесс загрузки не завершался операционной системой по тайм-ауту или OOM-сигналу:

  1. Исключение из списка оптимизации: Откройте Настройки -> Приложения -> Immich -> Батарея и выберите профиль Без ограничений (Unrestricted). Это исключает приложение из глобальных политик Battery Optimization.
  2. Снятие лимитов сетевого адаптера: В подменю Мобильные данные активируйте параметры Разрешить фоновую передачу данных и Неограниченный доступ при экономии трафика.
  3. Обход специфических надстроек вендоров: В оболочках Xiaomi (HyperOS/MIUI), Samsung (One UI) и BBK (ColorOS/OxygenOS) действуют агрессивные проприетарные демоны очистки оперативной памяти. В свойствах приложения Immich необходимо активировать флаг «Автозапуск» (Autostart), перевести контроль активности в режим «Нет ограничений», а в меню запущенных приложений повесить постоянный «замок» на карточку процесса.

После этих настроек Immich Mobile App разворачивает устойчивый системный сервис с постоянным уведомлением в системной шторке (ForegroundService), который удерживает сетевой сокет активным и выполняет поблочную передачу файлов без разрывов соединения даже при глубоком сне устройства.

Обработка оригинальных медиаформатов без серверного сжатия

Архитектура Immich построена на строгом сохранении контрольных сумм исходных файлов: сервер гарантирует побайтовую идентичность входящего контента оригиналу (bit-for-bit storage). Серверное перекодирование с потерями качества при сохранении мастер-копий исключено.

Клиентское приложение и серверный бэкенд обрабатывают сложные нативные форматы без предварительной деградации:

  • Live Photos: Ассет выгружается в виде неразрывной связки двух независимых объектов — статичного контейнера HEIC (или JPEG) и синхронизированного видеоролика высокого разрешения в формате QuickTime .MOV. Сервер регистрирует их в базе PostgreSQL единым логическим ресурсом, сохраняя возможность воспроизведения анимации и звука при просмотре.
  • Apple ProRes и расширенные кодеки: Тяжелые видеопотоки ProRes (битрейт 100–400+ Мбит/с) пересылаются на сервер в исходных контейнерах. Сервер Immich принимает поток через чанковый multipart-запрос без применения ffmpeg-транскодирования «на лету» к мастер-файлу.
  • Изображения сверхвысокого разрешения и RAW-файлы: Проприетарные несжатые слепки сенсоров (Apple ProRAW DNG, Canon CR3, Sony ARW, Nikon NEF) сохраняются в хранилище без модификации метаданных EXIF/XMP.

Процессы генерации превью (WebP для веб-интерфейса, AVIF для списков миниатюр) и легковесных видеопрокси (H.264/H.265) вынесены в асинхронные очереди очередей BullMQ под управлением контейнера immich-microservices.

Пакетная передача тяжелых ProRes-роликов и серийной съемки в RAW создает пиковые нагрузки на подсистему дискового ввода-вывода и сетевую карту хоста. При эксплуатации Immich на VPS серверные NVMe-накопители корпоративного класса (PCIe 4.0) облачной платформы tropic.host выдерживают интенсивные случайные операции записи сотен параллельных фрагментов данных со скоростью свыше 50 000 IOPS (4K QD1) без температурного троттлинга. Аппаратная виртуализация KVM без оверселлинга гарантирует полное отсутствие процессорных задержек планировщика (CPU Steal Time %st = 0.0%) в моменты параллельной перекладки метаданных в базу PostgreSQL, а симметричные аплинки 1–10 Гбит/с со стеком TCP BBR на европейских маршрутах (Франкфурт, Амстердам) обеспечивают предельную пропускную способность мобильного канала без сброса TCP-окон при переключении сотовых сетей 5G/LTE на локальный Wi-Fi.

Регламент Disaster Recovery: автоматическое резервное копирование и верификация восстановления

Прямое копирование каталога PGDATA с работающего сервера через rsync или создание моментальных файловых снимков неизбежно приводит к повреждению базы данных Immich. В процессе транзакционной активности СУБД PostgreSQL модифицирует shared buffers в оперативной памяти и синхронизирует страницы с NVMe-накопителем асинхронно через контрольные точки (checkpoints). Копирование файлов СУБД «на лету» захватывает разорванные страницы (torn pages) и незафиксированные транзакции из Write-Ahead Logging (WAL).

Ситуация становится критической из-за расширения pgvector: графовые векторные индексы HNSW (hierarchical navigable small world) оперируют многослойными структурами смежности между эмбеддингами лиц и текстовых запросов мультимодального поиска CLIP. Несогласованный слепок памяти приводит к повреждению графа связей. При обращении immich-server к испорченному индексу сессия PostgreSQL аварийно завершается с ошибкой SIGSEGV или бесконечно блокирует воркеры в системных вызовах epoll_wait.

Архитектура резервирования: транзакционный дамп и дедуплицированный S3-снапшот

Надежная стратегия Disaster Recovery при эксплуатации Immich на VPS требует строгого разделения типов данных на две изолированные очереди: 1. Реляционное состояние и векторное пространство: логический дамп кластера СУБД через утилиту pg_dumpall. Создание дампа в единой транзакции гарантирует атомарность метаданных, системных ролей и бинарных представлений векторных колонок vector(512) / vector(768). 2. Медиатека (Blob Storage): оригиналы фото, видеоконтейнеры, миниатюры и кэш машинного обучения. Для сотен гигабайт или терабайт данных регулярные полные копии нерациональны: применяется инкрементальный блочный бэкап с дедупликацией на базе CDC (Content Defined Chunking).

┌────────────────────────────────────────────────────────────────────────┐
│                        Хост Immich на VPS                              │
│                                                                        │
│  ┌───────────────────────┐             ┌────────────────────────────┐  │
│  │  immich_postgres      │             │  /mnt/storage/immich       │  │
│  │  (PostgreSQL+Vector)  │             │  (RAW / ProRes / DNG)      │  │
│  └──────────┬────────────┘             └─────────────┬──────────────┘  │
│             │ pg_dumpall (pipe)                      │ CDC chunking    │
│             ▼                                        ▼                 │
│  ┌───────────────────────┐             ┌────────────────────────────┐  │
│  │ GPG encryption        │             │ Restic                     │  │
│  │ (AES-256 symmetric)   │             │ (Client-side AES-256 GCM)  │  │
│  └──────────┬────────────┘             └─────────────┬──────────────┘  │
└─────────────┼────────────────────────────────────────┼─────────────────┘
              │ TLS 1.3 / TCP BBR                      │ Rclone S3 Backend
              ▼                                        ▼
┌────────────────────────────────────────────────────────────────────────┐
│             Удаленное объектное S3-хранилище (Offsite Backup)          │
│    Bucket: immich-db-dumps              Bucket: immich-restic-repo     │
└────────────────────────────────────────────────────────────────────────┘

Для шифрования дампа применяется утилита GPG encryption с симметричным шифром AES-256. Для медиафайлов используется Restic, отправляющий данные в удаленное объектное хранилище напрямую или через абстракцию Rclone S3.

Автоматизация резервного копирования

Все операции агрегируются в скрипте /usr/local/bin/immich-backup.sh. Скрипт перехватывает дамп СУБД через пайп без сохранения промежуточных незашифрованных файлов на локальный диск, исключая утечку данных при компрометации локальных точек монтирования.

#!/usr/bin/env bash
set -Eeuo pipefail

# Конфигурация путей и переменных окружения
COMPOSE_DIR="/opt/immich"
BACKUP_DIR="/var/backups/immich"
PASSPHRASE_FILE="/root/.immich_gpg_pass"
RESTIC_ENV_FILE="/root/.restic_s3.env"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")

# Загрузка учетных данных S3 (RESTIC_REPOSITORY, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, RESTIC_PASSWORD)
source "${RESTIC_ENV_FILE}"

mkdir -p "${BACKUP_DIR}"

# 1. Транзакционный дамп СУБД с pgvector и ролями
echo "[*] Создание консистентного дампа PostgreSQL..."
docker compose -f "${COMPOSE_DIR}/docker-compose.yml" exec -T database \
    pg_dumpall --clean --if-exists -U postgres \
    | gzip -9 \
    | gpg --batch --yes --symmetric --cipher-algo AES256 --passphrase-file "${PASSPHRASE_FILE}" \
    > "${BACKUP_DIR}/immich_db_${TIMESTAMP}.sql.gz.gpg"

# 2. Передача шифрованного дампа СУБД в S3 через Rclone
echo "[*] Ротация дампа в S3-хранилище через Rclone S3..."
rclone copyto "${BACKUP_DIR}/immich_db_${TIMESTAMP}.sql.gz.gpg" "s3-backup:immich-backups/db/immich_db_${TIMESTAMP}.sql.gz.gpg" --fast-list

# Удаление локальных дампов старше 3 суток
find "${BACKUP_DIR}" -name "immich_db_*.sql.gz.gpg" -mtime +3 -delete

# 3. Инкрементальный снапшот медиа-библиотеки через Restic
echo "[*] Запуск инкрементальной синхронизации медиатеки..."
restic backup \
    --exclude="${COMPOSE_DIR}/library/thumbs" \
    --exclude="${COMPOSE_DIR}/library/encoded-video" \
    "${COMPOSE_DIR}/library"

# 4. Ротация снимков Restic
echo "[*] Применение политики ретеншена Restic..."
restic forget \
    --keep-daily 7 \
    --keep-weekly 4 \
    --keep-monthly 6 \
    --prune

echo "[+] Резервное копирование завершено успешно."

Каталоги миниатюр (thumbs) и перекодированных прокси-видео (encoded-video) исключаются из инкрементального снимка флагом --exclude. Они представляют собой детерминированные производные артефакты, которые при необходимости генерируются заново сервисом immich-machine-learning. Их исключение сокращает объем репозитория Restic на 30–45% и минимизирует паразитные операции случайного ввода-вывода.

Запуск процесса регламентируется демоном Cron. Блокировка flock предотвращает наложение повторных сессий бэкапа при пиковом объеме новых медиафайлов:

# /etc/cron.d/immich-backup
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

0 3 * * * root /usr/bin/flock -n /var/run/immich-backup.lock /usr/local/bin/immich-backup.sh >> /var/log/immich-backup.log 2>&1

Пошаговый протокол восстановления (Recovery Runbook)

План аварийного восстановления (Disaster Recovery) описывает шаги по воссозданию сервиса с нуля на чистом инстансе KVM VPS после полной деградации исходного хоста.

Шаг 1: Развертывание базового окружения ноды

На новой KVM-ноде платформы tropic.host с чистым образом Ubuntu 24.04 LTS устанавливаются Docker Engine, Restic, Rclone и GPG. Аппаратная виртуализация KVM без оверселлинга процессорных ресурсов (%st = 0.0%) критически важна на этапе распаковки: декомпрессия многогигабайтных дампов и дешифрование криптоконтейнеров утилизируют 100% вычислительных инструкций vCPU.

apt-get update && apt-get install -y docker.io docker-compose-v2 restic rclone gnupg gzip
mkdir -p /opt/immich/library /root/.backup-keys

На ноду загружаются парольные файлы /root/.immich_gpg_pass и конфигурация /root/.restic_s3.env.

Шаг 2: Развертывание инфраструктурных контейнеров

В /opt/immich/docker-compose.yml переносится целевой compose-файл проекта. Для предотвращения конфликтов во время наполнения базы приложение запускается в режиме изоляции — поднимаются только СУБД и Redis:

cd /opt/immich
docker compose up -d database redis

# Ожидание готовности сокета PostgreSQL
until docker compose exec -T database pg_isready -U postgres; do
    echo "Ожидание готовности сокета PostgreSQL..."
    sleep 2
done

Шаг 3: Восстановление дампа СУБД и векторного пространства

Из S3-хранилища скачивается последний дамп, расшифровывается через GPG и напрямую передается в сокет СУБД:

source /root/.restic_s3.env
LATEST_DUMP=$(rclone lsf "s3-backup:immich-backups/db/" | sort | tail -n 1)

echo "[*] Загрузка и распаковка дампа: ${LATEST_DUMP}"
rclone cat "s3-backup:immich-backups/db/${LATEST_DUMP}" \
    | gpg --batch --decrypt --passphrase-file /root/.immich_gpg_pass \
    | gunzip -c \
    | docker compose exec -T database psql -U postgres

После применения дампа обязательна принудительная реиндексация таблиц с векторными эмбеддингами. При массовой вставке данных структуры HNSW могут оказаться несбалансированными:

docker compose exec -T database psql -U postgres -d immich -c "
REINDEX TABLE CONCURRENTLY smart_search;
REINDEX TABLE CONCURRENTLY asset_faces;
VACUUM ANALYZE smart_search;
VACUUM ANALYZE asset_faces;
"

Шаг 4: Восстановление медиа-библиотеки через Restic

С помощью Restic восстанавливается файловое дерево оригинальных медиафайлов:

source /root/.restic_s3.env
echo "[*] Восстановление медиатеки из репозитория Restic..."
restic restore latest \
    --target /opt/immich/library \
    --verify

chown -R 1000:1000 /opt/immich/library

Серверные NVMe-накопители корпоративного класса (PCIe 4.0) облачной платформы tropic.host обеспечивают линейную скорость записи свыше 2500–3500 МБ/с, благодаря чему восстановление репозиториев объемом в сотни гигабайт упирается исключительно в пропускную способность сетевого интерфейса. Симметричные каналы 1–10 Гбит/с с алгоритмом TCP BBR в дата-центрах Франкфурта и Амстердама позволяют выкачивать снапшот из объектного хранилища на предельной скорости канала без сброса пакетов и джиттера.

Валидация целостности данных (Data Integrity) и контрольный прогон

Перед запуском основного стека Immich выполняется аудит целостности связей базы данных и дисковой подсистемы:

  1. Проверка расширением amcheck: исключает физические повреждения B-tree и системных каталогов СУБД: bash docker compose exec -T database psql -U postgres -d immich -c " CREATE EXTENSION IF NOT EXISTS amcheck; SELECT bt_index_check(c.oid), c.relname FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relam = 403 AND n.nspname = 'public'; "
  2. Контроль физического присутствия ассетов на NVMe-диске: верифицирует соответствие путей в PostgreSQL реальным файлам в /opt/immich/library: bash docker compose exec -T database psql -U postgres -d immich -t -A -c " SELECT \"originalPath\" FROM assets LIMIT 10; " | while read -r file_path; do if [ ! -f "/opt/immich/library/${file_path}" ]; then echo "[ERROR] Файл отсутствует на диске: ${file_path}" exit 1 fi done echo "[+] Проверка согласованности файловой системы и СУБД пройдена."
  3. Запуск рабочего стека микросервисов: bash cd /opt/immich docker compose up -d
  4. Верификация клиентской синхронизации: На мобильном клиенте выполняется принудительное обновление таймлайна (pull-to-refresh). Приложение выполняет HTTP-запрос GET /api/sync/delta-sync, сопоставляя локальный кэш с восстановленным состоянием на сервере. Контрольный маркер корректности — генерация запроса без дублирования существующих фотопотоков и успешное выполнение векторного поиска по распознанным лицам через форму веб-интерфейса без ошибок в логах immich-server.

Диагностика и устранение типовых сбоев при эксплуатации Immich

Эксплуатация микросервисного стека Immich на VPS сопряжена с пиковыми нагрузками на дисковую подсистему и оперативную память в моменты фонового инжеста медиаданных. Некорректная изоляция контейнеров, гонки блокировок при одновременной синхронизации или сбои версионирования схем СУБД приводят к каскадным авариям сервиса. Ниже разобран пошаговый регламент выявления и устранения критических инцидентов.

                  ┌───────────────────────────────┐
                  │ Инцидент: Падение / Зависание │
                  └───────────────┬───────────────┘
                                  │
         ┌────────────────────────┴────────────────────────┐
         ▼                                                 ▼
┌─────────────────┐                               ┌─────────────────┐
│ Exit Code: 137  │                               │ Exit Code: 0 /  │
│ (SIGKILL)       │                               │ Worker Timeout  │
└────────┬────────┘                               └────────┬────────┘
         │                                                 │
         ▼                                                 ▼
[ dmesg: OOM Killer ]                            [ docker logs inspect ]
  ├─ immich_machine_learning                       ├─ Redis Queue Stalled
  └─ Лимиты cgroups v2 / swap                      ├─ FFmpeg error / pipe fail
                                                   ├─ Database Deadlock
                                                   └─ Storage Quota / EACCES

1. Аварийное завершение immich-machine-learning по OOM Killer

При обработке RAW-снимков высокого разрешения или параллельной генерации CLIP-эмбеддингов контейнер машинного обучения резко наращивает потребление памяти. Если объем resident set size (RSS) процесса превышает лимит cgroup или физический объем RAM ноды, ядро Linux отправляет процессу сигнал SIGKILL.

Диагностика системных журналов

Проверьте факт срабатывания механизма OOM Killer в кольцевом буфере ядра и статус завершения контейнера:

# Поиск событий вытеснения по памяти в системном журнале ядра
dmesg -T | grep -E -i "oom[-_]killer|killed process.*python3"

# Проверка кода завершения контейнера через Docker CLI
docker inspect immich_machine_learning --format 'ExitCode: {{.State.ExitCode}}, OOMKilled: {{.State.OOMKilled}}'

Код ошибки ExitCode: 137 в связке с OOMKilled: true однозначно подтверждает принудительное завершение процесса ядром. Дополнительно вывод docker logs immich_machine_learning --tail 50 покажет обрыв выполнения задачи на этапе инференса модели (например, transformers или onnxruntime).

Устранение и жесткая изоляция cgroups

Для предотвращения падения соседних микросервисов жестко ограничьте ресурсы ML-воркера в docker-compose.yml, задав параметры limits и reservations:

services:
  immich-machine-learning:
    image: ghcr.io/immich-app/immich-machine-learning:release
    deploy:
      resources:
        limits:
          cpus: '3.0'
          memory: 4096M
        reservations:
          memory: 2048M
    environment:
      - MACHINE_LEARNING_WORKERS=1
      - MACHINE_LEARNING_CACHE_FOLDER=/cache

Параметр MACHINE_LEARNING_WORKERS=1 снижает параллелизм обработки до одного потока, стабилизируя кривую потребления RAM ценой незначительного замедления фонового сканирования.

При выборе конфигурации для развертывания Immich на VPS критически важно исключить эффект «шумных соседей»: на стандартных тарифах облачных платформ с агрессивным оверселлингом всплеск чужой нагрузки приводит к росту CPU Steal Time (%st) и принудительному сбросу страниц памяти гипервизором. На KVM-инфраструктуре tropic.host за счет честной аппаратной виртуализации параметр %st строго равен 0.0%, что исключает спонтанные падения ML-пайплайна из-за скрытого дефицита процессорных квантов и памяти хост-машины.


2. Зависание очередей транскодирования в Redis и ошибки FFmpeg

Immich использует Redis в качестве брокера очередей BullMQ для управления задачами кодирования видео, генерации превью и извлечения метаданных. Некорректный медиаконтейнер (битый заголовок MP4, поврежденный поток HEVC/H.265) вызывает аварийный сбой внешнего бинарника ffmpeg, блокируя рабочий поток воркера со статусом Redis Queue Stalled.

Анализ логов сервера

Симптомы сбоя фиксируются в журнале основного контейнера:

docker logs immich_server --since 30m | grep -E -i "FFmpeg error|stalled|Job failed"

Типовой маркер ошибки:

[Microservices:QueueService] Error: Job stalled more than maxStalledCount: generate-video-preview:104928
[Microservices:VideoTranscodingService] FFmpeg error: Error while decoding stream #0:0: Invalid data found when processing input

Очистка зависших задач через Redis CLI

Если очередь перешла в заблокированное состояние, воркеры перестают брать новые задачи, а в веб-интерфейсе таймлайн застывает. Для восстановления работы выполните точечную зачистку поврежденных ключей BullMQ без потери общей конфигурации:

# Подключение к консоли Redis внутри compose-сети
docker compose exec -T redis redis-cli -p 6379 PING

# Проверка количества зависших задач транскодирования
docker compose exec -T redis redis-cli -p 6379 LLEN "bull:videoTranscoding:stalled"

# Аварийная очистка зависшей очереди и разблокировка воркеров
docker compose exec -T redis redis-cli -p 6379 EVAL '
local keys = redis.call("keys", "bull:videoTranscoding:*")
for i, name in ipairs(keys) do
    if string.match(name, ":stalled") or string.match(name, ":active") then
        redis.call("del", name)
    end
end
return "OK"
' 0

После сброса поврежденных идентификаторов перезапустите микросервис обработки очередей:

docker compose restart immich_server

3. Сбои миграций PostgreSQL и устранение блокировок (Database Deadlock)

При переходе между мажорными версиями контейнеров Immich запускает автоматические миграции структуры таблиц через TypeORM. Если в момент перезапуска стек продолжал обрабатывать входящий трафик от фоновых клиентов, транзакция миграции ALTER TABLE встает в очередь ожидания эксклюзивной блокировки AccessExclusiveLock, провоцируя состояние Database Deadlock.

Диагностика блокировок в pg_stat_activity

Определите заблокированные транзакции и PID блокирующего процесса:

docker compose exec -T database psql -U postgres -d immich -c "
SELECT 
    blocked_locks.pid     AS blocked_pid,
    blocked_activity.usename  AS blocked_user,
    blocking_locks.pid    AS blocking_pid,
    blocking_activity.usename AS blocking_user,
    blocked_activity.query    AS blocked_statement,
    blocking_activity.query   AS current_statement_in_blocking_process
FROM  pg_catalog.pg_locks         blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks         blocking_locks 
    ON blocking_locks.locktype = blocked_locks.locktype
    AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database
    AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
    AND
## Диагностика и устранение типовых сбоев при эксплуатации Immich

Штатная эксплуатация микросервисного стека Immich сопровождается пиковыми всплесками утилизации дискового ввода-вывода (IOPS) и оперативной памяти в моменты первичного сканирования ассетов, параллельного ML-анализа и фонового транскодирования. В таких условиях рассинхронизация контейнеров, исчерпание пулов соединений СУБД или повреждение бинарных медиапотоков приводят к остановке конвейера обработки.

---

### 1. Аварийное завершение immich-machine-learning: вытеснение по OOM Killer

При генерации многомерных векторных представлений (CLIP-эмбеддингов) и поиске лиц с использованием нейросетевых моделей инференс-воркер аллоцирует значительные объемы оперативной памяти под веса моделей и тензорные матрицы. Если суммарный объем Resident Set Size (RSS) процесса превышает лимит cgroup или физический объем RAM хоста, активируется механизм ядра Linux — `OOM Killer`.

#### Инструментальная идентификация сбоя

Факт принудительного уничтожения процесса ядром фиксируется в кольцевом буфере `dmesg`, а код завершения контейнера проверяется через Docker Engine:

```bash
# Поиск сообщений ядра об уничтожении процесса инференса по нехватке памяти
dmesg -T | grep -E -i "oom[-_]killer|out of memory.*python"

# Проверка кода завершения контейнера машинного обучения
docker inspect immich_machine_learning --format 'ExitCode: {{.State.ExitCode}}, OOMKilled: {{.State.OOMKilled}}'

Статус OOMKilled: true и ExitCode: 137 (128 + сигнал 9 SIGKILL) свидетельствуют о превышении доступного пула RAM. Дополнительно команда docker logs immich_machine_learning --tail 30 зафиксирует мгновенный обрыв выполнения задачи без генерации Python-стектрейса.

Локализация лимитов cgroups v2 и оптимизация потоков

Для предотвращения вытеснения критических сервисов (базы данных и Redis) ML-контейнер изолируется жесткими лимитами в файле docker-compose.yml:

services:
  immich-machine-learning:
    image: ghcr.io/immich-app/immich-machine-learning:release
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 4096M
        reservations:
          memory: 2048M
    environment:
      - MACHINE_LEARNING_WORKERS=1
      - MACHINE_LEARNING_CACHE_FOLDER=/cache
      - TRANSFORMERS_CACHE=/cache

Снижение числа воркеров MACHINE_LEARNING_WORKERS=1 предотвращает параллельную загрузку дубликатов тяжелых весов в адресное пространство разных процессов, стабилизируя потребление памяти на отметке 2.2–3.1 ГБ даже при анализе несжатых 48-мегапиксельных RAW-кадров.

Для надежной работы ресурсоемких моделей развертывать Immich на VPS необходимо исключительно на базе гипервизора KVM без оверселлинга RAM и vCPU. На облачной платформе tropic.host за счет изолированного выделения ядер AMD EPYC и Ryzen 9 показатель CPU Steal Time (%st) строго равен 0.0%, что исключает спонтанные падения ML-пайплайна из-за скрытой нехватки ресурсов хоста.


2. Зависание очередей транскодирования в Redis и FFmpeg error

Immich передает задачи на извлечение EXIF-метаданных, генерацию webp-миниатюр и транскодирование видео в Redis через библиотеку BullMQ. Поврежденный контейнер медиафайла (битый заголовок Moov atom, фрагментированный HEVC-поток) вызывает аварийную остановку кодека:

[Microservices:VideoTranscodingService] FFmpeg error: Invalid data found when processing input
[Microservices:QueueService] Error: Job stalled more than maxStalledCount: transcode-video:49102

Когда количество перезапусков превышает порог maxStalledCount, очередь переходит в статус Redis Queue Stalled, прекращая обработку последующих ассетов.

Диагностика и очистка заблокированных очередей

Инспектирование аномалий выполняется через чтение журналов микросервиса:

docker logs immich_server --since 1h | grep -E -i "FFmpeg error|Job stalled"

Если очередь транскодирования заблокирована, сбросьте состояние активных и зависших задач через внутренний интерфейс redis-cli, не разрушая сессии пользователей:

# Проверка длины очереди зависших заданий транскодирования
docker compose exec -T redis redis-cli LLEN "bull:videoTranscoding:stalled"

# Точечное удаление заблокированных дескрипторов задач через Lua-скрипт
docker compose exec -T redis redis-cli EVAL '
local stalled = redis.call("keys", "bull:videoTranscoding:stalled*")
for _, k in ipairs(stalled) do redis.call("del", k) end
local active = redis.call("keys", "bull:videoTranscoding:active*")
for _, k in ipairs(active) do redis.call("del", k) end
return "CLEARED"
' 0

Альтернативный способ — переход в панель управления Administration -> Jobs, выбор пункта Video Transcoding и нажатие команды Clear Failed / Stalled Jobs. После очистки очередей перезапустите управляющий сервер:

docker compose restart immich_server

3. Сбои миграций PostgreSQL и Database Deadlock при обновлении

Мажорные обновления схемы данных Immich требуют блокировок уровней ShareUpdateExclusiveLock или AccessExclusiveLock. Если в момент наката миграций мобильные клиенты активно отправляют бинарные чанки через REST API, транзакция DDL встает в очередь ожидания за длительными UPDATE-запросами, провоцируя возникновение взаимоблокировки — Database Deadlock.

Выявление конкурирующих блокировок в pg_stat_activity

При зависании миграции определите заблокированные и блокирующие процессы в СУБД:

docker compose exec -T database psql -U postgres -d immich -c "
SELECT 
    pid, 
    pg_blocking_pids(pid) AS blocked_by, 
    wait_event_type, 
    wait_event, 
    state, 
    query 
FROM pg_stat_activity 
WHERE cardinality(pg_blocking_pids(pid)) > 0;
"

Для принудительного освобождения ресурсов завершите блокирующий процесс без остановки СУБД:

# Завершение сессии по обнаруженному PID (например, 1482)
docker compose exec -T database psql -U postgres -d immich -c "SELECT pg_terminate_backend(1482);"

Регламент отката и ручной фиксации версии

Если миграция завершилась фатальной ошибкой и нарушила целостность схемы TypeORM, контейнер immich_server циклически перезапускается. Выполните откат на предыдущую стабильную версию:

  1. Остановите микросервисы приложения, зафиксировав работу СУБД: bash docker compose stop immich_server immich_machine_learning
  2. Зафиксируйте в .env предыдущий номер релиза вместо плавающего тега :release (например, IMMICH_VERSION=v1.105.1).
  3. Восстановите согласованный дамп базы данных, снятый перед обновлением: bash gunzip -c /opt/immich/backups/immich_db_pre_upgrade.sql.gz | \ docker compose exec -T database psql -U postgres -d immich
  4. Выполните холодный запуск контейнеров с повторной валидацией схемы: bash docker compose up -d docker logs immich_server -f --tail 100

4. Конфликты блокировок файлов, Storage Quota и разграничение прав доступа

При одновременной синхронизации медиатеки с нескольких смартфонов на уровне файловой системы возникают коллизии параллельной записи в один каталог. В журналах docker logs immich_server это проявляется кодами EEXIST, EBUSY или сбоями создания превью из-за нехватки дискового пространства (ENOSPC).

Аудит дискового пространства и дескрипторов (Storage Quota)

Проверьте остаток доступного пространства и лимиты свободных дескрипторов (inodes) в точке монтирования хранилища:

# Проверка свободного места и inodes на накопителе
df -h /opt/immich/library
df -i /opt/immich/library

Если дисковая квота исчерпана (Storage Quota 100%), демон Docker и PostgreSQL автоматически переводят файловые дескрипторы в режим Read-Only, что приводит к каскадному падению всего стека.

Для стабильного инжеста десятков тысяч ассетов инфраструктура должна базироваться на производительных серверных NVMe PCIe 4.0. На платформе tropic.host дисковые пулы обеспечивают скорость случайного чтения 4K QD1 свыше 50 000 IOPS, исключая деградацию задержек (latency p99 < 1.5ms) даже при одновременной выгрузке 4K-видеопотоков по симметричным BBR-каналам пропускной способностью до 10 Гбит/с.

Нормализация прав доступа POSIX (UID/GID)

Смешение пользователей хост-системы и внутренних процессов контейнеров приводит к ошибкам EACCES: permission denied. Контейнеры Immich исполняются от пользователя без привилегий с идентификаторами UID=1000 и GID=1000.

Восстановите корректного владельца и маску прав на директории загрузки и миниатюр:

# Сброс владельца директорий медиатеки на UID/GID контейнера
chown -R 1000:1000 /opt/immich/library /opt/immich/thumbs /opt/immich/profile

# Рекурсивная установка прав: rwx для директорий, rw для файлов
find /opt/immich/library /opt/immich/thumbs -type d -exec chmod 750 {} +
find /opt/immich/library /opt/immich/thumbs -type f -exec chmod 640 {} +

Сводная матрица типовых неисправностей Immich

В таблице сгруппированы эксплуатационные сбои, их диагностические маркеры в журналах ядра и контейнеров, а также целевые сценарии восстановления сервиса.

Тип сбоя Диагностический маркер (docker logs / kernel) Корневая причина (Root Cause) Алгоритм устранения
Падение ML-контейнера ExitCode: 137, OOMKilled: true, сообщения dmesg: oom-killer Исчерпание физической RAM при инференсе нейросетей CLIP / Face Detection Добавить лимиты memory: 4096M в compose-файл, установить MACHINE_LEARNING_WORKERS=1
Зависание очередей BullMQ Redis Queue Stalled: transcode-video, Job stalled more than maxStalledCount Необрабатываемый битый медиаконтейнер, вызвавший сбой фонового процесса Очистить ключи bull:videoTranscoding:* через Redis CLI или сбросить очередь в веб-панели
Авария кодирования видео FFmpeg error: Invalid data found when processing input, code 1 Повреждение кодека h.264/h.265 или несовместимый аудиокодек в исходном ассете Найти ассет по ID в логе, исключить из очереди транскодирования, обновить профиль FFmpeg
Взаимоблокировка СУБД Database Deadlock, ERROR: deadlock detected, canceling statement Конкурирующие транзакции DDL-миграций и параллельного клиентского инжеста Завершить блокирующие процессы функцией pg_terminate_backend(), ограничить входящие запросы
Отказ DDL-миграции QueryFailedError: relation already exists или column does not exist Попытка обновления через несколько мажорных версий в обход миграционного пути Зафиксировать предыдущую версию образа, восстановить дамп СУБД, обновляться последовательными релизами
Отказ записи ассетов EACCES: permission denied, open failed /upload/library Несоответствие прав доступа на томах хоста (файлы созданы под root:root) Выполнить chown -R 1000:1000 и скорректировать права chmod 750 на каталоги медиатеки
Аварийный Read-Only Storage Quota exceeded, ENOSPC: no space left on device Заполнение дискового раздела медиафайлами либо исчерпание пула свободных inodes Расширить дисковый том NVMe, очистить временные файлы /tmp, выполнить docker image prune -a

Часто задаваемые вопросы (FAQ)

Чем Immich на VPS превосходит Nextcloud Photos и Photoprism?

Immich изначально спроектирован как прямой аналог Google Photos с нативными мобильными клиентами, быстрой поддержкой фоновой синхронизации, продвинутым распознаванием лиц и семантическим поиском на базе CLIP. Nextcloud Photos значительно уступает в производительности и отзывчивости интерфейса при библиотеках свыше 50 000 фото, а Photoprism требует платной подписки для многопользовательского режима и не имеет полноценного мобильного клиента для двусторонней синхронизации.

Какой объем vCPU и оперативной памяти необходим для стабильной работы Immich?

Минимальная рабочая конфигурация для одного пользователя составляет 2 vCPU и 4 ГБ RAM с обязательным выделением 2–4 ГБ Swap. Для комфортной работы семьи из 3–4 человек с активным машинным обучением и генерацией превью рекомендуется KVM VPS с 4 vCPU, 8 ГБ RAM и накопителем NVMe PCIe 4.0, исключающим дисковые задержки при случайных операциях ввода-вывода.

Можно ли хранить медиатеку Immich на внешнем S3-хранилище?

Да, Immich поддерживает монтирование внешних S3-совместимых объектных хранилищ через механизмы rclone/goofys или прямое подключение внешних библиотек (External Libraries) в режиме чтения. Однако для базы данных PostgreSQL, кэша Redis и директории thumbnails обязательно должен использоваться локальный высокоскоростной NVMe накопитель.

Как безопасно обновить Immich до новой версии без потери данных?

Перед каждым обновлением необходимо создать дамп базы данных командой docker exec -t immich_postgres pg_dumpall -c -U postgres > backup.sql. Затем ознакомьтесь с Release Notes на GitHub на предмет Breaking Changes, выполните docker compose pull и перезапустите контейнеры через docker compose up -d. Миграции базы данных выполняются автоматически при старте.

Почему для Immich не подходит контейнерная виртуализация OpenVZ/LXC?

В контейнерных средах OpenVZ и LXC ядро хоста разделяется между всеми жильцами, что блокирует доступ к современным инструкциям процессора (AVX2/AVX-512), ограничивает тюнинг параметров ядра (sysctl, cgroups v2) и часто приводит к принудительному завершению ML-процессов системным OOM Killer хост-машины из-за оверселлинга памяти.