System.DirectoryServices Search Performance – Part 1

NOTE: This post is part of a series.

At Silicon Valley Code Camp we got in a discussion about efficient LDAP queries. I thought it would best to write a few blog posts about performance considerations of searching when using System.DirectoryServices (including LDAP queries). I’m using the MSDN article Creating More Efficient Microsoft Active Directory-Enabled Applications as the basis for my posts, however personal experience and other sources are sprinkled in heavily. I find the MSDN article talks about performance from a statistical stand point as opposed to a real world standpoint.

Today’s post is about configuring your DirectorySearcher object. Future posts will cover:

  • LDAP Filters
  • Advanced Binding Options
  • Analyzing Performance of Searches
  • Creating New Indexes

Some of this information is specific to Active Directory/ADAM. However many if not most of the concepts are universal to all LDAP servers.

Set Appropriate Search Root

If you were searching your computer for a file you would have your searching tool start at the closest folder that you could to where the file could possibly be. For example if you know the file is in c:Windows you would start the search there instead of C:. The closer you get to the file, the faster the search will be.

The same concept applies to LDAP queries. If you know the object is under OU=Staff,DC=domain,dc=com then you would want to start searching there.

In .Net this is easy to do. On your DirectorySearcher object set the property SearchRoot to where you want the search to start.

DirectorySearcher searcher = new DirectorySearcher();
searcher.SearchRoot =
   new DirectoryEntry(LDAP://OU=Staff,DC=domain,DC=local);

If you do not set the SearchRoot (or set it to null) the search will look at RootDSE of the “default” LDAP server (meaning this only works on an AD connected client) and start the search from the DefaultContext. That’s a fancy way of saying that you will search your entire domain.

Set Appropriate Search Scope

There are three kinds of search scope:

  • Base – Only search the object specified as the SearchRoot
  • One-Level – Only search direct children of the SearchRoot
  • Subtree – Search all children of the SearchRoot, including sub-children.

In .Net the base level search is often not necessary because if we know the path to the object we can simply create a DirectoryEntry object for it. However there are circumstances where it becomes useful.

The theory of setting search scope is similar to setting an appropriate search root. If you know the object is in the SearchRoot container there is no need to iterate through sub-OU’s. The fewer objects the directory has to search the faster your query will be.

Setting your search scope in .Net is once again trivial. Here we set it to One-Level.

DirectorySearcher searcher = new DirectorySearcher();
searcher.SearchScope = SearchScope.OneLevel;

Set PropertiesToLoad Property

You can define which LDAP attributes should be returned when searching for an object. The more attributes returned, the more processing power to retrieve those attributes, the more data on the network, which means the longer it will take to retrieve the data you need.

In .Net if you do not specify the PropertiesToLoad property it returns the default set of attributes. When searching against my Windows 2003 Forest it retrieves 25 LDAP attributes. I have no idea where it gets that list from, or if it is modifiable.

To set your own list of PropertiesToLoad simply use the following code:

DirectorySearcher searcher = new DirectorySearcher();

searcher.PropertiesToLoad.Add("sAMAccountName");

Use Paged Searches

By default Active Directory will only return a maximum of 1000 results. Only being able to search for up to 1000 objects would make the service useless as a directory server. LDAP servers provide a method called paging to return larger result sets by allow clients to request the next page of objects. This prevents excessive memory use by the server (and the client). By paging the results it only has to store up to 1000 objects at a time in memory.

To enable paging in .Net all we need to do is set the property PageSize to a value larger than 0. Ideally you would set the PageSize to be the same as the servers MaxPageSize value to reduce the number of pages required to iterate through the entire set. If you set the PageSize to a value larger than the MaxPageSize the server will still return the number of objects specified by MaxPageSize.

DirectorySearcher searcher = new DirectorySearcher();

searcher.PageSize = 1000;

After PageSize is set paging happens seamlessly. If you foreach through the SearchResultCollection it seamlessly goes from object 1000 to object 1001.

SearchResultCollection results = searcher.FindAll();
foreach (SearchResult result in results)
{
   Console.WriteLine(result.Path);
}

If you step through your code you will see network activity and a visible delay when you try to read object 1001.

Advanced DirectorySearcher Settings

Here are some more advanced and less prominent settings you can change on your DirectorySearcher objects.

The property PropertyNamesOnly causes the directory to only return the names of properties and does not retrieve their value. This is useful if you only want to see if an object has a value for a certain attribute, or if you’re going to convert to a DirectoryEntry object anyways. Converting to a DirectoryEntry object causes a new call to LDAP which will then create an object cache and populate the properties values anyways.

The property ServerPageTimeLimit sets how long the server should search for objects before returning a page of results. This is useful if you are doing a complicated query and want to retrieve results after so many seconds, even if a full page of results has not been retrieved yet. With this setting enabled you will either receive a page of results when the PageSize limit is reached, or the specified amount of time as elapsed.

Advertisements