maco's life

主にエンジニアリングと読書について書いていきます。

アニメイトラボに入社しました

2年8ヶ月勤めていた面白法人カヤックを退職して、アニメイトラボで働き始めました。

10月~11月に転職活動をしていて、いくつかの魅力的な企業からオファーを頂いていたのですが、アニメイトラボは設立間もない会社で地盤整えていくフェーズだったのが楽しそうだったり、以前から一緒に働いてみたいと思っていた方が在籍していたりなどのご縁もあって、入社することを決めました。

オフィスは千駄ヶ谷にあって、会社の近くに銭湯があるのが良い感じです。

前職でお世話になった方々と「次が決まったら教えてね」というお話もあったので、ご挨拶を兼ねてブログに書きました。

今後とも皆様よろしくお願いいたします。

DDLとかの検証にDummyデータをさくっと用意するSQL

前にダミーデータサクッとつくりたいわっておもって、便利ストアドプロシージャつくったのでブログにも書いておく。

gist.github.com

SELECTして結果をINSERTするところをコピペしていけば、ストアドプロシージャなんかつかわないでもいいんだろうけど、DRYなコードにしたいじゃん?

そういうことです。

NoPaste作った

丸一日かかってしまったけど、ちゃんと作ってみた

github.com

構成

  • Kossy
  • DBIx::Sunny
  • etc...

DBIx::Sunny初めて使ったけど、シュッと使えて便利だった

感想

今まで一人で一からアプリケーションを書くことは、あまりなかったから良い経験になった

それと共に、自分のPerlを使った実装力がないなーっておもってつぶやいた

地球で二番目っぽいので満足でした 😇

ISUCON5 本戦での学び

ISUCON5の本戦をchatzmersとして参戦してきました。 結果からいうと惨敗でした。

敗因としてRobert C. Pike氏の下記の言葉に全て詰まっていると思います。

推測するな、計測せよ

どういうこと?

今回使用されていたデータベースがpostgresでした。 僕たちは

  • 日常的に業務でmysqlに触れていてpostgresより詳しい
  • 本戦の予行演習で mysqlのチューニング方法を予習していた

以上の判断から最初にpostgresをmysqlに置き換えることをしました。 限られた時間しかない中、もしかしたらdbにボトルネックがないかもしれない という状況で先に移行をするという決断をしたのは判断ミスだったなと思います。

今回で言えばslowqueryをみたり、cpuの使用率をみたら今回はDBに全く 負荷がなかったことはわかっていたようなので、 予選の時にできていた計測して確実に潰していくというスタンスは本戦でも 徹底していくべきでした。

mysqlに移行して何がよくなかったのか

単純に移行に時間がかかりました。 やった作業と時間がかかったポイントとしては

  • 初期データをmysqlでいれられる状態のものに置き換える
  • 移行した際に文字化け問題に悩まされる(これは最後まで解決できなかった)
  • 移行に伴いケアレスミスを連発して、少しずつ時間を削ってしまった

以上の点で苦戦しました。

結局何をしたのか

僕達ができたこととしては、

  • dbとappのserverを分割
  • dbサーバー * 1 + appサーバー * 2の構成にする
  • dbをpostgresからmysqlにする
  • その他アプリの微修正(スコアに全く影響がなかったので割愛)

以上ができたこととなります。 複数台構成にしたので、単純にスコアが初期スコアの1000から約2倍の1900ほどの上がり successで無事11位になれました。

まとめ

結果として力不足を露呈する形となりましたが、学びは多かったです。

僕らが現職で入社した時に行った社内ISUCONではスコアは良かったもののfailに終わってしまい、 スコアが残らなかったという苦い経験をしました。 それを加味して今回は、本戦が始まる前に確実にsuccessにして終わらせよう という話をしていたのでそれは有言実行できて良かったです。 二年半前からはちょっと成長ができたのかな...?

来年は上位目指して頑張りたいとおもいます。

最後に

運営の皆様本当にお疲れ様でした! ISUCONというイベントが行われる大切さと、参加することで得られる多くの経験を知ることできました。 様々な背景をもつエンジニアの方々一つの目標に向かって競い合う機会は滅多になく、そんな有意義なイベントが 今年も開催できたのは運営の大変な努力があったおかげだと考えると、感謝の極みです!本当にありがとうございました!

ISUCON5 予選4位通過でした

社内ISUCONには何度か参加したことあるものの、社外の人も参加するISUCONには初めての参加でした。 チーム名は「chatzmers」で、弊社の2013年新卒同期の @m0t0k1ch1@rg_gs と 僕 で出場し、最終スコア 21242 で予選総合4位で通過しました。

isucon.net

無事予選突破して、先輩エンジニアの方々と本選で戦えるのが本当に嬉しいです!

チームメンバーブログ

チームメンバーのブログエントリーです。 自分のエントリーより、詳細に丁寧に書かれているのであわせて読んでいただけたら幸いです。

前日までにやったこと

ISUCON予選前までに、メンバーで集まってISUCON4の問題にGCPを使って挑戦することをしていました。 主な目的としては、実際にやってみてGCPに慣れることと、 予選で使うであろうミドルウェアまわりの設定の方法やツール等の使い方を予習することでした。

僕は2回あるうちの1回しか練習に参加できなかったので、前準備はあまり力に慣れなかったけど @rg_gsが予選で使うであろう自動デプロイのツールつくったり、supervisorの設定をおぼえる、リポジトリつくるなどし、 @m0t0k1ch1がGazellekataribe, pt-query-digestを使ってみたりなどをやってくれて頼もしかったです。 詳しくは2人が、ブログ書いたらこちらにもリンクをはります。

当日やったこと

僕らが選択した言語はみんな大好きPerlでした。 そして最初に

  • alp, pt-query-digest etcを使って遅いと思われる箇所を洗い出す (@m0t0k1ch1)
  • systemdの設定 (@rg_gs)
  • アプリのコードを完璧に理解する(自分)

をやってみて、「あーボドルネックだらけだな」ってなったので、 おもに重かった、//friends/footprints/entriesを すごいことをやろうとせず地道に直そうという方針で対応していきました。

主に自分がやった修正は一番多く叩かれてその上遅かった/のまわりのコードの 改修で、 自分がした修正で効いたーってなったのは以下でした。

  • friendsは数しか必要ないのでcountするようにする
  • LIMIT 1000で取ってきている処理をよしなに直す
  • entriesはページ上でcontentを使ってないので、titleだけ取ってくるようにする

これらの修正でスコアが伸びて1位に躍り出て「これがISUCONか」と鳥肌たちつつ、わいわいしてました。 f:id:Maco_Tasu:20150928185552p:plain

(その後抜かれたのは言うまでもないです)

他にもN+1問題を直してみたりといったアプローチを入れてみたものの、 クエリの実行計画かわって逆に遅くなったりなどあり思うように伸びず。 少し直してはベンチ回して検証して、良かったらmasterへmergeということを地道にしていました。

@m0t0k1ch1は事前準備の知識を活かしながら

  • InnoDB => MyISAM変更
  • Gazelleを使うようにする
  • userのデータはプロセスキャッシュに乗せてN+1の箇所でクエリが飛ばないようにする
  • friendsはoneだけつかって取ってくるようにする
  • Nginxで静的ファイルを返すようにする
  • etc...

をやっていました。 Gazelleに変えた変更や、MyISAMに変えた変更でだいぶスコア伸びててさすがって感じでした。 (事前準備のかいあってシュッと変更していた@m0t0k1ch1もすごかった)

@rg_gsは主に

  • 自動デプロイの設定
  • footprintsで変なGroupByをしているクエリを適切にindexはりつつ直す
  • entryのコメントの数をRedisを使ってうまくできないかチャレンジ

などをしてくれていました。commit&pushしたら自動でデプロイしてくれるなどの地盤作りがあったおかげで 本当に快適ベンチ回すことができました。最高です。 また、entryのcommentの数をRedisに入れるのは地味に苦戦して、予選当日はつらーってなってたけど 先程@fujiwaraさんisucon予選のエントリーブログを見て aofを使ってて、そういう手があるのかーってなりました。さすがです...orz

それぞれがやったことベースでまとめましたが、時系列順にどう対応していったかはきっと他のチームメンバーがまとめてくれるはず...。 あと書き忘れありそう

やり残したこと

  • entryのbodyがだいぶ大きいので、 gzip圧縮したら結構スコアが上がったんじゃないだろうか(予選中は思いつかなかった)
  • CPUを使い切ることができなかったけど、ボトルネックは他にどこにあったのか

この2つができなかったので、またベンチかけれるなら挑戦してみたいです。

最後に

今回一緒のチームで参加した同期は、入社した時に社内ISUCONで皆FAILをするというつらいおもいを経験していたこともあり、 事前準備で予習と対策をたて、予選に臨んだことが結果としていい方向に事が運んだかなといった感じでした。 また予選前に、弊社の先輩エンジニアの@tkuchikiさんがalpというLTSVで吐かれたログをよしなに集計するツールを作っていらっしゃったので、そちらを使わせていただきシュッと遅いAPIを手軽に探ることが出来ました。ありがとうございます!

最後にこんなにやっていて楽しいと思える問題と、それに挑戦する機会をくださった運営の皆様、本当にありがとうございました!お疲れ様でした!

本選も頑張ります🙏

DateTimeオブジェクトそのまま比較するのと、DateTime->epochで比較するのどっちが速いか

ふと気になったのでベンチマーク取ってみた。

ベンチマーク

#!/usr/bin/perl

use strict;
use utf8;
use warnings;

use DateTime;
use Benchmark qw(:all) ;

my $now_1 = DateTime->now;
my $now_2 = $now_1->clone;
my @times = ($now_1, $now_2);
my $today = DateTime->today;

cmpthese(0, {
    'datetime' => sub {
        [grep { $_ >= $today } @times];
    },
    'epoch' => sub {
        [grep { $_->epoch >= $today->epoch } @times];
    },
});

1;
MacoTasu% perl -Ilib datetime-vs-epoch.pl
             Rate datetime    epoch
datetime  32462/s       --     -84%
epoch    205263/s     532%       --

epoch is faster than datetime !

なんとなく予想はできていたけど、epochのほうがやはり速かった。 性能差約5倍かな。

比較の方法

そもそもDateTimeオブジェクトとかDateTime->epochの比較とかどうなってるんだってばよ ってなったのでちょっとだけコードみた。

DateTime->epoch

epochの実装をみると

package DateTime;

...

sub epoch {
    my $self = shift;

    return $self->{utc_c}{epoch}
        if exists $self->{utc_c}{epoch};

    return $self->{utc_c}{epoch}
        = ( $self->{utc_rd_days} - 719163 ) * SECONDS_PER_DAY
        + $self->{utc_rd_secs};
}

のようなになっていて、utc_rd_*などに日にちと秒数が1970年1月1日までの経過日数を引いてそこに1日分の秒数をかけて秒に変換している。 単純に数字の変換の処理をして、その後比較してるだけなので速い。

Datetimeオブジェクトそのまま

Datetimeオブジェクト同士の比較は、闇っぽくてoperatorをoverloadしている。 ゆるふわ脳なので、なんかPerlがよしなに比較してくれてるんだろと思ったけど、頑張ってたのはDateTimeのモジュールでした。

比較しているところは

package DateTime;

...

sub _compare_overload {

    # note: $_[1]->compare( $_[0] ) is an error when $_[1] is not a
    # DateTime (such as the INFINITY value)

    return undef unless defined $_[1];

    return $_[2] ? -$_[0]->compare( $_[1] ) : $_[0]->compare( $_[1] );
}

sub _compare {
    my ( $class, $dt1, $dt2, $consistent ) = ref $_[0] ? ( undef, @_ ) : @_;

    return undef unless defined $dt2;

    if ( !ref $dt2 && ( $dt2 == INFINITY || $dt2 == NEG_INFINITY ) ) {
        return $dt1->{utc_rd_days} <=> $dt2;
    }

    unless ( DateTime::Helpers::can( $dt1, 'utc_rd_values' )
        && DateTime::Helpers::can( $dt2, 'utc_rd_values' ) ) {
        my $dt1_string = overload::StrVal($dt1);
        my $dt2_string = overload::StrVal($dt2);

        Carp::croak( 'A DateTime object can only be compared to'
                . " another DateTime object ($dt1_string, $dt2_string)." );
    }

    if (   !$consistent
        && DateTime::Helpers::can( $dt1, 'time_zone' )
        && DateTime::Helpers::can( $dt2, 'time_zone' ) ) {
        my $is_floating1 = $dt1->time_zone->is_floating;
        my $is_floating2 = $dt2->time_zone->is_floating;

        if ( $is_floating1 && !$is_floating2 ) {
            $dt1 = $dt1->clone->set_time_zone( $dt2->time_zone );
        }
        elsif ( $is_floating2 && !$is_floating1 ) {
            $dt2 = $dt2->clone->set_time_zone( $dt1->time_zone );
        }
    }

    my @dt1_components = $dt1->utc_rd_values;
    my @dt2_components = $dt2->utc_rd_values;

    foreach my $i ( 0 .. 2 ) {
        return $dt1_components[$i] <=> $dt2_components[$i]
            if $dt1_components[$i] != $dt2_components[$i];
    }

    return 0;
}

こんな感じの実装になっていたところまで確認したけど、不思議なことに途中からの記憶が無い。

結果

DateTimeオブジェクトを使った比較をする時は、DateTime->epochで比較したほうが速いのでいいのかもしれないですね。

参考資料

Dave Rolsky / DateTime-1.20 - search.cpan.org

追記(9/1)

とのこと疑問点をいっちー先生があげいてたのでキャッシュっぽいところコメントアウトして回してみたところ

MacoTasu% perl -Ilib datetime-vs-epoch.pl
             Rate datetime    epoch
datetime  34357/s       --     -81%
epoch    178069/s     418%       --

以上の結果になりました。ちょっとだけスコア落ちたけどそれでも約4倍ぐらいはでました。

commitにhookして、gofmtを実行する

golangっぽいコードに整形してくれるgofmtをcommit直前に実行し、 gofmtの結果追加の差分がでたら、1度commitを取りやめるようなcommit hookを書きました。 gofmtは差分があってかつ、拡張子が.goのファイルにのみ実行されます。

デモ

f:id:Maco_Tasu:20150822184311g:plain

コード

gist.github.com

このコードを.git/hooks/pre-commitに書いておけば、commit直前にhookされるようになります。

最後に

複数人でコードを書くような場合に、共通のhooksとしてそれぞれ設定おけば、 必ず整形されたコードのみがcommitされる良い状態になるのかなと考えて書いてみました。

※エディタをきちんと設定しようっていう話で済む気もしますが、別の試みとしてやってみた感じです。