関西ソーシャルゲーム勉強会でVPCとChefに関する発表をしてきました
第5回 関西ソーシャルゲーム勉強会でAmazon VPCとChefに関する発表をしてきました。発表資料は以下のリンクからどうぞ。
ガチャみたいに設定した確率で抽選を行うPHPライブラリを作った
くじ引きやガチャガチャのように、あるものはよく出る、あるものはあまり出ない、というように確率に差がある上で一つ抽出する、というプログラムをたまに書くことがあるので、その部分だけを行うPHPライブラリを作った。
インストール
Composerを使う場合は、composer.jsonのrequireに
"cloned/luckybox": "$VERSION"
を追加。$VERSIONに入れられるバージョンはPackagistを参照。今のところは 0.9.* としておくと良いでしょう。
Composer推奨だけど、Composerを使わない場合はGitHubからソースコードをダウンロードしてrequire_onceしてもOK。
使い方
コインは60%、キノコは35%、スターは5%、という設定で1回くじを引いてみる例はこんな感じ。
<?php use LuckyBox\LuckyBox; use LuckyBox\Card\IdCard; // Items $items = array( 1 => array('name' => 'Coin', 'rate' => 60), // 60% 2 => array('name' => 'Mushroom', 'rate' => 35), // 35% 3 => array('name' => 'Star', 'rate' => 5), // 5% ); // Setup $luckyBox = new LuckyBox(); foreach ($items as $id => $item) { $card = new IdCard(); $card->setId($id) ->setRate($item['rate']); $luckyBox->add($card); } // Draw $card = $luckyBox->draw(); $item = $items[$card->getId()]; echo "You got {$item['name']}" . PHP_EOL;
LuckyBoxに確率(rate)を設定したCardを詰めて、LuckyBox#draw()するとその確率に応じた割合で一つ取得できる。
バンドルされているIdCardクラスは、rate以外にidのみ保持できるクラスで、より複雑なCardを作りたい場合は、Cardインタフェースを実装したものであれば自作しても利用可能。
デフォルトでは無限に LuckyBox#draw() できるけれど、LuckyBox#setConsumable(true) するとadd()したCardの分しか引けなくなる。こんな感じで利用できる。
<?php $luckyBox = new LuckyBox(); // Add some cards. $luckyBox->setConsumable(true); while (!$luckyBox->isEmpty()) { $card = $luckyBox->draw(); // Do something. }
0.5% みたいなより精度の高い確率を設定したい場合は、より大きな数をrateに設定すると可能。
<?php $card1 = new IdCard(); $card2 = new IdCard(); $card1->setRate(1023); // 10.23% $card2->setRate(8977); // 89.77%
ここでは説明しやすいように合計を100とか10000にしているけれど、実際には全てのrateの合計に対する比率になっているので、「1:1:3」みたいな指定も可能。「1:1:3」とすると「20%:20%:60%」という確率になる。
同じような要件があって、まだコーディングしていなければ、是非使ってみてください。
ウェブサイトで英数字のIDを表示するときに最適なフォント
注文IDや招待IDなど英数字の羅列を表示することがあるとして、ユーザーがその文字列をコピペできない(もしくはコピペが面倒、難しい)場合、各文字を間違わずに識別できる必要がある。具体的にはゼロとオーなどの似ている文字を勘違いせずに済むようにしたい。
一般的にウェブサイトで良く利用されるフォントであるArial、Verdanaでゼロとオーを表示すると下記のようになる。次に勘違いしやすそうなイチとエルも書いてある。
左から、ゼロ オー イチ エル。
こうして見ると細長い方がゼロだなと区別は付く。とはいえ、ゼロにスラッシュがあればより勘違いする可能性が低いだろうから、ゼロにスラッシュのあるフォントを探してみた。
OS X Mountain Lion:フォントリストより、Macにインストールされているフォント(ただし英語フォントのみ)でゼロにスラッシュのあるものを探してみた。こんなにたくさんのフォントがインストールされているのに、ゼロにスラッシュが入っているフォントは下記のみだった。
今度はList of Microsoft Windows fontsからWindowsにあるフォントでゼロにスラッシュのあるものを探してみた。
え、Consolasだけ?あんなにもフォントがあるのに。
結論
font-family: "Monaco", "Consolas";
※MacをMonacoにしたのはイチとエルの区別も分りやすいというのがありますが単に好みです。Consolasはイチとエルが似ているのが微妙ですね。Linuxは手元にGUIが動く環境を持っていないので検証していません。また、Webフォントはブラウザが対応していない可能性があるので検証していません。
PHPカンファレンス関西2013のLTでPHPUnitに関する発表をしてきました
PHPカンファレンス関西2013のLT(ライトニングトーク)セッションでPHPUnitを使ってCoverage 100%を目指すためのちょっとしたTipsについて発表してきました。
はてダにSlideShareをうまく貼付ける方法が判らないので、発表資料は以下のリンクからどうぞ。
「ヒッグス粒子の謎」を読んだ
- 作者: 浅井祥仁
- 出版社/メーカー: 祥伝社
- 発売日: 2012/09/03
- メディア: 新書
- 購入: 23人 クリック: 258回
- この商品を含むブログ (11件) を見る
もしかしたら平易な説明にするために平易な表現になりすぎているため、本来理解に必要な難しい理屈が記載されていないのが原因かもしれない。
とはいえ、「真空」「反粒子」「場」といったテーマについて今までよりも知ることができたし、何よりヒッグス粒子が質量の起源として考えられているという基本的なことも本書で初めて知ることできたのは良かった。
Symfony2+Doctrine2.3でSharding(水平分割)を実現する
Symfony2 + Doctrine2.3の環境でデータベースのSharding(水平分割)を行う際の実装方法など。ここで言うShardingは、例えば10台データベースを利用するとしてユーザーIDなどを基準に利用するデータベースを各10台のどれかに振り分けるような場合(参考: 分割 (データベース) - Wikipedia)
確認環境
下記手順でSymfonyを展開。DocumentRootがSymfony/web。% wget "http://symfony.com/download?v=Symfony_Standard_Vendors_2.1.1.tgz" . % tar zxvf Symfony_Standard_Vendors_2.1.1.tgz % chmod 777 Symfony/app/cache Symfony/app/logs
データベースはMySQL 5.5を利用。mysqld_multiを使ってlocalhostに3インスタンス起動(ポートは3306、3307、3308)。
3つのインスタンス全てに下記SQLを実行しておく。今回はuserテーブルをそれぞれのデータベースで振り分けるという例。
CREATE DATABASE `sharding` DEFAULT CHARSET=utf8; GRANT ALL PRIVILEGES ON `sharding`.* TO 'sharding'@'127.0.0.1' IDENTIFIED BY 'sharding' USE sharding; CREATE TABLE `user` ( `id` int(11) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Bundle作成手順を省略するために確認はAcmeDemoBundleを変更して行う。下記URLにアクセスしているイメージ。
http://localhost/app_dev.php/demo/
自前でConnectionを切り替える
app/config/config.ymlに下記のようなDoctrineの設定を行う(内容をわかりやすくするためにparameters.ymlの値を使ってません)
doctrine: dbal: connections: db1: driver: pdo_mysql host: 127.0.0.1 port: 3306 dbname: sharding user: sharding password: sharding charset: UTF8 db2: driver: pdo_mysql host: 127.0.0.1 port: 3307 dbname: sharding user: sharding password: sharding charset: UTF8 db3: driver: pdo_mysql host: 127.0.0.1 port: 3308 dbname: sharding user: sharding password: sharding charset: UTF8
同じホストだけどそれぞれポートが3306、3307、3308と分かれて設定されている。この状態でコントローラに下記のようなコードを書くと各MySQLにそれぞれデータが挿入される。
<?php class DemoController extends Controller { public function indexAction() { $conn = $this->get('doctrine')->getConnection('db1'); $conn->insert('user', array('id' => 1)); $conn = $this->get('doctrine')->getConnection('db2'); $conn->insert('user', array('id' => 2)); $conn = $this->get('doctrine')->getConnection('db3'); $conn->insert('user', array('id' => 3));
3306ポートにはuserテーブルにid:1のデータ。3307にはid:2、3308にはid:3。これで一番基本的なコネクションの切り替えは出来るのだけど、当然のことながらDoctrineを使っているのにこのようなコードは書きたくない。
EntityManagerで切り替える
Symfonyのドキュメントにあるようなデータの取得方法をしようとする場合は素のConnectionを扱わずにEntityManagerを使いたい。下記コマンドでEntityクラスを作成する(src/Acme/DemoBundle/Entity/User.phpが作られる)。
% php app/console doctrine:mapping:import AcmeDemoBundle annotation % php app/console doctrine:generate:entities AcmeDemoBundle
※今回はプライマリキーに値を自分で設定するので、上記コマンドでUser.phpを生成したあとにidに対してのsetterを追加している(idがstrategy="IDENTITY"なので上記コマンドだけだとidに対するsetterが生成されない)。
<?php use Acme\DemoBundle\Entity\User; class DemoController extends Controller { public function indexAction() { $user = new User(); $user->setId(1); $em = $this->get('doctrine')->getEntityManager(); $em->persist($user); $em->flush();
ドキュメントにあるように普通にこのようにすると3306ポートのMySQLにデータを保存してしまう。そこでapp/config/config.ymlに下記のようなORMの設定を追加する。
doctrine: dbal: ~上に同じなので省略~ orm: auto_generate_proxy_classes: %kernel.debug% entity_managers: db1: connection: db1 mappings: AcmeDemoBundle: ~ db2: connection: db2 mappings: AcmeDemoBundle: ~ db3: connection: db3 mappings: AcmeDemoBundle: ~
PHP側のコードはこんな感じ。
<?php use Acme\DemoBundle\Entity\User; class DemoController extends Controller { public function indexAction() { $user = new User(); $user->setId(5); $em = $this->get('doctrine')->getEntityManager('db2'); $em->persist($user); $em->flush();
これで3307にid:5が挿入される。データを取得する場合も同じようにEntityManagerを指定して取得すれば該当のMySQLに接続してデータを取得できる。
<?php use Acme\DemoBundle\Entity\User; class DemoController extends Controller { public function indexAction() { $em = $this->get('doctrine')->getEntityManager('db2'); $repos = $em->getRepository('AcmeDemoBundle:User'); $user = $repos->find(5);
ShardManagerを使う
ここまでの情報をうまく使って実装してもSharding自体は可能だけど、Shardingのためのオリジナリティあふれる自前コードを書かなければならないし、Doctrineのコード全体に渡ってコネクション名を管理するのは避けたい。そこで、Doctrine2.3で提供されているShardManagerインタフェースを使ってShardingを実現するというのがこのエントリの本旨。ここまでに書いたデータベースの切り替え方法は他でもよく書かれていることなのだけど、土台から書かないとよくわからない感じになりそうだったので書いてみた。
13. Sharding — Doctrine DBAL 2.1.0 documentation
このページは英語だけどDoctrineに限らないShardingをする上での検討事項がわかりやすく書いてあるので原文を参照するのがおすすめ。主にShardingすることによる制約、Sharding対象の全データベースにまたいで一意になるプライマリキーをどのように生成するか、ShardManagerインタフェースの使い方が書かれている。
データベースにまたがって一意になるプライマリキーの生成
13.1.2. Table Generatorの方法をここではやってみる。IDをインクリメントするだけの管理テーブルを作成して採番する方法。Single point of failureなどのこの方法に起因する欠点も原文に書かれているので参照のこと。下記のようにテーブルを作成する。ここでは3306ポートのMySQLに下記SQLを実行(Doctrine\DBAL\Id\TableGeneratorのdocコメントにこのテーブル定義が書いてある)。
CREATE TABLE `sequences` ( `sequence_name` varchar(255) NOT NULL, `sequence_value` int(11) NOT NULL DEFAULT '1', `sequence_increment_by` int(11) NOT NULL DEFAULT '1', PRIMARY KEY (`sequence_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
あとはPHPでTableGeneratorを利用すればすぐに採番を開始できる。
<?php use Doctrine\DBAL\Id\TableGenerator; class DemoController extends Controller { public function indexAction() { $conn = $this->get('doctrine')->getConnection('db1'); $tableGenerator = new TableGenerator($conn, 'sequences'); $id = $tableGenerator->nextValue('user');
sequencesテーブルは下記のようになる。nextValueする度にsequence_valueがsequence_increment_by分だけインクリメントする。
mysql> select * from sequences; +---------------+----------------+-----------------------+ | sequence_name | sequence_value | sequence_increment_by | +---------------+----------------+-----------------------+ | user | 1 | 1 | +---------------+----------------+-----------------------+ 1 row in set (0.00 sec)
ShardManagerを使ってShardingする
プライマリキーの生成ができるようになったので、いよいよShardManagerを使ってShardingをしてみる。ShardManagerはインタフェースなので今回はDoctrine\DBAL\Sharding\PoolingShardManagerクラスを使って実装する。実装結果を先に見た方がわかりやすいのでまずはPHPから。<?php use Doctrine\DBAL\Id\TableGenerator; use Doctrine\DBAL\Sharding\PoolingShardManager; use Acme\DemoBundle\Entity\User; class DemoController extends Controller { public function indexAction() { $conn = $this->get('doctrine')->getConnection(); $shardManager = new PoolingShardManager($conn); // globalとして設定しているデフォルトの接続先に接続 $shardManager->selectGlobal(); $tableGenerator = new TableGenerator($conn, 'sequences'); $id = $tableGenerator->nextValue('user'); // $idを基準にして接続すべきデータベースへ接続 $shardManager->selectShard($id); $user = new User(); $user->setId($id); $em = $this->get('doctrine')->getEntityManager(); $em->persist($user); $em->flush();
大体こんな感じになる。selectGlobal、もしくはselectShardするとその後のクエリは全て同じデータベースに流される。つまり、適切にselectShardさえできれば個々のクエリの接続先は意識しなくても大丈夫。データベースをまたがってのトランザクションは制約として使えないので、1トランザクションで1shardということを意識してShardManagerを扱う。
selectGlobalはデフォルトとして設定されている接続先(後述)に接続する。selectShardは引数を基準にしてSharding先に接続する。
データを取得するときも同じで、事前にselectShardさえしていれば特定のEntityManagerを呼び出したりしなくても大丈夫。
<?php use Doctrine\DBAL\Id\TableGenerator; use Doctrine\DBAL\Sharding\PoolingShardManager; use Acme\DemoBundle\Entity\User; class DemoController extends Controller { public function indexAction() { $conn = $this->get('doctrine')->getConnection(); $shardManager = new PoolingShardManager($conn); $shardManager->selectShard($id); // $idはリクエストなどから取得した仮定 $em = $this->get('doctrine')->getEntityManager(); $repos = $em->getRepository('AcmeDemoBundle:User'); $user = $repos->find($id);
ShardManager#selectShardしたときの接続先を決定する
selectShardに渡される基準値を使って接続先を決める必要がある。これはDoctrine\DBAL\Sharding\ShardChoser\ShardChoserインタフェースを実装したクラスを作成して行う。selectShardしたときに内部ではShardChoserが呼び出される。<?php namespace Acme\DemoBundle; use Doctrine\DBAL\Sharding\PoolingShardConnection; use Doctrine\DBAL\Sharding\ShardChoser\ShardChoser; class SimpleShardChoser implements ShardChoser { public function pickShard($distributionValue, PoolingShardConnection $conn) { return ($distributionValue % 2) + 1; } }
ここではあくまでも例なので偶数だったら1、奇数だったら2を返す単純なもの。このpickShardの戻り値が各shardのid(後述)になる。つまりselectShardの引数を使ってshardのidを返すように実装する。
ShardManagerが動作するように設定を行う
ここが難所で、今のところ正攻法な解決ができていない(というかわからない)。普通にapp/config/config.ymlに設定しようとするとShardManagerの設定をDoctrine\Bundle\DoctrineBundle\DependencyInjection\Configurationが想定していないので、設定エラーとみなされてしまう。この辺はおいおい対応されるのかなという感じ。今のところは13.8. Generic SQL Sharding Supportにあるように直接PHPコードで設定してしまうしかないのかもしれない。よろしくないけど、一応こういうことをすると設定が書けるようになるっちゃなる(真似しないようにしましょう)。
--- Configuration.php 2012-09-11 08:24:24.000000000 +0000 +++ vendor/doctrine/doctrine-bundle/Doctrine/Bundle/DoctrineBundle/DependencyInjection/Configuration.php 2012-09-19 14:25:52.615927030 +0000 @@ -139,6 +139,17 @@ ->booleanNode('profiling')->defaultValue($this->debug)->end() ->scalarNode('driver_class')->end() ->scalarNode('wrapper_class')->end() + ->scalarNode('shardChoser')->end() + ->arrayNode('global') + ->useAttributeAsKey('key') + ->prototype('scalar')->end() + ->end() + ->arrayNode('shards') + ->prototype('array') + ->useAttributeAsKey('key') + ->prototype('scalar')->end() + ->end() + ->end() ->booleanNode('keep_slave')->end() ->arrayNode('options') ->useAttributeAsKey('key')
この変更を行った上でのapp/config/config.ymlはこんな感じ。
doctrine: dbal: connections: default: wrapper_class: Doctrine\DBAL\Sharding\PoolingShardConnection shardChoser: Acme\DemoBundle\SimpleShardChoser global: driver: pdo_mysql host: 127.0.0.1 port: 3306 dbname: sharding user: sharding password: sharding charset: UTF8 shards: - id: 1 driver: pdo_mysql host: 127.0.0.1 port: 3307 dbname: sharding user: sharding password: sharding charset: UTF8 - id: 2 driver: pdo_mysql host: 127.0.0.1 port: 3308 dbname: sharding user: sharding password: sharding charset: UTF8 orm: auto_generate_proxy_classes: %kernel.debug% auto_mapping: true
PHPで設定するにしてもYamlで設定するにしても、ポイントはwrapper_classでConnectionクラスをラッパークラスに切り替えること。上記設定はあくまでもDoctrine\DBAL\Sharding\PoolingShardConnectionに準じた設定になっていて、この辺もおいおい変更されていく可能性がありそうだなと思う。
横浜まで来たら是非立ち寄りたいビアカフェHOPMAN
神奈川県の茅ヶ崎にあるBeer Cafe HOPMAN。東京からだと少し遠いのだけど、横浜や小田原からだとそれぞれ30分で行ける。茅ヶ崎駅からは歩いて5分。
ハンドポンプ@HOPMAN posted by (C)cloned
日本の地ビールや世界のビールを樽生で30種類(ハンドポンプ2本)用意しております
Beer Cafe HOPMAN
30種類の樽生というと都内のビアバー激戦区でも指折りの数だと思う。【国内ビアバー】樽生ビール取扱い数ランキング【1位〜12位編】によるとHOPMANは堂々4位ということらしい。
志賀高原ビール・AFPA@HOPMAN posted by (C)cloned
ビールの種類が多いというだけでもメリットだけど、特に注目したいのはお料理。「安くて旨い」というのはこういうお店を言うものだなと思った。
しいたけの炭火焼@HOPMAN posted by (C)cloned
運ばれて来た瞬間にしいたけの香りが広がり、口にすると炭火の香りと味がぐっとくるのに300円。
ヘルシー野菜グラタン@HOPMAN posted by (C)cloned
大口な野菜がたっぷり入った熱々のグラタンが480円(写真はSサイズ)。
きゅうりの天ぷら@HOPMAN posted by (C)cloned
甘みたっぷりのキュウリがカラっと揚げたてで380円。
食べログを見ると、星が3.52というのも凄いけど、「夜(平均)¥3,000〜¥3,999」というのも地味に凄いと思う。他のビアバーだと5,000円を超えることが多いと思う。
柔らかくなるまで何時間も炒めた玉ねぎが美味しいカレーライスも530円と、とてもビアカフェという値付けではない。旬の野菜を仕入れてくれているのでつい野菜ばかり注文してしまったけれど、もちろんチキン&チップスや手羽先などのお肉なメニューもある。
HOPMANのカレーライス@HOPMAN posted by (C)cloned
お店は照明が明るくてスペースが(席の間隔も)広い。また整理整頓・清掃がきっちりされていてきれいだった。化粧室前は雰囲気が違ってこんな感じでお洒落。
化粧室前@HOPMAN posted by (C)cloned
オーナーは「気軽に来れる店にしたい」ということを言っていたけど、この価格設定でこの種類のビールでこのお店の雰囲気ですからすでに実現しているじゃないですかと感じた。もちろん今後も引き続き期待しています(オーナー写真の掲載も快くOKしてくれた)。
オーナー@HOPMAN posted by (C)cloned
僕は独身だったころ結構一人で下北沢のビアバーうしとらに通っていたけど、うしとらに通えたのは一人でも行けるくらい雰囲気が良くてお客さんがフレンドリーで、そして何よりスタッフがお客さんの会話を聞き漏らさずにちゃんと話をつないでくれるお店だったからだと思う。HOPMANにもその良さがきっちりあって、オーナーを含めスタッフの方と会話を楽しむために来ているお客さんも多そうな雰囲気だった。
お隣さんのお誕生日サイズ@HOPMAN posted by (C)cloned
お店に伺った昨日はちょうどオーナーの誕生日だったのだけど、なんとお隣のカウンターに座った方も誕生日ということで、お誕生日サイズのビールを召し上がっていた。おめでとうございました。
1年半前にHOPMANに行ったときは、どちらかというと無事オープンおめでとうということで書いたけど、オープンからもうすぐ2年を迎えるに当たってきっちり理想のお店を目指して経営されているなと心から感じた。