ActiveDirectory DirectorySearcher: почему FindOne() медленнее, чем FindAll(), и почему свойства опущены?

У меня есть цикл, который извлекает некоторую информацию из ActiveDirectory. Это оказалось большим узким местом в производительности.

Этот фрагмент (внутри цикла, который выполнялся 31 раз) занял 00:01:14.6562500 (1 минута 14 секунд):

SearchResult data = searcher.FindOne();
System.Diagnostics.Trace.WriteLine(PropsDump(data));

Замена его этим фрагментом уменьшила его до 00:00:03.1093750 (3 секунды):

searcher.SizeLimit = 1;
SearchResultCollection coll = searcher.FindAll();
foreach (SearchResult data in coll)
{
    System.Diagnostics.Trace.WriteLine(PropsDump(data));
}

Результаты абсолютно идентичны, одни и те же свойства возвращаются в том же порядке. Я нашел некоторую информацию об утечках памяти в другом потоке, но производительность они не упомянули (я на .Net 3.5).


На самом деле это другой вопрос, но он дает некоторое представление о том, почему я вообще зацикливаюсь:

Я хотел получить все свойства в одном запросе, но я не могу заставить DirectorySearcher вернуть все нужные свойства за один раз (он пропускает около 30% свойств, указанных в PropertiesToLoad (также пытался установить его в конструкторе, который не делает разница), я обнаружил, что у кого-то еще была такая же проблема, и это его решение (для циклического их). Когда я перебираю их таким образом, используя FindOne() или FindAll(), я получаю все свойства, но на самом деле все это похоже на обходной путь.

Я что-то упускаю?


Редактировать:

Похоже, проблема была в том, как я получил первый DirectoryEntry, для которого я использовал DirectorySearcher.

Это был код, из-за которого DirectorySearcher возвращал только некоторые свойства:

private static DirectoryEntry GetEntry() {
    DirectoryContext dc = new DirectoryContext(DirectoryContextType.DirectoryServer, "SERVERNAME", "USERNAME", "PASSWORD");
    Forest forest = Forest.GetForest(dc);
    DirectorySearcher searcher = forest.GlobalCatalogs[0].GetDirectorySearcher();

    searcher.Filter = "OU=MyUnit";
    searcher.CacheResults = true;
    SearchResultCollection coll = searcher.FindAll();
    foreach (SearchResult m in coll)
    {
        return m.GetDirectoryEntry();
    }
    throw new Exception("DirectoryEntry not found");
}

После замены этого большого количества строк только этой строкой DirectorySearcher вернул все свойства, и цикл больше не требовался:

private static DirectoryEntry GetEntry2()
{
    return new DirectoryEntry(@"LDAP://SERVERNAME/OU=MyUnit,DC=SERVERNAME,DC=local", "USERNAME", "PASSWORD");
}

Теперь для получения всех требуемых свойств 31 записи требуется менее одной 18-й секунды. Итак, кажется, что два разных экземпляра одного и того же DirectoryEntry могут давать разные результаты в зависимости от того, как он был сконструирован... выглядит немного жутковато!


Редактировать

Использовал JetBrains DotPeek, чтобы посмотреть на реализацию. Функция FindOne начинается так:

public SearchResult FindOne()
{
  SearchResult searchResult1 = (SearchResult) null;
  SearchResultCollection all = this.FindAll(false);
  ...

Моей первой реакцией было Аргх! неудивительно... но потом я заметил спор. FindAll имеет закрытую версию, которая принимает логическое значение, это начало FindAll:

[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public SearchResultCollection FindAll()
{
  return this.FindAll(true);
}

private SearchResultCollection FindAll(bool findMoreThanOne)
{
  ... // other code
  this.SetSearchPreferences(adsSearch, findMoreThanOne);

Так что это дает немного больше понимания, но на самом деле мало что объясняет.


person Louis Somers    schedule 03.09.2013    source источник
comment
У меня была точно такая же проблема, как FindOne(), которая выполнялась в цикле дольше, чем FindAll(). FindAll() вернет несколько записей AD, но потребует пользовательской логики в клиенте для сопоставления записей, возвращенных с элементом, который в первую очередь запросил FindAll(). Это немного боли. Реализация MS FindOne не оптимизирована для производительности.   -  person Spock    schedule 10.12.2015
comment
Вы нашли решение для этого ответа? Я не думаю, что то, что здесь принято, является решением, которое вы ищете?   -  person Spock    schedule 10.12.2015
comment
@Spock, нет, не совсем так, за исключением использования LDAP, как описано в вопросе, кажется, что реализация AD никогда не оптимизировалась для повышения производительности. Я просто пошел с LDAP в конце концов. Есть некоторые сложности с использованием LDAP, когда при смене пароля, например, требуется безопасное соединение (LDAPS или LDAP через SSL), что является излишним для внутренних коммуникаций, но в любом случае это помогло достаточно для наших требований к производительности.   -  person Louis Somers    schedule 11.12.2015


Ответы (2)


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

private static DirectoryEntry GetEntry3()
{
    return new DirectoryEntry(@"GC://SERVERNAME/OU=MyUnit,DC=SERVERNAME,DC=local", "USERNAME", "PASSWORD");
}

Кроме того, в библиотеках Microsoft LDAP обычно есть способ указать, указываете ли вы имя сервера, потому что он делает некоторые оптимизации, которые могут быть очень медленными, если вы не говорите, что это было имя сервера. Для DirectoryEntry это конструктор с наибольшим количеством аргументов и AuthenticationTypes.ServerBind.

person Sean Hall    schedule 04.09.2013
comment
Кстати, есть идеи, почему FindOne намного медленнее, чем FindAll? Это явление сохраняется даже после создания экземпляра корневого узла по-другому. - person Louis Somers; 04.09.2013
comment
@LouisSomers Понятия не имею, можно подумать, что FindOne — это оболочка вокруг FindAll. - person Sean Hall; 04.09.2013
comment
Честно говоря, я был бы весьма разочарован, если бы это была просто оболочка вокруг FindAll. Я ожидаю, что он устранит некоторые циклы (на стороне сервера) и снизит нагрузку на сеть за счет передачи меньшего количества данных и, в конечном итоге, будет работать лучше. Но сюрприз, сюрприз... - person Louis Somers; 05.09.2013
comment
На самом деле, если вы посмотрите на реализацию MS FindOne () / (оболочка вокруг FindAll) под капотом, выполняя вызовы с использованием импорта COM-объекта API / DLL. Вероятно, это также является одной из причин, почему FindOne работает медленнее. См. System.DirectoryServices.Interop. - person Spock; 15.12.2015

Зацикливание — не лучшая идея. Я собираюсь проанализировать код этого парня:

objGroupEntry = sr.GetDirectoryEntry();
dso = new DirectorySearcher(objGroupEntry);

dso.ClientTimeout = TimeSpan.FromSeconds(30);

dso.PropertiesToLoad.Add("physicalDeliveryOfficeName");
dso.PropertiesToLoad.Add("otherFacsimileTelephoneNumber");
dso.PropertiesToLoad.Add("otherTelephone");
dso.PropertiesToLoad.Add("postalCode");
dso.PropertiesToLoad.Add("postOfficeBox");
dso.PropertiesToLoad.Add("streetAddress");
dso.PropertiesToLoad.Add("distinguishedName");

dso.SearchScope = SearchScope.OneLevel;

dso.Filter = "(&(objectClass=top)(objectClass=person)(objectClass=organizationalPerson)(objectClass=user))";
dso.PropertyNamesOnly = false;

SearchResult pResult = dso.FindOne();

if (pResult != null)
{
    offEntry = pResult.GetDirectoryEntry();

    foreach (PropertyValueCollection o in offEntry.Properties)
    {
        this.Controls.Add(new LiteralControl(o.PropertyName + " = " + o.Value.ToString() + "<br/>"));
    }
}

Я не знаю, почему он делает два обыска, но давайте предположим, что на это есть веская причина. Он должен был получить эти свойства из SearchResult, а не из возвращаемого значения pResult.GetDirectoryEntry, потому что это совершенно новый объект.

string postalCode = pResult.Properties["postalCode"][0] as string;
List<string> otherTelephones = new List<string>();
foreach(string otherTelephone in pResult.Properties["otherTelephone"])
{
    otherTelephones.Add(otherTelephone);
}

Если вы настаиваете на получении DirectoryEntry, запросите все свойства сразу с помощью RefreshCache:

offEntry = pResult.GetDirectoryEntry();
offEntry.RefreshCache(propertyNameArray);

Если ничего из этого не помогает, посмотрите на свои фильтры и посмотрите, можете ли вы использовать область BaseLevel.

person Sean Hall    schedule 04.09.2013
comment
Спасибо @ Hall72215, я отредактировал свой пост с некоторыми новыми выводами. Кажется, что разные экземпляры одного и того же DirectoryEntry дают разные результаты в зависимости от того, как они были созданы. Без зацикливания теперь это занимает меньше одной 18-й секунды :-) - person Louis Somers; 04.09.2013