С коллегой решили внедрить виртуализацию, чтобы идти в ногу со временем и упростить свои административные задачи. Из бесплатного и надёжного выбрали Proxmox VE. Debian под капотом вселяет уверенность и ближе по идеологии.
Всплыли, и не раз, проблемы оборудования с Proxmox VE, которые пока удаётся решать.
Так как в России всё делается через универсальный интерфейс /dev/ass, то умная мысль - купить под ноды с Proxmox VE более скоростные жёсткие диски и Интел гигабитные сетевые карты - пришла опосля.
Чтобы не ждать, решил начать создавать виртуальные сервера своей мечты. И тут совершил роковую ошибку, делая свои первые шаги. Все виртуальные сервера созданы с виртуальными жёсткими дисками IDE, а не используя VirtIO. Банально мало начитался перед началом внедрения и многое постигалось на своих ошибках. Единственный плюс из этой ситуации - усвоение урока будет основательным и надолго.
Сел оптимизировать под быстродействие виртуальные сервера или, как правильно нужно говорить, гостевые операционные системы (guest OS) и понял, что не использование VirtIO - это фейл. Многочисленные тесты в Интернете показывают хороший прирост быстродействия I/O дисков и сетевух при использовании VirtIO и глупо её не использовать. То есть без VirtIO чтобы не делалось будет казаться мелким и ничтожным.
Я себе явно усложнил ситуацию, получив этап "миграция с IDE на VirtIO в KVM". Но данный этап оказался не таким сложным. В Proxmox VE в папке /etc/pve/qemu-server/ лежат файлы conf по-одному на каждый виртуальный сервер. Виртуальная машина test, созданная сразу с VirtIO, показала какой должен быть в итоге conf.
... bootdisk: virtio0 virtio0: local:105/vm-105-disk-1.qcow2,size=10G ...
Сделав резервную копию и выключив виртуальные сервера с гостевыми Ubuntu 12.04 Server LTS, подправил конфигурационные по аналогии с test. Виртуальные сервера стартанули нормально и, благодаря UUID, ничего нигде править было не нужно. Создался /dev/vda вместо /dev/sda и вывод sudo blkid
, а так же скорость hdparm -t /dev/vda
показали, что всё прошло гладко и теперь используется VirtIO.
Сетевые карты на использование VirtIO были выставлены через веб интерфейс Proxmox VE и вывод lspci |grep Virtio
стал показывать использование VirtIO и для виртуального жёсткого диска и для виртуальных сетевых карт.
00:03.0 Unclassified device [00ff]: Red Hat, Inc Virtio memory balloon 00:0a.0 SCSI storage controller: Red Hat, Inc Virtio block device 00:12.0 Ethernet controller: Red Hat, Inc Virtio network device 00:13.0 Ethernet controller: Red Hat, Inc Virtio network device
Что ещё было оптимизировано?
Я выбрал всё таки файловую систему ext4, хотя многие тесты упорно рекомендуют ext3. Журналирование в файловых системах, естественно, не прибавляет скорости в I/O, но с ним можно разобраться, а сама ext4, помимо журнала, тоже получила множество улучшений и жаль было бы их терять. Ведь Google не зря перешёл с ext3 на ext4 без журнала!
К этому времени мне уже было известно о благотворном влиянии отключения шлагбаумов barrier=0 (или nobarrier). Это давным давно было описано у меня в Ускорении файловой системы.
Что за шлагбаумы? Код файловой системы обязан перед созданием записи фиксации [журнала] быть абсолютно уверенным, что вся информация о транзакции помещена в журнал. Просто делать запись в правильном порядке недостаточно; современные диски имеют кэш большого объёма и меняют порядок записи для оптимизации производительности. Поэтому файловая система обязана явно сообщить диску о необходимости записать все журнальные данные на носитель перед созданием записи фиксации; если сначала будет создана запись фиксации, журнал может быть повреждён. Блокирующая система ввода-вывода ядра предоставляет такую возможность благодаря использованию механизма ?"шлагбаумов" (barriers); проще говоря, "шлагбаум" запрещает запись любых блоков, посланных после него, до того момента, как всё, что было прислано перед "шлагбаумом", будет перенесено на носитель. При использовании "шлагбаумов" файловая система может гарантировать, что всё, что находится на диске, целостно в любой момент времени. Отключая шлагбаум barrier=0, мы ускоряем операции записи на разделы ext4.
В одной из англоязычных статей, её автор приводил тесты для файловых систем для MySQL сервера и с barrier=0 ext4 была не хуже ext3.
Кроме barrier=0, добавлены такие опции как relatime и commit=600.
relatime - атрибут времени доступа (atime) обновляется, но только в том случае, если изменились данные файла (атрибут mtime) или его статус (атрибут ctime).
commit – время между сбросами буферов на диск.
Естественно, такие манипуляции проводятся, так как точно будет использоваться источник бесперебойного питания. То есть UPS это один из ускоряющих элементов сервера.
Где-то в глубине души я знал, что с планировщиком CFQ нам не по пути, он сделан под углом честного дележа доступа к диску и пытается сортировать очередь к диску, зная про головки HDD и минимизацию их перемещения. Но имеет ли смысл что-то сортировать у виртуального диска?
Вначале я выставил планировщик noop, который является простейшим планировщиком и ничего не сортирует. Но статьи уважаемой IBM по лучшему использованию KVM утверждают, что лучше использовать планировщик deadline и что так поступает сама IBM. Да и Canonical вместо CFQ стала использовать deadline.
Пока решил довериться IBM и Canonical и выставить deadline по умолчанию.
IBM считает, и наверное не безосновательно, что для бо́льшой производительности нужно отключить zone_reclaim_mode и выставить swappiness=0.
NUMA (Non-Uniform Memory Access — неравномерный доступ к памяти или Non-Uniform Memory Architecture — Архитектура с неравномерной памятью) — схема реализации компьютерной памяти, используемая в мультипроцессорных системах, когда время доступа к памяти определяется её расположением по отношению к процессору.
Оборудование большинства операционных систем - это системы NUMA, со своей штрафом-платой (NUMA penalty) при удалённом доступе к памяти.
NUMA penalty = CPIremote / CPIlocal , где CPI - cycles per instruction.
Если NUMA penalty становится высокой, то операционная система осуществляет очистку (zone reclaim).
Для примера, операционная система выделяет память в ноде NUMA, но нода NUMA полна. В таком случае, операционная система начинает очистку памяти локальной ноды NUMA, а не выделяет немедленно память на удалённой ноде NUMA. Выигрыш в выделении памяти на локальной ноде перевешивает недостаток скорости при очистке памяти (reclaiming memory).
Однако, в некоторых ситуациях очистка памяти (reclaiming memory) снижает производительность до такой степени, что верно и обратное. Иными словами, выделение памяти на удалённой ноде NUMA будет быстрее, чем очищение памяти на локальной ноде.
Гостевые операционные системы могут вызывать zone reclaim в следующих случаях:
Настройка huge pages, использование KSM и отключение zone reclaim эти шаги IBM считает хорошей практикой при использовании виртуализации KVM.
Для отключения zone_reclaim_mode на KVM хосте, нужно убедиться в текущем состоянии дел.
cat /proc/sys/vm/zone_reclaim_mode
Моя Proxmox VE нода выдала 0 и значит разработчики Proxmox VE уже решили нашу задачу. Если у вас не ноль, то в /etc/sysctl.conf нужно указать vm.zone_reclaim_mode=0 и рестарт.
Дефолтное значение swappiness = 60. Можно выставить от 0 до 100. Когда выставлен swappiness = 0 на системах Intel Nehalem, то менеджер виртуальной памяти удаляет страничный кеш (page cache) и кеш буфера (buffer cache), а не скидывает программу из памяти в swap.
Платформа Intel Nehalem использует расширенные таблицы страниц Extended Page Table (EPT). EPT не устанавливает бит доступа к страницам, которые используются гостевыми операционными системами. Таким образом, менеджер виртуальной памяти Linux не может использовать свой алгоритм LRU (least recently used), чтобы точно определить какие страницы не нужны. Точнее сказать, алгоритм LRU не может точно определить какие страницы в гостевых операционных системах являются лучшими кандидатами на вытеснение в swap. В это ситуации, во избежание ненужного своппинга лучше выставить swappiness=0.
Системы с процессорами Advanced Micro Devices (AMD) и с технологией Nested Page Table (NPT) не имеют описанной выше проблемы.
Узнайте текущее значение cat /proc/sys/vm/swappiness
Для KVM хоста в /etc/sysctl.conf нужно указать vm.swappiness=0.
Приложения, которые используют много непрерывных участков памяти, получат максимальную выгоду от huge pages, поскольку будет генерироваться меньше промахов Translation Lookaside Buffer (TLB).
Процессор чаще находит нужное отображение в TLB и реже сканирует таблицу страниц.
Таким образом, huge pages увеличивает производительность приложений, которые используют большие и непрерывные участки памяти.
Приложения, которые используют маленькие и непрерывные блоки памяти, не получат от huge pages выгоды.
Huge pages нужно настраивать в ручную и знать необходимость приложения в огромных блоках памяти. По этой причине, пока не стал связываться с ними.
Исторически так сложилось, что каждый из физических серверов под конкретную задачу обладал установленным на нём сервере MySQL для хранения данных. Чтобы удобно было администрировать задачу через веб интерфейс нужен веб сервер Apache. Другими словами сложилась ситуация - всё свое ношу с собой.
Так было с DHCP сервером - самописная веб морда, записи MAC и IP в MySQL, PHP, Perl, Apache. Такая же участь у почтовика - Postfix, учётные записи в MySQL, PostfixAdmin, PHP, Apache. Биллинг Интернет трафика - Squid, Squid2Mysql, учётные записи в MySQL, PHP, Apache.
Тяжело вырваться из этого круга, но хотелось сделать единый MySQL сервер с единым Apache и местом обитания всех веб морд. Создал виртуальную машину mysqlserver, которая обладала сервером MySQL и веб сервером Apache.
Единый MySQL позволит легко манипулировать доступом к базам данным. Но с другой стороны, я опасался эффекта "все яйца в одной корзинке". Зависимость от отдельного MySQL сервера могла сделать неработоспособной какую-либо службу при недоступности MySQL сервера.
Частично негативный эффект от единой точки отказа был сглажен. Например, DHCP сервер берёт записи из базы MySQL и формирует свой dhcpd.conf при изменениях в записях MAC и IP адресов. При отказе MySQL физически не изменить базу данных через веб интерфейс и, следовательно, DHCP сервер не увидит изменений и будет работать с ранее сформированным dhcpd.conf.
Новый Squid 3 изменил представление о сохранении своих логов вместо файлов в базу данных MySQL.
logformat squid_mysql %ts.%03tu %6tr %>a %Ss %03Hs %<st %rm %ru %un %Sh %<A %mt
access_log daemon:/etc/star_scripts/squid/log_mysql_daemon.conf squid_mysql
logfile_daemon /etc/star_scripts/squid/log_mysql_daemon.pl
Пришлось по другому запихивать логи в базу, нежели как раньше скриптом из проекта squid2mysql. Нашёл perl скрипт труд итальянца Marcello Romani, но у него своя схема базы данных и шикарные представления view, которые создают отличные запросы на все случаи жизни прокси сервера. Чтобы веб морда Squid2Mysql ничего не заметила и продолжала нормально свою работу, не смотря на новую схему от итальянца, я просто сделал нужный view и сопоставил новые поля из новой таблицы в старые поля старой таблицы. При недоступности MySQL скрипт от итальянца работает идеально и Squid продолжает работу и предоставляет доступ в Интернет. Единственное, что трафик не учитывается, но это лучший расклад при гипотетической неработоспособности отдельного сервера MySQL. Лучше терять логи, но дать доступ в Интернет.
То есть DHCP и Squid не смертельно связаны с MySQL и могут некоторое время прожить и без него. Но Postfix с MySQL связаны более жёстко. В MySQL хранятся почтовые пользователи и Postfix при своей работе постоянно обращается к базе данных. Для защиты от спама и вирусов, Postfix связан с Amavis + SpamAssassin, которые частично используют базу данных, чтобы получать белые списки людей, которых нужно исключить из защиты.
Решил поиграть в великого оптимизатора MySQL и получить порцию знаний по этой теме. Почитал замечательную книгу авторства Бэрон Шварц, Петр Зайцев, Вадим Ткаченко, Джереми Заводны, Арьен Ленц, Дерек Боллинг "MySQL. Оптимизация производительности". Для себя открыл, неведомый мне прежде, режим отлаженной записи ключей DELAY_KEY_WRITE. Это ускоряет многочисленные INSERT в таблицу, так как с этой опцией индексы хранятся только в памяти и записываются на диск в последнюю очередь, по команде FLUSH TABLES или по завершению mysqld.
Для таблицы access_log, в которую записываются огромные логи Squid 3 скриптом итальянца, я сделал ALTER TABLE access_log DELAY_KEY_WRITE = 1, помня об недостатках DELAY_KEY_WRITE и обязательному наличию UPS.
Прочёл на Хабре срач про сегментированные таблицы, которые калькой с английского часто называют партицированием. Кто-то говорил о сырости и глюкавости данного решения, дескать написанное криворукими индийцами за горстку риса. Кто-то утверждает, что это бред сивой кобылы.
Смысл в том, что сегментированная таблица позволит призрачно разделить таблицу по какому-либо критерию на призрачные таблицы с меньшим количеством строк. Меньше количество строк = меньше нагрузка при запросах. Меня бы устроило разделение access_log по дате месяца, так как жизнь показала, что база очень быстро распухает и работа с ней замедляется.
Профи советуют сегментировать примерно так.
ALTER TABLE access_log
PARTITION BY RANGE( TO_DAYS(date_day) )
(
PARTITION y2013m01 VALUES LESS THAN( TO_DAYS('2013-01-01') ),
PARTITION y2013m02 VALUES LESS THAN( TO_DAYS('2013-02-01') ),
...
PARTITION y2020m11 VALUES LESS THAN( TO_DAYS('2020-11-01') ),
PARTITION y2020m12 VALUES LESS THAN( TO_DAYS('2020-12-01') ),
PARTITION pcatchall VALUES LESS THAN (MAXVALUE)
);
Не большой совет от гуру - создать секции заранее, чтобы потом было проще. Вместо ... много много строк по аналогии от 2013 до 2020 года и все их месяцы.
Теперь EXPLAIN PARTITIONS SELECT покажет, что использование WHERE date_day будет затрагивать определённую секцию, что благотворно влияет на быстродействие.
Но принявшись за сегментирование, меня ждал fail. Как я понял, банально попал под ограничения секционированных таблиц и запрос ALTER TABLE access_log PARTITION BY RANGE( TO_DAYS(date_day) ) выдал A PRIMARY KEY must include all columns in the table's partitioning function.
Гуру SQL объяснили: ограничение, которое встретилось вам, звучит примерно так: каждый уникальный ключ (включая PK, который является UK NOT NULL) должен содержать все колонки, используемые в выражении, по которому идет секционирование. Вызвано это тем, что в MySQL отсутствуют глобальные индексы, так как поддержание подобных индексов достаточно дорогая операция. Иными словами, имея PK id и секционирование по полю ddate, MySQL бы пришлось каким-то образом гарантировать, что id уникален во всех партициях сразу. Для этого нужен индекс, проходящий сквозь все секции. MySQL такого не умеет. А вот если PKем станет пара id, ddate, то достаточно, чтобы внутри каждой секции эти значения были уникальными. Так как ddate в разных секциях будет разный, то это автоматически гарантирует уникальность пары id, ddate на всей таблице.
Получается или нужно сегментирование или детально разбираться в не в своей схеме таблицы и грамотно изменить индексы. Решил пока повременить с сегментацией таблицы.
Решил в скриптах Perl и PHP, которые за многие годы до меня администраторы на создавали для упрощения администрирования, разобраться и оптимизировать запросы в них.
Начал с простого. Ищем все SELECT FROM WHERE и анализируем колонки таблиц, участвующие в запросе. Банальное создание индексов на такие поля и EXPLAIN SELECT показывает не множество задетых строк, а единицы. Вроде мелочь, а проектировщик в прошлом не удосужился сделать такую отличную вещь, как индекс для поля. Минус у индекса, если можно назвать это минусом, можно считать необходимость в дополнительном обновлении индекса при операциях INSERT, UPDATE в таблице. Но кроме access_log все остальные таблицы не испытывают мощных INSERT, UPDATE и можно смело навешивать index налево и направо.
В поиске столбцов, которые участвуют в запросах и не имеют индекса, очень помогает параметр log-queries-not-using-indexes в /etc/mysql/my.cnf. Чтение лога mysql помогает найти виновника и устранить недоразумение. В логе сразу видно кто, откуда и каким запросом умудряется цеплять множество строк таблиц, не используя индексы.
Из книги "MySQL. Оптимизация производительности" узнал, что поле с индексом в запросе с WHERE должен стоять левее и когда встречал сложное условие, то переписывал его так, чтобы сначала было поле, а потом уже больше-меньше и выражение.
Потом пошла оптимизация посложнее. Автор Squid2MySQL в веб админке постоянно использовал запросы к "тяжёлой" таблице squidlog, хотя часто нужные данные лежали в более "лёгкой" таблице rdnload, в которой уже лежали пред вычисленные значения скаченного трафика. Логику автора не понять. Как писал выше, данные по скачанному через Squid теперь заносились скриптом итальянца, а не родным скриптом проекта, поэтому squidlog стал представлением (VIEW) для таблицы access_log. Но скрипты веб интерфейса Squid2Mysql я всё равно изменил, чтобы запросы больше обращались к таблице rdnload, чем через представление squidlog к access_log.
Самая лучшая оптимизация Apache - это использовать что-то полегче, чем Apache . Но известность и популярность Апача немаловажные вещи. Решено поставить именно его, но настроить его под низкие требования малочисленных администраторов.
У нас не высоконагруженный проект, а сайт с вебмордами админок. В общем, параметры такие:
StartServers 1 MinSpareServers 1 MaxSpareServers 2 MaxClients 5 MaxRequestsPerChild 300
StartServers определяет количество процессов сервера, запускаемых изначально, сразу после его старта. MinSpareServers и MaxSpareServers определяют минимальное и максимальное количество дочерних "запасных" процессов Apache. Такие процессы находятся в состоянии ожидания входящих запросов и не выгружаются, что даёт возможность ускорить реакцию сервера на новые запросы. MaxClients определяет максимальное количество параллельных запросов, одновременно обрабатываемых сервером. Когда количество одновременных соединений превысит это количество, новые соединения будут поставлены в очередь на обработку. MaxClients определяет максимально-допустимое число дочерних процессов Apache, запущенных одновременно. MaxRequestsPerChild определяет количество запросов, которое должен обработать дочерний процесс Apache, прежде чем завершить своё существование. Когда обработанных запросов станет больше MaxRequestsPerChild, то дочерний процесс будет перезапущен и это помогает при возможных утечках кривых скриптов.
Связано нитью рассуждений:
Proxmox VE.
Решения в плане безопасности внедрены во все версии Ubuntu и делают её безопасной и надёжной.
Ускорение Убунту.