はじめに
これまでデータベースや検索エンジンといったミドルウェアをチューニングする機会にたびたび遭遇してきました。
どのようなポイントでチューニングをすれば良いのかを理解するには、ミドルウェアの仕組みを理解する必要があり、
なぜミドルウェアがそのような仕組みになっているかを理解するには、基盤の部分つまり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