2019年振り返り

はじめに

なんかよく見たら 2年前の自分がちゃんと書いてるくせに 1年前の自分は書いてなくて、進化してるのか退化してるのかわからなかったので、今年からはちゃんと書こうと思いました。

yosuke-furukawa.hatenablog.com

会社

マネジメント

マネージャーも3年目ですね。なんだかんだ、メンバーにも恵まれてるなと思うことが多いです。 メンバーがアウトプットを率先してやってるので、エンジニアコミュニティとして大きく形成されているなと思います。エキスパート同士が互いを教え合い、また競い合いながら目的を達成していくのは見てて気持ちが良いです。

2019年振り返り.md · GitHub

engineer.recruit-lifestyle.co.jp

qiita.com

recruit-tech.co.jp

qiita.com

recruit-tech.co.jp

背中を黙って預けておけるメンバーなので、特にマネジメントの仕事と言ってもキャリアの相談や仕事の相談に乗って一緒に考えることはしてますが、やってることはその程度といえばその程度で、細かい事は任せてます。「『お前はどうしたいんだ』ってよく言うのか」、と聞かれますが、「メンバーがどうなりたいか」と「メンバーにどうなっててほしいか」というwill と期待の話はよくします。逆に言うと日々のマネジメントはそれくらいしかしてないです。

R-ISUCON で 1 位に

エンジニアコミュニティといえば社内でやってるイベントの R-ISUCON で 1位取ったりしました。

recruit-tech.co.jp

嬉しかったのは R-ISUCON やってたら ISUCON の予選を3位で通過したことです。自分が通過したことももちろん嬉しかったのですが、 R-ISUCON をやっていた他のグループも通過していてレベルが上ってることを感じました。

yosuke-furukawa.hatenablog.com

関連会社の技術顧問になった

ニジボックスという弊社の関連会社の技術顧問も新規でやることにしました。

会社の技術的な質を上げるには、技術部分の底上げをすることとトップレベルを引き上げることの2つが重要だと思っています。

トップレベルを引き上げることは自組織のマネジメントで行っていますが、縁あって底上げをする役目も一緒に行うことになりました。といってもまだ10月からなったばかりなので、 JavaScript の研修を教えたり、そもそもコードを書く時に意識することを教えたりといった基礎的な所と意識的な所を教えています。

qiita.com

Chrome Advisory Board になった

Chrome にアドバイスする諮問委員会、 Chrome Advisory Board のメンバーになりました(正確には2018年から)。現時点では日本に 3 人の諮問委員会メンバーがいて、リクルートの他だと Yahoo!Cyber Agent のメンバーがいます。活動としてはいち早く新しい Chrome の仕様を教えてもらって試して、使った結果をフィードバックするというのが主な仕事です。

AMP をやったり、 Next.js 使ったりしてるのはその辺があります。 Next.js に最近送った PR も AMP 系ですね。

github.com

そんな使い方があるのかーという、仕様の勉強にもなります。 Google のAMPの中の人達にこの手の活動を通じて気軽に質問できるようになったのは大きいですね。

この他にも Paul Irish に気軽に lighthouse の中身を聞いたり、実際にサービスを見てアドバイスもらったりできて良かったです。

Japan Node.js Association の活動

会社でやったこと以外にも自分が持ってる法人の活動も書いておきます。

JSConf.JP 開催

今年はなんといっても JSConf.JP 開催できたのが大きいです。

yosuke-furukawa.hatenablog.com

今年の前半からずっと JSConf をやってたし、日本で JSConf が開催できたのは日本の JavaScript コミュニティ全体にとって良かったと思います。トークの内容もJSコミュニティを代表するにふさわしい内容だったと自負しています。

ただこれまで JSConf EU とかで登壇依頼してたのですが、来年はやらないので新しい場所に行く必要があるかも。。

あと、来年はなんと言ってもオリンピックがあるので、前回みたいな外部の会場を抑えるのは難しいかもしれませんね。。。 今7, 8, 9月にやる予定だったイベントが軒並み9月後半、10月、11月にずれてきてるという話なので、会場の検索中です。

ただ間違いなく2020年も JSConf はやります。

個人活動

会社、自分の法人以外の活動も書いておきます。

登壇系

技術的な登壇はもとより、マネージャーや組織づくり系の登壇、エンジニアキャリア的な登壇に呼ばれましたね。

この辺に呼ばれた(自分が話した)感じですね。自分の中で新しいなと思ったのは Global CFP Day と EOF 2019 ですね。

Global CFP Day は「海外登壇にチャレンジするにはどうしたらいいか」、というテーマでめっちゃ面白いカンファレンスでした。

f:id:yosuke_furukawa:20191231193941p:plain
Global CFP Day

このカンファレンスで紹介された「Present! A Techie's Guide to Public Speaking」という本は 海外カンファレンスのテックトークとはどういうものなのかを紹介した本です。かなり面白いのでマジでおすすめです。

www.amazon.com

EOF 2019 は自分がマネジメントでやってたことをちゃんと言語化した形になりました。

あまりちゃんとした形で表明してなったけど、自分は勉強会と同じ考え方でマネジメントしてるんだなという発見になりました。

執筆系

以下の場所に記事を寄稿しました。

gihyo.jp

マイクロサービスのつなげ方の章で BFF の話を解説させていただきました。 また、 CodeGrid は Node.js v12 - v13 で変わることの話をさせていただきました。

www.codegrid.net

www.codegrid.net

後半のマジで忙しい時に2連続で執筆依頼が来たのですが、これを断っていたら今回執筆ゼロだったので受けてよかったです。

OSS

最近ちゃんとコードも書いていこうという精神で、 Write Code Everyday に挑戦しています。まだ4ヶ月が終わろうとしてるところですね。マネージャーになったけど、コード書いていないっていうのは嫌だったので、コードも書くし、マネジメントもやるし、っていう精神でやっています。

f:id:yosuke_furukawa:20191231195047p:plain
Write Code Everyday 挑戦中

OSS で言うとリクルートのライブラリで、 Specter を作ったりしていました。

github.com

yosuke-furukawa.hatenablog.com

結構中身がわかってきたし、不具合も見えてきたので、 Next.js へのコントリビューションをもう少し本格的にやろうと思います。

github.com

Node.js core contributor としては活動が少し落ち着いてしまったので、もう少し復活させようと思います。

Podcast

fukabori.fm に出させていただきました。結構この活動がきっかけで周辺会社とも絡む機会を増やせると良いなと思っています。内容は社内ISUCONですが、かなりエンジニアコミュニティづくりに寄った話が多かった気がします。

fukabori.fm

Blog

この辺が盛り上がった気がしますね。もっと書きます。。。

yosuke-furukawa.hatenablog.com

yosuke-furukawa.hatenablog.com

来年に向けて

明らかにブログが減ってしまっているのでまずはペースを2017年に戻したいのと、2017年は海外登壇も多くやっていたので、それも復活したいです。 Node.js は QUIC や ES Modules を起点にまたコントリビューションできる所見つけてやっていきます。

そろそろ新しい言語をまた覚え直すとかやりたいし、その一方で他の人達が新しいチャレンジしてるのも見てて面白そうだなと思うので、好奇心を原動力に2020年もまたがんばります。

実践 Off the main thread

実践 Off the main thread

実際に Off the main thread をやりつつ、パフォーマンスチューニングをする際にどこに気をつけるべきかを今やっているので、それについて話します。

Off the main thread とは

JavaScript の処理は基本的にメインスレッドで実施します。JavaScriptの実行処理以外にも記述された内容を解釈するためのパース処理やGC処理もメインスレッドをブロックします。メインスレッドの処理が多いとUI jankと呼ばれるガタツキ、チラツキ、画面の固まりの原因になります。

UI jankが発生していると、ユーザーがクリックしたり、text入力をしようとしてから反応するまでの時間(Input Latency)が即時ではなくなります

このUI jankを無くすために、なるべくメインスレッドを阻害する要因を減らすことが Off the main thread と呼ばれるトピックです。

Input Latency

ユーザが入力してから反応するまでの時間を指しています。 Off the main thread は前述の通り、このInput Latencyを少なくするための試みです。 メインスレッドでLong Taskを実行している間、特にレンダリングなどの重たい処理を実行している間は入力があっても即座に反応しません。

f:id:yosuke_furukawa:20190319025141p:plain
Long Task実行中に入力処理があった場合

この場合、inputの処理はキューイングされ、後回しになります。

f:id:yosuke_furukawa:20190319025356p:plain
入力されてから実際に発火して反応があるまでの時間

このレイテンシを最小限にしつつ、スムーズに動作しているように見せないと、固まっているかのように見えます。

「どれくらいで固まっているように見えるか」ですが、Google が出している、RAIL と呼ばれる原則では、100ms 以内に反応がないと、ユーザーは遅れているように見えるという指標があり、少なくともそれを満たす必要があります。 ただし、 lighthouse では 50ms 以内にしないと警告が出ます(アプリが表示し切るまでにさらに 50ms かかると推定されており、トータルで 100ms が Input に対しての Response になるため)。つまり、 Input Latency は表示し始めてから 50ms 、 入力が始まってから 100ms 以内に反応する必要があります。

developers.google.com

developers.google.com

何が Input Latency の遅延原因になっているか

Input Latency の遅延原因となっているのは主に JavaScript の実行だというレポート結果があります。

tdresser.github.io

f:id:yosuke_furukawa:20190319030621p:plain
input latencyの中で処理の内訳

2300以上のサイトを調査し、最初の30秒間に入力処理をした際の処理の内訳を精査したところ、V8.Execute という JavaScript 実行中の処理で25% ~ 70%の時間が発生しています。

これを Web Worker など、 Worker Thread で行えるようにし、 Input Latency を改善するというのが Chrome での topic の1つです。ただし、実際にやってみると、それ以前の問題が多く、実際にやればやるほど「Worker 以前に処理するべき問題」のが多く見つかります。

Worker 以前に処理するべき問題を放置して Worker Thread にしても Input Latency は改善できても処理そのものが重たいので、結局もっさりした動きになります。

実践 Off the main thread

今回の話はこの input latency を改善するため、 UI Jank をどうやって見つけるかとそれを改善するときのやり方、また実際にReact などの SPA を改善する時に見つけたありがちな問題について解説します。

UI Jank の見つけ方

Chrome だと DevTools の Performance タブから比較的簡単に見つけられます。 Performance タブで赤くなっている箇所では、 Jank が起きています。

UI Jank
UI Jank

この Jank を見つけたら、そこから中で何をやっているか見つけに行きます。この JSConf.JP のサイトでは hydration 処理と呼ばれる SSRCSR の状態を同期する処理で Jank が起きています。

Hydration処理
Hydration処理

このケースで言うと、 Hydration と呼ばれる SSRCSR の同期処理が走った後、React の props が変わり、 React の render 処理が中で走っています。結果として hydration 後に render が走ることで React の state が SSR 時のものと同期されることになります。一方で、 Input Latency は落ちる事になります。

ただし、『 UI Jank がある == 不具合』ではないので、これを必ずしも直さないといけないわけではありません。現時点ではどうしようもない処理もあります。 Hydration はその典型です。

よくある Jank のパターン

この hydration 以外にも発生するケーススタディがありますので紹介しておきます。

スクロールごとに重たい計算をしてしまうパターン

スクロールするたびにガタつくケースはだいたいコレですね。よくあるのは、スクロールしたタイミングで要素を変更する lazyload や 無限スクロールのような処理があるケースで、実装がまずいパターンです。

実装がまずい、と一口に書きましたが、「重たいオブジェクトを毎回生成する」、「scrollのたびに現在のviewportに要素が入っているかを毎回計算する」といった処理を指しています。

前者はオブジェクトのキャッシュかインスタンス生成を減らす事を検討し、後者は Intersection Observer などの API で再実装が求められます。 また、 scroll イベントを毎回発生させる必要がないなら、 throttle, debounce といったイベントを間引くことで処理そのものが発生する回数を減らすことができます。

ちなみにスクロールに限らず、『頻度高く発生するイベントをトリガーに重たい処理をしている』事がそもそも問題になります。

他のケースとしては、 moment のような時刻計算をするためのオブジェクトを表示されるたびに計算し、その計算のたびにインスタンスを作ってから時間の差分を計算しているケースもありました。

不要な props を渡してしまうパターン

ここからは主に React の話ですが、React に限らず、 SPA の view libraryではよく発生すると思います。

<Foo {...props} />

のように prop を全て展開して渡しているパターンや、 実際には使わないけど渡しているパターンですね。Reactの SPA の場合、UI Jank の8割方はこれです。この不要な props を渡しているところのバリエーションが多いです。

不要に Reconciliation といった差分検出処理が走ったり、 render が走ってしまい、 Jank が発生しやすくなります。

shouldComponentUpdate を書いて除外するか、きちんと渡す時に精査してから渡してあげれば発生しません。また、型を真面目に書いていればある程度防げたりもするでしょう。

関数をそのまま handler に渡してしまうパターン

こちらもよく見ます。以下のような状況ですね。

<Foo onClick={(e) => { ... }} />

これも、ある種の不要な props なのですが、趣が若干異なります。アロー関数に限らず、関数をその場で定義した場合、propsを渡す際に毎回 function オブジェクトが生成されて React に渡されます。こうなると関数オブジェクトそのものが毎回変わっているため、差分検出処理時に必ず差分がある事になります。

useCallback 等で callback をmemo化した状態で渡すか、 class componentにするなら、関数定義を constructor で定義するか static 関数にするかして、関数を再定義しない方法で handler に渡す必要があります。

どうにもならないときの処理として Worker を使う

処理をダイエットしたけど、どうしても減らない、とにかく重い計算処理を走らせる必要がある、という時に Worker を使いましょう。 Worker は Comlink 経由で使うと利用しやすいです。

実際に利用したときのシーンは AirSHIFT のブログに掲載されています。

web.dev

// Cost計算する処理、 worker を comlink で promisified している。
import React from 'react';
import { proxy } from 'comlink';
 
// import the workerlized calc function with comlink
const WorkerlizedCostCalc = proxy(new Worker('./WorkerlizedCostCalc.js'));
export default function Cost({ userInfo }) {
  // execute the calculation in the worker
  const instance = await new WorkerlizedCostCalc();
  const cost = await instance.calc(userInfo);
  return <p>{cost}</p>;
}

ただ Worker にしただけで速くなるわけではありません。 Worker にすることで Main Thread を逼迫することは減りますが、そもそも無駄な処理がないかを地道に特定し、逼迫する時間を減らすことが一番重要です。

まとめ

Off the main thread だからといって、なんでも worker でやれば OK という話ではありません。自分のアプリ、サイトが遅い要因を特定し、ある程度性能を改善してからどうしようもない時に Worker を使いましょう。そうすると Input Latency だけではなく、処理全体が軽くなります。

Worker は速くなると言っても、 Main Thread の逼迫を軽減するためのものであり、使ったからといって手放しで高速になるわけではありません。一方で、使いこなせれば非常に強力な武器になります。 Comlink などのツールは使いこなせると良いでしょう。

また、仕様側でもよりよい API 設計を考えている話が去年から出ています。この辺も追っておくと良いでしょう。

nhiroki.jp

また、 『UI Jank』の原因は JavaScript のメインスレッド逼迫だけではありません。 CSS や Layout 計算などの要因でも数多く発生します。

Jank の原因をまとめたサイトやチェックリスト等もあるので参考にしてください。

calendar.perfplanet.com

docs.google.com

jankfree.org

2020年の Node.js, 2025年の Node.js (Web Standard編)

この記事は Node.js Advent Calendar の 25 日目の記事です。

qiita.com

Node.js の 2020 年はどうなるのか 2025 年にはどうなっているのかを予想していこうと思います。 ちなみに、あくまで筆者の予想にすぎないです。こうなるという与太話みたいなものだと思っていてください。

Node.js のこれまでと今後

Node.js は進化を続けていますが、 2018 年に語った通り、その進化の方向は以下のような方向に流れています。

  • Web Standard
  • Performance
  • Security
  • Stability

speakerdeck.com

今回は主に Web Standard の部分に限定して、これまでとこれからと更にその先を予測してみようと思います。

Web Standard 2020 / 2025

Node.jsは Web Standard に追従するという旨の発信はずっとしています。全ての Web Standard を follow するといっても実際サーバサイドには不要なものもあります。取捨選択をしながらなるべく追従をしていくという形になるでしょう。

ES Modules

ES Modules が Node.js v13.2.0 より、 ES Modules が experimental フラグが取れ experimental オプション無しで動きます(ただし、API status は experimental のまま)。相互運用性の方法もほぼ確定になりました。 Node の ES Modules 対応は 2020 年から徐々にその割合を伸ばし、 2025 年には利用されているモジュールが ES Modules : CommonJS が 3 : 7位の比率になる と予想します。 徐々に .mjsesm に対応してほしいみたいな issue 増えてきましたね。

github.com

github.com

github.com

100% のモジュールが ES Modules になることは考えにくいですが、新しいモジュールや使われてるモジュールを始めとして、徐々に対応するような段階的にアップデートが行われるのではないかと思っています。詳しくは以下のエントリに書いたので詳細をご確認ください。

yosuke-furukawa.hatenablog.com

QUIC

QUIC の initial implementation の PR が作られました。

github.com

まだ ngtcp2 を持ってきて binding を作った段階で、 http3 の実装などは現時点ではまだです。 Node.js の QUIC 及び、 HTTP3 の対応は 2020 年で初期実装が完遂し、experimental 付きでリリース、 2025 年には quic, http3 ともに experimental flag が取れる と予測します。

一方で http2 はどうなるんでしょうね。クライアントとしては生き残る気がしつつも、毎回クライアント側で http1.1, http2, http3 を呼びわけたくないので、統一的なAPIからコールされるようにならないと厳しいですね。

fetch

http1.1, http2, http3 のクライアント側の話で言うと、それらを統一的に呼び分けるために fetch を入れようという話もあります。今だと require('http')require('http2') のようにロードするモジュールから分ける必要があります。

github.com

このPRはただの既存の npm にある node-fetch を core に入れてみようとしただけの議論用のPRですが、 ES Modules のような大物が終わった後は fetch の実装を進めるのかなと予測しています。

2020年には、、、議論は進んで fetch working group ができる、 2025年には http3 とともに fetch も入る と予測します。

Streams

fetch とくると、気になるのは Stream ですよね。 WHATWG Stream と既存 Stream は違うものなので別物として実装はされると思いますが、今の所進捗が見えません。リポジトリはあります。ただ議論が止まってますね。

github.com

新しくモチベーションがある人が引き継ぐ必要がありそうな気もします。 fetch の議論を進めていく上で一緒に進んでほしいと思います。やる気があったら実装してみます。

ただこうなると、Joyee が書いたこのスライドにもありますが、 fetch の完璧に compatible な実装を用意は難しいですね。また、 Browser API と完璧に同じにする必要も無いので、 Node.js なりに互換はあるが微妙に別物な fetch になりそうですね。

2020年はわかりません、おそらく今の感じだと進捗はないでしょう fetch working group ができたらそこで議論再開ではないでしょうか。2025年には一旦 WHATWG Stream 無しで実装が進んでリリースされてそうだと予測します。

ちなみに Stream ですが、 新しく BoB Stream という提案がされています。これは今までの Push と Pull 型の混在した Stream ではなく、完全に新しい API で再検討された Pull 型の Stream です。

github.com

WHATWG Stream のものとはAPIも概念も異なります。こっちのが議論は進んでいるので入るのは WHATWG Stream よりは早そうですね。 2025 年には決着付いてるでしょう。

Web Assembly

Web Assembly 対応は v8 中心に進んでいる上に、 WASI 対応も v13.3.0 で experimental flag 付きでリリースされました。

github.com

つまり、 Web Assembly 対応したモジュールを読み込むことができるのはもちろん、 WASI に対応した形でFile, Network IOを行うモジュールも使えるようになります。

ただまだまだ大本の WASM, WASI の仕様が unstable なのでどう転ぶかはわかりません。筆者がそこまで追いきれてない領域です。 2020年には徐々に今の native module が wasm/wasi を使って書かれるようになってくると予測します。ただ、大本の団体がまだ安定版を作れていないので、2025年でもまだ experimental なままな気がしてます。

先日公開された TensorFlow.js が wasm を backend にするというニュースが流れてきて、徐々にモジュールのレイヤでは使われ始めてるという実感を感じます。

github.com

その他 Web Platform API はどうなるのか

crypto は Web Crypto を Node.js API に採用する議論は進められています。

github.com

cryptoWeb Crypto も別物です。 Promise をベースとした Web Crypto でこちらはユーザーの API の利便性を考えられているものの、既存の crypto は性能をベースに考えられているので趣きが若干異なりますね。今の tls モジュールが Web Crypto をベースに変わるとかはちょっと想像ができません。こちらももう少し議論が進めば入る可能性はあります。 2020 年には決着がつきそうですね。入るかどうかは判断が難しいですが、2020年に experimental リリースされそうな気はします。

一方で、 WebTransport, Web Codec等の対応はまだ議論すらされていません。ちなみにWebSocketsも core に入れるかどうかは忘れられてる気もします。。。

Web Platform の API を入れるかどうかは、「ユースケースが Node.js にとって妥当かどうか」、「ユーザーランドで実装が困難かどうか」、「既存の API との整合性」で決まるので、それぞれの判断をする議論が無い限り話は進みません。

まとめ

  • ES Modules は 2020 年には増え続けて、 2025年には ESM : CJS で 3 : 7 程度の比率になる
  • QUIC は来年 experimental でリリース (v14) HTTP3 はそこから議論が始まって RFC がでたら2025年にはリリースされてる
  • fetch は来年一旦 working group ができる、2025年にはリリースされてる
  • stream は多分来年も何も進まないが、 fetch working group で一緒に議論はされる、 2025 年には一旦 whatwg stream 無しでリリースされる
  • WASM は既に初期リリースはされているので後は仕様側が安定したら徐々に進むはず、ただまだ仕様が unstable なままだと experimental flag は取れない。2025年にもまだ experimental な気がする。
  • その他: Web Crypto が入りそう、ほかはまだまだ、ユースケースから募集中。

書いてて思いましたが、 2025 年って v24 とかですね。。。こう書くと全然予測ができない気もしてきました。やっぱり予測するものではなく、自分たちで予測を超えていく動きをしたいですね。

来年も Node.js に JSConf にがんばります。

Universal Data Fetch ライブラリ、 Specter の紹介

この記事は Recruit Engineers Advent Calendar 2019 の 16日目の記事です。

adventar.org

最近僕が作っている OSS である Specter の話をします。

github.com

Specter とは

Client から Backend と BFF から Backend への Universal なデータフェッチを提供してくれるためのツールです。以下の特徴を持ちます。

  • 軽量
  • TypeScript Freindly な型付け機能
  • 投機的先読み

2年ほど前に報告されたCPUの投機的実行に基づく脆弱性である、 Spectre から来ています。と言っても、脆弱性の名前ではありません。投機的先読みを機能として持っているため、この名前をつけています(不吉な名前ではありますが・・・)。

まずは、 GraphQL でも grpc-web でもなく、なぜこいつが産まれたのか、その背景から説明します。

背景

リクルートでも GraphQL を使うケースは徐々に見え始めていますが、まだまだ少数派です。よくあるケースは REST-ish な API サーバがすでに Backend に存在し、それを基に BFF レイヤから API Aggregation をするようなケースです。

f:id:yosuke_furukawa:20191216002138p:plain
BFF が Backend API を Aggregation するケース

Specter 以前はこの部分に Fetchr と呼ばれる OSS を使っていました。しかしながら、 Fetchr は TypeScript などの昨今のフロントエンドのトレンドにマッチしておらず、 非同期呼び出しも callback API を中心として Promise 化されたラッパーが存在する程度でした(そこに以前は 不具合があり、一撃 DoS を直したり してました)。

次の候補として、 Client と BFF 間の通信で、 GraphQL を使うという話もありましたが、最近は AMP で amp-list を使うなど、 Response の戻り値の型が決まっているようなケースも有り、諸々のユースケースに対応するため、独自で開発するに至ったという経緯です。今回は特徴を解説しつつ、なぜこのような設計なのかの Why について解説します。 最後に How についても少しだけ解説します。

軽量

Specter 自身のファイルサイズは BundlePhobia によると、 minify, gzip 付きで 2.2kB 程度です。

bundlephobia.com

ちなみに他のよくある data fetch ライブラリと比較すると以下のとおりです。他のライブラリとは機能が違うので単純比較は難しいですが、同等の機能を提供している Fetchr よりも10kB以上削減されています

f:id:yosuke_furukawa:20191216010313p:plain
各種 bundle size まとめ lower is better

Specter は内部的に fetch を使っています。これまでの data fetch ライブラリは基本的に XHR をラップする形で Promise を提供しています。IE11等、 fetch をサポートしていないブラウザ用にはこのアプローチが必要なものの、最近では fetch をサポートしていないブラウザのが珍しいので、こうしています。

一方で、 IE11 用に unfetch と呼ばれる軽量な polyfill を用意しています。これにより、 IE11 のような fetch 未サポートの環境でも Specter は動作します。

TypeScript friendly

Specter は TypeScript で型をつけられるようになっています。一方で、 TypeScript じゃなくても普通の JavaScript でも呼べるようにAPIは作られています。 TypeScript にどっぷり浸かるような TypeScript specific なライブラリではなく、あくまで TypeScript friendly としているのは、そのためです。

// TypeScript で使えば普通に型をはめて使える(ただし、TypeScript specific ではないので、あくまで型を自称しているのみ。GraphQLなどのアプローチとは異なる)
const request = new Request<RequestHeader, RequestQuery, RequestBody>("counter", {
    headers: {},
    query: {},
    body: {
      count: (count + 1),
    }
  });
const data = await client.update<CounterResponse>(request);
// Generics をやめてしまえば、 JS から普通に使える
const request = new Request("counter", {
    headers: {},
    query: {},
    body: {
      count: (count + 1),
    }
  });
const data = await client.update(request);

既存のシステムがまだまだ JavaScript で動いているものも多い状況で派手に migration をするのではなく、段階的に移行できるようにするためにこうしています。特に Fetchr で動いているものを徐々に TypeScript にしているような状況では、 Specter のように段階的な移行ができると移行の計画が立てやすいという考えでこうしています。

投機的先読み (Prefetching)

Prefetch の方法はいくつかあります。Prefetch もパフォーマンスチューニング分野ではよく研究されているものの1つです。いくつか Prefetch の方法を紹介します。

Prefetch で、一般的なのは dev.to でも実装されている、マウスオーバーしたら prefetch するという方法でしょうか。この方法では ユーザーが押そうとしたリンク を先読みするという効果があります。ただし、この方法はマウスを使わない端末、つまりスマートフォンタブレットでは使えません。

また、 Next.js や Nuxt.js には Link 先を 投機的に prefetch してくれる機能が存在します。この機能は Prefetching Pages と呼ばれており、静的なリンクでは viewport 内に Link が入ったら先読みします。 ただし、この方法は若干富豪的です。 さらにあまりにもリンクが多い場合は通信容量も増えてしまいます(所謂ギガが減る

https://nextjs.org/docs#prefetching-pages

最近はもう少しスマートな方法として、 Predictive Prefetching と呼ばれる方法が登場しています。これは Google Analytics などで計測している統計データに基づいて Prefetch する方法です。 GA の情報から次に行く可能性の高いリンクを予め Prefetch する方法です。

web.dev

Specter はまだ GA と組み込んで実装する機能は未実装ですが、ヒューリスティックに先読みする機能 (経験則に基づいて先読みする機能) 自体は存在します。例えば、検索実行時に、経験則に基づいて、検索結果の上位5件を予め Prefetch するなどのアプローチをすることが可能です。

// HackerNews の top story 一覧を取得する
const res = await fetch("https://hacker-news.firebaseio.com/v0/topstories.json", {
  method: "GET",
  headers: {
    "Content-Type": "application/json; charset=utf-8"
  }
});
const data: Array<number> = await res.json();

// 上位10件に関しては先読みするためのヒントとなる "次のリクエスト候補" に入れることができる
// もしもギガが気になるなら取らなくても可能
const nextReqs = data.slice(0, 10).map((id) => new ClientRequest<{},{ id: number },null>("hnitem", { 
  headers: {}, 
  query: { id: id }, 
  body: null,
}));
const resp = new Response(
  {},
  data,
);
resp.setNextReqs(...nextReqs);
return resp;

先読みする時は下記の要領で実行します。

const data = await client.read<HackerNewsListResponse>(request);
setTimeout(() => {
  // ここで先読みする、先読みしたデータは cache として残るため、次に取りに行く時には cache から fetch できる
  const reqs = data.getNextReqs();
  reqs?.forEach((req) => client.read(req));
}, 100);

実際HackerNews APIを基に先読みするケースと、先読みしないケースの両方を検証した所、先読みを行わないときに比べて時間が500msec以上減っています(先読み結果をcacheしているため)。

先読みしないとき: avg. 566ms
先読みしたとき: avg. 15ms

また、 Universal なライブラリとして作っているため、 Prefetch のタイミングが柔軟に変更できることが強みの1つです。SPAなどのリッチなアプリケーション の場合は Service Worker で行えば main thread を阻害しないで 先読みが可能です。また、そこまでしなくても requestIdleCallback などのブラウザが busy じゃないタイミングで Prefetch させることも可能です。逆に AMP 等、 クライアントで柔軟に Prefetch できない場合はサーバサイド (Node.js) で Prefetch した結果を Cache に入れておく事も可能です。

詳しくは HackersNews の example を確認してください。

github.com

使い方など

Next.js や React, Redux で利用する時の方法については詳しくは GitHub の examples を参考にしてください。

Next.js で使うとき: specter/examples/nextjs at master · recruit-tech/specter · GitHub React, Redux で使うとき: specter/examples/counter at master · recruit-tech/specter · GitHub

その他、何かあれば GitHub で気軽に issue 報告をお願いします。日本語でも問題ありません。

github.com

展望

まずはリクルート社内で実践をしながら、より細かなニーズに対応していこうと思っています。 @agreed からクライアントを自動生成する、などのニーズはたくさんあります。さらに、弊社では Google Analytics だけではなく、 Adobe Analytics や独自分析基盤などがあるため、それらともインテグレートしていく必要があります。

まだまだ実験的なデータフェッチライブラリなので、事業ニーズと一緒に考える必要は数多くありますが、ある程度まで動くものができた段階で徐々に広げていく予定です。

まとめ

Specter について解説しました。 Specter 自身は軽量で、 TypeScript friendly で、 投機的先読みが可能なデータフェッチライブラリです。それぞれの機能についての背景と利用した際にどれだけの利点があるかについても加えて解説しました。

実験的なアプローチですが、リクルート社内にはアプリケーションが多数あるので、それぞれのユースケースに対応していこうと思っています。今後は以下のようなロードマップを描いています。

  • example や 事例を増やす
  • ドキュメント拡充
  • Predictive Prefetch 実装
  • Analytics 基盤との連携

よかったら使ってみてください :) Feedback をお待ちしています。

Dual Package Hazard

この記事は Node.js Advent Calendar 2019 の 11 日目の記事です。

qiita.com

今回は全 Node.js で ES Modules を利用するユーザーが知っておくべき Dual Package Hazard について紹介します。

ESModules がフラグ無しでサポートに。

これまでは ES Modules は --experimental-modules フラグが無いと使えませんでしたが、 フラグ無しで Node.js v13.2.0 から使えるようになりました。ES Modules については CodeGrid の記事で詳しく書いているのでそれを一読していただけると理解がスムーズになると思います。逆に読んだ方は Conditional Exports / Dual Package Hazard の節まで飛ばしてもらって構いません。

www.codegrid.net

Node.js における ES Modules のおさらい

ES Modules と従来の CommonJS ベースの Script とは別なものです。 ES Modules は何も宣言しなくても Strict Mode になり、予約語も global に定義されている変数も異なります。Node.js ではこれらの従来の CommonJS と ES Modules を分けるためにいくつかの方法を提供しています。

  1. ファイル拡張子が .mjs になっていること
  2. ファイル拡張子が .js になっている、もしくは拡張子がない場合、最も直近の親モジュールの package.json に記載されている type フィールドが module になっていること
  3. --eval--print もしくは Node の標準入力から渡されるようなケースでは --input-type=module をつけること

特にこれまでと大きく異なるのは、 ファイル拡張子です。 これまで (Node v10まで) はデフォルトで 1. のファイル拡張子が .mjs しか認めていませんでしたが、これからは .js でも package.json のフィールドに type: “module” の記述があれば ES Module として読み込まれます。

また、 ES Modules として宣言するのと同様の宣言が CommonJS でも可能です。.cjs 拡張子をつける、 type フィールドに commonjs と入れるなどの対応をすれば CommonJS として宣言することが可能です。

以下にフローチャートを記述します。

f:id:yosuke_furukawa:20191209230938p:plain
ES Modulesかどうか

ES Modules の相互運用性に関して

現在の Node.js には ES Modules と CommonJS の2種類の module があります。これらの module が混在する場合はどうなるでしょうか。 Node.js はどちらであっても透過的に扱えるように互換性を取っています。この「透過的に扱えるように互換性を取ること」を “Interoperability(相互運用性)” と呼びます。

この相互運用性を保つため、 ES Modules からでも CommonJS をimportできるようになっています。CommonJS から ES Modules を呼ぶことも可能ですが、詳細は後述します。

import するとき

以下のサンプルは ES Modules から module を import する時の方法です。

// このファイルは foo.mjs として定義され、拡張子が `.mjs` になっている。

// すべての Node.js のモジュールは ES Modules としても import できるようになっている。
import fs from “fs”;

// 名前付きで import する場合は以下のようになる。
import { readFile } from “fs”;

// ES Modules はもちろん import できる。`.js` であっても `.mjs` であっても、特別な記述がなければ import 時は ES Modules としてロードされる。
import foo from “./foo.js”;

// npm module などのモジュールもロードできるが、type フィールドがmodules じゃない場合は commonjs としてロードされる。
import _ from “lodash”;

// 無理矢理呼んだ場合であっても、 package.json の type フィールドが `module` ではないため、 commonjs になる。
import _ from “./node_moduels/lodash/index.js”;

// commonjs としてロードしてほしい場合は拡張子を .cjs にする。
import bar from “bar.cjs”;

CommonJS から ES Modules を呼びたい場合は、Dynamic Import 構文( import() )を使います。

// このファイルは CommonJSとして定義されている。
const _ = requrie(“lodash”);

// CommonJS から ES Modules を使う時は Dynamic Import で構文として使う。
import(“./foo.js”).then((foo) => {
  // foo
});

相互運用性をサポートしているため、CommonJS から ES Modules を呼ぶことも、 ES Modules から CommonJS を呼び出すことも可能です

ES Modules と CommonJS 両方に対応した package を作る

ES Modules 形式と CommonJS 形式の両方に対応した package を作る場合は以下の処理が必要です。両方に対応すると、 importrequire の両方でロードするモジュールを作ることが可能です。

  • 標準のロード形式を ES Modules 形式にするか CommonJS 形式にするか選択する。ES Modules 形式にする場合は、 package.jsontype フィールドを module にする。 CommonJS 形式にする場合は type フィールドを commonjs にする。
// ./node_modules/es-module-package/package.json
{
  "type": "module", // ES Modules 形式
  "main": "./src/index.js"
}
// ./node_modules/es-module-package/package.json
{
  "type": "commonjs", // CommonJS 形式
  "main": "./src/index.js"
}
  • 標準のロード形式を CommonJS 形式にした場合、ES Modules から読み込むエントリポイントを作り、そこから読み込むようにドキュメント等で記載が必要です。 ES Modules 形式にしたとしても同様で、 CommonJS から読み込むエントリポイントを作る必要があります。
// ES Modulesで読み込む用の endpoint から読み込んでもらう
import foo from “foo/esm.js”;
  • もしも import 時の endpoint をカスタマイズして使わせたい場合は package exports の機能を使います。package exports はファイルパスに対して別名をつける機能です。
 // ./node_modules/es-module-package/package.json
{
  "exports": {
    "./submodule": "./src/submodule.js"
  }
}
// package.json に書いてある exports をパスとして使える。
import submodule from 'es-module-package/submodule';

これらの機能をうまく使えば、見た目上は同じく使えます。 import する際は import submodule from “es-module-package/submodule” で、 require する際は const submodule = require(“es-module-package”); で使えます。

ただしこれだと読み込み先のモジュールの名前をドキュメントなりで教える必要があります。そこで v13.2.0 からの experimental な機能として追加された Conditional Export という機能を使うとこの状況を改善できます。

Conditional Export

読み込み先のモジュールのエントリーポイントを用意しておき、条件に応じて切り分ける機能です。

{
  "name": "test",
  "type": "module",
  "main": "./main.mjs",
  "exports": {
    ".": {
      "require": "./main.cjs",
      "default": "./main.mjs"
    }
  }
}

こうしておくと、 require("test") で呼ばれたときは main.cjs が呼ばれ、 import "test" で呼ばれたときは main.mjs が呼ばれます。 まだ実験的な機能なので使うためには --experimental-conditional-exports フラグ付きで呼び出す必要があります。

$ node --experimental-conditional-exports foo.js

このように ES Modules でも CommonJS でも呼ばれるようにする package のことを Dual Package と呼びます。

Dual Package Hazard

前置き長かったですが、やっと本編です。

Dual Package には特定のバグのリスクが伴います。このリスクのことを Dual Package Hazard と呼んでいます。きちんと理解して Dual Package 対応を進めていきましょう。

Dual Package はES Modules と CommonJS で一見同じものをロードしているようで、実はファイルが違います。この『ファイルが違う』、という事が下手をするとインスタンスの違いを引き起こし、結果として予期しない問題になり得ます。

Dual Package Hazard
Dual Package Hazard

例を挙げて解説します。 test という Dual Package を用意します。これは import から呼ばれたときは main.mjs を呼び出し、 require から呼ばれた時は main.cjs を呼び出します。

// node_modules/test/package.json
{
  "name": "test",
  "type": "module",
  "main": "./main.mjs",
  "exports": {
    ".": {
      "require": "./main.cjs",
      "default": "./main.mjs"
    }
  }
}

package の 規定となる class を定義しておきます。これを core.cjs として定義します。

// node_modules/test/core.cjs

module.exports = class A{};

ESModules で定義されてる main.mjscore.cjs をロードし、 instance 化した後で export で公開します。

// node_modules/test/main.mjs
import A from "./core.cjs";
export default new A();

CommonJS で定義されてる main.cjs の方も同様に class A を instance 化した後で、 module.exports で設定します。

// node_modules/test/main.cjs
const A = require("./core.cjs");
module.exports = new A();

同じようなものを export してますが実体が異なります。これを同じノリで使おうとするとハマる可能性があります。

// foo.mjs
// node --experimental-conditional-exports foo.mjs
async function main() {
  const t = require("test");
  t.core = false;
  const t2 = await import("test");
  console.log(t); // A { core: false }
  console.log(t2.default); // A { } !! 変更が反映されていない !!
}

main();

この小さなパッケージでは間違えることは少なそうですが、例えば plugin 形式で context を引き継ぐような webpack, gulp, grunt のようなツールでは、ES Modules と CommonJS が同じインスタンスを指していない事で plugin 同士で context が引き継がれず思いも寄らない不具合を引き起こしかねません。

このように同じものをロードしているようで、実は実体が違うものがロードされた結果、思いも寄らない不具合を引き起こすリスクを Dual Package Hazard と呼んでいます。 Node.js の package は特に pluggable な作りのものが多いので、気をつけて使う必要があります。

Babel や esm などの transpile するやり方は CommonJS に変換して使っているものが多いため、実際にはこの問題は起きません。 Node.js が両方で読み込めるように相互運用性を高めた結果、気をつけて利用しないと、発生しうるのがこの不具合です。

Dual Package Hazard を局所化する

これに対応するのは実はそこまで難しくありません。同じインスタンスを指すようにしてしまえばよいのです。この例だけで言えば、 core.cjs からロードしたものを instance にして返却するのではなく、 instance 化済みの状態で export し、 singleton にするという手があります。 

// node_modules/test/core.cjs
class A{};
module.exports = new A(); // singleton にして公開する

このようになるべく状態を作るところを一箇所にまとめ、エントリーポイントごとに状態を共通化させる事が重要です。

もしくは、 Node.js からは CommonJS 形式でのロードに集中し、 browser などの実行環境で ES Modules 形式を使うという消極的な戦略にしてしまうことも可能です。

// node_modules/test/package.json
{
  "name": "test",
  "type": "commonjs",
  "main": "./main.cjs",
  "exports": {
    ".": {
      "require": "./main.cjs",
      "browser": "./main.mjs", // browserっていう実行環境でのみ main.mjs を読み込ませる(何らかのツールでこの情報を参照してもらう)
      "default": "./main.cjs" // その他の状況では commonjsにする
    }
  }
}

この場合は、Nodeとbrowserで同じスタイルで読み込まれますが、そもそも実行環境がブラウザとNodeで異なるため、 CommonJS と ES Modules が同じ環境からロードされることがありません。※ browser propertyはデフォルトではブラウザが見てくれるわけではないのでなんらかのツールのアシストが必要になります。

Dual Package 自体は便利ですが、このような混乱があることを忘れずに。色々な回避策はもちろんありますが、まだベストプラクティスと呼べるほどのものが無いのが現状です。

まとめ

  • Node.js ES Modules はv13.2.0からフラグ無しで使えるようになりました。
  • ES Modules からも CommonJS からも両方から使えるように相互運用性をサポートしています。
  • ただし、 ES Modules と CommonJS の両方に対応した Dual Package を作る時は注意が必要です。
  • お互いの instance が違うことで ES Modules と CommonJS のどちらかだけでは起き得なかった不具合、 Dual Package Hazard が発生します。
  • これを直そうとするといくつかのトレードオフが発生します(singletonにする、環境ごとで切り分けるなど)
  • まだベストプラクティスはない状況です。積極的に使ってフィードバックするもよし、消極的にまだ運用を控えるのも良いでしょう。

参考資料

nodejs.org

github.com

JSConf.JP を開催しました。 / We have held JSConf.JP !

Acknowledgement

集合写真
JSConf Japan Photo

色々と終わって来たので鉄は熱い内にと思ってブログを書いてます。ホント大変なこともたくさんありましたが、やりきれて本当によかったです。

We have done the rest of tasks, so I am writing a blog. I had lots of troubles, problems, issues but I am so glad that I have completed to run JSConf.JP.

これまで JSConf.JP を開催したいという声はたくさんありました。ただしこれまで開催はできませんでした。

There are lots of voice that " I would like to hold JSConf.JP ". However noone hold the event.

理由はいくつかありますが、 JSConf を開催するのに 2つレギュレーションが必要だというのが大きな理由だと思います。

There are a few reasons, but the main reason is 2 rules to hold JSConf.JP.

1つは、 JSConf に参加したことがあること、もう一つは JSConf の別なオーガナイザーからメンタリングを受けていること。

First rule is to join JSConf as attendee, 2nd rule is to have mentoring by other JSConf organizer.

1つめのルールは簡単にクリアできましたが、2つめのルールをクリアするには割と勇気がいりますね。

First rule is easy to clear, 2nd rule needs to have courage to solve the rule.

まずは JSConf Japan をやりたいと言った時に相談に乗ってくれた JSConf Colombia の Juan に感謝を言いたいと思います。彼が僕のメンターになってくれたおかげで、 JSConf.JP が開催できました。

I would like to say thank you to Juan, JSConf Colombia organizer. He gave me some advice about how to hold JSConf Japan. I could hold JSConf.JP thanks to my mentor Juan.

他にもプロジェクトマネジメントのできない僕に代わり、自分で何をするべきかを考え、色々行動してくれたスタッフ、色々と不手際がありながらも参加してくれた参加者、スポンサーの皆様にも感謝を言いたいと思います。

And I would like to say thank you all staff that they think what to do and take some actions instead of me and all attendees, and all sponsors.

JSConf Panel Talk

ユニークな試みの1つとして、 JSConf Panel Talk というパネルディスカッション形式でのトークを私がやりました。

We have an unique contents, JSConf Panel Talk. I have done panel discussion styled talk.

このセッションでは、 JSConf EU のオーガナイザーの Jan, BrooklynJS, Google DevRel の Kosamari さん, Automatic の Lena Morita さんをお呼びして、日本なりの国際カンファレンスの在り方を話し合いました。

This session is discussed about Japanese owned International Conference with Jan, JSConf EU organizer and Kosamari, Google DevRel and Lena Morita, Automatic .

トークの内容は、例えば JSConf JP にライブトランスレーションがあったほうがいいか、 JSConf JP は他の JSConf と比べてチケットが安すぎではないか?といった内容を参加者から集めて語り合いました。

For example, talked contents are JSConf.JP would be better to set up live translation? Why is JSConf JP ticket so cheap compare to other JSConf? We have collected these opinions from attendees.

どのトピックも盛り上がりました。30分では足りないくらい。

Every topic are heated up. We need more time to discuss about that.

JSConf JP Best Prices
JSConf JP Best Prices

面白かったのは、完全に海外と日本ではカンファレンスのチケットに対する考え方に差があるところです。

I have an interest in the difference of ticket thinkings between Japan and international.

海外カンファレンスのオーガナイザーは基本的に企業が従業員にチケットを買っていくもの、という考え方です。対して、日本では、個人で買うもの、という考え方です。

International Conference organizers think company buys tickets for employees. However Japanese conference organizers think an individual buys tickets for themselves.

だから数万円以上の金額になることも珍しくありません。

So their ticket price become over 100USD. This price is not so rare.

日本でもそれを念頭に置いたカンファレンスもありますが、基本的に無料だったり、安い値段になります。

some Japanese conference have the ticket price over 100USD. However basically the ticket price is free or cheap.

海外カンファレンスは高くする代わりにより設備にお金をかけます。

International conference takes more cost for equipment, facility etc.

日本だとスポンサーを増やす必要があります。

Japanese conference needs to get more sponsors if same equipment needed.

どっちがいいというわけではないですが、いま時点で日本の JSConf.JP では、例えば全セッションをライブトランスレーションを用意するなどの対応をすると、予算が足りません。

I don't know which is better, but now our JSConf.JP does not have enough budget to setup live translation in full sessions.

また、海外では hallway track を意識するとのことでした。 hallway track は廊下でのセッションで、所謂井戸端会議のようなものですね。

And International conference keeps in mind "hallway track". hallway track is a session in hallway, in Japanese Idobata-Kaigi.

つまりコミュニケーションを意識する、もっと参加者同士のつながりを持ってもらえたら、次に来るときも「あの人に会いに行きたい」という意識から来てもらえるようになる、ということでした。

They keep in mind "communication", if attendees have connections with each others, they would come in next year, they have a feelings that "I want to see that person".

Next JSConf Japan

次もやります。次は 2020 年の 9月末頃を予定しています。

We will hold JSConf.JP in next year. We are planning to hold in September 2020.

あと TC39 が 東京で9月にミーティングがあるとのことです。 TC39 のメンバーともコラボレーションしたいと思っています。

And TC39 will hold their meeting in September at Tokyo. We would like to collaborate with TC39 members.

f:id:yosuke_furukawa:20191209033045p:plain

またぜひ来てください :)

We are looking forward to seeing you in next year :)

ISUCON 9 予選に isucon_friends として参加し、予選総合3位でした。

久しぶりの本戦出場

ISUCON3 以来なので 6 大会ぶりですね。。。思えばずっと予選で負け続けてきたものです。

yosuke-furukawa.hatenablog.com

結果どうだったか

予選3位通過でした(棄権含む)。最終スコアは 27,470 ですね。 isucon.net

1位 nil 1 [1] 52,440
2位 にがり 1 [1] 36,270
3位 isucon_friends 3 [0] 27,470 ★
4位 いんふらえんじにあー as Code 3 [0] 26,460
5位 ようするにメガネが大好きです 3 [0] 25,200

僕らよりもスコアが跳ねている、にもかかわらず、学生が1名でやっている、nilにがり は本当にすごいと思います。

何をやったかまとめ

言語は Go でした。 (Node.js ではありません。)

  • Index貼ったり、OR検索をUNIONに変えるなどでチューニング 2100点 => 2800点
  • キャンペーン還元率を 0 => 1 に。 この時点では3300点前後になるも、ボトルネックがログインに移った。
  • 3台構成にして、 nginx, app, DB にした。 この時点で 8800点程度、ただこの時点ではボトルネックはログインのまま。
  • bcrypt 剥がして SHA256 に rehash することに。ベンチマークを何度も回してその都度 rehash された password の結果をdumpし、パスワードハッシュ部分だけ書き換えるように。ベンチマーク流すたびに 10000点から少しずつ伸びていくので楽しかった。この時点で14000点
  • キャンペーン還元率を 1 => 2, 3, 4 と一気に変えていった。16000点に。
  • /buy 内でAPI 呼び出ししてる所を並列処理化、また同時に /users/transactions.json 内で API 呼び出ししてるところを並列処理化。25000点に一気に伸びた。
  • もう後は Lock wait 待ちが多くなったので、 N + 1 を改善しようとするも断念。Transaction Levelを REPEATABLE READ から READ UNCOMMITTED に変えてみたりして、悪あがき。 26000点になった。 (fail されないかビクビクしてた)
  • 最後にすべてのログをオフにしてレギュレーション確認しながらベンチマークを実行、 27420点 が出たところで終了

細かく効いたかは定かではないがやったこと

  • nginxのエンドポイントを HTTP/2 にした
  • Accept-Encoding が gzip だったら gzip 返すようにした (JSON のサイズが劇的に下がってた)
  • キャンペーンテーブルをインメモリに持った
  • 外部 API へのリクエストをする際に HTTP Agent の MaxIdleConns と MaxIdleConnsPerHost を拡張、1 ホスト 3000 コネクションまで持てるようにした。

やってみようと思いつつもできなかったこと

  • N+1 の解消
  • API のレスポンスが done status ならキャッシュする

この辺が思いついてはいたものの、残り時間で実装しきれず、断念した所です。 N+1 を IN 句に変えるだけでもやってもよかったかも。 この辺をやれていればもう少し伸びたかもしれません。

ISUCON9の感想

まずは予選突破できて本当に良かったです。若手や同僚にパフォーマンスの考え方を教えている身でありながら、本戦突破がそもそも1度しかできてなかったことを歯がゆく思ってました。

突破できて安心しました。

余談ですが、社内でR-ISUCONというISUCONを自分たち向けにカスタマイズした問題を作って会社全体で合宿しながら半年に一度のペースで会社全体で競っています。

今回、予選を突破している同僚が毎回社内ISUCONで一緒にしのぎを削ってるチームで、とても嬉しく思いました

・theorem 3 [0] 12,360
・ふんばり温泉チーム 3 [0] 12,060

R-ISUCONという社内ISUCONをやっていた結果、自分たち含めて3チームも予選を突破した事を本当に嬉しく思います。 このスタンプを社内でも使っていこうと思います。

f:id:yosuke_furukawa:20190908230806p:plain
本戦であいましょう

R-ISUCONについては今年の初めに 941さん と一緒にデブサミで登壇したのでその時の資料を展開しておきます。

logmi.jp