はじめに
これまでデータベースや検索エンジンといったミドルウェアをチューニングする機会にたびたび遭遇してきました。
どのようなポイントでチューニングをすれば良いのかを理解するには、ミドルウェアの仕組みを理解する必要があり、
なぜミドルウェアがそのような仕組みになっているかを理解するには、基盤の部分つまりOS(Linux)の仕組みを理解する必要があります。
個人的には、結局どのようなミドルウェアを使用するにも、
- いかに効率よく、メモリを使ってディスク上のファイルのデータを扱うか
- いかに効率よく、プロセスやスレッドで処理をさばくか
の2点だと思います。
本記事では、上記2点のうち、
- いかに効率よく、メモリを使ってディスク上のファイルのデータを扱うか
について、Linuxとミドルウェアがどのように工夫しているのかを、自分なりにまとめたものとなります。
※この記事は都度ブラッシュアップしていきます
※勉強中のため間違っている箇所があるかもしれません、ツッコんでいただけると幸いです
先に要約
- なるべくディスク上のファイルのデータをメモリに載せて読み書きしたい(ディスクI/Oを減らす)
- そのかわりメモリ上のデータを更新したらファイルにも反映する仕組みが必要(データの永続化)
- また更新内容を確実に担保するための仕組みも必要(クラッシュリカバリ)
Linuxではどう工夫しているのか
ディスクI/Oを減らす
ページキャッシュ
TODO 図
Linuxでは、一度ディスクより読み込んだデータはメモリ上にキャッシュされます。
そして次回以降の読み込み処理ではディスクへアクセスせずにキャッシュに対して行われます。
そうすることで読み込みが高速に行われます。
メモリマップトファイル
TODO 図
ディスク上のファイルのデータを仮想アドレス空間上にメモリマップする機能。
システムコールmmapによってメモリマップ処理を行います。
マップされたファイルのデータはメモリへのアクセスと同じように行えるため、ディスクI/Oの削減に効果があります。
ページキャッシュ上のデータへのアクセスについては、システムコールreadやwriteにてアクセスする他に、mmapにより仮想アドレス空間上にメモリマップする方法があります。
マップされたファイルのデータはメモリへのアクセスと同じように行える。
データの永続化
TODO 図
ダーティーページ
ページキャッシュ上で修正されたデータはすぐにはディスク上へ書き込まれません。
書き込みが行われたタイミングで、その都度ディスクへの書き込みが発生するのはパフォーマンス的によろしくないからです。
修正されたデータはダーティーページとよばれ、後述するライトバックの仕組みにより、非同期でディスク上へと書き込まれます。
ライトバック(遅延書き込み)
ページキャッシュ上の修正されたデータであるダーティーページはバックグラウンドプロセスにより、後ほどディスク上のファイルへとフラッシュされます。
なお、明示的にフラッシュさせたい場合は、fsyncやfdatasyncといったシステムコールを呼び出します。
MySQL(InnoDB)ではどう工夫しているのか
ディスクI/Oを減らす
MySQL(InnoDB)ではOSのキャッシュ機構(ページキャッシュ)を使用せずに、独自のキャッシュ機構(バッファプール)を使用することでディスクI/Oの削減を実現しています。
バッファプール
テーブルやインデックスといった主要な情報は、ディスク上のテーブルスペースで管理されています。
クライアントからそれらデータへアクセスする場合は、直接テーブルスペースにいくのではなく、キャッシュ領域であるバッファプールに対して行われます。
仮に欲しいデータがまだバッファプール上にキャッシュされていない場合は、いったんテーブルスペースより取得(フェッチ)してバッファプールへ格納します。
更新処理についても同様にバッファプールに対して行われます。
あくまでもメモリ上のバッファプールに対してアクセスを行うことで、ディスクへのI/Oを減らしています。
ページキャッシュの無効化
ディスクI/Oを減らす手段として、そもそもOSにはページキャッシュが存在します。
しかしながらInnoDBでは、データをキャッシュする手段としてはバッファプールを使用したほうが効率がよい。
そこでページキャッシュを無効化する設定を行うことで、より効率的なデータアクセスを実現しています。
関連パラメータ
- innodb_buffer_pool_size
-
バッファプールのサイズを指定します
-
- innodb_buffer_pool_instances
-
バッファプールのインスタンス数を指定します - インスタンス数が複数あることで、バックグラウンドスレッドによる
バッファプールへのI/Oの競合を抑えられます
-
- innodb_flush_method
- フラッシュする方法を指定します
-
O_DIRECTにすることでページキャッシュをバイパスさせることができます
データの永続化
キャッシュ上の更新データをディスク上へと反映する仕組みはLinuxと似たような仕組みとなっています。
都度反映ではなくバックグラウンドで行われます。
ダーティーページ
バッファプールに対して書き込まれた情報は即座にテーブルスペースへと反映(フラッシュ)されません。
都度フラッシュをするとディスクI/Oが頻発し、結果としてパフォーマンスに影響が出てしまうためです。
したがって、テーブルスペースへまだ書き込まれていない、バッファプール上にのみ存在する更新情報(ダーティーページ)が存在することになります。
ダーティーページはバックグラウンドスレッドにより非同期でテーブルスペースへと書き込まれます。
チェックポイント
バッファプール上のダーティーページはあるタイミングでテーブルスペースへフラッシュされます。
チェックポイントとは、あるタイミングでディスクへフラッシュするプロセスで、2種類のチェックポイントが存在します。
通常は定期的に少しずつフラッシュする、ファジーチェックポイントという仕組みを使います。
ファジーチェックポイントではダーティーページの割合やInnoDBログのサイズなどを考慮して、とても"良い塩梅"に少しずつフラッシュしてくれるので、
一度に全てのダーティーページをフラッシュする場合に比べて、ディスクI/Oのオーバーヘッドを分散させることができます。
一方、ダーティーページの割合やInnoDBログのサイズが閾値を超えた場合に発生するシャープチェックポイントは、一度に全てのダーティーページをフラッシュしてしまうので、
パフォーマンスの低下につながります。
関連パラメータ
- innodb_log_file_size
-
InnoDBログのファイルサイズを指定します
-
- innodb_log_files_in_group
-
InnoDBログのファイル数を指定します -
InnoDBログのサイズの合計はinnodb_log_file_size*innodb_log_files_in_groupとなります- この値があまりにも小さいと頻繁に
ダーティーページのフラッシュが発生してしまいます - ただしあまりにも大きいとクラッシュリカバリに時間がかかってしまうケースもあります
- この値があまりにも小さいと頻繁に
-
- innodb_max_dirty_pages_pct
-
バッファプール内におけるダーティーページの比率の上限を設定します - この上限に達した場合、強制的にチェックポイント処理(
シャープチェックポイント)が発生します
-
- innodb_io_capacity
- バックグラウンドで実行されるI/O処理の上限値を設定します
- innodb_write_io_threads
- バックグラウンドスレッド(書き込みI/O)のスレッド数を設定します
クラッシュリカバリ
InnoDBログ
InnoDBログとはデータの更新情報が記録されるファイルです。
InnoDBでは、更新処理が発生した際は、更新情報を先にInnoDBログバッファへ書き込んだあとに、バッファプールへ書き込まれます。
そしてInnoDBログバッファに書き込まれたオペレーション情報は、コミットのたびにInnoDBログへとフラッシュされます。(※)
なぜわざわざInnoDBログでオペレーション情報を保持しているかというと、ダーティーページのフラッシュ漏れを防ぐためです。
仮にダーティーページがフラッシュされていない状態でサーバがダウンしても、InnoDBログのオペレーションを元に更新内容を反映(リカバリ)することができます。
※innodb_flush_log_at_trx_commit=1の場合に限る
関連パラメータ
- innodb_log_buffer_size
-
InnoDBログバッファのサイズを指定します
-
- innodb_log_file_size
-
InnoDBログのファイルサイズを指定します
-
- innodb_log_files_in_group
-
InnoDBログのファイル数を指定します -
InnoDBログのサイズの合計はinnodb_log_file_size*innodb_log_files_in_groupとなります
-
- innodb_flush_log_at_trx_commit
-
InnoDBログへのフラッシュのタイミングを指定します -
1を指定することでCOMMITのタイミングでInnoDBログへのフラッシュが行われます
-
PostgreSQLではどう工夫しているのか
ディスクI/Oを減らす
共有バッファ
TODO
関連パラメータ
TODO
データの永続化
ダーティーページ
TODO
チェックポイント
TODO
関連パラメータ
TODO
クラッシュリカバリ
WAL
TODO
関連パラメータ
TODO
Elasticsearchではどう工夫しているのか
TODO 図
ディスクI/Oを減らす
Elasticsearchでは独自のキャッシュ機構(Indexing Buffer)とOSのキャッシュ機構(ページキャッシュ)をうまく利用することでディスクI/Oの削減を実現しています。
Indexing Buffer
データの登録(インデクシング)を行う際は、Indexing Bufferと呼ばれるメモリ領域に対して書き込みが行われます。
ディスクに直接アクセスせずにメモリに対して処理を行うことで、ディスクI/Oの軽減を実現しています。
(ページキャッシュ上の)Segment
Elasticsearchでは、IndexやShardと呼ばれる論理的な概念でデータを管理しています。
さらに内部的にはluceneのSegmentとよばれるファイル単位でデータを物理的に保持しています。
そしてSegmentはページキャッシュ上にキャッシュされます。
前述のIndexing Bufferへと書き込まれた情報は、そのままの状態では検索対象としてヒットしません。
検索処理はページキャッシュ上のSegmentに対して行われるため、キャッシュ上のSegmentへの書き込みが必要となります。
ではどうするのかというと、リフレッシュ処理を行う必要があります。
リフレッシュ処理を行うことで、ページキャッシュ上のSegmentへと更新情報が書き込まれます。
データ検索時も、ディスクに直接アクセスせずに、あくまでもメモリに対して処理が行われるので、ディスクI/Oの軽減が期待できます。
関連パラメータ
- ES_HEAP_SIZE
- ヒープサイズを指定します
- 物理メモリの半分を指定することが推奨されていますが、32GBを超えてはいけません
- indices.memory.index_buffer_size
-
Indexing Bufferのサイズを指定します - ヒープのうちどのくらいの割合を確保(%もしくはバイト)するかを指定します
-
- index.refresh_interval
- リフレッシュ処理を行う間隔を指定します
-
-1を指定することで自動的なリフレッシュを無効化できます
- index.number_of_shards
-
Shardの数を指定します
-
- index.number_of_replicas
-
Shardの複製(レプリカ)の数を指定します
-
データの永続化
(ディスク上の)Segment
ページキャッシュ上のSegmentは、フラッシュのタイミングでディスク上のSegmentとして作成されます。
フラッシュが行われる契機については、明示的にフラッシュ処理が行われるか、後述するTranslogがいっぱいになった場合のどちらかとなります。
なお、ディスク上のSegmentはいったん作成されると更新不可(Immutable)となります。
そのため、データの更新もしくは削除が発生した場合は、新たなSegmentを作成し、古いSegmentに削除フラグ(.del)が付与されます。
その後Segmentをマージすることで、更新や削除の情報が反映され、最新のSegmentとして永続化されます。
関連パラメータ
クラッシュリカバリ
Translog
Translogとはデータの登録および削除処理のオペレーションが記録されるファイルです。
そしてTranslogバッファに書き込まれたオペレーション情報は、都度Translogへとフラッシュされます。(※)
なぜわざわざTranslogでオペレーション情報を保持しているかというと、ページキャッシュ上のSegmentのフラッシュ漏れを防ぐためです。
仮にSegmentがフラッシュされていない状態でサーバがダウンしても、Translogのオペレーションを元に更新内容を反映(リカバリ)することができます。
※index.translog.durability=requestの場合に限る
関連パラメータ
- index.translog.sync_interval
-
TranslogバッファからTranslogへフラッシュする間隔を指定します
-
- index.translog.durability
-
TranslogバッファからTranslogへフラッシュする契機を指定します - デフォルト(
request)の場合はリクエストの都度Translogへと書き込まれます
-
- index.translog.flush_threshold_size
- フラッシュが発生する
Translogのサイズを指定します -
Translogのサイズが当該値に達するとフラッシュが発生します
- フラッシュが発生する
- index.translog.retention.size
-
Translogのファイル合計サイズを指定します
-
おわりに
TODO

