2010年11月27日土曜日
アプリケーションアーキテクチャとデータベース
2010年11月26日金曜日
データベースの選択
こうなると、インメモリデータベースも具体的に考えてみたくなります。
当初は、SQLiteのインメモリデータベースモードを使って、自分でメモリクラスタを組むようなことを考えていたのですが、色々調べてみるとどうもVoltDBがよさそうに思えてきました。
VoltDBはRDBMSなので、SQLの豊富なデータ操作機能を用いることができます。
いわゆるKVSを使うと、アプリケーション側で相当の作り込みが必要なので、この点でRDBMSは安心感があります。
VoltDBは、自動的にメモリクラスタを組んでくれるみたいなので、この点でもアプリケーションは何もしなくて済みそうです。
メモリクラスタによってスケーラビリティを高めるとともに、信頼性を向上させることができます。
VoltDBではJavaを使ったストアドプロシジャとしてプログラムを書く必要があるので、この点がちょっとクセのあるところです。
たとえば、本アーキテクチャの場合、ドメインサービスだけでなく、アプリケーションサービスもストアドプロシジャとして実現するような実現方式も検討が必要になります。
この点に問題がなければ、かなり有力な選択肢ではないかと思います。
インメモリデータベースの弱点は、メモリクラスタが完全にダウンした場合の永続性の確保と、大規模データがメモリに載り切らないリスクです。
本アーキテクチャでは、Eventログを用いて永続性は確保できているので、メモリにデータが載り切る場合には、Eventログの分散KVS(または普通のRDBMS)とVoltDBだけでシステムが組むこともできそうです。(AWSの場合、メモリモデルとして7.5GB, 15GB, 34.2GB, 68.4GBが用意されており、一般的なアプリケーションではメモリのみで運用することが可能です。すでにそういう時代に入っています。)
ただし、メモリクラスタ再起動時にEventログから最新の状態を復元するのに時間がかかりそうなので、何らかのチェックポイントをどこかに保存して置く必要があります。
そうなると、結局のところバックアップデータベースとしてHBaseを併用するのがバランスがよさそうです。
図の左側にあるマスターデータはローディングが早ければ何でもよいので、HBaseに相乗りで十分でしょう。
以上の考察から、今の所:
- Eventログ用DB:append-only&shardingで高速書き込みできる分散KVSの何か
- ドメインサービスのインメモリデータベース:VoltDB
- バックアップデータベース&Hadoop:HBase
- マスターデータベース:HBase(に相乗り)
2010年11月25日木曜日
Hadoopの置き場所
そこで、考えてみたのが以下の図です。
アーキテクチャ的には、MapReduceやその実装としてのHadoopだけでなく、より汎用的な概念の導入が有効ではないかということでBackground Computingとしています。
さらに細かく考えていくと、Background ComputingもBatchとRealtime(Streaming)に分けることができそうですが、これはいずれ。
ボクは、クラウドアプリケーションとは、非同期に発生するイベントを連続して受け取りながら、イベント受信をうけて内部状態を刻一刻と遷移させていくオブジェクトと考えています。右の図はこのあたりをJJUG CCC 2010 Fallでお話した時に使ったものです。
この中で、インデクサとしているものが、上の図のBackground Computingに相当します。
クラウドアプリケーション自身はイベント駆動で動作しますが、その応答性能や結果精度を向上させるために、必要な情報をバックグラウンドで常に作り続けているのも、クラウドアプリケーションのもうひとつの側面になります。
この部分の実現機構として、MapReduce/Hadoopが重要な選択肢というわけですね。
Hadoopを組み合わせるということで具体的に考えてみると、HBaseはカラム指向データベースということに思い至りました。アーキテクチャ素案では、ドメインサービスのメインデータベースはインメモリデータベースで、このバックアップとしてカラム指向データベースを併用しています。このバックアップデータベースをHBaseとすることで、Hadoopとの連携をシームレスに行う事ができるようになります。
Background Computingの結果は、バックアップデータベースに直接戻すケースと、イベントとして通常のデータ更新ルートに載せるケースの両方が必要でしょう。
そのあたりを図に追加しています。
2010年11月20日土曜日
クラウドアプリケーションのアーキテクチャ素案
並行・並列・分散技術。RDBMS JoinからDryad。旬のテーマで参考になる点が多々ありました。
これに加えて、懇親会で萩原さんからお聞きしたカラム指向データベースのお話が刺激的。クラウドアプリケーション・アーキテクチャのインスピレーションが浮かんだので、図にまとめてみました。
CQRS、Event Sourceのアーキテクチャの上で:
- 各種データベースの選択とアーキテクチャ責務分割
- プレゼンテーション、アプリケーション、ドメインの各サービスのアーキテクチャ責務分割
- 可用性の担保
- スケーラビリティの担保
Domain Serviceのクラスタを構築してin memory databaseでワークデータの管理をするのが面白いかなと思っているのですが、Domain Service初期化時にすべてのデータをメモリにローディングするのは現実的ではないので、バックエンドのデータベースが必要となります。ここにカラム指向データベースがぴったりはまるのではないか、というのがインスピレーション。RDBMSでもよいところですが、スケーラビリティを高めるにはカラム指向データベースという選択が有効と考えられます。
エージェント,というとやや大げさですが、ストアドプロシジャとかクロージャとか、そういった技術でデータの近くで処理を行って結果を返す機能が必要になるのは明らかなので、そのあたりもアーキテクチャに取り込みたいところ。ここは、Domain Service界隈で実現できるのではないかと考えています。
他にも色々と話題があるので、この図をもとにブログで実現方式を整理していこうと思っています。
2010年10月13日水曜日
[g3]データストア3
日曜日に公開したg3 0.2.1でg3アプリケーションDataStoreCrud.scala(以下に再掲)を実行してみましょう。
package org.goldenport.g3.app
import org.goldenport.g3._
import org.goldenport.g3.atom._
import org.goldenport.g3.messages._
import org.goldenport.g3.messages.datastore.{Create, Fetch, Query, Insert, \
Update, Delete, Drop}
class DataStoreCrud extends G3Application with UseRecord {
val KIND_NAME = 'g3crud
val schema = Schema(
IdField,
('name, XToken),
('zip, XToken),
('address, XString),
('phone, XToken, ZeroMore),
('comment, XString))
datastore('appds)
val create = Create(KIND_NAME, schema)
val fetch = Fetch(KIND_NAME, 5)
val query = Query(KIND_NAME, Id(5))
val insert = Insert(
KIND_NAME,
Record('id -> 5, 'name -> "Yamada Taro",
'zip -> "1234567", 'address -> "Yokohama",
'phone -> "0451234567", 'comment -> "omlet rice"))
val update = Update(
KIND_NAME,
Record('id -> 5, 'name -> "Suzuki Hanako"))
val delete = Delete(KIND_NAME,
Record('id -> 5))
val drop = Drop(KIND_NAME)
port("/create") agents(create) invoke("appds")
port("/fetch") agents(fetch) invoke("appds")
port("/query") agents(query) invoke("appds")
port("/insert") agents(insert) invoke("appds")
port("/update") agents(update) invoke("appds")
port("/delete") agents(delete) invoke("appds")
port("/drop") agents(drop) invoke("appds")
}
org.goldenport.g3.app.DataStoreCurdはg3に組み込まれているので、以下のように実行します。
$ g3 -g3.application:org.goldenport.g3.app.DataStoreCurd \
-g3.server
ルート「/」にアクセスすると、DataStoreCurdでは対応するポートがないのでエラーとなります。(エラーメッセージは改良する予定。)
以下、Create、Insert、Fetch、Update、Query、Delete、Dropの順に実行すると以下のようになります。特に画面の定義はしていませんが、ポートチャネルを実行して結果のメッセージに応じて自動的に整形して表示するようになっています。
おまけ
2010年10月10日日曜日
[g3]g3 version 0.2.1
配布物は以下の2つです。用途に合わせてどちらかをダウンロードしてください。
- g3-0.2-bin.zip:配布バイナリ。unzipして使用。
- g3-0.2-jar-with-dependencies.jar:実行形式。java -jar g3-0.2-jar-with-dependencies.jarで直接使用。
g3 0.2.1は、Google AppEngine、データストア、WebUI周りの改良を行ないました。
2010年10月8日金曜日
[g3]データストア2
前回の続きです。
データストアチャネル
データストアはデータストアチャネル経由でアクセスします。
ここでは、以下のようにappdsという名前のデータストアチャネルを定義しています。
datastore('appds)
データストアチャネルはデフォルトではg3に組み込んでいるRDBMSのDerbyを使用します。また、AppEngine上で動作させるとAppEngineのデータストアを使用します。
データストアチャネルの定義あるいは外部定義ファイルによって、任意のJDBC URLやJDBCドライバを指定することができます。ただし、現時点の実装ではデータベース固有のデータ型に対応していないので、Derby以外のRDBMSは事実上動作しないと思われます。いずれ、MySQLやPostgresなどのデータベースにもアクセス可能にする予定です。
カインドの作成
カインドの作成は、Createコマンドをデータストアチャネルに送信することで行ないます。Createコマンドには、カインド名とスキーマを設定します。
val create = Create(KIND_NAME, schema)
port("/create") agents(create) invoke("appds")
agentsエージェントは、メッセージを受信すると、引数に指定されたコマンドを発行するエージェントです。この場合は、Createコマンドをinvokeエージェントに送信しています。invokeエージェントは同期型でデータストアチャネルappdsにメッセージを送り、データストアアクセスの結果を受け取ります。 invokeエージェントは、このチャネルの最後のエージェントなので、invokeエージェントが受け取ったメッセージが、このチャネルの最終結果となります。
レコードのインサート
カインドに対するレコードのインサートは、Insertコマンドをデータストアチャネルに送信することで行ないます。Insertコマンドには、カインド名とインサートするレコードを設定します。
val insert = Insert(
KIND_NAME,
Record('id -> 5, 'name -> "Yamada Taro",
'zip -> "1234567", 'address -> "Yokohama",
'phone -> "0451234567", 'comment -> "omlet rice"))
port("/insert") agents(insert) invoke("appds")
レコードのアップデート
カインドに格納されているレコードのアップデートは、Updateコマンドをデータストアチャネルに送信することで行ないます。Updateコマンドには、カインド名とアップデートするレコードを設定します。レコードには、IDと更新するフィールドのみを設定すればOKです。
SQLの場合はUPDATE文で、AppEngineの場合は読み込みと書き戻しをデータストアチャネル側で行ないます。
val update = Update(
KIND_NAME,
Record('id -> 5, 'name -> "Suzuki Hanako"))
port("/update") agents(update) invoke("appds")
レコードのフェッチ
カインドに格納されているレコードの取り出しは、Fetchコマンドをデータストアチャネルに送信することで行ないます。Fetchコマンドには、カインド名とIDを設定します。
val fetch = Fetch(KIND_NAME, 5)
port("/fetch") agents(fetch) invoke("appds")
レコードのクエリ
カインドに格納されているレコードの問合せは、Queryコマンドをデータストアチャネルに送信することで行ないます。Queryコマンドには、カインド名と問合せ式を設定します。
ここでは「Id(5)」という問合せ式で「IDが5」のレコードの問合せを行っています。
val query = Query(KIND_NAME, Id(5))
port("/query") agents(query) invoke("appds")
レコードの削除
カインドに格納されているレコードの削除は、Deleteコマンドをデータストアチャネルに送信することで行ないます。ここでは、Queryコマンドには、カインド名とIDを設定したレコードを設定しています。
val delete = Delete(KIND_NAME,
Record('id -> 5))
port("/delete") agents(delete) invoke("appds")
カインドの削除
カインドの削除は、Dropコマンドをデータストアチャネルに送信することで行ないます。Dropコマンドにはカインド名を設定しています。
val drop = Drop(KIND_NAME)
port("/drop") agents(drop) invoke("appds")
次回に続きます。














