- Исходная конфигурация
- Часть 1: Первые признаки проблемы
- Часть 2: Диагностика — поиск узкого места
- Анализ производительности
- Часть 3: Анализ настроек виртуальной машины
- Часть 4: Вскрытие проблемы — ZFS как главный виновник
- Часть 5: Кардинальное решение — переход с ZFS на LVM
- Часть 6: Результаты в виртуальной машине после оптимизации
- Часть 7: Сравнительный анализ производительности
- Часть 8: Дополнительные оптимизации
- Часть 9: Ошибки, которые нужно избегать
- Восстановление диска после ошибочного теста
- Часть 10: Практические рекомендации
- Часть 11. Альтернативные методы повысить скорость внутри ВМ
- Часть 12: Финальные выводы
- Заключение
В один из дней решил провести установку Zabbix агентов по хостам виртуализации.
И сразу один из хостов начал сыпать уведомлениями :
Linux: sda: Скорость отклика при запросах на чтение/запись с диска слишком высока
Всё бы нечего, но виртуальная машина расположена на NVME диске.
Не думал что процесс решения затянется на долго:
Docker-контейнеры на виртуальной машине Ubuntu работали неоправданно медленно. Система мониторинга показывала высокий iowait ( 26%-90%), а производительность диска была катастрофически низкой. Изначальные тесты показывали всего 12 000 IOPS при 4K случайных операциях — это ничтожно мало для NVMe диска.
В этой статье я подробно опишу весь процесс диагностики, выявления проблем и оптимизации, который привел к увеличению производительности в 2.8 раза (до 34 000 IOPS).
Исходная конфигурация
-
Хост: Сервер Proxmox VE 9.0
-
Диск: ADATA 2TB NVMe
- Адаптер Asus HYPER M.2 X16 GEN 4 CARD
-
Виртуальная машина: Ubuntu 22.04, 8 vCPU, 17 ГБ ОЗУ
-
Нагрузка: Множество Docker-контейнеров Jira, Confluence и т.д
-
Файловая система хоста Proxmox : NVME диск для ВМ на ZFS (один диск!) с целью будущего расширения в 2 диска
Часть 1: Первые признаки проблемы
Мониторинг выявил аномалии
Первым звоночком стал высокий показатель %iowait в системе:
-
%iowait= 26 – в пиках 90 % — система слишком много ждала операций ввода-вывода -
aqu-sz(average queue size) = 3.21 — накопившаяся очередь запросов -
%utilдиска = 38%-75% — диск был постоянно занят
Первый тест производительности в VM
Я запустил комплексный тест fio для имитации нагрузки Docker:
#Если данной утилиты нет, требуется её установить, так же дополнительно сразу установим все нужные для работы пакеты
apt install fio iotop -y
fio --name=docker_prod --ioengine=libaio --rw=randrw --bs=4k --iodepth=32 --numjobs=8 --size=4G --runtime=60 --time_based --group_reporting
Результаты были шокирующими:
Run status group 0 (all jobs):
READ: bw=46.6MiB/s (48.9MB/s), io=6054MiB (6348MB), run=129826-129826msec
WRITE: bw=46.7MiB/s (49.0MB/s), io=6062MiB (6357MB), run=129826-129826msec
Ключевые проблемы:
-
Всего 12 000 IOPS для 4K случайных операций
-
Пропускная способность всего 47 МБ/с
-
Задержки 99-го перцентиля: 60-200 мс (должно быть < 10 мс)
-
Тест длился 129 секунд вместо 60 — система не справлялась
Для NVMe диска это катастрофически низкие показатели. Ожидались значения на порядок выше.
Часть 2: Диагностика — поиск узкого места
Тестирование физического диска на хосте
Чтобы исключить проблему с железом, я проверил производительность NVMe диска напрямую на хосте Proxmox:
# Создаем временный файл для теста
fallocate -l 4G /tmp/fio_test.tmp
# Запускаем тест на файле
fio --filename=/tmp/fio_test.tmp --name=host_test --ioengine=libaio --direct=1 --rw=randrw --bs=4k --iodepth=32 --numjobs=8 --size=4G --runtime=60 --time_based --group_reporting
# Удаляем файл после теста
rm /tmp/fio_test.tmp
Если нужно тестировать именно диск – монтируем его:
# Создаем раздел, файловую систему и монтируем
sudo mkfs.ext4 /dev/nvme0n1p1 # ВНИМАНИЕ: это сотрет данные на разделе!
sudo mount /dev/nvme0n1p1 /mnt/test
# Тестируем на смонтированной ФС
fio --filename=/mnt/test/fio_test.tmp --name=host_test --ioengine=libaio --direct=1 --rw=randrw --bs=4k --iodepth=32 --numjobs=8 --size=4G --runtime=60 --time_based --group_reporting
Результат на хосте:
Run status group 0 (all jobs):
READ: bw=385MiB/s (404MB/s), io=22.6GiB (24.2GB), run=60018-60018msec
WRITE: bw=385MiB/s (404MB/s), io=22.6GiB (24.2GB), run=60018-60018msec
-
98 600 IOPS — Хороший показатель
-
385 МБ/с пропускной способности
-
Задержки 99%: 10-19 мс
Вывод: Берем как пример NVME + ZFS и идем дальше .
Анализ производительности
| Параметр | На хосте (RAW) | В VM | Разница |
|---|---|---|---|
| 4K Random IOPS | 98 600 | 12 000 | 8.2x медленнее |
| Пропускная способность | 385 МБ/с | 47 МБ/с | 8.2x медленнее |
| Задержки 99% | 10-19 мс | 60-200 мс | 3-10x хуже |
Разница в 8 раз явно указывала на проблемы с виртуализацией или файловой системой.
Часть 3: Анализ настроек виртуальной машины
Проверка конфигурации VM в Proxmox
Я проверил ключевые настройки виртуальной машины:
# Проверка типа контроллера диска
qm config <VMID> | grep -E "(scsi|virtio|cache)"
# Проверка настроек ЦПУ
qm config <VMID> | grep -E "(cpu|numa)"
Настройки казались правильными:
-
Тип контроллера: VirtIO SCSI
-
Кэширование: writeback (рекомендуется) – быстрее, риски при отключении питания
-
ЦПУ: host (прямая передача инструкций процессора)
-
NUMA: включена
# Остановите VM
qm stop 100
# Отредактируйте конфигурацию
nano /etc/pve/qemu-server/100.conf
# Добавьте/измените следующие параметры:
args: -device virtio-scsi-pci,id=scsi0,num_queues=8
boot: order=scsi0
scsihw: virtio-scsi-single
scsi0: local-zfs:vm-100-disk-0,cache=writeback,discard=on,iothread=1,ssd=1
cpu: host,flags=+aes
numa: 1
machine: q35
Часть 4: Вскрытие проблемы — ZFS как главный виновник
Анализ настроек ZFS
Ключевой момент: на хосте Proxmox диск был переведен в ZFS. Я проверил настройки пула:
zfs get all ADATA-2TB | grep -E "(sync|atime|compression|recordsize|logbias|primarycache)"
Обнаружились критические проблемы в настройках:
ADATA-2TB recordsize 128K default
ADATA-2TB compression zstd local
ADATA-2TB atime off local
ADATA-2TB primarycache all default
ADATA-2TB logbias latency default
ADATA-2TB sync standard default
Почему эти настройки убивали производительность:
-
recordsize=128K— слишком большой для 4K операций Docker.
Каждая 4K операция тратила 128K места → 32x overhead. -
compression=zstd— сжатие добавляло нагрузку на CPU и задержки. -
sync=standard— каждая операция записи ждала подтверждения. -
logbias=latency— оптимизировано для задержек, но убивало пропускную способность. -
primarycache=all— кэшировал данные в RAM вместо метаданных.
Оптимизация настроек ZFS
Я попытался оптимизировать ZFS для нагрузки Docker:
После перезагрузки VM произошло небольшое улучшение, но не радикальное.
Часть 5: Кардинальное решение — переход с ZFS на LVM
Почему ZFS с одним диском — плохая идея
ZFS — прекрасная файловая система, но она создана для:
-
Многодисковых массивов
-
Гарантии целостности данных
-
Снапшотов и репликации
Для одного диска с нагрузкой множества VM/Docker ZFS добавляет:
-
Избыточные накладные расходы (COW, транзакционные журналы)
-
Высокую нагрузку на CPU
-
Большие задержки при синхронных операциях
План перехода на LVM
Я принял решение перейти на LVM + XFS как более подходящее решение для моего сценария.
Шаг 1: Резервное копирование всех VM
# Создание бэкапов всех виртуальных машин
for vm in $(qm list | awk 'NR>1 {print $1}'); do
echo "Backing up VM $vm..."
vzdump $vm --compress zstd --mode stop --storage local
done
Либо делаем миграцию всех ВМ на другие диски
Убеждаемся что дисков ВМ и данных не осталось на диске

Шаг 2: Остановка ZFS пула
# Экспорт ZFS пула
zpool export ADATA-2TB
Если не получается (пул занят): # 1. Остановить все VM использующие этот storage 2. Или перезагрузить сервер 3. Если в shell вы находитесь в этом диске , перейдите на другой диск
Шаг 3: Очистка диска и создание LVM структуры
# Очистка диска (ВНИМАНИЕ: удаляет все данные!)
wipefs -a /dev/nvme0n1
# Создание GPT таблицы
parted /dev/nvme0n1 mklabel gpt
# Создание раздела на весь диск
parted /dev/nvme0n1 mkpart primary 1MiB 100%
parted /dev/nvme0n1 set 1 lvm on
# Настройка LVM
pvcreate /dev/nvme0n1p1
vgcreate vm-nvme /dev/nvme0n1p1
lvcreate -l 100%FREE -T vm-nvme/thinpool
# Добавление в Proxmox как хранилище
pvesm add lvmthin nvme-fast --vgname vm-nvme --thinpool thinpool
Проверка производительности после перехода на LVM
После перехода я снова запустил тест на хосте:
Результат с LVM:
Run status group 0 (all jobs):
READ: bw=603MiB/s (632MB/s), io=35.3GiB (37.9GB), run=60007-60007msec
WRITE: bw=603MiB/s (632MB/s), io=35.3GiB (37.9GB), run=60007-60007msec
Улучшение на хосте:
-
IOPS: 154 000 (было 98 600 с ZFS) — +56%
-
Пропускная способность: 603 МБ/с (было 385 МБ/с) — +57%
-
Диск загружен на 100% (было 76%)
Часть 6: Результаты в виртуальной машине после оптимизации
После восстановления VM из бэкапов и настройки на новом LVM хранилище, я запустил финальный тест:
fio --name=docker_prod --ioengine=libaio --rw=randrw --bs=4k --iodepth=32 --numjobs=8 --size=4G --runtime=60 --time_based --group_reporting
Финальные результаты в VM:
Часть 7: Сравнительный анализ производительности
До и после оптимизации ВМ
| Параметр | ZFS (исходно) | LVM (после) | Улучшение |
|---|---|---|---|
| 4K Random IOPS | 12 000 | 34 100 | +184% (2.8x) |
| Пропускная способность | 47 МБ/с | 133 МБ/с | +183% (2.8x) |
| Задержки 50% | ~60-200 мс | ~3.3 мс | 18-60x лучше |
| Задержки 99% | 60-200 мс | ~7.8 мс | 8-25x лучше |
| Disk util в VM | 54% | 99.95% | диск используется полностью |
Эффективность виртуализации
| Параметр | На хосте (LVM) | В VM (LVM) | Эффективность |
|---|---|---|---|
| 4K Random IOPS | 154 000 | 34 100 | 22% |
| Пропускная способность | 603 МБ/с | 133 МБ/с | 22% |
22% эффективности — это нормально для виртуализированной среды
Часть 8: Дополнительные оптимизации
Внутри виртуальной машины (Ubuntu)
# Установка планировщика 'none' для NVMe
echo "none" | sudo tee /sys/block/sda/queue/scheduler
# Увеличение глубины очереди
echo 1024 | sudo tee /sys/block/sda/queue/nr_requests
# Настройка readahead
echo 256 | sudo tee /sys/block/sda/queue/read_ahead_kb
# Оптимизация параметров ядра
cat >> /etc/sysctl.conf << EOF
vm.dirty_ratio = 10
vm.dirty_background_ratio = 5
vm.swappiness = 1
EOF
sudo sysctl -p
На хосте Proxmox (оптимизация VM)
В конфигурацию VM добавлены параметры:

args: -device virtio-scsi-pci,id=scsi0,num_queues=8
iothread: 1
discard: on
ssd: 1
Часть 9: Ошибки, которые нужно избегать
Опасность тестирования на RAW устройствах
В процессе диагностики я случайно уничтожил данные на диске командой:
# ОПАСНО: записывает случайные данные на RAW устройство!
fio --filename=/dev/nvme0n1 --rw=randrw --bs=4k --iodepth=32 --numjobs=8 --size=4G
Эта команда перезаписала:
-
GPT таблицу разделов
-
LVM метаданные
-
Все данные на диске
Безопасные альтернативы:
# 1. Только чтение (безопасно)
fio --filename=/dev/nvme0n1 --rw=randread
# 2. Тест в файле (безопасно)
fallocate -l 10G /tmp/testfile.bin
fio --filename=/tmp/testfile.bin --rw=randrw
rm /tmp/testfile.bin
# 3. Тест с direct=0 (кэшированный, медленнее но безопаснее)
fio --filename=/dev/nvme0n1 --rw=randrw --direct=0
Восстановление диска после ошибочного теста
Если вы всё же перезаписали диск, восстановление структуры:
Часть 10: Практические рекомендации
Для каких сценариев что использовать
ZFS подходит для:
-
Многодисковых массивов (RAID-Z)
-
Систем, где важна целостность данных
-
Снапшотов и репликации
-
Хранения больших файлов (видео, базы дампов)
LVM + XFS подходит для:
-
Одиночных дисков
-
Виртуальных машин и контейнеров
-
Нагрузки с множеством мелких операций
-
Когда важна максимальная производительность
Часть 11. Альтернативные методы повысить скорость внутри ВМ
Данную информацию вынес в отдельную статью ссылка
Мониторинг производительности
Регулярные проверки:
# Быстрый мониторинг
iostat -x 1
# Проверка очереди запросов
cat /sys/block/sda/queue/nr_requests
# Мониторинг в реальном времени
iotop -ao
# Проверка задержек
ioping -c 10 /
Часть 12: Финальные выводы
Что было сделано:
-
Выявлена проблема — низкая производительность диска в VM (12 000 IOPS)
-
Исключены проблемы с железом — физический диск работал нормально (98 600 IOPS)
-
Обнаружены неоптимальные настройки ZFS — recordsize=128K, compression=zstd, sync=standard
-
Принято решение о переходе на LVM — как более подходящее решение для одного диска
-
Проведена миграция с полным бэкапом и восстановлением
-
Достигнут результат — 34 100 IOPS (+184% к исходному)
Ключевые уроки:
-
ZFS не всегда оптимален — для одиночного диска с нагрузкой VM/Docker он добавляет лишние накладные расходы
-
Тестируйте безопасно — никогда не запускайте тесты записи на RAW устройства
-
Измеряйте на всех уровнях — сравнивайте производительность на хосте и в VM
-
Настройки имеют значение — правильные параметры recordsize, compression, sync критически важны
Для Docker-хоста итоговые показатели отличные:
-
34 000 IOPS достаточно для 30-50 активных контейнеров
-
Задержки 3-8 мс — отлично для веб-приложений и баз данных
-
133 МБ/с пропускной способности — хорошо для резервного копирования и миграций
Заключение
Оптимизация производительности диска — комплексная задача, требующая понимания всех уровней стека: от физического диска через гипервизор до гостевой ОС. В моем случае переход с ZFS на LVM дал почти 3-кратный прирост производительности, что полностью решило проблему с медленной работой Docker-контейнеров.
Главный вывод: нет универсальных решений. ZFS — прекрасная файловая система, но не для всех сценариев. Для одиночного NVMe диска с нагрузкой множества виртуальных машин LVM с thin provisioning оказался значительно более эффективным решением.
Эта история также показывает важность регулярного тестирования производительности и мониторинга. Проблема была обнаружена до того, как пользователи начали жаловаться на медленную работу, что позволило провести оптимизацию планово, с созданием бэкапов и минимальным временем простоя.






