CMU Database Systems をひたすら追っていく ~20 Logging Schemes~
この記事は「けんつの1人 DBMS アドベントカレンダー Advent Calendar 2019 - Adventar」 23 日目の記事です。
- はじめに
- Logging Schemes
- Failure Classification
- Buffer Pool Management Policies
- Write-Ahead Logging
- Checkpoint
- おわりに
はじめに
今日はログ周りの話。
WAL とか、ACID の原子性、耐久性、一貫性などを担保するための重要な要素。
Logging Schemes
リカバリアルゴリズムはデータベースに置いて一貫性、原子性、耐久性を確保する上で需要な要素となっている。
このリカバリアルゴリズムで行う大きく分けて2つにわかれる。
またここのでキーワードとして以下の2つがある。
Failure Classification
DBMS で起こる障害は大きくわけて 3 つに分類できる。
Transaction Failures
System Failure
Storage Media Failure:
- Non-Repairable Hardware Failure: ディスク障害、不揮発性ストレージの一部がクラッシュする。これから回復するにはアーカイブバージョンから復元するしかない。
Buffer Pool Management Policies
バッファプールを管理するポリシーの2つを紹介する。
例えば、No-Steal + Force で実装するなら。
変更がディスクに書き込まれなかった場合は、中止されたトランザクションによる変更を元に戻す必要はない。
また、コミット時に全ての変更がディスクに書き込まれることが保証されているため変更をやり直す必要はない。
ただし、トランザクションが変更する必要のある全てのデータがメモリに収まらない場合、トランザクションがコミットする前のダーティページをディスクに書き込まないため、そのトランザクションを実行できないという制限がある。
Write-Ahead Logging
ディスクページに変更が加えられる前にデータベースに加えられた全ての変更のログをログファイルに記録する手法。
殆どの DBMS で使用されているが、リカバリするためにはログを追う必要があるので処理に時間が掛かる。
ログはDBを復旧するための UNDO, REDO に必要な全ての情報を持っている。
Steal + No-Force システムを例に解説する。
Implementation
更新されたページの関連する全てのログレコードはページ自体がストレージに書きこまれるよりも前に永続化される。
- 全てのログレコードがストレージに書き込まれ、永続化されるまでトランザクションがコミットされたとはみなされない
- トランザクションが開始したら、各トランザクションのログに BEGIN レコードが書き込まれ開始点としてマークされる。
- トランザクションが終了したら、COMMIT レコードをログに書き込み、ログレコードがフラッシュされることを確認する。
- 各ログエントリには トランザクションID, オブジェクトID, 変更前の値(UNDOに使用)、変更後の値(REDOに使用) が格納される。
- トランザクションのコミット時にディスク全体のログ記録を行う必要がある。
Deferred Updates
Checkpoint
WAL の大きな問題としログファイルが肥大化するという問題がある。
クラッシュ時にこのログファイルが肥大化していると、ログを追ってリカバリする処理に時間がかかることがある。
そのため、チェックポイントを設け定期的にバッファの持つ情報をディスクにフラッシュする必要がある。
チェックポイントはどの程度設けるのが良いという基準はなく、チェックポイントが多すぎるとパフォーマンスが低下し、少なすぎるとその意味が薄れてしまう。
そのため、DBMS が担う役割や要求パフォーマンスに左右される。
おわりに
次はリカバリについて
CMU Database Systems をひたすら追っていく ~19 Multi-Version Concurrency Control~
この記事は「けんつの1人 DBMS アドベントカレンダー Advent Calendar 2019 - Adventar」 22 日の記事です。
はじめに
今回は MySQL や PostgreSQL でも採用されている MVCC について。
Multi-Version Concurrency Control
これは単に同時実行制御に収まらない広い概念として存在している。過去10年間に実装されたあたらしい DBMS では最も広く利用されているらしい。
これが何かというと、DBMS は DB 内の単一論理オブジェクトにたいして複数の物理バージョンを維持する。
具体的には以下のような処理を行う。
Key Properties
書き込みは読み込みをブロックしない。読み込みも書き込みをブロックしない。
読み取り専用トランザクションはロックを取得せずに一貫したスナップショットを読み取る。
DBMS がスナップショットで実行できるクエリをサポートする。
Version Storage
DBMS が論理オブジェクトの物理バージョンを持つ方法のこと。具体的にはポインタフィールドを使用して論理タプル毎にバージョンチェーンを作成する。
これによって DBMS は実行時に特定のトランザクションから見えるバージョンを見つけることができる。
インデックスは常にチェーンの先頭を指し、スレッドは読み取るべきバージョンを見つけるまでチェーンを検索する。
Append Onky Storage
新しいバージョンは同じテーブルスペースに追加される。
- Oldest To Newest: 新しいバージョンをチェーンの最後に追加する
- Newest To Oldest: チェーンの先頭は最新となるため高速に検索できるが、インデックスはバージョンごとに更新する必要がある。
Time Travel Storage
古いバージョンは別のテーブルスペースにコピーされる。
Delta Storage
変更された属性の元の値が別のデルタレコードスペースにコピーされる。
Garbage Collection
DBMS は時間の経過とともに DB から開放可能な物理バージョンを削除する必要がある。
これには2つのアプローチがある。
Tuple Level Garbage Collection
- Background Vacuuming: 個別のスレッドが定期的にテーブルをスキャンし、開放可能なバージョンを探す
- Cooperative Cleaning: ワーカスレッドはバージョンチェーンを検索するときに再利用可能なバージョンを検索する
おわりに
次はログ周りだったかな
CMU Database Systems をひたすら追っていく ~17 Two-Phase Locking~
この記事は「けんつの1人 DBMS アドベントカレンダー Advent Calendar 2019 - Adventar」 21 日目の記事です。
はじめに
今日は Two-Phase Locking について。
Transaction Locks
ラッチというのは以前にも何度か解説していると思うがこれからはロックの話。
ロックには2つ種類がある。
- 共有ロック:複数のトランザクションが同じデータを読み取ることができるロック。トランザクションが共有ロックを取っている間に他のトランザクションが共有ロックを同じデータに対して取ることができる。
- 排他ロック:トランザクションがデータを変更するときにかけるロック。このロックは重複してかけることができず、同時に1つのトランザクションのみ排他ロックを取ることができる。
またトランザクションは以下のようにロックマネージャーを用いて実行・管理される。
Two Phase Locking
Two Phase Locking (2PL) は悲観的な同時実行制御プロトコルであり、トランザクションがその場でDB内のオブジェクトにアクセスできるかどうかを決定する。
トランザクションが競合した場合は、2PL で十分に対応できる。しかし、トランザクションが ABORT し、他のトランザクションをロールバックする必要がある場合は無駄な処理が生まれる。
以下の手順を具体的にはとる。
Phase1: Growing
- 各トランザクションはロックマネージャから必要なロックを要求する
- ロックマネージャはロック要求を許可または拒否する。
2PL Deadlock Handling
Two-Phase Locking ではデッドロックを解消するために検出する場合と防止する2つの方法がある。
おわりに
次は MVCC
CMU Database Systems をひたすら追っていく ~16 Concurrency Control Theory~
この記事は「けんつの1人 DBMS アドベントカレンダー Advent Calendar 2019 - Adventar」 20 日目の記事です。
はじめに
どうも、最近キアヌリーブスが出演する映画を片っ端から見ているけんつです。
ジョンウィックがめちゃくちゃ面白い。
SQL のパーサを yacc を使って実装することが時間的に厳しいので妥協して普通に勉強します。
今回は Concurrency Control Theory ということで同時実行制御についてです。
Transactions
トランザクションは、データベース上のSQLによる一連の操作を処理する上で基本的な単位となる。
ただこのトランザクションを直列で実行した結果と同じになるように、同時に制御することは困難でいくつかの手法や、トランザクションそのものの性質を知る必要がある。
Definitions
その前に諸々定義、説明していく。
まず、トランザクションが実行された結果としては COMMIT, ABORT の 2 通りしかない。
トランザクションが実行され状態が COMMIT になった場合は全ての変更がデータベースに保存されていることが保証される。
ABORT になった場合はそのトランザクションによる変更はトランザクション実行前と同じ状態になっていることが保証される。
また、どういった基準でデータが正しいかということを判断するために ACID 特性というものが存在する。
- Atomicity(原子性) : 全てのトランザクションは完了するかしないかの二択しか取らない。
- Consistency(一貫性) : 処理することで不整合をもたらすトランザクションは実行されない。整合性の保証。
- Isolation(分離性) : トランザクションは他のトランザクションの影響を受けない。
- Durability(永続性) : トランザクションがコミットされるとその結果が失われないことを保証する。
次にそれぞれの特性の詳細を追っていく。
ACID: Atomicity
トランザクションは完了するかしないかの二択しかもたないという性質。これを実現するのには2つの手法が考えられる。
Shadow Paging
DBMS はページのコピーを作成し、トランザクションはそのコピーに対して変更を加える。トランザクションが完了した段階でそのコピーを反映するという手法。
これは原子性と同時に永続性も担保できるが、最近ではほとんど使われていないらしい。
Logging
こっちは一般的な方法で、全てのアクションに対するログを取ることで ABORT されたトランザクションの操作を取り消すことができる。
WAL なんかはまさにこれ。
ACID: Consistency
DBMS が保持するデータは全て整合性が担保されるべきで、クエリは常に正しい結果を返す必要がある。
ACID: Isolation
DBMS はトランザクションがまるで直列に実行されたかのように、並列に実行する必要がある*1。
そのため、分離性がある程度のレベルで担保されていないとデータに不整合が発生してしまう。それらをどうやって防いでいくかという話。
ACID: Durability
コミットされたトランザクションによる全ての変更は、クラッシュしたり再起動後も永続化されている必要がある。
これは Shadow Page や Logging によって担保することができる。
おわりに
次は Two Phase Locking について
*1:適切にロックなどを挟むが
CMU Database Systems をひたすら追っていく ~10 Query Processing~
この記事は「けんつの1人 DBMS アドベントカレンダー Advent Calendar 2019 - Adventar」19日目の記事です
はじめに
どうも、最近という出だしでブログ記事を 19 個も連続で書くと流石にそろそろネタがなくなってくるけんつです。
今回は SQL の実行周りに関連する話で、久々に CMU の講義に戻ってきます。
Query Processing
Query Plan
Query がどのように実行されるかというと、まず SQL が DBMS に投げられるとそれを構文解析に通してツリー構造を作る。
そのツリー構造をもとにどのように実行するか決定する。このとき、実行計画では可能な限りインデックススキャンを利用する必要がある。
Processing Model
DBMSではどのようにクエリプランを実行するかということを定義する。ワークロードに対して色々なトレードオフをもつ様々なモデルが存在する。
ここではそれらについて解説する。
Access Methods
Access Methods とはそのままで、SQL を解析した結果のクエリプランに基づいてクエリを実行する際にどうやってテーブルにアクセスするかという話
大きく分けて2つある。
Sequential Scan
これは順番にアクセスしていくやつ。
- Prefetch: 各ページにアクセスする際に、DBMS がディスクからのロードなどを極力挟まないように事前に数ページまとめて保持しておく
- 並列化: 複数のスレッドやプロセスをつかって並行スキャンしていく。
- Zone Map: 各タプル属性の集計を事前に計算しておく。DBMS はゾーンマップを最初にチェックすることで、ページにアクセスする必要があるかどうかを判断できる。検索するページの総数を減らす目的がある
- Late Materialization: 各オペレータは次のオペレータが必要する最小限の情報を渡す。
- Heap Clustering: タプルをクラスタインデックスで指定された順序を使用して、ページに格納する。
Index Scan
一つ以上のインデックスを選択して、クエリに必要なタプルを見つける。
複数のインデックスを使用する場合、DBMS は一致するレコードIDの集合を生成する。Bitmap, Hash Table, Bloom Filter などを使用することが多い。
おわりに
書かれていることが少なかったので自分で調べていく必要がありそう。
OLTP を目指していきたいので Iterator Model を実装する必要があるのかな?
SystemPage の実装
この記事は「けんつの1人 DBMS アドベントカレンダー Advent Calendar 2019 - Adventar」 18 日目の記事です。
はじめに
どうも、最近ダクソリマスタードで RTA をやりはじめたけんつです。黒騎士斧槍めちゃ強いですね。
今回もめちゃくちゃ短いが、SystemPageの実装についてです。
SystemPage と言っているけどおそらく catalog といったほうがよくてテーブルの情報を持っているやつです
実装
import "encoding/json" type SystemPage struct { MaxPageId uint64 `json:"MaxPageId"` TableName string `json:"TableName"` ColumnTypes []string `json:"ColumnType"` ColumnNames []string `json:"ColumnName"` } func NewSystemPage(tableName string) *SystemPage { return &SystemPage{ MaxPageId: 0, TableName: tableName, ColumnTypes: []string{}, ColumnNames: []string{}, } } func SerializeSystemPage(s *SystemPage) []byte { buf, err := json.Marshal(*s) if err != nil { panic(err) } return buf } func DeserializeSystemPage(data []byte) (*SystemPage, error) { var sys SystemPage err := json.Unmarshal(data, &sys) if err != nil { return nil, err } return &sys, nil }
これに関しては語ることがない。
テーブルの情報を持っていたかった。以上。
おわりに
こんな短くていいのか?
Disk Manager の実装について
この記事は「けんつの1人 DBMS アドベントカレンダー Advent Calendar 2019 - Adventar」17 日目の記事です。
はじめに
どうも、最近そろそろ DBMS 作るのでなく MySQL とかの勉強をしたいなと思い始めたけんつです。
今日はディスク周りの話です。
といってもすることがあまりないのでめちゃくちゃ短くなりそうです。
データの永続化
実装
今回は Direct I/O を使っていない
やっていることも簡単でファイルの RW だけ
一応 Page, SysPage 毎に分けているけどこれを書いていて、もう少し抽象化した方がいいなと思った
package storage import ( "io/ioutil" "os" "path" "path/filepath" "strconv" ) type DiskManager struct { } const ( RootDir = ".toybox" PagePrefix = "page_" SystemPageDirectory = "sys" SystemPageFileExt = ".json" ) func NewDisk() *DiskManager { return &DiskManager{} } func (d *DiskManager) SavePage(tableName string, pageId uint64, page *Page) error { buf, err := SerializePage(page) if err != nil { return err } tablePath := filepath.Join(RootDir, tableName) _, err = os.Stat(tablePath) if os.IsNotExist(err) { err := os.MkdirAll(tablePath, 0777) if err != nil { panic(err) } } pid := strconv.FormatUint(pageId, 10) pagePath := path.Join(RootDir, tableName, PagePrefix+pid) return ioutil.WriteFile(pagePath, buf[:], 0777) } func (d *DiskManager) SaveSystemPage(tableName string, sysPage *SystemPage) error { json := SerializeSystemPage(sysPage) tablePath := filepath.Join(RootDir, SystemPageDirectory) _, err := os.Stat(tablePath) if os.IsNotExist(err) { err := os.MkdirAll(tablePath, 0777) if err != nil { panic(err) } } sysPagePath := path.Join(RootDir, SystemPageDirectory, tableName+SystemPageFileExt) return ioutil.WriteFile(sysPagePath, json, 0777) } func (d *DiskManager) LoadPage(tableName string, pageId uint64) (*Page, error) { pid := strconv.FormatUint(pageId, 10) pagePath := path.Join(RootDir, tableName, PagePrefix+pid) _, err := os.Stat(pagePath) if os.IsNotExist(err) { return nil, err } bytes, err := ioutil.ReadFile(pagePath) if err != nil { return nil, err } var buf [PageByteSize]byte copy(buf[:], bytes) return DeserializePage(buf) } func (d *DiskManager) LoadSystemPage(tableName string) (*SystemPage, error) { pagePath := path.Join(RootDir, SystemPageDirectory, tableName+SystemPageFileExt) _, err := os.Stat(pagePath) if err != nil { return nil, err } bytes, err := ioutil.ReadFile(pagePath) if err != nil { return nil, err } return DeserializeSystemPage(bytes) } func (d *DiskManager) LoadAllSystemPage() (map[string]*SystemPage, error) { path := path.Join(RootDir, SystemPageDirectory) files, err := ioutil.ReadDir(path) if err != nil { return nil, err } systems := make(map[string]*SystemPage) for _, file := range files { p := filepath.Join(path, file.Name()) bytes, err := ioutil.ReadFile(p) if err != nil { return nil, err } sysPage, err := DeserializeSystemPage(bytes) if err != nil { return nil, err } systems[file.Name()[:len(file.Name())-5]] = sysPage } return systems, nil }
おわりに
次は、SystemPage (Catalog) の話なのでまた短い。