ぱいぱいにっき

Pythonが好きすぎるけれど、今からPerlを好きになりますにっき

#japanpm Japan.pm 2021でhotwireをMojoliciousから使うLTをしてきました

久しぶりに勉強会発表して緊張したなということで。

yapcjapan.connpass.com

これに参加&LTしてきました。

参加の感想

  • 型や(静的|動的)解析に関連する話が多く、Perl型マニアとしてはとても満足するトークばかりでした。

scrapbox.io

Perlでも関数の型をチェックしたい - Speaker Deck

scrapbox.io

  • データ解析ツールを作った話やAWS CDKの解説、InnoDBクラスタの仕組みなど、実世界に寄った話も盛り上がり、裏トークも合わせて聞くと深く知見を得られた気がしてよかったです

  • オフラインにあって、オンラインカンファレンスではなかなか成立しにくいものとして、セッションの間の時間での廊下での交流や、会の後の懇親会がありますが、それに似たような体験を得るために会の後に、交流会と称してDiscord内にボイスチャットグループを話題別にたくさんつくって解決していて、かなり面白かったです

  • 交流会に「型」と名付けられたルームがあったので思い切って飛び込んでみたのですが、型や静的解析関連の登壇をされた方や興味がある人でPerlの型チェックにまつわる悩みトークが出来てよかったです。

blog.sushi.money

型チャンネルの話題は参加されていたid:hitode909さんの記事のも言及があります。

発表

speakerdeck.com

Mojolicioushotwireを使うトークをしました。というかhotwireっていうのがあって、それがこう言う事ができて、しかもフレームワーク依存はないですみたいなのを伝えたかった感じです。

スライド中のコードはこちらに上げています。動かす手順なども書いたので、興味がある方はぜひ。

sandbox/turbo/example1 at master · mackee/sandbox · GitHub

個人的な反省

  • 速く終わりすぎた。多分速く終わりすぎていたと思う(計測していない)
    • 手元にストップウォッチを置くの忘れていた
    • 通しの練習が不足していた
    • このへんは終わらないので事前に説明を飛ばすと決めていたところを予定通り飛ばしたが、時間を手元に用意しておけば飛ばさない判断もできていたと思う
  • hotwire/turboがなんであるか(そもそもJSのライブラリであるよとか)そういう前提の話をすっ飛ばしてしまったなと、スライド見返していて思った
    • 20分40分のトークだと、この辺の前提条件の共有みたいなのを、僕はかなり念入りにやるのだが。というのもWebエンジニアのカンファレンスでハードウェアの話をするなど聴者の専門分野と違う話をすることが多いので。ただ久しぶり&LTというのもありすっぽ抜けていた
  • 今回は高度な話題はなくして中級者レベルの話題を目指したが、そういう人たちにとって重要な「これをやって何が嬉しいか」みたいな部分の説明が不足していた
  • HTMLぶん投げて書き換えているみたいな当たりの面白さの説明は、「面白さが分かる人向け」なので果たして入れるべきかどうか、しかし演者が面白いと思うことをいうのが重要だなとは思う

hotwireについての感想

主にフロントエンドエンジニアやサーバ含めて全部やるエンジニアからhotwireという技術が広まることについての懸念がインターネット上で話されている。というのも、turboやstimulusという技術は現在のWebフロントエンド技術スタックからは連続していない技術であり、どちらかといえばRails/Django/Laravel的なHTTPリクエストを受けるとレスポンスとしてHTMLを返す技術の延長線上に存在しているものだからだと思う。これらは技術の相互運用が難しい。turboの前身であるturbolinksでも同種のハレーションが起こっていた。その時の記憶を思い出す人も多いのだと思う。

昨今のいわゆる"モダン"と呼ばれるWeb開発の環境は、JavaScriptによって駆動するブラウザ上で動くGUIアプリケーションに対して、サーバはHTMLではなくJSONやその他シリアライズフォーマットで返すものが指すものが多い。僕も仕事ではそういう環境でAPIサーバを書いている。もっというとBFFと呼ばれるアーキテクチャを採用すると、フロントエンドリソースの配信やキャッシュ、複数のAPIサーバからのレスポンスを集約するなど、フロントエンドのためにサーバサイドでしかかつて出来なかったことを、フロントエンド技術の延長線上で行うことが提唱されている。ここまでくると、サーバサイドとしてはWebアプリケーションを書いていると言うより、domain specificなデータベースのプログラムを書いている感覚に近くなってくる。そして、APIをしゃべる汎用的なデータベースでよいのであれば、そもそもmBaaSでよいということになり、FirebaseやAppSyncといったサービス、GraphQLとhasuraのようなAPIサーバを構築してくれる技術などが発達していく。

おそらく、僕の予測では、この流れは一般的な消費者がアクセスするようなWebサービスでは止まらないと思う。一方で、そこまでみんなGUI書くのが好きかと言われたらそうではないし、限られた組織向けのWebアプリケーションに、ユーザ体験が良くすることが得意なフロントエンドエンジニアを充てられるかどうかと言えば、今はノーであるという現場が多いのではないのだろうか。ここで指している"限られた組織向けのWebアプリケーション"というのは、社内向け管理画面だとか、勤怠管理システムだとか、在庫管理システムだとか、一般のお客さんからは見えないところで動いているサービスのことを指している。

とはいえ、体験が悪い勤怠管理システムに悩まされている業界の人は結構いるようで、僕もかつてはその一人であったけれども、やはり優れたGUIというのは充てられる人がいるいないに限らず、必要なものだと思う。しかし、優れたデータベースを作れる人が必ずしも優れたGUIを作れるかと言われたらそうではないし、いても超人の類なので、片手間でも優れたGUIを作れる機構というのは、選択肢として悪くないと思う。優れたGUIを作るのには技術だけではなく、作ろうとするシステムに対する深い理解も必要だし、他の例を引用するなどの労力も必要である。現代のフロントエンドスタックは 、それらとデータベースの操作をきちんとやる作業の片手間にやれるほど、お手軽ではないと僕は感じている。なので、hotwireを管理画面に採用するのを同僚に提案してみようと思う。フロントエンドの人には、僕ら以外の一般ユーザの体験を向上するのに注力したほうがビジネス的には正解だと思うからだ。

ただ、hotwireを剥がして一般的なフロントエンドスタックに載せ替えますといったときに、フロントエンドエンジニアの呪詛が激しいと思われるので、採用するなら採用したときのメンバーで閉じて作り続けていく覚悟が必要なんだと思う。ただ、turboを剥がしても普通のMPAとして作動するように作れとドキュメントにも書いてあるので、turbo特有の挙動に頼ったUIを作らなければ、そこまで移行は難しくない、そもそもMPAのアプリケーションをSPAにするのと同じぐらいの労力にとどまるのでは、とは思う。ただ、MPA -> SPAも難しく、というか静的なドキュメントをGUIアプリケーションという別物に作り変えるようなものなので、そもそもページの再設計が必要。

turboをMojoで使うときに困ったときのtips集

onchange=this.form.submit()でfetch requestが発火しない

this.form.submit()をすると、普通のHTMLであれば、submit buttonが押されたのと同じ動作、つまりフォーム送信が行われる。しかし、turboが導入された環境だとsubmit buttonが押されたらページ遷移せずにfetchが使われてリクエストし、レスポンスに応じてturbo driveやturbo framesによるDOM要素の書き換えが行われる。

ただ、今回のデモアプリで示した、トグル状態が変更したら送信するようなときに、素のJSだとタグ内にonchange="this.form.submit()"と書いてしまうのがお手軽だが、これだとturboの環境でもフォーム送信&ページ遷移が行われてしまう。

ググったところ以下のフォーラムのスレッドが見つかり、このスレッドではstimulusでrequestSubmit()というメソッドを呼び出していたが、普通に書いても使えそうだったので、onchange="this.form.requestSubmit()"と書いたところ、ちゃんとfetchのほうが発火したので、そのようにした。しかし今書いてて思ったのだが、turbo無し環境だとこれ動くのか?と思った。

Triggering Turbo Frame with JS - #23 by walterdavis - Hotwire Discussion

turbo streamsでメッセージを投げつけるときの形式

どうやるんやろと思って、turbo-rails gemを呼んでみたのだが、railsにはそんなに詳しくなくて、ぼんやりしかわからなかったので、とりあえず分かる言語でまず調べるか思ったところ、以下のGoでturbo streamsを使う記事が出てきた。

Turbo Streams powered by Go WebSockets - DEV Community

この記事で上げているリポジトリには、

{ 
  "identifier": 
     "{\"channel\":\"Turbo::StreamsChannel\",  \"signed_stream_name\":\"**mysignature**\"}",
  "message":
    "<turbo-stream action='append' target='board'>
    <template>
        <p>My new Message</p>
    </template>
     </turbo-stream>"
}

という形でWebSocketにメッセージを流しているっぽいので、これをそのまま採用したところちゃんと動作したのでそのまま採用している。しかし、messageキーの部分はわかるが、identifierの部分は何なのかわかってない。ActionCableとかそっち方面由来なんですかね?消しても動くのではと密かに思っている。

...

思ってるんなら試せばええやんと思って試したところ、普通に動いたので、message部だけで良さそう。

件のリポジトリのHTMLにはこういう記述もあって、

<!--
  <turbo-cable-stream-source 
    channel="Turbo::StreamsChannel" 
    signed-stream-name="**mysignature**"
    >
  </turbo-cable-stream-source>
-->

たぶんチャンネルとかシグネチャを設定できるんだと思う。これに対応するturbo-rails gemの実装はここだと思う。

turbo-rails/cable_stream_source_element.js at main · hotwired/turbo-rails · GitHub

これ、turboじゃなくてturbo-railsの方なので、turbo単体で動くのか?という気持ちがある。過去のバージョンだと動いていたのかもしれない。

turbo-streamsのmessageの文字化けだとかタグがエスケープされる

どちらかというとPerlあるあるなんだけれども、utf8フラグがあべこべの状態でながすとどうこうというやつである。最終的には、

my $rendered = $c->render_to_string(template => "append_messages");
$connection->send(decode_utf8(encode_json({
    identifier => encode_json({ channel => "Turbo::StreamsChannel", signed_stream_name => "**mysignature**" }),
    message    => $rendered->to_string =~ s/\n//gr,
})));

こんな感じのへんてこりんになってしまった。この中には様々なトラブルシューティングの跡が詰まっている。

まず、MojoのWebSocketの接続オブジェクト(ここでいう$connection)は、Mojo::Transaction::WebSocketなのだが、これにもちゃんとJSONエンコード機能はついている。それを使うと上のコードはこのようになる。

$connection->send({ json => {
    identifier => encode_json({ channel => "Turbo::StreamsChannel", signed_stream_name => "**mysignature**" }),
    message    => $rendered,
}});

render_to_string使ってレンダリングしたやつも、to_stringと言いながら実はMojo::ByteStreamというオブジェクトなので、そのままJSON::XS::encode_jsonに突っ込んでも、シリアライズ出来ませんと怒ってくる。しかし、$connection->sendの機能でJSONエンコードした場合は、Mojo::JSONが使われるので、こいつはMojo::ByteStreamを食べられるので、そのままエンコード可能だ。

しかし、Mojo::JSONには普段は有用だがこの場合にはおせっかいな機能があり、

f:id:mackee_w:20210220143554p:plain

ドキュメントの記述なのだが、つまりXSS対策のためにHTMLタグをエスケープする。しかし、今回は実際にレンダリングするためのHTMLを送っているので、エスケープはされてほしくない。というわけで、Mojo::JSONを使わずにJSON::XSを使っている。

それから、s/\n//grとしているのは、改行コードが入っているとそれがそのまま\nと表示されてしまうからである。これ思ったが、turbo-rails側はどうしているか見に行けばよかったなと思った。この記事も長くなってきたので、今は調べないが、ちゃんとした解決方法を教えてほしい。

あとは、全体をdecode_utf8することである。JSON::XSにもutf8フラグを立てたり立てなかったりする機能があるのだが、それだとうまくいかなかった。なんでこれでうまくいって、JSON::XSでうまくいかないかは分かっていない。こんなのでPerlで仕事している。仕事でもあまり深く考えずにdecode_utf8 encode_utf8を試して文字化けしなかったら採用みたいなことをやっている。utf8フラグがどうのこうのあるが、こういうのは変数を受ける側がどう言う状態を想定しているかで挙動が違うので、知識よりはライブラリ側のコード読むか実際に試したほうが良いみたいな悪い学習をしている。

WebSocketのコネクション維持と再接続

turbo側が切っているのか、それともMojo側が切っているのかは知らないが、Webインスペクタを眺めていると素のnew WebSocketだと無通信が30秒で切れるような挙動を起こしていた。このへんは昔にWebSocket接続管理ミドルウェアを作ったときの知識から引用して、5秒おきにping messageを投げるようにした。そうすると切れなくなった。人生そういうものである。

my $id;
$id = Mojo::IOLoop->recurring(5 => sub ($loop) {
    if (!defined $c->tx || $c->tx->is_finished) {
        $loop->remove($id);
        return;
    }
    $c->send([1, 0, 0, 0, WS_PING, 'Hello World!']);
});

このとき、接続が切れている(is_finished)などのケースでは、繰り返しのタイマーを削除するなどしている。ちなみにMojoはイベントドリブンで動くので、こう言う芸当が可能なのである。ノリもJavaScriptに近い。

しかし、それでもアプリケーション再起動とかのときに接続が切れて切れっぱなしで不便というのもあったので、ReconnectiongWebSocketを使っている。これと同名でおそらく機能が同じのライブラリもあり、こちらはメンテされている雰囲気でなんだかモダンだし、普通ならこっちを選ぶんだが、cdnjsから配信されているからという理由で、前者を選択した。とにかくJavaScriptのビルド環境を用意したくないという気持ちが全面に現れている。

まとめ

Japan.pmまたやってほしい。というか** Weekly Talksみたいな感じで、週1とにかく集まるみたいなのもありでは無いか。それほど技術的な雑談に飢えている。特に会社外の人の意見を吸いたい。

あと技術は触ってみると印象が変わることがあるぞい

Backends for Frontendsはあるが、Frontends for Backendsはなぜ無いのか

この記事では僕の以下の疑問について書き出して、思索し、議論を喚起するものであり、何らかの答えが書かれているものではありません。

Webフロントエンドエンジニアに向けたバックエンドサーバーを構築する仕組みは様々提案されているものの、その逆であるバックエンドエンジニアに向けたフロントエンドを構築する仕組みがあまり提案されていない

この記事を書くきっかけ

以下の記事を読んで、フロントエンドの技術スタック内でサーバサイドアプリケーションの一部の責務を負うような動きがどんどん加速している現状であるが、我々バックエンドエンジニアはどういう動きをすればいいか考えている。

zenn.dev

zenn.dev

筆者の立場

  • 自社開発のWebサービスのバックエンドアプリケーション開発を担当している
  • 現代フロントエンドに関しては、社内ツール程度はNuxt.jsを用いて書いたことがある。2C向けのプロダクションコードではデバッグ目的でTypeScriptを読む程度
  • 関心があるのは効率かつユーザのニーズを満たせる永続的データ構造とそれをうまく扱えるバックエンドアプリケーションの設計

なぜBackends for Frontendsと分かれているのか。

日本のほとんどのWeb開発現場においてはフロントエンドエンジニアとバックエンドエンジニアという2つの職能が並立して存在していると、僕の観測範囲では思う。しかし割り当てられたロールのラベルがそうなっているだけで、用いる技術はかぶっているケースもある。

現代の一般的なWebアプリケーションの構成は以下のようになっていると想定している。

f:id:mackee_w:20210110173335p:plain

これはかなり簡略化した形であり、例えばほとんどのサーバサイドWebアプリケーションにはログ集計解析基盤もあるし、フロントエンドスタックにSentryなどを用いたエラー監視スタックが含まれていることもあると思う。また、僕はフロントエンドエンジニアの仕事をすることはあまりないので、どうしても解像度に偏りが出てしまう。あくまでも、同僚がやっていることを外から観測するとこういう形で触っていることが分かれているようだ。さらにいうと僕が関わっているWebサービスはSPAとサーバサイドテンプレートレンダリングを併用しているものの、フロントエンドスタックでのSSRは採用していない。

そして職能ごとの責務はこのように分かれている。

f:id:mackee_w:20210110174117p:plain

ブラウザからこっちと、ブラウザがネットワークを通して向こう側に分かれている。ただし、フロントエンドスタックのビルドを通してキャッシュ制御を行っている領域もあるし、HTMLテンプレートはUIにも関わってくるため、フロントエンドエンジニアが担当している。というわけで赤字にしておいた。

つまるところ、「あなたはここまで」「わたしたちはここからここまでやる」と壁ができている。そのほうが専門性が深まり、効率が高くなるからだ。でももっと細かく分けることも出来る。実際、我々のチームは社内の他のプロジェクトではSREチームと呼ばれる部署が担当している部分もサーバサイドエンジニアが行っている。インフラ構築であったり、監視の部分だ。でもこれは切り出す事例が世の中にはたくさんある。更にいうと、データベースの管理はDBAという職能の方が専門的にやっているケースも世の中にはある。やろうと思えば無限に分割できるし、やろうと思えば全部やれる。全部やっている例が、世間ではフルスタックエンジニアと呼ばれること僕は理解している。

また、昨今ではこれらの一部をSaaSやPaaSに任せる例もある。僕のWebサービスAWS上に構築され、メインのDBはAmazon Auroraを用いていることから、DBAおよびSREの一部の役割をAWSにやってもらっていると考えられる。アプリケーションによっては、FirebaseやVercelなどを用いると、CDNから先の向こうをほぼ全てサービス側にやってもらうことも可能である。

フロントエンド技術がバックエンドへ揺り戻しされる対する考察

じゃあなぜ全部やらないのか。それはそれぞれの技術スタックが複雑になり難易度が上がってきたからだ。僕の記憶では2010年以前のWeb開発においてはフロントエンドエンジニアと呼ばれず、マークアップと言う仕事として、現在のサーバサイドエンジニアが片手間にHTMLとCSSを書いたり、現在のデザイナーがこれらを書いていたと思う。しかし、Webが単なるページから動的なアプリケーションに進化し、HTMLがドキュメントを表現するための記法から、UIを記述するための基盤として利用されるようになった。そのため、より多くの技術の理解が求められて、マークアップ周辺の仕事はフロントエンドエンジニアという職能に切り出されてきた。同じ現象は、サーバサイドエンジニアからSREという職能が切り出されたのにも似ている。

一人で担当できないぐらい、技術が発展したから複数人で切り分けて担当するようになった。では、なぜフロントエンドエンジニアがわざわざ現状切り分けられているバックエンドの領域に足を踏み入れようとしているのか。それは2つの理由があると思っている

フロントエンドエンジニアが達成したい目的がフロントエンドのスタック内では収まりきらなくなった

Server Side Renderingという技術は、ブラウザサイドでのJavaScriptの実行では実現できなかったことをサーバサイドで実行することで解消する。アクセス一発目のレンダリングスピードを高めるためにサーバサイドリソースを使ったり、CDNキャッシュを利かして解決する。また、JavaScriptを解釈しないクローラに対しても有効に働く。しかし、本来はブラウザ向けに書かれたコードをサーバで実行するには制限が多い。フロントエンド感覚でサーバサイドで動くコードを書くのは未だに難しい。

そこで、React Server ComponentはSSRが抱えていた、SPAのアクセス一発目のみにフロントエンドのコードをサーバサイドで実行するのではなく、SPAのコンポーネントを局所的に常にサーバサイドで実行したものを透過的にクライアントが取得する点で違いがあると考えている。境界を再設定することで、よりフロントエンドが主でバックエンドが従である関係を強調し、扱いやすくしている。

フロントエンドでの実行に比べて、サーバサイドでの実行はいくつかのメリットがある。ユーザのブラウザに依存しないコントローラブルな実行環境、実行内容やコードの秘匿性などだ。このへんをどう実現するかは、様々なアイディアが出てくると思われる。しかし、どうやって運用するかどうか、うまく運用するかという点についてはもう少し事例がほしいと感じる。

この辺の議論はこちらの記事にもまとめられている。

zenn.dev

◯◯エンジニアの〇〇には関心領域が込められている

じゃあみんなフロントエンジニアがフルスタックエンジニアになればいいじゃん、それが幸せと思うが、僕はそうはならないと現実的には思う。一人の人間が関心を寄せられる領域というのは思ったよりも狭い。UIを構築するためのマークアップは専門的な知識が必要だし、ビルドパイプラインにも、HTTP通信についてもそうだ。既にフロントエンドエンジニアが抱えている技術の領域は広く、そして深化していっている。

エンジニアというのはくっきりしているか、ぼんやりしているかの過多はあるものの、その仕事をするときのある程度の軸が存在すると僕は考えている。これを関心と呼ぶ。関心の周辺の領域というのは、効率よく学習でき、モチベーションもあるが、関心から遠いと効率が悪くなる。仕事上のWebサービスというのはユーザに見たり使ってもらったりして、その結果直接的・もしくは間接的にお金をもらうことを目的としているが、それはどのエンジニアであっても共通している目的だ。しかし、関心がそれぞれ違うし、それによって目的に対する手段が変わってくる。それがフロントエンドエンジニア・バックエンドエンジニアとラベリングされている物の本質である。一般的には責任の範囲とも言われるが、僕の仕事場ではあまりそれが当てはまらない。得意不得意はあるものすべての技術領域には物理的にはチーム内の人間は手を出せるので、責任範囲より関心の違いといったほうがしっくりくる。

どうしても、普段触ってる関心領域より遠い技術領域は学習の効率が悪くなってしまう。フロントエンドエンジニアで、普段はUI周りを触っているエンジニアほどバックエンド方向の技術領域は精度良く進めるのは難しい。これは世の中の情報についてもそうで、「フロントエンドエンジニア向け」「バックエンドエンジニア向け」とわかりやすいように情報が分かれている。BFFに必要とされる技術は、これまでのバックエンドの知見をかなり含んでいる。しかし、情報収集の際にフロントエンドエンジニア向けばかりの情報を読むと、理解が遠回りになってしまう可能性があるのではないか。

こういったラベリングは、主に求人上のマーケティングワードや、働く人のコンフォートゾーンとしても機能していたが、それが実態を表さなくなってくると思われる。実態が先か、言葉が変わったり、新しい言葉が生まれるのが先か、それちょっと予想がつかない。

バックエンド側からのアプローチがもっとあってもよいのではないか

Backends for Frontendsがフロントエンドの人が使いやすいように、バックエンドの仕組みをフロントエンドの技術スタックで構築したり、理解しやすいように抽象化するという目的もBFFにはあると思われる。では、バックエンドの技術スタックでフロントエンドを構成するアプローチは考えられるのか。

RailsのHotwireやPhenixのLiveView等が挙げられる。これらはSPAをトラディショナルなテンプレートレンダリングを行うフレームワークで作ることに対する回答だと見ている。また、vuguという、Goのフロントエンドライブラリがある。GoでVueライクなコンポーネントを書いて、wasmで実行するものである。本来、ブラウザ向けの技術スタックがNode.jsを介して動いてBFFを構成するように、バックエンド向けの技術スタックがフロントエンドで動く一例だと思う。

また、そもそもバックエンドだけ書いて、フロントエンドの記述を最小限に抑えるアプローチもあるのではないか。バックエンドアプリケーションの記述を最小限にするために、BaaSやサーバレス環境を用いる手法に対するアプローチである。これが僕が捉える、Frontends for Backendsであると感じる。実際この記事でいいたいのはここだけです。

僕は仕事では、GraphQLをしゃべるサーバを書いている。ここで思うのは、GraphQLさえ吐いて、あとは汎用的なコンポーネントを配置すればWebアプリケーションが作れたらいいのになと思う。例えば、管理画面のような独自のデザインやブランディングが必要ない場面ではbootstrapのようなコンポーネントとGraphQLのクエリを紐付けることさえすれば、そういったSPAが構成できるのではないか。React Adminがかなりこれに近いが、それにしてもReactやJavaScriptツールチェインの知識が多少必要である。

管理画面向けで言うと、Vironがある。VironはSwaggerを元にしたAPIサーバを書けばUI側を自動構成できるフレームワークである。こういったものをフロントエンドエンジニアの人はどう考えているかを知りたい。

github.com

しかし、一定の管理画面ではこれらは成立するだろうけれども、現代はSPAは、独特のコンポーネントのライフサイクルが存在する。これを、自動生成のツールだけで実現できるかは僕はわかっていない。また、多くの人が使うWebアプリケーションだと、出来合いのコンポーネントでは実現できないようなUIやインタラクションも存在すると思う。そういうのを人手を介さずに生成するには難しい。そこまで来るとノーコード, ローコードの領域に入ってくる。

というか僕がここまで言っているのは、得意なこと/力を入れたいこと以外はノーコード, ローコードで解決したいという話に近くなってくる。SaaSを活用してWebサービスを作ることもそうだし、サーバレスも、実際はサーバ運用レスであることもその証左だと思う。

僕は、バックエンドが得意だ。だからフロントエンドは楽して書きたい。そもそもフロントエンドがないとCLIのアプリケーションしか作れないからだ。

まとめ: とはいえ結局全部やることになる

もともとは一つの職能であった、Webエンジニアが技術の発展に伴って複雑化し、分業化されていったことを話した。しかし、それがBackends for Frontendsに代表されるような、それぞれ分割統治されていた技術領域だけで解決できなくなり、他方の領域の一部を担当するような現象が起こっていることを話した。

最終的には、全部やる人と、一部を深くやる人に分かれていくのではと予想している。実際にはクラウドコンピューティングという領域ではそれが起こったのだ。かつて、インフラエンジニアというデータセンターのサーバラックを管理したり、調達するような職能の人は会社内にいたり、委託されてそれをやる専門業者がいた。そういう仕事のやり方もまだ残って入るものの、AWSに代表されるクラウド事業者を用いてサービスを構成する際には、インフラエンジニアはクラウド事業者内で深く作る人と、クラウド事業者が提供するサービスを構成して組み合わせて使う人に分かれた。それでより高度に、より堅牢なサービスを作れるようになった。

フロントエンド・バックエンドにもそういう変化が起き、あるコンポーネントを深く作る人と、それを組み合わせる人に分かれていく。そうすることで、必要な知識を分担することが出来、さらに高度なWebサービスが作れるようになる。ただ、それどういう職業であると求人市場で言うか、どういった給与体系/雇用体系、サービス体系なのか。まだ僕にはわかっていない。

2020年の振り返りと2021年の抱負

はい、今年は色々ありましたね。しかし、他人と共有しやすい「色々」があったのであって、毎年色々はあったのではないか、つまり今年は多くの人が同じことを話題にした年であると言えるのではないか、そう考える日々です。

去年のやつ 2019年の振り返りと2020年の抱負 - ぱいぱいにっき

登壇とかイベントとか執筆とか

CircleCI ユーザーコミュニティミートアップ#8

circleci.connpass.com

speakerdeck.com

CircleCIのイベントで話した新しいAPIを活用した話。しかしこのあとにCircleCIもいくつかの新機能が入ったり、社内で一部使い始めているGitHub Actionsも大きく変わったので、振り返るとここはActionsで作るなあとかそういうのもある。これは1月なのでまだオフラインイベントが開催されていた時期。

Tsukuba Mini Maker Faire 2020

あと、仕事とは関係ないのだけれども、2月にTMMFで出展をした。

tmmf.jp

f:id:mackee_w:20201231205113j:plain

内容としては、昨年のbuildersconで頒布したFMラジオバッジや自作キーボードなどを展示していた。周囲の出展も面白かった。会場のローカルで熱気が凝縮した雰囲気もあり、東京でやるのとは別にまた出展したいと思った。

吉祥寺.pm23【オンライン】

kichijojipm.connpass.com

あと、オンラインイベントになったあとに、吉祥寺.pmで仕事で取り組んでいる話をした。

speakerdeck.com

この話でおこなった、GoでGraphQLをさばくサーバは無事本番投入して元気に今も動いているので、どこかで話をしたい。

Perl Hackers Hub

それから、WEB+DB PressPerl Hackers Hubでまた記事を書かせていただいた。

gihyo.jp

仕事でも、比率は少なくなったものの、まだまだPerlを書いているを書いているので、コミュニティに何らか還元をし続けたい。

ISUCON 10

isucon.net

藤原組として出た。他のお二人とは普段からよく一緒に仕事していたけれども、ISUCONに一緒に出るのは初めてであった。なんなら藤原さんとは出題時の移植を過去にやったことがある。

成績としては予選落ちだったが、並行回答チームというエキシビジョン枠の形で決勝に参加させていただいて、successしたチームの中では全体3位の成績だったので、良かったなとなった。来年は決勝に出たい。

オンラインイベントについて

みなさんも認識している通り、今年はリアルイベントが激減してしまった年だった。なので、登壇自体もあまりなく、オンラインの勉強会に行ったのもあんまりなく、今年は技術的な悩みを聞いてもらったり、自分が体験していないことを聞くような機会も少なく、今年、それと来年以降の技術に対するスタイルを変えなければならないのかな、とか思っていた。まあ、もがいても体力を食うだけなので、今は様子見状態ですが。

僕は技術勉強会やカンファレンスに行くときの目的として、一番に懇親会をおいていた。本編の登壇は生で話を聞いたり、質問をすることも大きな価値であるけれども、その話を肴に懇親会で他の参加者と技術的な話をして理解度を深めることで、僕は知識を得ていった体験が今まではあった。

一方、今年主流となったオンラインイベントの懇親会は同様の体験を得るのは難しい。今年の前半は、オフラインができなくなったので、オンラインにその代理の体験を求める動きが多かったのだけれども、オンラインはオンライン、オフラインはオフラインという別のものであり、それぞれ代わりになるものではない事がわかってきた。なので、オンラインのイベントでは代わりではなくオンラインならではの情報の取り方、接し方をしなくてはという気持ちがあるのだけれども、なかなか僕なりの答えを見出せていない。

代わりに一人でドキュメントと対峙することも多くなったし、一人でモックをガッと作ってしまうことも多い。発表の場があまりないので電子工作関連もM5Stackを使って色々作っているが、すぐにパーツを使い回すために崩したり、社内の仲間内だけの共有をしていたりする。作ることに専念できていていいのか、それともシェアとコラボレーションのサイクルが回っていかないことを損と考えるのか、わからない。。。。

仕事

今年は仕事の内容が変わった。去年までは社内ツールの作成だったり、技術検証をやっていたのだけれども、今年は運用中のWebサービスのサーバサイドエンジニアの仕事をやっている。その一環が以下のような仕事。

techblog.kayac.com

techblog.kayac.com

今年前半にこのプロジェクトに慣れて、後半は新機能の実装とともに、コア機能をほぼ全部PerlからGoに書き換えるという仕事をした。Goに書き換えた結果、コードの責務が整理されて、ある程度、型安全に守られながら書きやすくなり、レスポンスタイムが安定し、またDynamoDBに置き換えたことでほぼ無限のスケールアウト性能を手に入れた。

よかった、よかったのだが、GraphQL, DynamoDB, クリーンアーキテクチャなどの新しく導入した技術たちは、扱うのが初めてであり、今の時点でもある程度の歪みが生じてしまっている。また、Perlとの旧システムとの連携を取るためにアーキテクチャ的な無理をしているので、来年はこのあたりの解消を確実にやっていくのが、足元の目標ではないか。

今年得た技術として一番大きいのは、メインのDBを使い慣れたRDBMSではなくDynamoDBを使うことにしたことである。僕が今まで一番使ってきたデータストアはMySQLであり、ロックの仕方やパフォーマンスチューニングに一定の蓄積された知識があった。そうではなく、全く未体験のNoSQLを使うのはなかなかに抵抗があることであり、できるのかなと思ったが、理屈では出来て感情が拒否しているという感じだったので、やってみたらちゃんと動いてよかった。なんならボロボロでread/write throttlingが起こる状態からproduction readyの状態に持っていったチューニングもやったので、そのへんもどこかで話してみたい。

一年を通してかなり真剣に付き合った技術としてGraphQLがあるが、エコシステムは独特であるものの、数あるスキーマ定義言語/メッセージングプロトコルの一つであるので、あんまり自分の中では特別視していない。今後も型有りRESTful API定義言語として付き合うと思う。

とはいえ、サーバサイドの変化の流れもある。今年はEC2で運用されていたトラディショナルなアプリケーションをECSを使ったフルコンテナ環境に持っていったこともしたが、よっぽど理由がない限りはこれがLambdaやCloudRun/knativeといったサーバレス環境に行く流れを感じている。またコンテナやサーバレス環境で賄えないような機能要件も、FirebaseやAuth0やstripe、SendGridといったSaaSを併用するようになっていくと思う。ただ、これは僕らが向き合っている市場環境で取りうる最適な選択肢であって、コスト面や機能面で折り合わずに自作することもあると思う。けれども、僕には内製することが製品に大きなアドバンテージを与えるわけではない場合には、外部の部品を使うのを、前よりもためらわなくなったと感じる。

それでも、コアの機能は自分たちで作り続ける。むしろそれに専念するために、必要ないところはSaaSを使う。今の仕事でいうと、トーナメント表の進行管理や対戦管理が自分たちのシステムで今一番価値があるところだと僕は思っているので、それにはかなり力を入れる。それ以外は、外部サービスをできる限り使う。

ただ、SaaSをフル活用するというのは、知識のロックインにもつながる。あるSaaSで手に入れた知識を他のSaaSで使えるとは限らない。こういうとき、僕はプログラムを書くというより、組む、組み合わせると言う事が多いが、自分がモノをイチから作っている感覚ではなく、すでにあるパーツを組み合わせて新たなサービスを作っていると感じるからだ。昔の言い方だとマッシュアップと言ったり、別の観点だとオフザシェルフというかもしれない。

自分でイチから作らないことに対して、僕はあまり危機感を感じていない。知識を新たに手に入れなければいけないというのはそうだが、それはやればいいし、ドキュメントの読み方や探し方、共通のAPIの叩き方や扱い方などそういったメタ知識をためていくのが重要だと感じているからだ。最近だとwebhookといった形で、SaaSから自分たちで用意したエンドポイントにイベントを送信されることがあるが、こういったのもメタ知識である。

来年はLambdaにより傾倒するようなアーキテクチャを組むようになるだろうし、よりNoSQLを活用するようになるだろうし、SaaSも活用すると思う。それを胸張ってハックしているというぐらいにものにしたいと思う。

趣味

キーボード

今年は一気に失速した。現状、興味を失ったに近い状態にある。というのも、人と会わなくなったり、新しいハードウェア欲があまりなく自分のために作るつもりにもあまりなれなかったからだと思う。とはいえPulsarは今でも欲している人がいるし、異常にハンダがむずくて僕が発送を行うたびにめっちゃ細かいUSB-Cコネクタのはんだ付けをして発狂する現状が改善されればまた販売にチャレンジしてみようと思う。

買い替えた。

f:id:mackee_w:20201231214308j:plain

とはいえ前に乗っていたのは、実家近くの知り合いに譲ったので、また実家に帰ったときに乗らせてもらおうと思う。本当に楽しい車だった。色んな所に行った。

f:id:mackee_w:20201231214458j:plain

f:id:mackee_w:20201231214413j:plain

歩く

世の中こんななので、8月くらいまで家の中に引きこもりっきりで、仕事で時々会社に行くぐらいの生活で、睡眠の質も悪く、思えば体的に辛かったなと思ったのですが、9月ぐらいから歩くことを考えていた。

初っ端に9月に奥日光に行った。そんでもって戦場ヶ原を歩いた。

f:id:mackee_w:20201231214800j:plain

f:id:mackee_w:20201231214911j:plain

そのあとに伊豆で海近くの崖を歩いたりした。

f:id:mackee_w:20201231215000j:plain

f:id:mackee_w:20201231215018j:plain

それから、新たに始めたキャンプのついでに長瀞の山を登った。

f:id:mackee_w:20201231215128j:plain

f:id:mackee_w:20201231215157j:plain

ソロキャンプ

YouTube見てたらキャンプしたくなり、キャンプを始めた。車を持っていたのも大きい。

f:id:mackee_w:20201231215310j:plain

特に意識してなかったのだけれど、ワンポールテント+プラシパラトカの組み合わせをしているので、軍幕っぽい感じのコーディネートになっている。

来年へ

いつも来年はこれをしたいなあとここに書くのだけれど、今年は意外にも現状維持ぐらいにしか考えていない。死ぬのか?いや、今のまま平穏に暮らしたい。

とはいえ、状況が改善されてオフラインイベントがある程度開催され、旅行にもある程度行けるようになり、人と交流を持ちたい。それが叶わなければ、今どきにあったコミュニティを探すかもしれない。来年のことはわからない。。。

あーあと、Rustやりたいんだった。ずっとやりたいって言っているのをどうにかしたいですね。

Webサービスの機能を実装するか否か取捨選択のときに考えること

スマホアプリでも業務アプリでも同じようなことがあると思うんだけども、僕が携わってるのはツール的なWebサービスなので限定しておく。あとこれは個人的な体験から生まれた思考です。

 

ツール的に使われるWebサービスを作っていて、機能が十分に足りてなくて、何か追加するならこれ、という選択肢がいくつもある場合にはやはり全部一気に取り掛かることはできない。なんでかっていうと、一応言葉にしておくと、欲しい機能は無限に溢れ出てくるように見えるが、実装に使えるコストはそれに比べてかなり限定されているからだ。

 

金があればいいかと言われると、機能というのは人が多かれ少なかれ取り組んで出来るものだし、人を雇うには金が必要だが、金があれば必要な人が確保できるかというとそうではない。誰でもいいというわけではなく、ある程度プロジェクトの分野の知識がなければ、余計に時間を浪費する。また人が多くても、束ねるのも労力が必要で、小回りも効かなくなる。そこまで来ると小さい開発チームに分割していくのが業界的にも良いとされているが、マネジメントをオーバーヘッドと考えると、コストに対する効率が悪くなるように見えるかもしれない。なお、僕は直接成果物に触ってプロジェクトにコミットする職能ですが、マネジメントをやる人も必要なものだと感じる派閥です。

 

また、Webサービス開発においていい感じにチームを割れた経験は僕にはなく、少数精鋭で各個撃破が一番良いと思っている。しかし少数精鋭の効率の良いチームを作って回していくのも困難な話なのだ。

 

さて、各個撃破するということは、実装する機能に優先順位をつける行為に直結する。だとすれば、どういう基準で優先順位を付けるか。これにもいろいろな考え方がある。

 

まず、使われる機能であること。これは最優先事項である。使われない機能を実装することほど無意味なことはない。

そんなことは当たり前の話で、しないだろうと、皆さんは思うかもしれないが、サービス開発において、使われない機能を実装してしまうのは往々にしてありうる。

SNSや問い合わせなどで「この機能が欲しい」「この機能があればあなたサービスに乗り換える」という要望を受けることがある。これ自体は大変ありがたい。けれども、お客さんはその機能が実装されたからといって、その機能を使う義務はない。また、望み通りのものが実装される保証はない。いくら言葉で説明しても、頭の中のニュアンスまでそっくり他人に移植出来るわけではないからだ。

なので字面をそのままそっくり受けとるのは、僕は危険だと思っている。でも要望自体は捨ててはいけない。その言葉の裏に隠れたニーズを汲み取るが重要だ。でもこれは推測でしかなく、不確実性も高い。

 

では、実際に使われる機能を実装するにはどうすればいいか。これはチキンエッグ的結末で恐縮なんだけども、やってみてこれは望みのものでしたか、と聞くしかないと僕は思う。

要望する人がチーム内や会社内の人間であったり、開発費用を負担してくれるような協業パートナーであれば、まだ容易い。パワポでこんなのどうですか、と持っていって説明すれば、だいたい擦り合わせることができる。

ただ、相手が不特定多数の一般ユーザさんだと途端に難しくなる。直接言葉を届けられる相手以外だと、まだ世の中にないものを伝えるのは難しい。また限定的な機能でリリースしたり、モックを作るのも手ではあるが、お客さんは完成品が欲しいのであって、試作品を見たいわけではないので、これもまた上手く伝わるか自信がない。

 

僕は過去にゲームアプリ開発の現場にいたことがあり、以下のような体験をしたことがある。ゲーム開発では、本開発に入る前にモックやパワポの段階で面白いか、や、売れるか、を見て進退を決めるのだけども、試作の段階だとエフェクトや綺麗な絵や音が入っていないことが大半である。でも見たいのはゲーム性の部分なので本来は必要がない部分であるし、鍛えられた人であれば想像で補える。ただ、訓練されてない人は、きれいな表現を面白さの根源と勘違いしてしまって、判断を誤ることがある。きれいな表現は面白さを増幅するが、0に何掛けても0なので、0かどうかを判断しなければならない場で、掛ける側を見てしまうと事故が発生してしまう。

 

Webサービス開発でも同様のことはあり、経験を積んで試作品と完成品の差分を覚えて我々は試作品から完成品を想像できるようになる。ただ、世の中の大半の人はそうではないので、試作品を不特定多数の人に晒すのはかなり勇気がいることだと僕は思う。それが完成品と勘違いされるのが最もダメージがでかいからだ。

 

僕が思う、一般のお客さんがサービスに要望を出してそれを通すにはどうすればいいかを考えてみる。

それは、そのサービスを現時点で使っていること、さらにそれを周囲に公言していることが大事だと考える。サービスを使用している時点で利害関係者だ。もちろんまだ見ぬ未来にやってくるお客さんのために何か作るのも、サービスの拡大発展には必要である。しかし、これは博打で、使われない機能を作ってしまうリスクが高くなる。また、すでに使用しているユーザーであれば機能実装後にフィードバックを得て、作ったものがニーズからズレてるかズレてないかを判定できる。新しいユーザーというのは、新しく機能が実装されたからサービスを使い始めようというのではなく、大半が他人が使ってるから自分も使おうとなる。観察しているとそういう傾向だ。なので、すでにサービス自体を使っていて、フィードバックを得やすく、周囲に公言している人の意見を重要視したくなる。また、サービスと一蓮托生の人ほどとことん考えられたユースケースを提案してくるだろうというメタな推測もある。

 

ただ、これもある視点ではリスクをはらんでいる。今いる人にとってな大事な機能が、将来のユーザにとって障害になることもある。僕はこれをゲームアプリで学んだし、ゲームはそれがライフサイクルに組み込まれているものの、Webサービスはそうはいかんやろとも思う。

よくデザインや機能がリニューアルして既存のユーザから使いにくくなったと不評を受けるサービスがあるけれども、我々作る側は、上記のことを自分の中の天秤にかけて判断し、それから実際の綱渡りをしていると感じる。一歩バランスを崩せば真っ逆さまである。

 

サービス運用というのは、走ってる車の部品を走りながら取り替える行為をずっと続けるようなもの、と会社の人が言っていたのを思い出す。それは動いているプログラムを壊さないように機能を付け足す観点だと思うけれども、機能自体を付け足して、それ自体は完璧に動くが、サービス全体で見たサイクルが壊れることもしばしばある。

 

でも、これだから物を作るのって楽しいんですよね。

Webサービスの障害対応のときの思考過程

起こってほしくはないのですが、あらゆるWebサービスは完璧に動作する状態を維持することは難しく、やはり障害対応・トラブルシューティングといった作業が発生します。

筆者は普段仕事で障害対応を不幸なことによくやるのですが、障害対応のスキルというのはスピードや判断の正確さが求められるせいか、今までやったことがある人・ノウハウがある人に集中し、それ以外の人は眺めるだけ・あとからログを見返すだけの状態によく陥ることがあります。

これはWebサービスを開発・運用するチームとしてみたときにそういった苦労が特定の人に集中するのは良くないので、それを緩和する目的として、筆者が障害対応時に考えていることを記述してみます。なお、これが唯一の正解ではないとは思っているので、ツッコミや、自分はこう考えているよというのを教えていただければ幸いです。

具体的な手法を避けて思考の方法を述べているのは、障害というのはパターンで解決できることはそんなに無いからです。また、Webサービスによって監視項目やロギング、アプリケーションの特性、インフラ設計などがまるっきり違います。なので、調べ方や考え方など、手法に至る過程を記述したほうがより汎用的かなと考えました。

障害とは

ここで言葉の定義をはっきりしておきましょう。ここでいうWebサービス障害とは、サービスにビジネス上のインパクトを与えるような事象が発生したときのことを指します。インパクトの大小は問わず、例えばデプロイの結果、フロントエンドのUIがデグレってしまって特定の人しか使わないような機能のボタンが隠れてしまったケースも障害とします。人によっては「バグ」だとか、「不具合」と言うかもしれませんが、ここでは乱暴にひっくるめて「Webサービスの障害」として扱います。

障害対応の段階

Webサービスの障害を人間の病気と同様にあてはめてみると、急性期回復期慢性期に当てはめられます。また、病気の期間について調べたところ前兆期という言葉も見つけたのでこれも使ってみます。

前兆期

Webサービスの障害が発現しない段階です。しかし放置すると、障害が発現してしまうような状態を指します。

ここで運用する人が認知できてきたらもうけもので、Webサービス監視というのは、こういった障害の前兆を見つけるためにアラートを仕掛けています。しかし、病気と同様で症状が出ない状態で予兆を見つけるというのは至難の業であり、そもそもこういった前兆が来ると予想してアラートが仕掛けられているのであれば、それが出ないような対策もセットで取られていて、なかなかアラートが出ない、なんてこともあります。とはいえ、対策が無効化されるような変更を、考慮漏れでシステムに加えることもありますし、不要ではありません。

またアラートにはならないが、障害の前兆をメトリックから見つけることは、普段とは違う箇所を探すことでもあり、普段を知らなければ障害を認知することもできないわけです。筆者は、新しくWebサービスを担当することになったら、最初の一ヶ月ぐらいは、毎日数十分はサービスメトリックを見て、普段のWebサービスの負荷特性などを見ます。Webサービスによって負荷が高まる時間帯や曜日なども違い、また負荷が高まったときにまずボトルネックになる部分というのも違ってきます。そのために普段を知るのが重要です。

こういった前兆を見つけた場合、トリアージを行います。たとえば1時間以内に対策を取らないと障害に発展するとなれば、優先度高とし、障害に準じる形の作業を行います。1ヶ月放置しても良い障害であればissueを立てて、週に一回の定例などで共有しタスクの割当などをすればよいでしょう。

急性期

障害が発現した状態です。障害の発現状態には様々あり、サービス全体がダウンすることや、一部の機能が使えなくなる状態、全員もしくは一部のユーザがログインが出来ない状態になったり、ごくごく特定の条件で進行不能に陥るなどあります。

こういった急性期になったとしても、障害が見える形で現れていないこともあります。先述したように一部の状態にあるユーザのみが機能不全に陥っている場合、そのユーザからの報告がないと障害に気付けないこともあります。障害に気づくにはどうするべきか。監視であったり、ログ収集を行って障害を検知できます。一般ユーザ向けのサービスであればTwitterをサービス名でエゴサーチすることも出来ますが、障害が起こったときはだいぶ心が痛むので、はじめのうちはやらないことをおすすめします。

障害が起こったときには、以下のことをリストアップします。

  • 障害の技術的な事象
    • 「ページが見られない」だけでは正確ではなく、ステータスコードは200が返るが白いページ, 404や500などの想定していないステータスコード, 200が返りHTMLは返せているが一部の画像やCSS・JSなどのリソースが取得できずに壊れているなど、技術的な情報を正確に把握することが重要です
    • 「ページが壊れている」という報告だけでは、「なにかが起こっている」しか得られません。技術的な症状の情報を得て、原因を絞り込む手がかりとしていきます。
  • 正確な影響範囲
    • 全員に同じことが起こっているのか、それとも一部の人だけに起こっているかだけでも、原因の範囲を絞り込む手がかりになります。また、対応する人も変わっていきます。全員に起こっている場合、インフラ側に近い問題なことも多いですが、一部の人に起こっている場合は、インフラ側ではなくアプリケーション側・フロントエンド側に原因があることがあります
    • しかし、キャッシュに起因する問題は影響範囲がじわじわと広がっていくなど、これだけで原因の範囲を決めつけるのは早急です。あくまで当たりをつけたり、後述するダメージコントロールの判断材料に使います
  • 具体的な再現方法
    • 影響する人が限られたり、特定のページ・機能だけに障害が起こっている場合、障害が起こっている事がわかって、症状がわかったとしても、その障害を起こすための手順が特殊である場合があります
    • 再現方法を特定するために一番早い方法は、再現した人に前後の操作など聞き取ることですが、開発チームの中の人ではなく一般ユーザからの報告であった場合、困難になります。障害が起こったときから時間が経っている場合は忘れている場合もあり、不正確であることがあります。その情報をもとに調査する場合は、まず裏を取ります。裏を取るというのは、アクセスログであったり、こういった障害やカスタマーサポートのために出している行動ログから報告された行動と照らし合わせます。数字や固有名詞は見間違いなどが起こる事が多いので、裏を取ることが大切です。
    • こういったログがあれば、報告なしにそれだけで再現手順を導けることもあるのですが、当たりをつけるという意味で、一般ユーザさんからの報告は大いに役立ちます。

急性期にはまずここまでができれば御の字で、特に再現方法などは導けないこともあります。また、一つ忘れていると思われることで、「根本的な原因」があります。原因は突き止めることができればそれに越したことはないのですが、急性期に行うべきことであるダメージコントロールには必須ではないです。

ダメージコントロール

急性期に行うことは、根本的な原因を解決するのではなく、障害の被害が拡大するのを止めるダメージコントロールです。具体的なダメージコントロールの手法としては、

  • 障害が起こっている機能・ページを止める
  • サービス全体をメンテナンス入りさせる
  • 負荷に耐えるためにサーバ台数を増やす

などが挙げられます。これらの対処法に原因を特定し、それを直す手法は含まれていません。すぐに直せるのであれば、こういったダメージコントロールを行わずに障害の根本原因を断つのもいいと思いますが、そういった対処をすぐ取れないケースは多々存在します。

この考え方は、ユーザに対して正しくサービスを提供できないサービスはサービスを提供しないことよりも悪いという考え方のもとに立っています。また、アプリケーション側のバグによって、データを破壊していき、それ以後の復旧を困難にするような障害の場合は、直すことよりも止めることをより強く推奨します。

とはいえ、機能を止めたりメンテ入りさせたりするのは、ビジネス上のインパクトが激しく、サービスのステークホルダーの判断が必要です。障害対応はエンジニアだけではなく、サービス全体の判断が下せるステークホルダーの参加も必須になってきます。この場合のステークホルダーとは、お金を管理するプロデューサーなどの人たちを指します。ステークホルダーの人たちも、もしかしたらアラートを受けるべきかもしれません。みなさんが担当するサービスではどうなっていますでしょうか?

またユーザに対して影響が少ない形で、障害を止めるためには、Webサービスそのものに対する理解も必要です。普段、ユーザさんがどのようにサービスを使用しているのか、どういった機能を重点的に使っているのかという知識です。こういった知識がない状態だと、正しくサービスを止める判断ができなくなってしまうので、普段からドッグフーディングをすることも重要です。

複数の障害発生とトリアージ

障害というのは同時に起こることがあります。大体は一つの原因が、別々の障害として表出するのですが、急性期には2つの事象を1つの原因として考えて同時にさばく暇はありません。なので同時に別々の障害に対して2つを対処する状況に陥ります。

まず、2つの事象を明確に切り分けます。この切り分けというのも実は難しい仕事です。切り分けずに全部サービス止めるというのも一つの手です。もし切り分けられたら、優先度をつけます。例えばごくごく一部のユーザにしか出てない現象は後回しにしたり、データを壊さずに表示だけの問題であっても後回しにします。データを壊すような障害は直さない限り永続的に出てしまうので、できれば最優先で直したいものです。

もし、それぞれが優先度中ぐらいで、同じぐらいの優先度であったり、バックエンドととフロントエンドなど、明確に分野が切り分けられる障害であればチームを分けます。それ以後はチームは独立して働き、進捗を報告するぐらいにとどめます。

慢性期

障害の事象自体がダメージコントロールによって収束したとに訪れるのが慢性期です。障害の原因は表出していないが、隠された状態です。まずその隠された原因を調べます。

原因を調べる手がかりとして、急性期に挙げた「障害の事象」「影響範囲」「再現方法」が使えます。また、このときにはまだ調べていなかったログや、アプリケーションコード、インフラの設定などをすべて見直して障害と原因の因果関係について迫ります。

さて、こうして幸いにも障害の原因がわかるとそれを直すということになるのですが、この直し方についても考える必要があります。

例えば、永続的データストアのデータが障害によって汚染されている場合、それを修正した上でないと直したコードであっても障害が残ってしまう場合があります。その場合、データを直すバッチスクリプトなどを流すわけですが、先に直したコードを本番化しなければまたそういった汚染データが発生するなどの状況もありえます。どちらを先にやればいいのでしょうか。

1つの方法としては、ダメージコントロールと同様に障害の原因となった機能を一旦停止して汚染されたデータが新たに発生しないようにした上で、修正バッチスクリプトを流して本番化、それから機能の提供を再開させる手法が考えられます。

もう1つの方法として汚染されたデータを考慮したコードに修正するというのもあります。しかしこういった、後方互換性を考慮したコードは後々消す手間であったり、消し忘れて歴史的経緯が消えて混乱することもあるので、できれば直したらすぐに消してしまいたいところですね。

また、アプリケーションコードの場合、原因を修正するためにはテストコードもセットで書きましょう。デグレが起こって同じ障害が起こることは往々にしてあります。筆者の体感としても、一度障害が起こった場所はまた起こりやすい傾向にあります。なのでテストを書きましょう。テストを書いてCIを回せば世に出る前に障害を防ぐことができます。こんなにコスパがいいことは他のあまりありません。

回復期

晴れて障害が原因まで解消されました。ここで余裕があれば原因の分析を行いましよう。アプリケーションコードが原因であれば、

  • 原因となったPull Request・修正はどこか
    • コードレビューの観点に加えたり、もし自動検知できるものであれば、そういったテストを書いたり、linterを入れる
  • アーキテクチャに問題はないか
    • アーキテクチャが複雑・データのバリデーションが甘い、甘くなりやすい・予想外の挙動が起こりやすいなど
    • 漸進的にアーキテクチャに小修正を加えられるならそれが一番幸せで、全部書き直すのは最終手段
  • 障害に気づくまでの時間が遅くなかったか
    • 監視項目の充実
  • 障害の原因や影響範囲を探るのが困難ではなかったか
    • ログを増して似たような障害に備える

など、見直しをしつつ、障害対応を行わなかった他の人たちが追体験できるような場を設けると、チームとしての障害対応力も向上するかと思います。

まとめ

以上、障害対応を時系列をもって考えるポイントについて挙げてきました。まとめると、

  • 前兆で気付けるのが一番良い。そのために監視とログがある
  • 障害は見つけるのも困難であることが多い
  • 障害レポートは客観的に・技術的に正確に
  • 障害収束は原因の修正が一番の目的ではなく、障害を拡大させないこと
  • 原因を修正するときは直し方も考える
  • 障害対応についての情報を積極的に周りに伝える

といった感じです。この文章がみなさんの障害対応のときの備えになると幸いです。

2019年の振り返りと2020年の抱負

こんばんは、そろそろあけまして。

去年のやつ 2018年の振り返りと2019年の抱負 - ぱいぱいにっき

なんか読んでいると、デュアルモニターに苦労していたようですが、今もカチカチ切り替わってて目に悪いです。

登壇

色々やった気がする。

YAPC::Tokyo 2019

speakerdeck.com

Perl5で静的解析をするという野心的な話だった。それゆえ不完全燃焼に終わった面もあり、YAPC::Kyoto 2020で発展版をお見せしようと計画している。

mackee.hatenablog.com

golang.tokyo #25

speakerdeck.com

なんか静的解析ばっかりやってないか? 仕事ではそんなでもないし、何ならこの時期は自動生成できそうなコードを手でゴリゴリ書いていた気がする。

沖縄学生×企業エンジニア 7月大LT大会!!!

speakerdeck.com

アナグラ君 id:AnaTofuZ が行ったイベントに沖縄まで飛んでいった。沖縄は去年に引き続き3回目。やっぱり沖縄はごはんもうまいし、気候も良いし、いい。。。

この話はゲームのサーバエンジニアって職業がそこまでやること広まってないよね、他の業界の人や学生さんにって思って作った資料。

ただ、同じイベントであとの方に発表したこっちのほうで覚えている方が多いのではないか。

speakerdeck.com

builderscon 2019

speakerdeck.com

今年はbuildersconでトークしたし、これ以外にもゲリラのカンファレンスバッジを作成して配った。

hachiojipm.hatenablog.com

僕の中で今年、もっとも腕が伸びた技術は基板の制作だったり、表面実装のはんだ付け技術だと思う。

マスタデータNight #1

speakerdeck.com

もともとマスタデータNight的なイベントをやりたいと言っていた。

今年のCEDECでこのトーク CEDEC2019: 大規模モバイルゲーム運用におけるマスタデータ管理事例を聞いてやはりやらねばと決意し、

id:karupanerura さんをはじめ多くの方に協力していただき、イベントを開くことが出来た。その1発目のトークが上記のもの。

よく考えたら自分が発起人のイベントで一発目に話す人っているだろうか。でも、このイベントの方向性を決めるという意味で、はじめに喋ったほうがと自然に決めた。

イベント自体は大盛況で、アンケートも高評価が多かったので、早速次回を構想しています。

仕事

今年は特定のサービスのチームに所属するのではなく、要素技術の研究開発や、社内の共通ツールの整備などをしていた。会社ブログのAdvent Calendarに結構書いた。ドメインに密着していないことをやっていたからか、外に言えるようなことが多く出来たとも言える。

CircleCI API v2で自由自在に業務ワークフローのタスクを実行する - KAYAC engineers' blog

runc脆弱性に対応するためにうっかりECSからFargateにしました - KAYAC engineers' blog

ゲーム内お知らせをHugo+Netlify CMS+CircleCIで作りました - KAYAC engineers' blog

GitHub APIを使うBotたちのGitHub Appsへの移行 - KAYAC engineers' blog

新マスタデータ管理システムakashicの開発 - KAYAC engineers' blog

GoのDBライブラリと俺たち、それからsqlla - KAYAC engineers' blog

Lambdaを使ったサーバレス構成の社内アプリのデバッグのためにX-Rayを使ってみた - KAYAC engineers' blog

社内勉強会をサロンにmigrationしました - KAYAC engineers' blog

今年はこういう仕事してて、なんだか今まで一番肌に合う仕事だったなと思う。孤独感も感じることもあったが、僕は技術でオーバーキルすることに快感を覚えるようだし、チームの中でやっていくとオーバーキルはなるべく避けなければならない。自分が最後まで面倒見きるつもりだったら、技術的オーバーキルもある程度認められる面があると思う。もちろん底上げという面でオーバーキルをソフトキル程度にするために、周りに広めないといけない。テスト書きまくってメンテナンス日リティを向上するのもそういう手段だろう。

来年はあんまりこういう感じじゃないので残念だけれど、またこのスタイルに返ってこれるようにしたい。

個人開発

何より今年はキーボード作って売ったことだと思う。

mackee.hatenablog.com

中国の工場ではんだ付けしてもらってキット化した。

ほかにも「うんこキーボード」なるものも作った。

うんこキーボードは、個人で自宅で単価1000円以下のキーボードデバイスを100個量産できるかというチャレンジのもと作ったものだ。様々な電子工作的ハックを重ねて概ね達成できた。この成果を今後のカンファレンスバッジ頒布業に活かしていこうと思う。

旅行

今年は

2月に仕事で大阪

いい湯でした

なるほどこういう感じな

5月のGWに香港・深セン

なう

photos.app.goo.gl

7月に沖縄

モノレール

我々はいまボトルネックにいる

10月に名古屋

現地のあさごはんです

これ

11月にカンボジア・タイ・マレーシア・香港

まどろみのなかからこんにちは

まいにちビール

お先にいただきます

12月に箱根

おはようございます

めっちゃ行ってる。

香港・中国と東南アジアを回って、「英語が出来たらもっと楽しめるなこれ!!」となった。それ以降、ちゃんと英語ドキュメントを仕事で作ったOSSで書くようになった。

来年身につけるべき技術は英語やとなっている。

私生活

同人活動

今年はあまり同人活動は出来ていない。今年の会社の自己評価に、テクい文学表現を使ってしまっているのは、発散する先がないからである。会社の人へ、長文になってすまぬ。

会社の人と書いた技術書展の書籍には書いているので、ノー同人活動ではない。あと5月の文フリにはちゃんと出た気がする。出たっけ?

今年は骨折なしで終われそう。他に大きな怪我や病気もしてないので、様々なことに感謝。ただ、体重面が現状維持かちょっと上向きなので、課題感はある。

ゲーム

ONIとデスストぐらいか。あとテトリス99。どうタワはレート2500行った後にシーズン切り替わってリセットしてからやってない。この前ポケモンタワーバトルをしたら、どうタワ界の方に煽られまくって憤死したので、何らかの鍛えが必要。

大学生の時に使っていた実家の車を、この年末に自宅に戻ってとってきた。いじり甲斐があるので楽しみ。

婚活

3戦3敗。土俵に立ててない説もある。

2020年の抱負

仕事面は……まあ求められたことをやる感じだろうか。もしかしたら転機があるかもしれない。静的解析とマスタデータへの興味は失わないようにする。フロントエンドもちゃんと本気だしてやる必要がありそうな気配。

ハードウェアはキーボードを足がかりに同人ハードウェア界に本格進出するみたいな目標を立ててみる。同人誌もな〜ちゃんと書かんとな。

せっかく車を得たので、色んな所に足を伸ばしたい。足を伸ばして出来るアクティビティにチャレンジする。骨を折らない程度に。一緒にアクテビティにチャレンジする仲間を募集しております。

あと英語。何をどうするのが一番自分に適しているかわからんから、これって思ったのを片っ端からやって覚えるのがいいか。

まとめ

書いている間に年を越してしまった。しかし、文を書きながら年越しできるのは幸せなことなのかもしれない。小さなことに幸せを覚えると、コスパがいいって思ったので、今年はそういう感じで行く。

GitHub AppsでPithubを使うためのモジュールGitHub::Apps::Authと使った黒魔術の紹介

こんにちは、おげんきですか。最近体がバキバキなので良い整体を探しております。川崎近辺でお願いします。

この記事は、Perl Advent Calendar 2019の14日目の記事です。13日目はomokawa_yasuさんのTie::Fileで大容量ファイルを処理する - Qiitaでした。今回もtieの話を少しします。

GitHub Appsって何? なんで使いたいの?

同日の会社のAdvent Calendarに記事を書いたのでこれを読んでほしい。

techblog.kayac.com

つまり、要約すると、

  • GitHub APIを叩くときに、管理とか諸々の理由で個人のGitHub Token使うやつから、GitHub AppsのTokenを使いたい
  • 個人のGitHub Tokenと違ってGitHub AppsのTokenは1時間で有効期限が切れる
    • よりセキュアであると言えるし、たぶん用途としてはWebhook来たときにワンショットで処理する感じだから、Botみたいにこちらから能動的にやるみたいなのはメインの用途ではないのでは?
    • そもそも永続的な認証トークン自体は寛大すぎた
  • 何かしらのテクで既存のGitHub APIを使うライブラリを使ってBotが動くときもコードの変更をそこまでせずにGitHub Appsに移行したい

という内容です。

最近流行りのGitHub Actionsを使えば、対象のリポジトリを操作するためのGitHub Tokenが環境変数で降ってくるので、こういう苦労はないかもしれないですね。ただ、さらにそこから別のリポジトリを触るとかすると、必要な話に。

PithubでのGitHub Tokenの扱い

Perl製のBotGitHub APIを叩くときに、Pithubを使っております。

metacpan.org

他にもPerl界ではNet::GitHubってのもあります。しかしここでは、Pithubに焦点を絞ってお話します。

public repositoryのread以外の操作を行う場合はGitHub Tokenが必要です。というわけで、Pithubのコンストラクタでは以下のように、GitHub Tokenを渡して皆さん操作します。

my $pit = Pithub->new(
  user  => 'plu',
  repo  => 'pithub',
  token => 'my_oauth_token',
);

(SYNOPSISより抜粋)

ここでtokenは文字列として渡しています。しかし、Perlにおいて文字列は変なことをしない限り不変なもの。GitHub Appsで要求されるような「1時間毎にAPI叩いて更新する」みたいなのはできないわけです。

では頑張って「変なこと」をしていきましょう。

変なこと案その1 tie

みんな大好きtie変数です。Perl Mongerの半分ぐらいがtie期の麻疹にかかると言われています。僕も今回1日ほどかかりました。

perldoc.jp

tie変数とは、Perlのプリミティブ型の変数として振る舞いながら、アクセス・代入などの機構をPerlコード上で自作する仕組みです。

今回は、変数がアクセスされるたびに、

  • GitHub Tokenを変数内部に持っていない
    • APIを叩いてGitHub Tokenを取得しキャッシュしGitHub Tokenを返す
  • キャッシュしているGitHub Tokenを内部に持っている
    • 有効期限が切れてなければそのまま返す
    • 切れていれば再びAPIを叩いてGitHub Tokenを取得してキャッシュし返す

という機構を作ろうと考えました。

今回はスカラ変数でtie変数を作りたいと考えたので、Tie::Scalarを継承してpackageを作ります。このpackageは上記の機構をObjectで管理するクラスを受け取ってtie変数として利用できるようにします。

package GitHub::Apps::Auth {
    sub new { ... }

    # issued_tokenは有効期限を考慮しつつGitHub Tokenを返すメソッドです
    sub issued_token { ... }
}

package GitHub::Apps::Auth::Tie {
    require Tie::Scalar;
    our @ISA = qw/Tie::StdScalar/;

    sub FETCH {
         my $self = shift;
         return $self->issued_token;
    }
}

そして、これをtieでtie変数にします。

my $auth = GitHub::Apps::Auth->new(...);

tie $auth, "GitHub::Apps::Auth::Tie";

すると、このあと$authは他の変数に代入するたびに、有効期限を考慮したvalidなtokenを常に返してくれるようになります!

my $token1 = $auth;
sleep 3600;
my $token2 = $auth; # $token1のトークンは有効期限が切れているので違うトークンが返ってくる。

いや〜〜ヘンtieですね〜〜〜〜。

あ、すみません今のナシ。

つまり、これで我々は勝ったか!?に思えました。が、これでは要件満たさないのです。

触った瞬間に文字列になるtie変数

ところで今まで言ってたアクセスってなんでしょうか。他の変数への代入だとか、関数に対して引数に渡すときにも変数のアクセスが行われます。では上記のtie変数をPithubで使ってみましょう。

my $pit = Pithub->new(
  user  => 'plu',
  repo  => 'pithub',
  token => $auth,
);

これで良さそう・・・? ところでこの名前付き引数でコンストラクタにtie変数を渡したときもアクセスが行われます。アクセスが行われるということは......つまりPithubに渡されるのはただ文字列トークンであるということです。

いや〜〜これは欲しいものではなかった! ほしいのは外面は文字列として振る舞いながら、中はいい感じにメソッドが叩かれていいかんじにその時々最適な文字列を返すやつ!

もうちょっと詳しく言うと、tie変数は値を収める変数に対して魔法をかけるものであって、今回ほしいのは変数の中身である値のほうに魔法をかける物が欲しいのでした。

変なこと案その2 overload

演算子オーバーロードPerlに限らず様々なオブジェクト指向言語に存在する機構です。しかしそのトリッキーな挙動から、多くの言語では黒魔術扱いされているでしょう。Perlも例外では有りません。ただ、他の黒魔術に比べると、見た目のえげつなさは抑えられているかも。

perldoc.jp

普通、演算子といえば四則演算や、比較演算子を思い浮かべるでしょう。しかし、Perlは変数を文字列として評価する際の""ダブルクオートも演算子であり、オーバーロードが可能です。その他、文字列比較演算子eqや文字列結合演算子.なども含めてオーバーロードすれば、文字列として自然に扱えるオブジェクトを作成することが出来るでしょう。

ではやってみましょう。

package GitHub::Apps::Auth {
use overload
    "\"\"" => sub { shift->issued_token },
    "." => sub {
        my $self = shift;
        my $other = shift;
        my $reverse = shift;

        return $reverse ? $other . $self->issued_token : $self->issued_token . $other;
    },
    "eq" => sub { shift->issued_token eq shift };

    sub new { ... }

    sub issued_token { ... }
}

そんでって、Pithubに突っ込む!

my $auth = GitHub::Apps::Auth->new(...);
my $pit = Pithub->new(
  user  => 'plu',
  repo  => 'pithub',
  token => $auth,
);

これで、GitHub::Apps::Authインスタンスは、文字列として振る舞うので、Pithubの内部でもObjectとして保持されるものの、いざ使われるときはそのときに使われるトークンを払い出します。結論を言えば、CPANに上げているGitHub::Apps::Authはこの方式を採用しています。

勝った・・・! Pithubでもちゃんと使えるのと、1時間以上経ったら新しいtokenが使われているのを確認しております。

その他の工夫

上記のoverloadの定義だと、文字列結合をした際には「常に有効なtokenを返す不思議な文字列」としての性質を失ってしまいます。なので、文字列結合のメソッドはもう少し工夫をしています。

use overload
    "." => sub {
        my $self = shift;
        my $other = shift;
        my $reverse = shift;
 
        $other = "" unless defined $other;
 
        my $new_self = bless {}, ref $self;
        %$new_self = %$self;
 
        $reverse ?
            $new_self->_prefix($other . $new_self->_prefix) :
            $new_self->_suffix($new_self->_suffix . $other);
        return $new_self;
    };

クラスのattributeとしてprefixとsuffixの入れ物を用意し、文字列結合をしようとした際は、objectをcloneした上で、新しいobjectに結合を試みた文字列を入れています。

そして、tokenを返すときに、prefixとsuffixをその場で結合して返しています。

sub issued_token {
    my $self = shift;
 
    if ($self->_is_expired_token) {
        return $self->_prefix . $self->_fetch_access_token . $self->_suffix;
    }
 
    return $self->_prefix . $self->token . $self->_suffix;
}

この工夫で例えば my $header = "Bearer x-access-token:" . $auth;のように文字列結合をした場合でも、GitHub Tokenの部分は1時間ごとに入れ替わります。

この措置は、GitHub APIクライアント側で認証のために使うHTTPヘッダをあらかじめ組み立てて、それを使い回すようなケースを想定して実装しています。

まとめ

  • Pithubとかに渡すGitHub Tokenと入れ替えるだけでGitHub AppsのTokenが使えるモジュールを書いたよ
    • CPANにすでに上げているよ
  • 中身はoverloadっていう黒魔術を使っているよ
    • あんまりやりすぎると制御が効かなくなるよ、たぶん
  • patches welcomeだよ https://github.com/mackee/GitHub-Apps-Auth
  • PithubにPull Request送るのも試してみますね

明日はbayashi_netさんです。