本文へ移動

ホーム / SRE / 第3部 コンピュータとネットワークの基礎

第3部 コンピュータとネットワークの基礎・第3章

コンピュータの基礎(CPU・メモリ・OS)

★★☆ 重要18トピック・面接 1問

面接で「それはなぜ?」と一段深く聞かれたときに答えられるよう、土台になる概念を図で解説します。各項目の「面接ではこう答える」は、そのまま声に出して練習できる長さにしています。

ひとことで

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(ソケット)コア0HWスレッド1HWスレッド2L1(コア専用)L2キャッシュコア1HWスレッド1HWスレッド2L1(コア専用)L2キャッシュコア2HWスレッド1HWスレッド2L1(コア専用)L2キャッシュコア3HWスレッド1HWスレッド2L1(コア専用)L2キャッシュL3キャッシュ(全コアで共有)メインメモリ(RAM)この例は4コア×2スレッド=8 vCPU。AWSのvCPUの多くはハードウェアスレッド1つに相当する
CPUの中の構造

mpstat -P ALL でコアごとの偏りを見ると、1コアだけ張り付いているボトルネックが分かる。

面接ではこう答える

CPUは複数のコアを持ち、各コアが独立して命令を実行します。1コアで2つの処理を交互に進めるハードウェアスレッドがあり、クラウドのvCPUはこれ1つ分に相当することが多いです。たとえば4コア8スレッドなら8 vCPUに見えます。

別のページで開く →

Linux ラボを読み込んでいます…

ひとことで

コアの数より多い処理を動かすために、OSが実行する処理を細かく切り替えること。切り替えにはコストがかかる。

OSのスケジューラは、実行したい処理(プロセスやスレッド)に順番にCPU時間を割り当てます。切り替えるときは、今の処理のレジスタなどの状態を保存し、次の処理の状態を復元します。これがコンテキストスイッチで、1回は数マイクロ秒程度ですが、CPUキャッシュの中身も入れ替わるため、回数が増えると目に見えて遅くなります。

スレッドを増やせば速くなるとは限らないのはこのためです。待ちの多い処理(I/O)ならスレッドを増やす効果がありますが、計算ばかりの処理ではコア数を超えて増やしても切り替えが増えるだけです。

確認方法見るもの
vmstat 1cs列(1秒あたりの切り替え回数)、r列(実行待ちの数)
pidstat -w 1プロセスごとの自発的・非自発的な切り替え
topload average と CPU使用率の差
1つのコアは、プロセスやスレッドを細かく切り替えて動かしているCPUコア1つ処理A処理B処理C処理A処理B切り替え時間 →赤い部分がコンテキストスイッチ。レジスタの保存と復元、キャッシュの入れ替えが起き、スレッドが多すぎるとここが増えて遅くなる
コンテキストスイッチの様子

スレッドを増やしすぎると、赤い切り替えの時間が増えて実際の処理が減る。

面接ではこう答える

OSはコア数より多い処理を動かすため、実行する処理を短い時間で切り替えています。切り替えるたびにレジスタの保存や復元、キャッシュの入れ替えが起きるので、スレッドを増やしすぎるとかえって遅くなります。

3.3記憶階層とキャッシュ

用語★★☆ 重要
ひとことで

CPUに近い記憶ほど速いが小さく、遠いほど遅いが大きい。この差を埋めるためにあらゆる層でキャッシュが使われる。

CPUのレジスタ、L1〜L3キャッシュ、メインメモリ、SSD、HDD、ネットワークの順に、アクセス時間は1段ごとに桁違いに遅くなります。メモリはL1キャッシュの約100倍、SSDはメモリの約1,000倍遅い、という感覚を持っておくと設計の判断に役立ちます。

キャッシュが効くのは、データの使われ方に偏りがあるからです。一度使ったデータはまた使われやすく(時間的局所性)、近くのデータも続けて使われやすい(空間的局所性)。この性質はCPUキャッシュでも、DBのバッファプールでも、Redisでも、CDNでも同じです。

1段下がるごとに桁が変わる。だからキャッシュが効くアクセスにかかるおおよその時間(対数目盛)と、L1を1秒に例えた体感おおよその時間L1=1秒ならレジスタ0.3ns0.3秒L1キャッシュ1ns1秒L2キャッシュ4ns4秒L3キャッシュ20ns20秒メインメモリ100ns1分40秒SSDランダム読み100µs約1日同じDC内の往復500µs約6日HDDシーク10ms約4か月日本⇔米国の往復150ms約5年CPUに近いほど速く小さく高価。遠いほど遅く大きく安い。数値は桁を示す目安
記憶階層ごとのアクセス時間

ネットワーク越しの呼び出しは、メモリ参照の数千〜数百万倍遅い。呼び出し回数を減らす設計が効く理由。

面接ではこう答える

CPUに近いほど速く小さく、遠いほど遅く大きいという階層があり、メモリはL1キャッシュの約100倍、SSDはメモリの約1,000倍遅いです。データの使われ方には偏りがあるので、各層でキャッシュを使ってよく使うデータを速い層に置きます。

ひとことで

各プロセスに「自分専用の広いメモリがある」ように見せ、実際の物理メモリとの対応は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のページ0Bのページ1Aのページ2Bのページ0Aのページ1スワップAのページ3実メモリに無いページに触るとページフォルトが起き、ディスクから読み戻す(遅い)。これが頻発するとスワップで性能が急落する
仮想メモリと物理メモリの対応

同じ「ページ0」でも、プロセスごとに別の物理メモリを指している。

面接ではこう答える

仮想メモリは、各プロセスに専用のアドレス空間を見せ、MMUがページテーブルで物理メモリに変換する仕組みです。プロセス同士の隔離ができ、物理メモリが足りないときは使われていないページをスワップに逃がします。スワップが頻発すると大きく性能が落ちます。

別のページで開く →

Linux ラボを読み込んでいます…

3.5プロセスのメモリ配置

用語★★☆ 重要
ひとことで

1つのプロセスのメモリは、プログラム本体、変数、ヒープ、スタックといった区画に分かれている。

下から順に、テキスト(機械語のプログラム本体、読み取り専用)、データ・BSS(グローバル変数や静的変数)、ヒープ(実行中に必要に応じて確保する領域)、そして上のほうにスタック(関数呼び出しの記録とローカル変数)が並びます。ヒープは上へ、スタックは下へ伸び、間が空き領域です。

1つのプロセスのメモリは、用途ごとに区画が分かれている高いアドレス低いアドレスカーネル空間アプリからは直接触れないスタック関数呼び出し・ローカル変数空き領域ヒープnewやmallocで確保BSS・データテキスト(機械語)(予約領域)下に伸びる関数を呼ぶたびに積まれ、戻ると消える深い再帰で溢れるとスタックオーバーフロー上に伸びる明示的に解放するかGCが回収解放し忘れるとメモリリークグローバル変数・静的変数プログラム本体(読み取り専用)
プロセスのメモリ配置

スタックとヒープは向かい合って伸びる。どちらも無限ではない。

面接ではこう答える

プロセスのメモリは、プログラム本体のテキスト、グローバル変数のデータ領域、動的に確保するヒープ、関数呼び出しに使うスタックに分かれています。ヒープとスタックは逆方向に伸びていきます。

3.6スタックとヒープ

用語★★☆ 重要
ひとことで

スタックは関数呼び出しのたびに自動で積んで自動で片付く領域。ヒープは自由に確保でき、片付けは自分か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に任せます。ヒープの解放漏れや参照の持ちすぎがメモリリークになります。

ひとことで

使われなくなったヒープ上のオブジェクトを、プログラムの代わりに自動で探して解放する仕組み。

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方式を調整します。

3.8プロセスとスレッド

用語★★☆ 重要
ひとことで

プロセスは「実行中のプログラム1つ分」で、メモリが独立している。スレッドはプロセスの中の実行の流れで、メモリを共有する。

プロセスは、OSがプログラムを実行するときに作る単位で、それぞれが自分専用の仮想メモリ、開いているファイルの一覧、PIDを持ちます。あるプロセスが壊れても、他のプロセスのメモリは壊れません。新しいプロセスは、Linuxではfork(自分の複製を作る)とexec(別のプログラムに置き換える)で作られます。

スレッドは、1つのプロセスの中で並行に動く実行の流れです。コード、ヒープ、開いているファイルはプロセス内で共有し、スタックとレジスタだけをスレッドごとに持ちます。共有しているのでデータの受け渡しが速く、作るのも軽いですが、同じデータを同時に書き換えると壊れる(競合状態)ため、ロックなどの排他制御が必要です。また1つのスレッドがメモリを壊すと、プロセス全体が落ちます。

プロセススレッド
メモリ独立プロセス内で共有(スタックは別)
作るコスト重い軽い
データのやりとりパイプ・ソケットなどが必要共有メモリで直接
障害の影響他のプロセスに及ばないプロセス全体が落ちる
例PHP-FPMのワーカー、PythonのmultiprocessingJavaのスレッドプール、Pumaのスレッド

補足:GoのgoroutineやJavaの仮想スレッドは、OSのスレッドの上で動くさらに軽い実行単位で、少数のOSスレッドに大量の処理を割り当てて動かします。

プロセス同士は隔離され、スレッド同士はヒープを共有するプロセスA(PID 1001)スレッド間で共有コードグローバル変数ヒープファイル・ソケットスレッド1スタックレジスタスレッド2スタックレジスタスレッド3スタックレジスタプロセスB(PID 1002)スレッド間で共有コードグローバル変数ヒープファイル・ソケットスレッド1スタックレジスタ×AとBはお互いのメモリを直接見られない(通信にはパイプやソケットが必要)Aのスレッド1〜3は同じヒープを読み書きできる。速いが、排他制御が必要
プロセスとスレッドの関係

プロセスの壁は厚く、スレッドの壁は薄い。速さと安全のトレードオフ。

面接ではこう答える

プロセスは実行中のプログラムの単位で、メモリ空間が独立しているため互いに影響しません。スレッドはプロセスの中の実行の流れで、ヒープなどのメモリを共有するので軽く速いですが、競合状態への対策が必要で、1つの異常がプロセス全体に及びます。

別のページで開く →

Linux ラボを読み込んでいます…

項目プロセススレッド
メモリ空間独立同じプロセス内で共有
生成コスト大きい小さい
障害の影響他のプロセスに及ばない1スレッドの異常がプロセス全体に及ぶ
通信プロセス間通信(パイプ、ソケット)が必要メモリを直接共有。排他制御が必要
例Pythonのマルチプロセス、PHP-FPMJavaのスレッドプール、Puma

3.10競合状態とロック

用語★★☆ 重要
ひとことで

複数のスレッドが同じデータを同時に読み書きすると、順番次第で結果が壊れる。これを防ぐのがロック。

「値を読む→計算する→書く」の間に別のスレッドが割り込むと、片方の更新が失われます。これが競合状態(レースコンディション)です。タイミング次第で起きたり起きなかったりするので、再現が難しい厄介な不具合になります。

対策は、その一連の操作を同時に1人しか実行できないようにすることです。ミューテックス(ロック)を取ってから処理し、終わったら外します。単純な加算などは、CPUが分割されない1つの命令として実行するアトミック操作で済ませることもできます。ただしロックを使うと、2つのスレッドがお互いの持つロックを待ち合うデッドロックの危険が生まれます。DBのトランザクションで起きる問題も、本質はこれと同じです。

用語意味
並行(Concurrency)複数の処理を切り替えながら同時に進めているように扱うこと(1コアでもできる)
並列(Parallelism)複数の処理を本当に同時に実行すること(複数コアが必要)
ミューテックス一度に1人だけが入れる鍵
アトミック操作途中で割り込まれない操作
デッドロックお互いが相手のロックを待って永遠に進まない状態
ロックなしで同時に更新すると、値が1つ失われる(競合状態)スレッド1共有変数 countスレッド2count = 55を読む5を読む5+1=6を書く5+1=6を書く2回足したのに count = 6(本来は7)対策:ミューテックスで「読む〜書く」を1人ずつに/アトミック操作を使う
競合状態の発生例

DBのロストアップデートと同じ形。どの層でも「読んで書く」の間が危ない。

面接ではこう答える

競合状態は、複数のスレッドが同じデータを同時に読み書きして、実行順によって結果が壊れる現象です。読む・計算する・書くの一連の操作をミューテックスで排他にするか、アトミック操作を使って防ぎます。ロックの取り方を誤るとデッドロックが起きます。

ひとことで

アプリは安全のため直接ハードウェアに触れず、カーネルに「お願い」して処理してもらう。そのお願いの窓口がシステムコール。

メモリは、アプリが動くユーザー空間と、OSの中核が動くカーネル空間に分かれています。ファイルを読む、ネットワークに送る、プロセスを作る、といったハードウェアに関わる操作は、すべてシステムコールを呼んでカーネルにやってもらいます。呼ぶたびにユーザー空間とカーネル空間の切り替えが起きるので、細かいシステムコールを大量に呼ぶと遅くなります。

straceを使うと、プロセスがどのシステムコールをどんな引数で呼び、どこで待っているかが見えます。「プロセスが固まっている」ときに、どのファイルやソケットで止まっているかを突き止める強力な手段です。

アプリはシステムコールを通してしかハードウェアに触れないユーザー空間アプリライブラリランタイムシェルシステムコールopen・read・write・socket・connect・fork・mmap など(strace で見える)カーネルスケジューラメモリ管理ファイルシステムネットワークハードウェアCPUメモリディスクNIC
ユーザー空間とカーネルの関係

CPU使用率の us はユーザー空間、sy はカーネルで使った時間。sy が高ければシステムコールの多さを疑う。

面接ではこう答える

アプリはユーザー空間で動き、ファイルやネットワークなどハードウェアに関わる操作はシステムコールでカーネルに依頼します。straceで呼び出しを観察でき、CPU使用率のsyが高いときはシステムコールの多さを疑います。

ひとことで

プロセスが開いているファイルやソケットにつける整理番号。Linuxでは「すべてはファイル」として番号で扱う。

プロセスがファイルを開いたり、ネットワークの接続を作ったりすると、カーネルは番号(ファイルディスクリプタ、fd)を返します。0は標準入力、1は標準出力、2は標準エラーで、それ以降が順に割り当てられます。この番号には1プロセスあたりの上限があり(ulimit -n)、超えると「Too many open files」で新しいファイルも接続も作れなくなります。

ファイルもソケットも「番号」で扱う。番号には上限があるプロセスのfd表0標準入力1標準出力2標準エラー3access.log4DBへのソケット5クライアントのソケット上限(ulimit -n)超えると「Too many openfiles」で新しい接続が失敗調べ方ls /proc/PID/fd | wc -llsof -p PID閉じ忘れ(リーク)でfdが増え続けるのが典型的な障害。数の推移を監視する
ファイルディスクリプタの表

接続数の多いサーバーでは上限を引き上げ、fd数の推移を監視する。

面接ではこう答える

ファイルディスクリプタは、プロセスが開いているファイルやソケットを指す番号です。1プロセスあたりの上限があり、閉じ忘れなどで使い切ると新しい接続が作れなくなります。/proc/PID/fd や lsof で確認できます。

別のページで開く →

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などで多数の接続を見張り、準備できたものだけを処理するので、少ないスレッドで大量の接続をさばけます。その代わり重い処理で止めると全体が止まります。

3.14OS基礎

★★☆ 重要
テーマ押さえる点
プロセスとスレッドプロセスはメモリ空間が独立、スレッドは共有。スレッドは軽いが競合状態に注意
コンテキストスイッチCPUが実行対象を切り替えるコスト。スレッドが多すぎると性能が落ちる
仮想メモリページングとスワップ。スワップ発生は大幅な性能劣化のサイン
システムコールユーザー空間からカーネル機能を呼ぶ仕組み。strace で観察できる
同期 / 非同期 I/Oブロッキングの有無。epoll によるイベント駆動(Node.js、Nginx)
コンテナの仕組みnamespace(隔離)+ cgroups(リソース制限)。VMとの違いはカーネル共有

3.15計算量(O記法)

用語★★☆ 重要
ひとことで

データが増えたときに、処理時間が「どんな増え方をするか」を表す記号。正確な秒数ではなく、増え方の型を表す。

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)なのでほとんど増えません。

計算量例100万件での目安
O(1)ハッシュテーブルの参照一瞬
O(log n)B+Tree の探索、二分探索約20回の比較
O(n)全件走査100万回
O(n log n)ソート約2000万回
O(n²)二重ループ、N+1 の悪化版1兆回(現実的でない)

3.17ハッシュテーブル

用語★★☆ 重要
ひとことで

キーから計算で「置き場所の番号」を出し、探さずに直接取りに行く仕組み。

ハッシュ関数は、どんなキーも決まった範囲の数値に変換します。その数値を置き場所(バケット)の番号として使うので、何件あっても平均O(1)で取り出せます。プログラミング言語の辞書(Pythonのdict、JavaのHashMap)やRedis、Memcachedの中身がこれです。

違うキーが同じ番号になる衝突は避けられないので、同じバケットの中をリストでつなぐなどして解決します。データが増えると衝突が増えるため、ある程度埋まったらバケットを増やして入れ直します(リハッシュ)。この入れ直しの瞬間だけ重くなることがあります。順序を持たないので、範囲検索や並べ替えはできません。

キーから「置き場所の番号」を計算するので、探さずに一発で届くuser:42user:7user:99ハッシュ関数hash(key) % 801234567user:42 の値user:7user:99同じ番号に当たったら(衝突)、リストでつなぐ。偏ると遅くなる平均O(1)だが、範囲検索や並び順は苦手(だからDBのインデックスはB+Treeが主流)
ハッシュテーブルの仕組み

等価検索は最速だが、範囲検索はできない。用途でB+Treeと使い分ける。

面接ではこう答える

キーをハッシュ関数で数値に変換し、それを配列の添字として使うことで平均O(1)で値を取り出す構造です。衝突はチェイン法などで解決します。順序を持たないので範囲検索には向かず、DBのインデックスにB+Treeが多い理由もここにあります。

コーディング試験がある会社では必要。ハッシュテーブル(平均 O(1)、衝突処理)、木構造、計算量の見積もりを押さえる。SRE向けにはログ集計やレート制限の実装などが出やすい。

この章のまとめ

🖨 まとめシート(印刷・PDF)
  • B+Tree・ハッシュ・計算量で「なぜ速いのか」を説明できるようにする
  • CPU・メモリ・プロセス・スレッドの仕組みが、障害の症状とどうつながるかをつかむ
  • CPUの構造(コア・スレッド・vCPU):CPUは「命令を実行する装置」。中に複数のコアがあり、それぞれが独立して処理を進める。
  • コンテキストスイッチとスケジューリング:コアの数より多い処理を動かすために、OSが実行する処理を細かく切り替えること。切り替えにはコストがかかる。
  • 記憶階層とキャッシュ:CPUに近い記憶ほど速いが小さく、遠いほど遅いが大きい。この差を埋めるためにあらゆる層でキャッシュが使われる。
  • 仮想メモリとページング:各プロセスに「自分専用の広いメモリがある」ように見せ、実際の物理メモリとの対応はOSとCPUが裏で管理する仕組み。
  • プロセスのメモリ配置:1つのプロセスのメモリは、プログラム本体、変数、ヒープ、スタックといった区画に分かれている。
  • スタックとヒープ:スタックは関数呼び出しのたびに自動で積んで自動で片付く領域。ヒープは自由に確保でき、片付けは自分かGCが行う領域。
  • ガベージコレクション(GC):使われなくなったヒープ上のオブジェクトを、プログラムの代わりに自動で探して解放する仕組み。
  • プロセスとスレッド:プロセスは「実行中のプログラム1つ分」で、メモリが独立している。スレッドはプロセスの中の実行の流れで、メモリを共有する。
  • 競合状態とロック:複数のスレッドが同じデータを同時に読み書きすると、順番次第で結果が壊れる。これを防ぐのがロック。
  • ユーザー空間・カーネル・システムコール:アプリは安全のため直接ハードウェアに触れず、カーネルに「お願い」して処理してもらう。そのお願いの窓口がシステムコール。
  • ファイルディスクリプタ:プロセスが開いているファイルやソケットにつける整理番号。Linuxでは「すべてはファイル」として番号で扱う。
  • I/Oモデル(ブロッキングとイベントループ):「データが届くまで待つ」間に、スレッドを止めて待つか、他の仕事をしながら待つか、の違い。
  • 計算量(O記法):データが増えたときに、処理時間が「どんな増え方をするか」を表す記号。正確な秒数ではなく、増え方の型を表す。
  • ハッシュテーブル:キーから計算で「置き場所の番号」を出し、探さずに直接取りに行く仕組み。

この章のクイズ

面接ではこう聞かれる

まず答えを見ずに声に出して答え、そのあと開いて比べる。

Q1プロセスとスレッドの違いは?

模範回答

プロセスはメモリ空間が独立していて、一つが落ちても他に影響しません。スレッドは同じプロセスの中でメモリを共有するため軽く速い一方、競合状態への対策が必要で、一つの異常がプロセス全体に及びます。

深掘りされたら

  • コンテナはプロセスとどう違いますか?

見られているポイント運用上の影響まで言えるか