ザカードクラウド

しばらくぶらぶらしていましたが、昨日私がプロデュースするデータベースがリリースされましたので報告させていただきます。

フリーウェイ、無料のカード型クラウドデータベースを発表 (japan.internet.com)

データベース名は「ザカードクラウド」。12年以上前からお世話になっている社長の経営するフリーウェイジャパンから提供されています。


一度はやらなければと思っていたのですが、誰でも直感的に使えるデータベース、というものを、クラウドという技術によってインフラが大きく変化しているこの時期に、いちからデザインしてみました。

スクリーンショットはこんな感じです。

技術的には、Google App Engineのデータストア(BigTableとprotocol bufferをベースに作られている)でインフラを、Objective-JのCappuccinoでクライアント部分を開発しました。この先は技術的な話に触れて行きますので、そんなことに興味はないという方は、こちらから実際に触ってみてください。ブラウザはGoogle ChromeまたはアップルのSafari、ログインするにはGoogleのアカウントが必要です。

ザカードクラウドのアプリへ



元々このアイデアは、パソコン黎明期にアスキーサムシンググッドという会社から提供されて人気を博していたザカード、というデスクトップソフトウェアをクラウド上で使えたらいいな、というニーズからスタートしました。ザカードというのは、今ではファイルメーカーぐらいしか無くなってしまったカード型データベースに分類されるもので、図書館の貸し出しカードのようなカードで、データを管理するシステムのことです。技術的にはリレーショナルデータベースというものに圧倒されて完全に隅においやられてしまいましたが、その分かりやすさと操作のしやすさから、今でも根強いファンがいるのがカード型データベースであり、その日本製の代表格がザカードという製品だったわけです。

そのザカードの良さをできるだけ残しつつ、クラウドという環境に今使える技術を考慮し、設計したのがザカードクラウドになります。特に注意したのは、ブラウザで提供するという決定から、ホームページの延長のようなウェブアプリケーションではなく、きちんとデスクトップ製品の競合と認識されるレベルのものを作ろうという事でした。これには以下のようなチャレンジがありました。

  1. 非同期/分散が特徴のクラウドデータベースでどうやって確実なデータ操作を実現するか
  2. デスクトップ級の複雑なコードをJavascriptでどうやって書くか(かつスピーディーに改善していくか)


非同期/分散が特徴のクラウドデータベースでどうやって確実なデータ操作を実現するか

例えばデスクトップのアプリケーションを開発しようとする場合、ファイルシステムがOSから提供され、ファイルシステムの操作が開発環境から提供されるので、基本的にはアプリケーション自体に専念することができます(あなたがOSをいじっている奇特な技術者で無い場合)。ところがブラウザではどうでしょうか?HTML5でローカルディスクへのアクセスができるようになったとはいえ、サイズの問題に、何より共有の問題があります。そう、唯一できるのはHTTPプロトコルでサーバーにアクセスすることだけです。

これはいってみればハードディスクへのデバイスドライバを書いているようなもので、信頼できるフレームワークファイルシステム)を構築することなしに、一定以上の複雑なシステムをその上に構築することはできないと考えました(私一人では)。インフラが慣れ親しみ、四角四面のリレーショナルベースだったらまだ多少の救いはありましたが、なんでも受け入れてくれるNoSQLのBigTable。リレーショナルではそもそもコストに見合うものが作れない。

そこでまず考えたのは、どういうデータをどう扱うか、それを定義し管理する役割の層を作ろうということでした。参考にしたのはMacの開発環境であるCocoaのCoreDataフレームワークでした。EntityDescriptionにデータが定義され、各EntityのインスタンスはManaged ObjectとしてContextの中でidentityを与えられ管理される。これは非常に良さそうに思え、そっくりObjective-Jに移植する作業から開始してみました。データストアへのアクセス部分を管理するCoordinatorは簡略化しましたが、当初これはなかなかうまくいきました。

うまくいかないことが分かったのはそれから一ヶ月程経ってからのことで、事前にEntityDescriptionを定義しなければ使えないということでした。当たり前と言えば当たり前なのですが、今回作ろうとしているのはザカード。ユーザーが自由に項目は定義できるようでなければならないのです。具体的に問題だったのは、どこのサーバーにどうアクセスしたらよいかでさえ、事前に定義するのではなく、Entityに処理させるようにできないと、フレームワーク化できない点でした。

そこで、ManagedObjectModelをブートストラップとすることにし、そこに含まれるEntityDescriptionから必要なデータのロードを行い初期化することにしました。後はユーザーの操作に応じてEntityDescriptionが解釈してサーバーにアクセスする指示を行います。

こうしてようやく何か確かなものが、あらゆるものが定義可能(何も決まっていない)な世界に登場しました。ファイルシステムが出来上がった感じです。


ところがこれでもまだまだ。ようやく土俵に上がったところです。ここからはいよいよ非同期/分散のデータベースを確実に処理できるようにしなくてはなりません。

Google App Engineのデータストアはkey/valueのデータベースで、isolationはserializable/read committed、コミットはログベースで開始と完了の同期操作(ログは非同期で世界中のどこかのGoogleのサーバーに並列に書き込まれます)、ancestorとnamespaceによる空間があるのが特徴です。実際必要なものは全部揃っていて、後はこれらをどう設計し、使えるようにするか、そこが実際のチャレンジでした。

このデータベースについて分かりやすく言うと、同じグループ(ancestorでつながっている)にあるものはserializableに処理され、そうでないものはread committedになります。つまり同じグループであれば同期的に処理されるので整合性のないデータを見る事はないが、その分グループが大きくなると全体が同期処理されるので性能が出ないという問題に直面します。

この、整合性を維持しながら性能を出すには、やっぱり大部分はread committedにせざるを得ません。幸い今年の春に、更新の度に増加するversionがサポートされましたので、トランザクションの中で操作を行い、versionを常に確認しながらデータ処理を行えば、上書きを防ぐことができます。

〜ちなみにGoogleのデータストアではデータの取得コストは1。つまり保有するデータ数に依存しません。すごいと思いませんか??(ただし検索のページ送りなどのオフセットはその分のコストもちゃんと計算されますので要注意。代わりにcursorを使うことが可能です)〜

チャレンジは、こうした上書きを含め、以下に示すようないろんなデータの状態を正しく管理することでした。

・サーバーから読み込まれたままの状態
・サーバーから読み込まれてクライアントで更新された状態
・サーバーから読み込まれてクライアントで削除された状態
・以上のケースで、他のユーザーによって既に更新されてしまった状態
・クライアントで追加された状態

結局これらの管理は全てcontextが行うことにしました。容易に想像できるように、これらを確実に管理するには高度なデータバインディングが必要だろうということです。Cappuccinoを採用した一番の理由はここにあります。CappuccinoはObjective-Jですが、Objective-Jはjavascriptのスーパーセットなので、javascriptでもあります。つまり、これはManagedObjectのコードの一部ですが、javascriptとObjective-Jを同時に使うことができます。

- (void)setValue:(id)aValue forKey:(CPString)aKey
{
    // avoid implicit conversion
    if ([self valueForKey:aKey] === aValue)
        return;

    if (_changeAware)
    {
        [[_description managedObjectContext] setChange:self forEntity:[_description name]];

        [self willChangeValueForKey:aKey];
        if (!_modified)
            _modified = {};
        _modified[aKey] = aValue;
        [self didChangeValueForKey:aKey];
    }
    else
    {

        if ([self isNew])
            _original = {};

        // update _original
        _original[aKey] = aValue;
    }
}

Cocoaに詳しい人はお気づきだと思いますが、このコードはKVC/KVO対応であり、javascriptに詳しい人は、javascriptjavascript objectのプロパティ操作をしていることが分かると思います。

Javaに長く携わってきた経験より、JavaかCかと言った議論ではなく、必要に応じてJavaとCの両方というのが現場には必要だと痛感しており、Objective-Jとブラウザのネイティブ言語であるjavascriptを同時に使えるObjective-Jはこうした点で非常に強力であり、これを使う事でチャレンジを克服することができました。


デスクトップ級の複雑なコードをJavascriptでどうやって書くか(かつスピーディーに改善していくか)

さて、これについてはもうすでに話が出ましたが、Cappuccinoという解決策を選びました。もちろんこれ自体もチャレンジであるわけで、もし他に確実な解決策があるならそれを選択しますが、まだそういうものがない以上、解決策自体がチャレンジだったのもやむを得ませんでした。

当初気にしたのは以下の点でした。

  • デバッグできるか
  • パフォーマンスチューニングできるか(プロファイリングできるか)

至極当然な話ですが、javascriptの世界では大変です。ほとんどの関数はanonymousで誰が誰だか分かりません。Cappuccinoではこんな感じでデバッグできます。

ご覧のように実際のコードがほとんどそのまま残されて読めるようになっています。実はこのためにザカードクラウドSafari/ChromeWebKitブラウザに限定しています(Cappuccino自体はInternet ExplorerでもFirefoxでも大丈夫)。

パフォーマンスチューニングも特定の部分に限定してプロファイリングさせることができるので、かなり正確に数字が取れます。ただチューニングが本当に試されるのはもう少し先だと思いますが。



最後に、面白いと思ったらCappuccinoを使ってみてださい。私はCappuccinoのreviewerでissueをチェックしていますので、issueやグループへの質問にはいつも目を通しています。もし英語が不慣れであれば日本語でもどうぞ。