понедельник, 6 июня 2011 г.

Странный DirectoryServices. Совет второй: прогревайте кэш атрибутов руками

Продолжим начатую тему. Вы знаете, что происходит, когда запрашиваешь тот или иной атрибут Active Directory у объекта типа DirectoryEntry, и почему большая часть программ делает это неоптимальным образом? А как сделать правильно? Поговорим об этом.

Кэш кэшу рознь.
Давайте вспомним, как выглядит типичный код, запрашивающий атрибуты объекта Active Directory через класс DirectoryEntry:
DirectoryEntry entry = new DirectoryEntry(
    "LDAP://CN=John Doe,CN=Users,DC=neverhood,DC=org");
object displayNameProp = entry.Properties["displayName"][0];
object homePhoneProp = entry.Properties["homePhone"][0];
object adminDescrProp = entry.Properties["adminDescription"][0];

Всё довольно просто, поэтому мало кто задумывается, что же скрывается за этой простотой. А тут есть над чем подумать. Ведь каждый атрибут хранится на удалённом контроллере доменов. Список атрибутов расширяем – стало быть, не ограничен.

Перспектива малоприятная, как может показаться на первый взгляд: либо отдельный запрос к контроллеру доменов при обращении к каждому атрибуту, либо загрузка всех атрибутов при создании объекта. На самом деле ситуация немного лучше. При запросе первого атрибута, внутри объекта DirectoryEntry происходит вызов метода IADs::GetInfo соответствующего ADSI объекта. Согласно документации этот метод загружает поддерживаемые атрибуты во внутренний кэш ADSI объекта. На практике же это означает загрузку всех атрибутов, имеющих заданные значения. Если у вас в домене установлен Microsoft Exchange и ещё пару приложений уровня предприятия, расширяющих схему, то число загружаемых атрибутов легко может достигнуть сотни.
Значение любого запрашиваемого атрибута сначала ищется в загруженном кэше. И если только оно там не обнаруживается, выполняется отдельный запрос к контроллеру домена, результат этого запроса тоже кэшируется.

Долой автопилот! Даёшь ручное управление!
Как не сложно догадаться, если вы будете запрашивать некоторый атрибут, значение которого в большинстве случаев окажется незаданным, вы всегда будете иметь 2 обращения к контроллеру доменов на каждый объект. Причём первый запрос будет загружать кучу ненужной вам информации. Исправить ситуацию может один метод класса DirectoryEntry, на который мало кто обращает внимание:
public void RefreshCache(
    string[] propertyNames
);

Из сигнатуры этот метода становится понятно, что он выполняет загрузку в кэш только тех атрибутов, список которых вы передадите ему в качестве параметра. Более того, он это делает одним обращением к контроллеру домена с помощью метода IADs::GetInfoEx. Никакой лишней дополнительно информации не загружается. Таким образом, пример, приведённый выше, можно переписать так:
DirectoryEntry entry = new DirectoryEntry(
    "LDAP://CN=John Doe,CN=Users,DC=neverhood,DC=org");
entry.RefreshCache(new string[]{
    "displayName",
    "homePhone",
    "adminDescription"});
object displayNameProp = entry.Properties["displayName"][0];
object homePhoneProp = entry.Properties["homePhone"][0];
object adminDescrProp = entry.Properties["adminDescription"][0];

Этот вариант будет работать быстрее, и расходовать гораздо меньше памяти, особенно на больших коллекциях объектов.

2 комментария:

Александр комментирует...

Это тоже не оптимально?)
this.entry = new DirectoryEntry(this.LDAPStr, this.Login, this.Pass, AuthenticationTypes.Secure);
this.Searcher = new DirectorySearcher(entry);
this.Searcher.Filter = Filter;
Searcher.PropertiesToLoad.Add("givenName");
Searcher.PropertiesToLoad.Add("displayName");
Searcher.PropertiesToLoad.Add("sn");

Алексей Коротаев комментирует...

Здесь с производительностью вроде всё в порядке. В данном случае вы используете не DirectoryEntry, а DirectorySearcher - это принципиально иной способ доступа, реализуемый другим ADSI объектом. Более того, если вы не добавите эти свойства в список ззагружаемых, то вы и не получите их значения на выходе, даже если они содержат значения в AD – DirectorySearcher не содержит никакого внутреннего кэша и ничего не подгружает дополнительно. Необходимо только помнить про предыдущий совет и правильно выбрать контроллер домена, на котором будете выполнять поиск.

Отправить комментарий