ホーム / SRE / 第3部 コンピュータとネットワークの基礎
第3部 コンピュータとネットワークの基礎・ 第3章
コンピュータの基礎(CPU・メモリ・OS) ★★☆ 重要 18トピック・面接 1問
目次 まとめ クイズ 面接 ↑ 上へ この章で押さえること
B+Tree・ハッシュ・計算量で「なぜ速いのか」を説明できるようにする CPU・メモリ・プロセス・スレッドの仕組みが、障害の症状とどうつながるかをつかむ 面接で「それはなぜ?」と一段深く聞かれたときに答えられるよう、土台になる概念を図で解説します。各項目の「面接ではこう答える」は、そのまま声に出して練習できる長さにしています。
ひとことで CPUは「命令を実行する装置」。中に複数のコアがあり、それぞれが独立して処理を進める。
コア は実際に計算をする単位で、4コアなら同時に4つの処理を進められます。多くのCPUには、1つのコアで2つの処理を交互に素早く進めるハードウェアスレッド (IntelではHyper-Threading)があり、OSからは2つのCPUに見えます。ただし性能が2倍になるわけではありません。
クラウドのvCPU は、多くのx86インスタンスではこのハードウェアスレッド1つ分です(AWS Gravitonでは物理コア1つ分)。また、クロック周波数 (GHz)は1秒間に刻む拍の数で、高いほど1コアあたりの処理が速くなります。
用語 意味 SREで気にする場面 コア 独立して計算できる単位 シングルスレッドのアプリはコアが多くても1つしか使えない ハードウェアスレッド 1コアで2つの処理を交互に進める仕組み vCPU数の数え方 CPUキャッシュ(L1/L2/L3) メモリより速い、CPU内の小さな記憶 データの局所性で性能が大きく変わる CPUクレジット AWSのT系インスタンスで、平常時に貯めて負荷時に使う仕組み 使い切ると急に遅くなる(steal時間が増える) steal時間(st) 仮想化基盤側に奪われたCPU時間 ホストの混雑やクレジット切れのサイン
vCPUの数=コア数×ハードウェアスレッド数 CPU(ソケット) コア0 HW スレッド1 HW スレッド2 L1(コア専用) L2キャッシュ コア1 HW スレッド1 HW スレッド2 L1(コア専用) L2キャッシュ コア2 HW スレッド1 HW スレッド2 L1(コア専用) L2キャッシュ コア3 HW スレッド1 HW スレッド2 L1(コア専用) L2キャッシュ L3キャッシュ(全コアで共有) メインメモリ(RAM) この例は4コア×2スレッド=8 vCPU。AWSのvCPUの多くはハードウェアスレッド1つに相当する CPUの中の構造 mpstat -P ALL でコアごとの偏りを見ると、1コアだけ張り付いているボトルネックが分かる。
面接ではこう答える 読み上げ
CPUは複数のコアを持ち、各コアが独立して命令を実行します。1コアで2つの処理を交互に進めるハードウェアスレッドがあり、クラウドのvCPUはこれ1つ分に相当することが多いです。たとえば4コア8スレッドなら8 vCPUに見えます。
SREとの関わり インスタンスサイズの選定、CPU使用率の読み方(全体50%でも1コアが100%のことがある)、T系インスタンスのクレジット切れ障害に関わります。
ブラウザで試す(Linux): CPU を食うプロセスを本物の Linux で見つけて止める閉じる 別のページで開く →
Linux ラボを読み込んでいます…
ひとことで コアの数より多い処理を動かすために、OSが実行する処理を細かく切り替えること。切り替えにはコストがかかる。
OSのスケジューラ は、実行したい処理(プロセスやスレッド)に順番にCPU時間を割り当てます。切り替えるときは、今の処理のレジスタなどの状態を保存し、次の処理の状態を復元します。これがコンテキストスイッチ で、1回は数マイクロ秒程度ですが、CPUキャッシュの中身も入れ替わるため、回数が増えると目に見えて遅くなります。
スレッドを増やせば速くなるとは限らないのはこのためです。待ちの多い処理(I/O)ならスレッドを増やす効果がありますが、計算ばかりの処理ではコア数を超えて増やしても切り替えが増えるだけです。
確認方法 見るもの vmstat 1 cs列(1秒あたりの切り替え回数)、r列(実行待ちの数) pidstat -w 1 プロセスごとの自発的・非自発的な切り替え top load average と CPU使用率の差
1つのコアは、プロセスやスレッドを細かく切り替えて動かしている CPUコア1つ 処理A 処理B 処理C 処理A 処理B 切り替え 時間 → 赤い部分がコンテキストスイッチ。レジスタの保存と復元、キャッシュの入れ替えが起き、スレッドが多すぎるとここが増えて遅くなる コンテキストスイッチの様子 スレッドを増やしすぎると、赤い切り替えの時間が増えて実際の処理が減る。
面接ではこう答える 読み上げ
OSはコア数より多い処理を動かすため、実行する処理を短い時間で切り替えています。切り替えるたびにレジスタの保存や復元、キャッシュの入れ替えが起きるので、スレッドを増やしすぎるとかえって遅くなります。
SREとの関わり スレッドプールやワーカー数の決め方、load averageの読み方の根拠になります。
ひとことで CPUに近い記憶ほど速いが小さく、遠いほど遅いが大きい。この差を埋めるためにあらゆる層でキャッシュが使われる。
CPUのレジスタ、L1〜L3キャッシュ、メインメモリ、SSD、HDD、ネットワークの順に、アクセス時間は1段ごとに桁違いに遅くなります。メモリはL1キャッシュの約100倍、SSDはメモリの約1,000倍遅い、という感覚を持っておくと設計の判断に役立ちます。
キャッシュが効くのは、データの使われ方に偏りがあるからです。一度使ったデータはまた使われやすく(時間的局所性)、近くのデータも続けて使われやすい(空間的局所性)。この性質はCPUキャッシュでも、DBのバッファプールでも、Redisでも、CDNでも同じです。
1段下がるごとに桁が変わる。だからキャッシュが効く アクセスにかかるおおよその時間(対数目盛)と、L1を1秒に例えた体感 おおよその時間 L1=1秒なら レジスタ 0.3ns 0.3秒 L1キャッシュ 1ns 1秒 L2キャッシュ 4ns 4秒 L3キャッシュ 20ns 20秒 メインメモリ 100ns 1分40秒 SSDランダム読み 100µs 約1日 同じDC内の往復 500µs 約6日 HDDシーク 10ms 約4か月 日本⇔米国の往復 150ms 約5年 CPUに近いほど速く小さく高価。遠いほど遅く大きく安い。数値は桁を示す目安
記憶階層ごとのアクセス時間 ネットワーク越しの呼び出しは、メモリ参照の数千〜数百万倍遅い。呼び出し回数を減らす設計が効く理由。
面接ではこう答える 読み上げ
CPUに近いほど速く小さく、遠いほど遅く大きいという階層があり、メモリはL1キャッシュの約100倍、SSDはメモリの約1,000倍遅いです。データの使われ方には偏りがあるので、各層でキャッシュを使ってよく使うデータを速い層に置きます。
SREとの関わり N+1問題(ネットワーク往復の積み重ね)、DBのバッファプールのヒット率、キャッシュ導入の効果見積もりに使えます。
ひとことで 各プロセスに「自分専用の広いメモリがある」ように見せ、実際の物理メモリとの対応はOSとCPUが裏で管理する仕組み。
プロセスが使うアドレスは仮想アドレス で、CPUの中のMMU がページテーブル を使って物理メモリの場所に変換します。メモリは4KBのページ 単位で管理されます。これにより、プロセス同士がお互いのメモリを壊せない(隔離)、物理メモリより大きな空間を使える、といった利点があります。
プロセスが触ったページが物理メモリに無いとページフォルト が起き、OSがディスクから読み込みます。物理メモリが足りないと、使われていないページをディスクのスワップ 領域に追い出します。スワップが頻繁に起きるとディスクの遅さが直接響き、性能が急落します。それでも足りなければ、OOM Killerがプロセスを強制終了します。
用語 意味 VSZ(仮想サイズ) プロセスが確保した仮想メモリの大きさ。実際に使っているとは限らない RSS(常駐サイズ) 実際に物理メモリに載っている量。メモリ使用量はこちらで見る ページフォルト 必要なページが物理メモリに無く、OSが用意すること スワップ メモリが足りないときにページをディスクへ逃がす仕組み OOM Killer どうしても足りないとき、カーネルがプロセスを選んで終了させる
各プロセスは自分専用のアドレス空間を持っているように見える プロセスAの仮想メモリ ページ0 ページ1 ページ2 ページ3 プロセスBの仮想メモリ ページ0 ページ1 ページ2 ページテーブル MMUが仮想→物理を変換 ページは通常4KB 物理メモリ(RAM) Aのページ0 Bのページ1 Aのページ2 Bのページ0 Aのページ1 スワップ Aのページ3 実メモリに無いページに触るとページフォルトが起き、ディスクから読み戻す(遅い)。これが頻発するとスワップで性能が急落する 仮想メモリと物理メモリの対応 同じ「ページ0」でも、プロセスごとに別の物理メモリを指している。
面接ではこう答える 読み上げ
仮想メモリは、各プロセスに専用のアドレス空間を見せ、MMUがページテーブルで物理メモリに変換する仕組みです。プロセス同士の隔離ができ、物理メモリが足りないときは使われていないページをスワップに逃がします。スワップが頻発すると大きく性能が落ちます。
SREとの関わり メモリ使用量の正しい読み方(VSZではなくRSS)、スワップ発生時の判断、OOMの調査に使います。
ブラウザで試す(Linux): メモリ不足(OOM キラー)を本物の Linux で起こす閉じる 別のページで開く →
Linux ラボを読み込んでいます…
ひとことで 1つのプロセスのメモリは、プログラム本体、変数、ヒープ、スタックといった区画に分かれている。
下から順に、テキスト (機械語のプログラム本体、読み取り専用)、データ・BSS (グローバル変数や静的変数)、ヒープ (実行中に必要に応じて確保する領域)、そして上のほうにスタック (関数呼び出しの記録とローカル変数)が並びます。ヒープは上へ、スタックは下へ伸び、間が空き領域です。
1つのプロセスのメモリは、用途ごとに区画が分かれている 高いアドレス 低いアドレス カーネル空間 アプリからは直接触れない スタック 関数呼び出し・ローカル変数 空き領域 ヒープ newやmallocで確保 BSS・データ テキスト(機械語) (予約領域) 下に伸びる 関数を呼ぶたびに積まれ、戻ると消える 深い再帰で溢れるとスタックオーバーフロー 上に伸びる 明示的に解放するかGCが回収 解放し忘れるとメモリリーク グローバル変数・静的変数 プログラム本体(読み取り専用) プロセスのメモリ配置 スタックとヒープは向かい合って伸びる。どちらも無限ではない。
面接ではこう答える 読み上げ
プロセスのメモリは、プログラム本体のテキスト、グローバル変数のデータ領域、動的に確保するヒープ、関数呼び出しに使うスタックに分かれています。ヒープとスタックは逆方向に伸びていきます。
SREとの関わり コアダンプやメモリ使用量の内訳を読むときの前提になります。
ひとことで スタックは関数呼び出しのたびに自動で積んで自動で片付く領域。ヒープは自由に確保でき、片付けは自分かGCが行う領域。
スタック は、関数を呼ぶとその関数用の区画(フレーム)が積まれ、関数が終わると取り除かれます。ローカル変数や戻り先がここに入ります。確保も解放もポインタを動かすだけなので非常に速いですが、大きさは小さく(Linuxのメインスレッドで通常8MB、Javaのスレッドで通常1MB程度)、深い再帰で溢れるとスタックオーバーフロー になります。スレッドごとに1つあります。
ヒープ は、関数が終わっても残しておきたいデータや、大きさが実行時まで分からないデータを置く場所です。プロセス内の全スレッドで共有します。C/C++ではmallocとfreeで自分で管理し、JavaやGo、Pythonでは参照されなくなったものをGC(ガベージコレクション)が回収します。解放し忘れたり、不要な参照を持ち続けたりすると、使用量が増え続けるメモリリーク になります。
スタック ヒープ 入るもの ローカル変数、関数の戻り先 new/mallocで作ったオブジェクト 寿命 関数が終わると自動で消える 明示的に解放するか、GCが回収するまで 速さ 非常に速い スタックより遅い(確保・GCのコスト) 大きさ 小さい(MB単位) 大きい(設定やメモリ量次第) 共有 スレッドごとに別 プロセス内の全スレッドで共有 典型的な障害 スタックオーバーフロー メモリリーク、GCによる停止、OOM
スタックは自動で片付き、ヒープは参照がなくなるまで残る スタック(スレッドごと) main() config = ● handle(req) user = ● parse(body) buf = ● i = 3(値そのもの) ↑ 最後に積んだものから消える(LIFO) ヒープ(プロセス全体で共有) Configオブジェクト Userオブジェクト バッファ(1MB) 誰も参照していない 赤い箱はGCがあれば回収される。C/C++でfreeし忘れたり、Javaでも参照を持ち続けたりするとメモリリークになる スタックとヒープの関係 スタック上の変数がヒープのオブジェクトを指している。指す人がいなくなったものが回収対象になる。
面接ではこう答える 読み上げ
スタックは関数呼び出しのたびに自動で積まれて自動で消える領域で、速いですが小さく、スレッドごとにあります。ヒープは動的に確保する領域で、全スレッドで共有し、解放は自分で行うかGCに任せます。ヒープの解放漏れや参照の持ちすぎがメモリリークになります。
SREとの関わり メモリリークの調査(RSSが右肩上がり)、Javaのヒープサイズ設定、スレッド数とメモリの関係(スレッドごとにスタックが必要)に関わります。
ひとことで 使われなくなったヒープ上のオブジェクトを、プログラムの代わりに自動で探して解放する仕組み。
GCは、スタックやグローバル変数から参照をたどって「まだ使われているもの」を見つけ、たどれないものを解放します。便利な反面、回収の作業中にアプリの処理を止める時間(Stop The World )が発生することがあります。この停止中は全リクエストが待たされるので、平均レイテンシは問題なくてもp99だけが跳ねる 原因になります。
症状 考えられる原因 対策 p99レイテンシだけ周期的に跳ねる GCの停止 GCログで停止時間を確認、GC方式やヒープサイズの調整 GCが頻繁に走りCPUが高い ヒープが小さい、短命なオブジェクトを大量に作る ヒープを増やす、不要な生成を減らす GC後もメモリが減らず最後にOOM メモリリーク(参照が残っている) ヒープダンプで何が溜まっているか調べる
GCの「全停止」中は全リクエストが待たされ、p99が跳ねる アプリ 短い停止 長い全停止(Full GC) 時間 → ヒープが小さすぎる・オブジェクトを大量に作る・メモリリークで回収できない、の3つが典型原因 GCログで停止時間と頻度を見て、ヒープサイズやGC方式を調整する GCによる停止 停止が長いほど、その間に届いたリクエストすべてのレイテンシが伸びる。
面接ではこう答える 読み上げ
GCは参照をたどって使われていないオブジェクトを自動で解放する仕組みです。回収中にアプリを止める時間があるため、レイテンシのp99が跳ねる原因になります。GCログで停止時間と頻度を見て、ヒープサイズやGC方式を調整します。
SREとの関わり Java・Go・Node.jsなどのアプリのレイテンシ調査、メモリリークの切り分けで必要になります。
ひとことで プロセスは「実行中のプログラム1つ分」で、メモリが独立している。スレッドはプロセスの中の実行の流れで、メモリを共有する。
プロセス は、OSがプログラムを実行するときに作る単位で、それぞれが自分専用の仮想メモリ、開いているファイルの一覧、PIDを持ちます。あるプロセスが壊れても、他のプロセスのメモリは壊れません。新しいプロセスは、Linuxではfork(自分の複製を作る)とexec(別のプログラムに置き換える)で作られます。
スレッド は、1つのプロセスの中で並行に動く実行の流れです。コード、ヒープ、開いているファイルはプロセス内で共有し、スタックとレジスタだけをスレッドごとに持ちます。共有しているのでデータの受け渡しが速く、作るのも軽いですが、同じデータを同時に書き換えると壊れる(競合状態)ため、ロックなどの排他制御が必要です。また1つのスレッドがメモリを壊すと、プロセス全体が落ちます。
プロセス スレッド メモリ 独立 プロセス内で共有(スタックは別) 作るコスト 重い 軽い データのやりとり パイプ・ソケットなどが必要 共有メモリで直接 障害の影響 他のプロセスに及ばない プロセス全体が落ちる 例 PHP-FPMのワーカー、Pythonのmultiprocessing Javaのスレッドプール、Pumaのスレッド
補足:GoのgoroutineやJavaの仮想スレッドは、OSのスレッドの上で動くさらに軽い実行単位で、少数のOSスレッドに大量の処理を割り当てて動かします。
プロセス同士は隔離され、スレッド同士はヒープを共有する プロセスA(PID 1001) スレッド間で共有 コード グローバル変数 ヒープ ファイル・ソケット スレッド1 スタック レジスタ スレッド2 スタック レジスタ スレッド3 スタック レジスタ プロセスB(PID 1002) スレッド間で共有 コード グローバル変数 ヒープ ファイル・ソケット スレッド1 スタック レジスタ × AとBはお互いのメモリを直接見られない(通信にはパイプやソケットが必要) Aのスレッド1〜3は同じヒープを読み書きできる。速いが、排他制御が必要
プロセスとスレッドの関係 プロセスの壁は厚く、スレッドの壁は薄い。速さと安全のトレードオフ。
面接ではこう答える 読み上げ
プロセスは実行中のプログラムの単位で、メモリ空間が独立しているため互いに影響しません。スレッドはプロセスの中の実行の流れで、ヒープなどのメモリを共有するので軽く速いですが、競合状態への対策が必要で、1つの異常がプロセス全体に及びます。
SREとの関わり ワーカー数・スレッド数の設計、メモリ使用量の見積もり、マルチプロセス型とマルチスレッド型のアプリの障害の違いを説明できます。
ブラウザで試す(Linux): /proc・lsof・strace でプロセスの中をのぞく閉じる 別のページで開く →
Linux ラボを読み込んでいます…
項目 プロセス スレッド メモリ空間 独立 同じプロセス内で共有 生成コスト 大きい 小さい 障害の影響 他のプロセスに及ばない 1スレッドの異常がプロセス全体に及ぶ 通信 プロセス間通信(パイプ、ソケット)が必要 メモリを直接共有。排他制御が必要 例 Pythonのマルチプロセス、PHP-FPM Javaのスレッドプール、Puma
ひとことで 複数のスレッドが同じデータを同時に読み書きすると、順番次第で結果が壊れる。これを防ぐのがロック。
「値を読む→計算する→書く」の間に別のスレッドが割り込むと、片方の更新が失われます。これが競合状態 (レースコンディション)です。タイミング次第で起きたり起きなかったりするので、再現が難しい厄介な不具合になります。
対策は、その一連の操作を同時に1人しか実行できないようにすることです。ミューテックス (ロック)を取ってから処理し、終わったら外します。単純な加算などは、CPUが分割されない1つの命令として実行するアトミック操作 で済ませることもできます。ただしロックを使うと、2つのスレッドがお互いの持つロックを待ち合うデッドロック の危険が生まれます。DBのトランザクションで起きる問題も、本質はこれと同じです。
用語 意味 並行(Concurrency) 複数の処理を切り替えながら同時に進めているように扱うこと(1コアでもできる) 並列(Parallelism) 複数の処理を本当に同時に実行すること(複数コアが必要) ミューテックス 一度に1人だけが入れる鍵 アトミック操作 途中で割り込まれない操作 デッドロック お互いが相手のロックを待って永遠に進まない状態
ロックなしで同時に更新すると、値が1つ失われる(競合状態) スレッド1 共有変数 count スレッド2 count = 5 5を読む 5を読む 5+1=6を書く 5+1=6を書く 2回足したのに count = 6(本来は7) 対策:ミューテックスで「読む〜書く」を1人ずつに/アトミック操作を使う 競合状態の発生例 DBのロストアップデートと同じ形。どの層でも「読んで書く」の間が危ない。
面接ではこう答える 読み上げ
競合状態は、複数のスレッドが同じデータを同時に読み書きして、実行順によって結果が壊れる現象です。読む・計算する・書くの一連の操作をミューテックスで排他にするか、アトミック操作を使って防ぎます。ロックの取り方を誤るとデッドロックが起きます。
SREとの関わり DBのロックやデッドロック、分散ロック(Redisなど)、二重実行の防止を考えるときの基本になります。
ひとことで アプリは安全のため直接ハードウェアに触れず、カーネルに「お願い」して処理してもらう。そのお願いの窓口がシステムコール。
メモリは、アプリが動くユーザー空間 と、OSの中核が動くカーネル空間 に分かれています。ファイルを読む、ネットワークに送る、プロセスを作る、といったハードウェアに関わる操作は、すべてシステムコール を呼んでカーネルにやってもらいます。呼ぶたびにユーザー空間とカーネル空間の切り替えが起きるので、細かいシステムコールを大量に呼ぶと遅くなります。
straceを使うと、プロセスがどのシステムコールをどんな引数で呼び、どこで待っているかが見えます。「プロセスが固まっている」ときに、どのファイルやソケットで止まっているかを突き止める強力な手段です。
アプリはシステムコールを通してしかハードウェアに触れない ユーザー空間 アプリ ライブラリ ランタイム シェル システムコール open・read・write・socket・connect・fork・mmap など(strace で見える) カーネル スケジューラ メモリ管理 ファイルシステム ネットワーク ハードウェア CPU メモリ ディスク NIC
ユーザー空間とカーネルの関係 CPU使用率の us はユーザー空間、sy はカーネルで使った時間。sy が高ければシステムコールの多さを疑う。
面接ではこう答える 読み上げ
アプリはユーザー空間で動き、ファイルやネットワークなどハードウェアに関わる操作はシステムコールでカーネルに依頼します。straceで呼び出しを観察でき、CPU使用率のsyが高いときはシステムコールの多さを疑います。
SREとの関わり strace による調査、CPU使用率の us と sy の読み分け、コンテナの隔離の理解につながります。
ひとことで プロセスが開いているファイルやソケットにつける整理番号。Linuxでは「すべてはファイル」として番号で扱う。
プロセスがファイルを開いたり、ネットワークの接続を作ったりすると、カーネルは番号(ファイルディスクリプタ、fd)を返します。0は標準入力、1は標準出力、2は標準エラーで、それ以降が順に割り当てられます。この番号には1プロセスあたりの上限があり(ulimit -n)、超えると「Too many open files」で新しいファイルも接続も作れなくなります。
ファイルもソケットも「番号」で扱う。番号には上限がある プロセスのfd表 0 標準入力 1 標準出力 2 標準エラー 3 access.log 4 DBへのソケット 5 クライアントのソケット 上限(ulimit -n) 超えると「Too many open files」で新しい接続が失敗 調べ方 ls /proc/PID/fd | wc -l lsof -p PID 閉じ忘れ(リーク)でfdが増え続けるのが典型的な障害。数の推移を監視する ファイルディスクリプタの表 接続数の多いサーバーでは上限を引き上げ、fd数の推移を監視する。
面接ではこう答える 読み上げ
ファイルディスクリプタは、プロセスが開いているファイルやソケットを指す番号です。1プロセスあたりの上限があり、閉じ忘れなどで使い切ると新しい接続が作れなくなります。/proc/PID/fd や lsof で確認できます。
SREとの関わり 「Too many open files」障害、接続リーク、ulimitの設定、コンテナのfd上限に関わります。
ブラウザで試す(Linux): ディスクがいっぱいの状況を本物の Linux で直す閉じる 別のページで開く →
Linux ラボを読み込んでいます…
ひとことで 「データが届くまで待つ」間に、スレッドを止めて待つか、他の仕事をしながら待つか、の違い。
ブロッキングI/O では、読み込みを頼んだスレッドはデータが届くまで止まります。同時に多くの接続をさばくには接続の数だけスレッドが必要で、メモリとコンテキストスイッチの負担が大きくなります。
イベントループ では、1つのスレッドがepoll(Linux)などで多数の接続を見張り、「データが届いた」「書き込める」と準備ができた接続だけを順に処理します。少ないスレッドで大量の接続を扱えるので、Nginx、Node.js、Redisがこの方式です。ただし、1つの処理で重い計算をすると、その間すべての接続が待たされます。
1接続1スレッドは待ちで埋まる。イベントループは準備できたものだけ処理する 1接続1スレッド(ブロッキング) 接続 スレッド1:応答待ちで停止 接続 スレッド2:処理中 接続 スレッド3:応答待ちで停止 接続 スレッド4:応答待ちで停止 接続 スレッド5:応答待ちで停止 接続1万ならスレッドも1万で重くなる イベントループ(epoll) 接続 接続 接続 接続 接続 スレッド1つ 「準備できた」接続だけ 順番に処理 Nginx・Node.js・Redisの方式 重い処理で止めると全体が止まる ブロッキングとイベントループの比較 待ち時間が長い処理ほど、イベントループの効果が大きい。
面接ではこう答える 読み上げ
ブロッキングI/Oではデータを待つ間スレッドが止まるので、接続数だけスレッドが必要になります。イベントループはepollなどで多数の接続を見張り、準備できたものだけを処理するので、少ないスレッドで大量の接続をさばけます。その代わり重い処理で止めると全体が止まります。
SREとの関わり Node.jsでイベントループが詰まる障害、Nginxのworker設定、接続数の多いサービスの設計に使います。
テーマ 押さえる点 プロセスとスレッド プロセスはメモリ空間が独立、スレッドは共有。スレッドは軽いが競合状態に注意 コンテキストスイッチ CPUが実行対象を切り替えるコスト。スレッドが多すぎると性能が落ちる 仮想メモリ ページングとスワップ。スワップ発生は大幅な性能劣化のサイン システムコール ユーザー空間からカーネル機能を呼ぶ仕組み。strace で観察できる 同期 / 非同期 I/O ブロッキングの有無。epoll によるイベント駆動(Node.js、Nginx) コンテナの仕組み namespace(隔離)+ cgroups(リソース制限)。VMとの違いはカーネル共有
ひとことで データが増えたときに、処理時間が「どんな増え方をするか」を表す記号。正確な秒数ではなく、増え方の型を表す。
O(n)なら、データが10倍になれば処理時間もおよそ10倍。O(n²)なら100倍、O(log n)ならほとんど増えません。定数倍(2倍速いなど)は無視して、データが大きくなったときに支配的になる部分だけを見ます。
記法 データが1,000→100万(1,000倍)になると 身近な例 O(1) 変わらない ハッシュテーブルの参照、配列の添字アクセス O(log n) 約2倍 B+Treeインデックスの検索、二分探索 O(n) 1,000倍 インデックスなしの全件走査、ログを1行ずつ読む O(n log n) 約2,000倍 ソート O(n²) 100万倍 二重ループ、N+1問題が入れ子になったもの
データが増えたとき、処理時間がどう増えるかの「増え方」 データ量 n → 処理時間 O(n²) O(n log n) O(n) O(log n) O(1) O(1):ハッシュ参照 O(log n):インデックス検索 O(n):全件走査 O(n log n):ソート O(n²):二重ループ・N+1 計算量ごとの増え方 小さいデータでは差がなくても、本番の大きなデータで差が爆発する。
面接ではこう答える 読み上げ
O記法は、データ量が増えたときの処理時間の増え方を表します。たとえばインデックスなしの検索はO(n)なので、データが1,000倍になると時間も1,000倍になりますが、B+Treeの検索はO(log n)なのでほとんど増えません。
SREとの関わり 「開発環境では速いのに本番で遅い」障害の多くは、データ量の違いで計算量の差が表に出たものです。
計算量 例 100万件での目安 O(1) ハッシュテーブルの参照 一瞬 O(log n) B+Tree の探索、二分探索 約20回の比較 O(n) 全件走査 100万回 O(n log n) ソート 約2000万回 O(n²) 二重ループ、N+1 の悪化版 1兆回(現実的でない)
ひとことで キーから計算で「置き場所の番号」を出し、探さずに直接取りに行く仕組み。
ハッシュ関数は、どんなキーも決まった範囲の数値に変換します。その数値を置き場所(バケット)の番号として使うので、何件あっても平均O(1)で取り出せます。プログラミング言語の辞書(Pythonのdict、JavaのHashMap)やRedis、Memcachedの中身がこれです。
違うキーが同じ番号になる衝突 は避けられないので、同じバケットの中をリストでつなぐなどして解決します。データが増えると衝突が増えるため、ある程度埋まったらバケットを増やして入れ直します(リハッシュ)。この入れ直しの瞬間だけ重くなることがあります。順序を持たないので、範囲検索や並べ替えはできません。
キーから「置き場所の番号」を計算するので、探さずに一発で届く user:42 user:7 user:99 ハッシュ関数 hash(key) % 8 0 1 2 3 4 5 6 7 user:42 の値 user:7 user:99 同じ番号に当たったら(衝突)、 リストでつなぐ。偏ると遅くなる 平均O(1)だが、範囲検索や並び順は苦手(だからDBのインデックスはB+Treeが主流) ハッシュテーブルの仕組み 等価検索は最速だが、範囲検索はできない。用途でB+Treeと使い分ける。
面接ではこう答える 読み上げ
キーをハッシュ関数で数値に変換し、それを配列の添字として使うことで平均O(1)で値を取り出す構造です。衝突はチェイン法などで解決します。順序を持たないので範囲検索には向かず、DBのインデックスにB+Treeが多い理由もここにあります。
SREとの関わり キャッシュ(Redis)の設計、分散キャッシュでのキーの割り振り(コンシステントハッシュ)を理解する土台になります。
コーディング試験がある会社では必要。ハッシュテーブル(平均 O(1)、衝突処理)、木構造、計算量の見積もりを押さえる。SRE向けにはログ集計やレート制限の実装などが出やすい。
B+Tree・ハッシュ・計算量で「なぜ速いのか」を説明できるようにする CPU・メモリ・プロセス・スレッドの仕組みが、障害の症状とどうつながるかをつかむ CPUの構造(コア・スレッド・vCPU) :CPUは「命令を実行する装置」。中に複数のコアがあり、それぞれが独立して処理を進める。コンテキストスイッチとスケジューリング :コアの数より多い処理を動かすために、OSが実行する処理を細かく切り替えること。切り替えにはコストがかかる。記憶階層とキャッシュ :CPUに近い記憶ほど速いが小さく、遠いほど遅いが大きい。この差を埋めるためにあらゆる層でキャッシュが使われる。仮想メモリとページング :各プロセスに「自分専用の広いメモリがある」ように見せ、実際の物理メモリとの対応はOSとCPUが裏で管理する仕組み。プロセスのメモリ配置 :1つのプロセスのメモリは、プログラム本体、変数、ヒープ、スタックといった区画に分かれている。スタックとヒープ :スタックは関数呼び出しのたびに自動で積んで自動で片付く領域。ヒープは自由に確保でき、片付けは自分かGCが行う領域。ガベージコレクション(GC) :使われなくなったヒープ上のオブジェクトを、プログラムの代わりに自動で探して解放する仕組み。プロセスとスレッド :プロセスは「実行中のプログラム1つ分」で、メモリが独立している。スレッドはプロセスの中の実行の流れで、メモリを共有する。競合状態とロック :複数のスレッドが同じデータを同時に読み書きすると、順番次第で結果が壊れる。これを防ぐのがロック。ユーザー空間・カーネル・システムコール :アプリは安全のため直接ハードウェアに触れず、カーネルに「お願い」して処理してもらう。そのお願いの窓口がシステムコール。ファイルディスクリプタ :プロセスが開いているファイルやソケットにつける整理番号。Linuxでは「すべてはファイル」として番号で扱う。I/Oモデル(ブロッキングとイベントループ) :「データが届くまで待つ」間に、スレッドを止めて待つか、他の仕事をしながら待つか、の違い。計算量(O記法) :データが増えたときに、処理時間が「どんな増え方をするか」を表す記号。正確な秒数ではなく、増え方の型を表す。ハッシュテーブル :キーから計算で「置き場所の番号」を出し、探さずに直接取りに行く仕組み。 面接ではこう聞かれるまず答えを見ずに声に出して答え、そのあと開いて比べる。
すべて開く すべて閉じる
Q1 プロセスとスレッドの違いは? 模範回答
プロセスはメモリ空間が独立していて、一つが落ちても他に影響しません。スレッドは同じプロセスの中でメモリを共有するため軽く速い一方、競合状態への対策が必要で、一つの異常がプロセス全体に及びます。
深掘りされたら
見られているポイント 運用上の影響まで言えるか