====== Тюнинг XigmaNAS на слабом железе ======
Тонкие настройки производительности на старом железе.
Профиль машины: 2 ядра CPU ~1.3 ГГц, 4 ГБ RAM, 4 механических диска в пуле ZFS raidz1, загрузка с USB-флешки.
===== ZFS: ARC =====
# loader.conf
vfs.zfs.arc_max=1G
ARC по умолчанию растёт до ~половины RAM — на 4 ГБ это неприемлемо, конкурирует за память с остальными сервисами. Дальнейшее занижение ниже 1G не имеет смысла — теряется эффективность кэша без ощутимого выигрыша в свободной памяти.
===== ZFS: primarycache по датасетам =====
^ Датасет ^ primarycache ^ Причина ^
| pool root | metadata | директории/ACL, выигрывает вся файловая иерархия, включая SMB |
| датасет с активно перечитываемыми данными | all (local override) | реальный выигрыш от кэша данных |
| датасет с данными "пишем раз — не перечитываем" (архив бэкапов, транзитное шифрованное хранилище) | metadata | не вытеснять из маленького ARC то, что реально нужно |
zfs set primarycache=metadata tank
zfs set primarycache=all tank/active_data
===== ZFS: датасет под backup-репозиторий =====
^ Параметр ^ Значение ^ Причина ^
| compression | off | бэкап уже сжат средствами клиента резервного копирования |
| recordsize | 1M | крупные последовательные файлы — меньше метаданных/чек-сумм на объём |
| logbias | throughput | приоритет пропускной способности при больших последовательных записях |
| primarycache | metadata | данные пишутся раз, почти не перечитываются |
===== ZFS: датасет с зашифрованным транзитным содержимым =====
`compression=off` — если через датасет проходят уже зашифрованные блоки (см. статью про Syncthing, тип папки receive-encrypted): зашифрованные данные несжимаемы, попытка сжатия — чистые потери CPU.
===== ZFS: общие дешёвые тюнинги =====
zfs set xattr=sa redundant_metadata=most tank
* `xattr=sa` — extended attributes инлайн вместо скрытых директорий, эффективнее для SMB/ACL.
* `redundant_metadata=most` — дополнительная копия метаданных, минимальные накладные расходы.
===== Своп =====
**Не на USB-флешку** (загрузочный носитель): медленно под нагрузкой, ограниченный ресурс перезаписи флеш-памяти (свопинг ускоряет деградацию носителя, который одновременно и загрузочный).
**Не на zvol ZFS-пула**: запись в своп идёт через ZFS-стек, которому самому нужна память на I/O (ARC, транзакционные группы) — риск дедлока под давлением памяти.
Правильный вариант — raw-разделы (не zvol), собранные в RAID0 через `gstripe`, мимо ZFS-стека целиком:
Механизма создания разделов в GUI нет, поэтому нужно работать через CLI:
gpart add -t freebsd-swap -a 1m -s 2g -l swap0 ada0
gpart add -t freebsd-swap -a 1m -s 2g -l swap1 ada1
gpart add -t freebsd-swap -a 1m -s 2g -l swap2 ada2
gpart add -t freebsd-swap -a 1m -s 2g -l swap3 ada3
gstripe label -v swap0 /dev/ada0p2 /dev/ada1p2 /dev/ada2p2 /dev/ada3p2
Регистрация в XigmaNAS: System → Advanced → Swap, Type = Device, Device = `/dev/stripe/swap0`.
**Ограничение ZFS raidz:** ужать раздел под живым raidz-vdev нельзя (расширение поддерживается — RAIDZ expansion, обратная операция нет). Разметку под своп на тех же дисках, что и raidz-пул, закладывать заранее при создании пула — иначе единственный путь получить место под неё, это пересоздать пул.
Объём: ориентир "2× RAM" для legacy-железа; если нужен kernel core dump — своп не меньше объёма RAM.
===== ZFS: latency vs throughput =====
На слабом CPU/дисках приоритет — короче худший случай задержки отклика, а не пиковая пропускная способность. Актуально при потоке мелких операций записи (много мелких файлов разом).
^ Параметр ^ Дефолт ^ Значение ^ Требует ребута ^ Причина ^
| vfs.zfs.txg.timeout | 5с | 1с | Нет (live sysctl) | Короче окно накопления транзакционной группы — короче потенциальный стопор на её фиксации |
| vfs.zfs.dirty_data_max_percent | 10% от RAM | 5% | Да — read-only tunable, только через loader.conf | Меньше данных копится в памяти перед принудительным сбросом на диск |
| vfs.zfs.vdev.async_write_max_active | 10 | 3 | Нет (live sysctl) | Глубина очереди записи на vdev — на старых дисках без развитого NCQ большая очередь удлиняет отклик для операций, ожидающих за длинной очередью |
| vfs.zfs.vdev.sync_write_max_active | 10 | 3 | Нет (live sysctl) | То же для синхронных записей |
# loader.conf / sysctl.conf
vfs.zfs.txg.timeout=1
vfs.zfs.dirty_data_max_percent=5
vfs.zfs.vdev.async_write_max_active=3
vfs.zfs.vdev.sync_write_max_active=3
**arc_min — не трогать.** Может казаться логичным зафиксировать минимальный размер ARC, чтобы не проседал под нагрузкой, но на машине с и так туго рассчитанной памятью это забирает у ARC возможность сжаться и отдать память другим процессам именно тогда, когда она нужнее всего (при пиковой нагрузке). Оставлять `arc_min` неактивным — ARC пусть сам решает, когда сжаться.
===== vnode-лимит при большом числе мелких файлов =====
`kern.maxvnodes` — верхняя граница живых vnode в системе. Если по дереву датасета проходит live filesystem watcher (см. статью про тюнинг Syncthing — секция про fsWatcher/kqueue) или просто идёт операция над деревом с сотнями тысяч мелких файлов/директорий, число vnode может подступить к лимиту — система начинает агрессивно вытеснять vnode, что способно сериализовать файловые операции по всей системе, а не только у процесса-триггера.
Диагностика: `sysctl kern.maxvnodes vfs.numvnodes` — если `numvnodes` устойчиво держится близко к `maxvnodes` во время нагрузки, лимит тесен. Поднять его — не панацея (каждый vnode — расход памяти ядра), но если основная причина нагрузки на дерево устранена на уровне приложения (например, отключением live-watcher), поднимать лимит обычно не требуется.
===== Проверка: конфиг ≠ реальность =====
Значение в GUI/loader.conf/sysctl.conf не гарантированно применяется на живой системе. Встречается:
* тюнинг на sysctl, не существующий в текущей версии FreeBSD/OpenZFS — тихий no-op;
* значение в loader.conf, не совпадающее с реальным live-значением sysctl.
Проверка: `sysctl <параметр>` на живой системе после любой правки loader.conf/sysctl.conf — не доверять только факту наличия строки в конфиге.
===== Расписание акустики дисков =====
AAM (Automatic Acoustic Management), уровень 1–127: 1 — минимальный шум, 127 — максимальная производительность/шум.
ataidle /dev/ada0 # проверка поддержки и текущего значения
ataidle -A 1 /dev/ada0 # тихий режим
ataidle -A 127 /dev/ada0 # полная производительность
Расписания в GUI нет — через cron:
0 23 * * * root for d in ada0 ada1 ada2 ada3; do /usr/local/sbin/ataidle -A 1 /dev/$d; done
0 6 * * * root for d in ada0 ada1 ada2 ada3; do /usr/local/sbin/ataidle -A 127 /dev/$d; done
===== Cron: подводные камни =====
* Задачи с интервалом вида `@300` (в отличие от задач с фиксированным временем `H:M`) отсчитываются **от момента старта демона cron**, а не от часов. После `service cron restart` расписание таких задач сдвигается — первый цикл наступит не сразу, а через полный интервал от рестарта.
* Пустой узел `` в секции `//cron` конфига (пустая заготовка, которая может остаться в XML после операций через GUI) ломает страницу `System → Advanced → Cron`, как только реальных задач становится больше одной — список задач и кнопка добавления перестают отображаться. Лечится удалением пустого узла (`xml ed -d "//cron/job[not(*)]"`), сами задачи в `/etc/crontab` при этом продолжают работать штатно — ломается только рендер GUI.
* После правки `config.xml` через `xmlstarlet` — обязательна верификация чтением значения обратно из файла **и** из живого `/etc/crontab`/`sysctl`, exit-код команды правки не гарантирует, что изменение реально применилось.
===== Параметры, оставленные без изменений =====
^ Параметр ^ Значение ^ Причина не менять ^
| APM диска | максимальная производительность | сервер с бэкапами, экономия питания не приоритет |
| HDD standby timer | задаётся в **минутах**, не в секундах | при частом фоновом обращении к дискам (мониторинг раз в несколько минут) порог простоя физически не достигается |
| Power Mode CPU | adaptive | не проседает на всплесках нагрузки (в отличие от minimum), не расходует лишнее (в отличие от maximum) |