gallu’s blog

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

トランザクション周りの覚書

ものっそ覚書。
というか考えながら書いているに近い状態なので、もしかしたら文頭と文末で矛盾してるかもな場合は文末を信用してちゃぷたい。


さて。
MagicWeaponのデータ系ハンドルは、現状、こんな感じ。


data_handle
├db_handle_base
|├db_handle_plural(内部でdb_handle)
|└db_handle

└kvs_handle
 └memcache_handle

実際にはもうチョイあるんだけどね。今回にかかわるのを中心に書いてみる。


data_handleは「ありとあらゆる、データを扱うシステムのハンドル」を意味する、一番粒度の荒いクラス。
すべての「データを扱うサブシステム」のためのクラスは、この子を継承するのが条件。


db_handle_baseは、実際には「rdb_handle_base」って書いたほうが正しいんだよね。リレーショナルデータベース用のハンドルの基底クラス。なんで基底なのかは、もうチョイしたら説明する。
ちなみに同レベルにあるkvs_handleは「KVSを扱うためのデータハンドル」用クラス。この下にmemcache_handleとかがあるんだね。memcache_handleは、memcachedという「実装そのもの」を扱うクラス。粒度としては一番細かいところで、ここにいろいろ実装が書いてある。


db_handle_baseの直系のクラスはdb_handle。っつか歴史的経緯で話をすると「もともとdata_handle → db_handleって継承図だったんだけど、db_handle_pluralを作る必要が出てきたから、共通部分を持ち上げたのがdb_handle_base」って感じだ。
なので、db_handleはまぁ予想がつくとおり「DB用のハンドル」。この子単体だと、PDOとほぼ同系列。


で、db_handle_plural。
これは「マスター&スレイブの構成」と「テーブル単位のシャーディング(横分割なんて言い方もしますなぁ)」をどうしてもフレームワーク側で解決せざるを得ないシーンがあって、それ用に作ったクラス。
db_handle_pluralは複数の「db_handle インスタンス」を腹に抱えるような実装になってる。
大まかに「このテーブルのwrite用」とか「このテーブルのread用」とかってんでDBハンドルを使い分ける感じ。


んで。
ここ最近…でもないんだけど、いい加減トランザクション対応をいろいろやりたくて、ってのが、今回の改修の目的のひとつでもあり、眼目でもある。


とりあえずざっくりと、トランザクション周りの実装をdb_handleに持ったんだけど。
実際にBEGINを発行するあたりはここに持つべきなんだけど、たとえば「is_tran」みたいな実装は、本質的にはdata_handleに持つべきだったんだよね。
実際にトランザクションが可能かどうかってのはおいといてさ。


なので、とりあえず「トランザクション状態の保持とチェック」周りは持ち上げよう。
と、ここまではOK。


若干気をつけたいのはdb_handle_pluralに対するトランザクションの開始周りとか。具体的にはbegin()メソッド。
メソッドの引数にテーブル名を受け取って、db_handle_pluralは「担当するdb_handleにBEGINのSQLを流しつつ」「トランザクション状態をonにする」ってのをやらないとねぇ。
これもToDo。


問題は、実際にdata_handle群を使う、data_clump側。
具体的に問題なのは。data_clumpは「複数のdata_handle(を継承した実装)インスタンスを持っている」ってのがポイント。
ちなみに「複数のdata_handle(を継承した実装)インスタンスを持っている」理由は、端的には「RDBのキャッシュとしてmemcachedをもってる」とかってのを簡単に実装するため。
上述の場合「深度1にmemcache_handle、深度2にdb_handle」を持っているざんす。
動きとしては
・insert/update/deleteの場合、深度1と深度2にそれぞれinsert/update/delete
・selectの場合、深度1に存在していればそのデータを、存在していなければ深度2のデータを用いる
ってな感じ。
この辺が、特に最近「MagicWeaponとmemcachedとの相性がいいなぁ」と思う所以。


閑話休題


とりあえず限定条件。
まず「KVSのみ」の場合、そもbeginは使わないと思うから、いったん脳みそからはずす。
なので、想定したいのは
RDBのみ
・KVS→RDB
の2パターン。より厳密には
RDBのみ(mono)
RDBのみ(plural)
・KVS→RDB(mono)
・KVS→RDB(plural)
の4パターン。


理想とする挙動としては
トランザクション中の更新系は、KVSのキャッシュを「削除する」という動きをする
感じ。
そうすると、トランザクションコミットしたときに「キャッシュのデータが削除されてRDBにのみ情報が残る」から、変なキャッシュエラーとか起こさずにすむだろう、って魂胆。


問題。
厳密には、db_handle_pluralにおいては「無関係なテーブルのトランザクション」でトランザクションonになる可能性があるんだけど。
ん…シャーディングしてて、サーバAに「テーブルあ、い、う」、サーバBに「テーブルえ、お」があると仮定。
テーブルえに対するトランは、厳密には「サーバA」には無関係。
でもまぁ面倒なんでその辺は一緒くたにさせてもらおういったん。
…なんとなく、このシャーディング機能、最近のMySQL Spiderエンジン&MySQL Clusterを見てるとそろそろ「いらねぇかなぁ」とか思う瞬間もあるし。あんまりここに血道をあげすぎない。


そうするととりあえず。実際のSQL発行場所はともかくとして
・基底であるdata_handleでトランザクション状態を持つ
・db_handle_plural::begin(テーブル名)は、内部の担当db_handleにSQLのBEGINを発行していただきつつ、自分自身に対してtran_on()
・db_handle::begin()は、SQLのBEGINを発行しつつ、自分自身に対してtran_on()
ってやると、とりあえずなんとかなる気がする…ここまでは。


後はdata_clumpで「異なる深度のdata_handleたちに情報を渡す」方法だな。
いろいろと面倒が多いなぁ…いったん割り切るか。後でまた改修したほうがいい気がしてるし。
もちろん実装的には変な深度とかもてるけど、実際問題として現状
・KVS
・KVS→RDB
RDB
の3パターンしか許容していないので。こんど、こここれで絞ろう。これ以上深い、3深度以上とかいらないっしょ。


そうすると、以下のロジックが可能なはず。
・深度が1つの場合、そのまま処理する
・深度が2つある場合、深度2のトランザクション状態を深度1のハンドルに反映する


これをやれば、後は単純にmemcache_handleのupdateとかinsertとかそっち系の実装で「トランザクションonなら実際のinsertとかupdateとかの代わりにdeleteを走らせる」って処理でいけると思う。


ふむ…たぶんうまくいく、かなぁ。
突込みが入ったら、理論構築をしなおして実装もやりなおそうw