[AD/ADAM] 10000件以上のデータが登録された環境でサーバソートすると 0x8007202C (1)/4 - 昇順ソートの挙動

みなさんごきげんよう、ういこです。
最近気付いたんですが、このブログって、Technet というより MSDN 寄りなんでしょうか?もしかして浮いてるのでしょうか。
今日のお題は、文字化けの続き…と思ったのですが、Active Directory 関連で、ひとつ重要なケースがあったのでそちらを先に紹介させていただこうと思います。ちょっと長くなるので、3 部作になりそうです。あさってから出張で本社にいってしまうので、それまでに一気に Up します。

【問題】 10000 件以上、大量に ADAM および AD にオブジェクトが登録されている環境に対し、ソートを実行すると

"サーバーは要求された重大な拡張子をサポートしていません。(0x8007202C)"

というエラーが返され、ソートができない。しかし、Name など一部の属性では問題なく動作する。

【解説】Active Directory の大容量データに対するサーバ ソートについて (1) 昇順ソート ~ インデックスつき属性と、インデックスなし属性での挙動の違い
← このコンテンツです!
(2) 降順ソート ~ 既知の問題 (次回予定)
(3) Virtual List View は対処方法となりえるか?(3 回目予定)
(4) まとめ (4 回目予定)

- はじめに
Active Directory からデータを取ってくる際に、ソートしてデータを取得したい場合があると思います。このときのアプローチは、COM ベースの ADSI を使うのと、.NET Framework の System.DirectoryEntry を使うことになるでしょう。ソートの種類は、大きく分けて二つあります。

・サーバ ソート
・クライアント ソート

ソートされたデータを取得することが最終的な目的になりますが、これらの違いを端的にいえば、データをサーバ側で「整える(取得したデータの並べ替えを行う)」か、クライアント側で整えるかということです。サーバ側でデータを整えてやれば、処理は比較的早いです。しかし、サーバ側でメモリを使う、また後述しますがデータを整えるための一時作業領域の制限などがあるといったデメリットがあります。

一方、クライアント側でデータをもらってから整えるような処理を行えば、サーバの一時作業領域の制限に抵触しないですむものの、処理が遅くなりますし、クライアント側のメモリが必要になってしまいます。
ここいらへんの兼ね合いをどうするか、というのは、それぞれのシステム構成、要件などを総合的に判断する必要がありますので、一意にこれがいいという最大公約数的なソリューションはありません。

この記事がソートを要件に持つアプリケーションの設計の一助になればと考えています。この記事は、サーバ ソートにフォーカスしてご紹介させていただきます。
今日のポイントは、「数万件以上の大容量データが Active Directory に存在する場合のサーバ ソートの挙動」というところです。

- 昇順ソートの場合の現象のトリガーおよび原因
この現象は、サーバ側でソートを行う際、ソート キーにインデックス化されていない属性が指定された場合かつ LDAP ポリシーのソートを行う際のデータの一時格納領域 (MaxTempTableSize) に指定された数以上のデータ数が登録されている場合に発生します。
環境は、Active Directory だけでなく、ADAM でも発生します。トリガーは単純に、登録数です。
昇順ソート時に発生するのは、想定しうる動作であり、障害ではありません。

- 再現サンプル コード
<< サンプル スクリプト >>
'''''''''''''''''''' ここから ''''''''''''''''''
Const ADS_SCOPE_BASE = 0
Const ADS_SCOPE_ONELEVEL = 1
Const ADS_SCOPE_SUBTREE = 2

MsgBox "処理開始します"

Set conn = CreateObject("ADODB.Connection")
Set cmd = CreateObject("ADODB.Command")
conn.Provider = "ADSDSOObject"
conn.Open "ADs Provider"
Set cmd.ActiveConnection = conn

cmd.Properties("Cache results") = false
cmd.Properties("Page Size") = 1000
cmd.Properties("Size Limit") = 1000
cmd.Properties("SearchScope") = ADS_SCOPE_SUBTREE
cmd.Properties("Sort On") = "Name"
'cmd.Properties("Sort On") = "otherLoginWorkstations"
'cmd.Properties("Sort On") = "company"
'cmd.Properties("Sort On") = "department"
'cmd.Properties("Sort On") = "displayName"

cmd.CommandText = "SELECT Name FROM 'LDAP://dc=corp,dc=contoso,dc=com' WHERE objectCategory='user'"

Set rs = cmd.Execute
Wscript.Echo rs.EOF

conn.Close
'''''''''''''''''''' ここまで ''''''''''''''''''
<< .NET Framework サンプル>>
// ソースここから
DirectorySearcher ds = new DirectorySearcher();
ds.CacheResults = false;
ds.SearchRoot = objde;
ds.SearchScope = SearchScope.OneLevel;
ds.Filter = "(objectClass=oskevContact)";
ds.PageSize = 1000;
ds.SizeLimit = intRestrictCount; //制限数指定(ex. 1000)
ds.PropertiesToLoad.Add("company");
ds.PropertiesToLoad.Add("department");
ds.PropertiesToLoad.Add("displayName");
ds.ReferralChasing = ReferralChasingOption.All;
ds.Sort.Direction = SortDirection.Ascending; // or SortDirection.Descending
ds.Sort.PropertyName = "name"; // or company or department or displayName
SearchResultCollection src = ds.FindAll();
// ソースここまで

実は、既定の設定で、10000 件以上のデータが存在する環境に対し、このスクリプトを実行すると、cmd.Properties("Sort On") = "Name" の場合はよいのですが、それ以外の "company" などをソート キーにすると、ResultSet (処理結果) がマイナスで返ってしまいます。あるいは、"サーバーは要求された重大な拡張子をサポートしていません。(0x8007202C)" というエラーが返ります。

image

- なぜ ソートキーに指定した属性によって挙動が違うのか?
インデックス化されていない属性でサーバ側でソートを行い、その結果をクライアント側に渡す場合は、データの並べ替えのためサーバ側で一時的にデータを格納するテーブルが作成され、このテーブル内で指定されたキーによる整列を行います。この一時的なソートに用いられるテーブルのサイズが、前述の "MaxTempTableSize" という LDAP ポリシーで決められており、既定は 10000 です。ソートする対象のレコード数が MaxTempTableSize を超えた場合、ソート処理は行われません。

一方、インデックス化された属性のソートの場合にはこの一時データ格納テーブルを使わないためこの制限に抵触しません。[Active Directory スキーマ] スナップ イン (★) でこれらの属性を見てみると、問題の発生しなかった name 属性はインデックス化されています。

image
一方、現象が発生してしまう company 属性などはインデックス化されていないことがわかります。

image
属性ごとの昇順ソート時の動作の違いはこの「インデックスあり / インデックスなし」という状況から生じているのです。

★ Active Directory スキーマ スナップインの使い方はこちらのリンクに詳しい使い方があります。参考になれば幸いです。

[AD初級編] AD と ADAM の微妙な違い ~管理ツールもちと違う : スキーマ管理ツール編~ [https://blogs.technet.com/jpilmblg/archive/2009/06/24/ad-ad-adam.aspx](../jpilmblg/ad-ad-adam-1246 "https://blogs.technet.com/jpilmblg/archive/2009/06/24/ad-ad-adam.aspx

")
image

- 対処方法
この現象の対処方法は、以下のとおりですが、ただし 一番簡単なインデックス付与は、降順ソート時固有の問題にも影響されるため、昇順ソートだけでよいシステム上でのみの対処としかなりません

-1. MaxTempTableSize 値を拡張する (※変更方法はこちらあるいはこちらをごらんください)
-2. ソートキー対象のスキーマ属性にインデックスを付与する
-3. クライアント側でソートを行う

- 対処方法のメリット、デメリット
上記 3 点は、それぞれメリット、デメリットがあります。環境、アプリケーションの設計などにあわせ、 いずれの方法がよいか検討をお勧めいたします。

-1. MaxTempTableSize 値を拡張する
メリット
・クライアント側の負担が少ない
・アプリケーションの実装を変更する必要がない、もしくは変更が少なくてすむ
デメリット
・レコード件数の増加とともに、常に変更させる必要がある
・値を増加させるとサーバ側のメモリ消費量が大きくなる
・サーバ側の設定を変更する必要がある

-2. ソートキー対象のスキーマ属性ににインデックスを付与する
メリット
・インデックス付き属性に基づくクエリのパフォーマンスの向上が期待できる
・検索文字列を指定するときに、ワイルドカード (*) が使用可能になる
・アプリケーションの実装を変更する必要がない、もしくは変更が少なくてすむ
デメリット
・AD データベース のサイズの増加だけでなく、レプリケーションおよびオブジェクト作成時にもサイズ増加による悪影響を与えることがある(特にマルチバリュー属性の場合)
・値を増加させるとサーバ側のメモリ消費量が大きくなる
・サーバ側の設定を変更する必要がある

-3. クライアント側でソートを行う
メリット
・サーバ側に負担をかけない
・サーバ側の設定を変更する必要がない
デメリット
・クライアント アプリケーション側でソートを行うため、クライアント側のメモリ消費量が増大する可能性がある
・クライアント アプリケーションの動作が遅くなる
・アプリケーションの実装を変更する必要がある

- クライアント ソート
サーバ側に負担をかけたくない場合は、ADO.NET などを使用し、クライアント側に処理を持たせる "クライアント カーソル" の機能の実装も有効でしょう。クライアント側でレコードセットに "Page Size" 属性を設定することにより一回の検索につき、一ページ分のレコードの取得を行うようになります。
これは、サーバ側でも同様で、一回の検索につき、クライアントから要求されたレコード数のみのキャッシュを作成し、クライアントに送信します。このとき、クライアント側カーソルを使用している場合、サーバは 1 ページ分のレコードをクライアントに提供し、次のページのデータ要求が発生するまで、検索を一時停止します。この方法でも、一回につきある程度まとまったデータを取得するため、効率の向上が期待できます。
以下の技術情報に一例が載っていますので、ぜひごらんください。

"Windows 2000 または Windows Server 2003 LDAP クエリを並べ替えるため
にhttps://nguishedName 属性は使用できません。"
https://support.microsoft.com/default.aspx?scid=842637

Vhttps://asic .NET で ADO レコードセットを使用する方法
https://support.microsoft.com/kb/315974/ja

~ ういこう ~