生涯未熟

生涯未熟

プログラミングをちょこちょこと。

k6を使いこなしてみよう

この記事は MIXI DEVELOPERS Advent Calendar 2022 6 日目の記事です。

負荷試験を行う機会が年に何度かあるのですが、以前まではvegetaを使っていましたがちょっと高めの負荷をかけた時の挙動がよろしくなく、k6を試してみたところ不満が無かったので最近はk6を常用しています。

そんなk6をもうちょっと使いこなすために色々とまとめてみようかと思います。

k6とは?

Grafana Labsが開発した負荷ツール。

github.com

ツール自体はGo製で、負荷シナリオをJavaScriptで書きます。
負荷シナリオはk6 Browser RecorderというChrome拡張を使えばブラウジングしているだけで作成可能で、k6 Cloudを使ったWeb上でのシナリオ作成・管理・実行が可能です。
わざわざGitHub上でシナリオを管理しなくてもいいというのは個人的に便利だなと思った点ではあります👀 (どこのリポジトリに置くの?問題とか考えなくて済む)

chrome.google.com

k6.io

それ以外にもHTTP、WebSocket、gRPCなどのマルチプロトコルサポートや、カスタムビルドを使うことにより例えばIAPの突破をk6自体に組み込んだりなどの拡張性を持たせることが出来ます。(詳しくはxk6を参照)

github.com

その他、充実したドキュメントやマメに更新されているブログなどユーザーに対してのサポートも抜かりがないです。

k6.io

基本的な使い方

まずはインストールから。Macの場合は brew install k6 でok、それ以外はこちらを参照。

インストールが終わりましたら、次にシナリオの作成です。ドキュメントにもある最小の形でまずは試してみましょう。
シナリオを格納するディレクトリとして k6 という名称でディレクトリを作成し、その直下に以下の内容の test.js を作成してください。

k6側でテスト用に用意されている https://test.k6.io に対してGetリクエストを投げ、その1秒後にテストを終了するシナリオになります。では、そのようになっているのか実行してみましょう。

$ cd k6
$ k6 run test.js

          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: test.js
     output: -

  scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):
           * default: 1 iterations for each of 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)


running (00m01.7s), 0/1 VUs, 1 complete and 0 interrupted iterations
default ✓ [======================================] 1 VUs  00m01.7s/10m0s  1/1 iters, 1 per VU

     data_received..................: 17 kB 10 kB/s
     data_sent......................: 442 B 260 B/s
     http_req_blocked...............: avg=518.34ms min=518.34ms med=518.34ms max=518.34ms p(90)=518.34ms p(95)=518.34ms
     http_req_connecting............: avg=175.25ms min=175.25ms med=175.25ms max=175.25ms p(90)=175.25ms p(95)=175.25ms
     http_req_duration..............: avg=179.72ms min=179.72ms med=179.72ms max=179.72ms p(90)=179.72ms p(95)=179.72ms
       { expected_response:true }...: avg=179.72ms min=179.72ms med=179.72ms max=179.72ms p(90)=179.72ms p(95)=179.72ms
     http_req_failed................: 0.00% ✓ 0        ✗ 1
     http_req_receiving.............: avg=465µs    min=465µs    med=465µs    max=465µs    p(90)=465µs    p(95)=465µs
     http_req_sending...............: avg=50µs     min=50µs     med=50µs     max=50µs     p(90)=50µs     p(95)=50µs
     http_req_tls_handshaking.......: avg=330.41ms min=330.41ms med=330.41ms max=330.41ms p(90)=330.41ms p(95)=330.41ms
     http_req_waiting...............: avg=179.21ms min=179.21ms med=179.21ms max=179.21ms p(90)=179.21ms p(95)=179.21ms
     http_reqs......................: 1     0.587392/s
     iteration_duration.............: avg=1.7s     min=1.7s     med=1.7s     max=1.7s     p(90)=1.7s     p(95)=1.7s
     iterations.....................: 1     0.587392/s
     vus............................: 1     min=1      max=1
     vus_max........................: 1     min=1      max=1

予想通りにGetリクエストを1度投げてますね!このようにCLIスクリプトを実行すると統計データが表示されます。
では表示された項目について一つずつ見ていきましょう。

running (00m01.7s), 0/1 VUs, 1 complete and 0 interrupted iterations
default ✓ [======================================] 1 VUs  00m01.7s/10m0s  1/1 iters, 1 per VU

テスト実行時のプログレスバーですが、ここにはVUS(virtual users:仮想ユーザー)の数と実行時間、イテレート数と仮想ユーザーの実行数になります。

  • data_received:総データ受信数
  • data_sent:総データ送信数
  • http_req_blocked:TCP接続がブロッキングされた時間
  • http_req_connecting:TCP接続の確立にかかった時間
  • http_req_duration:リクエストを送信してレスポンスを受け取るまでの時間(http_req_sending + http_req_waiting + http_req_receiving)
  • expected_response:http_req_durationの中でもHTTPステータスコードが200~399のリクエストのみの値
  • http_req_failed:リクエストが失敗した数
  • http_req_receiving:レスポンスを受信していた時間
  • http_req_sending:リクエストを送信していた時間
  • http_req_tls_handshaking:TLSハンドシェイクにかかった時間
  • http_req_waiting:レスポンスを待っていた時間(TTFBのこと)
  • http_reqs:送信されたリクエスト数
  • iteration_duration:シナリオで設定された1イテレーションにかかった全ての時間
  • iterations:1VUが実行したイテレーションの数
  • vus:終了時点でのVU数
  • vus_max:実行中での最大VU数

色々ありますが、個人的に見ている指標は http_req_failedhttp_req_duration くらいですかね。勿論、他の指標も何かしらの異常な値が出ていれば見ますが、基本はこの2つを見ていればいいかなと思います。
シナリオをCLIで実行した場合はこのような出力ですが、k6 Cloudではこの指標がグラフで可視化された図を確認することができます。

※ただし、詳細に出力されるのはCLIの方なので特別なこだわりが無ければCLIで実行するのをオススメします(高負荷の場合はk6 Cloudの方がいいかも)

また、この指標は開発者がカスタムすることが出来、これは後ほど説明致します。

ここまでが最小の形でのシナリオ作成→実行までのフローになります。

k6 run

さきほど実行の際に利用した k6 run についてもう少し詳しく見ていきます。

k6 run はオプションでシナリオの実行の仕方を変えることができます。例えば、さきほどはシナリオが一回終われば実行を終了していましたが、「30sの間、50rpsで負荷をかけたい」などといった要望は出てくると思います。そういった時にオプションを付けてみましょう。
今回はテストのためにもう少し軽い負荷にしてみます。

$ k6 run test.js -d 5s --rps 20
          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: test.js
     output: -

  scenarios: (100.00%) 1 scenario, 1 max VUs, 35s max duration (incl. graceful stop):
           * default: 1 looping VUs for 5s (gracefulStop: 30s)


running (05.2s), 0/1 VUs, 4 complete and 0 interrupted iterations
default ✓ [======================================] 1 VUs  5s

     data_received..................: 52 kB 9.9 kB/s
     data_sent......................: 736 B 141 B/s
     http_req_blocked...............: avg=120.06ms min=6µs      med=11.5µs   max=480.22ms p(90)=336.16ms p(95)=408.19ms
     http_req_connecting............: avg=44.02ms  min=0s       med=0s       max=176.08ms p(90)=123.26ms p(95)=149.67ms
     http_req_duration..............: avg=182.34ms min=179.94ms med=182.49ms max=184.42ms p(90)=184.05ms p(95)=184.24ms
       { expected_response:true }...: avg=182.34ms min=179.94ms med=182.49ms max=184.42ms p(90)=184.05ms p(95)=184.24ms
     http_req_failed................: 0.00% ✓ 0        ✗ 4
     http_req_receiving.............: avg=143.75µs min=86µs     med=137.49µs max=214µs    p(90)=201.7µs  p(95)=207.84µs
     http_req_sending...............: avg=36.25µs  min=26µs     med=30µs     max=59µs     p(90)=50.6µs   p(95)=54.8µs
     http_req_tls_handshaking.......: avg=75.71ms  min=0s       med=0s       max=302.86ms p(90)=212ms    p(95)=257.43ms
     http_req_waiting...............: avg=182.16ms min=179.81ms med=182.26ms max=184.31ms p(90)=183.9ms  p(95)=184.11ms
     http_reqs......................: 4     0.766934/s
     iteration_duration.............: avg=1.3s     min=1.18s    med=1.18s    max=1.66s    p(90)=1.51s    p(95)=1.59s
     iterations.....................: 4     0.766934/s
     vus............................: 1     min=1      max=1
     vus_max........................: 1     min=1      max=1

実行してみましたが、20rpsを指定したのに0.7rpsしか出ていませんね。vusに注目していただくとvusが1しか生成されていないので、20rpsを実行するためにvusを増やしてみましょう。

$ k6 run test.js -d 5s --rps 20 -u 40
          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: test.js
     output: -

  scenarios: (100.00%) 1 scenario, 60 max VUs, 35s max duration (incl. graceful stop):
           * default: 60 looping VUs for 5s (gracefulStop: 30s)


running (08.0s), 00/60 VUs, 135 complete and 0 interrupted iterations
default ✓ [======================================] 60 VUs  5s

     data_received..................: 1.9 MB 237 kB/s
     data_sent......................: 34 kB  4.3 kB/s
     http_req_blocked...............: avg=158.32ms min=3µs      med=13µs     max=465.14ms p(90)=358.31ms p(95)=364.97ms
     http_req_connecting............: avg=77.78ms  min=0s       med=0s       max=183.75ms p(90)=177.89ms p(95)=180.75ms
     http_req_duration..............: avg=193.77ms min=171.88ms med=179.31ms max=362.84ms p(90)=188.16ms p(95)=346.3ms
       { expected_response:true }...: avg=193.77ms min=171.88ms med=179.31ms max=362.84ms p(90)=188.16ms p(95)=346.3ms
     http_req_failed................: 0.00%  ✓ 0        ✗ 135
     http_req_receiving.............: avg=15.43ms  min=40µs     med=122µs    max=178.5ms  p(90)=418.8µs  p(95)=170.85ms
     http_req_sending...............: avg=47.08µs  min=12µs     med=44µs     max=122µs    p(90)=83.2µs   p(95)=95.3µs
     http_req_tls_handshaking.......: avg=80.48ms  min=0s       med=0s       max=280.26ms p(90)=180.7ms  p(95)=184.18ms
     http_req_waiting...............: avg=178.29ms min=171.72ms med=178.65ms max=188.4ms  p(90)=184.01ms p(95)=185.03ms
     http_reqs......................: 135    16.97481/s
     iteration_duration.............: avg=2.85s    min=1.63s    med=2.65s    max=4.5s     p(90)=3.81s    p(95)=4.15s
     iterations.....................: 135    16.97481/s
     vus............................: 18     min=18     max=60
     vus_max........................: 60     min=60     max=60

vusを40にしてみたところ、20rpsに近付きましたね!このように実行する環境をオプションによって調整することが出来ます。様々なオプションが用意されているので是非とも k6 run --help でオプション一覧を眺めて頂きたいのですが、その中から使い所の多いオプションを抜粋します。

// --user-agent:ユーザーエージェントの指定
$ k6 run test.js --user-agent "test-agent"

// --out:指標の詳細データを出力、リクエスト毎にHTTPステータスコードやリクエスト/レスポンスの日時などの情報が確認できる
$ k6 run test.js --out json=result.json

// --log-output:画面上でのログの表示設定、リクエストのタイムアウトなど頻発する出力を抑制するのに使う
$ k6 --log-output none run test.js

k6 Browser Recorderを利用したシナリオの作成

k6とは?で紹介したk6Chrome拡張・をk6 Browser Recorder使ってシナリオを作成してみましょう。今回はexample.comを使ってシナリオを作成してみます。
事前作業として k6 Browser Recorderをインストールし、https://app.k6.io/account/login にアクセスし、ログインまたは新規登録を済ましておいてください。

まずは、example.comに飛びましょう。

k6 Browser Recorderを実行してみます。

Start recordingを押下します。

ツールウィンドウが読み込み状態になり、暫くすると上記の画面になりますので、そうしましたらページを一度リロードしてみてください。 その後、完全にページの読み込みが完了したらStopを押下します。

すると、新規タブが開きk6 Cloudが表示されます。

Test builderとScript editorが最初にあるのですが、個人的にはScript editorを選択するのをオススメしています。後から変えられるのでどちらでもいいのですが、開発者が扱うにはScript editorの方が馴染みあるJSの書かれたエディターが開かれるのでこちらを選ぶのが良いかと。
Generate sleepは500ms以上かかっている処理間に自動でsleepを入れてくれるのですが、自分は必要ないと感じているのでいつも選択していません。

また、今回は表示がされていませんがcssやjsなどのstatic assetsが存在する場合は、そのアクセス処理もスクリプトに含めるかどうか、外部ドメインへのリクエストも含めるかどうかも選択できます。外部ドメインへのリクエストは自分で管理しているもの以外は除くのが良さそうで、static assetsに関しては基本は含んでおいて状況によっては除いたりするのが良いかなと思います。

選択が終わりましたらSaveでScript editorに移動します。

ここからRun Testを押下することでそのまま実行できますし、一度生成されたスクリプトを手元でjsファイルにしてCLIで実行するも良し。状況によって使い分けてみてください。

シナリオの作成例

ここからはもう少しシナリオの作成を具体的にやっていきます。さきほど作成したexapmle.comへのリクエストを土台に改造していきます。

生成されたこのコードを見たときに注目すべきは export const options です。 k6 run のオプションを紹介しましたが、さらに細かい調整等を可能にするのがこのoptionsです。
現在は「vusが10で5分間負荷をかける」という条件でシナリオが書かれていますが、これを「40vus(必要があれば増やす)で20rpsの負荷を5秒間かける」としてみましょう。

確認のため、こちらを実行してみると

$ k6 run test.js

          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: test.js
     output: -

  scenarios: (100.00%) 1 scenario, 100 max VUs, 35s max duration (incl. graceful stop):
           * test_scenario: 20.00 iterations/s for 5s (maxVUs: 40-100, gracefulStop: 30s)


running (06.1s), 000/040 VUs, 101 complete and 0 interrupted iterations
test_scenario ✓ [======================================] 000/040 VUs  5s  20 iters/s

     █ page_1 - http://example.com/

     data_received..................: 162 kB 26 kB/s
     data_sent......................: 11 kB  1.8 kB/s
     group_duration.................: avg=159.18ms min=108.62ms med=117.98ms max=247.32ms p(90)=231.3ms  p(95)=238.23ms
     http_req_blocked...............: avg=44.95ms  min=4µs      med=15µs     max=121.48ms p(90)=114.97ms p(95)=117.98ms
     http_req_connecting............: avg=44.88ms  min=0s       med=0s       max=121.35ms p(90)=114.84ms p(95)=117.84ms
     http_req_duration..............: avg=113.97ms min=108.32ms med=114.34ms max=129.69ms p(90)=120.07ms p(95)=120.83ms
       { expected_response:true }...: avg=113.97ms min=108.32ms med=114.34ms max=129.69ms p(90)=120.07ms p(95)=120.83ms
     http_req_failed................: 0.00%  ✓ 0         ✗ 101
     http_req_receiving.............: avg=124.53µs min=44µs     med=125µs    max=254µs    p(90)=182µs    p(95)=186µs
     http_req_sending...............: avg=75.37µs  min=24µs     med=57µs     max=426µs    p(90)=138µs    p(95)=168µs
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s
     http_req_waiting...............: avg=113.77ms min=108.13ms med=114.13ms max=129.51ms p(90)=119.93ms p(95)=120.63ms
     http_reqs......................: 101    16.524309/s
     iteration_duration.............: avg=1.16s    min=1.1s     med=1.11s    max=1.24s    p(90)=1.23s    p(95)=1.23s
     iterations.....................: 101    16.524309/s
     vus............................: 40     min=40      max=40
     vus_max........................: 40     min=40      max=40

16.5rpsということで20rpsに近い値が出せていますね!それでは土台からの変更点を見てみます。

export const options = {
  scenarios: {
    test_scenario: {

さて、optionsの中で scenarios というのが設定されています。これはVUSやイテレーションの具体的な設定を行う際に使われるもので、詳しくはこちらに書いてありますが、エグゼキューターと呼ばれる実行エンジンの指定や、実行パターンの詳細な設定でより現実的な負荷計測が可能になります。
optionsに rps という設定項目もあるのですが、非推奨となっているため constant-arrival-rate エグゼキューターを使った一定のリクエスト送出を行う方法を取っています。
test_scenario は任意のキーとなっていますので、適当なシナリオに基づく名前を付けてください。

// rate, timeUnitを実行するためのExecutor
executor: 'constant-arrival-rate',

duration: '5s',

// timeUnitで指定された時間毎に反復するテスト回数
rate: 20,

// rateを反復させる時間
timeUnit: '1s',

// 初期に割り当てられるVUS数
preAllocatedVUs: 40,

// VUSが足りなかった場合に増える最大VUS数
maxVUs: 100,

scenarios で設定されている内容ですが、コメントに書いてある通りの設定をしている感じです。設定値はoptionsでも設定できる値は設定でき、エグゼキューター毎に設定できるものが違ったりするので、まずはエグゼキューターの把握。それから各エグゼキューターの設定値を確認していくのがいいでしょう。

あと、私が必ず付けているオプションがありまして discardResponseBodies: true を付けておくのをオススメします。こちらはレスポンスボディを破棄するかどうかの設定で、デフォルトでは破棄しない設定になっているのですが必要がないことがほとんどだと思いますので、テスト負荷を減らすために破棄をする設定にしておきましょう。

もう一つ、こちらはオプションではないのですが http.setResponseCallback(http.expectedStatuses({ min: 200, max: 399 })); という「何のHTTPステータスコードを成功とみなすのか?」を設定することができます。デフォルトで200~399までは成功扱いなのですが、場合によっては特定のHTTPステータスコード以外は成功としたくないという時に使えます。
試しに201~399までは成功とした場合にどうなるかやってみましょう。

          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: test.js
     output: -

  scenarios: (100.00%) 1 scenario, 100 max VUs, 35s max duration (incl. graceful stop):
           * test_scenario: 20.00 iterations/s for 5s (maxVUs: 40-100, gracefulStop: 30s)


running (06.1s), 000/040 VUs, 101 complete and 0 interrupted iterations
test_scenario ✓ [======================================] 000/040 VUs  5s  20 iters/s

     █ page_1 - http://example.com/

     data_received..............: 162 kB  26 kB/s
     data_sent..................: 11 kB   1.8 kB/s
     group_duration.............: avg=160.16ms min=107.11ms med=118.32ms max=253.38ms p(90)=235.91ms p(95)=239.88ms
     http_req_blocked...........: avg=45.4ms   min=3µs      med=15µs     max=132.49ms p(90)=117.31ms p(95)=118.75ms
     http_req_connecting........: avg=45.25ms  min=0s       med=0s       max=121.77ms p(90)=117.18ms p(95)=118.65ms
     http_req_duration..........: avg=114.49ms min=106.91ms med=115.03ms max=123.16ms p(90)=119.73ms p(95)=120.56ms
     http_req_failed............: 100.00% ✓ 101       ✗ 0
     http_req_receiving.........: avg=117.94µs min=19µs     med=93µs     max=2.62ms   p(90)=135µs    p(95)=152µs
     http_req_sending...........: avg=73.29µs  min=17µs     med=56µs     max=281µs    p(90)=147µs    p(95)=167µs
     http_req_tls_handshaking...: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s
     http_req_waiting...........: avg=114.3ms  min=106.79ms med=114.85ms max=122.99ms p(90)=119.6ms  p(95)=120.32ms
     http_reqs..................: 101     16.504622/s
     iteration_duration.........: avg=1.16s    min=1.1s     med=1.11s    max=1.25s    p(90)=1.23s    p(95)=1.24s
     iterations.................: 101     16.504622/s
     vus........................: 40      min=40      max=40
     vus_max....................: 40      min=40      max=40

想定通り、 http_req_failed が100%で全て失敗となっていますね。

カスタムメトリクスの作成

負荷の実行後に表示される指標をカスタマイズしてみましょう。設定できるカスタムメトリクスには以下の4種類があります。

  • Counter:累積値を判定する指標。例えば、HTTPステータスコード上は成功しているが、エラーコードをレスポンスで返している場合にそういったレスポンスが1件でもあれば失敗とする、みたいなことが出来る。
  • Gauge:最新の値を常に判定する指標。例えば、レスポンスボディのサイズが〇〇以上だったら失敗、のようなことが出来る。
  • Rate:追加した値の割合を判定する指標。例えば、エラー率が10%以上なら失敗のようなことが出来る。
  • Trend:追加した値の統計(最小、最大、平均、パーセンタイル)を判定する指標。例えば、平均レスポンスタイムが〇〇ms以下なら失敗のようなことが出来る。

それぞれのカスタムメトリクスに対して、 thresholds という条件を記述することで機能するのですが、ここで abortOnFail を設定すれば失敗した時点で負荷計測を中断することが出来たり、 delayAbortEval で失敗後も指定時間計測は続けるといったことも可能です。

この4つのカスタムメトリクスを加えたシナリオを書いてみましたので、参考にしてみてください。

まだまだ奥が深いk6

今回紹介したのはk6の要素のまだまだ一部です。他にも setup teardown や、 check を使ったシナリオの書き方などもありますので、ドキュメントを読んで習熟してみてください!

k6.io

Googleのソフトウェアエンジニアリングを読んだ

www.amazon.co.jp

を読みました。ソフトウェアエンジニアリングを広範に取り扱っためちゃくちゃ良い本だったので、下の連ツイを基にちまちま感想などを書いていく。
※ 684ページという大ボリューム&プライベートで様々なイベントがあったので読むのに3ヶ月ほどかかりました・・・

どんな本だったか?

さっくり言うと「ソフトウェアリングという広範的な物事における考え方・やり方をしっかり学べる本」になります。
全体構成としては5部に分かれており、

  1. 主題
  2. 文化
  3. プロセス
  4. ツール
  5. 結論

となっています。

主題では「ソフトウェアエンジニアリングの定義・著者が発見した法則・時間的変遷による事象」などを絡めつつ、この本の前哨戦として何を学んでいかなくてはいかないのか?を説明されています。
文化は「メンバーとして、またリーダーとしてどういった文化を築かなければならないか」、プロセス・ツールでより具体的なスタイルガイド・コードレビュー・ドキュメンテーション・テストなどなどの話。
そして結論、という流れでソフトウェアエンジニアについて過不足なく学べる内容だったと思います。

個人的にどこが良かったか?

"Googleの"と銘打っていますが、「Googleではこんなクールなやり方をしているぜ!」といった本ではなく、最初期のGoogleが我々と同じような苦しみを抱え、時には失敗し、その結果どういったやり方に帰結したか?という流れをキチンと説明されているところがとても印象が良かったです。
3部 14.1.2.2 設定の問題にて、「Googleでは設定変更が大障害の原因の第一位である。」とあるように、誰もが経験することを勿論Googleも経験しているしそれが重大インシデントに繋がりこういった事故を起こした、といったことが赤裸々に書かれています。
Googleも人並みなミスを今までしてきた。ただそのミスをどういった考え方・やり方で根絶しているのかという流れは、自分自身に翻ってみても活かせることでしょう。

響いたところ

ここからは本書内で響いてメモったところを書いていきます。全部が全部を書いているわけではないので、気になった方は買って読んでみてください。

独自の法則

Hyrumの法則

APIに対してどういう契約仕様で作られたかをユーザーは考慮せず、ただ振る舞いに依存する。
つまり、設計者の意図を考慮せずにユーザーは利用する、ということであり"仕様"と"振る舞い"の乖離が大きくなればなるほど設計者とユーザーの距離が遠くなっていく。

このことによるデメリットは時間的経過によってより甚大なものになるのは想像に難くないでしょう。本書ではこの法則が様々な観点からちょこちょこと登場してきます。

Beyonceルール

「そんなに好きなら(≒ 破綻して欲しくないなら)そいつにテスト付けときゃよかったのに」
ミッションクリティカルであるなど、破綻が許せないなら必ずテストしましょうというルール。特にインフラストラクチャの文脈で語られるルールです。
インフラストラクチャの変更で何らかの障害が起きるといった可能性はありますが、それをCIで防げないのであればCIの落ち度であり変更には全く責任が無いとも言い換えることができるでしょう。

リーダーとは斯くあるべきか

チームリーダーについて語られている章は、個人的には本書内で熟読する価値が非常に高いと考えている一つで、「何をしなくちゃいけなくて、何をしてはいけないのか?」が書かれています。

リーダーが負う責務

「リーダーはプロジェクトの要所要所においてトレードオフを明確化させ、それをどのように行うべきかの決定における責務を負わなければならない」。これには非常に納得でした。
リーダーが決定に対して責務が負えないのであれば、チームとしての迷いが常に漂ったまま仕事をし続けなければいけなくなり非常に不健全です。メンバーに意見を乞うことは積極的にしなくてはいけませんが、決定は必ずリーダーがやるべきものです。

逆に「リーダーは完璧超人であるべき責務は負わなくていい。またその振る舞いをしてはいけない」、とはっきり書いてあります。それよりも、合意形成をよりスムーズに行うための"触媒"であったり、適切な答えを知っている人を知っている、といったサポート役としての振る舞いが重要視されます。

去れるリーダーになろう

あと面白いなーと思った考え方は第2部 6.2に書かれている「いつでも去れ」です。私も常々思っていたことですが、「自分が明日事故に遭い、働けなくなった場合にこのチームは何も支障なく回り続けられるだろうか?出来ないなら何が必要だろうか?」という考え方です。本書ではそういった考え方を"バス係数"と呼んでいます。
「プロジェクトが破綻するのに要するプロジェクト内でバスに轢かれる者の人数」がバス係数で、自分やメンバーがバス係数としてカウントされないような自律的組織を目指しましょうといった流れで解説されています。自分がいなくなっても回るように整備し、自動運転できることを見届けたら去り、また別のチームを整備していく・・・目指すべきリーダー像のひとつですね。

ブログの下書きに入れっぱなしになってたので、一旦公開。
また読み返した時に追記します。

Next.jsでyarn exportが何故か失敗する

謎の地雷を踏み抜いたのでメモ。

Next.jsで静的HTMLを出力する next export がありますが、これをローカルで実行すると何故かエラーが吐かれ失敗するという事象がありました。

Error occurred prerendering page "/hoge". Read more: https://nextjs.org/docs/messages/prerender-error
Error: Cannot find module for page: /hoge

CIだと通るのに不思議だな〜と思っていましたが、以下に解決法が載っておりました。

github.com

私の場合、別のターミナルでyarn dev (next)コマンドを実行させていました。そのプロセスを停止して、yarn build && yarn exportを実行したら、問題は解決しました。

あっ・・・ next dev やりながらだとダメなのね・・・
ということで next dev を止めて next export したら無事通りました。めでたしめでたし。

CypressのinterceptでGAと通信してるか確かめてみた

Cypress入門1日目だけどやってみてハマったので書く。

interceptについて

ネットワークリクエスト・レスポンスのSpyとStubが出来るよって機能らしい。

docs.cypress.io

今回のやりたいことはGAと通信しているかどうかなので、GAへのリクエストをSpyしてレスポンスを見るなりすれば疎通確認が取れる。

やったこと

そんなこんなでこういうコードが出来た。

ハマったところ

「こんな短いコードのどこにハマんねん」って感じでしょうが、 cy.intercept する際にどうやって別ドメインを指定すればいいのかサッパリわからず、変に cy.origin 使ってみたり右往左往してました。
で、結局optionに hostname があるのを発見してこれ使えばよかったんかいとなりました。
めでたしめでたし。

GitHub API v4でProjectV2のNode IDを取得する

GitHubから2022/10/01よりプロジェクト情報を取得する ProjectNext が廃止となり、代わりに ProjectV2 を使おうというお達しがありました。

docs.github.com

ProjectNext で使っていたNode IDを ProjectV2 でも使えるのかなとやってみたらダメだったので、新たに取得してみた。

やり方

仕事上でAPIを使っているということもあり、今回は会社のorganizationから取得してみました。
クエリはこんな感じ。

プロジェクト番号は各プロジェクト上に割り振られている番号でURL見るとわかると思います。

それかクエリ実行するのめんどくせーって人は単純に PN_XXXXXXX ってなってるIDを PVT_XXXXXXX って置換すれば良さそう。