LWP::Protocol::PSGI 0.10以降でLWP::UserAgentの:content_file引数が使えるようになっている

- Fix mirror() (haarg) #9

https://metacpan.org/changes/release/MIYAGAWA/LWP-Protocol-PSGI-0.10#L4

changesには `LWP::UserAgent#mirror` についてのみ言及があるけど、`get()` メソッドの `:content_file` 引数にも影響がある。

`mirror` メソッドも `:content_file` 引数も紆余曲折あり `LWP::Protocol#collect` を呼ぶことでレスポンスボディがファイルに保存される。

fix LWP::UA::mirror calls by haarg · Pull Request #9 · miyagawa/LWP-Protocol-PSGI

上記Pull Requestはつまるところ適切に `collect` を呼ぶようにする変更なので、 `mirror` も `:content_file` も適切に処理される、ということだった。

日記を書くWebアプリケーションを書いている

GitHub - aereal/nikki

昨年末からぼちぼち書いている。

モチベーション

  • いわゆるページを動的に生成するWebアプリケーションのパフォーマンスチューニングの練習台
    • サーバサイド、クライアントサイド双方に渡る改善を試みる余地がある
  • 諸々のWeb技術の遊び場
    • TypeScript
    • Google Cloud Platform
      • Kubernetes
    • HTTP/2
    • GraphQL
    • SPA = Single Page Application
  • 以上が目的としてではなく、手段として求められ高度にバランスされること
    • 普段から自分が使うアプリケーションであること
    • (技術採用など意思決定の練習)

……を目的に考えて日記を書くためのブログシステムを書きはじめた。

Web技術で遊び続ける定まった場所がないと、技術を使うための技術の域を出ない学習が続くなという問題意識から何か作ることにし、自分はWebでテキストを書くのが大好き人間なので、ブログシステムを作ることにした。

それでも自作するか
もしウェブプログラマなら自作すべきであると常に主張したい。

ブログシステムは自作すべきか? ブログサービス VS 自作システム | tech - 氾濫原

自分もこの意見に賛成で、まあブログシステムであることにこだわらなくてもいいけれど、趣味なら車輪の再発明など恐れずにとにかく遊び続けるのがいいと思う。

また完全無欠のASP型のサービスなどありえないわけで、その点では自作するとき自分が求めていてASPサービスにないものを作れるならば、それは完全な車輪の再発明とはいえないし、得るものは大きいと思う。

目的

パフォーマンスチューニングを目的に挙げているのは、仕事でもWebアプリケーションを作っているがそもそもパフォーマンスについて考える時間が少ないので知識も技術も身に付かない、けど求められるものではあるというギャップを埋めるためでもある。

そしてこうした興味・好奇心だけを目的とせず、使う自分自身の満足度・快適さに対する解決の手段として自分の興味がある技術を選べるような題材という点でも、ブログシステムは適している。

ソフトウェアエンジニア一般が趣味の開発でこうしたモチベーションでいるべきと考えているわけではなく、単に自分が趣味で作ることを考えるとき自分がよく使うもので、かつうまく動いていると自分が一番に喜ぶものでないと、継続して触りつづけるモチベーションが保たないだろうということを見越した、合理的な理由による。

技術

インフラはGoogle Cloud Platform、configuration managementを考えるのがだるいのでランタイムはコンテナに任せようということでk8s (GKE) を選んだ。
仕事でAWSを使っているので、では趣味ならGCPというくらい。あと料金計算が明快なのがいい、AWSリザーブインスタンスとか考えはじめると勘定がめんどう。
いまVPSに月におよそ1000円くらい払っているので、これを解約することを見越してプラス勉強代として月2000円まででやりくりしようと思っている。

パフォーマンスチューニングを深追いしたいということで、ポピュラーになったCDNは採用せず自分でリバースプロキシを運用することにした。

サーバサイドはRuby, DBはPostgreSQL, 記事の編集画面はTypeScriptで書いている。

インフラとフロントエンドが弱いと感じていて、そちらにリソースを割きたいのでサーバサイドは使い慣れたRubyにした。
Rubyはぱっと作ることができて安定感があるものの、型がなかったりもうちょっと冒険してもいいかなと思っている。
とはいえパッケージ管理とかでむやみにハマっていたくないし、予算上、ホストのメモリとかは潤沢にはできないのでScalaは選びづらいし、仕事でも趣味でもGoをよく書いており食傷気味なので、やはり落とし所として妥当かと思っている。

本当はサーバサイドはホットスポットではないのでGAEにしてもよかったんだけれども、予算の都合でStandard Environmentしか使えず、StandardだとJavaPythonかGoかPHPで、どれも気乗りしなかったのでGKEにした。

フロントエンドはそのうちSPAにしてみたい。
昔、仕事でSPAを触っていたけどhistory APIにバグのある地獄みたいな環境向けだったので、今のまともなブラウザと充実してきたと思われるライブラリ類で、リソース管理などの煩雑さが減ったのかそうでもないのか確かめてみたい。

いまは最低限、記事を書いてそれを配信するところまではできたのでGKEにデプロイしようとしている。
Kubernetesは耳慣れない概念がたくさん出てくるので難しいけど、GCPのドキュメントが充実しているのでチュートリアルを見様見真似でやりつつ要件・予算にあわせてあれこれ手探りで変えて試している。

おわり

VPSで遊ぶのも楽しいけれど、IaaS/PaaSが充実してきたことで予算の範囲内でマネージドサービスを利用し趣味の開発のボトルネックを解消することができそうでなかなか楽しい。
クラウド事業者にロックインされた範囲の中では、マネージドサービスから自前運用に切り換えることもできるわけなので、そういった点では上位互換といえそう。

MacBookのバッテリ残量をMackerelに投稿するプラグインをmkr plugin install対応した

github.com

sudo mkdir -p /opt/mackerel-agent/plugins
sudo chown -R $(id -un):$(id -gn) /opt/mackerel-agent/plugins

mkr plugin install aereal/mackerel-plugin-macbook-battery-health@v0.0.2
cat <<EOS >> /usr/local/etc/mackerel-agent.conf
[plugin.metrics.macbook_battery_health]
command = "/opt/mackerel-agent/plugins/bin/mackerel-plugin-macbook-battery-health"
EOS

this.aereal.org

以前作ったプラグインを `mkr plugin install`-readyにした。

対応といっても命名規則を整えてGitHubにリリースしたくらいで非常に手軽で便利。手順はMackerel公式のドキュメントが詳しくてわかりやすい。

mackerel.io
mackerel.io

実際に対応するための変更は以下のPull Requestにまとまっている。ちゃんと読んでいなくてzipじゃないとダメとか命名規則とかが適合していなくて無駄にトライアンドエラーを繰り返してしまった。

バッテリ残量の監視はなにかと便利なのでどうぞご利用ください。

2017年に利用した継続課金サービス

r7さんらの振り返りがおもしろかったので自分も書き出した。

medium.com

medium.com

継続しているサービス

dアニメストア

5,184 JPY/year

過去作から最新作までたくさん配信されているのでなにか見たいな〜という時に便利。

最近はテレビ放映とタイミングを揃えて配信される作品が増えたのでほんとうに良い時代になった。

今は京都という割と地上波のチャンネルに恵まれた地域に住んでいるけれど、地元は北海道なので深夜アニメがまったく見れずに悲しい思いをしてきたので、地域差が縮まるのは歓迎。

配信の仕組み自体は素朴という印象で、AmazonプライムビデオやNetflixと違いビットレートを調節してバッファリングの時間を減らすとかそういう取り組みはしてなさそうなので、ネットワークの品質に問題があるとストレスを感じることもある。

Amazonプライム

3,900 JPY/year

お急ぎ便などなどに使っている。あとプライムビデオのラインナップが絶妙で、dアニメストアと補完しあったり昔見たおぼえのある映画をたまに見ている。

GitHub

9,492 JPY/year

いつのまにかDeveloper planだとprivateリポジトリの上限がなくなってますます便利になった。

スケッチとかIntelliJの設定 (自動でコミットされるのでまずい内容が出てしまわないか心配なのでprivateにしている) などとりあえずprivateでも作っておけるという安心感を買っているようなもの。

IIJ mio

26,640 JPY/year (6GB ライトスタートプラン)

数年前にauからMNPしてずっと使っている。京都で生活していると人が多くて遅いというようなこともなく快適。

もともと3GBのプランだったけれど旅行で外に出る日が続くとすぐにクーポンを使い切ってしまうので6GBにした。

WiMAX 2+

44,352 JPY/year → ?

一人暮らししてから家の回線はWiMAXを契約しておりその流れで今も使っている。

……が、いまは通信量に上限があり、それはよいとしても課金して制限を解除する道も提供されていないので潮時かなーと思いしばらく立つ。

固定回線は、それはそれで地域において品質の良いISPを見つけること、工事に立ちあう必要があること、などから二の足を踏んでおりいまだにどうしようか考えるがまあいいかな……と思う。

次に引っ越すことがあったら諦めて固定回線を契約すると思う。

Adobe Creative Cloudフォトプラン

11,760 JPY/year

Lightroomなど使うために入っている。

Apple Music

11,760 JPY/year

ポピュラー音楽のラインナップは特に琴線に触れるところはないけどクラシックのラインナップが満足できる。

聞きたいオーケストラの盤が揃っているので好きな時に聞き比べたり参考にできるので気に入っている。

J-WESTカード:JRおでかけネット

1,080 JPY/year

旅行で東海道・山陽新幹線JR西日本の特急に乗る機会が増えたので契約した。

会社で出張するときは会社が契約しているEX-ICで新幹線に乗るのだけれど、あれを知ったら直前まで予約が変更できる便利さなしでは生きていけない。

のぞみに1回乗った時の割引額が1,080円なので1年に2回乗ったら回収できるのでかなりお得だと思う。

あと海外旅行前にVISAではなくMASTERCARDのカードを手に入れる目的もあった。

Netflix

(解約) → 11,400 JPY/year

ヴァイオレット・エヴァーガーデン」「からかい上手の高木さん」の配信がNetflixだけだったので再度契約した。

バッファリングの短さとか視聴するときのストレスのなさは使ったことのあるサービスの中では一番だと思う。

解約したり契約を見直したサービス

さくらのVPS

21,384 JPY/year → 10,692 JPY/year

1GBプランを2つ契約していたけど1つにした。

Elasticsearchとか計算機リソースを使うミドルウェアで遊ぶ時は、リソース単価の安いVPSのほうが使い出がある。

AWS (Amazon Web Services)

16,800 JPY/year → 1,047 JPY/year (予定)

古い写真のRAW画像の保存にGlacierを、このブログなどなどのドメインのネームサーバとしてRoute 53を使っており、これで年1,047円くらい。

……のつもりだったのだけれども、昨年明けに検証のために立てたEC2インスタンスを落とし忘れていて昨年1年間ずっと毎月10USDかかっていたことがわかって慌てて解約した。

MoneyForward

6,000 JPY/year → 解約

家計レポートとか見れたらおもしろいかなと思って契約したけど既に述べたようにEC2のインスタンスを起動しっぱなしで気付かないような人間なので、まずMoneyForwardにお金を払う前にやるべきことがたくさんあることに気がつき解約した。

おわり

EC2インスタンスを起動しっぱなしにする人間は何をやってもダメ。

数字で振り返る2017年

Amazon で購入した金額

金額 前年比
2017 483,040円 124.09%
2016 389,277円 124.54%
2015 312,547円 136.27%
2014 229,352円 -

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

この調子でいくと来年は45万くらい年間で使う目測だけど、そんなに成長するかな、どうかな。

数字で振り返った2016年 - Sexually Knowing

順調に124%成長が続いている、すごい。後で紹介するように給与は毎年110%ペースくらいなので、いつか追い付かれる。

給与

f:id:aereal:20180101215003p:plain

入社して以来、給与明細をGoogle Driveに保存してあり、支給額 (手取り) を12掛けして年俸額とした。
なので年俸額なのにやけにピコピコしている。

営業期が変わるタイミング (1月と7月) に人事評価がありそこで報酬が変わるので、だいたいそのタイミングで大きく変わる。

グレードという給与レンジを定めた概念が全6段階あって、それが変わるとベースも変わるのでそこで大きく変わる要因はそれくらいと見える。
いちおうグレードは役職と関係ないということになっている。

ちなみに控除額も有史以来記録してあり、見るとげんなりするもののひとつ。

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

記事数 前年比
2017 53 72.60%
2016 73 486.7%
2015 15 13.0%
2014 115 -

一転、減ってしまった。1月に大雪があったりクロアチア旅行に行ったり前半はよく撮ったし、後半も撮っていないこともないけれど上げていく気力がなかった。

9月から今もずっと仕事で完全に頭が参ってしまっている。書いていたらなんで仕事に忙殺されてこんな虚しい振り返りをしているんだとイラついてきた。

日記

記事数 前年比
2017 87 55.76%
2016 156 91.22%
2015 171 54.11%
2014 316 -

ほぼ半減した。9月くらいから本当に気が参って「仕事やめたい」とか「1000万ほしい」としか書いてない下書きがたくさんある。

近しい人に読んでほしくないので接続元のIPが地理的に近かったら見れなくするとか、そういうことをしたいな。

むすび

  • 年俸も120%成長させたい
  • 写真のアウトプットもっと増やそう

.devなどのTLDはChrome 63からHTTPSを強要される

その昔Powを使っていた名残で、開発時のループバックドメインに `.dev` をよく使っていたのだけれど、最近うまく動かなくて調べたところ、HTTPSで通信しようとして失敗していた。

アプリケーションでそんな設定した覚えないんだけどな……と訝しんでいたら、どうやらChrome (Chromium) に変更があり先読みするドメインのリストでHTTPSを強制する設定が追加されたらしい。

Chrome 63 forces .dev domains to HTTPS via preloaded HSTS

`.dev` はGoogleによって買われたらしいという話も書いてあって初耳。

それはそうとどうすればいいかというと、テスト・開発用に予約されたTLDがあるのでそれを使うのがよいだろうと上記記事には書いてある。

`.test` や新たに追加された `.localhost` などがそれにあたるので、それを使うのがよかろう、と。

元々予約されているわけでもなんでもなかったのでいきなり壊れても文句は言えないけど、しかしこういう変更入れることもあるんだなーという発見があった。

同じ名前のHTTPヘッダを複数回出力してもよいのか

結論: 値がcomma-separated listと定められているヘッダはOK

同じ名前のHTTPヘッダを複数回出力する、とは

こういう出力:

...
Vary: Accept-Encoding
Vary: User-Agent
...

同じ名前のHTTPヘッダを複数回出力すると、受信者はどう振る舞うのか

RFCより引用:

A sender MUST NOT generate multiple header fields with the same field name in a message unless either the entire field value for that header field is defined as a comma-separated list [i.e., #(values)] or the header field is a well-known exception (as noted below).

RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing

A recipient MAY combine multiple header fields with the same field name into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field value to the combined field value in order, separated by a comma.

RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
  • 送信者は値がカンマ区切りリストと定義されていないヘッダを複数回出力してはならない
  • 受信者は複数の同名ヘッダを出現した順にカンマ区切りリストへ結合してもよい

ということが書いてあります。

つまり以下のヘッダ出力があったとき:

...
Vary: Accept-Encoding
Vary: User-Agent
...

……受信者は以下のように結合するかもしれません:

...
Vary: Accept-Encoding, User-Agent
...

mackerel-aws-s3-stats: S3のオブジェクト数とサイズをMackerelに投稿するいいやつ

github.com

いいやつを作った。

AWS SDK for Goを使っているのでIAM roleが適切に設定されたEC2のインスタンス上ならAWSの認証情報は特別渡さなくてよい
(もちろん環境変数で渡してもよい) ので、MackerelのAPI keyとサービス名さえ渡せばすぐに使えます。

さらに `-no-post` でSensu互換形式でstdoutに出力だけすることもできるので、mackerel-agentが動いているホストでmkrがインストールされていればサービス名だけ渡すだけで動くので便利。

mackerel-aws-s3-stats --region ... --bucket ... --no-post | mkr throw --service my-service

f:id:aereal:20171212181401p:plain

f:id:aereal:20171212181405p:plain

どうぞご利用ください。

GAE/GoでNetatmoのAPIから取得した温湿度をMackerelに送るアプリケーションを動かす

GitHub - aereal/gae-go-netatmo-ws-mackerel

f:id:aereal:20171211115645p:plain

冬なのでNetatmo Weather Stationを購入して自宅で動かしはじめました。エアコンをつけるといきなり室温が上がってエアコンはすごいなーということがよくわかって便利。

ハードウェアももちろんWebサービスとアプリもよくできていて、単に現在のデータを見るだけならこれで十分ですが、不快指数を計算したらなにかとアラートを送ったりしたいのでMackerelに投げたい。

ので書きました。GAE/Goで動くやつです。

元々、同僚がGoogle Apps ScriptとAWS Lambdaで動かす実装を作っていてだいたいそれを参考にした。

家で動かすものでメンテフリーにしたく、GASは1週間後読み書きできる気はしないしAWS Lambdaはいいけどもっと素朴なWebアプリケーションで開発したいなと思ったのでGAE/Goで動かしています。

詳しくはREADMEに書いてあるけど、普通のWebアプリケーションとして動かし、GAEのcron機能で定期的にリクエストさせることで定期的にメトリックを送ることができる。

ポイントとしては:

  • GAE/GoはHTTPリクエストの発行にgoogle.golang.org/appengine/urlfetchモジュールを使う必要があるので、外部APIにアクセスするパッケージも対応する必要がある

感想:

f:id:aereal:20171211115813p:plain

  • GAE/Go, 実行環境が特殊なので開発でめっちゃハマりそうでイヤだなと思っていたけど意外とそうでもなかった
    • 上記のようにライブラリにちょっと手を入れる必要があるかも、くらい
    • 対応自体もわりと一般的というかGAE特化というほどでもないので穏便
  • stdout/stderrに出力してもなにも見えなくて、GAEのlogモジュールを使う必要がある
  • cron実行の結果とか500エラーの割合などがGAEのダッシュボードで見れて便利
    • cron実行結果のログはStackdriverで見れてうまく他のGCPサービスと連携してるんだなーと思った

大コンテナ時代を生きのこるためのJSON Schema

実行環境をコンテナ化するDockerが普及して久しく、CIやローカルの開発環境などどこかでコンテナ技術に触れているのではないでしょうか。

コンテナはその性質上、設定のプロビジョニングに古典的な設定ファイル (のパス) 受け渡しが難しいです。etcdconsulのようなKVS (= Key-value store) を用いることもあるでしょうが、素朴には環境変数で与えることが多いでしょう。

HerokuはThe Twelve-Factor Appというパターンを提唱し、その中でStore config in the environmentと述べています。

The twelve-factor app stores config in environment variables (often shortened to env vars or env). Env vars are easy to change between deploys without changing any code; unlike config files, there is little chance of them being checked into the code repo accidentally; and unlike custom config files, or other config mechanisms such as Java System Properties, they are a language- and OS-agnostic standard.

The Twelve-Factor App

参考: Building Twelve Factor Apps on Heroku | Heroku

しかし環境変数グローバル変数なので無闇に参照すれば古式ゆかしきスパゲティコードまっしぐらです。また環境変数の値は文字列として扱われるので表現力に乏しく利用者のために値の定義域を示す必要がしばしば生じますが、どのように行うべきでしょうか?

そんな悩みをJSON Schemaで解決しようという試みを紹介します。

環境変数の値の定義

ある環境変数がとりうる値の形式や範囲を考えてみます。

たとえば真偽値 (boolean) が挙げられます。 DEBUG=1デバッグフラグを立てるような変数です。
1か0か、あるいは値はなんでもよくて定義されていれば真とする場合もありますが、併せて真偽値とします。

あるいは数値 (number) も考えられます。 LOGLEVEL=1LOGLEVEL=7 など、ログ出力を数値で指定するケースがあります。

真偽値と数値を一般化すると列挙 (enum, enumeration) になります。たとえば PLACK_ENV=test PLACK_ENV=production など、文字列として扱われるがそのとりうる値があらかじめいくつかに決まっています。

また特殊な例として複数値 (multiple values) についても考える必要があります。
環境変数は辞書構造なので、ある名前に対応する値はただひとつに定められるため、複数の値を渡したい場合は値をデータ区切り文字 (delimiter) で分割して扱います。

たとえば PATH は値をコロンで区切ります *1

PATH はコロンで区切るというコンセンサスが得られているのであまり問題になりませんが、独自の変数を定義するときに区切り文字は何であるかはいよいよもって明示しなければ正しく使うことはできません。

JSON Schemaの利用

そこでJSON Schemaを利用します。

JSON Schema (JSON Schema Core)はその名の通りJSONの形式を表明する形式で、スキーマに基づいて入力の検証を行うためのボキャブラリを定義するJSON Schema Validationなどの仕様とあわせて利用されます。
以下、CoreとValidationをまとめてJSON Schemaと呼びます。

JSONと名がついていますがあくまでシリアライズ形式にJSONを選んでいるというだけで、利用する上でその他の制約はありません。

JSONとしてシリアライズ可能、すなわちJSONが定義する辞書構造 (object) とコレクション (array) のほかいくつかのスカラ値 (string, boolean, null) のいずれかで表現できる入力であればJSON Schemaで表明・検証できます。

先に述べた通り環境変数は名前と値がそれぞれ文字列である辞書構造なのでJSONとしてシリアライズ可能です。

以下はあるスクリプトが期待する環境変数JSON Schemaの例です:

{
  "title": "Schema of some script's environment variables",
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "DEBUG": {
      "type": "string",
      "enum": [
        "1",
        "0"
      ]
    },
    "LOGLEVEL": {
      "type": "string",
      "enum": [
        "0", "1", "2", "3", "4", "5", "6", "7"
      ]
    },
    "PLACK_ENV": {
      "type": "string",
      "enum": [
        "development", "test", "production"
      ]
    },
    "PATH": {
      "type": "string",
      "pattern": "[^:]+(:[^:]+)*"
    }
  },
  "required": [
    "PLACK_ENV"
  ]
}

先に述べたnumberやbooleanを期待する例がJSON Schemaで記述されています。

実際にこのスキーマを用いて検証を行ってみます。

Rubyjson-schema.gemを利用します。以下のGistにコード例を示します:

Gemfile · GitHub

実行結果の例です:

✘╹◡╹✘ < ruby validates_env.rb
The property '#/' did not contain a required property of 'PLACK_ENV'
✘>﹏<✘ < PLACK_ENV=a ruby validates_env.rb
The property '#/PLACK_ENV' value "a" did not match one of the following values: development, test, production
✘>﹏<✘ < PLACK_ENV=development ruby validates_env.rb
✘╹◡╹✘ <

PLACK_ENV=a を渡したらきちんと怒られてかわいいですね。

独自実装を含む他の検証方法に比してJSON Schemaを選ぶ利点はいくつかあります。

  • 相互運用性が高い
    • ランタイムによらず利用できるため、複数の言語を採用している場合に障壁となりません
  • 検証のセマンティクスの一貫性が保たれる
  • スキーマの再配布・拡張が容易
    • たとえば組織内で設定に用いる環境変数を共通化したいとき、あるURLでスキーマを配布・参照することで普及させやすくなるかもしれません
  • JSON Schemaのエコシステムの恩恵に与れる
    • たとえばドキュメント生成が簡単になるかもしれません

一方、物足りないと感じる点としては、enumやpatternプロパティを駆使するほかなく、あまり可読性が高くない点でしょうか。

この点については、JSON Schemaの仕様ではformatプロパティに独自のキーワードを追加・利用することを許容しているため、booleanライクな文字列の定義を追加し、それをformatに指定することで冗長な定義を避けられるかもしれません。

Implementations MAY add custom format attributes. Save for agreement between parties, schema authors SHALL NOT expect a peer implementation to support this keyword and/or custom format attributes.

JSON Schema Validation: A Vocabulary for Structural Validation of JSON

むすび

以上、設定のスキーマJSON Schemaで記述・検証することでコンテナ時代に即したtwelve-factor appパターンを促進する試みでした。

私はtwelve-factor appの環境変数を推進する姿勢については環境変数の表現力の乏しさを懸念し懐疑的でしたが、JSON Schemaを利用することを思い付いてからは懸念も解消され強く共感します。

既に述べたように独自formatを定義・利用するバリデータがあるとより便利かもしれないという思いもあるので、引き続き検討・導入していきたいところです。


この記事ははてなエンジニア Advent Calendar 2017の8日目の記事でした。

明日 (12/9) はid:ikesyoです、おたのしみに!

*1:ただしUNIX/Linuxにおける場合、Windowsでは異なる

寝て起きる

昨日書いてたコード、テストが落ちるけどわけがわからないので途方に暮れたけど、一晩寝て起きたらもしかしてこうでは? という気付きが得られて、出社して試したらうまくいった。

集中力は単に集中力と言われるのであらゆる対象に対して1つの共有のプールがあって、そこからリソースを奪い合うかのようなイメージだけど、意外とそうではなくて対象ごとにそれぞれプールがあって残量が0になると知性がなくなる、みたいなモデルなのかもしれない。

自分の中に集中力リソースが100あって、好きなタスクに100まで自由に注げるわけではなくて、集中力プールが5つあって、それぞれ20ずつ、みたいなかんじなのかもしれない。

だとすると、ひとつのことをずっとやっても集中できるのは20までなので、効率が悪い。最も効率よく働くには、集中力プールの数だけタスクをかけもちすればよい。

しかし集中力プールの数がいくつなのかは知らないし、使える時間は相変わらず共有リソースなのでどういう順番でやるかを考える必要がある。

あとタスクの進みが悪いときに他のタスクに手をかけるとなんとなく気まずさがある。

この世はままならぬ。

インストールがとても高速化されたcapistrano-strategy-jenkins_artifact 0.4.0をリリースした

GitHub - aereal/capistrano-strategy-jenkins_artifact: Capistrano 2 strategy that uses Jenkins' artifact as a distribution provider.

Release v0.4.0 · aereal/capistrano-strategy-jenkins_artifact · GitHub

capistrano-strategy-jenkins_artifactというgemを作ってメンテしており、これの0.4.0をリリースしました。

Travisのビルドでは最大約30倍速くなりました:

`bundle install` before: 126.69sec
`bundle install` after: 4.22sec

Goodbye jenkins_api_client by aereal · Pull Request #21 · aereal/capistrano-strategy-jenkins_artifact · GitHub

JenkinsのAPIを利用するためにjenkins_api_clientというgemを使っていたのですが、これはXML APIを利用することもあるので依存にnokogiriが入っており、`bundle install` に時間がかかるのがネックでした。

実際にこのgemで利用するAPIJSON APIでまかなえたので、net/httpベースで書き直すことでjenkins_api_client経由のnokogiri依存をなくしました。ありがとうjenkins_api_client.

このgemはCapistrano 2 strategy that uses Jenkins' artifact as a distribution provider.という説明の通り、Jenkinsのビルド成果物を取得するstrategyを提供します。

もともとはてなブログのデプロイ設定のために書いていたコードをgemとして切り出したもので、いまではMackerelなど社内のいくつかのサービスで導入されています。gem化してよかった。

ついでにOSS化したことでテストをちゃんと書こうというモチベーションが高まりお得。

参考: はてなブログのデプロイを約6倍高速化したはなし - Sexually Knowing

ISUCON7予選に秒速5000兆クエリというチームで参加した

ISUCON7 開催&日程決定! #isucon : ISUCON公式Blog

ISUCON7のオンライン予選にid:astjid:tanishiking24の3人で秒速5000兆クエリというチームで出場した。最終スコアは9万ちょっとで、ベストスコアとほぼ同値だったので実力を出し切ったと思う。

ISUCONに競技者として参加するのは初めてで、エンジニアリングにスコアをつけて競い合うというのは聞いていた以上に楽しい。

ちなみに「競技者として初」というのは、昨年のISUCON6の参考実装の移植として関わっていたので、ISUCONと関わったという意味では初ではなかった。

準備

2日前くらいにcatatuy/private-isuで練習した。振り返るとISUCON7の予選問題も画像をいかに効率よく返すかというところが最初のボトルネックで、早速予習が活きてお得。

pt-query-digestを使うとか、nginxのアクセスログを解析してボトルネックを見つけるとか、見つけたボトルネックの解消の分担だとか、タイムキープだとか、一通り押さえておきたいところは練習できたので事前準備は万端と言ってよい。

当日

開始が遅れるとのことだったのでオフィスの近くにあるちょっといい肉料理のお店でランチをいただいた。

肉専科はふう 京都御所南の和牛ステーキを堪能できる洋食のお店

開始してからは、まず3人でレギュレーションの読み合わせをした。予選だけど複数台構成ということでおおっとなる。練習していた時のデプロイは雑にホストに入って `git pull` するだけだったけど、手間とかリスクを考えて早めにデプロイ整備をしようということになった。

ほか、ローカルとグローバルにそれぞれ帯域制限があることなどを確認しあった。この時点で参考実装は読んでいなかったけれどグローバル側が100Mbpsだったので、とにかくベンチマーカーから送られてくるリクエストを減らす = クライアントキャッシュを使うのが肝要というあたりをつけた。

読み合わせが終わってからはid:astjSSH鍵のセットアップなどをしつつ、自分が初期プロビジョニング (mackerel-agentを入れたり、redis, memcachedを入れるなど) やデプロイ準備を進め、id:tanishiking24がkataribeのセットアップをしつつコードリーディングを進める、という分担になった。

レギュレーションを読み合わせましょう、とか、3人がどういう分担になるといいか、とかはISUCON本戦出場経験のあるid:astjに音頭を取ってもらった。普段からオペレーションも得意だしあてにはしていたけど、実際かなり頼りになり助かった。
id:tanishiking24も「じゃあこれよろしく」と振って危なげなく進めてくれるので、チームの並列度はマックスで最高だった。

練習でやったとおりnginxのアクセスログMySQLのslow.logを見てボトルネックを1つずつ潰していく方針で進めた。

id:astjがやってくれたimage.nameにuniqueインデックスを張る変更でスコアが5万くらいに上がってテンションが上がってきたと思う。早いチームは早いうちからスコアを伸ばしていて内心やや焦っていたけど、同じ土俵に立てたことで安心しつつ、競い合えているなーと楽しくなってきた。

その後、N+1クエリを殺すなど分担してスコアをちまちま上げつつ、いよいよアイコンの配信をどうにかしないと帯域で当たってどうしようもない、という状態になって案を考えはじめた。

last-modifiedをつけてconditional GETさせよう、という方針になったのだけど、これはあとから考えると穴がある判断だったし、ハマる原因になってしまったな、と反省。

JSやCSSにはif-modified-sinceは来ているように見えたけどアイコンには来ていないので、last-modifiedヘッダの日付形式がおかしいのではないか、とか、304を返すロジックを間違えているのではないか、とか後から考えれば的外れな検討ばかりして時間が消えていった。たぶん3時間くらい費してしまったと思う。

最後のほうは本当にお通夜で、しかも大差ない変更をしたつもりだけどスコアが5万→3万→2万→3万とよくわからない上下をするので、一層お通夜が進んだ。

だんだん頭がおかしくなってきて、gzip圧縮かけているとstrong etagが毎回変わってしまいそれでキャッシュされていないのでは? ということになりgzipを切るなどしたが、効果はなし。

頭も朦朧としてきてヤケクソになって誰ともなくcache-control max-age=0とかつけてfailしなかったら儲けものじゃない? って話になり、ベンチマーカーを走らせるとなんと6万くらいになった。

その時のid:astjはこんなかんじだった:

                                 ,.へ
  ___                             ム  i
 「 ヒ_i〉                            ゝ 〈
 ト ノ                           iニ(()
 i  {              ____           |  ヽ
 i  i           /__,  , ‐-\           i   }
 |   i         /(●)   ( ● )\       {、  λ
 ト-┤.      /    (__人__)    \    ,ノ  ̄ ,!
 i   ゝ、_     |     ´ ̄`       | ,. '´ハ   ,!
. ヽ、    `` 、,__\              /" \  ヽ/
   \ノ ノ   ハ ̄r/:::r―--―/::7   ノ    /
       ヽ.      ヽ::〈; . '::. :' |::/   /   ,. "
        `ー 、    \ヽ::. ;:::|/     r'"
     / ̄二二二二二二二二二二二二二二二二ヽ
     | 答 |     コ ロ ン ビ ア       │|
     \_二二二二二二二二二二二二二二二二ノ

あとは初期データに含まれるアイコン画像を静的に書き出すのをやって9万 (ベスト値) が出る。……ものの、my.cnfを書き換えてベンチマークを走らせると5万くらいに落ちる→もう一度走らせる→スコア戻らず→変更を戻す……というようなよくわからないスコアの上下が相変わらず発生するので、最後の30分はベストスコアである9万を越えるまでひたすらエンキューする猿たちになっていた。結果、最後の10分くらいでベストスコア近いスコアが出たので、そこで打ち止めとなり終了。

実践できて、次回もやっていきたいこと

  • ボトルネックの解析・発見→修正という基本的なプロセスを回しつづけたこと
  • 30分ごとに現況確認をしあい、ハマるのを概ね防げたこと
  • (良くも悪くも) 普段の自分たちが切れる手札だけを切れたこと、本番一発勝負の必殺技みたいなものはなかった

反省点

  • ベンチマーカーのconditional GETの挙動が不可解だとなったときに、動かす手が止まってしまった
    • 「仕様的には我々は間違っていないはずなのに」となったときに、仕様に照らすのではなく、ベンチマーカーの動きを知るためとにかくあれこれやってみるべきだった
    • 他にボトルネックのあたりはついていたが、帯域で当たっているのでまずアイコンをどうにかしないといけない、けど手がない……ということで3人とも止まってしまった
  • 他の人の感想を見るとgzip_staticとか知らないディレクティブがいろいろあった

感想

普段やっていることに近いし社内の他のチームと感想戦をしあってみて、丁寧な配信最適化をやろうというかんじで、楽しめたし自分たちに足りていないところがあることを痛感させられ、たいへん楽しめました。

悔しくありつつも、初ISUCON参戦で楽しかったので、次は優勝したい。

余談

予選参加することを決めたあとで10月22日に『響け!ユーフォニアム』のコンサートと被っていることに気がついて、慌てて21日の参加で問題ないか確認した時が一番肝が冷えた。

週5日8時間労働は厳しい

しばらく体調が悪く、週末になるとへとへとになる状態が続いている。

土日に休むとリフレッシュできて月曜日には元気一杯になるので、老衰による疲れやすさがあるにせよ、体力の最大値がそんなに大きく目減りしていることでもなさそうなので、今週は午後からの勤務として労働時間をだいたい50%にしている。

そしていまのところ調子がいい。いきなり気温が下がって身体に負荷がかかっているにしては、わりといい調子が続いている。

あとフィジカルというよりメンタルの負荷が大きそうで、午後に出勤するまでのあいだ、家の掃除をしたり洗濯をしたり、あるいはピアノを弾いたり、そういう時間が増えたことも大きいように思う。

つまりそういった家事や趣味をするぶんのフィジカルリソースの消費は変わらずあるものの元気というわけなので、メンタルリソースの消費が激しいという推論。

生涯で得られる幸福の総量について考えると、収入を2倍にするより労働時間を半分にするほうが総量は大きくなりそうだし、健康も保てて寿命が延びそう。

スマートフォンからSlackを消した

休んでいる時にたまに見たりしてしまい無闇に疲れていたので消してしばらく経つ。

緊急の連絡はDMでやりましょうって合意できているし、DMはメールでも通知が来るようにしているので、ひとまず困らないだろうと思っているけど、実際どうかはわからない。

あとグループウェアにも気軽にアクセスできないようにしたいけど、それはちょっとめんどうそうで悩む。

休み中に疲れが溜まる要因を減らしていきたい。