Shogo's Blog

Dec 17, 2015 - 6 minute read - perl

PerlのDBIx::Class利用上の注意点

この記事は、Perl 5 Advent Calendarの17日目の記事です。 Redis::Fast の reconnect についての中で DBIx::Classのreconnectについても触れています。 DBIx::Classの安全にreconnectionが行えるように考慮されていますが、色々と注意点があります。 reconnection周りで調べてみたので、Advent Calendarの枠を借りてまとめたいと思います。 DBIx::Classとは DBIx::ClassはPerlのO/Rマッピングモジュールです。 テーブル間のリレーションを定義でき、JOIN句の入ったクエリもサポートする等、かなり高機能なモジュールです。 もう僕はJOIN句をDBIx::Class以外で書ける気がしません。 詳しくはtypester先生の解説記事をどうぞ。 Perl Hackers Hub 第3回 DBIx::Classでデータベース操作(1) 第3回 DBIx::Classでデータベース操作(2) 第3回 DBIx::Classでデータベース操作(3) サンプル サンプルとしてユーザの所持金を管理する簡単なアプリを作ってみます。 Webアプリとか作るの面倒だったので、コンソールアプリです。 package My::Schema::User { use base 'DBIx::Class::Core'; __PACKAGE__->table('user'); __PACKAGE__->add_columns( id => { data_type => 'INTEGER', is_nullable => 0, is_auto_increment => 1, }, username => { data_type => 'VARCHAR', size => 255, is_nullable => 0, }, ); __PACKAGE__->set_primary_key('id'); # userとmoneyは1対1の関係で、userに対応するmoneyが必ず存在しなければならない __PACKAGE__->has_one( 'money' => 'My::Schema::Money', { 'foreign.

Dec 16, 2015 - 3 minute read - git

git-mergeの挙動をカスタマイズする

最近gitのコンフリクト解消職人みたいになっていてすごくつらいです。 普通のプログラムであれば順番が重要なので手動でのコンフリクト解消は避けられないのですが、 僕が相手にしているのは最終的にMySQLに食わせるデータなのでそこまで順番は重要ではありません。 順番に挿入したところで、MySQLが順番にかえしてくれるとは限りませんからね。 このようなケースではある程度機械的にマージできるのでは?と調べてみました。 merge driver いろいろググってみるとgitattributesでファイル毎にマージの細かい挙動を制御できるようです。 通常マージの方法はgitがよしなに選択してくれますが、merge属性に以下の項目を指定することでマージの方法を強制することができます。 text テキストファイルとしてマージする。 コンフリクトすると <<<<<<<, =======, >>>>>>>でコンフリクトした場所を教えてくれる。 binary バイナリファイルとしてマージする。 コンフリクトするとマージしようとしたファイルを残しておいてくれる。 union テキストファイルとしてマージする。 textと違ってコンフリクトしてもマーカを付けない。どちらの変更も残すように適当にマージしてくれる。 適当なので コンフリクト時の行の順番は保証されない text, binaryはコンフリクトしたときによく見る挙動ですね。 unionは初めて知ったので、簡単なレポジトリを作って挙動を確かめてみました。 $ # masterブランチ上でmembers.txtにAliceを追加する $ git init $ echo Alice > members.txt $ git add members.txt $ git commit -m 'add Alice' [master (root-commit) 8c39714] add Alice 1 file changed, 1 insertion(+) create mode 100644 members.txt $ $ # add-bobブランチ上でmembers.txtにBobを追加する $ git checkout -b add-bob Switched to a new branch 'add-bob' $ echo 'Bob' >> members.

Dec 13, 2015 - 1 minute read - go golang

Goでデプロイ神社書いてみた

Go その2 Advent Calendar 2015の13日目の記事です。 その1 その2 その3 六曜を知ることができる便利コマンドを作ってみたお話です。 Deploy神社とは Maco_Tasuが作ったいつdeployしたら安全かを教えてくれる便利APIです。 詳しくは作者ブログ記事をどうぞ。(Deploy神社APIを作った- 眠すぎて明日が見えない) 便利APIなのですが、依存している外部APIが利用できなくなってしまったため、Deploy神社自体が利用できなくなっています。 作ってみた デプロイする時間が分からないと不便なので、Go実装を作ってみました。 shogo82148/go-deploy-shrine go getしてきてお祈りを捧げればデプロイするべき時間を教えてくれます。 $ go get github.com/shogo82148/go-deploy-shrine/cli/pray $ pray 今日は旧暦の11月3日(先勝)です。deployは午前中に済ませましょう。 先勝 - Weblio 六曜の一。急用や訴訟などによいとされ,早く事を行うのがよく,午前は吉,午後は凶という日。先勝日。せんかち。さきがち。 今日12月13日は先勝で午前中にデプロイするのが良いようです。便利ですね。 六曜とは むかしのカレンダーには暦注と呼ばれる「今日の運勢」みたいなものが記載されていたらしいです。 六曜はその暦注のひとつで、現在のカレンダーにも記載されることの多い影響力の大きなものです。 詳しくはWikipediaで。 六曜 - Wikipedia 旧暦の(月+日)を6で割った余りから簡単に求めることができます。 0: 大安 1: 赤口 2: 先勝 3: 友引 4: 先負 5: 仏滅 旧暦とは 旧暦の月日を求めることができれば六曜は簡単に出せるのですが、 日本における旧暦である天保暦は月の満ち欠けと太陽の動きを元にした暦法であり、 月と太陽の動きを正確に予測する必要があります。 Go版デプロイ神社では「日の出・日の入りの計算―天体の出没時刻の求め方」で紹介されていた計算式を用いています 2033年旧暦閏月問題 天保暦をそのまま当てはめると2033年に月を決定できない問題が知られています。 日本カレンダー暦文化振興協会というところが「閏11月を推奨する」との見解を2015年8月に出しています。 2033年旧暦閏月問題の見解 Go版デプロイ神社では時憲暦方式を採用したつもりです。 せめてGoっぽい話題を 引数に日付を渡すとその日の六曜をかえしてくれます。 いろんな形式に対応していて、以下はすべて2006年1月2日の六曜を返します。 $ pray 20060102 $ pray 1/2/2006 $ pray 2-Jan-06 $ pray 2-Jan-2006 $ pray 2/Jan/2006 $ pray 'Jan 2 2006' 2006-01-02は旧暦の12月3日(友引)です。昼のdeployはさけましょう。するなら朝晩が吉です。 引数の解析には tkuchiki/parsetimeを使っています。 たいていの日時フォーマットなら解析してくれる便利ライブラリです。

Dec 9, 2015 - 4 minute read - perl time leapsecond

Perl の DateTime 利用上の注意点

この投稿は Perl 5 Advent Calendar 2015 の 9日目の記事です。 Perl の Time::Piece 利用上の注意点 という記事の最後にDateTimeへの言及があったのですが、 DateTimeはDateTimeでいろいろとハマりどころがあるんですよね・・・。 僕も今年いくつか罠にハマりました。ちょうどアドベントカレンダーの季節ですし、この機会にハマりどころをまとめてみることにします。 遅い いろんなところで言われていることですが 遅い です。 試しに代表的な日付を扱うモジュールでベンチをとってみました。 (比較のために時間をとるためのPerlの組み込み関数も入れてあります) # いろんな形式で今の時間を取得する use Benchmark qw/ cmpthese /; use Time::HiRes (); use Time::Moment; use Time::Piece (); use DateTime; cmpthese 0, { 'time' => sub { time }, 'Time::HiRes' => sub { Time::HiRes::time }, 'localtime' => sub { () = localtime }, 'Time::Moment' => sub { Time::Moment->now }, 'Time::Piece' => sub { Time::Piece->localtime }, 'DateTime' => sub { DateTime->now( time_zone=>'Asia/Tokyo' ) }, }; Rate DateTime Time::Piece Time::Moment localtime Time::HiRes time DateTime 5303/s -- -95% -98% -99% -100% -100% Time::Piece 103765/s 1857% -- -67% -71% -98% -99% Time::Moment 313599/s 5814% 202% -- -11% -93% -98% localtime 354215/s 6580% 241% 13% -- -92% -98% Time::HiRes 4706723/s 88658% 4436% 1401% 1229% -- -72% time 16536995/s 311751% 15837% 5173% 4569% 251% -- それにしてもTime::Moment速いですね。組み込みのlocaltimeと互角とは。

Nov 23, 2015 - 2 minute read - go golang

Go言語でGraceful Restartをするときに取りこぼしを少なくする

少し前にStarletにGraceful Restartが時たま上手く動かない問題を修正するpullreqを投げました。 原因は割り込みハンドラ内でexitを呼んでいたからでした。 「割り込みハンドラ内ではフラグを建てるだけ」 「メインのプログラム内でそのフラグを見て分岐する」という原則があるのですが、それを守るのは難しいということですね。 (しかし新たな問題を産んでしまいrevertされてしまいましたが・・・ まあ修正後のコードも考え方は一緒です。割り込みホント難しい・・・) このpullreqを取り込んでもらうときに再現実験をやってみたのですが、 Goでもちゃんと動くのかな?と気になったので Go言語でGraceful Restartをするで紹介した プログラムに同じテストをやってみました。 2017-01-22追記: Go1.8以降でGraceful Shutdownがbuild-inになるので、この記事で紹介したライブラリは不要となりました。 詳しくはGo1.8のGraceful Shutdownとgo-gracedownの対応を参照。 mannersでテストしてみる 前回の記事ではmannersとgo-server-starterの 組み合わせが良さそうとの結論になったので、この組み合わせでテストしてみます。 以下テストに使用したコードです。 (今回の内容とは直接関係は無いですが、go-server-starterに変更が入ってFallbackのやり方が前回から少し変わってます) package main import ( "fmt" "log" "net" "net/http" "os" "os/signal" "syscall" "time" "github.com/braintree/manners" "github.com/lestrrat/go-server-starter/listener" ) var now = time.Now() func main() { log.Printf("start pid %d\n", os.Getpid()) signal_chan := make(chan os.Signal) signal.Notify(signal_chan, syscall.SIGTERM) go func() { for { s := <-signal_chan if s == syscall.SIGTERM { log.Printf("SIGTERM!!!!\n") manners.Close() } } }() listeners, err := listener.

Oct 14, 2015 - 1 minute read - go golang

Goオールスターズで登壇してきました

先週の日曜日に登壇してきました。 過去に自作したGoプロダクトの紹介 - Goオールスターズ from Shogo Ichinose 過去に自作したGoプロダクトの紹介 - Goオールスタース 発表の10日くらい前にsongmuさんがKAYACのIRCに現われオールスターを募集に来てくださったものの、 弊社スターの都合がつかないため僕が代わりに発表してきました。 KAYACではGoプロダクトたくさん動いていますが説明は作者にお任せしたほうがいいかなと思い、 自作のGoプロダクトをメインに発表してきました。 go-rgba4444 androidbinary - Androidのバイナリファイルを解析するgoのライブラリ go-sql-proxy - Go言語でSQLのトレースをする go-dithering - Go言語で画像の減色を行う go-prove/go-tap - Go言語でPerlのテストを早くする go-webtail/go-webtail - Go-webtailってのを書いた go-prove、CPANに上げればいいんじゃない?w #eventdots — songmu (@songmu) 2015年10月11日 Perl Archive Network とはいったい・・・ KAYACではいろんなGoプロダクトが動いているのでこちらもどうぞ。 go-katsubushi snowflake-likeなIDジェネレータ stretcher Consul/Surfと連携したデプロイツール rin AWS-S3に出力されたログをRedshiftへインポートするツール mirage Dockerを使ったテスト用環境構築 alphawing Android/iOSアプリの社内配信ツール スライドにちょこちょこ修正いれててGopherくん人形もらうの忘れてたけどもらっておけばよかった。 他の人の発表はこちら。 Goオールスターズ GoオールスターズToggetterまとめ Goオールスターズで登壇してきました - おそらくはそれさえも平凡な日々 Goオールスターズでpackage managementについて話してきました - YAMAGUCHI::weblog Goだけでモバイルアプリを作ろう Goオールスターズ - 考える人、コードを書く人

Sep 28, 2015 - 1 minute read - perl go golang

AnySan::Provider::Slackとape-slackを書いた

先週、今のプロジェクトでのメインのコミュニケーションツールをIRCからSlack切り替えました。 それにともないIRCに済んでいたボットたちもお引越しする必要があったので、 ボットとSlackをつなぐためのライブラリを書きました。 AnySan::Provider::Slack ape-slack Perlとgoのボットが住んでいるのでそれぞれの言語で実装してあります。 AnySan::Provider::Slack PerlのAnySan用のモジュールです。 use AnySan; use AnySan::Provider::Slack; my $slack = slack token => 'YOUR SLACK API TOKEN', channels => { 'general' => {}, }; $slack->send_message('slack message', channel => 'C024BE91L'); AnySan->run; AnySanを使うだけでも便利なんですが、 今のプロジェクトではAnySanを対話形式で使いやすくするようにUnazuSanを使っています。 UnazuSanはIRC前提で書かれていて、AnySan::Provider::Slackをインストールしてもそのままは使えません。 UnazuSanを置き換えるもの面倒なので、イベントの名前を書き換えて投げ直すことで、 SlackのメッセージをIRCに見せかける方法をとっています。 またSlackのOutgoing Webhookで@つきのmentionを捕まえるにもあるように、 Slackのメンションは <@U08DGJVJ7>のような形式になってしまい、UnazuSanは自分へのメッセージとして扱ってくれません。 これをUnazuSanが解釈できる形式に置き換えるのがポイントです。 use 5.010; use warnings; use utf8; use Encode qw/encode_utf8/; use UnazuSan; use AnySan; use AnySan::Provider::Slack; my $unazu_san = UnazuSan->new( host => 'example.com', password => 'xxxxxxxxxxx', enable_ssl => 1, join_channels => [qw/arcade/], respond_all => 1, ); my $slack = slack( token => 'YOUR SLACK TOKEN', channels => {}, as_user => 1, ); AnySan->register_listener( slack => { event => 'message', cb => sub { my $receive = shift; # fake irc privmsg $receive->{event} = 'privmsg'; $receive->{message} =~ s/<\@xxxxx>:/unazusan:/; AnySan->broadcast_message($receive); }, } ); $unazu_san->on_command( help => sub { my ($receive, @args) = @_; $receive->reply('help '.

Sep 28, 2015 - 1 minute read - isucon

ISUCON5の予選に参加して惨敗してきた

こんにちは、チームぽわわ4 feat. ネコトーストラボです。 ISUCON5の予選に参加してきて見事に惨敗してきました。 お題 「ISUxi」という名前の「高負荷に耐えられるSNSコミュニティサイト」。 日記やコメントの投稿ができて、ホーム画面には「あしあと」「あなたへのコメント」「あなたの友だちの日記エントリ」「あなたの友だちのコメント」が表示されています。 日記にはprivateとpublicの公開範囲があって、これの出し分けも必要です。 やることおおい・・・。 やったこと 〜開始 時間余裕でしょと思ったら全くそんなことなかった — ひさいち (@hisaichi5518) 2015年9月25日 時間余裕でしょと思ったら全くそんなことなかった — Ichinose Shogo (@shogo82148) 2015年9月25日 5時間で決着をつける https://t.co/AbnnSyHuZ8 — Ichinose Shogo (@shogo82148) 2015年9月26日 バッテリ残量との戦いがすでに始まっている #ISUCON #アダプタ忘れた — Ichinose Shogo (@shogo82148) 2015年9月26日 〜午前中 ソースコードをgit管理下に置くとか準備したあと、ソースコードを眺めてスキーマやクエリの改善ができないかを見てました。 主にインデックスに不足は無いか、ループクエリは無いかを見てみました。 インデックスに関しては必要そうなところにはすでに貼ってあって、これ以上することなさそうな感じ。 ループクエリに関しては、ホーム画面の「あなたの友だちのコメント」の部分で、エントリ情報や、関連するユーザの情報を取ってくるところで見つけたので、JOINに書き換えられないか着手。 しかし、実行計画が大きく変わって極端に遅くなってしまい、なんだこれーってなってました。 〜14時 SQLじゃ無理だってことで、Redisに切り替え。 エントリやコメントをRedisのリストで管理して、 エントリやコメントを投稿したときに友だち全員に配信する形式に変更しました。 ある程度書けてこれで動くのでは!ってとこまで書けたんだけど、 「投稿した時に友だちに配信」形式だと、友だち関係があとから変化するケースに対応できないという気がつく。 いろいろ考えてみたものの、友だち関係が変化した場合は元の実装を使うしか思いつきませんでした。 そしてここでバッテリー切れ・・・ あと3%…(ヽ´ω`) — Ichinose Shogo (@shogo82148) 2015年9月26日 〜16時 アダプタを借りることができて延命しました。ありがとうございます! アダプター貸していただけました。ありがとうございます!m(__)m #isucon — Ichinose Shogo (@shogo82148) 2015年9月26日 コメント部分のキャッシュが一応は動いたので、エントリ部分についてもRedisを使ったキャッシュ化を進めてました。 200位スコアはあがるものの劇的な改善にはならず・・・(ヽ´ω`) 〜19時 コメントや日記部分これ以上の改善案を思いつけなかったので、諦めてあしあとの改善に着手。 DATE()関数をGROUP BY句に使っていてインデックスが使えない感じだったので、 カラムにしてインデックスが効くように書き換え。 しかし、ベンチが最後まで通らず、この修正は断念・・・。

Sep 19, 2015 - 1 minute read - perl

テストでも:ok_maopy:したい人へ

shogo82148/p5-Acme-OkMacopy use strict; use Test::More; use Acme::OkMacopy; ok_macopy "macopy is cool", "ok_macopy"; done_testing; 様子です pic.twitter.com/sA96GmqKmQ — トーカナイザの守護霊 (@mackee_w) 2015年9月17日 :ok_macopy:

Sep 19, 2015 - 1 minute read - perl go golang

Go言語でPerlのテストを早くする

Test::mysqld::Multiというモジュールを書いてみたみたいな涙ぐましい努力により5分で終わるようになったテストですが、 プロジェクトのコードも増えて人も増えた影響で、 テスト時間が約7分まで伸び、テストのキューに10個近く並んで順番待ちさせられるという状況になってしまいした。 この状況を解決すべく go-prove というものを書いてみたので、そのご紹介です。 proveが遅い理由 proveがテストの結果を読むところがブロッキングI/Oになっているらしく、そのせいで遅くなっているらしいです。 Perl-Toolchain-Gang/Test-Harness#30 実際に結果読んでいるところはこの辺ですかね。 selectとか使っていてなるべくブロッキングしないような作りにはなっていそうですが、どこかでブロッキングしてしまっているようです。 今のプロジェクトだと32コアのCPUで32並列で動かしてもCPUを100%使い切ることができませんでした。 Shunme ググるとShunmeというプロジェクトでproveの問題を解決しようという試みが行われているようです。 Shunmeというperl用のテストハーネスモジュールを書き始めました magnolia-k/p5-Shunme しかし残念ながらproveのプラグイン機構はサポートしておらず、Formatterの指定オプションもないようです。 今のプロジェクトではプラグインでMySQLを立てたり、JUnitでテスト結果をフォーマットしたりということをしているので、そのままは使えなさそう。 ちょっと改造するにはソースコードの理解が大変そうなので断念。 「(逆に遅くなるときも有ります)」というところも気になりますね・・・。 go-prove いろいろテストの実行方法を調べてはみましたが、どの方法も並行処理に苦労している模様。 テストファイル自体はただのPerlのスクリプトなので、実行して集計する部分は別にPerlにこだわる必要ないのでは? 並行処理といえば今ならGolangでしょ!ってことでproveのGo実装を書いてみました。 go-prove 例えば以下のようなテストをかいて、 use Test::More; ok "macopy"; done_testing; go-proveコマンドと実行すると、JUnit形式でテスト結果が出力されます。 $ go-prove 2015/09/19 21:45:44 start t/macopy.t 2015/09/19 21:45:44 finish t/macopy.t <testsuites> <testsuite tests="1" failures="0" time="0.225" name="t_macopy_t"> <properties></properties> <testcase classname="t_macopy_t" name="" time="0.225"></testcase> </testsuite> </testsuites> go-prove -j 32とするとgoroutineを32個生成して、32並列でテストを実行してくれます。 I/Oの処理をGolangのランタイムがよしなにやってくれるので、楽ちんです。 また、今のプロジェクトではApp::Prove::Plugin::MySQLPoolを使っているので、それ相当の機能をgo-prove -plugin mysqldで使えるようにしました。 プラグインを有効にするとMySQLサーバを立ち上げて、その接続先情報をGO_PROVE_MYSQLD環境変数に設定してくれます。 実際にプロジェクトのコードで試してみたところ7分かかっていたテストが4分を切るようになりました。 CPUの使用率も100%近くになって、有効活用できているようです。 まとめ Perl製のproveは並列実行に弱い Goで書きなおしてCPUをフル活用できるようになった 早くはなるものの、既存のテストコードに手を加える必要があってちょっと怖いかなと思ったので、プロジェクトへの組み込みはやってません。 まあ本番環境で走るものではないので、ある程度動くことが確認できたら置き換えてみたいですね。