Tropic Host

Развертывание Paperless-ngx на KVM VPS в Docker: автоматическое OCR-распознавание документов и S3 бэкапы

33 мин чтения
Tropic

Краткий вывод: Для стабильной работы Paperless-ngx в Docker-окружении на KVM-виртуализации с активным пайплайном Tesseract OCR требуется минимум 2 vCPU с нулевым оверселлингом (%st = 0.0%), 4 ГБ RAM с квотированием через cgroups v2 для предотвращения срабатывания OOM Killer при рендеринге 300 DPI сканов, а также от 40 ГБ NVMe (не менее 5 000 IOPS на 4K QD1). Сетевой стек требует аплинка от 100 Мбит/с с алгоритмом TCP BBR и обязательной терминацией TLS 1.3 для пакетного ingest-импорта документов по WebDAV/REST API и фоновой выгрузки снапшотов в объектное S3-хранилище по HTTPS. Вынос очередей задач в Redis и изоляция транзакций в PostgreSQL гарантируют latency p99 веб-интерфейса в пределах 150 мс без деградации дисковой подсистемы во время параллельной работы OCR-воркеров.


Содержание

  1. Аппаратные требования и сайзинг KVM VPS под интенсивный OCR-процессинг
  2. Подготовка сервера: тюнинг ядра Linux и настройка очередей задач
  3. Развертывание Paperless-ngx в Docker Compose с PostgreSQL и Redis
  4. Оптимизация Tesseract OCR: языковые пакеты и многопоточность распознавания
  5. Настройка Nginx Reverse Proxy с защитой доступа и SSL сертификатом
  6. Регламент Disaster Recovery: шифрованный экспорт документов и бэкап в S3
  7. Часто задаваемые вопросы (FAQ)

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

Оптимизация производительности стека paperless-ngx на VPS в Docker с активным OCR-пайплайном требует четкого понимания вычислительной модели фоновых обработчиков. Процесс распознавания входящих документов состоит из трех ресурсных этапов: первичной декомпозиции и растеризации PDF-контейнера через Ghostscript/qpdf, геометрической нормализации изображения (deskew, clean, unpaper) и матричных вычислений нейросетевой модели Tesseract 5.x (LSTM-движок).

Каждый из этих этапов создает принципиально разный профиль нагрузки на подсистемы процессора, оперативной памяти и дискового ввода-вывода. Недостаток ресурсов или агрессивный оверселлинг со стороны хостинг-провайдера мгновенно приводит к деградации очереди Celery, блокировке воркеров и срабатыванию OOM Killer ядра Linux.

                    Входящий PDF / TIFF / JPEG
                               │
                               ▼
        ┌──────────────────────────────────────────────┐
        │  Ghostscript / pikepdf (Растеризация)        │ ──► Аллокация RAM: 400–850 МБ/поток
        └──────────────────────┬───────────────────────┘
                               ▼
        ┌──────────────────────────────────────────────┐
        │  Unpaper / ImageMagick (Предобработка)      │ ──► Всплеск I/O: /tmp (RAM/NVMe)
        └──────────────────────┬───────────────────────┘
                               ▼
        ┌──────────────────────────────────────────────┐
        │  Tesseract 5.x LSTM Engine (OCR)             │ ──► vCPU: 100% saturation (AVX2/AVX-512)
        └──────────────────────┬───────────────────────┘
                               ▼
            Сохранение в PostgreSQL + Индекс Whoosh/Solr

Профиль нагрузки на vCPU: SIMD-инструкции, многопоточность и влияние CPU Steal Time

Движок Tesseract OCR 5.x опирается на целочисленные и вещественные матричные вычисления нейросетевых моделей LSTM (Long Short-Term Memory). Производительность распознавания напрямую зависит от аппаратной поддержки векторных инструкций хостового процессора: AVX2, AVX-512 и FMA.

При обработке страницы скана формата А4 с разрешением 300 DPI одиночный поток Tesseract утилизирует выделенное ядро vCPU на 100%. Если документ состоит из 20 страниц, последовательная обработка одним воркером занимает 45–90 секунд. Попытка ускорить пайплайн за счет распараллеливания через параметр PAPERLESS_OCR_WORKERS масштабирует потребление процессорного времени строго линейно.

На виртуализированных серверах ключевым фактором стабильности OCR является параметр CPU Steal Time (%st). Если хостер допускает оверкоммит вычислительных ресурсов, гипервизор (KVM/QEMU) принудительно отбирает кванты времени у гостевой ОС в пользу соседних виртуальных машин. Для математического аппарата Tesseract прерывание выполнения инструкций вызывает массовый сброс кэшей L1/L2/L3 процессора. В результате время распознавания одной страницы возрастает в 3–5 раз, а latency p99 очереди задач уходит за пределы таймаутов Celery:

# Непрерывный аудит утилизации ядер и фиксация процессорного троттлинга
mpstat -P ALL 1 10

В выводе mpstat колонка %usr должна приближаться к 95–100% на задействованных ядрах, а значение %st обязано сохранять строгий ноль:

14:10:01  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal   %guest  %idle
14:10:02    0   98.02    0.00    1.98    0.00    0.00    0.00    0.00    0.00   0.00
14:10:02    1   97.50    0.00    2.50    0.00    0.00    0.00    0.00    0.00   0.00
14:10:02    2    0.50    0.00    0.20    0.00    0.00    0.00    0.00    0.00  99.30
14:10:02    3    0.10    0.00    0.10    0.00    0.00    0.00    0.00    0.00  99.80

Присутствие даже минимального %st > 1.5% свидетельствует о деградации хоста. Для развертывания paperless-ngx на VPS в Docker с OCR критически важна честная KVM-виртуализация без оверселлинга, доступная на вычислительных узлах tropic.host: выделенные ядра AMD EPYC и Ryzen 9 с базовой частотой от 3.5 ГГц гарантируют стабильный %st = 0.0% и прямой проброс инструкций AVX2 гостевой ОС.


Архитектура памяти: сайзинг под растеризацию и защита от OOM Killer

Оперативная память при OCR-процессинге расходуется не самим Tesseract, а сопутствующими утилитами пред- и постобработки: 1. Ghostscript (растеризация): страница многостраничного PDF с плотностью 300–600 DPI декодируется в несжатый растровый буфер в оперативной памяти. Одностраничный цветной разворот A4 в RGBA (2480 × 3508 пикселей) занимает в оперативной памяти от 35 до 120 МБ в сыром виде, но рабочие буферы Ghostscript и библиотеки pikepdf требуют до 450–700 МБ RSS на поток. 2. Unpaper (очистка от шумов): выполняет выравнивание строк, удаление черных полос сканера и сегментацию. Утилита создает временные копии растровых матриц в памяти, добавляя до 250 МБ к потреблению каждого воркера.

Общий объем RAM для стабильной работы ноды рассчитывается по формуле:

$$RAM_{total} = RAM_{OS+Services} + (N_{workers} \times (RAM_{Ghostscript} + RAM_{Tesseract} + RAM_{Unpaper})) + RAM_{Redis/PG}$$

Для изоляции воркеров и предотвращения падения основного демона paperless-ngx в Docker применяются лимиты cgroups v2. Использование директивы mem_limit без настройки механизма memory.high приводит к жесткому аварийному завершению процесса сигналом SIGKILL:

# Фрагмент docker-compose.yml: тонкий сайзинг воркера в cgroups v2
services:
  webserver:
    image: ghcr.io/paperless-ngx/paperless-ngx:latest
    deploy:
      resources:
        limits:
          cpus: '3.80'
          memory: 4096M
        reservations:
          cpus: '2.00'
          memory: 2048M
    environment:
      # Ограничение параллелизма на уровне Celery и Tesseract
      - PAPERLESS_WEBSERVER_WORKERS=2
      - PAPERLESS_TASK_WORKERS=2
      - PAPERLESS_OCR_WORKERS=2
      - PAPERLESS_OCR_PAGES=3
      # Запрет Tesseract захватывать все потоки хоста через OpenMP
      - OMP_THREAD_LIMIT=1

Переменная OMP_THREAD_LIMIT=1 критически важна: по умолчанию библиотека OpenMP внутри контейнера пытается задействовать все обнаруженные ядра CPU под обработку каждой страницы, провоцируя постоянные переключения контекста ядра Linux (context switches) и конкурентную борьбу за L3-кэш между параллельными задачами Celery.


Нагрузка на I/O подсистему и NVMe бенчмарки

В процессе обработки сканов генерируется плотный поток временных файлов: промежуточные TIFF-слои, растровые маски и текстовые дампы сбрасываются во временный каталог /tmp контейнера.

При одновременной работе 3–4 воркеров диск подвергается смешанной нагрузке: последовательная запись больших временных файлов (Ghostscript write) сочетается с частыми случайными чтениями блоков данных обучающих моделей Tesseract (tessdata_best, файлы объемом 15–25 МБ на каждый язык, читаемые блоками по 4K–16K).

При latency p99 дисковой подсистемы свыше 15 мс обработка пакета документов останавливается: воркеры Celery переходят в состояние D (Uninterruptible Sleep), ожидая завершения системных вызовов fsync() и write().

Инфраструктурная платформа tropic.host решает эту проблему за счет применения серверных NVMe-накопителей корпоративного класса (PCIe 4.0) с показателями случайного чтения 4K QD1 свыше 50 000 IOPS. Задержка записи на таких накопителях удерживается в пределах $p99 < 0.8\text{ мс}$, что исключает I/O-блокировки очереди даже при пакетной загрузке сотен сканов через сетевую папку сканера или WebDAV.


Сравнительная матрица аппаратного сайзинга под нагрузку

Выбор конфигурации сервера опирается на планируемый суточный объем документов, среднюю толщину файлов (страниц на PDF) и требования к задержке обработки:

Профиль нагрузки Суточный объем / Сценарий vCPU / Архитектура RAM / cgroups v2 Limit Celery & OCR Конфигурация Требования к IOPS (NVMe 4K QD1) Рекомендуемый профиль tropic.host
Personal / Home Lab До 50 стр/сутки, бытовые чеки, квитанции, накладные 2 vCPU (KVM, %st = 0%) 4 ГБ (Limit: 3.5 ГБ) TASK_WORKERS=1
OCR_WORKERS=1
OMP_LIMIT=1
$\ge 5\,000\text{ IOPS}$
latency $< 5\text{ ms}$
tropic.host KVM NVMe-2 (2 vCPU / 4 GB RAM / 40 GB NVMe)
SMB / Active Archive 200–800 стр/сутки, бухгалтерский документооборот, счета 4 vCPU (High-Freq 3.5+ GHz) 8 ГБ (Limit: 7.0 ГБ) TASK_WORKERS=2
OCR_WORKERS=2
OMP_LIMIT=1
$\ge 15\,000\text{ IOPS}$
latency $< 2\text{ ms}$
tropic.host KVM NVMe-4 (4 vCPU / 8 GB RAM / 80 GB NVMe)
Enterprise / Ingestion Pipeline 2 500+ стр/сутки, непрерывный потоковый ввод со сканеров 8–16 vCPU (AMD EPYC / Ryzen 9) 16–32 ГБ (Limit: 28 ГБ) TASK_WORKERS=4–8
OCR_WORKERS=4–6
OMP_LIMIT=1
$\ge 50\,000\text{ IOPS}$
latency $< 0.8\text{ ms}$
tropic.host KVM NVMe-8 / PRO (8–16 vCPU / 16–32 GB RAM)

Системный тюнинг ядра Linux для ноды под управлением Docker

Для предотвращения дискового троттлинга и деградации воркеров при пакетном импорте документов операционную систему хоста необходимо адаптировать через sysctl. По умолчанию ядро Linux агрессивно удерживает грязные страницы памяти (dirty pages) в оперативной памяти, а затем сбрасывает их на диск крупными массивами, замораживая ввод-вывод.

Внесите следующие директивы в /etc/sysctl.d/99-paperless-performance.conf:

# Минимизация вытеснения страниц контейнера в swap
vm.swappiness = 10

# Плавный фоновый сброс грязных страниц (предотвращает I/O-фризы воркеров)
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10

# Расширение лимитов файловых дескрипторов под тысячи временных файлов и сокетов
fs.file-max = 2097152

# Увеличение интервала миграции потоков между ядрами CPU для сохранения L2/L3-кэша Tesseract
kernel.sched_migration_cost_ns = 5000000

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

sysctl --system

Если на инстансе смонтирован высокоскоростной NVMe, монтирование рабочего каталога временных файлов в tmpfs (RAM-диск) внутри контейнера полностью снимает нагрузку на физический SSD-накопитель, снижая время обработки одной страницы на 22–30%:

# Оптимизация дискового I/O через временную файловую систему в оперативной памяти
services:
  webserver:
    tmpfs:
      - /tmp:size=1024M,uid=1000,gid=1000,mode=1777

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

# Диагностика переключения контекстов и задержек CPU потоков Tesseract
pidstat -w -u -C "tesseract" 2 5

Отсутствие аномальных значений cswch/s (voluntary context switches) и nvcswch/s (involuntary context switches) в выводе утилиты подтверждает, что контейнерная среда paperless-ngx на KVM VPS в связке с Docker и OCR работает в оптимальном режиме без дефицита циклов vCPU.

Подготовка сервера: тюнинг ядра Linux и настройка очередей задач

При развертывании paperless-ngx на VPS в Docker с активным OCR ключевым фактором стабильности становится предсказуемое распределение системных ресурсов между веб-интерфейсом, СУБД, брокером сообщений Redis и асинхронными воркерами Celery. В отличие от типовых микросервисов, обработка входящих документов сочетает в себе разнородные типы нагрузки: кратковременный всплеск I/O при чтении и сохранении PDF, длительную нагрузку на CPU с активными инструкциями AVX2/AVX-512 при распознавании текста движком Tesseract, а также лавинообразное потребление оперативной памяти при растеризации многостраничных документов через Ghostscript и pdftoppm.

Устранение задержек аллокации памяти: Redis и Transparent Huge Pages

Брокер сообщений Redis хранит очереди задач Celery (celery, celery_heavy, celery_indexing) целиком в RAM. При фоновом сохранении состояния (RDB snapshotting) через системный вызов fork() ядро дублирует таблицы страниц памяти. Если в ОС включен механизм Transparent Huge Pages (THP), ядро оперирует страницами размером 2 МБ вместо стандартных 4 КБ. При механизме Copy-on-Write (CoW) любая модификация даже нескольких байт в очереди задач приводит к принудительному копированию всего блока в 2 МБ, что вызывает микрофризы воркеров (latency p99 возрастает с 0.8 мс до 45–70 мс) и неконтролируемое раздувание RSS (Resident Set Size).

Отключение THP на уровне ядра выполняется созданием отдельного systemd-юнита:

cat << 'EOF' > /etc/systemd/system/disable-thp.service
[Unit]
Description=Disable Linux Transparent Huge Pages (THP) for Redis
DefaultDependencies=no
After=sysinit.target local-fs.target
Before=mongod.service redis-server.service docker.service

[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/enabled && echo never > /sys/kernel/mm/transparent_hugepage/defrag'

[Install]
WantedBy=basic.target
EOF

systemctl daemon-reload
systemctl enable --now disable-thp.service

Для предотвращения ошибок fork() при пиковых аллокациях памяти подсистема виртуальной памяти Linux переводится в режим безусловного оверкоммита. Дополните конфигурационный файл /etc/sysctl.d/99-paperless-performance.conf директивами подсистем vm и сетевого сокета:

# Разрешение выделения виртуальной памяти без предварительной проверки физического остатка (требование Redis)
vm.overcommit_memory = 1

# Максимальная глубина очереди входящих соединений к сокетам (предотвращает дропы TCP SYN при всплесках)
net.core.somaxconn = 65535

# Увеличение буферов передачи для быстрых локальных IPC-соединений между контейнерами
net.ipv4.tcp_max_syn_backlog = 8192
net.core.netdev_max_backlog = 10000

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

sysctl -p /etc/sysctl.d/99-paperless-performance.conf

Изоляция воркеров через cgroups v2 и защита от OOM Killer

При обработке отсканированного TIFF или PDF-файла формата A4 с разрешением 600 DPI несжатый 24-битный растровый слой в памяти занимает:

$$\frac{4960 \times 7016 \times 3}{1024 \times 1024} \approx 99.5 \text{ МБ на одну страницу}$$

Пакетная загрузка архивного скана на 40–60 страниц способна одномоментно затребовать 4–6 ГБ RAM. В стандартной конфигурации Linux OOM Killer при исчерпании доступной памяти уничтожает процесс с наибольшим значением oom_score, которым часто оказывается PostgreSQL или Redis, что приводит к повреждению базы данных или потере очереди.

В архитектуре Docker на базе cgroups v2 необходимо жестко разделить лимиты контейнеров, задать порог мягкого троттлинга через memory.high и скорректировать приоритеты уничтожения через oom_score_adj.

Пример секции сервисов в docker-compose.yml с детерминированной защитой критических узлов:

services:
  broker:
    image: docker.io/library/redis:7.2-alpine
    restart: unless-stopped
    command: redis-server --appendonly no --save "" --maxmemory 512mb --maxmemory-policy noeviction
    oom_score_adj: -1000
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          memory: 256M

  db:
    image: docker.io/library/postgresql:16-alpine
    restart: unless-stopped
    oom_score_adj: -900
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2048M
        reservations:
          memory: 1024M

  webserver:
    image: ghcr.io/paperless-ngx/paperless-ngx:latest
    restart: unless-stopped
    oom_score_adj: 200
    deploy:
      resources:
        limits:
          cpus: '4.0'
          memory: 6144M
        reservations:
          memory: 2048M

Значение oom_score_adj: -1000 полностью исключает Redis из списка целей ядра при дефиците памяти. Контейнер webserver, исполняющий Celery consumer и Tesseract, имеет положительный скоринг 200. Если воркер превысит допустимый порог, ядро завершит только дочерний процесс OCR внутри контейнера, сохранив целостность базы данных и брокера.

Предсказуемость такого распределения опирается на физическую честность виртуализации: на облачной платформе tropic.host инстансы KVM функционируют с нулевым переподписом вычислительных ресурсов (CPU Steal Time %st = 0.0%). Высокая производительность ядер AMD EPYC и Ryzen гарантирует, что аллокаторы памяти не встанут в бесконечный futex wait из-за вытеснения виртуального CPU гипервизором соседних арендаторов.

Сайзинг пула Celery и многопоточности Tesseract

Paperless-ngx использует двухуровневую модель параллелизма: число независимых воркеров Celery (PAPERLESS_TASK_WORKERS) и количество вычислительных потоков OpenMP, выделяемых библиотеке Tesseract на один документ (PAPERLESS_THREADS_PER_WORKER).

Ошибочная установка обоих параметров в максимальные значения провоцирует деградацию производительности из-за каскадного переключения контекстов (context switching). Если сервер имеет 4 vCPU, а конфигурация задает 4 воркера по 4 потока в каждом, 16 потоков будут непрерывно бороться за 4 вычислительных конвейера.

Оптимальный сайзинг рассчитывается по формуле:

$$N_{\text{workers}} = \max\left(1, \; \left\lfloor \frac{N_{\text{vCPU}}}{2} \right\rfloor\right)$$

$$T_{\text{threads}} = \min\left(2, \; \left\lfloor \frac{N_{\text{vCPU}}}{N_{\text{workers}}} \right\rfloor\right)$$

Для инстансов с разным объемом вычислительных ресурсов параметры среды в файле docker-compose.env конфигурируются следующим образом:

Конфигурация vCPU / RAM PAPERLESS_TASK_WORKERS PAPERLESS_THREADS_PER_WORKER PAPERLESS_OCR_PAGES Поведение пайплайна
2 vCPU / 4 ГБ RAM 1 2 50 Последовательная обработка, минимальный риск OOM
4 vCPU / 8 ГБ RAM 2 2 100 Баланс между веб-интерфейсом и параллельным OCR
8 vCPU / 16 ГБ RAM 4 2 0 (без лимита) Высокоскоростной батчинг больших архивных пакетов

Пример директив тюнинга воркеров в docker-compose.env:

# Количество параллельных процессов Celery для OCR и парсинга
PAPERLESS_TASK_WORKERS=2

# Лимит потоков OpenMP внутри каждого экземпляра Tesseract
PAPERLESS_THREADS_PER_WORKER=2

# Автоматический перезапуск воркера после обработки N документов (предотвращает утечки памяти в C-библиотеках)
PAPERLESS_WORKER_MAX_TASKS_PER_CHILD=30

# Время ожидания завершения задачи до принудительного SIGKILL (секунды)
PAPERLESS_TASK_TIMEOUT=300

Директива PAPERLESS_WORKER_MAX_TASKS_PER_CHILD=30 является обязательной для промышленной эксплуатации. Движок Tesseract и библиотеки манипуляции изображениями (Leptonica, ImageMagick) подвержены фрагментации хипа (heap fragmentation) в аллокаторе glibc. Принудительный перезапуск дочернего процесса каждые 30 задач возвращает неиспользуемые страницы операционной системе, удерживая базовое потребление RSS контейнера в пределах 1.5–2.2 ГБ.

Мониторинг задержек очередей и валидация конфигурации

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

# Проверка текущего количества задач, ожидающих OCR в очереди Celery
docker compose exec broker redis-cli -p 6379 llen celery

# Измерение внутренней задержки обработки команд движком Redis (норма: < 1.0 ms)
docker compose exec broker redis-cli --latency -h 127.0.0.1 -p 6379

Статус активных процессов Celery инспектируется непосредственно через точку входа paperless-ngx:

# Вывод активных задач и распределения воркеров
docker compose exec webserver document_consumer --help > /dev/null 2>&1 || \
docker compose exec webserver celery -A paperless inspect active

Контроль задержек планировщика ввода-вывода и очередей дисковых операций в моменты пикового парсинга выполняется через iostat:

# Мониторинг нагрузки на NVMe накопитель с интервалом в 1 секунду
iostat -x -z 1 10

Показатели %util выше 85% и await более 2.5–3.0 мс сигнализируют о дисковом узком горлышке. Использование корпоративных накопителей NVMe PCIe 4.0 на узлах tropic.host гарантирует удержание показателя latency случайного доступа 4K QD1 на уровне десятков микросекунд, исключая блокировку очередей Celery при одновременной записи базы данных и генерации сотен растровых миниатюр документов.

Развертывание Paperless-ngx в Docker Compose с PostgreSQL и Redis

Промышленная эксплуатация стека Paperless-ngx на виртуальном сервере строится на полной изоляции вычислительных компонентов. Монолитные инсталляции с базой данных SQLite неприменимы в производственных условиях: одновременное выполнение фонового распознавания Tesseract OCR, сброс метаданных в базу и клиентские поисковые выборки вызывают взаимные блокировки уровня БД (database is locked) и скачки задержек p99 до десятков секунд.

Ниже представлена архитектура отказоустойчивого стека на базе раздельных контейнеров: веб-приложения с воркерами Celery, сервера СУБД PostgreSQL 16, брокера очередей Redis 7 и вспомогательных микросервисов трансляции форматов Gotenberg и Apache Tika.


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

Контейнер Paperless-ngx исполняет внутренние процессы от непривилегированного пользователя с заданными UID и GID. Несоответствие идентификаторов на стороне хоста приводит к ошибкам прав доступа при сохранении входящих сканов (Permission denied в логах document_consumer).

Создайте системного пользователя paperless с фиксированным UID 1000 и подготовьте структуру каталогов в /srv/paperless:

# Добавление изолированной группы и системного пользователя
sudo groupadd -g 1000 paperless-svc
sudo useradd -u 1000 -g paperless-svc -m -s /usr/sbin/nologin paperless-svc

# Формирование файловой иерархии сервиса
sudo mkdir -p /srv/paperless/{storage,incoming,export,db-data,redis-data,config}

# Назначение владельца на директории документов и очередей
sudo chown -R 1000:1000 /srv/paperless/storage /srv/paperless/incoming /srv/paperless/export /srv/paperless/config

# Назначение владельцев для томов СУБД и брокера (стандартные UID образов Postgres и Redis)
sudo chown -R 999:999 /srv/paperless/db-data /srv/paperless/redis-data

# Ограничение прав на директории баз данных
sudo chmod 700 /srv/paperless/db-data /srv/paperless/redis-data

# Права на каталог входящих документов (обеспечивает запись сетевым сканерам и SMB-ресурсам)
sudo chmod 775 /srv/paperless/incoming

Каталог /srv/paperless/incoming служит точкой монтирования директории потребления (consume). Когда сторонние МФУ или скрипты инжеста сбрасывают файлы в этот каталог, встроенный демон inotify регистрирует событие закрытия дескриптора на запись (IN_CLOSE_WRITE) и ставит документ в очередь на обработку.


Конфигурация параметров окружения: .env

Все чувствительные ключи, учетные данные баз данных, настройки языковых моделей Tesseract и параметры параллелизма выносятся в защищенный файл /srv/paperless/.env.

Сгенерируйте файл со следующими директивами:

# /srv/paperless/.env

# Системная локализация и временная зона
TZ=Europe/Berlin
LC_ALL=C.UTF-8

# Идентификаторы непривилегированного пользователя внутри контейнера
USERMAP_UID=1000
USERMAP_GID=1000

# Криптографический ключ Django (сгенерирован через: openssl rand -hex 32)
PAPERLESS_SECRET_KEY=9d7c58f1a3e20b4c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c

# Публичный URL системы (необходим для корректной генерации ссылок и CSRF-токенов)
PAPERLESS_URL=https://docs.infra-local.internal
PAPERLESS_ALLOWED_HOSTS=docs.infra-local.internal,127.0.0.1,localhost
PAPERLESS_CSRF_TRUSTED_ORIGINS=https://docs.infra-local.internal

# Параметры движка OCR (Tesseract)
PAPERLESS_OCR_LANGUAGE=rus+eng
PAPERLESS_OCR_LANGUAGES=rus eng deu
PAPERLESS_OCR_MODE=skip
PAPERLESS_OCR_SKIP_ARCHIVE_FILE=with_text
PAPERLESS_OCR_DESKEW=true
PAPERLESS_OCR_ROTATE_PAGES=true
PAPERLESS_OCR_PAGES=0
PAPERLESS_OCR_CLEAN=clean

# Распределение вычислительных ресурсов парсера
# Количество воркеров Celery для параллельной обработки входящих файлов
PAPERLESS_TASK_WORKERS=2
# Количество потоков OpenMP/Tesseract на один воркер
PAPERLESS_THREADS_PER_WORKER=2

# Параметры подключения к PostgreSQL
PAPERLESS_DBENGINE=postgresql
PAPERLESS_DBHOST=paperless-postgres
PAPERLESS_DBPORT=5432
PAPERLESS_DBNAME=paperless_production
PAPERLESS_DBUSER=paperless_app
PAPERLESS_DBPASS=DbSecretPassphrase_2026_SecureKey!

# Параметры подключения к Redis
PAPERLESS_REDIS=redis://:RedisAuthToken_9921_Strict!@paperless-redis:6379/0

# Интеграция парсеров внешних форматов (Office, HTML, RTF)
PAPERLESS_TIKA_ENABLED=1
PAPERLESS_TIKA_ENDPOINT=http://paperless-tika:9998
PAPERLESS_TIKA_GOTENBERG_ENDPOINT=http://paperless-gotenberg:3000

Установка прав доступа на файл .env:

sudo chmod 600 /srv/paperless/.env

Параметр PAPERLESS_OCR_MODE=skip критически важен для оптимизации процессорного времени: если входящий PDF уже содержит встроенный текстовый слой (генерируемый цифровыми системами документооборота), Paperless-ngx извлекает его напрямую без энергоемкого растрирования страниц и вызова нейросетевых моделей OCR Tesseract.


Архитектура оркестрации: docker-compose.yml

Манифест развертывания спроектирован с учетом изоляции сегментов сети (bridge без прямого доступа извне для СУБД и брокера), монтирования временных файлов в оперативную память (tmpfs) и аппаратного лимитирования ресурсов (cgroups v2).

Вынесение каталога временных файлов в tmpfs снижает нагрузку на дисковую подсистему: декомпрессия многостраничных PDF-файлов (100–300 МБ) в массив несжатых растров TIFF и последующая обработка происходят непосредственно в RAM, сохраняя ресурс NVMe-накопителей и ликвидируя задержки I/O.

Создайте файл /srv/paperless/docker-compose.yml:

services:
  paperless-redis:
    image: docker.io/library/redis:7.2-alpine
    container_name: paperless-redis
    restart: unless-stopped
    command: >
      redis-server
      --requirepass RedisAuthToken_9921_Strict!
      --save ""
      --appendonly no
      --maxmemory 512mb
      --maxmemory-policy noeviction
      --tcp-backlog 511
    networks:
      - paperless-backend
    volumes:
      - /srv/paperless/redis-data:/data:rw
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "RedisAuthToken_9921_Strict!", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5
    deploy:
      resources:
        limits:
          cpus: "1.00"
          memory: 512M
        reservations:
          cpus: "0.20"
          memory: 128M

  paperless-postgres:
    image: docker.io/library/postgres:16-alpine
    container_name: paperless-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${PAPERLESS_DBNAME}
      POSTGRES_USER: ${PAPERLESS_DBUSER}
      POSTGRES_PASSWORD: ${PAPERLESS_DBPASS}
    command: >
      postgres
      -c shared_buffers=256MB
      -c effective_cache_size=768MB
      -c work_mem=16MB
      -c maintenance_work_mem=64MB
      -c min_wal_size=512MB
      -c max_wal_size=2GB
      -c checkpoint_completion_target=0.9
      -c checkpoint_timeout=15min
      -c wal_buffers=16MB
      -c default_statistics_target=100
      -c random_page_cost=1.1
      -c effective_io_concurrency=200
      -c max_worker_processes=4
      -c max_parallel_workers_per_gather=2
    networks:
      - paperless-backend
    volumes:
      - /srv/paperless/db-data:/var/lib/postgresql/data:rw
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${PAPERLESS_DBUSER} -d ${PAPERLESS_DBNAME}"]
      interval: 10s
      timeout: 5s
      retries: 5
    deploy:
      resources:
        limits:
          cpus: "2.00"
          memory: 1536M
        reservations:
          cpus: "0.50"
          memory: 512M

  paperless-gotenberg:
    image: docker.io/gotenberg/gotenberg:8.0.3
    container_name: paperless-gotenberg
    restart: unless-stopped
    command:
      - "gotenberg"
      - "--chromium-disable-javascript=true"
      - "--chromium-allow-list=file:///tmp/.*"
      - "--api-timeout=60s"
    networks:
      - paperless-backend
    deploy:
      resources:
        limits:
          cpus: "1.00"
          memory: 1024M
        reservations:
          cpus: "0.20"
          memory: 256M

  paperless-tika:
    image: docker.io/apache/tika:2.9.1.0
    container_name: paperless-tika
    restart: unless-stopped
    networks:
      - paperless-backend
    deploy:
      resources:
        limits:
          cpus: "1.00"
          memory: 1024M
        reservations:
          cpus: "0.20"
          memory: 256M

  paperless-core:
    image: ghcr.io/paperless-ngx/paperless-ngx:2.5.4
    container_name: paperless-core
    restart: unless-stopped
    env_file:
      - /srv/paperless/.env
    depends_on:
      paperless-postgres:
        condition: service_healthy
      paperless-redis:
        condition: service_healthy
      paperless-gotenberg:
        condition: service_started
      paperless-tika:
        condition: service_started
    networks:
      - paperless-backend
      - paperless-frontend
    ports:
      - "127.0.0.1:8000:8000"
    volumes:
      - /srv/paperless/storage:/usr/src/paperless/data:rw
      - /srv/paperless/storage/media:/usr/src/paperless/media:rw
      - /srv/paperless/incoming:/usr/src/paperless/consume:rw
      - /srv/paperless/export:/usr/src/paperless/export:rw
    tmpfs:
      - /tmp:size=1024M,uid=1000,gid=1000,mode=1777
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:8000/api/"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s
    deploy:
      resources:
        limits:
          cpus: "3.50"
          memory: 3584M
        reservations:
          cpus: "1.00"
          memory: 1024M

networks:
  paperless-backend:
    name: paperless-backend-net
    driver: bridge
    internal: true
  paperless-frontend:
    name: paperless-frontend-net
    driver: bridge

Анализ конфигурации сервисов

  1. Разделение сетевых контуров: Сеть paperless-backend сконфигурирована с директивой internal: true. Контейнеры баз данных и парсеров физически изолированы от выхода во внешнюю сеть и от входящего внешнего трафика через драйвер Docker Bridge. Доступ к веб-интерфейсу открыт только контейнеру paperless-core через сеть paperless-frontend, где порт 8000 жестко забинжен на локальный сокет петли обратной связи хоста (127.0.0.1:8000). Прямой проброс наружу исключен — трафик должен проксироваться через Nginx или Traefik с терминацией TLS.
  2. Параметры PostgreSQL под NVMe:
  3. random_page_cost=1.1: значение приближено к seq_page_cost=1.0, сообщая оптимизатору запросов, что дисковая подсистема использует быстрый случайный доступ (NVMe SSD), что заставляет планировщик отдавать приоритет индексным сканированиям вместо последовательных чтений (Seq Scan).
  4. effective_io_concurrency=200: активирует асинхронный prefetch страниц памяти ядром Linux на дисковых контроллерах с поддержкой аппаратных очередей.
  5. checkpoint_completion_target=0.9 и checkpoint_timeout=15min: размазывают сброс грязных страниц WAL по времени, предотвращая I/O-фризы в момент фиксации чекпоинта.
  6. Отказоустойчивость Redis: Redis функционирует исключительно как in-memory брокер сообщений для Celery. Директивы --save "" и --appendonly no отключают запись снимков памяти (RDB) и журнала упреждающей записи (AOF) на диск. Это полностью исключает системные вызовы fork() и задержки сброса страниц памяти на диск при обработке очередей, экономя системные ресурсы.

Эксплуатация подобного плотного контейнерного стека требует стабильной предсказуемости аппаратных ресурсов гипервизора. На облачной платформе tropic.host виртуализация KVM развертывается со строгим паритетом vCPU к физическим ядрам процессоров AMD EPYC и Ryzen 9 (%st = 0.0%). Отсутствие оверселлинга CPU и использование серверных накопителей NVMe PCIe 4.0 корпоративного класса гарантируют, что всплески утилизации диска при генерации миниатюр документов и интенсивные операции PostgreSQL не приведут к деградации задержек обработки запросов.


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

Перед запуском убедитесь, что плагин Docker Compose v2 установлен и активен:

docker compose version

Поднимите сервисы в фоновом режиме:

cd /srv/paperless
docker compose up -d

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

docker compose logs -f paperless-core

Корректный старт сопровождается выводом информации о завершении миграций PostgreSQL (Applying ... OK), компиляции поискового индекса Whoosh/Xapian и запуске ASGI/WSGI-сервера Gunicorn на порту 8000.

После перехода контейнера paperless-core в статус healthy (проверяется через docker compose ps), выполните создание учетной записи суперпользователя:

# Интерактивное создание администратора системы
docker compose exec -it paperless-core createsuperuser

Команда запросит логин, валидный адрес электронной почты и административный пароль. Созданная учетная запись обладает неограниченными правами на управление правами доступа, шаблонами распознавания и автоматическими тегами через Web UI.


Проверка системных вызовов и лимитов cgroups v2

Для контроля соблюдения ресурсных ограничений контейнеров и проверки отсутствия троттлинга по CPU выполните аудит через подсистему cgroup v2:

# Получение идентификатора контейнера ядра Paperless
CONTAINER_ID=$(docker inspect --format="{{.Id}}" paperless-core)

# Проверка распределения процессорного времени и троттлинга CFS
cat /sys/fs/cgroup/system.slice/docker-${CONTAINER_ID}.scope/cpu.stat

В выводе обратите внимание на параметры: * nr_periods: общее число расчетных интервалов планировщика. * nr_throttled: количество периодов, в которых процессы были принудительно заморожены из-за превышения процессорного лимита. * throttled_usec: суммарное время нахождения потоков в состоянии ожидания.

Если отношение nr_throttled / nr_periods превышает 0.15 (15%), увеличьте параметр limits.cpus для сервиса paperless-core в docker-compose.yml, чтобы избежать задержек в работе веб-интерфейса во время выполнения параллельных задач OCR.

Проверьте корректность работы механизма inotify в каталоге входящих файлов:

# Инспекция дескрипторов событий файловой системы процессами paperless
docker compose exec paperless-core python3 -c "
import inotify.adapters
i = inotify.adapters.Inotify()
i.add_watch('/usr/src/paperless/consume')
print('Inotify watcher successfully attached to consume directory')
"

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

Оптимизация Tesseract OCR: языковые пакеты и многопоточность распознавания

Конвейер оптического распознавания текста (OCR) в Paperless-ngx построен вокруг связки OCRmyPDF, библиотеки pytesseract и бинарного движка Tesseract 5.x, использующего рекуррентные нейронные сети на базе архитектуры LSTM (Long Short-Term Memory). По умолчанию обработка входящих документов является наиболее ресурсоемкой операцией во всем стеке: векторизация, дескьюинг (устранение перекоса страниц), денойзинг и матричные вычисления нейросети создают непрерывную нагрузку на целочисленные и векторные блоки CPU (AVX2/AVX-512).

Некорректная конфигурация параллелизма приводит к лавинообразному росту переключений контекста ядра (context switching), исчерпанию пула памяти и деградации производительности вплоть до срабатывания OOM Killer. Для построения предсказуемого и быстрого пайплайна требуется точная синхронизация между воркерами очередей Celery, потоками OpenMP движка Tesseract и ограничениями подсистемы cgroups v2.


Архитектура многопоточности: Celery против OpenMP

В базовой поставке Paperless-ngx возникают две конкурирующие модели параллелизма:

  1. Process-level concurrency (Celery Task Workers): регулируется переменной окружения PAPERLESS_TASK_WORKERS. Задает количество изолированных процессов-обработчиков очереди задач, забирающих входящие файлы из брокера Redis.
  2. Thread-level concurrency (Tesseract OpenMP): Tesseract скомпилирован с поддержкой OpenMP и по умолчанию пытается задействовать все доступные процессорные ядра хоста (nproc) для ускорения распознавания одной-единственной страницы.

Если на сервере с 4 vCPU запустить PAPERLESS_TASK_WORKERS=4 без ограничения внутренних потоков Tesseract, то при одновременной загрузке четырех многостраничных документов система породит $4 \times 4 = 16$ вычислительных потоков, жестко конкурирующих за 4 физических ядра. Это вызывает постоянную инвалидацию кэшей L1d/L2 процессора, взрывной рост метрики cs (context switches) в выводе vmstat до 150 000+ операций в секунду и падение задержки обработки $p99$ в 3–5 раз.

                   ┌───────────────────────────────────────┐
                   │   Входящий PDF (Consume Directory)    │
                   └──────────────────┬────────────────────┘
                                      ▼
                   ┌───────────────────────────────────────┐
                   │     Redis Message Broker (Queue)      │
                   └───────┬───────────────────────┬───────┘
                           │                       │
      PAPERLESS_TASK_WORKERS=2                     │
                           ▼                       ▼
              ┌────────────────────────┐┌────────────────────────┐
              │  Celery Worker #1      ││  Celery Worker #2      │
              │  (Isolated Process)    ││  (Isolated Process)    │
              └────────────┬───────────┘└────────────┬───────────┘
                           │                         │
      PAPERLESS_OCR_THREADS=1 (OMP_THREAD_LIMIT=1)   │
                           ▼                         ▼
              ┌────────────────────────┐┌────────────────────────┐
              │ Tesseract LSTM (Core0) ││ Tesseract LSTM (Core1) │
              └────────────────────────┘└────────────────────────┘

Расчет оптимального сайзинга воркеров

Для серверов общего назначения и сред с пакетной загрузкой (batch scanning) наивысшую утилизацию дает стратегия один воркер на одно ядро с блокировкой многопоточности Tesseract:

$$\text{PAPERLESS_TASK_WORKERS} = \max(1, \lfloor \text{vCPU} - 1 \rfloor)$$ $$\text{PAPERLESS_OCR_THREADS} = 1 \quad (\text{через } \texttt{OMP_THREAD_LIMIT}=1)$$

Один vCPU резервируется под операционную систему, реляционную СУБД PostgreSQL, Redis и веб-сервер Gunicorn.

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


Настройка переменных окружения в docker-compose.env

Для фиксации параметров многопоточности и управления поведением оптического движка внесите в файл docker-compose.env следующие параметры:

# ----------------------------------------------------------------------
# ПАРАЛЛЕЛИЗМ И ОРКЕСТРАЦИЯ ЗАДАЧ OCR
# ----------------------------------------------------------------------

# Количество параллельных процессов Celery для фоновой обработки
# Для инстанса с 4 vCPU оптимально значение 2 или 3
PAPERLESS_TASK_WORKERS=2

# Принудительное ограничение потоков OpenMP внутри каждого вызова Tesseract
# Блокирует создание суб-тредов, устраняя конкуренцию за кэш L2/L3
PAPERLESS_THREADS_PER_WORKER=1
OMP_THREAD_LIMIT=1

# ----------------------------------------------------------------------
# ЯЗЫКОВЫЕ ПАКЕТЫ И СТРАТЕГИЯ ИНФЕРЕНСА
# ----------------------------------------------------------------------

# Язык по умолчанию для интерфейса и базового сопоставления
PAPERLESS_OCR_LANGUAGE=rus

# Список языков, передаваемых Tesseract через аргумент -l (rus+eng)
# Порядок критичен: базовым указывается наиболее частотный алфавит
PAPERLESS_OCR_LANGUAGES=rus eng

# Режим обработки:
# skip — пропуск OCR, если в PDF уже есть встроенный текстовый слой (экономит до 90% CPU)
# redo — принудительный перерасчет и замена существующего слоя
# force — растеризация и распознавание всех страниц без исключений
PAPERLESS_OCR_MODE=skip

# Отключение предобработки для ускорения конвейера (если сканы высокого качества)
# deskew=false экономит до 15% процессорного времени на страницу
PAPERLESS_OCR_CLEAN=clean
PAPERLESS_OCR_DESKEW=true
PAPERLESS_OCR_ROTATE_PAGES=true

# Тайм-аут на обработку одного документа (в секундах) во избежание зависания очередей
PAPERLESS_OCR_USER_ARGS='{"invalidate_digital_signatures": true}'

Языковые пакеты: выбор между tessdata_fast и tessdata_best

По умолчанию в контейнер Paperless-ngx загружаются языковые пакеты ветки tessdata_fast. Это 8-битные квантованные целочисленные модели, оптимизированные для быстродействия за счет минимальной деградации распознавания (потеря точности менее 0.5–1.5% по метрике CER — Character Error Rate).

  • tessdata_fast (по умолчанию): размер словаря rus.traineddata составляет около 4.5 МБ. Инференс страницы A4 при 300 DPI занимает в среднем 0.8–1.4 секунды на одном ядре modern CPU. Минимальный расход RAM (до 80 МБ на поток).
  • tessdata_best (максимальная точность): размер словаря достигает 15–20 МБ. Используются полноразмерные float-веса. Время инференса страницы возрастает до 3.2–6.0 секунд (увеличение задержки в 3–4 раза), потребление оперативной памяти возрастает до 250–350 МБ на процесс. Данный вариант оправдан только для архивных сканов с низким контрастом или поврежденными шрифтами.

Для проверки списка языков, доступных движку внутри работающего контейнера, выполните:

docker compose exec paperless-core tesseract --list-langs

Вывод должен содержать требуемые идентификаторы:

List of available languages (3):
eng
osd
rus

(Словарь osd — Orientation and Script Detection — отвечает за автоматическое определение ориентации листа).

Если в системе требуются дополнительные языки (например, немецкий или китайский), нет необходимости пересобирать Docker-образ. Официальный скрипт инициализации Paperless-ngx анализирует переменную PAPERLESS_OCR_LANGUAGES при старте. Если язык отсутствует в системной директории /usr/share/tesseract-ocr/5/tessdata, скрипт автоматически скачивает соответствующий .traineddata через пакетный менеджер apt в слой контейнера.

Для промышленной эксплуатации с гарантией автономности и отсутствия внешних сетевых вызовов при перезапуске рекомендуется смонтировать выделенный volume для моделей Tesseract.

Монтирование кастомных языковых моделей через docker-compose.yml:

services:
  paperless-core:
    image: ghcr.io/paperless-ngx/paperless-ngx:latest
    container_name: paperless-core
    environment:
      - OMP_THREAD_LIMIT=1
      - PAPERLESS_OCR_LANGUAGES=rus eng deu
    volumes:
      - ./data:/usr/src/paperless/data
      - ./media:/usr/src/paperless/media
      - ./export:/usr/src/paperless/export
      - ./consume:/usr/src/paperless/consume
      # Персистентное хранилище предобученных языковых пакетов
      - ./custom-tessdata:/usr/share/tesseract-ocr/5/tessdata:ro

Загрузка оптимизированных словарей напрямую из официального репозитория Tesseract:

mkdir -p ./custom-tessdata
cd ./custom-tessdata

# Скачивание быстрых LSTM моделей
curl -sL -O https://github.com/tesseract-ocr/tessdata_fast/raw/main/rus.traineddata
curl -sL -O https://github.com/tesseract-ocr/tessdata_fast/raw/main/eng.traineddata
curl -sL -O https://github.com/tesseract-ocr/tessdata_fast/raw/main/osd.traineddata

# Верификация контрольных сумм и прав доступа
chmod 0644 *.traineddata

Устранение дискового узкого места: tmpfs для промежуточных растров

В процессе выполнения OCR входящий PDF-документ декомпозируется утилитами pdf2image / Ghostscript на несжатые растровые матрицы в формате PPM или TIFF с плотностью 300 DPI.

Одна страница формата A4 в 24-битном RGB-представлении при 300 DPI занимает в несжатом виде:

$$\frac{2480 \times 3508 \times 3 \text{ байта}}{1024 \times 1024} \approx 24.89 \text{ МБ}$$

Документ объемом 50 страниц генерирует более 1.2 ГБ временных данных, которые непрерывно записываются и считываются с диска в каталоге /tmp. На серверах с классическими SATA SSD или виртуальными дисками со скрытым троттлингом операций ввода-вывода дисковая очередь $await$ возрастает до сотен миллисекунд, замораживая процесс OCR.

Благодаря применению серверных накопителей NVMe PCIe 4.0 на узлах tropic.host, случайное чтение и запись блоками 4K QD1 стабильно превышают 50 000 IOPS. Тем не менее, для устранения любого дискового оверхеда и снижения износа ячеек памяти временную директорию /tmp следует перенести в оперативную память через виртуальную файловую систему tmpfs.

Добавьте секцию tmpfs в описание сервиса paperless-core в docker-compose.yml:

services:
  paperless-core:
    # ... основные параметры
    tmpfs:
      # Выделение RAM-диска под временные растровые файлы PDF/TIFF
      # Размер рассчитывается исходя из: (кол-во воркеров) * 1.5 ГБ
      - /tmp:size=3G,mode=1777
      - /usr/src/paperless/data/tmp:size=1G,mode=1777

Такая конфигурация гарантирует, что все операции трансформации матриц пикселей выполняются напрямую в памяти хоста с задержками на уровне сотен наносекунд.


Профилирование и аудит производительности OCR

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

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

# Мониторинг системных вызовов и переключения контекста в реальном времени
pidstat -w -u -C "tesseract|python3" 2

Обратите внимание на столбцы: * %usr: время выполнения кода в пространстве пользователя (должно быть близко к 90–98% на ядро при активном OCR). * %system: время нахождения ядра в режиме выполнения системных вызовов (не должно превышать 5–8%). * cswch/s (voluntary context switches): добровольные передачи управления. * nvcswch/s (non-voluntary context switches): принудительные вытеснения потока планировщиком ядра. Если это число превышает 1 500 на поток, это сигнализирует об избыточном значении PAPERLESS_TASK_WORKERS относительно доступных физических ядер.

Проверка отсутствия троттлинга по CPU со стороны гипервизора KVM:

# Мониторинг кражи процессорного времени (Steal Time)
mpstat 1 5

В выводе столбец %steal обязан сохранять значение 0.00. Любое отклонение выше 0.50% свидетельствует о том, что хост-нода провайдера перегружена чужими процессами, а планировщик KVM задерживает выделение физических тактов CPU для вашей виртуальной машины, что напрямую ломает вычисление весов в LSTM-сетях Tesseract. На изолированных инстансах tropic.host процессорные ядра жестко закреплены за виртуальной машиной, обеспечивая плотную линейную производительность вне зависимости от общего фона нагрузок в дата-центре.

Тонкая настройка sysctl хоста для высоконагруженного OCR

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

# Применение оптимизаций виртуальной памяти ядра хоста
sudo sysctl -w vm.swappiness=10
sudo sysctl -w vm.vfs_cache_pressure=50
sudo sysctl -w vm.dirty_background_ratio=5
sudo sysctl -w vm.dirty_ratio=10

# Персистентная фиксация настроек
cat <<EOF | sudo tee /etc/sysctl.d/99-paperless-performance.conf
vm.swappiness = 10
vm.vfs_cache_pressure = 50
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
EOF

sudo sysctl --system

Параметр vm.swappiness=10 гарантирует, что ядро Linux не начнет выгружать страницы Python-воркеров и буферы Tesseract в swap-файл при наличии свободной физической памяти, удерживая задержку $p99$ обработки документов в рамках детерминированных значений.

Настройка Nginx Reverse Proxy с защитой доступа и SSL сертификатом

После стабилизации параметров виртуальной памяти и изоляции дисковых очередей ядра Linux следующим критическим этапом является выстраивание внешнего сетевого периметра. Веб-интерфейс и API Paperless-ngx, обслуживаемые сервером Gunicorn/ASGI внутри контейнера webserver, по умолчанию слушают порт 8000. Прямой проброс этого порта наружу через директиву ports: - "8000:8000" в docker-compose.env создает прямую угрозу безопасности: встроенный веб-сервер приложения не рассчитан на терминацию TLS-сессий, медленные клиентские соединения (атаки типа Slowloris) и буферизацию объемных multipart-запросов со сканированными PDF-документами.

При промышленном развертывании стека Paperless-ngx на VPS в Docker с активным конвейером OCR реверс-прокси Nginx берет на себя роль фронтенда: берет на себя криптографическую нагрузку, отсекает неавторизованные подсети, управляет таймаутами тяжелых загрузок и мультиплексирует HTTP/2-соединения.

Изоляция сетевого сокета и ликвидация обхода межсетевого экрана Docker

Главная архитектурная ловушка Docker в Linux — механизм управления iptables. По умолчанию Docker Daemon добавляет собственные правила в цепочку PREROUTING таблицы nat и цепочку DOCKER таблицы filter. Это приводит к тому, что порт, опубликованный как "8000:8000", становится доступен всему интернету в обход любых локальных правил UFW или цепочки INPUT в nftables.

Для предотвращения утечки трафика зафиксируйте привязку сервиса исключительно к локальному интерфейсу 127.0.0.1 в файле docker-compose.env:

services:
  webserver:
    # ... остальные директивы контейнера
    ports:
      - "127.0.0.1:8000:8000"

Проверьте корректность биндинга сокета в пространстве ядра хоста:

sudo ss -tulpn | grep :8000

В выводе столбец Local Address:Port обязан содержать исключительно 127.0.0.1:8000. Присутствие 0.0.0.0:8000 или :::8000 категорически недопустимо.

Получение TLS-сертификата Let's Encrypt через Certbot

Перед генерацией итогового блока виртуального хоста выпустите доверенный SSL-сертификат. Для автоматизации ротации ключей используется Certbot с плагином для Nginx.

# Установка базового пакета Nginx и утилиты Certbot
sudo apt-get update && sudo apt-get install -y nginx certbot python3-certbot-nginx

# Первоначальный запрос сертификата по протоколу ACME (HTTP-01 Challenge)
sudo certbot certonly --nginx \
  -d docs.yourdomain.com \
  --non-interactive \
  --agree-tos \
  --email [email protected] \
  --rsa-key-size 4096

Проверьте системный таймер systemd, отвечающий за автоматическую пролонгацию сертификатов каждые 12 часов:

systemctl list-timers | grep certbot

Производственная конфигурация Nginx под обработку OCR-документов

Специфика Paperless-ngx накладывает жесткие требования на параметры HTTP-буферизации и поддержку двунаправленных сокетов: 1. Размер полезной нагрузки (client_max_body_size): Многостраничные сканы разрешением 600 DPI в формате PDF/A или несжатые TIFF-файлы легко достигают размеров 100–200 МБ. Дефолтный лимит Nginx в 1m приведет к ошибке 413 Request Entity Too Large. 2. Буферизация на NVMe: Директива client_body_buffer_size задает размер буфера в RAM. Если размер файла превышает этот буфер, Nginx сбрасывает тело запроса во временный файл в /var/lib/nginx/body/. На высокоскоростных накопителях NVMe корпоративного класса, применяемых на серверах tropic.host, случайная запись 4K блоками отрабатывает с задержкой $p99 < 0.8$ мс при производительности свыше 50 000 IOPS, исключая блокировку воркеров Nginx в состоянии D-state (uninterruptible sleep). 3. WebSockets: Paperless-ngx передает статусы задач фонового консьюмера и Tesseract OCR в веб-интерфейс в реальном времени через конечную точку /ws/status/. Без заголовков Upgrade и Connection интерфейс переходит в аварийный режим поллинга, создавая лишние тысячи HTTP-запросов к Gunicorn.

Создайте конфигурационный файл виртуального хоста /etc/nginx/sites-available/paperless.conf:

# Определение схемы переключения для WebSockets
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

# Зона ограничения частоты запросов для защиты API и форм аутентификации
limit_req_zone $binary_remote_addr zone=paperless_auth_limit:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=paperless_general_limit:10m rate=30r/s;

# Перенаправление незашифрованного HTTP-трафика на HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name docs.yourdomain.com;

    # Служебный локейшн для ACME-челленджей Let's Encrypt
    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name docs.yourdomain.com;

    # Параметры сертификатов Let's Encrypt
    ssl_certificate /etc/letsencrypt/live/docs.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/docs.yourdomain.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/docs.yourdomain.com/chain.pem;

    # Криптографический профиль безопасности (Mozilla Modern/Intermediate)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    # Оптимизация TLS-рукопожатий и OCSP Stapling
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 8.8.8.8 valid=300s;
    resolver_timeout 5s;

    # Защитные HTTP-заголовки
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

    # Настройки обработки больших сканов и PDF-документов
    client_max_body_size 250M;
    client_body_buffer_size 1M;
    client_body_timeout 300s;

    # Ограничение общего трафика
    limit_req zone=paperless_general_limit burst=50 nodelay;

    # Защита эндпоинтов авторизации от перебора учетных данных
    location /accounts/login/ {
        limit_req zone=paperless_auth_limit burst=3 nodelay;
        proxy_pass http://127.0.0.1:8000;
        include /etc/nginx/proxy_params_paperless;
    }

    location /api/token/ {
        limit_req zone=paperless_auth_limit burst=3 nodelay;
        proxy_pass http://127.0.0.1:8000;
        include /etc/nginx/proxy_params_paperless;
    }

    # Обработка очередей статусов через WebSockets
    location /ws/ {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_redirect off;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Увеличенные таймауты для персистентного соединения вебсокетов
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }

    # Основной прокси-блок
    location / {
        proxy_pass http://127.0.0.1:8000;
        include /etc/nginx/proxy_params_paperless;
    }
}

Вынесите параметры проксирования в отдельный служебный файл /etc/nginx/proxy_params_paperless:

proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;

# Отключение буферизации для потоковых ответов
proxy_buffering on;
proxy_buffers 16 64k;
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 128k;

# Увеличенные таймауты под длительные OCR-операции импорта
proxy_connect_timeout 60s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;

Активируйте виртуальный хост символической ссылкой и протестируйте синтаксис:

sudo ln -sf /etc/nginx/sites-available/paperless.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Сетевая фильтрация и изоляция доступа по IP

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

Вариант А: Ограничение доступа на уровне Nginx

Для жесткого разграничения доступа добавьте директивы allow и deny внутрь блока location /:

    location / {
        # Разрешенные доверенные IP-адреса и подсети администратора
        allow 198.51.100.24/32;
        allow 203.0.113.0/24;
        # Запрет для всех остальных источников
        deny all;

        proxy_pass http://127.0.0.1:8000;
        include /etc/nginx/proxy_params_paperless;
    }

Вариант Б: Автоматическая блокировка атак перебора через Fail2ban

Если архив должен быть доступен глобально, защита от подбора паролей к учетным записям Paperless-ngx делегируется демону fail2ban. Демон в реальном времени анализирует журнал доступа Nginx (/var/log/nginx/access.log), находит аномальные всплески ответов с кодами 401 Unauthorized или 403 Forbidden на URL аутентификации и динамически добавляет атакующие IP в таблицу блокировок nftables.

Создайте фильтр /etc/fail2ban/filter.d/nginx-paperless-auth.conf:

[Definition]
failregex = ^<HOST> - .* "(?:POST|GET) /accounts/login/.* HTTP/.*" (?:401|403|200)
            ^<HOST> - .* "(?:POST|GET) /api/token/.* HTTP/.*" (?:401|403)
ignoreregex =

Подключите соответствующий изолятор (jail) в /etc/fail2ban/jail.d/paperless.local:

[nginx-paperless-auth]
enabled  = true
port     = http,https
filter   = nginx-paperless-auth
logpath  = /var/log/nginx/access.log
maxretry = 5
findtime = 600
bantime  = 86400
banaction = nftables-multiport

Перезапустите службу и убедитесь в регистрации изолирующей цепочки:

sudo systemctl restart fail2ban
sudo fail2ban-client status nginx-paperless-auth

Интеграция стека завершена: Nginx берет на себя фильтрацию мусорного L7-трафика, криптографическую разгрузку и безопасную буферизацию объемных PDF-сканов на локальные NVMe, передавая очищенные данные на внутренний сокет 127.0.0.1:8000. На облачных KVM-инстансах tropic.host со связностью 1–10 Гбит/с и включенным алгоритмом TCP BBR сетевая задержка доставки тяжелых пакетов до клиентских браузеров остается минимальной, исключая обрывы соединений при параллельной загрузке сотен документов.

Регламент Disaster Recovery: шифрованный экспорт документов и бэкап в S3

При эксплуатации Paperless-ngx на VPS в Docker с активным OCR непрерывность доступа к юридическим и финансовым документам зависит от предсказуемости процедуры восстановления. Простое создание «горячих» снапшотов файловой системы или копирование томов Docker на лету сопряжено с риском получения поврежденного состояния (inconsistent state): в момент чтения дискового блока Celery-воркер может выполнять операцию распознавания, удерживая дескриптор файла в media/documents/originals/, в то время как транзакция PostgreSQL еще не сброшена на диск через fsync().

Отказоустойчивая стратегия Disaster Recovery (DR) разделяет данные на два независимых контура: 1. Штатный экспорт Paperless-ngx (document_exporter): формирует аппаратно-независимый архив документов с манифестом manifest.json, содержащим полные метаданные, теги, корреспондентов и контрольные суммы SHA-256. Этот формат гарантирует восстановление даже при полной смене версии СУБД или миграции между архитектурами CPU (x86_64 / aarch64). 2. Бинарный дамп PostgreSQL (pg_dump -Fc): обеспечивает мгновенный откат транзакций без необходимости длительной переиндексации поискового движка Whoosh.

1. Архитектура скрипта автоматизированного бэкапа

Резервное копирование выполняется в изолированном временном каталоге в оперативной памяти (tmpfs), либо на быстром NVMe-накопителе с последующим симметричным шифрованием GPG (алгоритм AES-256) и потоковой передачей в S3-совместимое объектное хранилище.

Для предотвращения деградации производительности веб-интерфейса и очередей OCR резервное копирование изолируется через подсистемы ядра: утилита flock блокирует повторный запуск скрипта, nice -n 19 снижает приоритет планировщика CPU, а ionice -c 2 -n 7 ограничивает приоритет операций ввода-вывода (I/O Best-Effort lowest priority). На виртуальных серверах tropic.host с корпоративными NVMe PCIe 4.0 (где случайное чтение 4K QD1 превышает 50 000 IOPS, а CPU Steal Time зафиксирован на уровне %st = 0.0%) процесс сжатия и шифрования укладывается в минимальное технологическое окно, не вызывая скачков $p99$ latency у обслуживающего Nginx.

Создайте рабочий каталог и сгенерируйте 64-байтный криптографический ключ:

sudo mkdir -p /opt/paperless-ngx/scripts /opt/paperless-ngx/backups /etc/paperless
sudo chmod 700 /opt/paperless-ngx/scripts /etc/paperless

# Генерация псевдослучайного ключа шифрования AES-256
openssl rand -base64 48 | sudo tee /etc/paperless/backup.key > /dev/null
sudo chmod 400 /etc/paperless/backup.key

Разверните боевой скрипт резервного копирования /opt/paperless-ngx/scripts/backup.sh:

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

# -----------------------------------------------------------------------------
# Скрипт холодного экспорта, шифрования и выгрузки бэкапа Paperless-ngx в S3
# -----------------------------------------------------------------------------

LOCK_FILE="/tmp/paperless-backup.lock"
exec 200>"$LOCK_FILE"
flock -n 200 || { echo "[$(date -Iseconds)] [ERROR] Резервное копирование уже выполняется." >&2; exit 1; }

TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
BACKUP_ROOT="/opt/paperless-ngx/backups"
STAGE_DIR="${BACKUP_ROOT}/stage_${TIMESTAMP}"
EXPORT_DIR="${STAGE_DIR}/export"
COMPOSE_DIR="/opt/paperless-ngx"
KEY_FILE="/etc/paperless/backup.key"

S3_BUCKET="s3://infra-backups-vault/paperless-ngx"
RETENTION_DAYS_LOCAL=7
RETENTION_DAYS_S3=30

log() {
    echo "[$(date -Iseconds)] [INFO] $*"
}

cleanup() {
    if [[ -d "$STAGE_DIR" ]]; then
        log "Очистка промежуточного каталога $STAGE_DIR..."
        rm -rf "$STAGE_DIR"
    fi
}
trap cleanup EXIT ERR

mkdir -p "$EXPORT_DIR"
cd "$COMPOSE_DIR"

log "Шаг 1: Запуск встроенного document_exporter Paperless-ngx..."
# Флаги: -c (сравнивать контрольные суммы), -p (сохранять права), -f (перезаписывать дубликаты)
docker compose exec -T webserver document_exporter \
    --compare-checksums \
    --delete \
    /usr/src/paperless/export

# Перемещение экспортированных файлов из примонтированного тома во временный каталог сборки
# Предполагается, что ./export примонтирован в /usr/src/paperless/export
mv "${COMPOSE_DIR}/export/"* "$EXPORT_DIR/"

log "Шаг 2: Создание консистентного бинарного дампа PostgreSQL..."
docker compose exec -T db pg_dump \
    -U "${POSTGRES_USER:-paperless}" \
    -d "${POSTGRES_DB:-paperless}" \
    -Fc \
    -Z 6 \
    -f "/tmp/db_${TIMESTAMP}.dump"

docker compose cp db:"/tmp/db_${TIMESTAMP}.dump" "${STAGE_DIR}/postgres_${TIMESTAMP}.dump"
docker compose exec -T db rm -f "/tmp/db_${TIMESTAMP}.dump"

log "Шаг 3: Сохранение локальной конфигурации окружения..."
cp "${COMPOSE_DIR}/docker-compose.env" "${STAGE_DIR}/docker-compose.env"
cp "${COMPOSE_DIR}/docker-compose.yml" "${STAGE_DIR}/docker-compose.yml"

log "Шаг 4: Упаковка в архив zstd и симметричное GPG-шифрование..."
ARCHIVE_PATH="${BACKUP_ROOT}/paperless_backup_${TIMESTAMP}.tar.zst.gpg"

tar -C "$STAGE_DIR" -cf - . \
    | zstd -T0 -3 \
    | gpg --symmetric \
          --cipher-algo AES256 \
          --batch \
          --passphrase-file "$KEY_FILE" \
          --output "$ARCHIVE_PATH"

chmod 600 "$ARCHIVE_PATH"

log "Шаг 5: Передача зашифрованного архива в объектное хранилище S3..."
# Использование AWS CLI с профилем резервного копирования
aws s3 cp "$ARCHIVE_PATH" "${S3_BUCKET}/paperless_backup_${TIMESTAMP}.tar.zst.gpg" \
    --storage-class STANDARD_IA \
    --no-progress

log "Шаг 6: Ротация старых локальных архивов..."
find "$BACKUP_ROOT" -maxdepth 1 -name "paperless_backup_*.tar.zst.gpg" -type f -mtime +"$RETENTION_DAYS_LOCAL" -delete

log "Шаг 7: Удаление устаревших бэкапов в S3 (старше ${RETENTION_DAYS_S3} дней)..."
EXPIRED_S3_DATE=$(date -d "${RETENTION_DAYS_S3} days ago" +"%Y-%m-%d")
aws s3 ls "${S3_BUCKET}/" | while read -r line; do
    FILE_DATE=$(echo "$line" | awk '{print $1}')
    FILE_NAME=$(echo "$line" | awk '{print $4}')
    if [[ "$FILE_DATE" < "$EXPIRED_S3_DATE" ]] && [[ -n "$FILE_NAME" ]]; then
        log "Удаление устаревшего объекта в S3: $FILE_NAME"
        aws s3 rm "${S3_BUCKET}/${FILE_NAME}"
    fi
done

log "Резервное копирование успешно завершено. Файл: $ARCHIVE_PATH"

Сделайте файл исполняемым и добавьте вызов в планировщик systemd:

sudo chmod +x /opt/paperless-ngx/scripts/backup.sh

Вместо устаревшего демона cron используйте связку systemd.service и systemd.timer, обеспечивающую запись логов в journald и изоляцию ресурсов. Создайте юнит сервиса /etc/systemd/system/paperless-backup.service:

[Unit]
Description=Paperless-ngx Disaster Recovery Encrypted Backup
After=network.target docker.service
Requires=docker.service

[Service]
Type=oneshot
Nice=19
IOSchedulingClass=best-effort
IOSchedulingPriority=7
ExecStart=/opt/paperless-ngx/scripts/backup.sh
StandardOutput=journal
StandardError=journal

Создайте таймер запуска /etc/systemd/system/paperless-backup.timer:

[Unit]
Description=Nightly execution of Paperless-ngx backup

[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=1800
Persistent=true

[Install]
WantedBy=timers.target

Активируйте таймер:

sudo systemctl daemon-reload
sudo systemctl enable --now paperless-backup.timer
sudo systemctl list-timers paperless-backup.timer

2. Пошаговый регламент Disaster Recovery (Cold-Start восстановление)

В случае отказа ноды, компрометации ОС или аппаратной аварии процедура восстановления на чистом инстансе выполняется по строго детерминированному сценарию. В качестве целевой среды развертывается KVM-инстанс tropic.host с чистым шаблоном Ubuntu 24.04 LTS и выделенным статическим IPv4.

Шаг 1: Подготовка нового хоста и установка зависимостей

Установите Docker Engine актуальной версии, пакет утилит распаковки и клиент AWS:

sudo apt-get update && sudo apt-get install -y --no-install-recommends \
    ca-certificates curl gnupg zstd awscli

# Установка официального репозитория Docker
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo 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" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

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

Шаг 2: Извлечение и расшифровка бэкапа из S3

Создайте структуру каталогов и восстановите криптографический ключ (ключ берется из защищенного хранилища секретов или парольного менеджера команды):

sudo mkdir -p /opt/paperless-ngx/restore /etc/paperless
# Запись мастер-ключа
sudo nano /etc/paperless/backup.key
sudo chmod 400 /etc/paperless/backup.key

cd /opt/paperless-ngx/restore

# Загрузка последнего актуального архива из объектного хранилища
LATEST_BACKUP=$(aws s3 ls s3://infra-backups-vault/paperless-ngx/ | sort | tail -n 1 | awk '{print $4}')
echo "Восстановление из архива: $LATEST_BACKUP"

aws s3 cp "s3://infra-backups-vault/paperless-ngx/${LATEST_BACKUP}" ./latest_backup.tar.zst.gpg

# Расшифровка и декомпрессия конвейером без записи промежуточных незашифрованных tar-файлов
gpg --decrypt --batch --passphrase-file /etc/paperless/backup.key latest_backup.tar.zst.gpg \
    | zstd -d -c \
    | tar -C /opt/paperless-ngx/restore -xf -

После распаковки в /opt/paperless-ngx/restore появятся файлы конфигурации docker-compose.yml, docker-compose.env, каталог export/ и дамп СУБД postgres_*.dump.

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

Перенесите файлы окружения в рабочий каталог /opt/paperless-ngx/:

cp /opt/paperless-ngx/restore/docker-compose.yml /opt/paperless-ngx/
cp /opt/paperless-ngx/restore/docker-compose.env /opt/paperless-ngx/
cd /opt/paperless-ngx/

# Создание локальных томов, если используются bind mounts
mkdir -p data media export consume

Запустите только инфраструктурные сервисы (БД PostgreSQL и брокер Redis):

docker compose up -d db broker

# Мониторинг готовности PostgreSQL принимать подключения
until docker compose exec db pg_isready -U paperless; do
    echo "Ожидание инициализации PostgreSQL кластера..."
    sleep 2
done

Шаг 4: Восстановление схемы и данных PostgreSQL

Существует два сценария восстановления целостности:

Вариант А: Быстрое восстановление из бинарного дампа СУБД (pg_restore)

Рекомендуется, если версия Paperless-ngx и схема базы данных не изменялись.

DUMP_FILE=$(ls -1 /opt/paperless-ngx/restore/postgres_*.dump | head -n 1)

# Передача дампа внутрь контейнера и восстановление
docker compose cp "$DUMP_FILE" db:/tmp/restore.dump
docker compose exec -T db pg_restore \
    -U paperless \
    -d paperless \
    --clean \
    --if-exists \
    --no-owner \
    --no-privileges \
    /tmp/restore.dump

docker compose exec -T db rm -f /tmp/restore.dump
Вариант Б: Каноническое восстановление через document_importer

Используется при смене архитектуры, повреждении таблиц или обновлении мажорной версии Paperless-ngx. Этот метод парсит manifest.json и восстанавливает документы с пересчетом контрольных сумм.

# Копирование экспортированных файлов в том импорта
cp -r /opt/paperless-ngx/restore/export/* /opt/paperless-ngx/export/

# Запуск контейнера веб-сервера без старта фоновых воркеров
docker compose run --rm -v /opt/paperless-ngx/export:/usr/src/paperless/export webserver document_importer /usr/src/paperless/export

Шаг 5: Перестроение поисковых индексов Whoosh и запуск стека

После физического восстановления данных запустите основной контейнер и воркеры OCR:

docker compose up -d webserver

# Принудительная полная переиндексация базы Whoosh для устранения расхождений поиска
docker compose exec -T webserver document_index reindex

# Запуск остальных сервисов (Tika, Gotenberg)
docker compose up -d

Шаг 6: Инструментальная валидация (Sanity Checks)

Убедитесь в целостности хранилища и отсутствии скрытых ошибок выполнения:

  1. Проверка целостности моделей Paperless-ngx: bash docker compose exec -T webserver document_sanity_checker Команда анализирует базу данных на наличие сиротливых записей, отсутствующих файлов оригиналов или несовпадающих хешей SHA-256. В выводе должно присутствовать подтверждение: Sanity checker detected no errors.
  2. Проверка отсутствия троттлинга и OOM-киллера в журнале ядра: bash sudo dmesg -T | grep -Ei "oom[-_]killer|out of memory" docker stats --no-stream
  3. Проверка отдачи API: bash curl -s -k -I http://127.0.0.1:8000/api/ | head -n 5 Успешный ответ HTTP/1.1 401 Unauthorized или HTTP/1.1 200 OK (в зависимости от заголовков авторизации) свидетельствует о готовности WSGI-сервера принимать внешний клиентский трафик.

Выполнение данного регламента гарантирует RPO (Recovery Point Objective) $\le 24$ часов (задается периодичностью таймера) и RTO (Recovery Time Objective) $\le 15$ минут для архива объемом 100 ГБ на дисковой подсистеме NVMe.

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

Сколько ресурсов процессора потребляет Paperless-ngx при OCR?

При импорте больших многостраничных PDF Tesseract OCR может загружать 100% выделенных vCPU. Рекомендуется использовать сервер минимум с 4 vCPU на базе AMD EPYC без оверселлинга.

Куда сохраняются исходные документы и метаданные?

Исходные файлы и превью сохраняются в постоянных томах Docker (volumes), а метаданные, теги и текстовые индексы — в базе PostgreSQL.