Upgrade to Pro — share decks privately, control downloads, hide ads and more …

k6による負荷試験 入門から実践まで

k6による負荷試験 入門から実践まで

https://techfeed.io/events/techfeed-experts-night-20
TechFeed Experts Night#20 〜 Webパフォーマンス・チューニング最前線 : 前編(概要、モニタリング、負荷テスト編)

FUJIWARA Shunichiro

June 07, 2023
Tweet

More Decks by FUJIWARA Shunichiro

Other Decks in Technology

Transcript

  1. k6による負荷試験 入門から実践まで
    2023.06.07 TechFeed Experts Night#20
    @fujiwara 藤原俊一郎

    View Slide

  2. @fujiwara
    面白法人カヤック SREチーム
    ISUCON 1,2,5,11 優勝4回
    ISUCON 3,8,12,13 運営(出題)4回
    github.com/kayac/ecspresso
    Amazon ECS デプロイツール
    github.com/fujiwara/lambroll
    AWS Lambda デプロイツール

    View Slide

  3. 今回話す「負荷試験」の範囲
    測定対象のWebサービス(アプリケーション)に対して
    機械的にリクエスト(HTTPなど)を送信して
    「性能(パフォーマンス)」を計測する
    ところで 「性能」 とは…?

    View Slide

  4. 負荷試験をする「前」に考えること
    ゴールを決める
    レイテンシ(レスポンスタイム)が目標?
    どのレスポンスを何秒で返せればOKなのか
    平均値? 中央値? P99? max?
    そのレイテンシが保てる最大の並列アクセス数は?
    スループット(単位時間内に処理できるリクエスト数)が目標?
    レイテンシ要件はない?
    レイテンシ1secで1,000並列
    レイテンシ10secで10,000並列(どちらも1,000req/sec)
    あるサーバーリソースでのxxの最大化が目標?

    View Slide

  5. 負荷試験をする「前」に考えること
    その試験で測りたいものを言語化する
    レイテンシ
    スループット
    サーバーリソースの利用率・利用量(コストも含む)
    運用中に実際に起きるどのような状況で、これらの数値目標を達成するのか
    【具体的な例】
    状況 : Push通知から○○分以内に○○人のユーザーがアプリを起動する
    シナリオ : アプリが起動してホーム画面に遷移するときに発生するAPIアクセス
    目標 : 平均レイテンシ250msec、P99 2sec、サーバーエラーなし
    詳しくは ISUCON本 1章へ

    View Slide

  6. 負荷試験をする「前」にやること
    負荷をかける対象のサーバーリソースは(できるだけ)固定する
    →「AutoScaling」的なものを止めておく
    負荷試験には 「変数が多い」
    変数の例: シナリオ(測定対象URL)、リクエストの送信レート、並列度…
    固定できないものは、せめてそのサイズ(キャパシティ)を同時に記録しておく

    View Slide

  7. 負荷試験をする「前」にやること
    サーバー側のメトリクスを本番同様に取得して、ダッシュボードを作る
    最低でも以下を一目で見られる状態に
    各種CPU使用率
    HTTP スループット(req/sec)、レイテンシ、statusコード別
    データベースへのクエリ数(など)
    アプリケーションサーバー固有の情報 同時リクエスト処理数(worker数)
    job queueがあるならそのメトリクス
    ネットワークトラフィック

    View Slide

  8. ツールの選び方
    最初におすすめ - k6 https://k6.io
    JavaScriptでシナリオが書ける
    (Node.JSではない)
    実行エンジンは Go
    (軽量、省メモリ)

    View Slide

  9. 他のツール/自作を検討したほうがいい場合も
    リクエストやレスポンスbodyが特殊 (k6でもgRPC, GraphQLはサポート)
    gRPC https://k6.io/docs/using-k6/protocols/grpc/
    GraphQL https://k6.io/blog/load-testing-graphql-with-k6/
    JavaScriptよりも他の言語が得意な人が多い場合
    複数シナリオ間で連係や同期を取る必要がある場合
    ゲームのレイドバトルとか
    自作用Go言語フレームワーク isucandar
    https://github.com/isucon/isucandar
    ISUCON本 付録Bに解説あり

    View Slide

  10. シナリオの作り方
    まず単一URL連打から
    import http from 'k6/http'
    import { check } from 'k6'
    export default function () {
    let res = http.get('https://example.com/')
    check(res, { 'success': (r) => r.status === 200 })
    }
    いきなり複雑なものは書かない

    View Slide

  11. k6で実行
    最初は「並列度(vus=virtual users)」= 1 から
    $ k6 run --vus 1 --duration 300s simple.js
    checks.............: 99.98% ✓ 52644 ✗ 6
    http_req_duration..: avg=285.56ms min=32.93ms med=144.61ms
    max=30.05s p(90)=521.34ms p(95)=788.36ms
    http_reqs.. ......: 52650 172.404676/s
    (いっぱい出力があるけど)とりあえずこの3個を見る
    checks: コード内の check()
    の成功・失敗数
    レイテンシ: http_req_duration
    スループット: http_reqs

    View Slide

  12. 結果を評価する
    1. k6の出力とサーバー側のメトリクスに齟齬がないか
    →メトリクスの取得がおかしい可能性が高いので見直す
    2. checksは想定通りか
    →エラーが目標/想定より多い場合は性能を評価する意味がない
    原因を調査する
    3. レイテンシは目標に達しているか
    →これ以上並列度を増やすのはたいてい無意味
    ボトルネックの調査をする
    4. スループットは目標に達しているか
    or →並列度を2倍にして変化を見る
    無闇に変数を変えて試さない

    View Slide

  13. 結果を評価しながら並列度/サーバーリソースを増やしていく
    並列度=1で
    エラー率/レイテンシが目標に達している
    スループットが目標に達していない
    →並列度を増やすことでスループットが増加する「可能性がある」
    並列度を増やしていくとある時点で…
    スループットが上がらない
    エラー率/レイテンシが目標に達しなくなる
    →「サーバーリソースを増強する」ことでレイテンシが改善/スループットが増加する「可能
    性がある」
    無闇にサーバーリソースを変えて試さない(時間の無駄)

    View Slide

  14. シナリオを増やしていく
    1. 参照系のリクエスト/画面遷移を足す
    2. ログイン/更新系のリクエストを足す
    (コードの具体例はISUCON本 4章に)
    本番のアクセスパターンと負荷を網羅しようと思うのはやめよう(大変)
    1. 一連のシナリオごとに実行する
    例: トップ→ログイン→マイページ→お知らせを見る
    これだけでも特定の箇所で大きな問題がある場合には改善点が分かる
    2. 複数のシナリオを混ぜて実行する
    複数シナリオを実際に想定される程度の割合で混ぜる

    View Slide

  15. 負荷試験中に気を付けること
    負荷は5分以上掛ける
    メトリクスの解像度が1分の場合、実行開始と終了時を除いた3分間のメトリクスし
    か信用できない
    エラー率/レイテンシが目標に達しない場合、並列度を増やさない
    たいてい状況は悪化するので時間の無駄
    常に「目標」を意識して負荷を与える、結果を評価する
    ベンチマーカーがボトルネックになっていないか気を付ける
    シナリオが重い、ツールが速くない場合、クライアントが先に根を上げる

    View Slide

  16. クライアント側で起きる問題の頻出例
    ネットワーク帯域が上限を打っている
    1Gbpsの回線で1MBのレスポンスを取得→(たったの)125req/sec(=125MB/sec)
    HTTP Keep-Aliveが無効になっている
    3 way handshake のオーバーヘッドが大きい
    無駄にファイルディスクリプタ(ソケット)を使う
    Goで自作した場合にやりがち: http.Response.Body
    を全て読み切らない
    →次のリクエスト時は新規のTCP接続になる
    ファイルディスクリプタを使い果たしている
    ulimit -n
    (max open files)がデフォルトの1024
    ローカルポート(エフェメラルポート)が枯渇している
    大量/高速にリクエストを送受信すると使い果たす
    ISUCON本 9章をどうぞ

    View Slide

  17. k6 応用編
    k6は基本的に、シングルプロセス(1台)で動作するツール
    クラスタリングの仕組みは公式には提供されていない
    Unless you need more than 100,000-300,000 requests per second (6-12M requests
    per minute), a single instance of k6 is likely sufficient for your needs.
    https://k6.io/docs/testing-guides/running-large-tests/
    公式 「10〜30万req/secぐらいは1インスタンスでいけるで」(チューニング頑張れば)
    分散実行をk8sでやる方法も紹介はされているが…
    https://k6.io/blog/running-distributed-tests-on-k8s/

    View Slide

  18. AWS Step Functions Distributed Mapで
    k6を分散実行してみた
    k6を気軽に分散実行したいので試してみた
    Step Functions(SFn): AWS上でworkflowを定義/実行で
    きるサービス
    Distributed Map: 大規模(1000並列以上)に並列実行して
    結果を集約できる
    k6をAWS Lambdaで実行できるようにする
    →SFn Distributed Mapで並列実行!!
    1台で大量の負荷を発生させるチューニングが不要に

    View Slide

  19. k6から必要な結果だけJSON出力
    シナリオの.jsに handleSummary()
    を定義すると
    https://k6.io/docs/results-output/end-of-test/custom-summary/
    export function handleSummary(data) {
    return {
    '/tmp/summary.json': JSON.stringify({
    'checks': data.metrics.checks.values,
    'http_reqs': data.metrics.http_reqs.values,
    })
    }
    }
    結果がファイルに出力される
    {"checks":{"rate":1,"passes":82,"fails":0},
    "http_reqs":{"count":82,"rate":27.087064577167027}}

    View Slide

  20. k6をAWS Lambdaで実行
    k6が配布しているLinuxバイナリはstatic link
    Zipに含めてしまえばAWS Lambdaのカスタムランタイム(provided.al2)で実行できる
    bootstrap > https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/runtimes-
    walkthrough.html
    function handler () {
    cd $LAMBDA_TASK_ROOT
    # k6
    でシナリオを実行する
    # $1:
    関数の引数としてvus
    を取る
    ./k6 run --quiet --vus "$1" --duration 30s simple.js
    # handleSummary
    で出力した結果をLambda
    のレスポンスにする
    cat /tmp/summary.json
    }

    View Slide

  21. SFn Distributed MapでLambdaを並列実行
    SFnへの入力例: 3並列でそれぞれvus=1で実行する
    ["1", "1", "1"] //
    この値はvus
    として使われる
    配列の値が各Lambda関数への引数になる
    ./k6 run --vus "$1" --duration 30s simple.js

    View Slide

  22. 分散実行の結果はS3に保存される
    "Output"
    : Lambdaが返した結果のJSON文字列
    [
    {
    "Input": "\"1\"",
    "Output": "{\"checks\":{\"rate\":1,\"passes\":82,\"fails\":0},
    \"http_reqs\":{\"count\":82,\"rate\":27.087064577167027}}",
    "StartDate": "2023-06-06T16:07:24.203Z",
    "StopDate": "2023-06-06T16:07:41.272Z"
    // ...
    },
    // ...
    //
    並列分実行した結果が並ぶ
    ]
    あとはこれを集計すればよさそう!?

    View Slide

  23. まとめ
    負荷試験はやる「前」の準備が大事
    ゴールと目標の設定
    いたずらに変数を増やさないために準備
    負荷試験をやっているときは「評価」が大事
    結果を評価してから次の試行を
    いたずらに試行錯誤で時間を無駄にしない
    負荷を与える側にもハマりどころはいろいろある
    クライアント側のボトルネックに気がつかないと時間を無駄に
    k6をStep Functionsで分散実行してみた
    気軽に分散実行できてよいのでは?

    View Slide