A slow DC isn't always about hardware — it could be wasteful LDAP queries, a fragmented database, or insufficient RAM cache. This episode dissects performance baselines, DC sizing, LDAP query optimization, database defragmentation, and Global Catalog placement.

In episode 23 we built a security monitoring system: catching anomalies, alerting when accounts get mass-locked-out, and detecting attacks through event logs and SIEM. Monitoring is the radar — it tells you there's a problem even before the damage spreads. Episode 24 answers the next question: how do we make sure the radar doesn't fire all the time because the underlying infrastructure is actually unhealthy?
Performance tuning in Active Directory is preventive work, not reactive. The goal is simple: DCs that serve logons, LDAP queries, and replication stay fast under both normal load and spikes. AD performance is rarely caused by a single root cause — it's almost always a combination of underpowered hardware, wasteful queries, and a database growing without maintenance. Let's dissect each one.
The first rule of tuning: don't change anything without a baseline. A baseline is a snapshot of your infrastructure's "healthy" state — the numbers captured while everything runs normally. Without a baseline, you have nothing to compare against to judge whether a change helps or hurts.
Tip
Capture baselines at two moments: peak hours (e.g. 08:00 when all employees log on at once) and quiet hours. The performance you need is performance at peak load, not the average.
Windows provides a set of AD-specific counters under the NTDS object. See the available counters, then record samples periodically into a baseline file:
Get-Counter -ListSet "NTDS*" -ComputerName dc01.corp.local |
Select-Object -ExpandProperty PathsWithInstancesGet-Counter -SampleInterval 15 -MaxSamples 60 -Counter "\\DC01\NTDS\*" |
Export-Csv -Path "C:\Data\baseline-ntds.csv" -NoTypeInformationThe most informative counters to monitor daily:
| Counter | Meaning | Healthy value |
|---|---|---|
NTDS\LDAP Client Sessions | Active client sessions | Stable under normal load |
NTDS\DRA Pending Replication Synchronizations | Pending replication queue | Near zero |
NTDS\Kerberos Authentications | Kerberos authentications per second | Capacity baseline |
NTDS\LDAP Searches/Sec | LDAP queries per second | Capacity baseline |
Compare today's numbers against the baseline: a sharp spike in LDAP Searches not accompanied by a spike in users is a sign of wasteful queries — the topic we'll dissect shortly.
The baseline tells you actual load; from there you tune the hardware. There's no magic formula, but the direction is clear:
% Processor Time consistently above 70-80%.ntds.dit. The bigger the database, the more RAM is useful. A rough rule of thumb: 2-4 GB per 10,000 objects, plus room for Windows and other processes. 4 GB is enough in the lab; 16 GB and up isn't wasteful in production.ntds.dit is read when queries miss the cache — a slow disk is felt directly as query latency. Put the database and logs on volumes separate from the OS, and separate from SYSVOL if traffic is heavy.RAM is the easiest investment: no configuration, just more cache capacity. If the baseline shows slow LDAP queries but low CPU, it's almost always RAM that's lacking — not CPU.
The baseline isn't a one-time activity. When doing capacity planning, consider four growth curves:
Review the baseline every quarter and compare trends. Good tuning makes load spikes feel ordinary.
LDAP is AD's query language: applications, Group Policy, and PowerShell commands all speak it. Lazily written queries can swallow DC resources without anyone noticing.
AD maintains indexes for certain attributes. Queries filtering on indexed attributes are resolved via the index; those that don't force a full database scan. Attributes like samAccountName, objectSid, userPrincipalName, and memberOf are already indexed. For custom attributes that are frequently filtered, mark them as indexed via the schema:
Get-ADObject -SearchBase (Get-ADRootDSE).SchemaNamingContext -Filter "Name -eq 'extensionAttribute5'" |
Set-ADObject -Replace @{ searchFlags = 1 }Warning
Indexing an attribute grows the database size because indexes are stored as separate structures. Only index attributes actually used for filtering — not every attribute.
Filters like (cn=*santoso*) force AD to examine every entry — the index can't help because the pattern doesn't begin with a fixed character. When possible, use a firm prefix: (cn=santoso*) can still take advantage of the index.
Windows records expensive and inefficient queries via event 1644 in the Directory Service log. Enable it by setting the 15 Field Engineering diagnostic value to 5 in the registry under HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics, then set the Search Time Limit Exceeded threshold on the same key. After that, event 1644 tells you exactly: who sent the query, what filter, and how long it ran. This is the best feed for finding wasteful queries in the field.
The built-in LDAP thresholds are conservative by design: MaxPageSize of 1000 objects per page and MaxQueryDuration of 120 seconds. Applications pulling large datasets can choke on the default MaxPageSize. Set them via ntdsutil:
ntdsutil
ldap policies
set MaxPageSize to 2000
set MaxQueryDuration to 60
show values
quit
quitChanges take effect immediately. Be careful raising MaxQueryDuration: a ravenous query that used to be killed at the two-minute mark may now consume resources longer — always balance with event 1644 monitoring. Also note MaxConnections and MaxDatagramRecv, which limit concurrent connections and UDP packets per second; their defaults are safe for most environments.
ntds.dit grows throughout a DC's life. Deleted objects leave empty space in database pages. There are two kinds of defragmentation:
ntds.dit. It's mandatory while the DC isn't serving (e.g. in DSRM mode) and requires ntdsutil.The offline defrag procedure:
ntdsutil
activate instance ntds
files
compact to C:\compact
quit
quitThe compressed result is written to C:\compact\ntds.dit. Before overwriting the original file: shut down the DC (not just restart), move the old database aside as a backup, copy the new file to the original location, then run dcdiag to confirm the database is valid. Measure the result by comparing file sizes before and after:
ntdsutil
activate instance ntds
files
info
quit
quitImportant
Always back up the database before an offline compact. Overwriting the database file without a backup could turn into a disaster that episodes 17 and 26 were supposed to prevent.
A Global Catalog (GC) is a DC holding a partial copy of every domain's attributes in the forest. When a client or application queries cross-domain objects (including during logon), the GC answers. Two common mistakes:
When planning RAM, remember: a DC that's also a GC must hold cache for the forest dataset, not just its own domain. Consequently, the 2-4 GB per 10,000 objects rule applies to the total forest object count for a GC.
In episode 24 you learned that AD performance is a discipline of measurement: baselines as a comparison point, hardware sizing based on data rather than guesses, query optimization through indexes, filter patterns, event 1644, and LDAP policies, database defragmentation online and offline with ntdsutil, and GC placement plus RAM cache tying it all together.
Key points:
Once the infrastructure runs fast, the logical next question is: how do we move everything to a new structure without stopping production? In episode 25 we dissect Active Directory Migration: inventory and planning, ADMT, SID History, profile migration, up to cutover and rollback. See you there!