前提
読んでみた書籍はこちら
書籍で大規模サービスの事例として、はてなブックマークが取り上げられていました。
- 登録ユーザーは100万人以上、1500万のユニークユーザー/月
- 数十億アクセス/月
- ピーク時の回線トラフィック量は430Mbps
- サーバーは500台以上
これと比較すると関わったプロジェクトとしては小〜中規模位程度で大規模は関わったことはないのですが、面白そうだったので読んでみました。
データベース(RDB)の中身としては2009年時点で以下のようでした。
レコード数 | データサイズ |
entryテーブル:1520万 | 3GB |
bookmarkテーブル:4500万 | 5.5GB |
tagテーブル:5000万 | 200GB |
インデックスを効かせないSQLでは200秒待っても結果が返ってこないとか
mysql> select url from entry use index(hoge) where eid = 9615899;
これは私の中の体感値とも似ています。
技術選定するタイミングで数億レコードを入れたテーブルに対してSQLを実行してパフォーマンスを確認することがありましたが、こりゃ使えないとなった記憶があります。
そもそもRDBで要件を満たす必要があるのか?とか考えると論点がズレるなと思うのでここでは横に置いておきます。
なぜ反応が遅くなるか?
勘所としては以下の様な感じだそうです。
- メモリ内で計算ができないから
- データ量の増加に強いアルゴリズム、データ構造ではないから
もうちょっと位置付けとかを整理すると以下のようなイメージになります。
(これは書籍を読んでの自分の中の全体像イメージ図です。DBをどう描くかとか悩んだんですが、以下の様に。間違えてたらすいません🙏 あくまでイメージを持つことが目的です。)
メモリ内で計算ができないから
アプリケーションはメモリにあるデータを読み込んで、そのデータをWEBアプリに返して動きが滑らかなアプリにしています。
もしメモリにデータがない場合は、ディスクにデータを読みにいって、そのデータをメモリに格納しつつ、WEBアプリに返す形になります。
この時、メモリにデータが載らないと、基本的にはディスクをずーっと読んでいく動作になります。
問題はこのディスクを読む動作です。
ディスクはメモリに比べてとても遅いです。ディスクはメモリに比べて10^5, 10^6倍、データ探測の速度が遅いです。
なのでこの動作が入ると処理速度がとても遅くなってしまいます。
なぜディスクの探索速度が遅いか
- ディスクは回転して複数の箇所から読み取る
- ヘッドと呼ばれる磁気を読み取る
という、物理的な動作が入るためです。
この結果、データを探索するのに数ミリ秒の時間がかかってしまいます。
もし、ディスクのさまざまな箇所にデータが散らばっててて、二部探索などでデータを探すとなると、ディスクを回転してヘッドを動かして探して、また回転してヘッドを動かして探して、と繰り返すので結果さらに遅くなります。
一方でメモリの場合は、電気的な部品で探究速度に物理的な要因が乗らないのでマイクロ秒単位でデータ探索ができます。
転送速度も影響している
探究速度だけでなく、CPU⇄メモリ、CPU⇄HDD間のデータの転送速度にも差があります。約100倍位の差があります。
CPU⇄メモリ間は高速なバスで接続されているため約GB/秒ですが、CPU⇄HDD間は約十MB/秒となります。
SSDが最近は割と主流になっていますが、物理的な探索速度は高速になりましたが、転送速度がボトルネックになるようで、メモリほどの速度は出ないようです。
データ量の増加に強いアルゴリズム、データ構造ではないから
アルゴリズム・データ構造を駆使できると、数億件レコードを数MBで保持するようなデータ構造もできるようです。(すごい🙄)
なので、大規模データを対峙した場合にはアプリのパフォーマンスにより大きな影響が出てきます。
例えば、線形探索はO(n)、二分探索はO(log n)と計算量を表せて、二部探索の方が計算量は少なく済みます。
アルゴリズムのオーダー表記というものがあり、以下のような大小関係になります。
O(1) < O(log n) < O(n) < O(n log n) < O(n) < O(n) … O(n) < O(2)
この計算量を考えてプログラムを組むことが大切のようです。
アルゴリズムとデータ構造が一緒に議論されるのは、
アルゴリズムでよく使う操作に合わせてデータ構造を選ぶ必要が必要があるからのようです。
データ構造に合わせたアルゴリズムで探索・挿入・ソートなどを行うのがベターとのこと。
この辺り正直よくわからなかったので、また別の機会で学びたいところ。
何が原因かを調査する・対策する
何が原因で遅くなるかを調査をする視点として以下があります。
- ロードアベレージを確認する
- CPU、I/Oを確認する
ロードアベレージを確認する
負荷見きわめのとっかかりとして、
top
コマンド uptime
コマンドなどでロードアベレージを確認します。ロードアベレージだけでは原因がどこかは判断できないこともあるので、値を見て探りを入れることができます。
アプリのロードアベレージが低いのにスループットが上がらない場合は、ネットワーク・リモートホスト側に原因がないかの調査も視野に入ってきます。
CPU、I/Oを確認する
ロードアベレージが高かった場合、CPUとI/Oどちらに原因があるのか切り分けます。
調査の仕方としては、
sar
コマンドや vmstat
コマンドでCPU使用率やI/O待率の推移で確認します。CPU負荷が高そうな場合の調査・対策
- ユーザープログラムがボトルネックか、システムプログラムが原因なのかを
top
コマンド、sar
コマンドで確認する。
ps
で見えるプロセスの状態やCPU使用時間などを見て、プロセスを特定する。
- プロセスからさらに詳細を調べる際は、
strace
でトレースしたり、oprofile
でプロファイリングして詰めていく。
CPUが負荷が高くなるケースとしては、
- ディスクやメモリ容量などがボトルネックになっていない
- プログラムが暴走してCPUに異常に負荷がかかっている
のいずれかなので、前者の場合でスループットに問題なければスケールアウトやプログラムのロジック・アルゴリズム改善で対応します。
WEBサーバ・APサーバは基本的にはCPU負荷しか掛からない場所なので、スケールアウトの手段が出てきます。
I/O負荷が高そうな場合の調査・対策
I/Oが高い場合は、
- プログラムからの入出力が多くて負荷が高い
- スワップが発生してディスクアクセスが発生している
のいずれかがほとんどです。
この調査は、
sar
コマンドや vmstat
コマンド、 ps
コマンドで確認して問題の切り分けを行います。前者の場合、
- メモリ増設でキャッシュ領域を拡大できるなら、メモリ増設する。
- メモリ増設できない場合は、データの分散やキャッシュサーバの導入などを検討する。もしくはプログラムでI/Oを軽減を検討する。
後者の場合、
- プログラムの不具合でメモリを使いすぎている場合は、プログラムの改善を行う。
- メモリが不足している場合はメモリ増設で対応する。
- メモリが増設できない場合は、分散を検討する。
I/O負荷はDBサーバの領域になりますが、こちらはCPU負荷の対策のように簡単にはWEB・APサーバのスケールアウトができないです。
理由は、もしスケールアウトして二つのデータベースがある時、片方のデータベースを更新した時に、もう片方のデータベースをどうやって同期するかと言う問題が発生するからと、更新系クエリが増えてくると負荷が厳しくなってくるからです。
といっても、WEBアプリケーションは、参照系がクエリ全体の90%と言われているので、参照系がボトルネックになることが多いようです。
そのため、DBの領域ではマスター・スレーブ構成をとり、マスターを追いかけてコピーする構成にするわけです。
APサーバからは、ロードバランサを経由してスレーブに問い合わせて、クエリを複数サーバに分散させます。
SELECTなどの参照系はロードバランスを通すようにして、更新系はマスタへ直接実行するようにします。
マスタをスケールさせたい場合は、
- テーブル分割
- key-valueストア
があるようですが、そこまで理解していないのでまたの機会へ。
と、こんな感じです。
負荷が高ければスケールアウトすればいいじゃん??と、単純な話ではなくちゃんとどこが原因だから、その適した対策を実施した!の背景がちょっと理解できた気がしました。
エンジニア始めたてにも基礎情報を眺めてインプットしたような記憶があるのですが、実務経験を数年通してこれを読んでみて、改めて理解が深まった気がしました。