blog.tomoyat.dev

Database Internalsの5章のメモ その1

2024-05-12

トランザクション周りの処理の理解を深めたくて、昔読んだんだけど復習でもう一度読み直している。データ指向アプリケーションデザインの方も読み直したい。

ACIDについて

トランザクションとはDBMSにおける動作の分割不可能な単位で、複数の読み書きの操作の塊。

データベースのトランザクションには、守るべき4つの特性があり、その特性の頭文字を使ってACIDと呼ばれている。

原子性 (Atomicity)

トランザクション内で実行される命令は分割が不可能。命令が全て成功するか、いずれも成功しないか、の状態しか存在しない。

一貫性 (Consistency)

これは本来の意味はデータが正しい状態が維持されるみたいなことでアプリケーション側のデータの正しさの定義までを含んでいる保証。データベース的には制約が常に維持されていることを指しそう。

分離性 (Isolation)

並行して実行される複数のトランザクションは、他のトランザクションが存在しないかのように実行させる。他のトランザクションが変更している途中の値とかは観測できない。

これを完璧にやろうとするとパフォーマンスに影響が出るので、多くのデータベースでは弱い分離レベルを定義してそれに従っている。

永続性 (Durability)

トランザクションがコミットされると、それは永続化される。電源やシステムやDCに障害があろうとも。

バッファ管理

大半のDBは遅い永続ストレージと高速なメインメモリといった2つのレベルの記憶階層を持っている。永続ストレージにアクセスする回数を減らすためにページをメモリにキャッシュする。

このようなキャッシュをページキャッシュやバッファプールという。

  • ページイン:キャッシュしてないページをディスクから読み込まれること。
  • フラッシュ:キャッシュ上のページをディスクに書き込む
  • ダーティ:キャッシュにあるページで、変更が入っているがディスクに書き込まれてない状態
  • eviction:新たなページをページインさせたいがキャッシュが一杯などの時に、キャッシュしているページをキャッシュから消す。(フラッシュが必要ならする)

データベースは大体ページと呼ばれる単位でデータの読み書きをしていて、ページが必要になったら、ページキャッシュに問い合わせて、キャッシュにあればそのページをもらって、なければページキャッシュが一度ストレージからデータを持ってきてキャッシュしてそれを返す。

ページを使う側は当然そのページを書き換えることがあって、書き換えたページはダーティと呼ばれて、一度ディスクに書き込みをしないとキャッシュから消すことはできない。

キャッシュの領域はディスクの領域よりも小さいから、全てのページを同時にキャッシュに載せることはできないからキャッシュからページを消す、evictionが発生する。このページをevictionする方法はいろいろあって、FIFOとかLRUとかCLOCKとかいうアルゴリズムがある。

ただBtreeの上位ノードのように結構な頻度でアクセスされるページはページキャッスにピン留めして消されないようにすることができる。

リカバリ

WAL

ページキャッシュがあるので、ページへの変更はフラッシュされるまではメモリ上だけにある。この場合メモリ上にしか変更が反映されてないと、マシンがクラッシュした時にコミットされたデータが消えてしまう。そのためWrite Ahead logが必要。WALまたコミットログともいう。

WALは変更内容をログファイルに追記していくことで変更の永続化を行う。追記なのでイミュータブルになっている。WALはログレコードで構成されていて、全てのレコードはユニークで単調増加するLSN (Log Sequence Number)を持っている。

このままだとWALがどんどん増え続けていくので、どこかでWALを切り詰める必要がある。WALにあるページの変更がディスクに反映されてことを示すチェックポイントが存在して、そのチェックポイントが参照しているWALのLSNより古いものは削除できる。

WALに書き込まれる状態変更を表す内容は、RedoログとUndoログの二種類がある。

  • Redoログ:更新前に状態に適用すると更新後の状態になる
  • Undoログ:更新後の状態に適用すると更新前の状態になる

StealポリシーとForceポリシー

メモリ内で行われた変更をいつディスクに反映するかを決定するために、steal/no-stealポリシーとforce/no-forceポリシーを定義する。

Steal

  • stealポリシー:トランザクションによって変更されたページのフラッシュをコミット前でもしても良い
  • no-stealポリシー:コミット前には変更されたページをフラッシュしてはいけない

no-stealポリシーだと、ページキャッシュより大きいページを変更しようとするときに、変更がページキャッシュにのらないものが出てきて、ページをディスクにフラッシュして空きを作ることができない。よってページキャッシュより大きいページにわたる変更をすることができない。

その代わりにコミット前にデータが反映されてないので、ページを元に戻すためのundoログが入らなくなる。逆にいうとStealポリシーの場合はundoログが必須。

Diskを使うデータベースは大体Stealポリシーらしい

Force

  • Forceポリシー:トランザクションがコミットする前に、変更された全てのページをディスクにフラッシュする必要がある
    • コミットされたトランザクションは全て永続化されてるからredoログが入らなくなる。例外はあるらしいけど
  • no-forceポリシー:トランザクションの間に変更されたページがディスクにフラッシュされてなくてもコミットすることを許容
    • コミットされたトランザクションがディスクに反映されてないことがあるので、リカバリの時にredoログを使用してコミットしたトランザクションを復元する

どちらにせよ、redoログ、undoログをWALにが書き終わるまではコミットできない

ARIES

ARIESというデータベースがクラッシュした時のリカバリ方法がある。その仕組みではsteal/no-forceポリシーを採用している

なので、コミット前に変更がディスクに反映されているかもしれないし、コミット済みのデータがディスクに反映されてないかもしれない。

リカバリー以下の3フェーズで進められる。

  • Analysis:クラッシュ時に進行中だったトランザクションを特定する。またRedo処理を始めるLSNを決める。
  • Redo:コミット済みで反映されてない変更をRedoログを使って反映していく
  • Undo:完了してないトランザクションをロールバックしていく。