がるの健忘録

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

get_recursive_depth() を実装しました

MWでいうところのModel(「アプリケーションデータ、ビジネスルール、ロジック、関数」ってところから、ウチでは実際の処理一式。他のPHPフレームワーク的には「Controller内にある1Action相当」って思うとわかりやすいかと)に、get_recursive_depth()メソッドを実装しました。


すげぇ大雑把に、MagicWeaponも一応「いわゆるMVC」なので。
・controllerでrequestを受け取って
 →振り分けて
 →担当のModelをアサインして
・Modelで処理して(DBアクセスは、ウチの場合、data_clumpって子と、後は生SQL)
・プログラム制御をcontrollerに戻して
・conrollerからviewに処理がうつって
・viewで出力(するのに、基本、modelとかをある程度参照する)
・(なんかあれば)controllerで〆処理
ってな流れで動きます。


で。
基本的には「controller → model → controller → view → controller」って感じで終わるのですが。
modelの部分が、時々ネストします & modelには「初期処理 / 主処理 / 後処理」ってのがあるので(これは他のPHPフレームワークでも大抵ありますな。大抵、forward、って言われているように認識をしております)。
そうすると


・controller
 ・model1の初期処理
 ・model1の主処理
  ・model2の初期処理
  ・model2の主処理
   ・model3の初期処理
   ・model3の主処理
   ・model3の後処理
  ・model2の後処理
 ・model1の後処理
以下略


なんてのが発生しえます。
基本的には、各modelごとに「必要な初期処理と必要な後処理を書けばいい」のですが。
時々「いずれにしてもmodelでやっていただきたい初期処理 / 後処理」ってのがありまして、かつ、それが更に時々「二回以上走るのは大変にあずましくない」なんてぇ処理がございます。
ちなみにMagicWeaponのmodelですが、大体、こーゆー継承関係を持ってます。


[base_model_skeleton]
 [base_model]
  システム共通のmodel
   front共通のmodel
   admin共通のmodel
    個別の処理を実際に書くmodel


角括弧でくくってるのが、MagicWeaponの直接提供しているクラスですな。
で、上述のような「常にやりたい前処理/後処理」は、システム共通とか、front共通とかadmin共通とか、その辺に書く事が多いです。
それを「一回だけ」にしたい時はどうするか? っていうと…ってのを今回実装したのですが。
例えば、こんなコードをお勧めしようと思っています。

  if (0 == $this->get_recursive_depth()) {
    処理を書く
  }


もうちょいクドく丁寧に書いておくと。
初期処理の場合、こんな感じ。

public function initialize() {
  // 親呼んで
  $r = parent::initialize();
  // 「1回だけ動かしたい」処理
  if (0 == $this->get_recursive_depth()) {
    処理を書く
  }
以下略
}


後処理なら、こう。

public function finalize(){
中略
  // 「1回だけ動かしたい」処理
  if (0 == $this->get_recursive_depth()) {
    処理を書く
  }
  // close系なのでこっちの処理が終わってからcallする
  parent::finalize();
}


これを書くと、上述でいう所の「model1の初期処理」とか「model1の後処理」とか「だけ」で動いて、model2とかmodel3の時には「動かない」ようにすることが出来ます。
勿論、例えばこれが


・controller
 ・model2の初期処理
 ・model2の主処理
 ・model2の後処理


って呼ばれ方をされる時は、ちゃんとmodel2のタイミングで、動きます(forwardで呼ばれてないから)。


本当はマニュアルに書けって話なんですが、マニュアルがちょいと停滞しているので、memoを兼ねて。

「URLの擬似静的化」のMagicWeapon用の覚え書き

今度どこか(多分github)にまとめますが。
簡易的に、MagicWeapon用のをmemoっておきます。


mod_rewriteの記述

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-s
RewriteRule ^ index.php [L]


振り分け用のベース(index.phpに記述する)

// c=の動的解決用下ごしらえ
// パラメタ部分を除去する
//var_dump($_SERVER);
@list($path, $dummy) = explode('?', $_SERVER['REQUEST_URI'], 2);
//var_dump($path);

// DocumentRootじゃない場合は、ここで削る
//$path = substr($path, strlen('/削るディレクトリ名'));
//var_dump($path);

// XXX ディレクトリで終わるとファイル名ん所にディレクトリ名が入っちゃうんで、小細工
$dflg = false;
if ('/' !== $path) { // topディレクトリのみ指定、の時は除外
if ('/' === $path[strlen($path) - 1]) {
$dflg = true;
$path = $path . 'dummy_file';
}
}
// ファイル情報を取得
$pathinfo = pathinfo($path);
//var_dump($pathinfo);

// ファイル名ダミーの時の消去
if (true === $dflg) {
$pathinfo['basename'] = '';
$pathinfo['filename'] = '';
}
//var_dump($pathinfo);

// 使うので$_SERVERに突っ込んどく
$_SERVER['EX_PATHINFO'] = $pathinfo;

// 明らかにオカシイのはここで刎ねる!
// XXX これやらないと、エラーが多い画面とか、重い重い orz
if ( ('html' !== @$pathinfo['extension'])&&('' !== $pathinfo['basename']) ) {
if ('index.php' !== $pathinfo['basename']) {
header("HTTP/1.1 404 Not Found");
echo '404 Not Found';
exit;
}
}

// ------------------------------------------
// URLからの、c=(コマンド)の動的な解決
// ------------------------------------------
//var_dump($_GET);
if ('' === (string)@$_GET['c']) {
// 分解
$dirs = explode('/', $pathinfo['dirname'] . '///');
// デフォルトの解決
if ('' === $dirs[1]) {
$dirs[1] = 'index';
}
if ('' === $dirs[2]) {
$dirs[2] = 'index';
}
// コマンド名の自動生成
$_GET['c'] = "{$dirs[1]}_{$dirs[2]}";
}

//var_dump($_GET);

所謂「URLの擬似静的化」についての諸々

うちとこの生徒さん(達)に質問されたのですが、ちょうど「どっかで書かないとなぁ」って思ってたネタでちょうど良いので。


URLの擬似静的化(または、URLの静的化)ってのは…んと…
http://example.com/program.php?param=param
ってなので動く動的なHTMLを、
http://example.com/program/param/
みたいな感じで動かしたい的なほにゃららです。


実際に見るところで…ECサイトだと(最近ECサイトやってるんで、ちょうど思いつくので)。
商品詳細のPageを見るのに
http://example.com/item_detail.php?item_id=0123
ってのを
http://example.com/item/detail/0123/
http://example.com/item/detail/0123.html
なんて感じにしておきたい的なほにゃらら。


先に「なんでンなことしたいの?」っていうと「SEO的に有利だっていう噂がまことしやかに叫かれているから」。
ぶっちゃけそもそも論としておいちゃん「SEO」にほぼ興味がないので*1、割と切り裂くような発言になりますが。
「えす、い〜お〜!!」って言ってる人達は、とても「URLの擬似静的化」を大切にしたり重要視したり珍重したりしてます。
エビデンスがほとんどないか、或いは「古いエビデンス」だけなんですがね(かつ、当然ながら「数値」とか「検証」とかってのを見た事がない)。
一方で「静的化? いらないんぢゃね?」的な話も、正直ちらほらと、googleさん辺りの方角からは。


ただまぁ「じゃぁシステム開発でそれを盛り込むのにどれくらいコストがかかるのか?」っていうと、知っていればそんなに「莫大なコスト」はかからないのと。
まぁ「信じちゃっている」方々の熱い情熱に水指すのもぶっちゃけ面倒なんで(ってここで書いてたら台無しだよねぇってのはおいといて)。
かかる費用をご負担いただけるのであれば「いいんぢゃね?」とか思うので、そんなに大きく反対するものでもございません。


というわけでまぁ「そもそも何でンなもんが欲しいの?」ってあたりがとりあえず片付いたところで、本題に入り込んでみましょう。


端的には
mod_rewriteなどによる「リクエストURL」の切った張った
・適当な「処理する子」をAbstract Factoryパターンその他的な感じで生成してcall
ってな感じの流れになります。
(昔は PATH_INFO なんてのもございましたのですがねぇ…めっきりmod_rewrite一本槍になった風潮が、なにやら寂しい想いが、全く無いのかと問われると、些か疑問を感じる瞬間も、ふと、あったりするものでございます)

リクエストURLの加工

まずは「リクエストURLの加工」から。
http://example.com/program/param/


ってアクセスしても、もしファイルがなければ当然ながら404で終わって終了です…って終わられると些か困りますので、解決します。
mod_rewrite(もしくはmod_rewrite相当の機能) というものがございましてどこぞで「スイス製のアーミーナイフ」とか「かなりイケてるっぽい黒魔術だが、やっぱり所詮は黒魔術である」とか色々と言われているもの*2がございます。
実際問題「切れ味いいし便利なんだけど乱用すると瞬時でヒドイ事になるので使うのはとても慎重に最低限」という…まぁいつもおいちゃんが話をしている「お道具は最低限を使う」って話ですねぇ。
これを使うと「入ってきたURLを加工できる」とかいう大変な優れものでございます。
細かい使い方は説明しないんで適宜ググれ。


まぁこれをつかって「URLを加工して」「404じゃないようにする」んですねぇ。
「アクセスを(大抵の場合)index.phpに集約」させたりするわけですつまり。


先に、各フレームワークのものを見てみましょう。
大体どこも「.htaccess に記載する」前提で書いています。まぁ「.htaccess嫌い*3」ってレベルの御仁なら「読み替えられる程度のスキルはある」と思われるので、その辺は適宜。
バージョンとか面倒なんで記載しませんが、半年〜1年弱くらい古いバージョンのを見てるので(前に調査したのがあるのでそのまま流用)、その辺加味した上でご覧下さりませ。
あと、有効行だけ切り出しているのも注意。<IfModule mod_rewrite.c>を省略しているのも注意。
よくわからない人は一端読み飛ばしてください。ほんのりと解説っぽいものを下に書いてるんで。


symfony 1系

RewriteEngine On
RewriteRule ^$ index.html [QSA]
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]


symfony 2系

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ app.php [QSA,L]


cakephp

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]


fuelPHP

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]


Zend Framework(多分1系)

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]


MagicWeapon(やりたい時の推奨予定の書式)

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-s
RewriteRule ^ index.php [L]


ちなみに、cakePHP

RewriteEngine on
RewriteRule ^$ webroot/ [L]
RewriteRule (.*) webroot/$1 [L]

RewriteEngine on
RewriteRule ^$ app/webroot/ [L]
RewriteRule (.*) app/webroot/$1 [L]

が割と面白いかなぁ、っと。
「共有サーバの事とかを想定している」んだと思うので。


閑話休題


大まか「3つの機能っつか文法」を確認しつつ、状況を理解していきましょう。
まず

RewriteEngine on

これ。
これがないと「ナイフが鞘に入っていて、術士がワンドを持っていない状態」なので、動きませんので必ず書きましょう。


次に出てくるのが「RewriteCond」と「RewriteRule」。


まず「RewriteCond」は簡単に書くと「if文」。
ここに書かれている条件が「falseだったらこの時点で処理終了」となります。
なので「書き換えたくないURL、の時の条件」があるんなら書く感じですねぇ。


「RewriteRule」は書き換えのルール。
「こんな風に書き換えて」ってお願いをします。


ちなみに「httpでのアクセスを一切弾くっていうか転送する」ような場合、こんなのを書いておくと楽っちゃぁ楽です。

RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^.*$ https://%{HTTP_HOST}%{REQUEST_URI} [NC,R,L]

これは
・%{HTTPS} !=on(もしhttpsがonじゃなかったら:ようはhttp://によるアクセスだったら)
・「^.*$」の条件に当てはまるURLを(正規表現で^と$の間が.*なんでようは「全部」)、https://%{HTTP_HOST}%{REQUEST_URI} [NC,R,L] に書き換えて
って感じです。
%{変数名}はまぁ色々なものが入ってくるのですが、興味があったら各自調査。


閑話休題(多いな今回…)


さて。
http://example.com/program/param/
を、今回とりあえず「DocumentRoot直下の index.php」に向けたい、と考えますと。
端的には、こうなります。

RewriteEngine On
RewriteRule ^ index.php [L]

RewriteRuleの[L]は「ここで変換終了!」って意味あいなので、今回はあんまり意味は無いですが、事故防止でよく使われます。
またRewriteRuleですが、正直、「^.*$」も「^(.*)$」も「^」も意味は一緒で「確実になんであろうがマッチする」ので、文字数少ない方が好みなんで雑に書いてます。


さて。これを実際に仕込んでみるとわかるのですが、あっという間に「画像ファイルもCSSJavaScriptファイルもなんにも見れなく」なります。
そりゃそうですなDocumentRootでこれ入れたら「あらゆるURLのアクセス先をindex.phpにする」ので、あらゆるの中には、画像ファイルもCSSファイルも全部、一切合切含まれますから B-p


なので、各フレームワークとも微妙に差異があるのですが、
・REQUEST_FILENAMEがディレクトリでなければ
・REQUEST_FILENAMEがシンボリックリンクでなければ
・REQUEST_FILENAMEがファイルでなければ
って形で「実在しないアクセスなら」って条件を、RewriteCond 使って指定しているわけなのですね。
これによって「画像ファイルとか、あるファイルならわざわざURLの書き換えとかしねぇよ」ってなるわけです。


故に、色々違いますが、大雑把には

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-s
RewriteRule ^ index.php [L]

ってな感じになるわけです。
特にRewriteRuleの、[]の中の細かいこだわりの有無は、有り無しともに「その人達の考え方」が見て取れて面白いですねぇ、っと。


かくして、存在しない
http://example.com/program/param/
が、無事に
http://example.com/index.php
に書き換えられた結果、index.phpが晴れ晴れしくcallされるわけですな。

index.phpでやるべき最低限の処理

さて。
index.phpを叩いていただいたのはいいのですが「実際にはどんなURLで来てるのよ?」ってのがわからないと以降処理がでけないので、どうにかします。


fuelの

RewriteRule ^(.*)$ index.php/$1 [L]

この系の書式のほうが、少なくとも昔は、多かったように記憶しているのですが…大体10年弱くらい前。
最近は

RewriteRule ^(.*)$ index.php [QSA,L]
RewriteRule ^(.*)$ app.php [QSA,L]
RewriteRule ^(.*)$ index.php [QSA,L]
RewriteRule ^.*$ index.php [NC,L]
RewriteRule ^ index.php [L]

ってな感じでいずれも「URLの大本のパス名の情報とは設定もしてくれやがらねぇ」感じなので、自力でなんとかしましょう。


まぁ端的には

$_SERVER['REQUEST_URI']

で「本来のURL」なんざ余裕で把握が可能なので。
あとは自力で切った張った

その後はぶっちゃけ設計次第。


まずは自分の環境で適宜

var_dump($_SERVER['REQUEST_URI']);

とか書いてみて、それに対して自分が「どーゆー処理を書きたいのか」を考えて妄想して設計してから、ですな。


ちなみに、最近の多くの、本当に多くのPHPフレームワーク
http://example.com/コントローラ名/アクション名/
ってなってるので。
そうすると、超絶壮絶雑にソレっぽく書くと

$awk = explode('/', $_SERVER['REQUEST_URI']);
クラス名 = $awk[1];
メソッド名 = $awk[2];
//
$obj = new クラス名();
$obj->メソッド名();

こんな感じの処理になるわけです。
ちなみに上のコードのまま書くと、びっくりするくらいバグるしセキュリティホールあるから「自力で組もうとかすんな」って感じではあるのですが。
超絶大雑把には「こーゆー処理になってる事が多いよ」的なイメージでとらえてくだしゃんせ。


なんか質問とかありましたら、適宜連絡などいただければ。

*1:某社で一時期ご一緒だった、とある女史…ってには若い女性だたのですが、彼女のSEOが、おいちゃん的には今のところ「唯一」許容も共感も出来るSEOでしたねぇ、ってのは懐かしいお話です

*2: http://net-newbie.com/trans/mod_rewrite.html

*3:例:おいちゃん

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

http://d.hatena.ne.jp/gallu/20121112/p1 の続き。

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

の部分で、根本的に勘違い。


ん…まず。
data_clumpには現在、2つのモードがあるざんす。

・キャッシュモード
浅いレイヤーをキャッシュのように用いることからこのネーミングになった。
「最近使ったものはキャッシュしておきたい」系に適している。
デフォルトのモードはこっち。


・コードマスターモード
コードマスタにおいて比較的よく用いられるためにこのネーミングとなった。
原則「すべてのコードをメモリ上における」みたいな状態のものに適している。

で…

Create
・キャッシュモード
すべてのレイヤーに共通して書き込む
・コードマスターモード
もっとも深いレイヤーのみ書き込み、残りのレイヤーに対しては削除を行う

Read:2モード共通
・キャッシュモード
・コードマスターモード
浅いレイヤーから順次探していく
また、もっとも浅いレイヤー意外で見つかったら、見つかったレイヤーより浅いところのすべてに書き込んでおく

Update
・キャッシュモード
すべてのレイヤーに共通して書き込む
・コードマスターモード
もっとも深いレイヤーのみ書き込み、残りのレイヤーに対しては削除を行う


Delete:2モード共通
・キャッシュモード
・コードマスターモード
すべてのレイヤーに対して共通して消しこむ

ん…モード切り分ける意味、そんなにないなぁ。
細かいところまでチューンするとまぁ「CreateやUpdateの時にキャッシュレイヤーに書き込んでおく」と、「CreateやUpdateの時にキャッシュレイヤーの情報を削除、次にReadが発生するタイミングで書き込む」よりも少しコストメリットはあるんだけど、でも誤差レベルな気がする。その辺は、整合性とか安全性とか重視したいし。


で。
モードを全部「コードマスタ」系にすると、当初想定している

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

がすでに実装済みなので、無問題。
よし、モードを切って「コードマスタモード」のみにしませう。


…うんうんしみじみYAGNIだねぇ結局モード使わなかったねぇ(苦笑
っちゅわけで、その方向で実装を進めませう。

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

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


さて。
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

tokenizerともう一つのトークンの考察

MagicWeaponには「tokenizer」という「一意のトークンを発行する」クラスがあります。ちょっとしたDBのPKに死ぬるほど便利なのでよく使います。
一方で、以前に書いたのですが( http://d.hatena.ne.jp/gallu/20120402/p2 )、違うトークンの作り方もあります。


で。おいちゃんは「使い分けてる」ので、その辺の話をもやもやと。
「ぢぢぃの世間話を聞いてる」くらいの生ぬるいスタンスで読んでくださいw


大まかに結論先に書いちゃうと。
tokenizerは「一意であることを保証したい」ケースで使って、もう一個のトークンは「推測困難性がほしい」時に使います。


ん…まず、うちのtokenizerの仕様から。
前提として「62進数」ってのを使ってます。
62進数表記にすると、0-9とa-zとA-Zで表現できるので、ま〜ま〜便利です。素直にbase64でもよかったのですが、できたら英数だけで表現したかったので作りました。


んで。
tokenizerは、デフォルトで
・現在のエポックタイム(実際には、数値がでかいんで、固定の数値を減算してます)
・現在のマイクロ秒
・プロセスID
・乱数
の4つの数字を、デフォルトだと「−(ハイフン)」でつなげてます。
オプションで「自分のIPアドレス」をここに足せるのと、CとJavaで組んでた時には「スレッドID」も足してました。
PHPにはスレッドないんで、いったん省いてます。


これをやると。乱数以外の部分でかぶるのが「同じマシンの同じプロセスID上で、同じ秒+同じマイクロ秒で、処理が2回以上走る」場合で、これほぼほぼありえないので。
ゆえに「一意である」ことを大変簡単かつ確実に取得できるんで、好んで使ってます。
で…実装当初、とはいえ数字を普通に連結すると「すげぇ長い文字列」になったので、62進数作りましたw
現在、だいたい20〜30文字以内くらいで収まります。


んで。
このtokenが「推測困難か?」って問われると、個人的には「可能ぢゃね?」って思ってます。
乱数もさほど気にした関数使ってませんし(PHPの場合mt_randつかってまふ)、エポック秒は容易。マイクロ秒もある程度あたりはつくでしょうし、プロセスIDだって、案外と。
なので、tokenizerは「一意であることを保証したいとき」に使います。


一方で。
セッションIDとか、今度書くけどCSRFの割符なんかに使いたいのは「推測困難な文字列」なので。
そうすると、以前も書いたのですが

$token = hash('sha512', file_get_contents('/dev/urandom', false, NULL, 0, 128), false);

とすると…
・urandなので、たぶんきっと、ある程度質の良い乱数であると期待できる(はずw)
・sha-512なので、結構空間広いので、そう簡単に「偶然当たる」ってのもあるまいて
というあたりの発想から「推測困難なIDがほしい」時に重宝します。


一方で。いやまぁ「ねぇだろ」とは思うのですが、一応、可能性としては「いつかどこかで、以前発行したIDと重複する」可能性が0ではないので。
基本的には「一意のIDがほしい」用途では、あんまり使わないです。


時々聞かれるので、説明を省略する意味合いを込めて、めもw


PS
そういえば…「推測困難」なほう、MagicWeaponに組み込みたいのですが…名前どしよ?
UUIDっぽい使い方をするのですが…一応「UUIDは16バイトの数値」って決まりがあるしなぁ…「似非UUID」とかって名前にしようかしらん?w

おまけ:もしMagicWeaponでメッセージキューを実装するとしたら

すげぇアバウトなインタフェース草案。


送信

  // オブジェクトを作成
  $msq = new mw_message_queue();

  // key設定のやり方への可能性
  $msq->set_key($key)
  $msq->set_key($config)
  // クラス継承してコンストラクタで設定

  // メッセージを設定。stringでもオブジェクトでも、かなぁ
  $msq->set_message($message);
  
  // 省略可能
  $msq->set_message_type(1);
  // 実際に使うんなら、継承して、以下のような感じのほうが好みだなぁ
  $msq->set_message_type_hogera();
  $msq->set_message_type_mugugu();

  // メッセージをぶち込み
  $r = $msq->enqueue();

  if (false === $r) {
    print $msq->get_error_code();
  }


受信

  // オブジェクトを作成
  $msq = new mw_message_queue();

  // key設定のやり方への可能性
  $msq->set_key($key)
  $msq->set_key($config)
  // クラス継承してコンストラクタで設定

  // 省略可能
  $msq->set_message_type(1);
  // 実際に使うんなら、継承して、以下のような感じのほうが好みだなぁ
  $msq->set_message_type_hogera();
  $msq->set_message_type_mugugu();

  // 非同期にするなら設定
  $msq->set_nowait();

  // メッセージをげと
  $r = $msq->dequeue();

  if (false === $r) {
    print $msq->get_error_code();
    return ;
  }
  // else
  $message = $msq->get_message();