Когда пора масштабировать хранилище изображений
Хранилище изображений на одном сервере TrueNAS с четырьмя HDD в RAIDZ1 и 32 ГБ RAM перестаёт держать нормальные задержки, когда сервис выходит на 1500-2500 запросов в минуту и 400-500 одновременных пользователей. Диски уходят в 90-95% утилизации, среднее время ответа на чтение растёт с 10 до 100 мс.
Прямой ответ: пока %util дисков не превышает 60%, чтение укладывается в 10-15 мс, а пул ZFS заполнен меньше чем на 80%, одного сервера достаточно. Помогают SSD-кэш, recordsize под крупные файлы, сжатие lz4 и настройка Nginx. Когда диски стабильно держат 80% и выше, а сеть подходит к пределу 1 GbE, вертикальный рост упирается в цену: каждый следующий терабайт обходится дороже предыдущего, а единая точка отказа остаётся на месте.
Дальше по порядку: метрики и пороги для решения о расширении, приёмы вертикального роста, переход на MinIO или Ceph, шардирование и репликацию, узкие места в сети и метаданных, план миграции без простоя и таблица сравнения решений.
Ключевые метрики и пороговые значения
Семь метрик закрывают почти все случаи, когда нужно понять состояние хранилища изображений.
| Метрика | Норма | Пора расширяться | Чем смотреть |
|---|---|---|---|
| Утилизация дисков, %util | до 60% | стабильно больше 80% | iostat -x 1 |
| Задержка чтения и записи | 5-15 мс | больше 20 мс на 95-м перцентиле | iostat -x 1, fio |
| Заполнение пула ZFS | до 70% | больше 80%, падает скорость записи | zpool list, zfs list |
| Сетевой throughput | до 50% канала | больше 70% от 1 GbE или 10 GbE | sar -n DEV 1, iftop |
| iowait процессора | до 20% | больше 40% продолжительное время | mpstat 1, top |
| Время ответа API отдачи изображения | до 50 мс | больше 100 мс | nginx stub_status, Prometheus |
| Открытые файловые дескрипторы | до 50% лимита | больше 80% лимита | lsof, node_exporter |
Для ZFS добавьте zpool iostat -v 1: колонка busy показывает загрузку каждого диска, bw даёт реальную пропускную способность пула. Разбивку нагрузки по ядрам снимает mpstat 1, состояние контейнеров с хранилищем - docker stats. Постоянный мониторинг удобнее держать в Prometheus с Grafana или в Zabbix: разовые замеры пропускают пики, а именно пики ломают сервис.
Отдельно считайте объём метаданных. Миллион объектов в одном бакете MinIO или в одном каталоге NFS замедляет листинг и обход дерева даже при свободном месте на дисках. Если число файлов растёт быстрее объёма, узкое место окажется в метаданных, а не в дисковом I/O.
Пример из практики: когда одного сервера перестало хватать
Сервис изображений на TrueNAS SCALE: пул ZFS из четырёх HDD по 8 ТБ в RAIDZ1, 32 ГБ RAM, доступ по NFS и раздача через Nginx с локальным кэшем. На старте 200 пользователей и 300 запросов в минуту, задержка чтения 10 мс, %util дисков 35%.
Через год нагрузка выросла до 500 одновременных пользователей и 2000 запросов в минуту. Задержка поднялась до 100 мс, %util держался на 95%, iowait 45%. Первый шаг: два NVMe под SLOG и L2ARC, память до 64 ГБ, recordsize=1M и compression=lz4. Задержка вернулась к 25-30 мс. Эффекта хватило на три месяца.
Дальше упёрлись в физику: четыре диска не выдают больше 450 IOPS случайного чтения, а 1 GbE ограничивает отдачу 110 МБ/с. Вертикальный путь дал отсрочку, но не убрал единую точку отказа. Итогом стал кластер MinIO из четырёх узлов с erasure coding 8+4.
Вертикальное масштабирование: наращивание ресурсов одного сервера
Вертикальный рост - самый быстрый способ выиграть время. Вы добавляете диски в пул, память, NVMe под журналы и кэш, настраиваете параметры ядра и Nginx. Простой минимален, приложение переписывать не нужно.
Подробное сравнение платформ хранения с требованиями к дискам, сети и команде собрано в материале про TrueNAS, Ceph, ZFS и другие системы хранения, его стоит прочитать до закупки железа.
Аппаратные ограничения и экономическая целесообразность
Считайте потолок до покупки. Сервер с восемью слотами под HDD: пул RAIDZ2 из шести дисков по 16 ТБ даёт 96 ТБ сырого объёма и около 64 ТБ полезного. Два оставшихся слота разумно держать под горячий резерв или SSD. Дальше объём растёт только заменой дисков на более ёмкие, а это перезапись всего пула.
Экономика ломается на памяти и процессоре. Переход с 64 на 128 ГБ ECC с заменой всех модулей стоит как половина нового сервера. Линии PCIe тоже конечны: NVMe-кэш, сетевые карты 25 GbE и HBA делят один и тот же пул линий.
Вертикальное масштабирование оправдано, когда ожидаемый рост нагрузки не превышает двух-трёх раз, бюджет ограничен, а требования к доступности допускают плановые окна обслуживания на 1-2 часа. Если сервис обещает 99,9% доступности или объём изображений растёт на терабайты в месяц, пора смотреть на кластер.
Настройка TrueNAS и ZFS перед масштабированием
Порядок действий для пула с изображениями размером 200 КБ - 5 МБ:
- recordsize под размер файла: zfs set recordsize=1M tank/images. Значение по умолчанию 128 КБ даёт лишние операции чтения при отдаче крупных JPEG.
- Сжатие: zfs set compression=lz4 tank/images. JPEG и PNG уже сжаты, но lz4 почти не тратит CPU и экономит место на миниатюрах и служебных данных.
- Отключение лишних записей: zfs set atime=off tank/images.
- SLOG под синхронную запись: zpool add tank log mirror /dev/nvme0n1 /dev/nvme1n1. Нужен, если приложение пишет через NFS с sync=always или ведёт журнал метаданных.
- L2ARC под чтение: zpool add tank cache /dev/nvme2n1. Индекс кэша живёт в RAM и занимает 1-2% его объёма, поэтому L2ARC имеет смысл начиная с 64 ГБ памяти.
- Ядро: vm.dirty_background_ratio=5 и vm.dirty_ratio=20 через sysctl, чтобы грязные страницы не копились и не вызывали залповые сбросы на диск.
- Nginx перед хранилищем: sendfile on, tcp_nopush on, open_file_cache max=10000 inactive=60s. Это снимает с дисков часть обращений к одним и тем же файлам.
Ошибки, которые ломают всё: SLOG из одного SSD без зеркала (отказ устройства при потере питания ведёт к потере подтверждённых записей) и L2ARC на сервере с 32 ГБ RAM, где кэш вытесняет ARC. Эффект от каждой настройки подтверждайте замерами до и после.
Горизонтальное масштабирование: переход к распределённому кластеру
Кластер даёт то, чего не даст ни один одиночный сервер: продолжение работы при отказе узла и рост производительности вместе с объёмом. Добавляя узел, вы получаете диски, сеть и процессор для erasure coding одновременно.
Практических путей два. MinIO - объектное хранилище с S3 API, разворачивается за вечер. Ceph - универсальная платформа: объектное (RGW), блочное (RBD) и файловое (CephFS) хранилище в одном кластере, но с заметно более высоким порогом входа.
Ceph: архитектура и компоненты
Кластер Ceph собирается из демонов:
- MON - мониторы, хранят карту кластера и держат кворум. Нужно три штуки, пять для крупных инсталляций.
- MGR - менеджер, метрики и модули управления.
- OSD - по одному демону на диск, хранят данные и отвечают за репликацию.
- MDS - метаданные CephFS, нужен только для файлового доступа.
- RGW - шлюз S3 и Swift для объектного доступа.
Данные не привязаны к узлам. CRUSH-карта описывает иерархию (диск, хост, стойка) и вычисляет, на какие OSD положить каждую копию. Объект попадает в placement groups (PG), и каждая PG живёт на нескольких OSD. Пулы задают уровень избыточности: size 3 и min_size 2 означают три копии и продолжение записи при двух доступных OSD.
Развёртывание через cephadm: cephadm bootstrap --mon-ip 10.0.0.11, затем ceph orch apply osd --all-available-devices. Пул под изображения: ceph osd pool create images 128 128, ceph osd pool set images size 3, ceph osd pool set images min_size 2. Состояние всегда проверяйте командой ceph -s: HEALTH_OK и отсутствие PG в состоянии inactive. Минимум для продакшена: три MON, шесть OSD на разных хостах, сеть 10 GbE и отдельные NVMe под журналы OSD. Разбор компонентов и критериев выбора собран в статье про Ceph как распределённую систему хранения.
MinIO: простота и S3-совместимость
MinIO закрывает задачу хранения изображений с доступом по HTTP(S): S3 API, presigned URL, версионирование, lifecycle-политики. Одиночный узел поднимается одной командой: docker run -d -p 9000:9000 -p 9001:9001 -v /mnt/data:/data minio/minio server /data --console-address ":9001".
Распределённый режим запускается на всех узлах одинаково: minio server http://node{1...4}/data{1...8}. Данные раскладываются по дискам через erasure coding, типовые схемы избыточности 8+4 и 4+2. Отдельного RAID не требуется, диски подключаются как JBOD. Управление и диагностика идут через mc: mc admin info, mc admin heal, mc replicate. Выбор между MinIO, Ceph RGW и TrueNAS S3 по производительности и стоимости владения описан в статье про S3-совместимые хранилища.
Шардирование и репликация данных
Шардирование распределяет объекты между узлами, репликация создаёт копии. В Ceph изображение 1 МБ на входе RGW превращается в объект RADOS, который через CRUSH привязывается к набору PG, а каждая PG размещается на трёх OSD из разных хостов и стоек. Сеть получает тройной объём записи, зато отказ диска или целого узла не прерывает чтение: клиент берёт копию с живого OSD, а кластер восстанавливает потерянную копию в фоне.
В MinIO работает erasure coding: объект режется на блоки данных и блоки чётности, например 8+4. Потеря до четырёх дисков из двенадцати в наборе не приводит к потере данных, восстановление запускается командой mc admin heal. При репликации между площадками (mc replicate) копии синхронизируются асинхронно, поэтому между сайтами возможна согласованность в конечном счёте. Внутри одного кластера S3 API отдаёт сильную согласованность: запись становится видна последующим чтениям сразу.
Практическое следствие: не смешивайте роли. Отдельный пул или бак под миниатюры с высокой частотой запросов и отдельный под оригиналы с редким доступом экономят и диски, и CPU на erasure coding.
Узкие места при масштабировании хранилища изображений
Кластер меняет профиль нагрузки: то, что раньше упиралось в шпиндели, теперь упирается в сеть, процессор erasure coding и метаданные.
Сетевые задержки и пропускная способность
При репликации 3x каждый байт записи уходит в сеть трижды, при erasure coding 8+4 накладные расходы составляют около 50% плюс служебный трафик восстановления. Минимум для Ceph - 10 GbE, для высоких нагрузок 25 GbE. Пропускную способность проверяйте до развёртывания: iperf3 -c ip-узла -P 4, задержку - ping с размером пакета 9000 байт. Jumbo frames (MTU 9000) на всех коммутаторах и интерфейсах снижают нагрузку на CPU, но включение MTU только на части узлов приводит к фрагментации и падению скорости.
Клиентский и внутренний трафик репликации стоит развести по разным интерфейсам или VLAN. Иначе фоновая ресинхронизация после замены диска съедает полосу, которую ждут пользователи.
Дисковый I/O и выбор оборудования
Для ёмкости берите HDD, для журналов и кэша NVMe. Типовая конфигурация на три узла: в каждом восемь HDD по 8 ТБ и два NVMe по 1 ТБ под DB и WAL для OSD. Erasure coding дополнительно нагружает процессор, поэтому 8+4 на слабых CPU даёт меньшую скорость, чем 4+2. В MinIO диски подключаются без RAID: избыточность обеспечивает сам код, а RAID-контроллер только добавляет точку отказа и скрывает SMART-данные.
Работа с метаданными
В Ceph метаданные CephFS обслуживает MDS, и его пул разумно выносить на отдельные SSD: операции создания и переименования файлов чувствительны к задержке. В MinIO метаданные распределены по тем же дискам, что и данные, и проблема проявляется иначе: 10 млн изображений в одном бакете замедляют листинг до секунд.
Решение простое: раскладывайте объекты по префиксам. Ключ вида images/2026/09/11/ab12cd.jpg даёт равномерное распределение по внутренним наборам и быстрый list с префиксом. Для повторяющихся обращений полезен кэширующий слой: Nginx с proxy_cache или CDN перед бакетом снимает до 80% чтений с кластера.
Практические шаги по миграции на распределённое хранилище
Миграция разбивается на пять этапов, каждый проверяется отдельно.
Подготовка и развёртывание кластера
- Выберите технологию: MinIO для S3-доступа к изображениям, Ceph при потребности в блочном и файловом доступе рядом.
- Подготовьте серверы: одинаковое железо, отдельные NVMe под журналы, сеть 10 GbE и выше, синхронизация времени через NTP или chrony.
- Разверните ПО. Для MinIO достаточно бинарника и systemd-юнита с параметром ExecStart, для Ceph - cephadm bootstrap с последующим добавлением хостов.
- Соберите тестовый стенд: четыре виртуальные машины с тем же набором дисков дадут корректную оценку поведения erasure coding и репликации до закупки железа. Быстрее и дешевле поднять эти узлы в облаке: Timeweb Cloud позволяет заказать серверы и VDS с почасовой оплатой и удалить стенд сразу после проверки.
Перенос данных и переключение трафика
Синхронизация с TrueNAS в MinIO через rclone: rclone sync /mnt/tank/images s3:images --transfers 16 --checkers 32 --progress. Первый проход занимает часы, последующие инкрементальные - минуты. Перед переключением обязательно сверьте контрольные суммы: rclone check /mnt/tank/images s3:images --size-only, для критичных данных без флага size-only.
Трафик переводится на кластер через Nginx: в блоке upstream перечислите адреса всех узлов MinIO с параметром max_fails и fail_timeout, а старый сервер TrueNAS оставьте как резервный upstream с флагом backup. Переключение делайте в два шага: сначала на 1% пользователей, затем на всех. DNS с малым TTL или изменение upstream в конфигурации балансировщика дают откат за минуты.
Проверка и настройка после миграции
- Целостность: сравните выборку из 1000 файлов по хешу между старым сервером и кластером.
- Производительность: fio на запись и чтение случайными блоками, s3bench для задержек API. Сравните с метриками исходного сервера, а не с ожиданиями.
- Отказоустойчивость: погасите один узел и убедитесь, что чтение продолжается, а кластер возвращается в HEALTH_OK после включения.
- Мониторинг: Prometheus плюс Grafana с алертами на заполнение пулов выше 75%, PG в состоянии inactive, рост задержки S3 выше 100 мс и число незавершённых восстановлений.
Сравнение решений: TrueNAS, Ceph и MinIO
| Критерий | TrueNAS | Ceph RGW | MinIO |
|---|---|---|---|
| Сложность настройки | низкая, веб-интерфейс | высокая, отдельный кластер и команда | средняя, конфигурация из командной строки |
| S3 API | ограниченный | полный | полный, основная точка доступа |
| Горизонтальное масштабирование | в пределах одного узла | до сотен узлов | от 4 узлов и выше |
| Отказоустойчивость | RAIDZ, один узел | репликация или EC, до 2 отказов без потери данных | erasure coding 4+2 или 8+4 |
| Минимум для старта | 1 сервер | 3 узла, 6 OSD, 10 GbE | 4 узла или 4 диска |
| Сильная сторона | быстрый запуск, NFS и SMB из коробки | универсальность: объекты, блоки, файлы | простота и высокая скорость мелких объектов |
| Слабые места | единая точка отказа, потолок одного узла | порог входа, требования к сети и метаданным | только объектный доступ |
| Когда выбирать | небольшой проект, до 500 одновременных пользователей | Kubernetes, OpenStack, смешанные задачи | хранилище изображений с раздачей по HTTP(S) |
Разницу между объектным, блочным и файловым доступом и типичные ошибки выбора разбирает материал про типы хранилищ. Для типового сервиса изображений MinIO даёт лучший баланс: S3 API, erasure coding и предсказуемая скорость отдачи мелких файлов.
Заключение
Порядок действий для растущего хранилища изображений выглядит так. Замерьте %util дисков, задержку, заполнение пула и пропускную способность сети, сравните с порогами из таблицы. Если запас есть, выжмите текущий сервер: recordsize, lz4, SLOG и L2ARC на NVMe, кэш Nginx. Если диски держат 80% и выше, а сеть подошла к пределу канала, планируйте кластер и начинайте с тестового стенда на четырёх узлах.
Миграцию ведите через rclone с проверкой контрольных сумм и переключением трафика в Nginx двумя шагами. После перевода трафика оставьте старый сервер в резерве минимум на месяц и настройте алерты на заполнение пулов, задержку S3 и незавершённые восстановления. Общие подходы к работе с большими объёмами и переносу данных без потерь собраны в руководстве про хранение больших данных и миграцию.