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

パフォーマンスチューニングのために普段からできること/Performance Tuning: ...

パフォーマンスチューニングのために普段からできること/Performance Tuning: Daily Practices

Avatar for FUJIWARA Shunichiro

FUJIWARA Shunichiro

October 28, 2025
Tweet

More Decks by FUJIWARA Shunichiro

Other Decks in Technology

Transcript

  1. 自己紹介 @fujiwara (X, GitHub, Bluesky) @sfujiwara (hatena, mixi2) 2011〜2024 面白法人カヤック

    2025-02〜 さくらインターネット ISUCON 優勝4回 / 運営(出題)4回 github.com/kayac/ecspresso github.com/fujiwara/lambroll
  2. 待ち行列理論 あるサービスを提供する窓口に顧客が並ぶ時の状況をモデル化 お客さんの到着 窓口(処理) ↓ ↓ → → 待ち行列 →

    → ↑ ↑ λ (ラムダ) μ (ミュー) 到着率 処理率 1秒に何人来る? 1秒に何人捌ける? 一番単純な M/M/1 モデル(窓口が一つ)で説明
  3. 待ち行列理論 到着率 (λ): 単位時間に来る顧客(リクエスト)数 処理率 (μ): 単位時間に処理できるリクエスト数 平均応答時間 = 1

    ÷ (μ - λ) (M/M/1モデル) 例えば μ = 100/sec(1秒間に100リクエスト処理可能) λ = 50/sec(1秒間に50リクエスト到達) 平均レイテンシ(レスポンスタイム) = 1 / (100 - 50) = 0.02sec = 20ms
  4. μ = 100req/sec のサーバーのレイテンシ = 1 / (100 - λ)

    λ (req/sec) レイテンシ 体感 0 10ms 快適 20 12.5ms 快適 50 20ms 快適 80 50ms 快適 90 100ms 普通 95 200ms ちょっと重いかも? 99 1s 重いな… 100 ∞
  5. パフォーマンスの劣化が体感しにくい理由 平均応答時間は利用率がかなり高くなるまで大きく劣化しないが 利用率が高くなると急激に悪化する 50%=20ms(2倍) → 80%=50ms(5倍) → 95%=200ms(20倍) → 99%=1000ms(100倍)

    人間は数十ms程度のレイテンシ悪化は知覚できない 50ms程度までは気が付かない、100msを超えてやっと気がつく人が出る 人間が「重い」と感じた時点ですでに利用率は限界に近い 人間は知覚できないが機械なら計測できる。モニタリングをしましょう。
  6. システム全体の性能はボトルネックで決まる システムは複数の構成要素が全て繋がって処理を行う user ↔︎ CDN ↔︎ LB ↔︎ App ↔︎

    DB ↔︎ Storage... 一番細い(弱い)ところが埋まるとスループットは頭打ち (レイテンシは上がり続ける)
  7. 前職での昔話 - Lobi ゲームユーザー向けのコミュニティ サービス 2010年 ナカマップとしてリリース 2013年 Lobiに改名 オンプレミスの仮想化基盤(KVM)

    アプリケーションサーバーは比較的 容易にスケール可能 hostは24vCPU、台数も余裕あり データベースには ioDrive 当時最強の フラッシュメモリストレージ
  8. # ethtool eth1 Settings for eth1: Supported ports: [ TP

    ] Supported link modes: 10baseT/Half 10baseT/Full 100baseT/Half 100baseT/Full 1000baseT/Full Supports auto-negotiation: Yes Advertised link modes: 10baseT/Half 10baseT/Full 100baseT/Half 100baseT/Full 1000baseT/Full Advertised pause frame use: No Advertised auto-negotiation: Yes Speed: 100Mb/s Duplex: Full Port: Twisted Pair PHYAD: 1 Transceiver: internal Auto-negotiation: on MDI-X: on Supports Wake-on: pumbag Wake-on: g Current message level: 0x00000001 (1) Link detected: yes
  9. # ethtool eth1 Settings for eth1: Supported ports: [ TP

    ] Supported link modes: 10baseT/Half 10baseT/Full 100baseT/Half 100baseT/Full 1000baseT/Full Supports auto-negotiation: Yes Advertised link modes: 10baseT/Half 10baseT/Full 100baseT/Half 100baseT/Full 1000baseT/Full Advertised pause frame use: No Advertised auto-negotiation: Yes Speed: 100Mb/s Duplex: Full Port: Twisted Pair PHYAD: 1 Transceiver: internal Auto-negotiation: on MDI-X: on Supports Wake-on: pumbag Wake-on: g Current message level: 0x00000001 (1) Link detected: yes
  10. 100Mb/s !!? # ethtool eth1 Settings for eth1: Speed: 100Mb/s

    1GbpsのNIC/スイッチなのになぜか100Mbpsでリンクアップしていた (多分オートネゴシエーションがおかしかった) ネットワークが100Mbpsで頭を打った結果 DBはクエリを2msで完了して結果を送信する slow logはこのタイミングで閾値を超えたら書かれる(ので出ない) DBとアプリケーション間のネットワークがボトルネック アプリケーションは結果を受信するのに20〜30ms掛かってしまう
  11. 「推測するな計測せよ」 Rob Pike / Notes on Programming in C Rule

    1. You can’t tell where a program is going to spend its time. Bottlenecks occur in surprising places, so don’t try to second guess and put in a speed hack until you’ve proven that’s where the bottleneck is. Rule 2. Measure. Don’t tune for speed until you’ve measured, and even then don’t unless one part of the code overwhelms the rest. ルール1:プログラムがどこで時間を消費しているかを事前に予測することはできませ ん。ボトルネックは予想外の場所に発生するため、推測で高速化を試みるのではな く、まず実際に測定してボトルネックの発生箇所を特定することが重要です。 ルール2:測定を行いましょう。速度チューニングは、実際に測定を行った後でも必要 最小限に留めてください。特に、コードの一部が全体の処理時間の大部分を占めてい る場合にのみ実施すべきです。
  12. ダッシュボードで全体を俯瞰する ユーザーへの影響があるか一目で判断できる情報(CDNなど) ↓↓↓ 内部の情報(App, DBなど) の順に並べて問題があったときに見る順番と一致させる 1. CDNでのエラーレートやレイテンシは? (ユーザー影響を最初に確認) 2.

    アプリケーションの状況は? 3. アプリから使っているミドルウェアなどの状況は? 1枚に並べることで問題の発生箇所を俯瞰して眺められる 怪しいところがあったら各要素のダッシュボード/グラフ/APMを深掘りしていく
  13. 全プログラマーが知るべきレイテンシー数 nano sec L1キャッシュ参照 0.5 分岐予測失敗 5 L2キャッシュ参照 7 Mutexのロックとアンロック

    25 メインメモリー参照 100 Zippy[Snappy]による1KBの圧縮 3,000 1Gbpsネットワーク越しに2KBを送信 20,000 メモリーから連続した1MBの領域の読み出し 250,000 同一データセンター内におけるラウンドトリップ 500,000 0.5 msec ディスクシーク 10,000,000 10 msec ディスクから連続した1MBの領域の読み出し 20,000,000 20 msec パケットをカリフォルニア→オランダ→カリフォルニアと送る 150,000,000 150 msec http://norvig.com/21-days.html#answers
  14. マイクロベンチマークを手癖にする 例:「Goでsliceに要素を追加する場合、先にキャパシティを確保したほうが速い」 func AppendFromEmpty(n int) { var s []int //

    sliceを宣言するだけ for i := 0; i < n; i++ { s = append(s, i) } } func AppendFromPreallocated(n int) { s := make([]int, 0, n) // capacityをn個分確保したslice for i := 0; i < n; i++ { s = append(s, i) } }
  15. Go標準のtestingモジュールでベンチマークができる import "testing" func BenchmarkAppendFromEmpty(b *testing.B) { for i :=

    0; i < b.N; i++ { AppendFromEmpty(10000) } } func BenchmarkAppendFromPreallocated(b *testing.B) { for i := 0; i < b.N; i++ { AppendFromPreallocated(10000) } } $ go test -bench . -benchmem (他の言語でも同じようなものがあります)
  16. $ go test -bench . -benchmem goos: linux goarch: amd64

    pkg: example.com/bench cpu: AMD Ryzen 5 3400G with Radeon Vega Graphics BenchmarkAppendFromEmpty-8 9975 121229 ns/ 357627 B/op 19 allocs/op BenchmarkAppendFromPreallocated-8 43009 27803 ns/ 81920 B/op 1 allocs/op 事前にcapacityを確保することで メモリのアロケート回数が減る (19 -> 1 allocs/op) 4倍速い (121μs = 0.12ms -> 28μs = 0.028ms) のがわかる
  17. 実例: ISUCON 11 優勝の分岐点 Zipを生成してダウンロードさせる機能 初期実装は zip 外部コマンド呼び出し → Go

    の archive/zip で作成するように fujiwara組: zip.Store (非圧縮)を指定してCPUコスト削減 NaruseJun: 圧縮(deflate)したZipを生成 ← 非圧縮だったら逆転していたらしい