YAPC::Kansai 2017 OSAKAに参加した

YAPC::Kansai 2017 OSAKA

オーディエンスとして参加したので聞いたトークのメモなど。

Webアプリケーションのキャッシュ戦略とそのパターン (40min)

speakerdeck.com

主に Web アプリケーションの世界におけるキャッシュの話。目新しい話題ではないけれど、キャッシュは本質的にむずかしい話題だし、よくまとまった内容なのでこれからキャッシュの戦略を考える際の議論に役立ちそう。

「パターンに名前を与えると、名前を使って認知し議論できます」という話がよかった。確かにどれもよくあるパターンだけど、普段会話するときは「get_or_set 的なやつで……」みたいな表現をしがちだったので、名前をつけることだけでもとても意義のある内容だと思う。

後日 id:moznion さんと個人的に会話して Varnish の話をするなど、発表をもとに会話が生まれる技術カンファレンスの原体験みたいなことが起きて、そういう意味でもいいトークだったなーと思う。

いい話聞けたし、最近自分も Varnish を触っているので YAPC::Fukuoka 2017トークに応募しようかな、と思った。

Perl でわかる!サーバレス (20min)

www.slideshare.net

別のところ (社内だったかも?) でも「ServerlessってつまりCGIじゃん」みたいな話を聞いたので、静かに普及しつつある見解っぽい。

Microsoft Azure Functions のランタイムが公開されているところは後発ならではでよいなと思った。

利用しているBaaSが終了するときにすべきこと または Parse.com の終了と私たちの取り組み (20min)

speakerdeck.com

parse.com 終了は対岸の火事だったけど、このトークを聞きながら何度か自分のことのように肝が冷えることがあって、臨場感のあるトークだった。

撤退の作戦を考える際に DNS のレイヤからアプリケーションのレイヤまで検討できるだけのエンジニアがいるチームだからこそ切り抜けられたのかもなあ、と思った。小さいチームっぽいけど、いいエンジニアが集まっているチームという印象を受けた。

あとこういう内容だとともすればヘイトばかり撒き散らしがちだけど、ケーススタディとして具体性があって役立ちそうだし、parse.com へのリスペクトを欠かしていなくて建設的で聞いていて安心感があった。

カヤックのゲーム開発・運用の「今」 力技と効率化の先に我々が目にしたものとは (40min)

speakerdeck.com

Google スプレッドシートプログラマブルであるという意味でエンジニアにも受け入れられていて、良くも悪くも「エクセル」はもはや表計算ソフトではなくて「エクセルなんだなあ……とか思った。

イベントでものすごいスパイクがやってくるとか、カラムにサイズの大きなデータを入れていて転送量が逼迫するとか、心当たりのあるできごとが多くて他山の石感があった。

マスターデータとコードでは、コンフリクト解決の方法や頻度が違うので別リポジトリにしているのはなるほどってかんじ。

はてなシステムの考古学 (40min)

speakerdeck.com

15年の歴史をご紹介という内容。

こうして俯瞰してみるとコンウェイの法則のお手本みたいだなーと思った。人が増えてきて効率化のために Ridge のようなフレームワークが生まれ、さらに増えて人の多様性が増すと実装としてのフレームワークが薄まる揺り戻しが起きる、というような。

感想

気付いたときには懇親会のチケットが売り切れていて、それだけが個人的に残念であったものの、イベントとしてかなり濃い Perl トークが多くて楽しかった。

次回は福岡で、温故知新にかけて未来をテーマとのこと。福岡観光したいしトーク応募考えます。

Git であるディレクトリ以下に最近追加されたファイルを見る

git log \
  --grep 'Revert' --invert-grep \ # revert コミットを除く
  --diff-filter=A \ # 新規追加のみ
  --format='' --name-only \
  -- lib/Plack/Middleware

lib/Plack/Middleware/ 以下に最近ファイルを新規追加したコミットの一覧:

lib/Plack/Middleware/IIS7KeepAliveFix.pm
lib/Plack/Middleware/IIS6ScriptNameFix.pm
lib/Plack/Middleware/LighttpdScriptNameFix.pm
lib/Plack/Middleware/Head.pm
lib/Plack/Middleware/Recursive.pm
lib/Plack/Middleware/HTTPExceptions.pm
lib/Plack/Middleware/BufferedStreaming.pm
lib/Plack/Middleware/Dechunk.pm
lib/Plack/Middleware/Log4perl.pm
lib/Plack/Middleware/LogDispatch.pm

Vue.js で分割払いの手数料をわかりやすく表示するページを作った

Card Payment

リポジトリ: playground/card-payment at gh-pages · aereal/playground · GitHub

実質年率って意味わからないし計算がめんどうなので、金額と支払い回数を入れると総支払い額と月々の支払い額を表示するページを作った。

普段自分が使うブラウザで動けばいいので arrow function を生で使ってままで、トランスパイラを使って ES5 に変換していない。

Vue.js はライブラリを読み込んでさっと使いはじめられてよい。

UIKit という CSS ライブラリを試してみた。Bootstrap と同じくらいの範囲をカバーしつつも、その名の通りフォームとか操作するためのモジュールがたくさんある。

クラス属性を使ったり `uk-grid` のようなカスタム属性を使ったりと節操がない印象。

分割払いしまくりたいわけではなく、むしろカジュアルに手数料の高さなどを確認して戒めようというもの。

……X-T2 ほしい。

Go の text/scanner で改行をトークナイズしたいときは

Go の text/scanner はレキサを書く面倒な仕事の大半をいいかんじにしてくれて本質的なコードに取り組みやすくとても便利。

しかしはてな記法Markdown のように、行全体が単なるテキストではなくいくつかのパターンからなる文法のレキサを書くときは、パターン中に明示的に改行を含めたい。

どういうことかというと:

line: inlines CR

みたいに書きたい。

しかし scanner.Scan が改行を含む空白文字をスキップしてしまうので改行をトークナイズする、ということができない。

結論からいうと Scanner.ScanScanner.Whitespace というフラグを見て空白文字かどうか判断するので、このフラグを変えて改行は空白文字ではないと教えてあげるとよい。

(よく読めば Scanner.Whitespace にコメントで説明が書いてあった)

実際のコード:

Support inline HTTP annotations by aereal · Pull Request #2 · aereal/go-text-hatena · GitHubn

WANDRD Prvke Pack を買った - カメラバッグ考2017

PRVKE Pack というバックパックを買ったのでそれの紹介と、バックパックを選ぶにあたっていろいろ比較検討したメモについて書いておく。

新たなバッグに求めること

  • 素早くカメラにアクセスできること (サイドアクセス)
  • フルサイズ DSLR とレンズが入ること
  • 見た目

昨年、フルサイズの PENTAX K-1 に買い替えたうえ、さらに DFA☆ 70-200mm F2.8 という望遠ズームレンズも買い足した。

望遠ズームは大きいし重いので、これに加えていくつかのレンズを入れて持ち歩けるだけの容量のあるバッグを欲しくなったというのが、そもそものきっかけ。

機材が増えて多少重くなっても体に負担がかからないようバックパックを求める一方、レンズ交換が手間にならないようカメラやレンズの気室が分かれていることも条件とした。

他に検討したバックパック

Google Spreadsheet に諸元などをまとめて比較し、PRVKE Pack に加えて以下を最後まで検討した:

  • Chrome Niko Pack
  • ZKIN Raw Yeti
  • Thule Covert DSLR Rolltop Backpack

Chrome Niko Pack は要件をほとんど満たしていたものの容量が少なかった。 24L くらいで、実物を見るとかなり小さい印象だった。

ZKIN Raw Yeti は多少重いこと以外ほとんどよかったけれどいかんせん高すぎる。 ヨドバシで7万くらいして、レンズ買えるじゃん……と思って候補から落ちた。

Thule のバッグはだいたいよかったけれど見た目がやぼったくて実物を見るとあまり欲しくなくなった。

WANDRD PRVKE Pack

カメラと交換レンズを入れて撮りに出かける時のためのバックパックとして PRVKE Pack を選んで買った。

1ヶ月半くらい比較検討してみて求める要素をすべて持っていたので購入に至った。

Kickstarter で資金を集めながらバッグを作っているアメリカの小さなメーカーで個人輸入するしかなく、実物を手に取る機会がなかったのは若干不安だった。 その分、レビュー記事や YouTube の動画を通してサイズ感を念入りにチェックした。

ただほとんどのレビューは自分と比べると大柄な男性であることが多くて背負った印象はあまり当てにならなかった。 その点では SLRLounge のレビューに載っている写真は、レビュアーが女性だったので身長は近しく、レビュー本文も充実していてだいぶ有益だった。

価格は270米ドルで、PayPal 経由で決済した。当時のレートでは33,996円になった。 他に検討していた候補より少し高かったけれど、他はどれも求めるところにあと一歩足りない印象だったのでほとんど選択肢はなくなっていた。

気に入っているところ

  • サイドからカメラ用気室にアクセスできる
  • 手持ちのフルサイズと交換レンズを入れられるだけの容量がある
  • 加えて着替えなども入れられる容量がある
  • 荷室に背中側からアクセスでき荷造りしやすい
  • 見た目

公式では目安としてフルサイズの DSLR とズームレンズが2〜3本入るよと言っており、実際にそれくらいのサイズ感だった。 今の手持ちレンズで大きいものは DFA☆ 70-200mm F2.8 と DFA 15-30mm F2.8 で問題なく収まる。

一般にバックパックは中身を取り出すのに手間がかかるけれどもレンズ交換に手間はかけたくないのでサイドアクセスができると便利。

さらに飛行機に乗るときはなるべく受託手荷物を無くしたいのでバッグひとつで搭乗したい。

そうするとカメラやレンズに加えて着替えなども収められるだけの容量がほしい。 とはいえ数泊程度で、かつ下着や肌着くらいで済ませるので大した量ではないけれど。

clamshell-opening

あと荷室に背中側からアクセスできるためカメラやレンズを荷造りするときに楽。

いまいちなところ

だいたい満足しているけれど、強いて挙げると大きさと重さはちょっと気になる。

とはいえ普段使い用にはこれまで使っていたバッグも使うし、もっぱら旅行やレンズを持ち出したい時用のつもりなので、現実的には許容できる。

あと価格がちょっと高かった。2万円代だったらなあ、と思うけれどもう買ってしまったのでどうでもよくなった。

セットアップスクリプトでは必ず rbenv exec/rbenv which したほうがいい理由

簡単に言うと、rbenv の shims はシェルの `PATH` 探索とは独立して実行ファイルを探索しているため。

以下、単に rbenv としているけれどクローンである plenv なども同様と考えてよい。

shims とは

rbenv の README が詳しい。

Shims are lightweight executables that simply pass your command along to rbenv. So with rbenv installed, when you run, say, rake, your operating system will do the following:

  • Search your PATH for an executable file named rake
  • Find the rbenv shim named rake at the beginning of your PATH
  • Run the shim named rake, which in turn passes the command along to rbenv
GitHub - rbenv/rbenv: Groom your app’s Ruby environment

シェルは shims だけを探索する。

shims を実行すると rbenv によって Ruby のバージョンごとの実行ファイルが探索され、見つかった実行ファイルを `exec` する。

セットアップスクリプトの例

アプリケーションのセットアップを自動化するために以下のようなスクリプトが添えられていることが多いと思う:

#!/bin/bash

set -e

which bundle || gem install bundler
bundle install

Bundler がなかったら入れて、 `bundle install` で依存ライブラリを入れて終わり。

`which(1)` が成功するけど `rbenv which` が失敗する例

そんなことがあるのかと思うけれど、shims のライフサイクルを考えるとありうる。

rbenv で管理している Ruby のいずれかで Bundler をインストールした時点で `bundle(1)` の shim が作られる。

一方で、各バージョンごとの実行ファイルの実体は、各バージョンにおいてインストール (gem install) しないと存在しない。

つまり以下のようになる:

rbenv shell 2.0.0
rbenv exec gem install bundler
which bundle # => ~/.rbenv/shims/bundle: この時点で shim が作られる
rbenv which bundle # => ~/.rbenv/versions/2.0.0/bin/bundle: 実体も作られる

rbenv shell 2.4.0
which bundle # => ~/.rbenv/shims/bundle: 既に 2.0.0 でインストールし shim は作られている
rbenv which bundle # => (fail): 2.4.0 ではまだインストールしていないので存在しない!

rbenv exec gem install bundler
which bundle # => ~/.rbenv/shims/bundle: 既に 2.0.0 でインストールし shim は作られている
rbenv which bundle # => ~/.rbenv/versions/2.4.0/bin/bundle: めでたく 2.4.0 に対応する実体もインストールされた

先に紹介したセットアップスクリプトは `which bundle` で shim が探索されて見つかってしまうので `gem install bundler` が実行されないが、そのアプリケーションで使うバージョンに対応する実行ファイルの実体は存在しないので `bundle install` が失敗する、ということが起きてしまう。

そこで rbenv exec/rbenv which

どうすればいいかというと、タイトルにあるとおり `rbenv exec` / `rbenv which` を使うとよい。

これらは基本的に `exec(1)` `which(1)` の shims を考慮するバージョンと考えてよいので単純にコマンドに前置するだけで対応はほぼ完了する。

まとめ

  • rbenv ないし rbenv のクローンを使っているときは `rbenv exec` `rbenv which` を使う

Perl で memcached を使うときは Cache::Memcached::Fast::Safe がよさそう

memcached はコマンドインジェクションの機会が主に2つあって、1つはプロトコルで定められたキー長250文字を越えた場合と、キーに改行文字を含む場合。

詳しくは: 第2回 memcachedのセキュリティと脆弱性:memcachedの活用と運用 実践編|gihyo.jp … 技術評論社

今やっているプロジェクトでは Cache::Memcached::Fast を使っているけど、安全なキーの構築は Cache::Memcached::Fast を呼び出すレイヤでそれぞれ行うようになっている。

キー長の制約を知らなくて250文字以上のキーを使おうとしてうまく動かなくてハマったし、そもそもよく知られたセキュリティリスクがあるならライブラリレベルで対策されているとよさそう。

ということで調べたら Cache::Memcached::Fast::Safe というのがあったのでこれがよさそう。

  • Cache::Memcached::Fast のサブクラスなので同じように使える
  • キーはデフォルトでサニタイズされる
  • ついでに get_or_set という便利なやつが生えている

markdown-toc: Go で Markdown の見出しをパースして目次を作る

github.com

Go でテキスト処理をする練習のために書いてみた。

mdtoc - The markdown ToC generator - ウェブログ - Hail2u.net が元ネタで再実装というかんじ。

小難しいことをやるとどうしても泥臭くなるけど、普段書いている高水準なコードはこれくらい泥臭くてともすれば効率が悪いんだろうな〜、ということを Go を書くたびに思っている気がする。

数字で振り返った2016年

社内 LT 大会で「数字で振り返る2016年」と題していろいろ個人にまつわる数字を集めたので整えたものを遅ればせながら旧年の振り返りとします。

Amazon で購入した金額

金額 前年比
2016 389,277 円 (124.54%)
2015 312,547 円 (136.27%)
2014 229,352 円 -

カメラやレンズなどは含んでいない。主に Kindle 本や生活用品など。

年間でサラリーマンの月収として現実味のある額 *1 ほどしか使っていないと考えるかどうか。

この調子でいくと来年は45万くらい年間で使う目測だけど、そんなに成長するかな、どうかな。生活用品に使う額は減りはしないだろうけど大きく増えもしないだろう。

額の増加が鈍化すると踏んで特に考えないことにする。増加率が130%に戻ったら考える。

写真ブログに投稿した記事数

記事数 前年比
2016 73 486.7%
2015 15 13.0%
2014 115 -
  • 2015年がやばかった
    • 怪我したの
    • 自転車に乗った

2015年はロードバイクにはまったこと、小指を骨折したことが重なって激減したという背景がある。

そう考えると2016年はロードバイクにあまり乗らず PENTAX K-1 など大きな買い物をした割には2014年の6割程度で奮わない。

写真ブログは自分が発表するに耐えると考えたものだけを載せる方針なので、目が肥えてハードルが高まったかもしれない。

また、最近は組写真っぽいアプローチについて考えはじめて、1記事で複数の写真を載せることが増えたように思うので、1記事1写真が多かった気がする2014年と比べて写真数では見劣りしないかもしれない。

とにかくアウトプットの機会を増やさないことには上達しないし、気が向いたときに載せるくらいのペースだと選りすぐりを発表する場としても片手落ちなので、今年は数を重視して2014年の1.5倍まで戻したい。

写真 (カメラごと)

機種 枚数 割合 採用率
all 4,925 - 17.6%
K-1 3,642 73.9% 19.7%
K-3 1,202 24.4% 11.6%
K-7 33 0.7% 36.4%
dp0 48 1.0% -

Lightroom より。これを算出したのは12月2日だったので、実際はこれより増えている。

採用率というのは Lightroom でフラグを立てて現像し Google Photos に上げた写真の数の割合。20%弱という微妙な数字が素人っぽい。

意外とK-3が多かったなという印象だけれど3月から4月の梅・桜シーズンにまだいたのでさもありなん。

写真 (レンズごと)

機種 枚数 割合 採用率
all 4,925 - 17.6%
70-200mm 231 4.7% 12.6%
35mm 89 1.8% 23.6%
55mm 785 15.9% 13.9%
31mm 2,557 51.9% 17.9%
77mm 886 18.0% 20.1%

こちらも Lightroom より。

31mm が大好きなことが使用率からわかる。一方で採用率は目立って高くないので、いいレンズだし気に入っているけれど、あとから自分の眼鏡に敵う写真が量産できているわけではない。

77mm は少し採用率が高いようにも見えるけど有意差はない。

Strava (自転車)

走行距離

実績 前年比
2016 226.6km 19.93%
2015 1,136.8km -

時間

実績 前年比
2016 15時間47分 -
2015 70時間23分 -

獲得標高

実績 前年比
2016 6,501m 29.72%
2015 21,868m -

まあ2015年が元気すぎた。時間は有限なので、写真を撮りにいけばロードバイクに乗る時間は減るし……という当たり前のことではある。

とはいえ運動不足を解消するためにも2015年ほどと言わずとももう少し乗りたい気はする。

日記

記事数 前年比
2016 161.45 94.41%
2016 148 86.54%
2015 171 54.11%
2014 316 -

減少傾向にある。家にいる時間が減ったからだろうか。そもそも2014年はプライベートでいろいろあってずっとなにか書いていた気がするし、2015年くらいがいつもの調子だったかも。

むすび

これらに加えてアフィリエイト収入について書いた (雀の涙ほど) けれど Amazon の規約的に公開するのはよくなかった気がするので省略。

趣味のことでもいろいろ数字を出してみると発見があっておもしろい。

ここでは書いていないけれど、入社して以来、月収を額面、控除額などつぶさにしてスプレッドシートに記録していて、毎月給料日に記録するたび控除額を書き入れて暗い気持ちになるし、ここ3年の控除額の推移を見るとさらに暗くなる。

今年は光熱費などの推移も記録していきたい。今年もよろしくおねがいします。

*1:月100万までが現実味を感じられる

はてなブログのデプロイを約6倍高速化したはなし

  • 今年、稼働中のサービスであるはてなブログのデプロイ方法を新しい方式へ無事故で移行し、従来と比べて約6倍速くデプロイできるようになりました。

この記事では、安全にデプロイ方式を変えたプロセスを順を追って紹介します。

はてなブログと継続的デリバリー

はてなブログは1日あたり平均して1.02回デプロイを行っています。これは土日を除いた週5日の営業日に対する平均です。ざっくりとした算出で、祝日は考慮していません。5月と9月の祝日を含めるともう少し多くなるかもしれません。

また、原則として休日の前日にはデプロイしないことになっています。もしもデプロイした変更にバグがあった場合、休日が明けてから対応するか、さもなくば休日中に対応する必要があります。 安定したサービスの提供のためと、開発メンバーの精神衛生のためにチームで取り決めました。

デプロイが遅い

はてなブログのデプロイにかかる時間を Mackerel のサービスメトリクスとしてプロットしています。

f:id:aereal:20161213183243p:plain

従来、はてなブログのデプロイには1回あたりおよそ300秒ほどかかっていました。 なお、ここでいうデプロイとは bundle exec cap deploy をシェルで実行して各ホスト上に変更が行き渡るまでの一連の流れを指します。

1回あたり300秒ということは、デプロイした変更に致命的な不具合があって変更を巻き戻したり修正をデプロイするためにさらに300秒かかるということです。

念入りに QA を行ったり、テストを充実させたり、事前に不具合を混入させない努力はもちろん行うべきですしはてなブログでも取り組んでいますが、根絶することは現実的ではありませんし悪い事態に陥ったときのことも欠かさず考えなければいけません。

リカバーに時間がかかる状況ではリスクのある変更をためらってしまいますし、事故が起きたときの影響・損失はできるだけ小さく留めたいものです。

複雑なデプロイ設定

遅いという現状がわかったところで実際にデプロイの仕組みへ手を入れて高速化を図りたいところですが、 Capistrano 2 と古い社内ライブラリを組み合わせた上、Thread を直接使い並列実行するためにほとんど魔改造といって良いほどの状態で、手を出しかねていました。

Capistrano 2 と古い社内ライブラリ、そしてはてなブログ独自の設定と、3つの実装のかけ算により複雑さは極まっていました。

このような状態で変更を加えようとしても、良くて袋小路に入って時間を浪費してしまう程度、悪ければ致命的な不具合により障害に繋がります。

そこでまずデプロイで起きる変更の仕様をテストコードへまとめ検証可能にしました。 テストが書けた・書こうという気持ちが挫けなかったのは「何が起きているかはわかるが、どうして起きているかわからない」類のコードだったからとも思います。

デプロイのテストを書く

デプロイの仕様とはなんでしょうか? end-to-end 的に考えると「追加・変更された機能を使えるようになる」ことでしょうが、あまりに漠としてテストへ落とし込みづらいです。

これから書こうとしているテストは開発者向けなのでよりホワイトボックス的に「任意のバージョンのアプリケーションがホスト上に配置され、アプリケーションプロセスが再起動される」とします。

より細かくは:

  • 任意のバージョンのアプリケーションが配置される
  • 任意のディレクトリにアプリケーションコードが配置されている (e.g. /apps/Hatena-Blog/releases/$VERSION)
  • ワーキングディレクトリがデプロイしたいバージョンのディレクトリの symlink になっている (e.g. /apps/Hatena-Blog/current -> /apps/Hatena-Blog/releases/$VERSION)
  • アプリケーションが再起動される
  • Plack::Middleware::ServerStatus::Lite のアクセスカウンタが0になっている

……くらいに分けられそうです。

アプリケーションが再起動したか確かめる方法は悩みましたが、後述するようにテストを実行する際はホストをロードバランサから外すため、リクエストを受け付けることはないという前提を立てて、アクセスカウンタがリセットされたことを指標に据えます。

リモートのホスト上でファイルシステムなどを見てアサーションを行うくらいであれば SSH 越しにシェルスクリプトを実行するくらいでもよいのですが、自動実行し、テスト結果を収集するところまで考えるとより高水準なフレームワークを使いたいところです。

まさしくこのような用途のためのフレームワークとして Serverspec があります。

たとえば「ワーキングディレクトリ (= /apps/Hatena-Blog/current) がデプロイしたいバージョンのディレクトリの symlink になっている」アサーションは以下のように書けます:

describe file('/apps/Hatena-Blog/current') do
  it 'is a symlink' do
    expect(subject).to be_symlink
  end

  it 'is linked to releases' do
    expect(subject.link_target).to be_start_with('/apps/Hatena-Blog/releases/')
  end
end

Plack::Middlware::ServerStatus::Lite のアクセスカウンタがリセットされている」アサーションは、素朴にレスポンスボディから正規表現でアクセス数を抽出します:

describe 'total access' do
  it 'looks like restarted' do
    total_accesses = `curl -s http://#{host}:#{port}/server-status`.
      each_line.
      grep(/\ATotal Accesses:/) {|l| l[/\ATotal Accesses: (\d+)/, 1] }.
      first.to_i

    expect(total_accesses).to be < 10 # デプロイ直後は10回もアクセスがきていないだろう
  end
end

テストフレームワークの実装そのものは RSpec なので RSpec のフォーマッタがそのまま使えます。JUnit 形式の XML を出力するフォーマッタを導入すれば Jenkins でテスト結果を収集するのも簡単です。

これでデプロイを行って事後条件が満たされているか自動テスト可能な状態になりました。

次はより高速なデプロイの仕組みについて検討します。

ボトルネックの発見、そして pull 型から push 型のデプロイへ

はてなブログのデプロイのボトルネックを明らかにするため、デプロイの所要時間をより細かに見てみます。

Capistrano によるデプロイはアプリケーションコードの配布 (deploy:update) やプロセスの再起動 (deploy:restart) などいくつかのタスクに分割され、それらを逐次 (あるいは並列に) 実行していき、すべて完了したらデプロイが完了、というモデルです。

そこで各タスクごとそれぞれにかかった時間を計測し、各タスクを Mackerel のグラフ定義の1メトリックとしさらに積み上げグラフにすることで、デプロイ全体にかかった時間を詳細に可視化してみます。

参考: Mackerel のグラフ定義

文章で表現すると煩雑で伝わりにくそうですが、実際のグラフを見るとわかりやすいでしょう:

f:id:aereal:20161213183243p:plain

update_elapsed が支配的であることがわかります。 update_elapseddeploy:update の実行にかかった時間です。 実際、デプロイ中も deploy:update で待たされる印象が強かったので主観とも一致しています。ここを改善できるとよさそうです。

なぜこんなにも時間がかかるかというと、中央の Git リポジトリからアプリケーションコードの変更を取ってくるアーキテクチャになっているのですが、この Git リポジトリがスケールしないためです。

詳しくは YAPC::Asia Tokyo 2015 で発表した「世界展開する大規模ウェブサービスのデプロイを支える技術」を参照してください。

さて、Git リポジトリボトルネックとわかったのですが、他にアプリケーションコードの配布の方法を考えてみると上記発表でも触れたソースコードをひとつのファイルにまとめて HTTP でダウンロードする方法が挙げられます。

むしろアプリケーションの配置を VCS からソースコードを取得するだけで済ませるというのは、事前のコンパイルを必要としないインタプリタ言語かつ Web アプリケーションという限定的なシチュエーションでのみ実現できることで、むしろ1ファイルにまとめてダウンロードさせるという方法はより古典的でありふれているでしょう。

ソースコードをまとめたアーカイブファイルを各ホストにダウンロードさせるよう指示するデプロイを仮に push 型デプロイとしますが、この push 型デプロイで高速化できるか・スケールさせられるかは、既に上記発表にあるように検証済みなので、新デプロイ方式は push 型デプロイにすることとなりました。

次は実際に新デプロイ設定を書きます。

新デプロイへの移行

既存の設定をベースに書き換えていくこともできますが、今回はスクラッチから書いていくことにし、リポジトリもアプリケーションコードとは別のリポジトリを作りました。

そもそも既存の設定が秘伝のタレ化して行き詰まっていたという背景がありますし、古い社内ライブラリは Git リポジトリからアプリケーションコードを配布するアーキテクチャを前提とした便利ライブラリという体裁が強いので、もはや有用ではなくなります。

新たなデプロイ設定は、まずステージング環境のデプロイに導入しました。アプリケーションの開発と同じように、まずスコープを狭めて安全な砂場で試します。

次にロードバランサから外した一部のホストのみにデプロイし、前述したテストを実行し、期待する事後条件を満たすか確認します。

Web アプリケーションを実行するホストだけではなくバッチジョブを実行するホストなどでも同様に検証し、問題なくデプロイされたことを確かめた後に、サービスのホスト全体のデプロイをついに移行しました。

念入りにテストを書いたりコードレビューをしてもらってはいるものの、やはり稼働中のサービスのデプロイ方法を変える瞬間ほど緊張した瞬間はなかなかありません。

幸いなことに事前の準備が功を奏し無事故で移行でき、現在も push 型方式でデプロイしています。

結果

f:id:aereal:20161213183331p:plain

従来の方式ではおよそ300秒ほどかかっていたデプロイが、新方式では平均でおよそ 50秒 に短縮されました。実に約6倍の高速化です。

従来は年間でおよそ20時間近く待ち時間が発生していましたが、新しい方式では年間3時間ほどに短縮されました。

不具合などによりリカバーを行う場合や、はてなブログチームでは導入していませんが Site Reliability Engineering における error budget に対するインパクトも大きく変わりえます。

『継続的デリバリー』の p.68 より:

開発・テスト・リリースプロセスを自動化することで、ソフトウェアをリリースする際のスピードや品質、コストがかなりの影響を受ける。 ......それによってビジネス上の利益をより素早く提供できるようになる。リリースプロセス自体の敷居が下がっているからだ。

今回のデプロイ改善は直接利益をもたらすものではありませんが、リリースマスターの拘束時間が減ることにより心理的な負担が減るなど、はてなブログというサービスを改善し安定した提供を続けるためにポジティブな影響を与えています。

まとめ

はてなブログのデプロイを高速化した背景と導入のステップについて紹介しました。

この記事では紹介しませんでしたが、実際にデプロイを高速化したことで障害に繋がることを防げたこともあります。 エンジニア個人としても普段行うデプロイが速く済むのは数字以上に快適で達成感もありました。


この記事は、はてなエンジニアアドベントカレンダー2016の16日目の記事です。

昨日は id:amagitakayosi でした。明日は id:ikesyo です。

レビュー依頼はすばやく見る

お題「エンジニア立ち居振舞い」 ということでエンジニアの立ち居振舞いについて書きます。

今いるチームでは毎日午後からレビュータイムということでレビュー依頼された Pull Request を見ています。

f:id:aereal:20161110230426p:plain

最近、他のチームからレビュー依頼が消化されなくて頑張って見ているけど大変という相談を受けたりしたのですが、今いるチームではどうしているかというと、毎日レビュー依頼ラベルがついている Pull Request がなくなるまでレビューを続けます。

コードレビューの具体的なテクニックなどは別の機会にとっておくこととしますが、以下のような記事などが参考になると思います:

レビュータイムの導入・消滅・再導入 - $shibayu36->blog;

コードレビュー - hitode909の日記

レビュー依頼されているということは、リリースまでに残る主な工程はコードレビューを受けるくらいでしょう。ということは、コードレビューが滞ると完全にブロックされてしまいます。

チームとしてコードレビューが滞ると、いつリリースできるか見立てが立てづらくなってしまいます。

ずっとコードレビューをしていると他のタスクがリリースできないのでは? という懸念があるかもしれませんが、そもそもコードレビューが滞っていてはリリースはできないため、コードレビューの時間を減らしたところで払うべき労力を先延ばしにしているだけといえそうです。

どのみちかけるべき労力であるならば、すばやく見てフィードバックを返すほうが健全という気持ちで日々、レビュー依頼をすばやくなくなるまで見ています。

最近、レビュー依頼ラベルがついたら Slack で教えてくれるようになったので、レビュータイム以外でも気がついて手があいたら見るようにしています。

f:id:aereal:20161110230427p:plain

このように毎日コードレビューし・されています。みなさまの立ち居振舞いを教えてください。

お題「エンジニア立ち居振舞い」

indirect object notationでハマらないためのno indirectプラグマ

普段、Perlを書くときは require_ok などでモジュールがコンパイル可能か・構文エラーがないかをテストするようにしているけれど、 require_ok では発見できない構文エラーを発見したのでそれの詳細と対策について。

実行時エラーになる構文エラー (?) とindirect object notation

実行時エラーに構文エラーになることなんてあるのかよと思うけど、厳密には構文エラーではなく、意図しない構文解析の結果、コンパイルは成功するが実行するとエラーになるというコードができあがってしまった。

具体的には以下のようなコード:

package App::Logger;

use Exporter 'import';

our @EXPORT = qw(INFO);

sub INFO ($;@) { ... }

1;
package App::Loader::User;

sub find {
  local $@;
  eval { ... };
  if ($@) {
    INFO $@;
  }
}

1;

App::Logger はいろいろ便利なことをしてくれるアプリケーションロガーで定義されたサブルーチン INFO はprototypeが宣言されており INFO 'hi'; INFO 'user: %s', $user->name; のように呼び出せる。

App::Loader::UserApp::Loader::INFO を呼び出しているパッケージだけど、 use App::Logger するのを忘れていた。しかしコンパイルは通る。つまり構文解析には成功しているということである。なぜだろうか?

具体的には INFO $@; がなぜ構文解析に成功するのか。答えはindirect object notationとして妥当な構文であるからだった。

indirect object notationはperldoc perlmodに詳しいが、 MOD->METHOD という呼び出しは METHOD MOD と等しく、後者をindirect object notationという。

つまり INFO $@$@->INFO構文解析されて妥当というわけである。

no indirect プラグマ

謎は解けたが、ひとつのことを成し遂げるために複数のやりかたがあるという状態は、特にチーム開発においてはうれしいことばかりではない。堅牢にソフトウェアを作るためには、曖昧さが少なくエンバグのおそれが少ない書き方ができるほうが望ましい。

indirect object notationを意図せず使っていたら警告なりしてほしいところだけど、そんなときはindirectプラグマが使える。

no indirect; を書いたスコープでindirect object notationが使われていると警告される。 no indirect 'fatal'; で警告する代わりに例外を投げる。

プラグマの影響範囲はデフォルトでレキシカルだけど no indirect 'global'; でグローバルに有効にできる。

これでindirect object notationでハマることを減らせそうで助かってめでたいですね。

PENTAXで残した記憶たち

小学生のころにレンズ付きフィルムを少し使ったくらいで、カメラといえばデジタルの自分にとって長らく写真は、親に言われるまま渋々と学校行事のスナップ写真を注文して買うだけのものだった。

高校生のころ、京都や東京で淡々と、あるいは鬱屈と撮った写真をぽつりと載せ続ける Web サイトを見続けて、いつか自分もこの人と同じものを見たいと感じて調べた結果、デジタル一眼レフカメラの存在を知り貯金をはじめた。

いろいろなカメラに目移りしつつも、直線が多く精悍なかたちに惹かれて PENTAX K-7 を選んだ。

http://scontent-a.cdninstagram.com/hphotos-xpa1/t51.2885-15/10401820_648333031918627_1723368488_n.jpg

真冬の北海道は屋外と屋内の温度差が激しくカメラには過酷な環境であるにも関わらず毎日裸で持ち出してはなんでもない風景を撮り続けた。

進学で大阪に引っ越してからもそれは変わらず、新生活に寄せていた期待は裏切られ友人もできず暗い気持ちで過ごす毎日を捉え続けたのはPENTAX K-7だった。

このカメラに足を引っ張られたことは一度もなく、雨が降ろうと雪が降ろうと真夏の炎天下だろうと、どれだけ厳しくても静かに風景を捉えつづけてくれた。先に根を上げるのはきまって自分。

そのあと PENTAX K-3 に買い替えてからは防湿庫にいることが多かったが、とうとう今年の春、手元を離れていった。

https://www.instagram.com/p/BExNFd1i0Va/

代わりにやってきたのは PENTAX K-1. PENTAX K-7 から変わらず手に馴染み、今年も、これからもきっと自分が死ぬまでシャッターを切り続けるでしょう。いつも先に根を上げてしまうのは自分だったから。

それどこ大賞「買い物」
それどこ大賞「買い物」バナー

Scala めく春

春なので4年前くらいにコップ本の初版を読んで以来の Scala を書きます。

Scalaスケーラブルプログラミング第2版

Scalaスケーラブルプログラミング第2版

Vim

vim-scala を入れて構文ハイライトなどを手に入れます。

syntastic

syntastic は後述する vim-ensime が syntastic のコマンドを呼んでおり、インストールしていないとエラーが出るので導入しました。

(もともと syntastic を使っていたのですが、非同期実行ができなかったのでブロックされてしまい使うのをやめていました。)

せっかくなので lightline.vim で構文チェック結果を表示するようにしました:

ENSIME と vim-ensime

ENSIME brings Scala and Java IDE features to your favourite text editor

ENSIME とは IDE が提供するような定義元へのジャンプや型の表示などのリッチな機能を提供するツールです。

ENSIME は、ビルドツールと統合してプロジェクトの設定を書き出し (.ensime)、エディタが JVM のプロセスを起動し TCP (WebSocket) で通信するというアーキテクチャになっています。

ドキュメントに書いてあるようにいくつかのエディタいくつかのビルドツールがサポートされています。

vim-ensime

ensime-vim は、ENSIME という IDE が提供するような定義元へのジャンプや型の表示などのリッチな機能を提供するツールVim 用クライアントを提供するプラグイン

一時は開発が停滞しており使い物にならなかったそうですが、今はわりと活発に開発されているようでドキュメントに書いてあるとおりに導入したところちゃんと動いて使えています。

vim-ensime は Python インターフェースから WebSocket で ENSIME のプロセスと通信するという実装になっています。

websocket-client という pip パッケージを入れる必要があるので、OS XPythonsudo を使って入れたくないので Homebrew で入れた Python を使います。 ……が、手元に入れていた MacVim はシステムの Python にリンクしていたので python/dyn が使えるよう MacVim をビルドします。

brew unlinkapps
brew unlink macvim
brew tap macvim-dev/macvim
brew install --with-properly-linked-python2-python3 --HEAD macvim-dev/macvim/macvim
brew linkapps macvim-dev/macvim/macvim
✘╹◡╹✘ < vim --version | grep python
+cryptv          +linebreak       +python/dyn      +vreplace
+cscope          +lispindent      +python3/dyn     +wildignore

okok.

sbt

公式のインストール手順に書いてあるとおり sbt プラグインをインストールするだけ。

思い出す

手を動かします。

はてなインターン事前課題をやって、はてな教科書を読んで課題を進めます。Scala で Web アプリケーションを書くにあたってひととおりなにか書くにあたって、課題がまとまっていてよいです。

コップ本を片手に Scala API リファレンス を眺めるのも乙なものです。

おわり

(ひさしぶりに) Scala を書くにあたっていろいろ環境を整えたり、Scala を書く際の勘所を得る (取り戻す) ためにコードを書いたり資料を読んだりしました。

次は Play でちょっとしたアプリケーションを書いたので、その記録です。

便利 zsh 設定 N 連発

シェルなどの設定の知見が意外と広まってないよね、ということで話題になったので書きました。

:q で exit

https://github.com/aereal/dotfiles/blob/1be211fcda027f75b0624eab2c44a288f8e0691e/.zshrc#L174

Vim 使っていると :q したくなるので alias をつくった。

バックスラッシュをチルダに置き換える

https://github.com/aereal/dotfiles/blob/1be211fcda027f75b0624eab2c44a288f8e0691e/.zshrc#L109-L120

~/ はけっこう使うわりに押しにくいので、バックスラッシュを入力したら ~/ に展開するようにしてある。

カーソル位置の左が空白か行頭の場合のみ展開する、という風にしているので echo 'a\'' などは普通に入力できる。

Vim 風に tmux のペインを分割してコマンドを実行

https://github.com/aereal/dotfiles/blob/1be211fcda027f75b0624eab2c44a288f8e0691e/.zshrc#L153-L171

C-w v とか C-w s で、現在プロンプトに入力しているコマンドを分割したあとのペインで実行する。

tail -F /var/log/app/fluentd.log[C-w]v などと入力すると縦分割してログを眺められて便利。

エスケープが雑なのと、分割したペインでは新たに tmux 経由でシェルが実行されるので環境変数が引き継がれないなどあるけど、だいたい便利に使えている。

時間がかかったコマンドの実行終了時に自動で time する

https://github.com/aereal/dotfiles/blob/1be211fcda027f75b0624eab2c44a288f8e0691e/.zshrc#L15

REPORTTIME に指定した時間より長くかかったコマンドの終了時に、自動的に time の結果を出力する。