带通配符的 Active Directory 查询性能不佳
Active directory query with wildcards has poor performance
我正在用 C# 编写一个方法,该方法应该查询 Active Directory 并查找显示名称格式为 {displayName} 的所有用户和组(通配符搜索,前导和尾随通配符),该方法将用于自动完成字段。
问题是我写的方法的性能真的很差,尝试查询 AD 需要 30 秒到一整分钟,具体取决于查询字符串。
我的组织的 AD 非常大,但如果花费这么长时间,自动完成字段将毫无意义。
这是我现在使用的代码:
// Intialize the results list.
result.queryResult = new List<Classses.ADSearchObject>();
// Set up domain context.
PrincipalContext pc = new PrincipalContext(ContextType.Domain, Domain, Constants.adQueryUser, Constants.adQueryPassword);
// Set up a directory searcher.
DirectorySearcher dSearcher = new DirectorySearcher();
// Define a SearchCollection to store the results.
SearchResultsCollection searchCol;
// Define returned result paging for performance.
dSearcher.PageSize = 1000;
// Define the properties to retrieve
dSearcher.PropertiesToLoad.Add("sAMAccountName");
dSearcher.PropertiesToLoad.Add("displayName");
// Define the filter for users.
dSearcher.Filter = $"(|(&(displayName = {result.querystring}*)(objectCategory=person))(&(displayName=*{result.querystring})(objectCategory=person)))";
// Search based in filter and save the results.
searchCol = dSearcher.FindAll();
// Add the results to the returned object
foreach (SearchResult searchResult in searchCol)
{
DirectoryEntry de = searchResult.GetDirectoryEntry();
// Code to get data from the results...
}
// Define the filter for groups.
dSearcher.Filter = $"(|(&(displayName={result.querystring}*)(objectCategory=person))(&(displayName=*{result.querystring})(objectCategory=person)))";
// Search based in filter and save the results.
searchCol = dSearcher.FindAll();
// Add the results to the returned object
foreach (SearchResult searchResult in searchCol)
{
DirectoryEntry de = searchResult.GetDirectoryEntry();
// Code to get data from the results...
}
目前搜索分为用户和组,以便于区分它们,但如果它能显着提高性能,我会将它们统一到一个搜索中。
编辑: 正如用户 rene 所建议的,我使用了 Stopwatch
来检查 FindAll
所花费的时间,我还检查了我的时间foreach
循环。
我发现即使使用 AD 索引的前导通配符(不是)进行搜索,FindAll
调用也需要大约 100 毫秒(非常快)。
显然,花费时间最长的调用是我的 foreach
循环,大约需要 40 秒(40,000 毫秒)。
我正在用 foreach
循环中的代码块更新问题,因为我还没有想出如何提高它的性能:
// --- I started a stopwatch here
foreach (SearchResult searchResult in searchCol)
{
// --- I stopped the stopwatch here and noticed it takes about 30,000ms
result.code = 0;
DirectoryEntry de = searchResult.GetDirectoryEntry();
ADSearchObject adObj = new ADSearchObject();
adObj.code = 0;
if (de.Properties.Contains("displayName")
{
adObj.displayName = de.Properties["displayName"].Value.ToString();
}
adObj.type = "user";
result.queryResults.Add(adObj);
}
注意我在更新的代码中开始和停止 'Stopwatch' 的地方,我不知道为什么开始循环需要这么长时间。
当然,对于唯一值,子字符串匹配比相等匹配的成本更高。同样,绝大部分的运行时间落入您的迭代器块也就不足为奇了,根据您的分析,它总共消耗了 40 秒。
如果您确信仅通过设置迭代器就会导致性能大幅下降,我不这么认为 - 那是因为您选择了时间点。
StartClock("foreach");
foreach (SearchResult searchResult in searchCol)
{
// use an empty block to speed things up or
StopClock("foreach");
// whatever
RestartClock("foreach");
}
StopClock("foreach");
LogClock("foreach");
如果您注意我已经评论过的最佳实践,我希望获得巨大的性能提升(对于大量条目):向服务器发送单个请求以接收搜索结果中所需的所有内容,并且不要为每个项目发送另一个请求。虽然对 GetDirectoryEntry() 的单次调用只会消耗 <1ms,但大量的条目将使您的代码对您的应用程序自动完成功能毫无用处。
感谢@rene 为该过滤器表达式提供了一个标准形式。我不知道 Active Directory 中的过滤器优化,所以我会选择
的确定路径
(&(objectCategory=person)(displayName=*{result.querystring}*))
我正在用 C# 编写一个方法,该方法应该查询 Active Directory 并查找显示名称格式为 {displayName} 的所有用户和组(通配符搜索,前导和尾随通配符),该方法将用于自动完成字段。
问题是我写的方法的性能真的很差,尝试查询 AD 需要 30 秒到一整分钟,具体取决于查询字符串。
我的组织的 AD 非常大,但如果花费这么长时间,自动完成字段将毫无意义。
这是我现在使用的代码:
// Intialize the results list.
result.queryResult = new List<Classses.ADSearchObject>();
// Set up domain context.
PrincipalContext pc = new PrincipalContext(ContextType.Domain, Domain, Constants.adQueryUser, Constants.adQueryPassword);
// Set up a directory searcher.
DirectorySearcher dSearcher = new DirectorySearcher();
// Define a SearchCollection to store the results.
SearchResultsCollection searchCol;
// Define returned result paging for performance.
dSearcher.PageSize = 1000;
// Define the properties to retrieve
dSearcher.PropertiesToLoad.Add("sAMAccountName");
dSearcher.PropertiesToLoad.Add("displayName");
// Define the filter for users.
dSearcher.Filter = $"(|(&(displayName = {result.querystring}*)(objectCategory=person))(&(displayName=*{result.querystring})(objectCategory=person)))";
// Search based in filter and save the results.
searchCol = dSearcher.FindAll();
// Add the results to the returned object
foreach (SearchResult searchResult in searchCol)
{
DirectoryEntry de = searchResult.GetDirectoryEntry();
// Code to get data from the results...
}
// Define the filter for groups.
dSearcher.Filter = $"(|(&(displayName={result.querystring}*)(objectCategory=person))(&(displayName=*{result.querystring})(objectCategory=person)))";
// Search based in filter and save the results.
searchCol = dSearcher.FindAll();
// Add the results to the returned object
foreach (SearchResult searchResult in searchCol)
{
DirectoryEntry de = searchResult.GetDirectoryEntry();
// Code to get data from the results...
}
目前搜索分为用户和组,以便于区分它们,但如果它能显着提高性能,我会将它们统一到一个搜索中。
编辑: 正如用户 rene 所建议的,我使用了 Stopwatch
来检查 FindAll
所花费的时间,我还检查了我的时间foreach
循环。
我发现即使使用 AD 索引的前导通配符(不是)进行搜索,FindAll
调用也需要大约 100 毫秒(非常快)。
显然,花费时间最长的调用是我的 foreach
循环,大约需要 40 秒(40,000 毫秒)。
我正在用 foreach
循环中的代码块更新问题,因为我还没有想出如何提高它的性能:
// --- I started a stopwatch here
foreach (SearchResult searchResult in searchCol)
{
// --- I stopped the stopwatch here and noticed it takes about 30,000ms
result.code = 0;
DirectoryEntry de = searchResult.GetDirectoryEntry();
ADSearchObject adObj = new ADSearchObject();
adObj.code = 0;
if (de.Properties.Contains("displayName")
{
adObj.displayName = de.Properties["displayName"].Value.ToString();
}
adObj.type = "user";
result.queryResults.Add(adObj);
}
注意我在更新的代码中开始和停止 'Stopwatch' 的地方,我不知道为什么开始循环需要这么长时间。
当然,对于唯一值,子字符串匹配比相等匹配的成本更高。同样,绝大部分的运行时间落入您的迭代器块也就不足为奇了,根据您的分析,它总共消耗了 40 秒。
如果您确信仅通过设置迭代器就会导致性能大幅下降,我不这么认为 - 那是因为您选择了时间点。
StartClock("foreach");
foreach (SearchResult searchResult in searchCol)
{
// use an empty block to speed things up or
StopClock("foreach");
// whatever
RestartClock("foreach");
}
StopClock("foreach");
LogClock("foreach");
如果您注意我已经评论过的最佳实践,我希望获得巨大的性能提升(对于大量条目):向服务器发送单个请求以接收搜索结果中所需的所有内容,并且不要为每个项目发送另一个请求。虽然对 GetDirectoryEntry() 的单次调用只会消耗 <1ms,但大量的条目将使您的代码对您的应用程序自动完成功能毫无用处。
感谢@rene 为该过滤器表达式提供了一个标准形式。我不知道 Active Directory 中的过滤器优化,所以我会选择
的确定路径(&(objectCategory=person)(displayName=*{result.querystring}*))