BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル SharePoint オブジェクトモデルのパフォーマンスに関する考察

SharePoint オブジェクトモデルのパフォーマンスに関する考察

原文(投稿日:2009/3/4)へのリンク

SharePoint オブジェクトモデルは外部アプリケーションあるいはホストされた Web パーツによる SharePoint コンテンツデータベースに格納されるコンテンツの問い合わせ、修正および作成を可能にします。さまざまなユースケースシナリオに対応したオブジェクトモデルの正しい利用方法に関する多くのブログエントリ知識ベースの文書ベストプラクティスが存在します。

最も一般的なユースケースシナリオは SharePoint リストの表示および修正に関連するものですが、残念ながら SharePoint オブジェクトモデルが必ずしも最適なパフォーマンスにつながるような方法で使われているとは限らないため、多数のパフォーマンス上の問題を目にする部分でもあります。

ユースケース 1:SharePoint リストにはいくつの要素が格納されているか?

この質問に答えるには複数の手段があります。私が何度も目にしている一例は以下のようなものです。

int noOfItems = SPContext.Current.List.Items.Count;

このコードでリストの要素数を得ることはできますが、これを実行するためにはコンテンツデータベースからリストの全ての要素を取得しなければなりません。以下のスクリーンショットは上記のコードのように Count プロパティにアクセスする際にオブジェクトモデル内で何が実行されているかを示しています。

img1

これは小さいリストに対しては問い合わせがかなり高速なため問題にはならないかもしれません。しかしリストが時間とともに大きくなる場合やカスタムコードが本番のデータで全くテストされていなかった場合は問題になるでしょう。

このシナリオに対しては Microsoft は ItemCount という名称の SPList オブジェクト自身の別のプロパティを提供しています。正しいコードは以下のようになるでしょう。

int noOfItems = SPContext.Current.List.ItemCount;

この場合は、SharePoint はコンテンツデータベース内の Lists テーブルからただ1つのレコードを問い合わせるだけで済みます。全ての SharePoint リストの要素が格納された AllUserData テーブル全体に問い合わせずにその情報を取得するために、リスト内の要素数はここに重複して格納されています。

ユースケース 2:SPList を使用してリスト内の要素を表示する?

SharePoint オブジェクトモデルを利用して SharePoint リストの要素を反復処理するにはいくつかの方法があります。実際の SharePoint アプリケーションで私が目にしたことがある1つの方法は、開発者のマシン上では、あるいは非常に小さいリストに対してはうまく動作するかもしれません。しかし、ひとたび数百を超える要素数のリストに対して実行されるとパフォーマンスは台無しになるでしょう。まずは Web パーツ内で現在のコンテキストの SharePoint リストから最初の100個の要素にアクセスするのに利用できるコードスニペットを調べてみることから始めてみましょう。

SPList activeList = SPContext.Current.List;

for(int i=0;i<100 && i

SPListItem listItem = activeList.Items[i];

htmlWriter.Write(listItem["Title"]);

}

リスト内には少なくとも100個の要素があるものとして、SharePoint リストの最初の100個の要素から100個のタイトルを取得するために、このコードはデータベースとの間を何往復するでしょうか?驚くかもしれません。上記のコードを実行するトランザクションを解析した際のデータベースビューからわかるように、合計で200回のデータベース呼び出しが発生します。

img2

その理由は、各ループで Items プロパティにアクセスする際に毎回新しい SPListItemCollection オブジェクトを要求しているためです。Items プロパティはキャッシュされないため、毎回データベースから全ての要素を何度も繰り返し要求することになります。以下は最初のループにおける処理がどのようになっているかを示しています。

img3

正しい方法

これを行う正しい方法はもちろん Items プロパティの戻り値を SPListItemCollection 変数に格納することです。これによってデータベースへの問い合わせは1回だけとなり、コレクションオブジェクト内に格納された結果セットに対して反復処理を行うことになります。以下が修正されたサンプルコードです。

SPListItemCollection items = SPContext.Current.List.Items;

for(int i=0;i<100 && i

SPListItem listItem = items[i];

htmlWriter.Write(listItem["Title"]);

}

あるいは同様のコードにコンパイルされるであろう Items コレクションの IEnumerable インターフェースを利用した foreach ループを利用することもできます。今度は修正後のループが内部的にどのように実行されるか、以下に示します。

img4

ユースケース 3:本当に必要なデータだけを要求するために SPQuery と SPView を使用する

データベースに格納されたデータを扱わなければならないどのような種類のアプリケーションにおいても直面する可能性がある主なパフォーマンス上の問題の一つは、余計なデータが要求されているというものです。現状の手持ちのユースケースにおいて実際に必要とされる以上の情報を要求することによって、以下のような追加的なオーバーヘッドが発生する結果となります。

  • 要求された情報を収集するデータベース問い合わせのオーバーヘッド
  • データベースとアプリケーションの間の通信のオーバーヘッド
  • データベースおよびアプリケーション両方におけるメモリのオーバーヘッド

前掲の2つのユースケースを振り返ってみると、実行される SQL 文が毎回要求された SharePoint リストから全ての要素を選択しているのが見て取れます。SELECT 句が SELECT TOP 2147483648 ... となっているのを見ればわかると思います。

返却される行数を限定する

SharePoint リスト内の要素にアクセスする際に限定された結果セットだけが欲しい場合は SPQuery.Limited プロパティを利用することができます。

以下はその例です。

SPQuery query = new SPQuery();

query.RowLimit = 100;

SPListItemCollection items = SPContext.Current.List.GetItems(query);

for (int itemIx=0;itemIx

SPListItem listItem = items[itemIx];

}

SPList.GetItems と共に SPQuery オブジェクトを使用した場合、SELECT 句は以下のようになります。

img5

既に上記の例で取得したい要素の数は限定しました。しかしながらまだ SharePoint リストで定義されている全ての列を要求しています。本当にエンドユーザに対して全ての列を表示する必要がある場合や、何か計算をするためにそれら全てが必要な場合はこれでいいかもしれません。しかし大抵の場合は必要なのは数個だけで、全部ではありません。

 

取得される列を限定する

データベースから取得する列を限定するには2つの方法があります。

  • SharePoint ビュー「SPView」を使用する
  • SPQuery.ViewFields プロパティを使用する

よって前掲のサンプルコードは以下のような2つの方法で修正することができます。

SPQuery query = new SPQuery(SPContext.Current.CurrentView.View);

あるいは

SPQuery query = new SPQuery();

query.ViewFields = "";

両シナリオにおいて SELECT 句にはそれぞれ SharePoint ビューで定義されているフィールド、ViewFields プロパティで参照されるフィールドのみが含まれます。以下の図は SELECT 句の差異を示しています。

img6

ユースケース 4:SPQuery による SharePoint リスト要素のページング

SharePoint リストには何千個もの要素が含まれる可能性があります。リストの良好なパフォーマンスを得るために超えるべきではない2000個の要素数制限について聞いたことがあります。確かにこの制限を超えるとパフォーマンスに影響があり、インデックスを付加した列やビューを使用してこの制限を乗り越える方法があります。

これらの検討の他に、リスト内のデータに賢くアクセスすることも重要です。既に前のユースケースで解説したように、必要なデータだけにアクセスすることで SharePoint コンテンツデータベースの負担を軽減することができます。加えて、SharePoint オブジェクトモデルはリスト要素へのアクセスを強化するための追加的な機能を提供しています。

データのページングは例えばデータグリッドを利用したリッチクライアントアプリケーションやウェブアプリケーションでよく知られた手法です。ページングによってエンドユーザにとってわかりやすいナビゲーションが可能となり、また正しく実装されていれば、基礎となるデータベースにかかる負荷を減らすことができます。

SPQuery オブジェクトは問い合わせページの開始位置を指定できる ListItemCollectionPosition プロパティを提供します。RowLimit でページ毎に何個の要素を取得するかを指定することができます。ではサンプルコードを見てみましょう。

SPQuery query = new SPQuery();

query.RowLimit = 10; // ページサイズ

do

{

SPListItemCollection items = SPContext.Current.List.GetItems(query);

// ページの結果を利用して何かを行う

 

// 次の反復処理のためにポジションカーソルを設定する

query.ListItemCollectionPosition = items.ListItemCollectionPosition;

} while (query.ListItemCollectionPosition != null)

SPList.GetItems は問い合わせを受け取り、GetItems が呼び出される毎に要素を10個だけ返却します。SPListItemCollection は SharePoint リスト上のカーソルのように振る舞う ListItemCollectionPosition プロパティを提供します。このプロパティは更なるページの反復処理で次のページの開始地点を定義するために利用することができます。以下の図はデータベースの動作を示しています。

img7

SQL 文の1つを詳しく見てみると、あるページの要素を取得するのに SELECT TOP および WHERE 句が組み合わされて使用されているのがわかります。

img8

ユースケース 5:大量の SharePoint リスト要素を更新する

前のユースケースでは SharePoint リストに格納された要素の読み取りアクセスに注目しました。今度は要素の更新あるいは新しい要素の追加の一番良い方法についての議論です。SharePoint オブジェクトモデルは多岐にわたるインターフェースを提供しているため、この場合も複数の方法の中から選択することができます。

SharePoint リストの要素を追加あるいは更新する一番分かりきった方法は SPListItem.Update です。既存の要素を問い合わせる、または SPListItemCollection.Add 経由で新しい要素を追加することでリスト要素を取得することができます。

以下の例を見てみましょう。

for (int itemIx=0;itemIx<100;itemIx++) {

SPListItem newItem = items.Add();

// 全ての個別のフィールドに値を設定する

newItem.Update();

}

このコードを解析してみると、Update メソッドを呼び出す毎に、タスクを処理するストアドプロシージャを実際に呼び出す内部的なメソッド SPListItem.AddOrUpdateItem を本当に毎回呼び出しているのがわかります。

img9

100個の要素をリストに追加するのに合計4.5秒かかっているのがわかります。

個別更新の代わりに一括更新を使用する

もし多数の要素を更新しなければならない場合には、各要素に対して Update メソッドを使用しないことを強く推奨します。代わりに、SPWeb によって提供される一括更新用の関数 ProcessBatchData を使ってください。

ProcessBatchData は一括処理の定義を XML 形式で受け取ります。一括更新の利用方法を説明した素晴らしい文書があります。先ほどの例で一括更新を使用した場合の実装は以下のようになります。

StringBuilder query = new StringBuilder();

for (int itemIx=0;itemIx<100;itemIx++) {

query.AppendFormat("" +

"{1}" +

"New" +

"Save" +

"{2}" +

"", itemIx, listGuid, someValue, "urn:schemas-microsoft-com:office:office#");

}

SPContext.Current.Web.ProcessBatchData(

"" +

"{0}", query.ToString())

同じ100個の要素を ProcessBatchData で追加して内部を解析してみると、更新に費やされた時間は以下の通りとなっています。

img10

両者の更新手段を比較すると、一括更新を利用することで大幅なパフォーマンスの改善が得られることがわかります。

img11

注意

一括更新は大きめの更新を行う場合には本当におすすめです。ただし一括更新用の XML 作成のオーバーヘッドを考慮に入れてください。

  • 必ず StringBuilder を使用し、個別の文字列オブジェクトの連結はしないでください。
  • 生成される XML がメモリ不足例外が発生しない程度に小さくなるよう、一括更新の呼び出しを分割してください。上記の例では50000個の一括更新を実行した際にメモリ不足例外が発生しました。
  • 代替手段として、リスト Web サービスの UpdateListItems メソッドの利用も可能です。

ユースケース 6:一番遅いリストはどれなのか、それらはどう使われそしてなぜ遅いのか?

SharePoint リストに多数の要素が格納されればされるほど、そして表示時にリストがどのようにフィルタされているかによってパフォーマンスが低下する場合があることはよく知られています。リスト1つに対する2000個の要素制限について議論する文書やブログエントリも多数見つかるでしょう。しかしながら2000個の要素が真の問題なのではなく、事実それ以上の要素をリストに格納することはできます。すべてはそれらのリストがどのように表示されるかによるのです。よって問題は、どのリストがパフォーマンスを低下させているかをどうやって特定するのか、そしてそれらは一般にどのように使われているのか?ということになります。

パフォーマンスの修正処置を正しく行うためには、まず最初に現状の利用状況を把握してその結果発生するパフォーマンス上の問題を解析する必要があります。

SharePoint アプリケーションの現在のアクセス統計を把握するにはいくつかの方法があります。IIS のログファイルを解析するか、あるいは SharePoint の利用状況レポート機能を利用することができます。

リストのパフォーマンスを監視する一番簡単な方法はそれぞれの SharePoint リストおよび SharePoint ビューの URL に対応する HTTP レスポンス時間を解析することです。SharePoint の URL は http://servername/site/{リスト名}/{ビュー名}.aspx という形式になっています。

解析を行うためにそれら2つの指標に基づいてリクエストを分類することができます。私は記録された PurePath のデータを正規表現に従って分類するのに dynaTrace の Business Transaction 機能を使用しました。以下の図は私のリストおよびビューの利用状況を示しています。

img12

これらの結果から、どのリストおよびビューが高い頻度で使用されていてそれらがどの程度のパフォーマンスであるかということに関して良い指標が得られます。

特定のリストあるいはビューを表示するページに対する正確なデータを提供するだけの HTTP リクエストの解析に加えて、単に1つではなくそれ以上のリストあるいはビューにアクセスする、あるいは特別にフィルタされた方法でリストにアクセスするカスタム Web パーツやカスタムページによるリストの利用状況を解析することもできます。

これはリストおよびビューの表示あるいは SPList および SPView へのアクセスのために利用される SPRequest.RenderViewAsHtml のような SharePoint オブジェクトモデルとの相互作用を解析することで実現されます。以下の図は SPRequest メソッドの呼び出しに基づく利用状況およびパフォーマンスの評価指標を示しています。

img13

上図にはリストの内部的な GUID が表示されています。各リストおよびビューは GUID で一意に識別されます。実際のリスト名を調べるにはいくつか方法があります。GUID を URL に貼り付けてリストあるいはビューの設定を編集することができます。以下に例を示します。

http://servername/_layouts/listedit.aspx?List={GUID} (GUID は URL エンコードされていなければならない). 

他のオプションとしてはコンテンツデータベースをオープンして AllLists テーブルに問い合わせる方法があります。このテーブルには GUID とリスト名の両方が含まれています。

なぜ特定のリストが遅いのか?

どのリストおよびビューが頻繁にアクセスされているかわかったので、パフォーマンス低下の兆候を示しているものに注目することができます。エンドユーザのエクスペリエンスを改善するために、ごくたまにアクセスされるリストよりも最も頻繁にアクセスされるリストに注目するべきです。

リストのパフォーマンスが低下するのにはいくつかの理由があり得ます。

  • リストビューに表示される要素が多すぎる
  • フィルタおよび列インデックスを使用しないリストの要素が多すぎる
  • カスタム Web パーツによる非効率なデータアクセス

まとめ

SharePoint オブジェクトモデルは SharePoint アプリケーションを拡張するための簡単かつ柔軟な手段を提供します。このフレームワークは SharePoint リストに格納されたデータにアクセスしたり修正したりするさまざまな仕組みを提供しています。しかしながら、必ずしも全ての手法がどんなユースケースシナリオに対しても有効というわけではありません。SharePoint オブジェクトモデルの内部を知ることで、良好なパフォーマンスでかつスケールする SharePoint アプリケーションを作成することができます。

著者について

Andreas Grabner 氏はテクノロジ・ストラテジストとして dynaTrace Software 社に勤務しています。R&D 部門の一員としての役割の中で、彼は dynaTrace 社の商品戦略に影響を与え、またアプリケーションのライフサイクル全体にわたるパフォーマンス管理ソリューションの実装において重要な顧客と緊密に協力しながら仕事をしています。Andreas Grabner 氏は Java および .NET 分野でのアーキテクトおよび開発者として10年の経験があります。

著者の勤務先である dynaTrace software 社について

dynaTrace 社はビジネスに不可欠な Java および .NET アプリケーションのためのライフサイクルを通じた継続的なアプリケーションのパフォーマンス管理における大手企業です。dynaTrace は全ての主要な利害関係者、すなわち開発テストおよび製造のための一般的で統合されたパフォーマンス管理プラットフォームを提供する唯一のソリューションです。UBS、LinkedIn、EnerNOC、Fidelity、そして Thomson Reuters といった業界大手はアプリケーションのパフォーマンスを完全に視覚化し、より速やかに問題を特定して平均修復時間を劇的に90%も短縮するために dynaTrace の特許出願中の技術を利用しています。これらの、そしてその他の大手企業も、時間、お金、および資源を節約するため、パフォーマンス上の問題の発生を積極的に防ぎ、そしてそれでも起こるそれらの問題を迅速に解決するために dynaTrace を頼りにしています。

この記事に星をつける

おすすめ度
スタイル

BT