Appengine ja night#6と、エンティティ設計のまとめ

3月19日のAppengine ja night#6に行ってきた。
ネットで見てるだけだったひがさんのお話を生に聞けた。(そして話も少しだけしてきた)


話の内容は、GAE(Google App Engine)上での、グローバルトランザクションマイグレーションの話だった。


GAE上のトランザクションはエンティティグループ単位でのトランザクションしか対応していない。(以降、これをローカルトランザクションと言うことにする)
任意の2つのエンティティ間でローカルトランザクションしたければ、すべてのエンティティが同じエンティティグループに存在しなければならないが、それだとスケールしなくなる。


なもんで、任意の2つのエンティティは、一般的には別エンティティグループにあることを前提とせねばならず、エンティティグループをまたがったトランザクション機構が必要となる。
このエンティティグループをまたがったトランザクションを、ローカルトランザクション等を組み合わせて実現するのが、グローバルトランザクションというわけ。


なんて話は去年の4月頃からしていたので、じゃあSlim3ではどんな風にフレームワークに組み込んだのだろうかというのを聞きに行ってきた。
結果、パフォーマンス面でかなり画期的な実装がなされていたので、すごく参考になった。(私が当時想定していた処理時間の、実に5分の1ぐらいの処理時間で、グローバルトランザクションが可能だという結論になっていたので、ひどく驚いた。というか、私のは机上の空論で未実装だったので、実践を行っているひがさんには逆立ちしても絶対にかなわないわけだけれども)


Google App EngineでGlobal Transaction - ひがやすを blog
http://d.hatena.ne.jp/higayasuo/20100210/1265781747


また、Beer Talkでの、同じくひがさんのマイグレーションの話も、とても考えさせられた。
マイグレーションっていうのは、ソフトウェアのバージョンアップによるスキーマ仕様の変更が生じた場合に、データベースを旧スキーマから新スキーマへとどう移行させるかという問題。
停止可能なサービスのRDBMSとかだと、旧スキーマから新スキーマへと全データの変換処理をかませたりする。また、停止不可能なサービスのRDBMSだと旧スキーマから読んで旧スキーマと新スキーマの両方へ書き込む移行期間を挟んだりする。
しかしAppEngineでは2エンティティに書き込まなきゃいけないとなるとコストがかかり過ぎるので、旧新両方の書き込みは現実的じゃないそうだ(セッション中は気付かなかったけど、同エンティティグループ内の別エンティティとするのはどうなんだろ? アグリゲートパターンを用いて、データエンティティは常にアグリゲートルートにならない様にするとか…)。
また、全データの変換処理も不可能と仮定する。スケール可能であるということは、エンティティが平気で数億件とかの桁に入る可能性があることを示しているので、それら全てのエンティティを書き換えるのは、たとえメンテ時間を取れるとしても現実的ではないのだろう。


じゃあどうするのかっていうと、複数のバージョンのスキーマを混在した状態で使っていく。
どのバージョンのスキーマからでも読み込める様にしておいて、新たに書き込む時は最新スキーマで書き込む。
そのためには、スキーマ内に、そのエンティティがどのスキーマバージョンで書かれているのかを、情報として持つ必要がある。


なお、バージョン1、バージョン2、バージョン3とあるとき、バージョン1→バージョン2とバージョン2→バージョン3の変換プログラムは書くが、バージョン1→バージョン3の変換プログラムは書かない。バージョン1→バージョン2→バージョン3と変換を行えば、問題ない。
(変換プログラムの本数が、バージョンの二乗に比例して増えていくのは、誰しも嫌だ)


と、まあ、ここまではGAEの構造上、誰が考えても同じような結論になる話。(私もまったく同じことを考えていたので)


ただ、誰が考えても同じような結論になるっていう話と、それをフレームワークとして誰もが手を煩わせることなく同じ機能を簡単に実現できるようにしようっていう話はまったく別。
Slim3フレームワークとしてこのマイグレーションの問題をどう実現しようとしているのかには、とても興味があった。


まず、各スキーマ間の変換を書く場所というのは、私は2通りを考えていた。
ローレベルAPIエンティティでの変換を書くのか、マッピング後のオブジェクトでの変換を書くのか、の2通りである。ひがさんは後者を考えていた。


Beer Talkでは、エンティティにスキーマバージョン情報を持つことで、プロパティの追加だけでなくプロパティ名の変更も可能になると話していたが、Beer Talkが終わった後に個人的にお話をしてみた所、インデックスの問題が残るため、インデックスを持つプロパティ名の変更は出来ないのだろうなぁという話になった。
この結論からいくと、プロパティ名の変更はできず、プロパティの追加しかできないことになるので、実際の所はスキーマバージョンを持たなくても良いという結論になりかねない。が、それはそれ、これはこれで、インデックスを犠牲にしてでもプロパティ名を変えたかったり、そもそもプロパティの型をどうしても変更したいということはあるので、スキーマバージョン情報は持った方が良いという考えには賛成する。
また、発表中に質問したのだけれども、2種類のエンティティA・B間でリレーションがあるときに、エンティティAの持っているプロパティXを、エンティティB側に移動する様なスキーマ変更は、難しいだろうという話だった。
まあ会話の最中、私も難しいと思いながら質問したのだけれども、今になってよくよく考えると、同じエンティティグループ内は全て同じスキーマバージョンとして、ルートエンティティからエンティティグループ内の全てのエンティティに辿ることができるという前提下では、同じエンティティグループ内で、エンティティカインド間のプロパティの移動を行うということも不可能ではない気がする。
しかし「出来る」という話と、「出来て嬉しい」という話と、「それをスマートにフレームワークとして提供できるのか」という話は別だ。
フレームワーク作者としての答えとしては、難しい(≒できない)で、おそらく正しい。


−−−−


まとめに入る。
今回のAppengine ja night#6で、AppEngine上でスキーマ設計をする上での重要なポイントは、だいたい、見えた様な気がする。


まずグローバルトランザクションにかかるコストは、そんなに大きくない。
また、グローバルトランザクションを実現するために受けるスキーマの制約も、聞いた感じだと、無いと言って良さそうだった。
なので、わりと健全なスキーマ設計を行って良いということだ。


エンティティグループは、ほとんどのWebアプリケーションだと、1ユーザに対して1エンティティグループの場合が多いと思う。
掲示板アプリだと、1スレ1エンティティグループだろうし、場合によっては1レス1エンティティグループという所まで掘り下げた方が良い場合もある。ブログアプリだと、同じユーザの記事であっても、1記事1エンティティグループとかになる。
キャラを複数作って個別にプレイするゲームとかだと、1キャラクタが1エンティティグループになる(ただし、同時に複数キャラを操作するゲームの場合は、同じエンティティグループにすべきかもしれない。また、倉庫がある場合に同プレイヤーの全キャラを同一エンティティグループにするのも1つの手)かな。道具袋とかはシリアライズとかして、1キャラクタの持ち物全てを1エンティティにしてしまって構わない。そして持ち主のキャラクタと同じエンティティグループに入ったりする。
ソーシャルネットワークサービスの1ユーザは1エンティティグループで、フレンドリストは1エンティティに収めて良いし、こいつにはインデックスを付けなくて良い。「フォローしてるリスト」と「フォローされてるリスト」を別々に持って、グローバルトランザクションで同時に更新してあげれば良い。(常に相互リンクの場合は、相互フォローリストを1つだけ持って、こいつもやっぱりグローバルトランザクションで同時更新すれば良い)
ちなみにツイッターみたいなサービスだと、1発言が1エンティティグループになって、子エンティティとして配信エンティティが付く。配信エンティティはStringListのインデックスが付く…が、1エンティティ1000インデックスの制限があるので、場合によっては配信エンティティを複数個持つことになる。


というか、この辺はグローバルトランザクションが存在しなくても、変わらないものが多い。グローバルトランザクションが低コストで出来るようになって、できることが増えただけだ。(銀行口座や道具袋や倉庫周辺が、グローバルトランザクションのおかげで、かなりスッキリした設計で問題ないことになるのがありがたい)


次、マイグレーションに伴う制約に関してだが、基本的には以下の2点と考えて良いと思う。


・エンティティをまたいでのプロパティの移動はできない
・インデックスの貼られたプロパティのプロパティ名変更はできない


なので、この2点に気をつけて設計を行うと良い。
後の部分は、わりかし変更可能ということだ。
(インデックスに関しては、古いエンティティはインデックスにひっかからなくて良いのなら、旧プロパティ名と新プロパティ名が同居するバージョン期間を設ければ変更可能になる)