REDOとUNDOその3 [アーキテクチャ]

前回に続きJonathan Lewis氏の著書、Oracle Coreの中のREDOとUNDOに関する部分をメモしておく。

REDOの単純さ

REDOは単純にREDOレコードのストリームを連続的に出力し、共有プールのREDOログバッファになるべく早く詰め込む。Oracleはこれをディスクに書き込まなければならないが、運用的な理由で小さなオンラインREDOログファイルに書き込む。オンラインREDOログファイルの数は限られているので、ラウンドロビンで再利用しなければならない。
オンラインREDOログファイルの内容を長い期間保護するために、アーカイブREDOログファイルにコピーを作成sる。一度REDOレコードがREDOログ(バッファ)に書き込まれたら、基本的にはインスタンスは再度これを読み込むことはなく、「書き込み、忘れる」アプローチというシンプルなメカニズムになっている。

ただ、1つだけ複雑な部分がある。REDOの生成の唯一のボトルネックは、REDOログバッファにREDOのコピーを書き込むところである。10gより前、すべてのセッションに対してREDOログバッファは1つしかなかった。これに対し、1つのセッションは短時間に大量のREDOレコードを生成するかもしれないし、複数セッションが並列に動いているかもしれない。
共有プールに排他制御の仕組みを作ることは簡単だ。redo allocation latchを使ってOracleがREDOログバッファの制御を行っていることはよく知られている。プロセスはこのラッチを獲得し始めてログバッファに書き込むことができるので、複数プロセスにより同じ共有プールの領域(ログバッファ)を上書きすることを防げる。しかし、たくさんのプロセスがあると、競合によりリソースを大量に使ったり(ラッチスピンによるCPUリソース)、ラッチ獲得に失敗した後のスリープが大量に発生したりすることが起こりうる。

古いOracleのバージョンでは、データベースはそれほど忙しくなくREDO生成量もずっと少なかったので、1つの変更=1つのレコード=1つの領域確保、といった方式でほとんどのシステムで十分だった。しかし、システムが大きくなり高い並列度で領域確保を行う要件を満たすためには(特にOLTPといったシステム)、よりスケーラブルな方式が必要となる。そこで、10gで新しいメカニズムである「プライベートREDO」と「インメモリUNDO」が登場したのである。

これにより、プロセスはトランザクション全体を通して、生成したすべてのチェンジベクターをプライベートなREDOログバッファのペアに格納することができるようになった。トランザクションが完了すると、プロセスはすべてのプライベートなREDOをパブリックなREDOログバッファにコピーする。このとき、従来のログバッファ処理に戻る。つまり、プロセスはトランザクション毎にパブリックのredo allocation latchを1回獲得すればよいことになる。
...

UNDOの複雑さ

UNDOはREDOより複雑である。特に重要なのは、いかなるプロセスもまだ見るべきでないデータ項目を隠すために、原則的には、いかなる時点のUNDOにアクセスする必要がある、ということである。この要件を効率的に満たすために、OracleはUNDOレコードをデータベース内の特別な表領域(UNDO表領域)に格納している。そして、プロセスが必要なUNDOレコードを見つけるには、どこを探せばよいかわかるように、UNDOレコードへのさまざまなポインタを維持している。UNDO情報をデータベース内の通常のデータファイルに格納する利点は、UNDOブロックも通常のブロックと全く同じようにバッファリングされ、書き込みされ、リカバリされるということである。UNDOを管理する基本的なコードは、他の通常のブロックを扱うコードと同じになるのである。

プロセスがUNDOレコードを読まなければならない理由は3つあるので、UNDO表領域上のポインタを手繰る方法も3通りある。これらは3章で詳しく述べるが、ここでは簡単に最も一般的な2つについて述べておく。

読み取り一貫性

まず、最も一般的なUNDOの利用方法は、読み取り一貫性である。UNDOの存在により、セッションはまだデータの新しい状態を見るべきでないときは、そのデータの古いバージョンを見ることができる。

読み取り一貫性の要件が意味するのは、ブロックは変更を取り消すためのUNDOレコードへのポインタを持っていなければならないということである。しかし、実際は膨大な数の変更を取り消す必要があるかもしれないし、1つのブロックが保持できるポインタ数にも限りがある。そこで、Oracleは各ブロックに決められた数のポインタ(ブロックに関与する並列トランザクション毎に1つ)だけを保持し、これをブロックのITLエントリに格納する。プロセスがUNDOレコードを生成するとき、(通常)これらの中の1つのポインタを上書きし、前の値をUNDOレコードの一部として記録しておくのである。

あるUNDOレコードのブロックダンプを見ると、op: C uba: 0x0080009a.09d4.0dと記録されている部分がある。op: Cは、同じトランザクションによる前の更新の続きのレコードであることを示している。これによりOracleは、uba: 0x0080009a.09d4.0dがブロックの古いバージョンを作るために使う情報であることを知ることができる。このUNDOレコードに記録された更新前の情報をブロックの特定の行・カラムに反映すると共に、uba: 0x0080009a.09d4.0dをブロックのITLの特定のエントリに欠き戻す。

もちろん、Oracleはこのステップでブロックの古いバージョンを再構成した後、さらに古いバージョンが必要であると気がつくかもしれない。しかし、このブロックのITLスロットには、適用すべき次のUNDOレコードへのポインタがあり、そのUNDOレコードには更新前レコードの情報と共に、ITLエントリをさらにその前の状態にするための情報が含まれているのである。

ロールバック

次に、UNDOがよく使われるケースがロールバックである。具体的には、明示的なロールバック(セーブポイントへも含む)やトランザクションの途中で何らかのエラーが発生し、Oracleが暗黙的にステートメントレベルのロールバックを行うというケースである。

読み取り一貫性はシングルブロック、つまりあるブロックに対して関連するUNDOレコードをリンクされたリストから探す処理である。これに対し、ロールバックトランザクションの履歴、つまりトランザクションの実行(逆)順にUNDOレコードをリンクされたリストから探す処理である。

UNDOレコードを見てみると、リンクされたリストの痕跡があることがわかる。ダンプにあるエントリrci 0x0eの部分である。これは、このUNDOレコードの直前に生成されたUNDOレコードは同じUNDOブロック内の14番目(0x0e)にあることを示している。もちろん、直前のレコードが別のブロックにある可能性はある。その場合はrciエントリは0となり、rdba:エントリがその前のブロックアドレスを示す。前のブロックに戻る場合、通常はそのブロックの最終レコードが要求されるレコードである。もっとも、技術的にはirb:エントリによりレコードは特定することができる。しかし、irb:エントリが最後のレコードを指さない場合は、rollback to savepointした場合である。

読み取り一貫性とロールバックには重要な違いがある。読み取り一貫性は、データブロックのコピーをメモリ上に作成し、UNDOレコードを適用する。そしてこれは使い終わったらすぐに削除してしまってかまわないコピーなのである。しかしロールバックでは、カレントブロックを獲得しそれにUNDOレコードを適用する。これには3つの重要な効果がある。

・データブロックはカレントブロックであること。すなわち、最終的にはディスクに記録されなければならないブロックのバージョンである
・カレントブロックであるので、この変更にはREDOが生成される(たとえそれが元に戻す変更だとしても)
・Oracleのクラッシュリカバリのメカニズムにより安全に障害をリカバリするために、UNDOレコードを適用しながら「UNDO適用済み」とマークしていく必要がある。これによりさらにREDOが生成される。

ロールバックは大変な作業である。また、元の変更にかけたトランザクションと同じくらいの時間がかかり、REDOも同程度生成される可能性がある。ここで覚えておかなければならないのは、ロールバックはデータブロックを変更する作業であるということだ。つまり、ブロックを再び獲得し、変更し、書き込み、REDOにその変更を書き込む必要がある。さらに、もしトランザクションが大きく時間のかかるものであれば、いくつかのブロックはキャッシュから溢れてディスクへ書き込まれてしまうかもしれない。ロールバックするには、再びそれらをディスクから読み込まなければならないだろう!

他にもロールバックにより発生するオーバーヘッドがある。セッションがUNDOレコードを生成するとき、あるUNDOブロックを獲得し、pinし、それを埋めていく。ロールバックするときは、UNDOブロックから一度に1レコードずつ取り出す。つまり、1レコードずつ、ブロックの開放と獲得を繰り返すのである。これにより、初期の変更よりロールバックのときの方が、UNDOブロックに対するバッファアクセスが多くなる。しかも、OracleがUNDOレコードを獲得する際、毎回UNDO表領域がONLINEであるか調べる。これがdictionary cache(具体的にはdc_tablespaceキャッシュ)へのgetとして現れる。
---

実際の本には上記の内容に加えてブロックダンプや図があるので、もっと理解し易いと思う。また、UNDOの3つ目の使い方~遅延ブロッククリーンアウト~については、機会があれば紹介したいと思う。


◆参考文献

Oracle Core: Essential Internals for DBAs and Developers (Expert's Voice in Databases)

Oracle Core: Essential Internals for DBAs and Developers (Expert's Voice in Databases)

  • 作者: Jonathan Lewis
  • 出版社/メーカー: Apress
  • 発売日: 2011/12/06
  • メディア: ペーパーバック



nice!(0)  コメント(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

※ブログオーナーが承認したコメントのみ表示されます。