Čas od času se vyskytují otázky ohledně omezení na výsledky vyhledávání v Active Directory. Nejběžnějším problémem je hodnota MaxPageSize., která má výchozí hodnotu 1000. Existuje k tomu množství různých "urban stories", obvykle zmatených nebo polopravdivých.
Obvyklý požadavek od programátorů nebo skripterů zní - zvýšit. Chtěl bych tu vysvětlit, co to ve skutečnosti znamená. Abyste rozuměli tomu, že tuhle hodnotu není obvykle (neříkám že nikdy) potřeba zvyšovat. Místo toho je lepší změnit styl programování.
Vyhledávání v LDAP Active Directory
Ve Windows existuje knihovna na přístup do LDAP. Knihovna se jmenuje ADSI (Active Directory Services Interface). Používají ji všechny Windows aplikace, konzole Active Directory <Cokoliv>, PowerShell, VBScript i .NET Framework. a jeho namespace System.DirectoryServices. Takže se budeme zabývat jejími nastaveními.
Ale tahle vlastnost je obecnou LDAP vlastností (nejen) Active Directory. Takže ve skutečnosti není důležité, jestli používáte Windows implementaci klienta ve formě ADSI, nebo cokoliv jiného.
Příklad vyhledávání v jazyce PowerShell (vyhledá to všechny uživatele v organizační jednotce OU=Company):
$srch = [ADSISearcher] '(&(objectClass=user)(!objectClass=computer))'
$srch.SearchRoot = 'LDAP://OU=Company,DC=gopas,DC=virtual'
$srch.SearchScope = 'Subtree'
$srch.FindAll()
Poznámka: předchozí vyhledávání používá používá ADSISearcher type accelerator pro třídu System.DirectoryServices.DirectorySearcher.
Stránkované (paged) a nestránkované (nonpaged) vyhledávání (search)
Co nás ale zajímá je, jak se takový vyhledávací požadavek komunikuje se serverem. V předchozím příkladu jsem použil výchozí, nestránkované hledání(nonpaged search). To znamená v principu, že zašlu na DC jeden paket s požadavkem - s těmi vyhledávacími parametry. A dostanete "jeden paket" s výsledky vyhledávání.
Píšu tady "jeden paket" zaměrně v úvozovkách. Samozřejmě jich asi dostanete více, protože těch výsledků bude více a nevejdou se do jednoho TCP paketu. Ale bude to jen jeden paket ve směru do DC a několik paketů ve směru z DC ke klientovi. To podstatné zde je, že to je tedy mnohdy jen na jeden round-trip (dobře, nejspíš více roud tripů, ale jde o velikost TCP window, round-trip-time RTT a různé další optimalizace TCP stacku).
Samozřejmě to taky žere více RAM na DC i na klientovi. Na DC se celý výsledek musí připravit do paměti, potom se celý musí naložit do TCP a odeslat. Na klientovi se to musí zase celé přijmout do paměti. A přitom to stejně zpracováváte po jednom, ne?
Z tohoto důvodu existuje limit zvaný MaxPageSize. Tedy maximální počet výsledků, které je DC ochotné odeslat v jednom balíčku. Čistě proto, abyste ho nepřetěžovali debilními dotazy na všechny objekty. Výchozí je 1000. Informaci dostanete například v článku How to view and set LDAP policy in Active Directory by using Ntdsutil.exe.
Jestliže výsledek vyhledávání obsahuje více objektů, než MaxPageSize, dostanete jich jen MaxPageSize. Takže žádný error, prostě jich jenom dostanete méně.
V tom článku se dá najít info i o tom, že existuje podobný limit i pro počet hodnot v multihodnotových atributech (multivalue attribute) - MaxValRange. Stejný princip. Jestliže je v něčem více jak 1000 (Windows 2000), nebo 1500 (Windows 2003), nebo 5000 (Windows 2008) hodnot, tak se prostě nepřenesou.
Jak vyřešit problém MaxPageSize?
Logický nápad je zvýšit hodnotu MaxPageSize. Problém je, že to vyžaduje modifikaci configuration oddílu (configuration partition) v AD. To by tak nevadilo. Představte si, že programujete skript, nebo aplikaci, pro obecné prostředí. Nemůžete přece chtít po adminech, aby to všude nastavovali. Nemůžete to ani nastavit sami, protože to je docela zásah. Proč byste to také zvyšovali, ohrozíte výkon DCček, jen kvůli vašemu skriptíku.
Stačí upravit program. Stačí použít stránkované hledání (paged search) - jediná změna je políčko PageSize:
$srch = [ADSISearcher] '(&(objectClass=user)(!objectClass=computer))'
$srch.SearchRoot = 'LDAP://OU=Company,DC=gopas,DC=virtual'
$srch.SearchScope = 'Subtree'
$srch.PageSize = 1
$srch.FindAll()
Co to znamená, ten paged search? Řeknete, že chcete výsledky dostávat od DC po kouskách. Klient zašle požadavek ("jeden" TCP paket) a dostane jen několik výsledků ("na jeden" TCP paket). Až chce další výsledky, pošle další požadavek a dostane zase jich jen několik. V mém případě mám PageSize = 1, což znamená, že dostávám výsledky po jednom.
API je uděláno tak, že programátora to vůbec nezajímá. V obou případech, ať používáte nonpaged search, nebo paged search, je vám to úplně jedno. Jediná změna v programu je ta hodnota PageSize.
A je to?
Proč to teda není výchozí? Proč se výsledky nepřenáší po jednom by default?
To je kvůli rychlosti. Bavili jsme se tu o round tripu. Tedy o tom, že musíte zaslat jeden paket a dostanete zpátky celý výsledek (nonpaged search). V případě paged search, posíláte vždycky požadavky po jednom, čekáte na výsledek a zase posíláte další požadavek a tak pořád dokola.
To může ale celkem dost trvat. Příklad?
Řekněme, že výsledkem vyhledávání je 60 000 objektů. Řekněme, že paket tam a zpět mezi klientem a DC cestuje 2 ms na LAN. (tzv. round trip time RTT je tedy 2 ms). Řekněme, že objem celého přenosu je 20 MB. A vaše síť má rychlost 1 Gbps. Zanedbáme čas zpracování na DC a dodatečné RTT, které jsou potřeba pro TCP acknowledgement podle velikosti TCP window.
- použijeme nonpaged search. Zašlu jeden požadavek a za 2 ms začnu dostávat odpověď velkou 20 MB. Přenos 20 MB dat trvá 160 ms. Celá odpověď je u mě za 162 ms.
- použijeme paged search při PageSize = 1. Musím poslat 60 000 požadavků a dostat tedy 60 000 odpovědí. Každé kolečko trvá 2 ms. Celkem, i při zanedbání času potřebného na přenos 20+ MB, to bude trvat alespoň 120 sekund. Bude to ještě více, protože přenášíte data, při režijích tedy samozřejmě více, než těch původních 20 MB.
- když zvětšíte velikost PageSize, ušetříte čas poměrně. Ale zase se musíte vejít do limitu pro dané ADčko a jeho aktuální hodnotu MaxPageSize.
Paged search je tedy pomalejší. Výrazně. Ovšem při menší paměťové náročnosti, zátěži procesoru DC i klienta a celkově hladším průběhu. Bez limitů.
Závěr
Jestliže programujete něco, co stahuje z ADčka 60 000 objektů, tak je vám jedno, jestli to bude trvat v řádu sekund, nebo minut. Obvykle.