はじめに
最近この二つを調査する機会があったので、共通点や相違点などをざっくりと書いておきます。根拠(ベンチマーキングの結果など)は書きませんので、主観的な意見と捉えてください。記憶をたどって書くメモリダンプなので勘違いや不正確な点を含むかもしれませんが予めご了承ください。考察する範囲に関しては限定的であり、すべてを網羅しているものではありませんので、参考程度に考えてください。
校正や修正、追加などのPRはウェルカムです。ただ、提案を検証する時間が無いと思うので明らかにエラー、または明らかに欠落している点に関してはすぐにApproveしますが、それ以外は時間がかかるかもしれませんがご了承を。
Azure Cosmos Dbと Azure Table Storage の共通点
言うまでもないですが、どちらもクラウドデータベースです。Azureのサブスクリプション(無料のも含む)さえあればすぐに使えます。どちらもAzureのポータルから、作成、削除、マネージなどが出来ます。
どちらもサービスです。つまり、使っただけ(Azure Cosmos Dbの場合、使う予定の分だけ)お金を払います。必要に応じてスケールすることが出来ますし、短期のプロジェクトで使用して終わったら消す、といった使い方が出来ます。
どちらもRESTful APIを提供しています。アクセスするために提示するシークレットなども、具体的には多少ことなりますが、Azureポータル上で設定したシークレットを使います。
どちらも.NETと.NET Coreで開発できるNuGetパッケージがあります。RESTful APIを包んだライブラリーです。.NET言語で開発するならNuGetパッケージをプロジェクトに追加するのがまず第一歩でしょう。
どちらもスキーマのないデータを保存できます。SQLなどと比較して、カラムなどをあらかじめ設定する必要はありません。なおかつ、同じテーブル(コレクション)内に異なる型のデータを保存できます。
どちらも「テーブル」に相当する概念があり、複数作成できます。Azure TableではTableと呼び、Azure Cosmos Db ではCollectionと呼びます。どちらもAzureポータルからマニュアルで作成できますし、APIを使ってプログラム実行の一部として作成することも出来ます。(ほとんどすべてのプロビジョニングはコードを実行して行うことが出来ます)
どちらも PartitionKey という概念があります。スキーマは無いけれど、データの物理的なマネージメントをより効率的にするために、データの属性のひとつをPartitionKeyとして指定します。Azure Tableでは必須。Azure Cosmos Dbの場合、記憶が正しければオプショナルだったと思う。
どちらも データの内容を見ることが出来るツールが存在している。Azure Consmos DbはAzureポータルでクエリしてデータの内容を見れますが、Azure Tableでは見れません。ですが、両方とも Azure Storage Explorer などを使えば見ることが出来ますし、基本的なマネージメントも出来ます。
どちらも データのアップロードツールがあります。Azure Cosmos DbではDocumentDB Data Migration Tool、Azure Table ではStorage Explorer を使って100,000~1,000,000程度のデータならアップロードできます。ただ、それにかかる時間は異なります(後述)。
Azure Cosmos Dbと Azure Table Storage の相違点
コスト
コストに関しては、どんなことが出来るかに対してのコストなので一概にどちらが安いかとは言えませんが、データのボリュームを考えるとAzure Table Storageの方が断然安いです。ただ安いにはそれなりの理由もありますから、アプリケーションの要求と照らし合わせて考える必要があります。アクセス時の遅延での要求が高くない状況、たとえば完全にバックエンドの処理であり、しかも処理パイプラインで他のサービスとの依存関係が無い(少ない)場合、などはAzure Table Storageは良い選択かもしれません。
コストの面でAzure Cosmos Dbにユニークなのものとして「毎秒当たりのリクエスト単位(Request Unit per second)」というものがあります。RU/sと略します。このトピックだけで複数の記事になると思いますが、Azure Cosmos Dbを運用する上では避けては通れない概念です。このRU/sが費用と比例しますから、費用を考えるときはすなわちRU/sを考えます。
出来るだけ分かりやすく例えるとすると、レストランを経営しているとして、自分以外の人員を全て外部の会社から派遣してもらうとした場合、その派遣会社が「1時間毎に派遣できる人員数」を示すようなものです。(かえって分かりにくかったかな・・・)。つまり、コックさん、フロア、ウェイター、皿洗い、デリバリー担当、といった異なる役割対して、それぞれ何人派遣します、というやり方ではなく、「1時間あたりサービス人員3人」みたいな言い方をするのです。レストラン側は、この「1時間あたりサービス人員x人」のxの変数を毎時間変えてもいいのです。派遣会社は「1時あたりサービス人員3人」という能力に見合うように、人員を派遣しますが、すべての役割をきちんと果たします。「三人」ではなく「三人分の能力」として考えます。ただ、実際に週末の夜にレストランを回していくのに「1時間あたりサービス人員10人」のところを、レストラン側が「1時間あたりサービス人員5人」として設定していたら、それは設定ミスで、全体としてのお客さんは普段の倍の時間を待たされるはめになるでしょう。
このように、ある時間単位毎に使える要求(とそれを満たすための資源)を指定することで、混雑時に対応でき、さらにそれ以外の時間はコストを抑えられる、そういう運用体制を構築できるようになるのです。では具体的にどのRU/sが適切か、というトピックになると別の話題ですが、一言でいえば「実測」が基本だと思います。運用するデータのボリューム、データの型、クエリの複雑さ、ユースケースなどはアプリケーションごとに異なります。それを一定のスケールとしてサンプルにして、RU/sを実測してみて、それを基本単位にして、ビジネスがスケールした場合にどのぐらいコストが増えるかを見通す、というやり方が分かりやすいと個人的には思います。100人の顧客がユースケース1を1時間に300回、ユースケース2を1時間に200回、した場合に消費するRU/hourを測って、秒単位に直す、というった具合です。
Azure Table に関して言うとRU/sのような概念はありませんし、コストを算出するフォーミュラも単純です。だいたいデータのサイズとトランザクションの数で決まります。こういう言い方は乱暴ですが、とにかく安いので調査するのに人件費を使うぐらいなら、とりあえず使ってみてコストを観察する、といったアプローチでもいいかもしれません。
クエリの柔軟さ
これはAzure Cosmos Dbが圧倒的に優れています。SQLで一般的なクエリ操作はほとんど出来ます。SELECTはもちろん、ORDER BYによるソート、WHEREを使ったフィルター、JOINも出来ます(ただし、collectionをまたいだJOINはできません。collectionをリレーショナルなtableのように設計してしまうと苦痛を感じます。JOINはdocument内のあるデータからforeign keyで参照する別のデータをselectする、といった使い方で便利です)。ただ、SKIPはできません。ランダムに特定のrow まで行って、そこからTOP 100 を持ってくる、ということは出来ません。ページングをするとしたら、RequestContinuationというトークンを受け取って、後続のデータを読み込む際にそれを渡す、というやり方になります。ただ最近では普通になった無限ページスクロールのような使い方では問題ないでしょう。SKIPが出来ないのはAzure Table も同じです。
Azure Tableのクエリは限定的です。特にORDER BYは厳しいです。TableにはPartitionKeyとは別にもうひとつRowKeyというものを設定する必要があります。この二つがAzure Tableに格納されるデータで特別なもの全てです。このRowKeyはソートされた状態で保存されますから、上から読み込んでいけばすでにソート済となります。これだけです。他のfieldはなんのインデックスもありませんし、設定も出来ません。ということは任意のfieldでのORDER BYの操作は、Table内の全てのデータを読み込んでメモリ上でソートする必要があります。WHEREも同様で、全部読み込んでメモリ上でフィルター分け、をしなくてはなりません(RowKeyのfieldならフィルターが可能なのは確認しています)。
クエリの柔軟性の有無が一番問題になるとしたら、ユーザーがUIで結果を待たなくてはならない種類のオペレーションでしょう。メモリに読み込んでフィルターやソートをするやり方は、データセットが小さいのであれば問題にはなりません。100か1000ぐらいなら問題ないでしょう。ですが100000ぐらいになると数十秒の待ちになり、ユーザーはとても我慢できないでしょう。
Azure Tableのクエリの弱点を補う裏技として、ソートしたいfieldをRowKeyにしたバージョンの重複したデータをテーブルに格納しておく、というものがあり、Microsoft のドキュメンテーションでもそう記述されています。つまりCPUコストをストレージコストに変換しておくというわけですね。これはFieldの数やデータの更新の頻度によると思います。例えばアーカイブやhistoryデータなどなら、書き込み時に少し多めに作業をしておいて、いくつかの読み取りパターンに対応した状態にしておく、という方法もあるでしょう。どちらにしろ、読み込みのタイミングでデータのViewをいろいろ操作する、というのはAzure Tableでは諦めた方が賢明でしょう。
スループットの柔軟さ
これは先に上げたAzure Cosmos Db のRU/sの概念の話になってしまいますが、Azure Cosmos Db ではCollectionごとにスループットをRU/sで指定できます。つまり、よく操作されるCollectionは高スループット対応し、一度読み込まれるとキャッシュされるデータのCollectionは低スループット設定、というようなことが可能です。Collectionではなく、Collectionを含むDatabaseレベルでスループットを指定して、すべてのCollectionにスループットを共有させる、といったやり方もあります。Azure Cosmos Dbは設定されたスループットを忠実に守ります(実測したところ、すこし多めに出してくれます)。例えば、スレッドの数を増やして同時に多くの処理を要求すると、設定されたRU/sのレートを超えたあたりで、処理が拒否されるようになります(Rate limittingと呼ばれる挙動です)。その際は、どのぐらい待てば処理を再開できるかのヒントももらえます。ちなみにNuGetライブラリを使うと、その辺りのネゴシエーションも自動的にやってくれます。結果として外から見えるは「処理に時間が係る」という現象です。RU/sを上げて、同じ操作をすると処理が早く終わるのが分かります。
ひとつ気を付けないといけないのが、Azure Cosmos Dbのスループットは予約性で、予約分が課金されます。高スループット設定したCollectionに一度もアクセスしなかったとしても、その経過時間分は課金されます。RU/sを実験で高い数値に設定して、忘れてそのまま放置、などしてしまった日には請求書が来た日に、床に落ちた目玉を探すことになるでしょう。
Azure TableがどのぐらいDegree of Concurrency (DOP)を設定できるかまだよく調査しきれていませんが、すくなくともAzure Cosmos Dbのように表によく見えるような形で設定はできません。体験的な話で言うと、Azure Storage Explorer でデータをアップロートした際に、同じ量のデータをアップロードしたにも関わらず、CosmosDB Data Migration Tool でCosmos Db にアップロードするよりもずっと時間がかかりました。ただし、それがツールの実装(スレッドの数等)に依存するかどうか分かりません。
ひとつ言えることは、Azure Cosmos Dbの方がRU/sという概念を中心に、スループットをより簡単に柔軟的にマネージできるという事実があるということです。私は個人的に、Azure Cosmos Dbのコストが多少割高なのは、一種の保険だと考えています。いざという時、例えば全てのデータをできるだけ短時間でアップデートしなくてはならない、といった事案が出た場合Azure Cosmos Dbならば、一時的にスループット設定を上げ(例えば400 RU/s から20000/s まで上げて)、処理が終わったら400 RU/sまで落とす、というelasticな使い方が出来ます。
まとめ
Azure Cosmos Dbに関して言えば、ここでは触れなかったインデクスポリシーやコンシステンシーのレベルなどの設定も含めて、より高度な設定をすることでアプリケーションの要求により柔軟に対応できると言えるでしょう。一方、Azure Tableはそのシンプルな仕様により、利用目的は限定的にはなるけれど低い運用コストと、おそらくは低いラーニングコストで、大量のデータを扱えるデータベースを素早く構築できると思います。