理系学生日記

おまえはいつまで学生気分なのか

チームのタスクを全部ソースリポジトリのissueとして記載するのは違うのではないか

先日チームのレトロスペクティブで、タスクがチケット管理できてないよねという話があがった。 チームで仕事をするときのタスク管理はだいたいGitLabやGitHub、Azure DevOpsで行おうとするのだけれど、うまくいかないことが結構多い。

なんでうまくいかないのかなと思案したけど、そもそも「会社のe-Learningを受講する」とか「なんかの決裁を取得する」とかいうものをソースリポジトリで管理しようとしているのがおかしい。

開発に直結するものはリポジトリのissueを起票して議論すれば良い。 一方で、開発に直結しないものをソースリポジトリで管理しようとしても、issueとして記述する内容から違う。 そういうものを混ぜるのは害悪な気がしてきた。

もともとそういうタスクをissue化しようとしていたのは、そういったタスクも開発スピードに影響するからという理由づけをしたためだった。 だけど、それは理想時間と現実時間を切り分けて対処するべきなのでは。

開発と直結しない話も含めて管理しようとすると、ソースリポジトリとかではなく、asanaとかで管理した方が良さそう。

ISUCON11予選で敗退してきました

一昨年同様、@kkasaiさん、@hondaYoshitakaさんとISUCON 11へ参加しました。

一昨年はインスタンスチェックに失敗し、まさかのスコア0でフィニッシュでした。 一方で、今年はきちんとインスタンチェックも無事パスし、最終的なスコアは12,000前後という結果です。 参加チーム数が598組(1,421名)、最終的なぼくたちの順位は半分よりちょっと上くらいになりました。

対象となったお題やレギュレーションはこちらで公開されています。

インフラ

今回対象となったクラウドサービスはAWSでした。 チューニング対象となるシステムのインフラは、競技を開始した10:00から、公式から提供されるCloudFormationのコードを展開して構築します。 構築自体はスムーズに完了しました。あらかじめGitHubに登録したsshの鍵でログインができるようになる親切設計です。

構成としては、VPC内に3台のEC2が構築されるというシンプルなものです。

個々のEC2の中はほぼ同一構成となっており、nginx、アプリ(isucondition)、MariaDBで構築されていました。

ファシリティ

コロナ禍のため、全員が自宅からの参加です。 コミュニケーションはDiscord、リポジトリはGitHub、タスク管理や図の共有はmiroを使いました。

ソースの共有はCode With Meを利用しました。 こちらは、全員がIDEを共有しながらリアルタイムにコーディングを行えるというものです。 ある種モブプロのような開発になるわけで、私にとって初体験でしたが、極めて良い経験でした。

状況の確認

エンドポイント

ISUCON11にはNew Relic支援プログラムが存在するため、こちらを利用してパフォーマンスの悪いエンドポイントを確認することから始めました。

@kasaiが10個くらい出してくれて、おおよそ以下のエンドポイントから潰していこうという方針です。

  1. /POST /api/condition/:jira_isu_uuid
  2. /GET /api/isu

それぞれのソースについては@hondaYoshitakaさんが解析し、チューニングポイントを検討してくれました。

nginx

私の方もnginxのログ設定に$request_timeを加え、アクセスログにレスポンスタイムが出力されるようにしました。 その後、ベンチマーカーがどのようなアクセスパターンなのかを解析してみていました。

https://f.hatena.ne.jp/kiririmode/20210828090438

MariaDB

DB側で遅いクエリに関しては、@kasaiがスロークエリを取得し、いくつかマズそうなクエリを見つけてくれました。

Golang

ぼくはGoのプロファイラ(net/http/pprof)を担当しました。 前回は以下のような結果を取得し、ここからもチューニングポイントを絞り込んだので、それを再びという気だったのですが。

f:id:kiririmode:20190915083503p:plain

なぜか今回はその結果が取れず、CLIでpprofを操作して、時間がかかっているポイントを調べることにしました。

(pprof) peek getTrend
Showing nodes accounting for 40.65s, 100% of 40.65s total
----------------------------------------------------------+-------------
      flat  flat%   sum%        cum   cum%   calls calls% + context
----------------------------------------------------------+-------------
                                            17.63s   100% |   github.com/labstack/echo/v4.(*Echo).add.func1
         0     0%     0%     17.63s 43.37%                | main.getTrend
                                            17.62s 99.94% |   github.com/jmoiron/sqlx.(*DB).Select (inline)
                                             0.01s 0.057% |   runtime.duffcopy
----------------------------------------------------------+-------------

やったこと

インデックスの付与

MariaDBには全4テーブルがありました。 このうち今回のアプリは、isuisu_conditionあたりに多くのクエリが発行される実装になっています。

+------------------------+
| Tables_in_isucondition |
+------------------------+
| isu                    |
| isu_association_config |
| isu_condition          |
| user                   |
+------------------------+
4 rows in set (0.000 sec)

大量レコードの検索キーになっているにもかかわらず、インデックスが貼られていない部分にインデックスを張っていきます。

POST /api/condition/:jia_isu_uuidのBulk Insert対応

POST /api/condition/:jia_isu_uuidのAPI実装では多数回のinsertを独立したINSERT文で行っていたため、 これをbulk insertに変更しました。

POST /api/condition/:jia_isu_uuidでリクエストをdropしない

スコア加点要素としてISUのConditionは重要です。

ISU のコンディション確認(GET /api/condition/:jia_isu_uuid)で、ユーザが前回確認したコンディションより新しいコンディションを確認した際に加点

  • Info(50 件ごとに 20 点)
  • Warning(50 件ごとに 8 点)
  • Critical(50 件ごとに 4 点)

負荷走行時における加点について

その登録APIには一定確率でリクエストを落とす実装がされていました。

/ POST /api/condition/:jia_isu_uuid
// ISUからのコンディションを受け取る
func postIsuCondition(c echo.Context) error {
    // TODO: 一定割合リクエストを落としてしのぐようにしたが、本来は全量さばけるようにすべき
    dropProbability := 0.9
    if rand.Float64() <= dropProbability {
        c.Logger().Warnf("drop post isu condition request")
        return c.NoContent(http.StatusAccepted)
    }

これをすべて処理するように修正しました。 おそらく、我々がカリカリにチューニングできれば、このあたりがボトルネック化してくるのでしょう。 一方で今回我々がチューニングできた範囲だと、ここがボトルネックになるまでに至らなかったようです。

GET /api/isu/:jia_isu_uuid/iconのCache-Control対応

椅子画像を取得するGET /api/isu/:jia_isu_uuid/iconでは、データベース上に格納されているバイナリ画像を読み出し返却します。 ベンチマーカーはConditional GETに対応していること、 それなりにサイズの大きな入出力を都度させたくなかったため、当該APIではCache-Controlヘッダを返すようにしました。

画像が最新でなかったとしてもスコア減点要素はなかったため、雑にmax-age=60を指定しています。 スコアが下がったらチューニングする方針でしたが、スコアはあがったのでこのままいきました。

    c.Response().Header().Set("Cache-Control", "max-age=60")

なお、画像をデータベースではなくファイルとして保存しnginx等からサーブさせるという案もありましたが、 実装にちょっと時間がかかりそうなので見送りました。

MariaDBの外だし

EC2は3インスタンスある中で、ベンチマークは1インスタンスに対してのみ実行します。 MariaDBはCPU boundになっていたこともあり、アプリが利用するデータベースを別EC2インスタンス上のMariaDBへ向けるようにしました。

ベンチマーカーは、アプリの初期化用エンドポイント/initializeを実行し、データベースのデータを初期化するところからスタートする仕様になっています。 このため、単純にアプリ設定のホスト名変更ではうまくいきませんでしたが、そのあたりは@kasaiさんがうまく対応してくれました。

Echoのログを抜いた

pprofのpeekを見る限り、ログ出力に時間がかかる様子も見えていたため、EchoのLogger Middlewareを抜きました。

最後にインスタンスチェッカーだけ流し、タイムアップを迎えました。

$ isucon@ip-192-168-0-11:~/webapp/go$ sudo /opt/isucon-env-checker/isucon-env-checker
環境をチェックしています...
全てのチェックをパスしました

感想

振り返ってみれば、やればよかったことはたくさんあります。 コネクションプールの設定やAPMの駆使はチーム内でも話にあがりましたし、ジェイウォークも直したかった。

やるべきことはわかっているのに、実装スピードの出ないもどかしさがありました。 Golangを多少触ったくらいではなかなか対応できません。チームとして苦しんだポイントでした。

ただ、それでも2年ぶりのISUCONはとても楽しかったです。 アーキテクチャ図等が整備された中で計画を立て確実に進めていくのではなく、 瞬発的な仮説検証を繰り返すアプローチは、エンジニアとして非常に刺激的でした。 本戦進出を目指して、またぜひ精進して参加したいです。

もう1つ感動したのが運営で、これだけの規模のコンテストをここまでの完成度で運営できるというのはそれだけで優勝では ? 本当にお疲れ様でした。

なお、この日が妻の誕生日と重複していた点については参加申し込みをした後に気づき、誠心誠意謝罪いたしました。

SlackをRSSリーダー化してみている

ぼくはもともとRSSリーダーのヘビーユーザーでして、日々の情報収集はほぼRSSリーダーに依拠していました。 しかし、Livedoor Readerが終了し、乗り換えたInoreaderも無料枠だと150個くらいのフィードしか登録できなくなりました。

情報収集としてのRSS購読はたしかにオールドテクノロジー化しているように見えます。しかしぼくは、これ以外に向こうから情報を届けてくれる術を知りません。 ぼくのようなRSS難民は世の中に溢れているはずなのですが、あまり困っているという声も聞かず、みなさんRSSリーダーに有償登録されているのでしょうか。

前のプロジェクトで同様の相談をしていたところ、SlackをRSSリーダーとして使って言えるという声を聞き、最近はそれを試しています。

工夫していること

SlackをRSSリーダーとして利用するときの問題は既読管理です。

ぼくの要望としては以下の2つがあります。

  • 自分がどのエントリまで読んだのかを正確に記録しておきたい
  • チャンネルを開いた時、最後に読んだエントリから閲覧を開始したい

一方でSlackの既読管理の粒度は粗く、以下のオプションしかありません。これではぼくの要望の一方しか実現できない。

そういうわけで、pin機能を利用しつつなんとか上記の要望をクリアしている状態です。

  1. Slackの既読管理としては、前回最後にメッセージを確認したところから表示され、そこから確認をスタートします。チャンネルは既読になります。を選択
  2. 最後に読んだエントリへpinを立てる

何が難しいのかというと、読み進めていたチャンネルから別チャンネルに移動後、元のチャンネルに戻るケースです。 元のチャンネルに戻ったとき、当該チャンネルは全エントリが既読になっている状態になります。つまり、どのエントリまで既読にしていたかの情報がなくなってしまう。これはSlackのRSSインテグレーションが「ここまでは読んだ」という情報しか持たず、「どのエントリは読んだ」という粒度で管理していないからでしょう。

これを補うためのpinを立てる運用、という感じです。

ヘッドセットが欲しい

打ち合わせをする時間が多いのですが、ぼくはもっぱらAirPodsを使っています。

AirPodsはバッテリで動作するのですが、そんなに潤沢なバッテリは積んでいません。公式ページをみると、バッテリー駆動時間は最大5時間のようですが、体感的にはもっと短い。これはまぁ、結構使ってきたっていうこともあるのでしょう。

ただ、やはり打ち合わせ中にAirPodsのバッテリーが切れてしまって、途中からスピーカーフォンで会話せざるを得ない状況は避けたい。 プラスして、バッテリの有無のわからない状態も避けたい。どうせデスクでしか使わないので、この際だから有線でもかまわない。 聞く人の認知の邪魔にならない方が打ち合わせはうまくいくので、できればマイク音質の良いのが嬉しい。さがしたい。

3ヶ月ぶりの物理出社

コロナ禍でフルリモート勤務を長いことしているのですが、今日は請求書の対応やらロッカーの整理やらをしなければならず、3ヶ月程度ぶりに出社しました。

会社の同僚や先輩、上司と顔を合わせられるのはやっぱり良い。

ZoomMicrosoft Teamsでテレビ会議をすればある程度コミュニケーションは取れるのですが、 やっぱり「能動的」なアクションを取らないとコミュニケーションが取れません。

執務スペースを見渡すだけで、あるいは聞き耳を立てるだけで手に入る情報が、リモートだと手に入らない。 この情報の有無は結構大きくて、チーム作りやリスクマネジメントのインプットがどうしても小さくなります。 これまで気づけていた問題に気づくことが難しくなり、ほとんど努力せずに作り出せていた空気感を作れなくなる。 能動的に努力することはもちろん、もっと感度をあげないとダメなんだろうなというのを感じました。

とはいえ、出社は良いものである一方で通勤はだるい。会社までの通勤時間である片道1.5時間がつらい。リモートワークはやめたくない。 もはやコロナ禍以前に戻ることはできないので、この状況下でどう上手くやっていくかを考え続けるしかない。

ひな祭り

3/3、ひな祭りです。

雛祭り(ひなまつり)は、日本において、女子の健やかな成長を祈る節句の年中行事。

wikipedia:雛祭り

うちの子供は女の子なので、業務中に休憩をとってお祝いをしました。

ちらし寿司でも買おうかという話をしていたのですが、近所のスーパーでは売り切れていたっぽい。 ひな祭り当日にひな祭りの準備をするのはダメなのだなということを実感する。 去年も同じようだった気はするんだけどどうだったんだろうか。去年の記録がないのでよくわからない。

手毬ずしはあったので、手毬ずしでお祝いした。 子供はあんまり理解してないっぽい。

保育園でお雛様のコスプレで写真を撮ってもらっていたし、 お雛様の絵も書いていたので、なにか「おひなさま」という人がいるのだなというイメージはあったみたい。 一方で「おひなさま」と「おひめさま」の違いがわかっていなかったようで、ときどき混同していた。

LastPassから1Passwordへ移行

安易なパスワードは脆弱です。このため、多くの文字種を使った長いパスワードを作らないといけません。

一方で人類の記憶力も一般には脆弱です。 長く複雑なパスワードは覚えられませんし、一部の民族は始祖の巨人の気まぐれにより、簡単に記憶を改竄されたりします。

したがって、人類がパスワードを記憶するということ自体が脆弱です。そこで登場するのがパスワード管理ソフトというジャンルですね。 各サービスごとに複雑なパスワードを生成し、それをソフト側に保存させておく。これにより、使い回しのないパスワード管理が実現できます。また例えばMacとWindows、iPhoneとiPadというように端末間でそれらパスワードを同期できれば便利でしょう。

LastPassから1Passwordへ

この手のソフトウェアは最近有償がベースになってきていて、それでも無償でかなり広範囲に使わせてくれていたのがLastPassでした。

しかし2021/3/16より、LastPassの無償版だと端末間での利用に制約がかかるようになります。 LastPassでは「Computer」と「Mobile Devices」に端末を2タイプに分け、一方のタイプの端末でしか使えないようになると。

Starting March 16th, 2021, LastPass Free will only include access on unlimited devices of one type.

そういうわけで、これを契機に1Passwordへ移行することにしました。 2020年くらいまでは1Password無償版で何とか使っていたので、実際のところは1Password有償版での利用再開です。

1Passwordのプラン

1Password Familiesを契約することにしました。年払いで月$4.99。 このプランだと、特定のパスワードは家族で共有、特定のパスワードは自分のみが参照可能といった形での権限管理を実現できます。自分が死んだときに数々のWebサービスや有償サブスクリプションを簡単に止められるようにしたかったので。

iOSからの1Password利用

[設定]->[パスワード]->[パスワードを自動入力]から1Passwordの利用が可能です。

パスワード入力画面で1Passwordに保存したパスワードを呼び出せるようになります。

そのほか便利なところ

結構良いなと思ったのが、1Password側に2FAの設定を寄せられるという点でした。 最近は多くのサービスが認証アプリによる2FAをサポートしていますが、結果としてGoogle Authenticatorに代表される認証アプリと管理が分割されてしまっていました。 1Passwordは認証アプリ機能も持っているので、ログインに必要な情報はおおよそ1Passwordで完結できるようになりました。

同じ姿勢で長い時間座っているだけで腰が痛くなる

ぎっくり腰は癖になるという話はよく聞いていたのですが、 3年前にぎっくり腰を患って以来、やたら腰を痛めるようになりました。

腰が痛いのはつらい。腰が人体の要であることを痛感します。

カフェでノートPCを使うのに夢中なムーブをカマしていたのですが、さて帰るかと立ち上がったときに違和感がありました。 「おや、なんか腰痛いな」というくらい。歩けないほどではないし、実際に歩いて帰った。

家に帰ったら徐々に痛みが増してきて、立ち続けるのがつらい。 前傾姿勢を保たないと立っていられないほど痛みが増してきて、体を横にすることとしました。 そしたら眠気が襲ってきまして、身を任せるままに昼寝。起きたら破滅という流れです。寝返りすらきびしい。

そんな状態で子供がボディプレスをキメてくるなどして、非常に厳しい1日でした。