December 2015

Volume 30 Number 13

Microsoft Azure - Azure、Web API、および Redis を使用したデータの高速配信方法

Johnny Webb | December 2015

リアルタイム マーケティングが主流となる今日の環境では、ページの読み込みや検索クエリによって頻繁に待ち時間が生じることは、事実上、販売をも停滞させることになります。作成、保存、分析するデータ レコードが増えれば増えるほど、リアルタイム データから充実した洞察を得ることがいっそう難しくなります。問題になるのは、適切なテクノロジ ツールとアーキテクチャを選択し、無数のデータ レコードを最善の形でふるいにかけて瞬時に結果を表示することで、優れたカスタマー エクスペリエンスを提供する方法です。

ここでは、Redis と Web API の実装について顧客をサポートした、最近のユース ケースを紹介します。実装のアプローチ、そこで見つけた課題、および最終的にパフォーマンス要件をクリアした方法を取り上げます。

課題

数か月前、保険サービスや金融サービスを手掛け、Fortune 100 に名を連ねる多角的企業から、グローバル マーケティング企業の Harte Hanks に新しい企画が持ち込まれました。企画の主旨は、オンライン保険見積プロセスのカスタマー エクスペリエンスを改善し、充実することです。そのためには、顧客の保険証書データに対するアクセスを、高速かつ先を見越したものにする必要があります。

インターネット セルフサービス アプリケーションまたは保険外交員が直接対応するアプリケーションを使用して、顧客が 1 つまたは複数の保険証書を表示および選択できるようにすることが目標です。この目標を達成できれば、最終的には、ビジネス価値や顧客満足度が向上し、見積が契約に結び付く率が高まり、保険代理店への委託プロセスも改善されます。これを実現するには、見積プロセス中に顧客が格納した詳細情報やデータを、ほぼ瞬時に処理、照合し、先を見越したかたちで提供する必要があります。

クライアント企業のデータベースには 5000 万件以上のレコードが記録されています。従来のデータベースでこの種の要求を処理すれば、通常、読み込みには数秒を要します。つまり、課された課題は、このような大規模データ ソースにクエリを実行しても、数ミリ秒以内に顧客に結果を配信できる Web サービスを提供することです。

この課題に対し、Redis と Web API の実装を評価しました。全体として評価した測定要因は、以下のとおりです。

  • トランザクション要求数
  • オブジェクト要求数
  • 1 秒あたりの応答数
  • データの総帯域幅
  • サイトの合計スループット

実装方法

データを公開する Web サービスをビルドするには、それぞれ特定の機能を提供するコンポーネントから成る複数の層が必要です。Web サービスの需要が増えるにつれてに、これらの層を支えるインフラストラクチャが迅速に適応して、スケールを変えていく必要があります。このスケーラビリティは不可欠ですが、顧客満足度を向上する場合は、同じように重要な可用性も常に考慮する必要があります。さらに、公開するエンドポイントを監視し、特定の認証プロトコルにラップすることで、データを保護しつつ、パフォーマンスを最大限に高める方法で安全に配信する必要があります。今回のソリューションでは、こうしたニーズに対応するために Microsoft Azure を利用することにしました。Azure 内で、仮想マシン、Web アプリ、仮想ネットワーク、リソース グループ、可用性セットなどのリソースを組み合わせてデプロイし、完全な機能を備えた高品質のソリューションをビルドします。

データ層

今回のソリューションの大半は、マイクロソフト スタックを使用してビルドしているため、通常は Microsoft SQL Server を利用するのが適切です。ただし、今回のソリューションの SLA の要件では、レコード数が 5000 万件を超えるデータセットに対し、要求 1 回あたりのサービス応答時間が 1 秒未満、つまり 1 時間あたり約 6,000 件の要求を処理することが求められています。SQL Server のような従来型データベースはデータをディスクに保存するため、IOPS によるボトルネックが生じるため、すべてのクエリがこの要件をクリアすることを保証できません。問題をさらに複雑にしているのは、数テラバイトにおよぶ無関係なデータが格納される従来型データベースに、公開すべきデータのサブセットがまぎれていることです。そのため、大量のデータセットを迅速にサポートできるソリューションを評価するところから着手しました。この評価によって見つけたのが Redis です。

Redis は、複数のデータ型をサポートするインメモリ データ構造のストアです。サーバーの必須要件には、Linux ディストリビューション、データをカプセルできるだけの容量を備えた RAM、データを永続化できるだけの容量を備えたディスク領域などがあります。Redis をクラスターとして構成することもできますが、クラスター化は数テラバイトのデータを扱うソリューションに適しています。今回のソリューションで扱うデータセットは 50 GB 未満なので、簡潔さを保つため、単一のマスター/スレーブ構成をセットアップすることにしました。この構成をホストするため、Azure の仮想ネットワーク内に CentOS 7.1 を搭載する仮想マシン (VM) を 2 台作成します。各 VM には 56 GB のメモリ、静的 IP アドレス、および AES256 暗号化を使用する SSH エンドポイントを用意します。両 VM は可用性セットを共有するため、Azure は SLA によって 99.95% の稼働時間を保証します。おまけに、作成したすべてのリソースを、このソリューション専用に作成したリソース グループに追加することで、毎月の課金情報を監視、管理できるようにもなりました。VM をデプロイしてから、利用できるようになるまでほんの数分でした。

Redis サーバーを立ち上げるのは簡単で、1 時間も掛からないで完了しました。新しく作成したサーバーに最新の安定版リリースをダウンロードおよびインストール後、最初に構成ファイルを変更して、Redis で言うところの追加専用ファイル (AOF) 永続化を可能にします。つまり、インスタンスに送られるすべてのコマンドをディスクに保存します。これにより、サーバーを再起動すると、すべてのコマンドが再実行され、サーバーが元の状態に復元されます。AOF が肥大化しないように、BGREWRITEAOF コマンドを不定期にトリガーし、メモリ内の現在のデータセットを再構築するのに必要な最短シーケンスのコマンドを使用して、AOF を再書き込みします。最大メモリ使用量を 50 GB に構成し、バッファーを作成することで、大量のデータを読み込む場合でも Redis がすべてのシステム メモリを消費しないようにします。この点を考慮に入れて、今回のソリューションがさらにメモリを必要とする場合は、後から Azure ポータルを使用して VM のサイズを変更し、構成を更新することができます。次に、マスターを認識するようにスレーブ インスタンスを構成し、データ レプリケーションを作成します。こうすることで、マスター インスタンスが使用できなくなった場合でも、スレーブ インスタンスからクライアント企業にサービスを提供できるようになります。最後に、設定した構成が反映されるように Redis サーバーを再起動します。今回使用した構成は以下のとおりです。

appendonly yes
maxmemory 50000000000
slaveof 10.0.0.x 6379
# cluster-enabled no

データの読み込みとベンチマーク

従来型のデータベースから Redis に読み込む必要のあるデータには、約 4 GB の顧客のメタデータが含まれています。このメタデータは毎日更新されるため、ソリューションを最新に保つには、このデータを Redis のデータ型に変換して一括読み込みを行うプロセスを作成する必要があります。この変換と読み込みを実行するため、自動プロセスを作成し、この自動プロセスで毎日の変更セットを抽出して、Redis プロトコル形式のファイルに変換し、さらに利用可能な SSH エンドポイントを使用してそのファイルをマスター サーバーに転送します。マスター インスタンス内で Bash スクリプトを作成し、パイプ モードでファイルを一括読み込みします。パイプ モードは今回のソリューションの重要な要素で、このモードでは 2800 万件のレコードを 5 分以内に読み込むことができます。ただし、パイプ モードはクラスター実装とはまだ互換性がありません。最初のプロトタイプ段階で、2800 万件のレコードの読み込みに数時間を要することが判明しました。これはレコードを個別に転送していたことが原因です。このため、シンプルなマスター/スレーブ実装の設計を維持することに決めました。単一インスタンスに対するパイプ モードのコマンドと応答は以下のようになります。

$ cat data.txt | redis-cli –pipe
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 1000000

パイプ モードを実行後、応答にはエラーの数と受け取った返信の数が示されます。スクリプト内でこの応答を解析し、ファイルをアーカイブして、メール通知を適切な技術チームに送信します。各エラーについては、技術チームがこの抽出データを評価し、再読み込みが必要なデータをすぐに特定します。毎日処理するスクリプトが完成したら、Redis のベンチマーク ユーティリティを使用して、今回の実装に対してベンチマークを実行しました。図 1 は、このソリューションのパフォーマンス結果を示しています。このソリューションが今回の SLA 要件を満たしていることを確認できます。

Redis ベンチマーク
図 1 Redis ベンチマーク

サービス層

クライアント企業が今回のソリューションの唯一の利用者であることは非常に重要です。さいわい、Azure では Access Control Service を使用することで簡単に利用者を限定でき、今回のソリューション専用の OAuth プロバイダーを作成できるようにします。これを実装したら、今回のサービスは要求ごとに固有のトークンが必要になります。新しいトークンは 5 分後に再生成されます。なお、トークンは期限が切れるまで常時保持されます。すべてのサービスに対して新しいトークンを要求すると、必然的にソリューションでボトルネックが発生したり、さらに悪いことに、Azure で指定した要求の上限を超えた場合にはサービスにアクセスできなくなります。この機能を利用するのであれば、運用環境に実装する前に Azure ドキュメントを確認することを強くお勧めします。

利用可能な C# の Redis クライアントは複数ありますが、今回は StackExchange.Redis を使用します。これが最もよく使われ、なおかつ成熟していると考えているためです。データはすべてセット単位で格納されているため、LRANGE コマンドを使用してデータをクエリします。クエリごとに再接続しないように、StackExchange.Redis が使用する接続はシングルトン パターンで管理します。図 2 の例は、データの取得方法を示しています。

図 2 Redis への接続とデータの取得

private static Lazy<ConnectionMultiplexer> lazyConnection =
  new Lazy<ConnectionMultiplexer>(() => {
  return ConnectionMultiplexer.Connect(
    ConfigurationManager.AppSettings[Constants.AppSettings.RedisConnection]);
});
public static ConnectionMultiplexer Connection
{
  get
  {
    return lazyConnection.Value;
  }
}
public async static Task<MemberResponse> MemberLookup(MemberRequest request)
{
  MemberResponse response = new MemberResponse();
  try
  {
    if (request != null && request.customer != null && request.customer.Count() > 0)
    {
      foreach (var customer in request.customer)
      {
        Customer c = customer;
        RedisKey key = GetLookupKey(c);
        IDatabase db = Connection.GetDatabase();
        c.policies = await db.ListRangeAsync(key, 0, -1, CommandFlags.None);
        response.customer.Add(c);
      }
      response.success = true;
    }
    else
    {
      response.exceptions = new List<ServiceException>();
      response.exceptions.Add(Classes.ErrorCodes.Code_101_CustomerRequired);
      response.success = false;
        }
  }
  catch (Exception ex)
  {
    response.exceptions = new List<ServiceException>();
    response.exceptions.Add(Classes.ErrorCodes.Code_100_InternalError);
    response.success = false;
    Logging.LogException(ex);
  }
  response.executedOn =
    Utils.FormatEST(DateTime.UtcNow).ToString(Constants.DateTimeFormat);
  return response;
}

ソリューションを Azure でホストするために Web アプリを作成します。パフォーマンスを最大限に引き出すには、この Web アプリを Redis インスタンスと同じリージョンにデプロイすることが重要です。アプリをデプロイしたら、Redis 仮想ネットワークへの VNET 接続を作成し、サービスからデータに直接アクセスできるようにします。この接続の例を図 3 に示します。

VNET 統合接続
図 3 VNET 統合接続

この Web アプリは CPU 使用率に応じてインスタンスを 10 個までスケール変換するように構成しました。今回のソリューションのトラフィックは変化するため、Web アプリは必要に応じてスケール変換されることになります。追加のセキュリティ層として、クライアント企業専用の IP フィルタリングに加え、SSL 証明書もアプリに適用します。アプリは自動的にスケール変換されるように構成しましたが、フェールオーバーは自動的に行われません。クライアント企業にとってのソリューションの可用性を最大限に高めるために、プライマリ Web アプリの複製を作成します。ただし、その Web アプリは別のリージョンに配置します。両方のアプリを Azure Traffic Manager に追加します。これにより、自動フェールオーバーに利用します。

最後に、カスタム ドメインを購入して今回のソリューションの Traffic Manager URL をポイントする CNAME レコード作成し、実装を完了しました。ソリューションのパフォーマンスを毎日監視するには、Azure Marketplace から直接 New Relic のインスタンスを購入します。New Relic を使用することで、サーバーの可用性を確認できるだけではなく、改善が必要な分野をすぐに特定できます。

まとめ

今回のソリューションを Azure にデプロイすることによって、さまざまなテクノロジ製品を連携して、正しく実装できる強力なソリューションを迅速に提供する方法を説明してきました。Azure にソリューションをデプロイすることで、サーバーのメンテナンスが不要になるわけではありませんが、今回のパターンに適切に従えばうまくいくことでしょう。ソリューションの平均処理時間によっては、ソリューションをすべてクラウドに移行することで、ハードウェア コストを節約することができます。

実装の最後には、同時利用ユーザー数 50 人に対する平均応答時間が 25 ミリ秒まで短縮されました。これらの結果に基づくと、同時利用ユーザー数が 15 人増えるたびに、応答時間は約 10 ミリ秒長くなることになります。


Sean Iannuzzi は Harte Hanks のテクノロジ部門の責任者で、テクノロジ業界で 20 年以上の経験があり、各種のソーシャル ネットワーク、ビッグ データ、データベース ソリューション、クラウド コンピューティング、電子商取引、および財務アプリケーションに関して、テクノロジとビジネスにおけるビジョンのギャップを埋めるうえできわめて重要な役割を担っています。Iannuzzi には 50 を超える固有テクノロジ プラットフォームの経験があり、10 種類を超える技術表彰や認証を獲得し、テクノロジーの目標やソリューションを実現してビジネスの目的達成を支援することを専門にしています。

Johnny Webb は Harte Hanks のソフトウェア アーキテクトで、この 10 年間は名の通った顧客に対し、最先端のテクノロジを活用した高品質なソリューションを実装してきました。Microsoft .NET Framework、ソフトウェア アーキテクチャ、クラウド コンピューティング、Web 開発、モバイル開発、API 開発、SQL データベース、および NoSQL データベースに加え、完全なソフトウェア開発ライフサイクルを専門としています。

この記事のレビューに協力してくれた技術スタッフの Eric Riddle (Liquidhub) に心より感謝いたします。
Eric Riddle は、フィラデルフィアの Liquid Hub でテクニカル マネージャーを務めています。彼は、.NET Framework の誕生以来、.NET Framework を使用したソリューションの開発と設計に携わってきました。フロントエンド Web 開発、REST API、WCF、Windows サービス、および SQL Server を専門としています。金融サービスとダイレクト マーケティング業界に深い経験があります。