SharePoint Online での調整またはブロックを回避する

SharePoint Online での調整と、調整とブロックを回避する方法を説明します。より簡単に作業するためのクライアント側オブジェクト モデル (CSOM) および REST のサンプル コードも示します。

SharePoint Online でファイルを移行するときなどに CSOM プロセスが実行されるものの調整されていることがよくありますか。さらに悪いことに、ブロックされることさえあります。このような状況の詳細と、これを回避する方法を説明します。

調整とは

SharePoint Online は、SharePoint Online サービスの最適なパフォーマンスと信頼性を維持する目的で調整を使用します。調整では、リソースの過剰な使用を防ぐため、ユーザー アクションまたは (スクリプトまたはコードによる) 同時呼び出しの数が制限されます。

とは言え、SharePoint Online でユーザーが調整されることはほとんどありません。 このサービスは堅牢で、大容量を処理するように設計されています。 調整の対象になる場合、99% の確率でカスタム コードが原因であるといえます。 これは、調整される他の方法がないことを意味するのではなく、あまり一般的ではないということです。 たとえば、10 台のマシンを起動し、同期クライアントをすべて 10 台で実行するとします。 同期ごとに 1 TB のコンテンツだとします。 これはおそらく調整されるかもしれません。

調整が生じる方法

SharePoint Online で調整が発生する状況

あるユーザーが使用制限を超えると、そのユーザー アカウントからさらに送られる要求は SharePoint Online によって短時間、調整されます。調整が有効な間、ユーザー操作はすべて調整されます。

  • ユーザーがブラウザーで直接実行する要求の場合、SharePoint Online はユーザーを調整情報のページにリダイレクトし、要求は失敗します。
  • その他すべての要求の場合 (CSOM または REST 呼び出しを含む)、SharePoint Online は HTTP ステータス コード 429 (「要求が多すぎます」) または 503 (「サーバーがビジー状態です」) を返し、要求は失敗します。

問題のあるプロセスにより使用量の制限を超え続ける場合、SharePoint Online はプロセスを完全にブロックします。そうなった場合、要求はすべて失敗し、Microsoft は Office 365 メッセージ センターでブロックされたことを通知します。

アプリケーションの調整

ユーザー アカウントによる調整に加え、制限はテナント内の各アプリケーションにも適用されます。 SharePoint Online のすべてのアプリケーションには、テナントごとに固有の利用可能なリソースがありますが、同一のテナントに対して複数のアプリケーションが実行されると、最終的には同じリソース バケットからリソースを共有することになり、まれにですがレート制限が発生します。 このような場合は、SharePoint Online はバックグラウンドの動作よりもインタラクティブなユーザー要求を優先させようとします。

SharePoint Online での一般的な調整シナリオ

SharePoint Online でユーザー単位の調整が生じる最も一般的な原因は、クライアント側オブジェクト モデル (CSOM) コードまたは Representational State Transfer (REST) コードで実行される操作の頻度と数が多すぎることです。

  • 散発的なトラフィック

    影響を少なくするため、SharePoint Online に対する一定負荷または繰り返しの複雑なクエリは最適化する必要があります。 「アプリケーションをスキャンするためのベスト プラクティス」に従ってファイルの一括処理を行わない場合、調整が発生する可能性が高くなります。 これらのアプリケーションには、同期エンジン、バックアップ プロバイダー、検索インデクサー、分類エンジン、データ損失防止用ツール、およびデータ全体を推論して変更を適用しようとするその他のツールが含まれます。

    たとえば、ファイルを SharePoint Online へ移行した後に、ファイルのメタデータを更新するカスタム CSOM または REST スクリプトを実行するとします。CSOM/REST スクリプトは多数のファイルを非常に高い頻度で更新するため、調整が発生します。同様に、REST サービスを使用するオートコンプリート UI ウィジェットは、各エンド ユーザー操作中にリストの呼び出しを多数実行するため、同時にリソースを消費するその他の操作によっては、調整が発生することがあります。

    散発的調整

  • 圧倒的な大量トラフィック

    1 つのプロセスが、長期にわたって継続的に調整制限を大幅に超過します。

    • Web サービスを使用して、ユーザー プロファイル プロパティを同期するツールを作成したとします。このツールは、基幹業務 (LOB) 人事 (HR) システムの情報に基づいてユーザー プロファイルのプロパティを更新します。このツールが呼び出しを実行する頻度が高すぎます。

    • SharePoint Online でロード テスト用のスクリプトを実行しているので、調整が生じます。SharePoint Online ではロード テストはできません。

    • SharePoint Online のチーム サイトをカスタマイズし、ステータス インジケーターをホーム ページに追加しました。このステータス インジケーターが頻繁に更新されて、そのページで SharePoint Online サービスに対する呼び出しが多くなりすぎ、調整が発生します。

    • 移行アプリケーションまたはサイトをクロールしてデータを書き戻すアプリケーションを実行しながら OneDrive Sync クライアントを実行すると、要求量が多くなり、スロットルがトリガーされる可能性があります。

      規則的調整

  • サポートされていないユース ケース

    SharePoint Online のサポートされていない使用方法では、調整が発生する可能性があります。サポートされていないユース ケースの例には、Microsoft 365 と他のレポジトリとの間で SharePoint と OneDrive を中継サービスとして使用することが挙げられます。

  • 同じアプリケーションに対して複数の AppID を作成する

    バックアップやデータ損失防止など、アプリケーションが基本的に同じ操作を実行する場合は、個別の AppID を作成しないでください。 同じテナントに対して実行されているアプリケーションは、最終的にテナントの同じリソースを共有します。 従来、一部のアプリケーションでは、アプリケーションの調整を回避するためにこのアプローチを試みていましたが、テナントのリソースを使い果たし、テナント内で複数のアプリケーションが調整される原因となっていました。

正確な調整制限を設定できない理由

正確な調整制限を設定し公開するというのは簡単そうに思えるかもしれませんが、実際にはさらに厳しい制限につながります。 Microsoft では、SharePoint Online でのリソースの使用状況を継続的に監視しています。 SharePoint Online の信頼性とパフォーマンスを低下させることなくユーザーが最大数のリソースを利用できるように、使用状況に応じてしきい値を微調整しています。また、アプリケーションの制限は、テナントの全体的なユーザー トラフィック、使用状況、その他のいくつかの要因に基づいて設定されます。

このため、コードが Retry-After HTTP ヘッダー値を尊重することが非常に重要です。これにより、指定した日にコードは常時可能な限り高速で実行され、調整制限に達した場合も、コードは必要最小限の範囲で要求を減らすことができます。 この記事で後述するコード サンプルでは、Retry-After HTTP ヘッダーを使用する方法を説明します。

Sites.Read.All アクセス許可とアプリ専用の認証を使用している場合の、クエリのボリューム制限を検索する

SharePoint と OneDrive で、複数の数十億ものドキュメントを処理し、お客様が 1 秒あたりに大量のクエリボリュームを発行できるようにします。 アプリのみの認証を使用し、Sites.Read.All アクセス許可が与えられている (または強力な) SharePoint Online 検索 API を使用している場合、アプリは完全なアクセス許可で登録され、すべての SharePoint Online コンテンツ (ユーザーのプライベート ODB コンテンツを含む) にクエリを実行できます。

このようなアクセス許可を使用している SharePoint Online の検索クエリは 25 QPS に調整されることを、お客様へお知らせいたします。 検索クエリは429の応答で返され、2 分後にもう一度クエリを実行できます。 429 のリカバリーを待機する場合は、類似のアプリのみのアクセス許可を使用し、サービスに対して行う可能性があるすべての検索クエリ要求を必ず一時停止する必要があります。 調整の応答を受信するときに追加のコールを作成すると、アプリが未調整となるまでの時間が長くなります。

システムを拡張するにあたり、システムを効率的に実行し、システムを保護し、この変更を保護するためにシステム強化をすることの重要性を認識します。 この変更は、8月から2020の秋にまで、テナントに展開を始めることが想定されています。

調整に対応するためのベスト プラクティス

  • 要求あたりの操作数を削減する
  • 呼び出しの頻度を減らす
  • 可能な場合は CSOM と REST API よりも Microsoft Graph API を選択する
  • ユーザーがわかるように、トラフィックを装飾する (後述のトラフィックを装飾するためのベストプラクティスを参照)
  • Retry-After HTTP ヘッダーを活用する

Microsoft Graph は、最新の改善と最適化を備えるクラウド生まれの API です。 一般に、Microsoft Graph は、同じ機能を実現するために CSOM や REST よりも消費するリソースが少なくて済みます。 そのため、Microsoft Graph を採用すると、アプリケーションのパフォーマンスが向上し、調整が減る可能性があります。

お客様の要求に対して調整が行われた場合、調整が解除されるまでの遅延を最小限に抑えるため、Retry-After HTTP ヘッダーを活用する必要があります。

SharePoint Online は再トライのタイミングを動的に決定するため、Retry-After HTTP ヘッダーは、調整を受けた際に最も早く対処できる方法です。 言い換えると、呼び出しが失敗する場合でも、使用量の制限に反して呼び出しが発生するため、積極的なリトライは不利に働きます。 Retry-After HTTP ヘッダーを活用すれば、遅延を最小限に抑えられます。

SharePoint Online アクティビティの監視方法について詳しくは、「 Diagnosing performance issues with SharePoint Online」をご覧ください。

Microsoft Cloud における調整についてのより幅広い議論については「調整パターン」を参照してください。

調整を回避するために、http トラフィックを装飾する方法

高い可用性の維持のため、一部のトラフィックが制限される可能性があります。 調整はシステムの健全性が危険にさらされている場合に発生します。調整で使用する要件のひとつとしてトラフィックの装飾があります。これはトラフィックの優先度決定に直接影響を与えます。 適切に装飾されたトラフィックは、不適切な装飾を施したトラフィックより優先されます。

装飾されていないトラフィックの定義は?

  • SharePoint Online への CSOM または REST API 呼び出しに、AppID/AppTitle および User Agent 文字列がない場合は、非装飾のトラフィックです。 User Agent 文字列は、次のように、特定の形式になっている必要があります。

推奨事項には何がありますか?

  • アプリケーションを作成した場合、AppID と AppTitle を登録して使用するようお勧めします。 - これにより、総合的なエクスペリエンスが向上し、今後の問題解決のための最善の道筋が確立されます。また、以下の手順で定義する User Agent 文字列情報も含めます。

    注意

    Azure AD アプリケーションの作成方法の詳細については、「クイックスタート: Microsoft ID プラットフォーム にアプリケーションを登録する ページ」のような、Microsoft identity ドキュメント を参照してください。

  • SharePoint への API の呼び出しに、次の命名規則に従った User Agent 文字列を必ず含めてください。

種類 ユーザー エージェント 説明
ISV Application ISV|CompanyName|AppName/Version ISV を指定し、会社名、アプリ名をパイプ文字で区切り、その後ろにスラッシュで分離してバージョン番号を追加します。
エンタープライズ アプリケーション NONISV|CompanyName|AppName/Version NONISV を指定し、会社名、アプリ名をパイプ文字で区切り、その後ろにスラッシュで分離してバージョン番号を追加します。
  • SharePoint Online API を呼び出すための自前の JavaScript ライブラリを構築している場合は、http 要求にユーザー エージェント情報を付加していることを確認してください。また、適切な場所で、使用する Web アプリケーションをアプリケーションとしても登録することを検討してください。

注意

ユーザー エージェント文字列の形式は RFC2616 に従ったものとします。よって、この勧告に従った右の区切り文字を使用してください。 要求された情報のやりとりにも既存のユーザー エージェント文字列を付加することができます。

注意

ブラウザーで実行中のフロント エンド コンポーネントを開発している場合、最新のブラウザーのほとんどはユーザー エージェント文字列の上書きを許可していないので、この機能を実装する必要はありません。

クライアント側オブジェクト モデル (CSOM) を使用した場合で、ユーザー エージェントを使いトラフィックを装飾する例

// Get access to source site
using (var ctx = new ClientContext("https://contoso.sharepoint.com/sites/team"))
{
  //Provide account and pwd for connecting to SharePoint Online
  var passWord = new SecureString();
  foreach (char c in pwd.ToCharArray()) passWord.AppendChar(c);
  ctx.Credentials = new SharePointOnlineCredentials("contoso@contoso.onmicrosoft.com", passWord);

  // Add our User Agent information
  ctx.ExecutingWebRequest += delegate (object sender, WebRequestEventArgs e)
  {
      e.WebRequestExecutor.WebRequest.UserAgent = "NONISV|Contoso|GovernanceCheck/1.0";
  };

  // Normal CSOM Call with custom User-Agent information
  Web site = ctx.Web;
  ctx.Load(site);
  ctx.ExecuteQuery();
}

REST API を使用した場合で、ユーザー エージェントを使いトラフィックを装飾する例

次の例は、C# の形式ですが、SharePoint Online のページで使用されている JavaScript ライブラリに対しても、類似のユーザー エージェント情報の使用をお勧めします。

HttpWebRequest endpointRequest = (HttpWebRequest) HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/web/lists");
endpointRequest.Method = "GET";
endpointRequest.UserAgent = "NONISV|Contoso|GovernanceCheck/1.0";
endpointRequest.Accept = "application/json;odata=nometadata";
endpointRequest.Headers.Add("Authorization", "Bearer " + accessToken);
HttpWebResponse endpointResponse = (HttpWebResponse)endpointRequest.GetResponse();

CSOM コード サンプル: ExecuteQueryWithIncrementalRetry 実行メソッド

注意

SharePoint Online CSOM バージョン 16.1.8316.1200 (2018 年 12 月のバージョン) 以降を使用することが必要になります。

静的クラス内にこの拡張メソッドを追加し、ExecuteQuery ではなく ExecuteQueryWithIncrementalRetry を使用することで、コードによって要求の調整を処理します。

public static void ExecuteQueryWithIncrementalRetry(this ClientContext clientContext, int retryCount, int delay)
{
  int retryAttempts = 0;
  int backoffInterval = delay;
  int retryAfterInterval = 0;
  bool retry = false;
  ClientRequestWrapper wrapper = null;
  if (retryCount <= 0)
    throw new ArgumentException("Provide a retry count greater than zero.");
  if (delay <= 0)
    throw new ArgumentException("Provide a delay greater than zero.");

  // Do while retry attempt is less than retry count
  while (retryAttempts < retryCount)
  {
    try
    {
      if (!retry)
      {
        clientContext.ExecuteQuery();
        return;
      }
      else
      {
        //increment the retry count
        retryAttempts++;

        // retry the previous request using wrapper
        if (wrapper != null && wrapper.Value != null)
        {
          clientContext.RetryQuery(wrapper.Value);
          return;
        }
        // retry the previous request as normal
        else
        {
          clientContext.ExecuteQuery();
          return;
        }
      }
    }
    catch (WebException ex)
    {
        var response = ex.Response as HttpWebResponse;
        // Check if request was throttled - http status code 429
        // Check is request failed due to server unavailable - http status code 503
        if (response != null && (response.StatusCode == (HttpStatusCode)429 || response.StatusCode == (HttpStatusCode)503))
        {
          wrapper = (ClientRequestWrapper)ex.Data["ClientRequest"];
          retry = true;

          // Determine the retry after value - use the `Retry-After` header when available
          string retryAfterHeader = response.GetResponseHeader("Retry-After");
          if (!string.IsNullOrEmpty(retryAfterHeader))
          {
            if (!Int32.TryParse(retryAfterHeader, out retryAfterInterval))
            {
              retryAfterInterval = backoffInterval;
            }
          }
          else
          {
            retryAfterInterval = backoffInterval;
          }

          // Delay for the requested seconds
          Thread.Sleep(retryAfterInterval * 1000);

          // Increase counters
          backoffInterval = backoffInterval * 2;
        }
        else
        {
          throw;
        }
    }
  }
  throw new MaximumRetryAttemptedException($"Maximum retry attempts {retryCount}, has be attempted.");
}

[Serializable]
public class MaximumRetryAttemptedException : Exception
{
  public MaximumRetryAttemptedException(string message) : base(message) { }
}

SharePoint Online でブロックが生じた場合の対処方法

ブロックは、最も極端な形式の調整です。SharePoint Online サービスの全体的な正常性に深刻な影響を及ぼす可能性がある、長期にわたる非常に大量のトラフィックが発生している場合を除き、テナントをブロックすることは滅多にありません。ブロックは、過剰なトラフィックによって SharePoint Online のパフォーマンスと信頼性が低下することを防ぐために適用されます。アプリまたはユーザー レベルで適用されるブロックは、問題が解決されるまでの間、問題のあるプロセスの実行を阻止します。サブスクリプションがブロックされる場合、問題のあるプロセスに変更を加えるための処置をとらないと、ブロックを除去できません。

サブスクリプションがブロックされると、Office 365 メッセージ センターでブロックされたことが通知されます。このメッセージには、ブロックの原因、問題を解決するための方法のガイダンス、およびブロックを除去するための連絡先が記されています。

関連項目