ビッグ データ

ASP.NET パイプラインによる Hadoop を使用しない MapReduce

Doug Duerner
Yeon-Chang Wang

コード サンプルのダウンロード(VB)

ビッグ データを処理する MapReduce の能力を、開発中のスマートフォン アプリに追加しようと考えたことはありますか。あるいは、タブレットなどの小型デバイスに多種多様なデータ分析を追加しようと考えたことはありますか。でも、結局、難しすぎて手が出せないとあきらめていませんか。

それでは、既存の単一ノード アプリケーションを、設計全体を見直すことなく、すばやく簡単に分散システムに変換できればと考えたことはありますか。

このような考えが、きわめてセットアップと使用が簡単な RESTful MapReduce コンポーネントを作成するという冒険の旅へといざないます。

Hadoop のような製品は、ビッグ データの課題に適しています。今回取り上げるのは、ビッグ データ アプリケーションの開発を簡単にするために、いくつか機能を犠牲にして、簡潔さと機敏さを考慮して作成するソリューションです。この方法は、専門知識を必要としないで、システムを短時間で正常に稼働させることができます。このソリューションを魅力的にしているのは、Hadoop のセットアップの複雑さに比べたメッシュの簡潔さ、Hadoop クラスターの "重さ" に比べたソリューションの機敏さです。

今回作成するのは非常にシンプルなインフラストラクチャです。簡単に言うと、MapReduce を使用してノードの "メッシュ" を作成し、あるノードで計算負荷が高い処理を実行し、別のノードでデータ収集を実行して、それぞれの結果を 1 つの最終結果に関連付けし集約して、クライアントに返します。

背景

(ASP.NET パイプラインを備えた) IIS Web サーバーは、非常にスケーラブルなエンタープライズ クラスの Web サーバーになることが実証されています。とは言え、このようなテクノロジを Web ページの提供や Web サイトのホストに限定するのはもったいない話です。技術的には、HTTP 経由でアクセスされる、汎用のパイプライン メカニズムとして使用することも可能です。ASP.NET パイプラインの各ステップは順番どおりに実行されますが (前のステップが完了しないと次のステップに進めません)、各ステップを並列かつ非同期に実行してもかまいません。IIS Web サーバーは、HTTP 要求を処理する複数の ASP.NET パイプライン (複数の w3wp.exes) を実行するように構成できます。

ASP.NET パイプラインを、Web ページの提供やホスティングではなく、(たまたま HTTP 経由でアクセスされる) 汎用のパイプラインとして使用するのは、いささか正統ではないように感じられますが、(非同期パイプライン ステップがある) ASP.NET パイプラインは、実際にはマイクロプロセッサ内の CPU 命令パイプライン (ja.wikipedia.org/wiki/命令パイプライン) と非常によく似ています。また、(それぞれに ASP.NET パイプラインがある) 複数の w3wp.exe ファイルを持つことが可能なところは、マイクロプロセッサのスーパースカラー設計 (ja.wikipedia.org/wiki/スーパースカラー) に似ています。パイプライン機能を必要とするあらゆるものに IIS Web サーバーと ASP.NET パイプラインを使用するというソリューションを魅力的にしているのは、これらの類似点と、実証済みのスケーラビリティです。

既に RESTful MapReduce を実行する製品 (Hadoop、Infinispan、Riak、CouchDB、MongoDB など) は多数存在しますが、これらはセットアップが比較的難しく、専門知識が必要になるようです。

今回の前提条件として考えたのが、分散システムや MapReduce システムの設計とアーキテクチャに関する専門知識を必要とすることなく、既に稼働している既存の Windows IIS サーバーを使用すること、作成済みの既存のデータ API メソッドを使用すること、UI 画面のデータをオンデマンドで取得すること、そして、数分で分散 MapReduce システム全体を稼働させることでした。こうすれば、自身のサーバーやクラウドで、既存の小規模なアプリケーションを最小限の労力と知識で大規模な分散システムにすばやく簡単に変換することが可能になります。また、最小限の労力で、既存のスマートフォン アプリに多種多様なデータ分析を追加することも可能です。

この RESTful MapReduce コンポーネントは、別個にゼロから作成されているので、既存のアプリケーションを書き直す必要がありません。これは、既に広範なパブリック データ API がある既存のアプリケーションに基本的な分散機能を追加することだけが目的の場合に最も適しています。図 1 のように、"スキャッター/ギャザー" のような分散コンピューティング パターンをすばやく簡単にエミュレーションすることが可能です。

"スキャッター/ギャザー" のような分散コンピューティング パターンのエミュレーション
図 1 "スキャッター/ギャザー" のような分散コンピューティング パターンのエミュレーション

本稿付属のサンプル プロジェクトでは、この設計理念を用いて、将来拡張可能な、シンプルかつ基本的なインフラストラクチャの出発点を提供します。この設計の魅力は、他の製品よりも優れているところではなく、他の製品よりも簡単なところにあります。今日使用されている、大規模エンタープライズ MapReduce システムの設計を使いやすくしたものとお考えください。したがって、この設計は、Hadoop などの成熟したエンタープライズ MapReduce 製品の代わりとなるものでは決してありません。また、業界トップの製品と機能をほぼ同じくすることもありません。

MapReduce

簡単に言うと、MapReduce とは大きなデータ ストアを集約する方法の 1 つです。"Map" ステップは、多数の分散処理サーバー ノードで実行され、通常、各分散サーバー ノードでタスクを実行することでデータ ノードからデータを取得します。また、必要に応じて、分散サーバー ノード上でのデータの変換や前処理も可能です。"Reduce" ステップは、最終処理を行う 1 台以上のサーバー ノードで実行され、多数のさまざまな結合アルゴリズムを使用して "Map" ステップの結果をすべてまとめて 1 つの最終結果にします。

ビジネス オブジェクト API の場合、Map ステップはデータを取得するためにビジネス オブジェクト API メソッドを実行し、Reduce ステップは Map ステップの結果をすべてまとめて 1 つの最終結果セットにします (たとえば、主キーによる結合を行ったり、グループ別に合計するなどの集計を行います)。この結果が要求元のクライアントに返されます。

MapReduce の主なメリットの 1 つは、"スケール アップ" ではなく "スケール アウト" できることです。つまり、メインのサーバー ノード 1 つを拡張するために性能の高いハードウェアを購入するのではなく、普通のサーバー ノードをいくつも追加することが可能です。スケール アウトは、汎用の市販ハードウェアを使用するため、基本的には安価で柔軟な選択肢です。一方スケール アップは、性能の高いハードウェアが必要になるため、ハードウェアのコストが急激に跳ね上がる傾向にあり、スケール アウトに比べて出費が大幅にかさむことがほとんどです。

ちなみに MapReduce は、データのサイズがきわめて大きく (インターネット規模)、データがログ ファイルやバイナリ BLOB のように部分的に構造化/非構造化されている場合に優れています。対照的に、SQL リレーショナル データベースが優れているのは、スキーマで構造化されたデータを、少なくともリレーショナル データベースのオーバーヘッドが大量のデータを処理できなくなる点まで正規化している場合です。

図 2 では、MapReduce 処理の大まかな概要を示し、シンプルな SQL リレーショナル データベース クエリと、ビッグ データ MapReduce 処理の対応するクエリを比較しています。

シンプルな SQL リレーショナル データベース クエリと、MapReduce の対応するクエリの比較
図 2 シンプルな SQL リレーショナル データベース クエリと、MapReduce の対応するクエリの比較

REST

Representational State Transfer (REST) は、Create/作成、Read/読み取り、Update/更新、および Delete/削除 (CRUD) のパラダイムを使用して、HTTP 経由で実行されるパブリック API を定義します。CRUD はそれぞれ HTTP 動詞 (Get、Post、Put、Delete) に基づいて、要求元のクライアントにサーバーからオブジェクトの表現を返します。REST は、オブジェクト自体へのパブリック アクセスを、単なるオブジェクト上の機能操作ではなく、エンティティとして許可しようとします。REST は仕様や RFC ではなく、単なる設計上の推奨事項です。純粋な REST 設計を忠実に守り、オブジェクトをエンティティとして扱うように URL を書式設定すると、次のようになります。

http://server/MapReducePortal/BookSales/Book A

または、次のように、設計をより RPC スタイルにして、実行するクラスおよびメソッド名指定した URL を書式設定することもできます。

http://server/MapReducePortal/BookSales/GetTotalBookSalesByBookName/Book A
http://server/MapReducePortal/BookSales/GetTotalBookSalesByBookName?bookName=Book A

RESTful MapReduce

RESTful MapReduce とは、API と分散サーバー ノード間のトランスポート機構を対象に、MapReduce 操作を HTTP 経由で実行するという意味を表しています。

API とトランスポート向けに HTTP 経由で REST にアクセスすることには、次のように魅力的なメリットがいくつかあります。

  • ポート 80 経由の HTTP プロトコルがファイアウォールに適している。
  • ほぼすべてのプラットフォームのクライアント アプリケーションが、プラットフォーム固有の依存関係を必要とすることなくリソースを簡単に利用できる。
  • HTTP 動詞 (Get、Post、Put、Delete) は、シンプルで洗練されたリソース要求パラダイムである。
  • Gzip 圧縮がペイロードのサイズを削減するのに役立つ。
  • HTTP プロトコル自体にもメリットがある (組み込みキャッシュなど)。

現状のサンプル プロジェクトは、API とトランスポートを対象に HTTP 経由で REST にアクセスし、Get と Post のみをサポートして、書き込み操作はなく、完全に JSON で通信します。これは、外部で Hadoop 分散ファイル システム (HDFS) にアクセスするよく知られた方法のうちいくつか (Hadoop YARN や Hadoop WebHDFS など) に似ていますが、サンプル プロジェクトでサポートするのは、運用システムに最小限必要なものだけです。Hadoop の代替を作ったり、その広範な機能をすべて再現しようとは考えていません。目指すのは、機能は限られても、非常に初歩的で使いやすい Hadoop の代替を提供することです。

MapReduce の構成

サンプル プロジェクトでは、MapReduce システムで分散サーバー ノードとして使用する、各 IIS サーバー ノード上の仮想ディレクトリの \bin ディレクトリに MapReduceModule.dll をコピーし、web.config のモジュール セクションに次のようにエントリを配置します。

<modules>
  <add name="MapReduceModule" type="MapReduce.MapReduceModule" />
</modules>

これだけです。とても簡単ですね。

IIS サーバー ノードに仮想ディレクトリがない場合は、\bin ディレクトリを含む新しい仮想ディレクトリを作成し、そのディレクトリをアプリケーションに設定して、Microsoft .NET Framework 4 のアプリケーション プールを使用するようにします。MapReduce 要求で、より多くの処理パイプラインを使用できるよう、MapReducePortal 仮想ディレクトリを処理するアプリケーション プールで w3wp.exe ワーカー プロセス数を増やします。その他、IIS サーバーを調整する高度な構成オプションについてここでは扱いませんが、IT 部門のサーバー管理担当者が既に設定している場合がほとんどです。もし未設定の場合は、マイクロソフトの Web サイトを参照してください。

REST の構成

サンプル プロジェクトでは、既存のビジネス オブジェクト データ API メソッドのいずれかに PathInfoAttribute を配置して、URL をメソッドとメソッド引数にマップするのに使用する PathInfo 文字列を指定するだけです。

このサンプル コードが便利なのは、既存のビジネス オブジェクト データ API メソッドが現在返すどのデータ型も変更する必要がない点です。このインフラストラクチャは、返されるデータ型を動的に表すために .NET DynamicObject を使用するので、ほぼどのような型も自動的に処理できます。たとえば、既存のメソッドが Customer オブジェクトのコレクションを返す場合、DynamicObject は Customer データ型を表します。

PathInfoAttribute の PathInfo 文字列は、Windows Communication Foundation (WCF) が使用するのと同じ .NET UriTemplate クラスを使用するので、WCF Web HTTP REST プロジェクトや ASP.NET Web API 2 プロジェクトで実行できる高度な処理 (引数変数の名前の代用、ワイルド カードなど) がすべて可能です。どの URL をどのメソッドにマップするかを選択でき、完全な制御権があるので、自由に REST API を実装できます。たとえば次のように、純粋な REST API に近づけるために、オブジェクトを最上位のエンティティのように URL セグメントに表現させることが可能です。

http://server/MapReducePortal/BookSales/Book A
[PathInfoAttribute(PathInfo="/BookSales/{bookName}", ReturnItemType="Book")]
public BookSales GetTotalBookSalesByBookName(string bookName)
{
}

または、次のように、REST に大まかに従い、URL セグメントで実行するクラス名とメソッド名を URL セグメントに指定させることもできます。

http://server/MapReducePortal/BookSales/GetTotalBookSalesByBookName/Book A
[PathInfoAttribute(PathInfo="/BookSales/GetTotalBookSalesByBookName/{bookName}",
  ReturnItemType="Book")]
public BookSales GetTotalBookSalesByBookName(string bookName)
{
}

どのように設定するかは開発者しだいです。

魅力的な要素

サンプル プロジェクトの設計で魅力的な要素の 1 つがそのスケーラビリティです。そのために、ASP.NET パイプラインを MapReduce パイプラインとして使用して MapReduce 処理を実行します。ASP.NET パイプラインは順番に動作するので、Map と Reduce のステップ両方を実行するのに適しています。さらに、パイプラインは前のステップが完了しないと次のステップに進めませんが、各ステップを非同期に実行できる点が便利です。これにより、別の分散サーバー ノードからの Map 呼び出しを待機してブロックされているパイプラインでも、新しい MapReduce 要求を受け取って処理し続けることが可能です。

図 3 のように、各 w3wp.exe は、MapReduce パイプラインとして機能する 1 つの ASP.NET パイプラインを保持します。w3wp.exe (IIS ワーカー プロセス) は、MapReducePortal 仮想ディレクトリに割り当てられているアプリケーション プールによって管理されます。既定でアプリケーション プールには、仮想ディレクトリへの新しい着信要求を処理する w3wp.exe が 1 つだけありますが、好きなだけ w3wp.exe を保持するよう構成するのは簡単です。したがって、単一のスタンドアロン サーバー ノードにおいて、MapReducePortal 仮想ディレクトリへの MapReduce 着信要求を連携して処理する、複数の MapReduce パイプラインを保持することができます。ASP.NET パイプラインはそれぞれ非同期の性質を持っているため、多くの要求を並行して処理できます。このように、複数の w3wp.exes によって複数の ASP.NET パイプラインが実現されるので、新たな可能性が広がります。

アプリケーション プールの IIS ワーカー プロセス数を増やすことで MapReduce パイプラインを複数保持し、この IIS サーバー上の MapReducePortal 仮想ディレクトリに送信される MapReduce 要求を複数のパイプラインで処理
図 3 アプリケーション プールの IIS ワーカー プロセス数を増やすことで MapReduce パイプラインを複数保持し、この IIS サーバー上の MapReducePortal 仮想ディレクトリに送信される MapReduce 要求を複数のパイプラインで処理

サンプル プロジェクトの設計は、IIS サーバーを好きなだけ追加できるようになっているので、サーバー ノードのより大きな "メッシュ" を形成することができます (図 4 参照)。メッシュが大きくなればなるほど、問題もそれに伴って大きくなる可能性がありますが、これは断片化して対処すれば解決できます。また、メッシュが大きくなれば、さらに高度な並列処理が実現可能になります。1 台のサーバーに非同期の ASP.NET パイプラインが複数あれば、単一のサーバーの CPU コア間で並列処理が可能になります。サーバーのメッシュによって、多数のサーバー コンピューター間でさらに高度な並行処理が実現されます。IIS サーバーをメッシュに追加するのは簡単で、仮想ディレクトリ下の \bin フォルダーに MapReduceModule.dll をコピーして、web.config ファイルにエントリを追加するだけです。IIS サーバーは単なるスタンドアロン サーバーなので、追加構成は必要ありません。対照的に、Hadoop などの製品は、基本的にサーバーを実際のサーバー "クラスター" として構成しなければならないので、手間も、入念な計画も、専門知識も必要になります。

どのサーバー ノードも MapReduce 要求を開始でき、AJAX URL に表示されている他の分散サーバー ノードはすべて、その MapReduce 要求の Map ステップを並行して実行可能
図 4 どのサーバー ノードも MapReduce 要求を開始でき、AJAX URL に表示されている他の分散サーバー ノードはすべて、その MapReduce 要求の Map ステップを並行して実行可能

また、特別に IIS サーバーを構築する必要もありません。MapReduceModule.dll を、既にサーバー上にあるいずれかの仮想ディレクトリにコピーするだけで、既存の IIS サーバーを使用することができます。次回の AJAX 呼び出しには、URL QueryString にある distributednodes リスト パラメーター内の新しい IIS サーバーを含められるようになります。

サーバー メッシュ設計のもう 1 つのメリットは、マスター ノードに依存しないで機能する点です。Hadoop などの製品のマスター ノードは、サーバー クラスターとそのサーバー クラスター内のデータの場所を管理します。Hadoop が運用環境の限界まで拡張されたときに障害の原因となるのは、データ量やインフラストラクチャではなく、マスター ノードです。

今回のサーバー メッシュ設計にはマスター ノードがありません。どのサーバー ノードも MapReduce 要求を開始でき、データはデータを収集するノードに存在します。図 4 のように、どのサーバー ノードも並行して同時にデータを要求/提供することができます。サーバーは、Map 機能を実行しているサーバー メッシュのどのノードからもデータを要求することができます。また、結果を受け取ったら、Reduce ステップで 1 つの最終結果セットにまとめることができます。同時に、1 つのサーバー ノードが Map ステップを処理すると同時に、別のサーバー ノードが行った MapReduce 要求の結果を部分的に返すエッジ サーバー ノードとして機能することもでき、要求はそのノードで集約されます。

現状、要求を行っているクライアントが (URL QueryString にある distributednodes リストを通じて) データの場所を特定していますが、代わりに、各ノードのデータベース テーブルでこのリスト (または、このノードに最も近い隣接ノードか、複数のノードに分散しているデータをホストしているノード) を保存して、それらを実行時にプログラムで URL に追加するよう設計を変更することもできます。ある意味ではこれによって、単一マスター ノードの概念が、各ノードがデータの取得場所を認識している分散されたマスター ノードの概念に変わると言えるでしょう。マスター ノードがまるでメッシュ間に分散されるようになり、メッシュと共に拡張することが可能になります。

このメッシュ設計では、Windows Server、IIS Web サーバー、SQL Server データベースという多岐にわたる実証済みのマイクロソフト製品を使用しているので、これらの商用製品に組み込まれている堅牢な冗長機能やフォールト トレランス機能 (IIS の Windows Server ネットワーク負荷分散 [NLB]、SQL Server のページの自動修復 [可用性グループ/データベース ミラーリング]) を利用できます。これらの機能の詳細については、マイクロソフトの Web サイトを参照してください。

サンプル プロジェクトの設計では、複数の MapReduce 要求を "連鎖して"、ある MapReduce 要求への最初の入力が 1 つ前の MapReduce 要求の結果であるワークフローを形成することもできます。このためには、MapReduce 要求を Get から Post に変更して、MapReduce 要求の結果を Post 要求の本文に含めます。図 5 に、テスト ページの出力結果の例を示します。

テスト ページにおける、連鎖による出力結果の表示
図 5 テスト ページにおける、連鎖による出力結果の表示

サンプル プロジェクトの概要

基本的に、MapReduceModule.dll は ASP.NET パイプラインを MapReduce パイプラインに変換し、Map と Reduce の機能を両方実装するために HttpModule を使用しています。ちなみに、Reduce ステップ中に実行される結合操作 (union など) のいくつかは、IEqualityComparer<T> に依存します。T は DynamicObject なので、(IEqualityComparer<T> はコンパイル時に具象型を定義しなければならないにもかかわらず) 実行時に文字列値としてプロパティ名に基づき等値比較操作を実行することがある意味では可能です。これはすばらしいことです。

図 6 は MapReduceModule.dll の設計の大まかな概要で、MapReduceModule.dll の過程を紹介しながら処理フローを示しています。MapReduceModule が唯一必須の dll で、MapReduce インフラストラクチャに参加させるすべてのサーバー ノード上に存在していなければなりません。MapReduceModule.dll をサーバーに追加するのは簡単で、仮想ディレクトリの \bin フォルダーに MapReduceModule.dll をコピーし、web.config ファイルにエントリを追加するだけです。

MapReduceModule.dll の処理フローの大まかな設計概要
図 6 MapReduceModule.dll の処理フローの大まかな設計概要

図 6 では、IHttpModule が MAP 機能を有効にするために ASP.NET パイプラインの最初のステップを使用しており、そのために ASP.NET パイプラインの "要求処理の開始" ステップの間に発生する AddOnBeginRequestProcessingAsync イベントをサブスクライブしています。IHttpModule はさらに、REDUCE 機能を有効にするために ASP.NET パイプラインの最後のステップを使用しており、そのために ASP.NET パイプラインの "要求処理の終了" ステップ中に発生する AddOnEndRequestProcessingAsync イベントをサブスクライブしています。

つまり、サブスクライブするのは、ASP.NET パイプラインの "要求処理の開始" および "要求処理の終了" イベントのみです。これらは順番に実行され、前のステップが完了しないと次のステップには進みません。

"要求処理の開始" ステップでは IHttpModule がローカル ノードをクエリし、また URL QueryString にある distributednodes リスト パラメーター内の各分散サーバー ノードに HTTP Web 要求を送信して、すべての MAP 要求を開始します。各分散サーバー ノードに送信される HTTP Web 要求は、この要求を開始した URL と同じ URL を使用しますが、URL の中に distributednodes パラメーターはありません。

MAP 要求を受け取る分散サーバー ノード外では、2 つの同じ ASP.NET パイプラインのステップが順番に実行されますが、URL の中に distributednodes パラメーターがないので、"要求処理の開始" および "要求処理の終了" ステップは基本的にそのノードのみをクエリします。PathInfoAttribute で指定される MAP のデータ取得メソッドは、分散エッジ サーバー ノードで実行され、そこからローカル データが取得されます。各分散エッジ サーバー ノードから、要求元のサーバー ノードへ返される応答ストリームにおけるデータは、その後、キーとして URL が使用され HttpContext に保存されるので、最後の REDUCE ステップの間に再取得することができます。

要求元のローカル サーバー ノードでは、そこにあるローカル データを取得するために PathInfoAttribute で指定された MAP のデータ取得メソッドが実行されます。ローカル サーバー ノード上のデータはその後、キーとして URL が使用され HttpContext に保存されるので、最後の REDUCE ステップで再取得できます。

"要求処理の終了" ステップでは、IHttpModule が REDUCE ステップを実行するために、URL QueryString (sum=、union=、sort= などの定義済みのオプション、または reduce=CustomReduceFunction などのカスタムの関数オプションから構成することが可能) で提供された全データと REDUCE パラメーターを HttpContext で探します。次に、指定された REDUCE パラメーターを使用して、全ノードの全データ セットを 1 つの最終結果セットに結合/集約します。最後に、最終結果セットを JSON にシリアル化して、応答ストリーム内のその結果セットを、AJAX MapReduce 要求の開始元のクライアントに返します。REDUCE パラメーターが指定されていない場合、全ノードのすべての未加工データが返されます。図 7 に、テスト ページの出力結果の例を示します。

テスト ページの出力結果
図 7 テスト ページの出力結果

サンプル プロジェクトと Hadoop の比較

図 8 では、基本的な MapReduce 機能を Hadoop とサンプル プロジェクトで比較しています。

図 8 基本的な MapReduce 機能の比較

Hadoop サンプル プロジェクト
単語数を数える Java MAP ジョブ機能 PathInfoAttribute で修飾されたすべてのメソッドが MAP のようなジョブ機能を実現
単語数の合計を出す Java REDUCE ジョブ機能 URL QueryString の Reduce パラメーター (sum= など) が、sum 操作を実行する REDUCE のようなジョブ機能を実現
書き込み可能なインターフェイス (シリアル化) [Serializable()] 属性 (シリアル化)
WritableComparable インターフェイス (並べ替え)

IComparer<T> インターフェイス (並べ替え)

IEqualityComparer<T> インターフェイス (sum、union)

MAP ジョブへの入力は <key,value> ペアのセットで、REDUCE ジョブの出力は <key,value> ペアのセット PathInfoAttribute が設定されたメソッドの引数が MAP ジョブへの入力に。URL QueryString の reduce パラメーターは reduce 操作を実行し、REDUCE ジョブの出力のように結果を JSON にシリアル化

MapReduce が優れている一般的なシナリオの 1 つが、数百万個のドキュメントにおける特定の単語の出現数の計算です。図 9 では、有名な "Hello World" サンプル プログラムのビッグ データ版である "Word Count Sample" を実装する、基本的な擬似コードを一部比較しています。Hadoop の Java コード実装と、サンプル プロジェクトで同等のことを実行するのに使用できる対応する C# コードを示しています。このコードは単なる擬似コードで、適切でも完全でもないことに注意してください。目的は、どちらの設計でも同じ機能を実行できると示すことです。図 10 にテスト ページの出力結果を示しています。

図 9 "Word Count Sample" 擬似コードの比較

Hadoop MAP

public void map(LongWritable key, Text value, OutputCollector<Text, IntWritable> output,
  Reporter reporter) throws IOException {
  String line = value.toString();
  StringTokenizer tokenizer = new StringTokenizer(line);
  while (tokenizer.hasMoreTokens()) {
    word.set(tokenizer.nextToken());
    output.collect(word, one);
  }
}

Hadoop REDUCE

public void reduce(Text key, Iterator<IntWritable> values, OutputCollector<Text, IntWritable>
  output, Reporter reporter) throws IOException {
  int sum = 0;
  while(values.hasNext()) {
    sum += values.next().get();
  }
  output.collect(key, new IntWritable(sum));
}

サンプル プロジェクトの MAP

http://server/.../WordCount/Test.txt?distributednodes=Node1,Node2,Node3&union=Word&sum=Count
[PathInfoAttribute(PathInfo="/WordCount/{fileName}", ReturnItemType="Row")]
public HashSet<Row> GetWordCount(string fileName)
{
  HashSet<Row> rows = new HashSet<Row>();
  byte[] bytes = File.ReadAllBytes(fileName);
  string text = Encoding.ASCII.GetString(bytes);
  string[] words = text.Split(new char[ ]{ ' ', '\r', '\n' });
  foreach(string word in words)
  {
    dynamic row = new Row();
    row["Word"] = word;
    row["Count"] = 1;
  }
  return rows;
}

サンプル プロジェクトの REDUCE

http://server/.../WordCount/Test.txt?distributednodes=Node1,Node2,Node3&union=Word&sum=Count

テスト ページの出力結果
図 10 テスト ページの出力結果

図 11 は、サンプル プロジェクトで基本的な MapReduce 機能を実現する方法について示しています。URL のオブジェクト エンティティが、PathInfoAttribute によって MAP ステップ関数と同等のものにマップされています。また、sum= や reduce= などの URL QueryString の REDUCE パラメーター オプションが、Hadoop の REDUCE ステップ関数と等しくなっています。

図 11 サンプル プロジェクトの基本的な MapReduce 機能

          (Hadoop MAP)                    (Hadoop REDUCE)

http://server/.../BookSales?distributednodes=Node1,Node2,Node3&union=BookName&sum=Sales
[PathInfoAttribute(PathInfo="/BookSales", ReturnItemType="Book")]
public BookSales GetTotalBookSales()
{
}

          (Hadoop MAP)                    (Hadoop REDUCE)

http://server/.../Alarms?distributednodes=Node1,Node2,Node3&reduce=UnionIfNotDeleted
[PathInfoAttribute(PathInfo="/Alarms", ReturnItemType="Alarm")]
public Alarms GetAlarms()
{
}
private static HashSet<Alarm> UnionIfNotDeleted(HashSet<Alarm> originalData,
  HashSet<Alarm> newData)
{
}

その他の例

図 12 は、MapReduce 型の機能を実現する別の方法と、RESTful URL がそのメソッドにどのようにマップされるかを示しています。このメソッドの実装コードは、簡潔にするため省略しています。このコードは、単語数を数えるアルゴリズムから、チェーン展開している書店の各店舗にあるデータベースの BookSales テーブル データ、ビジネス オブジェクト クラスのコレクションを返すビジネス オブジェクト データ API メソッド、国中に散らばった場所のセンサー データまで、さまざまな実装方法があります。すべては、皆さんの想像力しだいです。ぜひ、お楽しみください。

図 12 サンプル プロジェクトで MapReduce を実現するその他の方法

http://server/.../BookSales/Book A?distributednodes=Node1,Node2,Node3&union=BookName&sum=Sales
[PathInfoAttribute(PathInfo="/BookSales/{bookName}", ReturnItemType="Book")]
public BookSales GetTotalBookSales(string bookName)
{
}

http://server/.../Alarms?distributednodes=Node1,Node2,Node3&union=AlarmID
[PathInfoAttribute(PathInfo="/Alarms", ReturnItemType="Alarm")]
public Alarms GetAlarms()
{
}

http://server/.../Alarms?distributednodes=Node1,Node2,Node3&reduce=UnionIfNotDeleted
[PathInfoAttribute(PathInfo="/Alarms", ReturnItemType="Alarm")]
public Alarms GetAlarms()
{
}
private static HashSet<Alarm> UnionIfNotDeleted(HashSet<Alarm> originalData,
  HashSet<Alarm> newData)
{
}

http://server/.../SensorMeasurements/2?distributednodes=Node1,Node2,Node3&union=SensorID
[PathInfoAttribute(PathInfo="/SensorMeasurements/{sensorID}",
  ReturnItemType="SensorMeasurement")]
public SensorMeasurements GetSensorMeasurements(int sensorID)
{
}

http://server/.../MP3Songs?distributednodes=Node1,Node2,Node3&union=SongTitle
[PathInfoAttribute(PathInfo="/MP3Songs", ReturnItemType=" MP3Song")]
public MP3Songs GetMP3Songs()
{
}

まとめ

今回は、HTTP 経由で RESTful にアクセスでき、スマートフォンやタブレットなどの小型デバイスで使用できる MapReduce 機能を実現するための、シンプルで基本的なインフラストラクチャを紹介しました。また、単一のノード アプリケーションを、基本的な分散システムに変換する方法についても触れました。

ほぼすべてのことを実行する、広範な MapReduce インフラストラクチャは多数存在しますが、今回の目的は、きわめてセットアップが簡単で使いやすい、基本的な MapReduce メカニズムを作成することでした。

ソリューションのセットアップと拡張が容易であれば、アイデアを (数台のノート PC で) 小規模にテストして、実証できたら (好きなだけ多くのサーバーに) 容易にスケール アップすることができます。

サンプル プロジェクトでは、Map ステップ内の既存のビジネス オブジェクト データ API メソッドが使用可能で、このためには、そのメソッドへの URL パスをマップする属性をメソッドに適用するだけです。(union のような) 主キーに基づいたデータの結合操作など、URL QueryString にシンプルなコマンドを追加することで Reduce ステップを制御することもできます。

既存のビジネス オブジェクトのデータ API メソッドに属性を適用して、URL の主キー フィールドに基づいて union コマンドを指定することで、単一のノードのアプリケーションを部分的に、ほとんど手間をかけずに基本的な分散システムに変換できる、シンプルなメカニズムが実現されます。これにより、1 か所から分散システム全体を把握することが可能になります。たとえば、通常はその単一のノード上の項目のみを取得するビジネス データ オブジェクトでも、項目内の主キー フィールドに基づいてマージされる、複数のノード上の項目を取得できるようになります。本社から、支社のすべてのデータをオンデマンドで相互に関連付けたり、集約したり、1 つの画面で確認したりすることが可能です。

小型デバイスの場合、"複雑な処理" が発生するのはデバイスではなくメッシュの IIS サーバーです。したがって、たとえばスマートフォン アプリでは、最小限のリソースでシンプルな HTTP 呼び出しを 1 回行って MapReduce のパラダイムを実現することができます。


Doug Duerner* は、マイクロソフト テクノロジが搭載された大規模なシステムの設計および実装を 15 年以上手がけている、シニア ソフトウェア エンジニアです。以前は、Fortune 500 に名を連ねるいくつかの金融機関に務めていました。また、ある商用ソフトウェア企業で大規模な分散ネットワーク管理システムを設計および開発した経験を持ち、そのシステムは DISA こと米国国防情報システム局 ("Global Information Grid" に使用) や米国国務省 (DoS) で使われていました。彼は心からのコンピューターおたくで、あらゆる側面に取り組んでいますが、最も複雑で困難な技術的ハードルにやりがいを感じていて、特にだれもに "不可能" と言わしめる問題を好んでいます。Duerner の連絡先は coding.innovation@gmail.com (英語のみ) です。*

Yeon-Chang Wang* は、マイクロソフト テクノロジが搭載された大規模なシステムの設計および実装を 15 年以上手がけている、シニア ソフトウェア エンジニアです。以前は、Fortune 500 に名を連ねるいくつかの金融機関に務めていました。また、ある商用ソフトウェア企業で大規模な分散ネットワーク管理システムを設計および開発した経験を持ち、そのシステムは DISA こと米国国防情報システム局 ("Global Information Grid" に使用) や米国国務省 (DoS) で使われていました。また、世界最大の半導体メーカーの 1 つに向けて、大規模なドライバー認定システムを設計および実装したこともあります。Wang は、コンピューター科学の修士号を取得しています。複雑な問題が大好物で、連絡先は yeon_wang@yahoo.com (英語のみ) です。*

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Mikael Sitruk および Mark Staveley に心より感謝いたします。
Mikael Sitruk は、多岐にわたるテクノロジが搭載されている大規模なシステムの設計および実装を 17 年以上手がけている、シニア ソフトウェア エンジニアです。マイクロソフトに入社する前は、通信会社でソフトウェア プロバイダー リーダーを務めており、いくつかの斬新な製品を実装した経験を持ちます。分散システム、ビッグ データ、機械学習に情熱を燃やしており、Hadoop エコシステムと、Cassandra や HBase などの NoSQL テクノロジに数年間取り組んでいました。Mikael の連絡先は Mikael.Sitruk@outlook.com (英語のみ) です。

Mark Staveley は、Azure Big Compute チームのシニア プログラマです。Azure チームに入る前は Microsoft Research で (ビッグ データ管理および処理プログラムの監督者として) 活動しており、Xbox One Compilers and Code Gen チームの一員 (ゲーム エンジンのパフォーマンスと互換性に従事) でもありました。Mark はクイーンズ大学で理学士、ワイカト大学で科学修士、メモリアル大学でコンピューター科学/計算化学の博士号を取得しています。マイクロソフトに入社する前は、カナダ最大のハイパフォーマンス コンピューティング センター 2 か所で研究者を務めていました。