As with any query language, many factors affect the overall performance of an LDAP query. As developers, we typically want our queries to be fast. We also know that it usually takes a while to learn all the tips and tricks, but it is this knowledge that separates the novices from the pros.
This section focuses on making the right choices to get the fastest queries possible.
Choosing the Right Search Root
One of the most important performance improvement tips is to use a SearchRoot that is the closest parent to the objects we wish to find. We then must scope the search correctly. Here are the binding locations and scopes, presented in order from best to worst.
Choosing the Right Scope
Looking at our available search scopes (Base, OneLevel, and Subtree), it would seem intuitively obvious to choose the narrowest scope available that will return what we want. Indeed, this is exactly the case, but the importance of this cannot be overstated. The difference between using a OneLevel search versus a Subtree search can mean an order of magnitude difference in speed and efficiency. Putting aside base-level searches (Base) for now, the biggest performance differentiator is going to be between using OneLevel and Subtree. This especially holds true when searching with nonindexed attributes. If we are vigilant in using an indexed attribute in our filter at all times, we can usually extract pretty good performance as well.
Now, if we happen to know the exact DN of the object we wish to search for, we can use a Base search to return its attributes. While there are definitely some uses for Base searches (attribute scope queries, for example), keep in mind that it might actually be easier simply to create a DirectoryEntry for the object instead. This is actually how the DirectoryEntry retrieves an object's attributes under the hood in the first place!
Creating Efficient Queries
Once the search scope and starting location are as efficient as possible, we can focus on other areas to help us improve the performance and efficiency of our queries. Table 5.4 lists some common areas that a developer can focus on to bring performance gains.
Tip |
Effect |
---|---|
Use indexed attributes in filters when possible. |
Try whenever possible to have at least one indexed attribute listed in the query filter. As an example, in Windows 2000, using objectCategory in lieu of or in conjunction with the objectClass attribute is more efficient, as the former is indexed. It turns out that objectClass is indexed by default in Windows 2003, but the premise is the same for other attributes. |
Return only the data required. |
By default, a search will return all of the normal attribute values for an object. If we need only a few of these, we can improve performance tremendously, simply by specifying the values we need. |
Request only the number of results needed. |
We should always try to specify the number of results we wish to receive with the SizeLimit property, unless we are executing a paged search. |
Avoid ANR searches. |
Ambiguous name resolution (ANR) is a terrific feature, but it can quickly get out of control in queries that are more complicated. It is important to remember that the actual ANR filter internally expands into a much larger filter that might drag down performance. |
Avoid careless substring searches. |
Use substring search filters with care. Whenever possible, place the wildcard at the end of the value ("sn=d*") in order to preserve index usage. |
Avoid other wildcard positions when possible ("sn=*unn" or "sn=*un*"). If this is not possible, then create a medial or "tuple" index on this attribute. Avoid unnecessary substring searches in general. |
|
Avoid sorting. |
Sorting can be a big performance killer. Even when performing it in the most efficient manner (see Sorting Search Results, in Chapter 4), it still can suck performance from our applications. We should always consider whether we can push the sorting to the client. If we can, this will always be more efficient than performing the sort server side. |
Properly place AND and OR operators. |
This is a tricky one to understand sometimes. Suppose we had the following type of filter (in English): "Select all users that have a last name of either kaplan or dunn." We could express that filter using something like this: |
(& (objectCategory=person) (objectClass=user) (| (sn=dunn) (sn=kaplan) ) ) |
|
It turns out this is not the most efficient way to do this. We really want to do something like this: |
|
(| (& (objectClass=user) (objectCategory=person) (sn=dunn) ) (& (objectClass=user) (objectCategory=person) (sn=kaplan) ) ) |
|
The query processor has been improved in Windows 2003, making this not as big an issue, but it should be kept in mind. |
|
Avoid bitwise comparisons. |
We demonstrated how to use bitwise comparisons in Chapter 4 and also suggested using them sparingly. The reason is that a bitwise comparison prevents the use of an index for that attribute. Therefore, when using these types of comparisons, it is important to keep in mind the first tip in this table and to use other indexed attributes when possible to limit the impact. |
Avoid using the NOT (!) operator. |
This is a subtle point, but the query processor processes objects that we might not have permission to view, and attributes that do not have a value as meeting the filter criteria, resulting in unnecessary matches. Additionally, this operator prevents the use of indices on affected attributes. |
Use connection caching. |
Opening and closing connections is inefficient. In the section ADSI Connection Caching Explained, in Chapter 3, we describe how to do this properly. |
Query an object only once. |
Instead of constantly requerying an object and generating network traffic and overhead, we should query the object only once and cache the data locally. |
Avoid referral chasing if possible. |
Referral chasing can send us bouncing from server to server looking for objects. This can be very time consuming to perform. When querying for objects that might exist in another domain in the same forest, consider using the global catalog rather than referrals. This is generally more efficient. |
Turn Caching Off When Possible
The CacheResults property of DirectorySearcher manipulates an underlying ADSI feature that enables client-side caching. By default, this property is set to true and caching is enabled in SDS. As we mentioned previously in Chapter 4, this property has little bearing for us in .NET. Internally, the entire result set is held in an ArrayList object in our SearchResultCollection object, so we will not actually contact the ADSI local cache when enumerating a result set more than once. It is safe to set this to false and lower the unmanaged memory requirements for our application.
Part I: Fundamentals
Introduction to LDAP and Active Directory
Introduction to .NET Directory Services Programming
Binding and CRUD Operations with DirectoryEntry
Searching with the DirectorySearcher
Advanced LDAP Searches
Reading and Writing LDAP Attributes
Active Directory and ADAM Schema
Security in Directory Services Programming
Introduction to the ActiveDirectory Namespace
Part II: Practical Applications
User Management
Group Management
Authentication
Part III: Appendixes
Appendix A. Three Approaches to COM Interop with ADSI
Appendix B. LDAP Tools for Programmers
Appendix C. Troubleshooting and Help
Index