2012-01-23
■○○した順に保つデータ構造
http://www.amazon.co.jp/dp/475981339X を読みつつ、もしランキング = 確率的ランキングとみなし、注文した順をランキングとして採用し、実現するとしたら、どうやって実現するだろうか、というのを考えてみた。 (実際のところ、かなりの更新頻度がないといけないであろうことや、spamなどのデメリットもあるので、現実的に使えそうなポイントはかなり考えないといけないのだろうけど。
確率的ランキング
本の中で説明されている数学的なバックグラウンドについてはちゃんとなぞっているわけでもないし、まったくしっかり理解できているわけではないです。
本のあらすじを乱暴にまとめると、Amazonのランキングを観察してみると、中間層の順位の推移を長期にわたって観察すると、大きく変動しているのが観察されて、そのデータから全体を統計的に推測すると、Amazonのランキングは、注文された商品の順番に近似している、と説明している。
ある瞬間の注文された商品の順番を切りとると、上位の商品は注文頻度は圧倒的に多いのでこのような仮定が成立する、らしい。
やりたいこと
商品数がとてもたくさんあるような場合に、注文した商品の順番をランキングとしてなるべくリアルタイムに反映したい
各々の商品のページには、現在その商品が何位なのか?といった情報も表示されているので、順位が下位である場合に時間のかかるような状態だと困る
私が考えたこと
まず考えたのは順番を保ちつづけないといけないので、更新のコストを下げることについて考えました。
1つ順位が入れかわるごとに、それ以降の順位情報をすべて更新しないといけないようだと、更新速度や、商品数がどんどんあがるごとに破綻していってしまう。
中間データの更新コストが低いデータ構造といえば何かな、と考えて思いついたのは、LinkedListでした。
単にLinkedListにすると、各々の順位を求めるのが、ひとつひとつのノードをたどっていかないといけないので、下位のアイテムの順位を求めるほど遅い、という欠点があります。
そこで、2の指数の順位のランキングはどのアイテムという辞書をつくってやり、ランキングを求める際は、一番近い2の指数の順位へ飛んでからポインタをたどる、という構造にしてみました。例えば、2位、4位、8位、16位、32位、64位、128位などは辞書を一回引くだけでどの商品か判別することができます。順位が下位であるほどポインタへ飛んでから辿る回数が多くなってしまう傾向にありますが、一からたどるよりはマシでしょう。(どちらからも辿れるように双方向なリストが望ましい
また、データを更新する際には、itemのidをベースにして、探索し、更新をかけないといけないので、itemのidをもとにリンクリストのノードへ飛ぶ辞書も用意しました。
以下のアイディアを実装したのが下記です。
https://gist.github.com/1663101
module Orderd class Ranking def initialize @first = @last = Node.new @item_id_of = {} @marker_of = {} @size = 0 end def top item_id n = @item_id_of[item_id] if n.nil? n = Node.new n.value = item_id @item_id_of[item_id] = n @size += 1 update_markers else return n if n.prev.nil? # nをLinkListから切りはなす np = n.prev n.prev = nil nn = n.next np.next = nn nn.prev = np update_markers n end n.next = @first @first.prev = n @first = n n end # markerの位置を調整してやる def update_markers node=nil marker_of = {} @marker_of.each do |k, v| marker_of[v] = k end start_marker = 0 unless node.nil? n = node counter = 1 n = node until n.next.nil? if marker_of[n] start_marker = marker_of[n] break end n = n.next counter += 1 end end get_markers(0, @size).each do |i| if @marker_of[i] if i < start_marker # top内から呼ばれることを想定しているので、データを抜いたところより前のランキングは1ずつずらす @marker_of[i] = @marker_of[i].prev else if node.nil? # nodeがなかった場合は、件数がかわってくるので順番をズラす @marker_of[i] = @marker_of[i].prev end end else counter = 1 n = @last until n.prev.nil? rank = @size - counter if rank == i @marker_of[i] = n end counter += 1 n = n.prev end end end end # 与えられた範囲内で # 2のn乗の配列を返す def get_markers start, last markers = [] num = 2 while num <= last if num >= start markers.push num end num *= 2 end return markers end def get_item item_id item = @item_id_of[item_id] i = 1 node = item marker_of = {} @marker_of.each do |k, v| marker_of[v] = k end counter = 0 until node.prev.nil? if marker_of[node] return marker_of[node] + counter end counter += 1 node = node.prev end return nil end def rank_list rank, num result = [] node = @first start_rank = get_markers(rank, @size) if @marker_of[start_rank[0]] i = start_rank[0] node = @marker_of[i] else i = 1 node = @last end until node.prev.nil? if i >= rank if i > rank + num else result.unshift node end else return result end node = node.prev i -= 1 end return result end def size @size end end class Node attr_accessor :prev, :next, :value def inspect prev_object_id = @prev.nil? ? "" : @prev.object_id next_object_id = @next.nil? ? "" : @next.object_id "<Node ##{self.object_id} value=#@value prev=#{prev_object_id} next=#{next_object_id}>" end end end orderd = Orderd::Ranking.new NUM = 10000 orderd.top(15) (1..NUM).each do |i| num = rand(NUM) p [i, num] orderd.top(num) end p orderd.get_item 15 p orderd.rank_list(100, 100).map { |n| n.value }
このような場合に、もっと良く知られたデータ構造とかあったりするんでしょうか?
2011-10-31
■[perl]memcachedのように使えるBloomFilter
YAPCでmalaさんの話を聞いていて、memcachedのようにお手軽に使えるbloom filterがあるとひょっとすると便利かもしれない、とふと思ったのでAnyEventつかって、Bloom::Fasterのwrapperを書いてみました。
https://github.com/walf443/p5-bloomd-server
以下のようなプログラムを書いてサーバーを起動します
use strict; use warnings; use EV; use AnyEvent; use Bloomd::Server my $cv = AnyEvent->condvar; my $server = Bloomd::Server->new( capacity => 100_000_000, backupdir => '.', ulog => 'ulog', ); $server->run $cv->recv;
起動したらncで会話できます。
$ echo "check foo" | nc localhost 26006 CHECK foo 0 END $ echo "check foo barfoo" | nc localhost 26006 CHECK foo 0 CHECK barfoo 0 END $ echo "set foo" | nc localhost 26006 OK $ echo "check foo barfoo" | nc localhost 26006 CHECK foo 1 CHECK barfoo 0 END
statsでサーバーの統計情報を知ることができます。
$ echo "stats" | nc localhost 26006 STAT capacity 100000000 STAT cmd_check 6 STAT cmd_set 1 STAT error_rate 0.001 STAT pid 87719 STAT server_id 1 STAT server_time 132001511179202 STAT uptime 237 STAT key_count 2970 STAT vector_size 1155555563 END
backupコマンドを実行すると、backupdirで指定したディレクトリに、snapshot + timestampというファイル名でbackupを取ることができます。
snapshot.132001522548775
backupするコマンドがXSレベルでファイルの読みかきをしているので、AnyEventをブロックしてしまうという問題は、redisをパクって、forkして子プロセスでバックアップファイルを作ることで解決しました。
from_snapshotでファイル名を指定すると、バックアップを使って、起動することができます。
また、レプリケーションをすることができます。
欠点としては、あまり早くないので、基本的には他にある既存のKVSを使った方がよいケースが多いでしょう。ただ、データをどれだけsetしても使用メモリ等は増えていかないので、そういう面で運用しやすさはあるのかなーと思っています。
今自分が使えそうかなーと思っているユースケースとしては、色々なユニークユーザーの件数の集計処理をリアルタイムで実現するのに使えるかもな、という感じです。(ユニークユーザーのデータを保持しようとするとたくさんのデータを保存しないといけないが、具体的なユーザーはわからなくてよく、だいたいのユニークがわかればよい)
2011-09-26
■[qudo][mysql]qudoで一日にjobが何件ぐらい投入されたか計測する
jobが一日あたり何件ぐらい投入されているのか、お手軽に確認できるようにするのに、MySQLのtriggerを使ってみるとどうだろ、ということでやってみた。
まず普段のqudoのschemaに加え、以下のようなテーブルを追加します。
CREATE TABLE `job_counter` ( `enqueued_on` date NOT NULL, `count` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`enqueued_on`) ) ENGINE=InnoDB
で、以下のようなtriggerを追加します。
delimiter / CREATE TRIGGER job_counter ON AFTER INSERT job BEGIN INSERT INTO job_counter (enqueued_on, count) VALUES (CURDATE(), 1) ON DUPLICATE UPDATE count=count+1; END / delimiter ;
これで、jobをenqueueすると、その日のjob_counterが+1されるようになるようだ。
なければ追加しつつあるカラムをインクリメントするようなSQLは、INSERT INTO xxx () VALUES ON DUPLICATE UPDATE 〜という構文を使うらしい。
CURDATE()を使っているので、レプリケーション構成をつくるときに、Rowベースレプリケーションにとかにしてないと、Master/Slaveがズレるのかなと思いつつ、ステートメントベースの設定の状態で、mysqlbinlogでバイナリログを見てみた感じだと、トリガに関してのbinlogは吐かれていないようなので、masterでもslaveでも各々triggerが実行される、という形になるっぽいのでよろしくないのかもしれない。ちょっと自信がないな。
http://dev.mysql.com/doc/refman/5.1/ja/stored-procedure-logging.html
2011-09-20
■[kvm]KVMをコマンドラインから使うの術
結構前に自宅サーバーの仮想化はXenにしていたのをこれからはKVMらしいのでKVMに変えてみるか、とやってみたときは、ホストOSにX入れないとうまく動かなかったりそういう点が嫌だなぁと思っていたが、だいぶ環境や情報が揃ってきて、Xenで作ったりするのと同程度には楽にできるようになったようだなと思ったので、部屋の模様替えして再起動したついでにえいや、とやってみた。
ちょっと覚え書き程度にまとめておく。
sudo -H virt-install -n natty -r 256 --vcpus=1 \ -f /var/lib/libvirt/images/natty.img -s 5 \ --os-type=linux --os-variant=virtio26 \ --network bridge=br0 \ --location='http://jp.archive.ubuntu.com/ubuntu/dists/natty/main/installer-amd64/' \ --nographics \ --extra-args='console=tty0 console=ttyS0,115200n8 preseed/url=http://192.168.0.101/preseed-natty.cfg preseed/url/checksum=7e717ea622372d92d0a461f07d8d4e1a language=en locale=en_US.UTF-8 hostname=natty'
起動CDのISOイメージをDownlooadしてくるのがめんどうな場合は--locationでURLを指定して直で起動できるようだ。(Debian/Ubuntu/CentOSあたりのメジャーなやつはだいたいURLがあるようだ)
--locationするURLがググっても出てこない場合は、インストールCDのisoファイルを手元にダウンロードしてきて、-cで指定する方法もあるようだ。
LinuxをCLIでインストールするのに重要なのが、--nographicsと、--extra-argsの'console=ttyS0, 115200n8'で、これがないと起動しているが画面には何も表示されなかったりする。シリアルコンソール以外で遠隔インストールをするには、VNCとかをつかってやるのがメジャーなようだ。(シリアルコンソール経由でインストールできないOS(?)の場合は使う必要がありそう)
115200n8という謎の文字が何を意味しているのか、というと、
http://lxr.linux.no/linux+v2.6.32/Documentation/kernel-parameters.txt#L514
http://lxr.linux.no/linux+v2.6.32/Documentation/serial-console.txt#L514
ただのオプションのようだ。115200は、serial-console.txtによるとスピードの最大値らしいので、最大値を指定する、というのが慣例になっているのかもしれない。n8は8bitごとに転送する、ということなのかな。オプションはこの数字じゃなくてもいけそうな気がするな。
なお、FreeBSDの場合はまたシリアルコンソール経由での起動の仕方が違っていて、
wget ftp://ftp.freebsd.org/pub/FreeBSD/releases/amd64/ISO-IMAGES/8.2/FreeBSD-8.2-RELEASE-amd64-bootonly.iso bsdtar -C bsdiso-serial -pxvf FreeBSD-8.2-RELEASE-amd64-bootonly.iso sudo bash -c "echo 'console=\"comconsole\"' >> bsdiso-serial/boot/loader.conf" sudo -H genisoimage --no-emul-boot -R -J -b boot/cdboot -v -o FreeBSD-8.2-RELEASE-amd64-bootonly-serial.iso bsdiso-serial
みたいにして、起動ディスクのISOイメージファイルを書きかえたあと、-cでISOファイルを指定しつつ起動する、という方法でなんとかなるようだ。(なお、mkisofsじゃなくgenisoimageをつかっていたり、bsdtarをつかっていたり、Debianでの実行を前提にして書いている)
preseed/url、preseed/url/checksumは、Debian/Ubuntuのインストーラーで毎回ポチポチ入力しないですむように、preseedを動作させるための指定であるが、VMの複製はvirt-cloneでやった方が早いし、楽なので、クリーンに入れなおしたいことが頻繁になければがんばって作る必要はなかったようだ。
インストールの途中で強制終了したときは、
sudo -H virsh destroy natty sudo -H virsh undefine natty
することで、また同じ名前で作れるようになるようだ。
ということで、Xenと違ってホストOSにゲストOSが限定される、ということがないので、自宅で色々マシンを作って実験して遊んだりするのはだいぶ快適になったはず。
2011-08-28
■ isuconに参加してきた
isuconに参加してきた
当日の行動
ややうろ覚えですが、自分がやっていたことをざっくり書いてみます。
- まず、実行用の環境へアクセスしようとしたら、reverse proxyのサーバーへしかsshできなかったので、とりあえずisuconユーザー以下にあるファイルをざっと眺める
- ソースいじっりするための、reposやdeployスクリプトを作ったり
- Devel::KYTProfをuseするようにして、ベンチマークを走らてみる
→ 即興で作ったdeployツールのバグに気づかず、Devel::KYTProfのログが出なくてあれー、ということで時間を使う。
→ そもそもClass::Data::Inheritableがうまくuseできていないという問題があったので、とりあえずsudo -H cpanmで入れるようにした。(たぶんarch用のpathもuse libするようにすればよかったはず)
- KYTProfをonでdeployを動かす
- MySQLのクエリを解析できるように、log_slow_queryを有効にして、long_query_timeを0にして全てのクエリがslow-logへ吐かれるようにする
だいたいここまでで12時くらい
とりあえずアプリ側をおまかせして、ミドルウェア系のチューニングをやろうかなと思い、
- メモリ等にも余裕はありそうだったので、starmanのプロセス数を増やしてみた(→ ベンチ速度にはほとんど意味なし )
- MySQLの設定はほぼデフォルト状態だったので、innodb-buffer-poolやkey-bufferをざっくり設定. ( → データは元々の状態で、ほぼメモリに乘りきっていたので、DBの負荷は高かったがあまり効果はなかったっぽい?)
あたりをやった。
ここまでやってみてアプリ側があんまり進んでいる感じがしなかったので、手を入れはじめた。
KYTProfのログにざーっと目を通すと、
かなり目につくやつは、
SELECT a.id, a.title FROM comment c INNER JOIN article a ON c.article = a.id GROUP BY a.id ORDER BY MAX(c.created_at) DESC LIMIT 10
だった。いちおうMySQLのslowlogをmysqldumpslowにかけてみて、やっぱりそこが一番遅い、というのを確認してから、作業しはじめた。
複数クエリに分割できないかなー、というのを考えていたのですが、他のチームがぐんぐんスコアあげているのに焦っていて、とりあえず少しでも早めにスコアあげたいということで、とりあえず
SELECT a.id, a.title FROM comment c INNER JOIN article a ON c.article = a.id GROUP BY a.id ORDER BY MAX(c.id) DESC LIMIT 10
して、少しはましになった。他チームの解法を聞くまで、article側にcommentの最終投稿カラムを持たせればよいだけ、というのに気づかなかったのは間抜けだった。orz ( この時点でスコアは1200程度)
なお懇親会で話を聞いてみると、最終投稿時刻カラムを持たせるかどうか、がスコア10000いくかいかないかの壁だったようだ。
このクエリを改善しても、やはり遅いので、ここはとりあえずmemcachedにのせよう、ということで、memcachedを構築して、使うようにしたりした。
投入してみると、どうやらmemcachedにデータがうまく入っていないらしい、ということで、
調査していた。結局並行で作業を色々していたうちに直ってしまったので、ちょっと気持ち悪いが、おそらくCentOSだとmemcachedの設定は/etc/sysconfig/memcached.confを変更しないといけないが、CentOSに不慣れで/etc/init.d/memcachedの中をいじってしまっていて設定の変更が効いていなくて、デフォルトの状態だとlocalhostからの接続しか受けつけない、とかそういう感じだったのではないかな、と思っているが定かではない。
その間に、memcachedのシリアライザや圧縮あたりの処理が入って無事動きだしたかと思いきや、「サイドバー更新後のコンテンツの更新がありません」チェックにひっかかるようになってしまった。
なので、更新時にキャッシュをクリアするようにしたが、まだダメで、更新時にキャッシュを作るように変更してみた。
だがまだうまくベンチマークのチェックが突破できない、ということで考えていて、Cache::Memcached::Fastの設定でnowaitを1にしていたので、post時に更新した瞬間にアクセスすると、リクエストは終わっていたが、更新されていない、という状態になったりしているのかなー、ということでオフにしてみたら、無事通るようになった。 ( この時点でスコアは5000程度 )
その後もひたすらベンチマークツールを動かしつつ、ログをみつつ、Devel::NYTProfの結果から遅いところを潰していく、という感じ。
DBIのコネクション貼ったり、とか、memcachedへの接続が遅いとか、ネットワーク周りの接続がXenだからか非常に遅かったのが気にかかったが、あまり調査している時間もなかったので、とりあえずはなるべく再接続させないようにインスタンスキャッシュしたりして接続回数を少し減らしたりした。
DBは、thread_cache_sizeやmax_connectionsを増やしてみたが、あんまり効果はなかったように思われる。(あんまり冷静に分析できてはいないけど)
あとは、コンテンツの新規追加後の編集はないので、サイドバー以外の情報はほとんどキャッシュすることができるとわかったので、通常のDBへ対するSELECT文をひたすらmemcachedへ向けていく、というオーソドックスな対応をしていて作業終了。
コンテンツ更新後にサイドバーが更新されていないとチェックが失敗するのに苦しめられつつ、最後には、結局失敗してしまった。
うちのチームがやった変更点は、https://github.com/walf443/isucon に反映されています。
反省
- あまり人数をうまく活用できなかった
- 事前打ちあわせとリーダーシップ重要
- 現状の状況報告と各々がやることを被らないようにはっきりさせる
- 最初のベンチマークツールを動かした時点で開始から1時間以上たってしまっていた
- まずは「推測するな、計測せよ」
- ベンチマークからプロファイルとかしてみた結果から判断しないと、限られた貴重な時間の中で、無駄なことに時間を使ってしまう
とても準備作業大変だったと思いますが、非常に楽しいイベントを用意していただいたライブドア様、ありがとうございました。
こういう参加者がみんな体験を共有できるようなイベントは懇親会とかでも今まで会ったことない人たちでもとても会話しやすくて非常によいですね。
2011-07-07
■[perl]Test::Tester
以前テストライブラリのテストを書く際に、Test::Builder::Testerがつかえる、という記事を書いたが、Test::Builder::Testerは、テストの失敗の文字列を一字一句かえないように調整したりするのが、ダルいなぁ、というのがあったりして、もっとよいやつを探していたら、Test::Testerというモジュールをみつけたので、紹介。
Test::TesterのTest::Builder::Testerより良い点としては、
- local $Test::Builder::Level = $Test::Builder::Level + 1しているかのチェックが意識せずに基本的にされる点
- テスト用の処理がコンパクトにまとめられるインターフェース(check_test)
あたりが個人的には良いかな、と思いました。
どちらのモジュールにせよ、ややトリッキーなことをして実現している面はあるので、テストのファイルは独立させておき、Test::Moreの比較的新しい機能であるsubtest内とかではやらない方が無難なようです。
2011-07-04
■[perl]続 Test::TypeConstraints
http://d.hatena.ne.jp/walf443/20110704/1309738408 で公開した、Test::TypeConstraintsですが、反応をもとに、微調整してCPANへあげました。
- type_is_a_okというメソッド名をtype_isaにしました
- Data::Validatorに依存せず、Mouse::Util::TypeConstraintsを直接使うようになりました
- type_doesというRole名を指定できる版を追加しました。
- coerceオプションで、coerce込みでチェックできるようにしました
■[perl] local $var += 1の挙動
Test::TypeConstraintsのレビューをしてもらっていたときにid:gfxさんに教えてもらったのですが、
local $Test::Builder::Level += 2;
としていたのですが、これは、「元々の$Test::Builder::Levelに2を足す」、という挙動にはならないです。
そもそも、これは、テストが失敗したときの呼出し元が変にならないようにするためのおなじないで、一つのメソッドしか経由していないので、+1するべきで、+1でうまく動いていないことに疑問を持つべきでした。
以下挙動を確認するためのプログラムです。
use strict; use warnings; use Test::More; our $TEST = 10; { local $TEST += 1; is($TEST, 1, "undef + 1 = 1"); } is($TEST, 10, "restore ok"); { local $TEST = $TEST + 1; is($TEST, 11, "10 + 1 = 1"); } is($TEST, 10, "restore ok"); done_testing();
わりとうっかり書いてしまいそうなので、要注意なり。
■[perl]Test::TypeConstraints
ちょっと固めに書いておきたいところで、メソッドの戻り値の型をテストしておきたくて、Smart::Argsとかを使うのに慣れてくると、ArrayRef[Int]とかでテストできると楽だなぁと思ったので書いてみました。
https://github.com/walf443/p5-test-type_constraints
内部的には、Data::Validatorを呼びだして、エラー時にメッセージをちょろっと変えているだけ。
自前のsubtypeつくったりして、そういうのをテストするときにも使えそうで、そういうときは、coerceが効いた方がよいのかな、思いつつ、どうやってそのあたりのインターフェースをかえようかな、というのは考え中です。
(Moose|Mouse)はけっこう使われてはいるはずなので、似たようなのが既にあるかもしれないですが、ぱっとみ見つからなかったです。
2011-04-09
■[perl]Skeleton::CoC
フレームワークにそってアプリケーションを開発していると、これを追加するには、このクラスとこのクラスを作って、ここにテンプレートを追加する、といったことがよくある。
railsとかだとscript/generateとかあってテンプレートを元にファイルを自動生成できるので、似たようなのを簡単に作れると便利なこともありそうだな、ということで今さらながら書いてみた。
他にも似たようなのありそうだなぁと思いつつ、意外とそれっぽいのがなくて、わりと簡単に使えるのではないかなと思う。
もちろんrubigenでもよいのだけど、環境によってはrubyやらgemがあまり入ってない環境もあるので。
https://github.com/walf443/p5-skeleton-coc
テンプレートの種別ごとに、define_targetで依存関係と、ファイル名を指定してやり、Data::Section::Simpleで対応するテンプレートファイルを書いてやる。
テンプレートファイルの文法にはText::MicroTemplateを使っているので、ほぼperlで好きなように書ける、といった感じ。
package YourApp::Skeleton; use parent(Skeleton::CoC); use String::CamelCase qw(); __PACKAGE__->define_target('controller' => [], sub { my ($self, $pkg) = @_; my $path = join "/", split /::/, $pkg; return "lib/YourApp/C/$path.pm"; }); __PACKAGE__->define_target('action' => ['controller'], sub { my ($self, $str) = @_; my $path = join "/", map { String::CamelCase::decamelize($_) } split /::/, $self->controller; return "assets/tmpl/$path/$str.html"; }); __DATA__ @controller ? my $self = shift; # $self is a Skeleton::CoC. package <? $self->project ?>::C::<?= $self->controller ?>; use strict; use warnings; use parent qw(YourApp::C); ? if ( $self->action ) { sub dispatch_<?= $self->action ?> { my ($self, ) = @_; } ? } 1; @@ action [% INCLUDE "include/header.html" %] [% INCLUDE "include/footer.html" %]
で、生成させるときに呼ぶファイルはこんな風に記述しておく。
strict;
warnings;
use YourApp::Skeleton;
my $skeleton = YourApp::Skeleton->new(
project => "YourApp",
action => "index",
);
$skeleton->parse_options(@ARGV);
$skeleton->generate;
で、こんな感じで実行してやる。
$ ./skeleton.pl controller Foo::Bar
# => generate lib/YourApp/C/Foo/Bar.pm
$ ./skeleton.pl controller Foo::Bar action baz
# => generate lib/YourApp/C/Foo/Bar.pm
# generate assets/tmpl/foo/bar/baz.html
個人的な要望にはわりと沿ってはいるが、まだそんなに使いこんではいないので、使いにくいケースはあるかもしれない。
もうちょい使いつつ、インターフェース等は変えるかもしれない。
2011-04-02
■[vim][git]pathogenのhelptagsしたらsubmoduleがdirtyになってめんどい
最近pathogen.vimへ移行して、だいぶvimのライブラリをupdateしたりするのが楽になったのですが、
:call pathogen#helptags()
とかやると、uniteのマニュアルとかも:h uniteとかで引けるようになる、というのはとてもうれしいのですが、その一方で、git statusとかすると、submoduleがdirtyになってしまって、困っていました。
gitignoreに追加したらいいんじゃないかな、とおもってやってみたら、helptagsしてもdirtyには入らなくなりました。
親レポジトリの.gitignoreに追加しても意味がなくて、core.excluesfileのgitignoreのファイルに、**/tagsおよび、**/tags-jaを追加するとよいらしいです。
2011-03-17
■[perl]Cache::Pluggable
Cache::な名前空間を持つライブラリは、get/setなどのインターフェースがわりとそろえてあるのですが、ライブラリによって微妙に挙動が違ったりして、ちょっと別のライブラリを検証してみたり、とかが意外としづらいです。
例えば、Cache::Memcached::FastではhashrefなどをStorableでシリアライズしつつ透過的にget/setしてくれますが、Cache::KyotoTycoonではそういう機能はありません。
そこで、wrapperを書いてアプリケーションからは使うようにしたりするわけですが、毎回似たようなものを生やしたりするのは飽きたよ、ということで、Pluginを書いてやって、コアはシンプルな機能のままで、Pluginを抜き差しするだけで挙動を変えられるようにしよう、ということでCache::Pluggableというやつを書いてみました。
Plugin機能は元々あるメソッドの機能を上書きとかして挙動をかえまくりたかった関係で、Mouse::Roleをつかってみました。
同じメソッドをmethod modifiersしまくりなので、ちょっと使うPluginが増えてくると、Pluginのロード順を気にする必要があるのがよろしくないかもしれない。
