直感に頼る

ロジカルシンキングは好きなんだけど、意図的に「論理的に正しい」手段より、直感でうまくいきそうな手段を採用することがままある。こうしたほうが間違った意思決定を避けることができると思うからだ。

たとえば「今マンションを買ったほうがめっちゃお得!」みたいなセールストークを受けた時などにこういう感覚を働かせている。 こういうセールストークをする人は、矛盾の無いように理論武装してるので、その場で論理的矛盾を着くことはできないこともありうる。

そういう時に論理的な正しさをよりどころにしていると、毎回マンションとかホーム家電とか新しいスマホとかを買わないといけなくなってしまうが、それはコスパの悪く、満足度の低い意思決定になるだろう。

なぜ論理的に正しい(そうに見える)結論を採用し続けると、正しくない意思決定に陥ってしまうのだろうか?おそらく前提に見落としがあったり論理展開に瑕疵があったりするのだろう。前提に全く見落としがないということを保証するのはほとんどのケースでできないので、間違った意思決定に繋がりうる。

おそらく論理的思考は、ある仮説の正しさを短時間で確度を高く評価したい時には向いているが、正しそうに見える論理が本当に間違ってないかを検証するのには向いてない。長期的な未来予測はほとんど当たらない。それは実は重要な前提の考慮漏れがあるからなのだろう。

それでも僕たちは日々意思決定をする必要がある。 僕はその確度を高めるためには論理的思考ではなく、直感に頼るほうが良いと考えている。そのほうが質の良い意思決定につながると考えている。

「マンションを買ったほうがいいという意見は正しそうに見えるけど、実際に運用することを考えるとローン返すことや、抵当権設定の処理や、住宅ローン利率の変動や地震などの災害や、考えないといけないことが増えて面倒そうなんで買わない。」というような感覚だ。

ある一面だけ見ると正しそうに見えるけど、それが本当にワークするのか?ということを想像することで、「論理的に正しそうに見えるけど間違った意思決定をしてしまってどうしようもなくなる」という事態を避けられることが多いと感じる。

必要な前提を洗い出して、論理に瑕疵がないなら、論理的に正しいことは必ず正しい。けど、現実的な計算時間で、すべての前提を漏れなく考慮することは不可能だ。それができるって言う人は、単に前提見落とししてることを意識外に出してるだけなのだ。直感に頼る理由はコスパがいいということにつきる。

直感に頼るという戦略を採用するなら、直感を鍛えることが重要であるという結論に行き着く。 直感とは超能力ではなく、経験から導き出されるパターンマッチ能力のことだと考えている。

人間のパターンマッチ能力は優れているので、論理的な根拠は示せなくても「ヤバそう」などの感覚として感じることができる。

なので、「ヤバそう」と感じること、そう感じた時にそれを言語化して、パターン化することを意識している。 僕にとってはテレビゲームはこの感覚を鍛えるのに役立ってると感じている。

Tower Defenseやローグライクが大好きなんですが、これらのゲームはヤバそうな雰囲気を察知することや、正しい意思決定をし続けることを要求するゲームです。 これらのジャンルのゲームをするのは直感を鍛えるのにオススメです。

バリデーションするタイミングについて迷った

バリデーションを行う場面について、判断に迷うケースがあった。多数のアイテムの中から購入するアイテムを複数選ぶのだが、制限のビジネスルールがいくつかあるとする。

購入の手順は、アイテムをカートに入れていって、その後カートの内容を購入する、という一般的なカート方式。 制約には「過去に購入したアイテムは二度と購入できない(できるアイテムもある)」「特定のアイテムを購入したことがないと購入できないアイテム」「特定条件アイテムの上限数」などがある。

バリデーションを行う場面は3つある。アイテム一覧をロードする時、アイテムをカートに入れる場面、カートを購入する場面。 まずカートを購入する場面では、すべての制約を満たしているか、確実にチェックしないといけない。ここはmust。

カートにアイテムを追加するときにもバリデーションを行うことができる。そもそも追加できないアイテムは、この時点で購入できないとエラーを出してやれば、ユーザはカート購入より前に制約に気づくことができる。ここでチェックすることはそんなに難しくない。

ロード時に制約を反映させるのは、データ数と実現方法によっては難しい。 「そもそも購入対象外のアイテムは、ユーザーに見せるアイテム一覧に表示させない」というようなことをするのは、結構難易度が上がる。

RDBMSなどのストレージから抽出する際に、SQLで制約を表現しないといけないとすると、いくつか問題が出てくる。まずSQLが複雑になって、可読性が下がり、抽出に時間がかかりうる。

indexの使えない制約をかける必要が出ると、抽出時間は条件やデータ数に左右されてしまうようになり、「どんなケースでも現実的な時間内で処理が完了するか?」を判断するのが難しくなってしまう。

カート追加時、カート購入時と別の場所に同じバリデーションを実装するのも、メンテ性やチェックの難易度を上げる。

ただ、ロードするアイテムをページングして表示しているなら、SQLビジネスロジックを実装せざるを得ない。

ページングしていなくて、表示件数がそんなに多くないなら、カート追加時と同じバリデーションをロードする各アイテムにも適用してから結果を返すという設計もありうる。今回はこのケースに当てはまったので、この方針を採用することにした。(バリデーションに失敗したものを結果から除外した。)

ロード時にバリデーションを行う別の実現方法としては、データロード自体は行うが、カートに追加できないアイテムは、追加ボタンを無効にするという手法もある。この方法ならページングしていても、SQLビジネスロジックを実装しなくても対応できそう。

ニューロンのスピードで文章を書く

会社のメンバーと、アウトプットを増やす方法について相談していたところ、文章を推敲することがアウトプットを少なくしているのではないか、という仮説に行き着いた。

ではどうすればいいかを考えてみたところ、推敲できないようにして文章を書いてみればいいのではないか、という話になった。Twitterへの投稿なら定量的にできているので、それをまとまった文章を書くときにもやってみればいいのではないか、というアイデアだ。

これはなかなか効果があるかもしれない、と思う。やりなおしが効くから手直ししたくなってなかなか書けなくなるので、やり直しができない環境であれば、多くの量を書けるようになるかもしれない。

そもそもアウトプットを増やす、というのが量さえ書けばよいのか?という話もあるが、個人的には質のことを考えず、考えていることをどんどんアウトプットできればそれでいいのではないかと思う。

物事を整理して書くコンテンツにもちろん価値があるが、そこからはライブ性や躍動感が失われてしまう副作用もあると感じる。うまくまとめられていない感覚や予想といったものを文章化するとき、それをうまくまとめようとすると表現できなくなってしまうことがあると思う。

そういった不確かなことや、小さな気付きといったものをアウトプットしていきたいのだ。そのためにはアウトプットにエネルギーがかかるようではいけない。身近な誰かに話をするように、ごく小さい労力でアウトプットできないといけないはずだ。

また、文章のリズムというのもそこから生まれてくるのではないかと思う。これはまだ仮説だけど、推敲を重ねた文章からはリズム感が失われてしまうと感じる。すばやく、ニューロンのスピードで読めるような文章を書くことができるか、という実験にもしたい。

そういったわけで、Twitterに書き込んで文章を書く、ということを早速試してみている。 一つ心配なのは、推敲せずに書いた文章に、ネガティブな心情が反映されてしまわないか、ということだ。まあそこは試しにやってみて、まずかったらまた考えようかなと思ってる。

ジョエル流の不具合報告の仕方

僕は Joel Spolsky の書いた記事が好きなんですが、不具合報告の仕方を説明した記事が今までに一番役にたってるので、それを紹介します。元記事を見つけようとしたけど、見つけられなかった...

ジョエルによると、不具合報告に必要なことは以下の3つだけ。

  • 期待していた挙動
  • 実際に起ったこと
  • それを再現する手順

これらを報告するだけで十分な不具合報告になる、という話です。

これを実際に実践してみると、確かにすごく使えるルールだと感じます。逆にこれらの情報が満たされていないと、バグ修正が大変になってしまう。

あと、このルールが洗練されてるところがすごく好きですね。必要なものは入っていて、かつ不必要なものが何もない。実に美しい。

このルールを利用して、自分たちの組織での不具合報告のルールを作ることもできる。

また、不具合報告にこれらのどれかが含まれてなければ、「再現手順をください!」とお願いすることもできる。

ここに含まれてないものは、不具合報告には必要ないのでは?という議論もすることができる。たとえば不具合が起きる法則みたいなものは、特に不具合報告になくても困らないけど、テスターさんによってはこういうのをがんばって法則を見つけようとしたりするので、それはしなくてもいいのでは、みたいな話ができる。法則を見つけてもらうよりは、再現手順を一つ報告してもらえば、不具合の原因が判明すれば、法則や影響範囲もわかるので。でも再現手順はできるだけ簡単に再現できるものを見つけてもらえるとありがたいですね。

今回これを書いたのは、これを忘れてバグの発見に手間取ってしまったんですが、ジョエルのルールに従って、再現手順を聞いておけばすぐに解決できたなー、という事例があったので共有しました。

団結力と結束力

Effective DevOps の毎日読書会を最近ヌーラボさんとしています。 今日は 9.3.5 チームの団結力 という章を読んだんですが、この章では「団結力」と「結束力」という言葉が使い分けられてます。

これらの使い分けはこの本の中での使い分けだと思うので一般性はないと思いますが、以下のように意味づけされていました。

団結力: チームメンバーがチームに貢献したいと思う度合い

結束力: チームメンバーを一つに束ねる力(共通の関心、利益)

団結力があると、メンバー間の信頼感があり、知識の共有が進み、共感が捗る。団結力はあるほうが良い。 団結力を高めるために結束力を利用できるが、結束力が高まりすぎるとエスノセントリズム(選民意識っぽい感じになってチーム外部に排他的な態度を取るようになる状態)になってしまってよくない。

エスノセントリズムはチーム間の協力ができなくなってdevops的にマイナスなので、結束力を極端に高めないほうが良い、とのこと。

「「読まなくてもいい本」の読書案内」の感想

確かAmazonで評判が良かったか何かで知ったので読んでみた。

内容は多方面にまたがっていて、刺激的な話ばかりだったのでスラスラと一気に読めてしまった。 知らなかった内容も多くて、今後興味を向けたい分野の参考にもなった。

話題はいくつかのグループに分かれていて、なんでそういうグループ分けにしたのかはわからない。ただ、それぞれの話題で共通する話題もあり、入口から入って最後まで進んでいくのに混乱しない構成であるのは確か。

自分の中で面白かった話題をいくつか挙げていく。

マンデルブロについての話

マンデルブロという名前はフラクタルアルゴリズムで絵を描くときに聞いたことはあったけど、具体的にどういう人なのかは知らなかった。

実は複雑系やカオス理論といわれる分野で業績を残していた人だったらしいです。世の中の様々な事象正規分布になっている、というのがよく言われますが、これでは説明できないことも多い。代わりにべき分布という考え方を導入して、その問題を解決したんだそう。ロングテール現象もべき分布なら説明できる。統計や複雑系の分野は今でもすたれてないので、勉強してみる価値がありそうだと思った。

ゲーム理論

ゲーム理論についても、基本的な考え方については勉強したことがあったけど、よくわかってなかった。それが非常に強力な考え方であることや、いろいろな分野に応用され続けていることなどを知れたのはよかった。これも勉強する価値がありそう。

政治体制について

政治体制の位置関係を知れたのもよかった。政治体制というのは人類が進化してきた結果として発達してきたもので、大きく分けて3種類の立場がある。平等主義リベラリズム自由主義リバタリアニズム共同体主義コミュニタリアニズム。それ以外に進化の過程から出てきたのではない功利主義という政治体制がある。

これらの立場はMECEになってるぽくて、今までの人生で目にしてきた政治体制は確かにこれらのどれかに含まれる感じだ。なのでどういう主張が出てきたとしても、これらのどこに重点を置いた主張なのか、ということを考えれば分類して特徴を知ることができてしまうので、非常に強力そう。

面白いのは、進化の過程で出てきた3つの立場の主張は人間の本質的な欲求なのだけど、それら全てかなえる政治体制というのはありえないということが論理的に言えてしまう。この本はその点を明快に説明している。なので、それぞれの立場で妥協をしないといけないのだけど、それぞれの幸福度を最大にして妥協するにはどうしたらいいか、ということを考えるのが功利主義という考え方。 どういうトレードオフにするのが最も幸福度が高いか、というのは頭のいい人なら日常的にやっていることだけど、それをつきつめて考える主張なので、功利主義というのは頭のいい考え方なことは間違いない。

意識について

ほかには無意識と意識に関しての実験の話が面白かった。僕らは常日頃、自分の行動は自分の意志の結果として行動している、と考えているけど、実験の結果、僕らが行動する時には意識の介在する余地はあまりなく、だいたい無意識に決定を下しているということがわかったそう。

面白いのは無意識に選択した結果行った行動について、なぜそうしたのかを質問すると、ありそうな理由をでっちあげるという点。僕らの行動は自分の意志で行ったものじゃなくて、無意識が判断して行っている。じゃあ僕らの意識が何をしているのかというと、あとづけの理由を考えて、自分の行動に矛盾がないと信じ込ませていること。僕らは自分にうそをつきつづけて、自分を納得させ続けている。 だとするなら、自分の行動の理由を深く考え続けることにはそんなに意味はない、といえるのではないだろうか。犯人探しや反省をやりすぎても意味がなくてそれよりは、そう選択した原因と、次はどうするのがいいかを考えることのほうが有意義そうだ。

最近の科学の流行を知るのに良い本

いくつかの分野について、パラダイムの歴史を知ることができるので、科学の歴史を俯瞰するのには役立つ本だと思う。 ただ、科学は現在進行形で進み続けているものなので、5年後、10年後にはそれらの成果を踏まえてアップロードする必要があるだろう。なのでこの本は今読んでおくことに価値があると思う。

また、本書では筆者のおススメの分野も紹介されているが、今後何を学んでいくかは読者が選択する自由がある。内容を妄信するのでなく、自分の選択の参考にする、というスタンスで接するのが、本書との良い付き合い方かなと思う。

ISUCON5予選でスコア19450出しました

サーバチューニングの勉強になればとISUCON5の予選に参加してみたところ、予想外に予選通過できてしまいました。 初めてISUCON参加してみての感想を一言で言うと、めちゃくちゃ疲れるけど楽しかったです。 参考までに、僕が行った作業内容を書いてみます。

ベンチマークの実行を成功させる

まずベンチマークを実行できるところまで持っていきます。ここまではなんとかがんばるしかないですね。

mysql, nginx,unicornの設定チューニングする

前回参加者のブログを参考に、あらかじめ準備していたチートシート通りに設定しました。 systemdの使い方がわからなくてサービスを再起動方法をググったりして、わりと手間がかかりました。

設定内容は以下のとおり。

my.cnf
innodb_buffer_pool_size = 2G
innodb_flush_log_at_trx_commit = 2
nginx.conf
worker_processes  5;
worker_rlimit_nofile  4096;

  log_format  ltsv  'host:$remote_addr\t'
                    'vhost:$http_host\t'
                    'port:$server_port\t'
                    'time:$time_iso8601\t'
                    'method:$request_method\t'
                    'uri:$request_uri\t'
                    'protocol:$server_protocol\t'
                    'status:$status\t'
                    'size:$body_bytes_sent\t'
                    'referer:$http_referer\t'
                    'ua:$http_user_agent\t'
                    'forwardedfor:$http_x_forwarded_for\t'
                    'forwardedproto:$http_x_forwarded_proto\t'
                    'apptime:$upstream_response_time\t'
                    'reqtime:$request_time';

  #access_log off;
  access_log /var/log/nginx/access.log ltsv;
unicorn

プロセス数を5に

プロセス数はあとで増やすなど試してみたんですが、効果がなかったので5に戻しました。 ログ解析をするために、ltsv形式で出力しようというのは事前に決めてました。

ここまでである程度スコアが上がったのを確認して、次に戦略を立てるためにログ解析の準備をしました。

ログ解析の準備をしてログ解析する

sonotsさんが前回のISUCONでltsvのログをスクリプトで整形しと書いてたので、このアイデアをそのまま使わせてもらいました。

https://gist.github.com/sonots/9ae0167d31d7f4b42c9a

実際の作業ではボトルネックになってる箇所を見やすいように、URLをグループごとに見れるように、スクリプトを調整しました。

あとはアクセスログを退避してクリアするスクリプトも作っておきました。 これでベンチマークを動かすごとに、詳細なログ解析が得られるようになりました。 これを見てると / へのアクセスの処理にほとんどの時間がかかってて、静的ファイルへのアクセスはほとんど影響してないことがわかりました。なので今回の問題は、静的ファイルを素早くさばくことよりアプリケーションの処理を高速化するのが効果的だろうと判断しました。 このログ解析の仕組みは最後までチューニング方針の決定に役立ってくれたので、準備しておいたかいがありました。

アプリケーションのチューニング内容

アプリケーションのチューニングでやったことは、とくに大きな構成の変更はせず、参考実装のボトルネックを潰していった感じです。 N+1問題を潰したり、mysqlのindexを追加して行ったりしたら順調にスコアが上がっていってくれました。 今回の課題はアプリケーションのボトルネックを潰すのがメインだったので、アプリケーションエンジニアの自分にとっては普段やってる仕事と同じような感じで作業していけました。

反省点

  • shellやエディタが普段の環境とちがうまま作業してしまって非効率でした。dotfileをgithubに置いておいて適用したほうがよかった。
  • N+1問題が起こってるところをgrepで見つけていたのが良くなかったので、N+1問題を検出する仕組みを準備しておくべきだった。
  • kazeburoの術によるとnginx設定はまだ足りないものがある感じ。( https://gist.github.com/sonots/62179703d429d36b1a57 )本選までに勉強しておこう。

良かった点

  • ログ解析ツールが役に立った stat.rbは役に立った。実行結果を分析できるので、効率的にボトルネックに対処できた。
  • mysql, nginxのチートシートは役に立った
  • 予習したおかげで、設定ファイルの場所などで戸惑わずにすんだ。

作業ごとのスコアの遷移を掲載しておきます。 だいたいアプリケーションの修正とindexの追加に時間を使っていて、順調にスコアが上げれてましたね。

12:00くらい
初回ベンチマーク実行
=> スコア 146

12:30
* nginx worker process を5に
* unicorn worker process を 5に
=> 881

12:35
my.cnf修正
innodb_buffer_pool_size = 1G
innodb_flush_log_at_trx_commit = 2
=> 889

13:01
my.cnf修正
innodb_buffer_pool_size = 2G
=> 911


13:12
vimrcをいつも使ってるサーバからコピペ。vimプラグインは面倒なので持ってこなかった。

13:30
ログを解析したところ / に時間がかかってることがわかったので /を高速化する

13:40
footprints, relationsにindex追加
=> 2248

14:19
is_friend? をone -> anotherの向きのみ見るように修正
=> 2762

14:30
tail -f してたのをやめた
=> 2911

14:55
friend_idsを最初に取っておくように
=> 4352

15:06
comment取得にもfriend_idsを使うように
=> 4828

15:43
comment取得をsqlだけで取得するように。commentsにindex追加
=> 12413


16:04
/friends のSQLをor使わないように修正
=> 12432

16:17
/friendsをリファクタリング
=> 12480

16:26
nginx, unicornのworkerプロセスを5 -> 10に
=> 12313
効果ないので8に変更
=> 
効果ないので5に戻した

16:55
footprintのindexをuser_id, owner_id, created_at に変更
=> 12460

17:08
一部erubis => slimに変更
=> 見た目には正しく変換できてるように見えるがbenchmarkerでエラーになるので中止。

17:46
トップページでfriendsがcountしか使われてなかったので、sqlでcountを取得するように修正
=> 13145

18:06
get_userが何度もviewから呼ばれてるので、開始時に取得するように修正(途中)
=> 14468

18:17
viewからentryを何度もSQLで取得してたので、一括して取得するように修正
=> 15476

18:31
/entriesでコメント数を取るために何度もSQL実行してたのを一括して取得するように修正
=> 16704

18:45
/footprints, /friendsでget_userを何度も実行してたのを一括して取得するように修正
nginxのaccess log をoffに
=> 19450