21. Настройка производительности
Этот раздел, возможно, один из самых важных в данном руководстве, поскольку, если Вы не настроили slapd(8) должным образом или не знаете, как спроектировать Ваш каталог и его окружение, производительность может оказаться очень низкой.
Прочтение и понимание инструкций и информации из следующих подразделов, а также эксперименты на их основе дадут Вам полное представление о том, как адаптировать Ваш сервер каталогов в соответствии с Вашими требованиями.
Необходимо отметить, что представленная ниже информация постепенно собиралась из основанного нашим сообществом списка FAQ. Очевидно, какую большую пользу читателю может принести данный опыт и рекомендации, почерпнутые из работы со службами каталогов в реальном мире.
21.1. Факторы, влияющие на производительность
Различные факторы могут влиять на то, какая производительность будет у Вашего каталога на выбранном Вами оборудовании и окружении. Мы постараемся обсудить это здесь.
21.1.1. Оперативная память
Увеличьте размер Вашего кэша, чтобы использовать всю доступную для работы каталога оперативную память. Если есть возможность, увеличьте количество оперативной памяти в системе.
Советы по настройке кэша BDB смотрите в подразделе "Кэширование". Обратите внимание, что LMDB не использует кэш и не имеет никаких параметров настройки, так что при использовании LMDB подразделом "Кэширование" можно пренебречь.
21.1.2. Диски
Используйте быстрые файловые системы и проведите собственное тестирование, чтобы выявить, какие типы фаловых систем лучше работают с Вашим уровнем нагрузки. Наши тесты на Linux показали, что EXT2 и JFS, как правило, обеспечивают лучшую производительность операций записи, нежели любые другие файловые системы, в том числе такие новые, как EXT4, BTRFS и т.п.
Используйте быстрые подсистемы. Разместите каждую базу данных и журналы на разные диски (для BDB это настраивается с помощью файла DB_CONFIG):
# Директория для размещения данных set_data_dir /data/db # Установки журнала транзакций set_lg_dir /logs
21.1.3. Топология сети
http://www.openldap.org/faq/data/cache/363.html
Здесь будет иллюстрация.
21.1.4. Проектирование структуры каталога
Здесь будут ссылки на другие разделы и иллюстрация хорошего/плохого макета каталога.
21.1.5. Предполагаемое использование
Здесь будет обсуждение.
21.2. Индексирование
21.2.1. Разберёмся, как работает поиск
Если в поисковом фильтре используются проиндексированные атрибуты, то в процессе поиска читаются индексы и непосредственно выбираются те записи, на которые эти индексы ссылаются. Если атрибуты в фильтре не были проиндексированы, тогда процесс поиска должен прочитать каждую отдельную запись в целевом диапазоне и проверить, соответствует ли она условию фильтра. Очевидно, при правильном использовании индексации, она помогает избегать выполнения большого количества работы.
21.2.2. Что нужно индексировать
Вы должны создать индексы в соответствии с фактическими условиями фильтров, используемых в поисковых запросах.
index cn,sn,givenname,mail eq
Затем каждый индексируемый атрибут может быть дополнительно настроен путём подбора типов индексов, которые необходимо сгенерировать для этого атрибута. Например, поиски соответствия подстроке и примерного соответствия для атрибута organization (o) вряд ли будут иметь большой смысл (и осуществляться очень часто). А поиски по атрибуту userPassword, вероятно, вообще лишены всякого смысла.
Основное правило: не переусердствуйте с индексами. Неиспользуемые индексы также требуют времени на обслуживание, и, следовательно, могут только замедлять ход событий.
За дополнительной информацией обращайтесь к man-страницам slapd.conf(8) и slapdindex(8).
21.2.3. Индексы наличия
Если клиентское приложение использует фильтры наличия и целевой атрибут присутствует в большинстве записей в Вашем целевом диапазоне, то все эти записи будут прочитаны в любом случае, поскольку все они попадают в результирующее подмножество. В поддереве, где все 100% записей будут содержать одни и те же атрибуты, индексы наличия не делают абсолютно НИЧЕГО полезного для поиска, поскольку все 100% записей совпадут с фильтром наличия.
Поэтому работы по созданию таких индексов превращаются в бесполезную трату процессорного времени, дискового пространства и оперативной памяти. Если Вы не знаете точно, как они будут использоваться, и не уверены, что фигурирующие в фильтрах наличия запросов атрибуты редко встречаются в целевых данных, лучше не создавать их.
Практически нет приложений, использующих фильтры наличия в своих поисковых запросах. Индексы наличия бессмысленны, если целевой атрибут существует в большинстве записей базы данных. Чаще всего в LDAP-системах не требуется делать индексы наличия, это просто бесполезная трата ресурсов.
Смотрите в подразделе Журналирование ниже, на что нужно обратить внимание, если в Вашем каталоге часто осуществляется поиск по неиндексированным атрибутам.
21.3. Журналирование
21.3.1. Какой уровень журналирования использовать
Уровень журналирования по умолчанию loglevel stats (256) — это действительно лучший выбор, поскольку, если проблемы начинают проявляться, не следует пытаться отследить их с помощью syslog. Вместо этого используйте флаги отладки и изучайте содержимое вывода потока stderr slapd. syslog слишком медленен для выполнения отладки, и, кроме того, он потенциально может терять данные, отбрасывая пришедшее сообщение, если не успевает его обработать.
Вопреки распространенному мнению, loglevel 0 — не идеальный вариант в рабочих системах, поскольку Вы лишаете себя возможности отследить, когда проблема стала проявлять себя впервые.
21.3.2. На что обращать внимание
Самое распространённое сообщение, на которое нужно обратить внимание:
"<= bdb_equality_candidates: (foo) index_param failed (18)"
Это означает, что какое-то приложение пытается использовать фильтр соответствия (foo=<некоторое значение>), а атрибут foo не имеет индекса соответствия. Если таких сообщений много, нужно добавлять индекс. Если же оно появляется всего пару раз за месяц, можно его проигнорировать.
Уровень журналирования по умолчанию — stats (256). Если установлен этот уровень, в журнал помещаются основные параметры каждого запроса, обычно получается 1-3 строки вывода. В Solaris и системах, которые предоставляют только синхронное журналирование, Вы можете отключить его полностью, но обычно его оставляют включённым, чтобы можно было отследить сообщения об индексировании, как только они появятся. В Linux Вы можете настроить syslogd на асинхронное журналирование, в этом случае и производительность возрастёт, и оперативность просмотра трафика syslog практически не пострадает.
21.3.3. Увеличение производительности
На некоторых системах Вы можете увеличить производительность журналирования, настроив syslog так, чтобы он не синхронизировал журналы из буфера в оперативной памяти с файловой системой при каждой записи (смотрите man-страницы syslogd/syslog.conf). В Linux Вы можете поставить "-" перед именем файла журнала в syslog.conf. Например, если Вы используете канал журналирования по умолчанию LOCAL4, попробуйте сделать так:
# LDAP logs LOCAL4.* -/var/log/ldap
Для системы syslog-ng добавьте или измените следующую строку в syslog-ng.conf:
options { sync(n); };
где n — количество строк, которые будут помещаться в буфер перед записью в файл.
21.4. Кэширование
Все мы знаем, что такое кэширование, правда?
Если коротко, "кэш — это блок памяти для временного хранения данных, которые могут быть повторно использованы" — http://en.wikipedia.org/wiki/Cache
Существует 3 типа кэшей: собственный кэш BerkeleyDB, кэш записей slapd(8) и кэш
21.4.1. Кэш Berkeley DB
Есть два подхода к настройке размера кэша BDB:
(а) размер кэша BDB, который требуется для загрузки базы данных через slapadd за оптимальное время;
(б) размер кэша BDB, который требуется для получения высокой производительности работы slapd после того, как данные были загружены.
Для подхода (а) оптимальный размер кэша — это размер всей базы данных. Если Ваша база данных уже загружена, получить его можно просто выполнив команду
du -c -h *.bdb
в директории, содержащей данные OpenLDAP (/usr/local/var/openldap-data).
Для подхода (б) оптимальный размер кэша — это просто размер файла id2entry.bdb плюс около 10% на прирост.
Необходимо выполнить настройку DB_CONFIG для каждого экземпляра базы данных типа BDB (back-bdb, back-hdb).
Обратите внимание, что в отличие от кэша
Существует также кэш IDL, используемый для временного хранения результатов поиска данных по индексу (Index Data Lookups). Если Вы сможете вместить всю базу данных в кэш записей slapd и все поиски по индексам в кэш IDL, это обеспечит Вам максимальную производительность.
Если же так сделать не получается, но есть возможность разместить базу данных целиком в кэш BDB, тогда лучше сделать это и уменьшить кэш записей slapd до необходимого размера.
В противном случае придётся искать баланс между кэшем BDB и кэшем записей.
Стоит отметить, что совершенно не требуется конфигурировать размер кэша BerkeleyDB эквивалентно размеру всей базы данных. Всё, что Вам действительно необходимо, — это кэш, достаточно большой, чтобы вместить Ваш "рабочий набор".
Это означает, что кэш должен быть настолько велик, чтобы содержать все те данные, к которым обращаются чаще всего, а также еще некоторое количество элементов, доступ к которым осуществляется не так часто.
Для получения дополнительной информации обращайтесь по адресу: http://www.oracle.com/technology/documentation/berkeley-db/db/ref/am_conf/cachesize.html
21.4.1.1. Расчёт размера кэша
База данных back-bdb размещается в двух основных файлах: dn2id.bdb и id2entry.bdb. Оба они являются базами данных в формате B-деревьев (B-tree). До этого мы никогда не описывали в документации внутреннее представление back-bdb, поскольку данный вопрос не представлялся ни тем, о чём следует беспокоиться каждому, ни тем, что следует запечатлеть на века. Однако, вот как обстоят дела в данный момент, в OpenLDAP 2.4.
B-дерево — это сбалансированное дерево (balanced tree); данные в нём хранятся в листовых узлах, а описание организации данных — во внутренних узлах (если Вы не знаете, что собой представляет древовидная структура данных, поищите информацию в Google, поскольку подробное описание выходит за рамки данной документации).
Для получения достойной производительности Вам потребуется достаточно кэш-памяти, чтобы разместить там все узлы по пути от корня дерева до конкретного нужного Вам элемента данных. Это будет достаточный кэш для единичной операции поиска. В общем случае, желательно иметь кэш, позволяющий разместить все внутренние узлы в базе данных.
db_stat -d
покажет Вам, сколько внутренних страниц находится в базе данных. Вы должны проверить это число как для dn2id, так и для id2entry.
Также имейте в виду, что id2entry всегда используют по 16KB на каждую "страницу", а dn2id используют столько, сколько по умолчанию использует та файловая система, на которой они размещаются, обычно 4 или 8KB. Во избежание переполнений, Ваш кэш должен быть как минимум настолько велик, чтобы вмещать общее количество внутренних страниц обоих баз данных dn2id и id2entry, плюс некоторое количество дополнительного пространства для размещения фактических данных из листовых страниц.
Например, я загрузил в мою тестовую базу данных OpenLDAP 2.4 LDIF-файл размером около 360MB. При этом механизм манипуляции данными back-hdb создал dn2id.bdb размером около 68MB, и id2entry размером около 800MB. db_stat показал мне, что dn2id использует страницы размером 4KB и содержит 433 внутренних и 6378 листовых страниц. id2entry использует страницы размером 16KB и содержит 52 внутренних и 45912 листовых страниц. Для того, чтобы эффективно извлечь какую-нибудь единичную запись из этой базы данных, кэш должен быть как минимум размером
(433+1) * 4KB + (52+1) * 16KB = 1736KB + 848KB =~ 2.5MB.
В расчёт не были приняты другие накладные расходы библиотеки, так что это даже меньше необходимого минимума. Если же ничего не было сконфигурировано, размер кэша по умолчанию — лишь 256KB.
В это число 2.5MB не входят также расходы на индексирование. Для каждого индексированного атрибута создаётся отдельный файл базы данных. В ранних версиях OpenLDAP эти индексные базы данных были в хэш-формате, но, начиная с OpenLDAP 2.2, для представления индексных баз данных также используется формат B-деревьев, поэтому при вычислении размера кэша для каждой индексной базы данных может применяться аналогичная процедура.
Например, если Вы проиндексировали только атрибут objectClass и db_stat показал, что objectClass.bdb использует 339 внутренних станицы размером 4KB, то дополнительный кэш, который потребуется для индекса данного атрибута, будет:
(339+1) * 4KB =~ 1.3MB.
Даже если включен только этот индекс, то, по расчётам, для этого механизма манипуляции данными нужен кэш размером как минимум 4MB. (Конечно, используя единственный общий кэш для всех файлов базы данных нельзя быть уверенным в том, что страницы кэша не будут использованы для чего-то такого, на что Вы не рассчитывали, однако подобное предположение даёт Вам некоторый шанс на успех).
С таким кэшем размером 4MB я могу получить слепок всей этой базы данных с помощью slapcat на моём 1.3GHz PIII за 1 минуту 40 секунд. Если удвоить размер кэша до 8MB, получение слепка займёт те же самые 1 минуту 40 секунд. Как только Вы выделили достаточно кэша, чтобы вместить внутренние страницы B-дерева, дальнейшее увеличение не даст никакого эффекта, пока размер кэша не станет настолько большим, чтобы вместить 100% страниц данных. У меня недостаточно свободной оперативной памяти, чтобы содержать в ней все 800MB данных id2entry, поэтому 4MB вполне достаточно.
Для механизмов back-bdb и back-hdb Вы можете использовать "db_stat -m" для проверки эффективности производительности кэша базы данных.
Подробнее о db_stat: http://www.oracle.com/technology/documentation/berkeley-db/db/utility/db_stat.html
21.4.2. Кэш записей slapd(8) (cachesize)
Кэш записей slapd(8) работает с декодированными записями. Смысл в том, что записи в кэше записей могут использоваться непосредственно, при этом достигается наиболее быстрый отклик. Если какой-либо записи нет в кэше записей, но её можно получить из кэша BDB, это позволит избежать операций ввода/вывода на диск, но всё равно потребует время на разбор, поэтому запрос такой записи будет выполняться медленнее.
Если этой записи нет и в кэше BDB, тогда BDB потребуется освободить часть своих текущих закэшированных страниц и получить необходимые страницы, в результате чего произойдёт несколько дорогостоящих операций ввода/вывода, а также разбора.
Конечно, самый оптимальный размер кэша таков, чтобы вместить полное количество записей в базе данных. Однако, большинство серверов службы каталогов не выдают ответов сразу по всей базе данных, так что достаточно установить размер кэша в меньшую величину, более точно соответствующую предполагаемому рабочему набору данных. Это второй из важнейших параметров для базы данных.
Что касается нахождения баланса между кэшем записей и кэшем BDB, то нужно учесть, что разобранные записи обычно занимают в памяти в два раза больше места, чем неразобранные на диске.
Как уже упоминалось, неверный выбор размера кэша базы данных приводит к снижению производительности. Этот признак не является показателем возникновения проблем с базой данных. Это просто факт того, что кэш не справляется с нагрузкой, что приводит к снижению производительности и увеличению времени отклика.
21.4.3. Кэш IDL (idlcachesize)
Каждый IDL хранит результаты поиска заданных запросов, поэтому кэш IDL в конечном итоге будет содержать результаты наиболее часто запрашиваемых поисков. Как правило, рекомендуемый размер этого кэша для back-bdb должен совпадать с "cachesize", а для back-hdb — 3x"cachesize".
Примечание: Настройка idlcachesize непосредственно влияет на эффективность поиска.
21.5. Потоки slapd(8)
slapd(8) может обрабатывать запросы с помощью настраиваемого числа потоков, которые, в свою очередь, влияют на скорость ввода/вывода в процессе соединения.
Это число зависит от количества "реальных" ядер в системе. Например, на сервере с двумя одноядерными процессорами следует установить 8 потоков, по 4 на каждое реальное ядро. Это максимальное значение для операций "чтения". Установка большего количества потоков на одно ядро приведёт к тому, что slapd(8) будет медленнее отвечать на операции "чтения". С другой стороны, это позволит ускорить операции записи в системах, где операции записи преобладают над операциями чтения.
Расчётная верхняя граница количества потоков для хорошей производительности операций чтения — 16 (что и является значением по умолчанию).