├── .gitignore ├── Gemfile ├── Makefile ├── README.md ├── Rakefile ├── TODO.md ├── VERSION ├── atomic-write.re ├── basic-design.re ├── catalog.yml ├── cfp.re ├── conclusion.re ├── concurrency-control.re ├── config.yml ├── data.re ├── doc ├── catalog.ja.md ├── catalog.md ├── customize_epub.ja.md ├── customize_epub.md ├── format.ja.md ├── format.md ├── format_idg.ja.md ├── makeindex.ja.md ├── makeindex.md ├── pdfmaker.ja.md ├── pdfmaker.md ├── preproc.ja.md ├── preproc.md ├── quickstart.ja.md ├── quickstart.md ├── writing_vertical.ja.md └── writing_vertical.md ├── file-io.re ├── images └── cover.jpg ├── indexes.re ├── lib └── tasks │ └── review.rake ├── locale.yml ├── memo.re ├── next-step.re ├── preface.re ├── sty ├── README.md ├── gentombow.sty ├── jsbook.cls ├── jumoline.sty ├── plistings.sty ├── review-base.sty ├── review-custom.sty ├── review-jsbook.cls ├── review-style.sty ├── review-tcbox.sty └── reviewmacro.sty ├── style.css ├── transaction.re └── wal.re /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | book.pdf 3 | *.html 4 | version_str.txt 5 | book-pdf/ 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rake' 4 | gem 'review', '2.5.0' 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build clean rebuild 2 | 3 | build: book.pdf version_str.txt 4 | cp -a book.pdf develop-transaction-system-v$$(cat version_str.txt).pdf 5 | 6 | version_str.txt: VERSION 7 | cat VERSION |sed -e 's/\./_/g' > $@ 8 | 9 | book.pdf: *.re *.yml 10 | rake pdf 11 | 12 | clean: 13 | rm -f book.pdf 14 | 15 | rebuild: 16 | $(MAKE) clean 17 | $(MAKE) build 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # データベース自作入門 2 | 3 | これは「データベース自作入門」本のソースコードです。 4 | フィードバック大歓迎です。Issue にでも登録してください。 5 | 6 | ## 著作権 7 | 8 | (C) 2018 Takashi HOSHINO 9 | 10 | ## ライセンス 11 | 12 | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.ja) 13 | 14 | ## ビルド 15 | 16 | 2022 年 8 月現在、[Re:VIEW](https://github.com/kmuto/review) 5.5 でビルドできます。 17 | LaTeX 経由で PDF が生成できることしか確認していません。 18 | 19 | ----- 20 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | Dir.glob('lib/tasks/*.rake').sort.each do |file| 2 | load(file) 3 | end 4 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | - はじめに、に「トランザクションとは〜」という説明を追加する。 4 | - ベンチマークのコラムをメモに書く。 5 | - tpcc-runner を紹介する。 6 | 7 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.0 2 | -------------------------------------------------------------------------------- /atomic-write.re: -------------------------------------------------------------------------------- 1 | = Atomic な永続化方法 2 | 3 | 4 | ファイルに対する @{write()} システムコールで書いたデータの永続化は @{fsync()} か @{fdatasync()} を、 5 | Mmap されたファイルの変更データの永続化は @{msync()} を使えば良いのでした。 6 | 基本的な永続化の操作はこれだけしかないのですが、もう少し高い抽象度から見た操作として、 7 | Atomic に書き込みを永続化するにはどうすれば良いかについて考えていきましょう。 8 | 具体的には、以下のそれぞれの場合について説明します。 9 | 10 | 11 | * Atomic にファイルに追記したい場合 12 | * Atomic にファイルまるごと上書きしたい場合 13 | * Atomic にファイルの一部を上書きしたい場合 14 | * Copy-on-Write (CoW) 15 | * その他 16 | 17 | 18 | == Atomic にファイルに追記したい場合 19 | 20 | この方法は追記ファイルである WAL ファイルの Atomic 書き込みに使えます。 21 | 前提として、データを書きたい場所、すなわちファイル内のオフセットは分かっているものとします。 22 | それまでも同様に Atomic に追記しているのであれば、ファイルの終端オフセットは分かるはずなので、 23 | それがこれから追記するデータの先頭オフセットとなります。 24 | 25 | まずは、書きたいデータの Checksum を計算します。アルゴリズムは @{crc32} など好きなものを選んでください。 26 | ただし、アルゴリズムの特性を良く理解してから選びましょう。 27 | 今回必要になる Checksum の性質で一番重視すべきものは、Hash 関数として捉えたときの衝突耐性です。 28 | 不完全なデータなのに完全だと誤判定されてしまう事象が発生する確率が無視できない状況では、 29 | データを正しく Atomic に書ける、という性質を担保するのが難しくなります。 30 | 31 | データを実際に書くときは、Checksum、データサイズ、データの中身を書き、永続化保証が必要なら直後に永続化命令を発行します。 32 | Checksum は典型的には固定サイズですが、そうでない場合は Checksum データの終端が分かるようにします。 33 | データサイズも同様です。例えば 64bit little endian unsigned integer を使うのであれば、8 bytes 固定です。 34 | このフォーマットは一例ですが、それぞれのデータの位置が後で分かるようになっていることが必要です。 35 | データサイズが固定長である場合は、あえて毎回データサイズを記録しなくても良いでしょう。 36 | 可変長のデータについては、別途固定長のサイズ情報を記録することで、区切りが分かるというわけです。 37 | C 言語の文字列のように、区切り文字を使うケースもありますが、 38 | データに区切り文字そのものを格納できないという制約があったり、 39 | 頑張って格納しようとするとエスケープ処理が必要だったり、 40 | 前から順番に読まないと区切りが分からないというデメリットがありますので、 41 | データを永続ストレージに格納するという文脈では多くの場合 42 | サイズ情報を別途記録する方が良いと考えられます。 43 | 特にエスケープ処理はセキュリティの穴が空きやすいので原則使わないようにしましょう。 44 | 45 | Crash recovery するときには、まず Checksum とデータサイズをメモリに読み込みます。 46 | これで、データサイズが分かる(データサイズそのものが正しいかどうかはこの時点でまだ分からないので、 47 | 値が大きすぎるなど、仕様に反するときは何らかのエラー処理を行う必要があるかも知れません)。 48 | 次に、データの中身をメモリに読み込み、Checksum を再計算し、記録されているものと一致すれば、 49 | それは正しく完全に書かれたデータだと信じられます。 50 | Checksum が不一致ならば、データは不完全と判断します。 51 | 52 | 「完全なデータが書かれているならば Checksum が一致する」という命題は真です。 53 | しかしその逆である、「Checksum が一致するならば完全なデータが書かれている」という命題は 54 | 必ずしも真ではありません。だから、信じると書きました。 55 | 完全なデータではないのに Checksum が偶然一致してしまった、 56 | という稀な事象が起きていないと信じているということです。 57 | このリスクを少しでも減らすためには、Checksum アルゴリズムの選定に気をつかうだけでなく、 58 | Checksum が一致したとされるデータが 59 | (DBMS レベルで、さらにはアプリケーションレベルで)正しいかどうかを確認するのが 60 | 良いでしょう。 61 | 62 | 63 | 64 | == Atomic にファイルをまるごと上書きしたい場合 65 | 66 | 設定ファイルなどを更新するときに、ファイルをまるごと Atomic に変更したいときがあります。 67 | ファイルを読む人は、Open したときのファイル内容を一貫して読めます。 68 | これは大きなファイルでも使えるテクニックですが、 69 | ファイル内容の変更量が少ない場合は無駄が多いデメリットとなりますので注意が必要です。 70 | 71 | まず対象と同一ディレクトリ内に Temporary な名前で新規ファイルを作成し、新しい中身を書き込み、永続化します。 72 | 同一ディレクトリでなくても構いませんが同じファイルシステムに属する場所でないと 73 | 次の Rename 操作が失敗するので注意してください。 74 | 次に、@{rename()} システムコールを使って名前を対象のものに変えます(上書きします)。 75 | @{rename()} は Atomic 動作が保証されており、@{rename()} の前後で対象ファイルを Open した人は、 76 | 古いファイル内容か、新しいファイル内容のどちらかを必ず見ることになります。 77 | 古いファイル内容は、それを Open している人がいなくなった後に削除されます。 78 | Rename 前の永続化は必須なので、注意してください。この永続化をサボる人が多すぎるらしく、 79 | サボっててもちゃんと動くようお節介をしてくれるファイルシステムもあるようです 80 | (ext4 の @{auto_da_alloc} オプション参照)。 81 | 82 | Rename の結果を永続化するには、新しいファイルに対してさらなる @{fsync} の実行が必要です。 83 | 以前は @{rename()} によるファイルメタデータの変更を永続化するために 84 | 対応するディレクトリエントリを永続化する必要があるという話がありましたが、 85 | @{ext4} など最近のファイルシステムでは対象ファイルの @{fsync} で良さそうです@{footnote_rename}。 86 | 87 | * 参考(1): @{http://d.hatena.ne.jp/kazuhooku/20100202/1265106190} 88 | * 参考(2): @{http://blog.gachapin-sensei.com/archives/618823.html} 89 | 90 | //footnote[footnote_rename][Linux 5.17 ext4 (ordered) で私が動作を確認した限りでは、Rename 後の永続化操作は、親ディレクトリに対する @{fdatasync} をするのでも新しいファイルに対する @{fsync} をするのでもどちらでも構わないようです。ただ、異なるファイルシステムや異なる OS では挙動が異なるかも知れませんので十分注意してください。] 91 | 92 | 93 | == Atomic にファイルの一部を上書きしたい場合 94 | 95 | MySQL などでは Double write という手段が使われています。 96 | Double write buffer という専用のファイルを用意し、そこにまず書いて永続化してから、 97 | 本体ファイルを上書きし、最後に Double write buffer から消します。 98 | Double write buffer に書くときは Atomic に追記するテクニックを使います。 99 | Crash recovery 時に Double write buffer に残っているデータは 100 | 本体ファイルにおいて中途半端に書かれている可能性があるので、 101 | 本体ファイルの上書き操作を再実行することで Atomic 性を担保します。 102 | 103 | Double write buffer は WAL における Redo log と似た働きをすると思って良いでしょう。 104 | WAL を使っているシステムの場合、WAL ファイルにログとして上書きしたいイメージを追記することで、 105 | Crash recovery 時に上書き操作を Redo することで Atomic 性を担保できます。 106 | 毎回 WAL などに書くとログファイルが膨れあがってしまうので、 107 | Checkpoint 後に初めて上書きする場合などに限るなどの最適化が行われます。 108 | この方式は PostgreSQL で採用されています。 109 | 110 | 典型的なブロックストレージでは Block ひとつの書き込みについては Atomic 性を持っています。 111 | ファイルシステムが何か特別なことをしていない限り、昨今の Linux では 4KiB sector HDD が存在するので、 112 | 実体としての Atomic write 単位が 512B だったり 4KiB だったりしますが、小さい方に合わせて、 113 | 512B Alignment された領域に 512B の @{write()} システムコールを 1 回のみ用いた書き込みであれば、 114 | Atomic に書かれるとみなして良いです。 115 | HDD に限らず Flash memory で作られたブロックストレージも、この性質を満たすように作られているはずです。 116 | Linux のファイルシステムのスーパーブロックの書き込みはこの性質を仮定しています。 117 | これはシステム依存の挙動であることに十分注意してください。 118 | あなたがシステム全てをコントロールできる立場にあり、特定の書き込みが Atomic に書かれることが確信できるなら 119 | ご自分の責任でそれに依存した設計をしても構いません。 120 | それを信じることができない環境では、Atomic 追記のテクニックを使う必要があります。 121 | 122 | 123 | 124 | == Copy-on-Write (CoW) 125 | 126 | 上書きするときにコピーする、という名前通りの手法です。 127 | CoW という言葉を使うときは、メモリ断片とそれを指すポインタ(ポインタは Atomic に書き換えられることが前提)の話と、 128 | ディスク上で Tree 構造を扱うときの話があるように思います。今回は後者の話です。 129 | Tree ノードの一部を上書きしたいとき(通常は Leaf ノード)、新しいノードを確保し、 130 | ノードの中身をまるごとコピーして、必要な変更を新しいノードに加えます。 131 | 新しい変更は Atomic に実行する必要はありません。新しいノードはまだ Root ノードから辿れない状態なので。 132 | ノードの位置情報を参照しているノード上で書き換える必要がありますが、 133 | その書き換えが Atomic に出来るならそうして終わりです。 134 | Atomic に書き換えできない場合は、同様に位置情報を書き換えたノードのコピーを作って……という操作を 135 | 再帰的に繰り返します。 136 | すると、いずれ一番上の Root ノードに到達します。 137 | Root ノードが Atomic に書けるならそれを Atomic に書き換えて終わりですが、 138 | そうでないなら、Root ノードの変更されたコピーを用意して、 139 | 新しい Root ノードの位置情報を、 140 | 何らかの方法でその Tree 全体を管理するデータ(Root の位置情報が記録されている場所) を Atomic に書き換えます。 141 | CoW のメリットは、WAL が不要な点と、(Root ノードまで CoW する場合に)自動的に過去の Snapshot が作られる点です。 142 | (Root ノードまで CoW する場合の)デメリットは、Tree の深さと同じ数のノードの CoW を実行する必要がある点です。 143 | 144 | 145 | == その他 146 | 147 | データの Atomic な永続化方法は他にもあるかも知れません。 148 | 興味があれば是非探求してみてください。 149 | Atomic な操作はそのシステムで用意されている何らかの Atomic なプリミティブ操作に依存して 150 | 構築されますので、プリミティブが何かについて意識することが大切です。 151 | 例えば、NVRAM はブロックデバイスと異なり、もっと細かい単位で永続化ができるようなので、 152 | よりきめこまかな方法で目的を達成できる可能性があります。 153 | 例えば、分散システムではたとえディスクに永続化しなくても、 154 | 複数ノードにコピーが存在する事実をもって永続化相当とみなせるかも知れません。 155 | 何を前提とできるかは環境によって異なり、またそれが未来永劫不変の性質であるとみなせるわけでもありません。 156 | DBMS も含め、ソフトウェアは環境に合わせて変化する必要があります。 157 | Atomic な永続化手法についても、例外ではありません。 158 | -------------------------------------------------------------------------------- /basic-design.re: -------------------------------------------------------------------------------- 1 | = DBMS を学ぶためのリファレンス設計 基本 2 | 3 | 4 | これまで、トランザクションを処理を目的とした DBMS が備えているべき 5 | 機能や性質について個別に説明してきました。 6 | では、全体としてどんな機能があればトランザクション処理できると言えるでしょうか。 7 | 本章では、トランザクション処理ができるといえる最小限の機能セットについて考えます。 8 | 習うより慣れろの精神で、作ってみよう、という人はこれを読んで 9 | 実際に動くプログラムとしての DBMS を作ってみてください。 10 | 11 | 12 | 13 | == 並行実行制御 14 | 15 | 並行実行制御は、今なおより良いものを求めて研究が行われている奥深いテーマですが、 16 | 必要最小限のトランザクション処理システムに必要か、と言われると、 17 | なくても成立すると思いますので、ここでは涙を飲んでバッサリ削ろうと思います。 18 | 並行実行制御をせずに、きちんと動かす、すなわち Serializable に実行するためにはどうするか。 19 | そうです、本当に Serial (直列)実行をすれば良いのです。 20 | 直列実行するスケジューラを Trivial scheduler といいます。 21 | また、データ構造の排他は大変で、設計と実装の難易度が上がりますので、 22 | 思いきってシングルスレッドで動かす DBMS を作ることにしましょう。 23 | 24 | ということで皆さんはまずシングルスレッドで動くトランザクション処理システムを 25 | 作ることを目標にしてもらおうと思います。 26 | Trivial scheduler には並行実行制御は不要なので、 27 | インデクスと WAL 機能があれば最小限の DBMS ができることになります。 28 | 29 | 30 | 31 | == インデクスとスキーマ 32 | 33 | インデクスを実現するデータ構造について、Tree map と Hash table を紹介しました。 34 | より単純な DBMS は、どちらかのみサポートしているでしょう。 35 | Tree map なら Range query が出来ますから、Phantom problem やその対処に興味がある人は 36 | Tree map を選びましょう。そのような事情がない人は、Hash table でも良いでしょう。 37 | 極端な例を挙げると、インデクスを使わずに Record の配列を使っても良い、と主張する 38 | 人がいるかも知れません。Table full scan はできますね…… 39 | ただ、お目当ての Record に低コストでアクセスできる機能はさすがに必須としたいなと私は思います。 40 | 41 | メモリ上とディスク上のデータフォーマットを無理に共通化したくありませんし、 42 | ページ単位で Atomic に書いたりするのは面倒そうですし、何よりバッファキャッシュ管理をしたくない 43 | (本書でも説明していません!!!)ので、 44 | インメモリ DBMS を作ることにしましょう。 45 | メモリに格納できない大きなデータベースは扱わないという割り切りをします。 46 | これで、メモリ上でインデクスを実装すれば良くなりました。 47 | シングルスレッド前提なので、排他制御も不要です。 48 | あれれ、多くのプログラミング言語ではほぼ標準ライブラリで 49 | この要件を満たす Tree map や Hash table を用意しているようですよ。 50 | それらのライブラリを使えばインデクスが何故高速か、そのメカニズムを知らなくても 51 | インデクスを実装できてしまいます。 52 | 私としては学びのために自分でインデクス構造を実装することも検討して欲しいのですが、 53 | 最小限、という意味ではサボって構いません。 54 | 55 | DDL (Data definition language) 等を用意すると手間なので、 56 | 設定ファイルを読み込む形で定義させるか、もっと簡単に作るために、 57 | ハードコーディングしてしまいましょう。 58 | 動的なスキーマ変更もサポートしないこととします。 59 | 極端なことをいえば、テーブルはひとつあれば十分じゃないでしょうか。 60 | 61 | Record の仕様を決めましょう。 62 | もちろん、たくさんの Primitive 型を用意して、任意の Column を組み合わせて Record を定義できると便利ですね。 63 | しかし、設計と実装を簡単にするために、サボりましょう。 64 | 巷の Key-value store と呼ばれるものには、Key 型も Value 型もバイト列型しか選べないものもあるようです。 65 | バイト列は表示するときに面倒なので、いっそ文字列に限定してしまいましょうか。 66 | 文字列も、ユニコードだのなんだのは面倒くさいので、ASII 文字だけに限定してしまいましょうか。 67 | 68 | リッチなスキーマを想定するのであれば、セカンダリインデクスは欲しくなりますが、 69 | Key-value store なら、プライマリインデクスだけあれば良いでしょう。 70 | 必要になったとしても、同じデータ構造を流用すれば良いので、 71 | セカンダリインデクスは比較的簡単に実装できると思います。 72 | 73 | 74 | == Write-ahead Logging 75 | 76 | インメモリ DBMS とはいっても、ACID property を満たすために永続化はしないといけません。 77 | ログ先行書き込み (WAL) 機能は Commit 条件を満たすために実装する必要があります。 78 | ログファイルのフォーマットを決めましょう。 79 | DBMS がどんなデータ操作をサポートするかを決めて、 80 | そのオペレーションの Redo log の仕様を決めます。 81 | SQL と似たようなインターフェースを想定するのであれば、 82 | Update/insert/delete の三種類をサポートすることになります。 83 | もっと簡単にしたいなら、Put (Upsert) のみサポートするのもありでしょう。 84 | Upsert とは、その Primary key を持つ Record が存在していなければ Insert、 85 | 存在していたら Update する、という操作です。 86 | 87 | Redo/undo log のどれを採用するかについて考えましょう。 88 | Trivial scheduler を使う場合、トランザクションロジックによる明示的な Abort 命令と 89 | Crash による Abort 以外では Abort しませんので、比較的簡単に実装できる Redo log のみを使う設計をオススメします。 90 | 91 | そのトランザクションを Commit するぞと決めるまで、 92 | メモリ上のデータベース本体に変更を反映しないようにすれば、Undo log は不要です。 93 | Redo log は 後述する Write set から作れます。 94 | Redo log は Atomic に書きましょう。Atomic に書く単位について、 95 | オペレーション単位で書くか、トランザクション単位で書くか、これも設計上の選択肢です。 96 | 97 | 98 | == Read set と Write set 99 | 100 | DBMS 側のトランザクション実行エンジンはどのように振る舞えば良いでしょうか。 101 | 本書は Non-deterministic DBMS を想定すると言いましたね。 102 | 実行エンジンは次にどんな命令が来るのか分からないのでトランザクションの状態管理をする必要があります。 103 | 104 | 同じ Record に対する複数回のアクセスをうまく吸収するための仕組みとして Read set と Write set があります。 105 | Read set はトランザクションが過去に読んだ Record の参照とその内容を保持しておきます。 106 | Write set はトランザクションが書いた内容で、やはり Record の参照とともに保持しておきます。 107 | Trivial scheduler を採用するとき Read set は不要です。なぜなら自分以外にトランザクションは 108 | 並行に実行されていないので、同じ Record を何回読んでも自分が変更しない限り同じだからです。 109 | 110 | Trivial scheduler を採用していても Write set は必要です。 111 | 何故なら Redo log のみ記録する設計を選んだからです。 112 | いついかなるときでも Undo はできないので、Undo が不要になることが確定するまで 113 | データベース本体に変更を反映してはいけません。 114 | Undo が不要になる瞬間は、Commit することが確実視されたときです。 115 | 一般には、CC protocol がそのトランザクションを Commit させると決定(Commit しても良いと判断)したときですが、 116 | Trivial scheduler においてその判断は不要なので、Commit 命令を受けとった瞬間が対応します。 117 | もしそれより前にデータベース本体に変更を反映してしまい、 118 | その後トランザクションロジックから Abort 命令が来たら、Undo できなくて詰みます。 119 | トランザクション実行中はデータベース本体と Write set を別々に管理しますが、 120 | トランザクションロジックから見ると、 121 | あたかもそれまでの変更がデータベース本体に反映されているかのように振る舞います。 122 | すなわち、Write set に存在する Record の Read 要求に対しては Write set に保持されている内容を返すということです。 123 | 別の見方をすれば Write set はキャッシュデータとして振る舞います。 124 | Write set はトランザクションによるデータベース変更への変更データそのものなので、 125 | Write set から Redo log が作れます。自分で決めたフォーマットに従って変換するだけです。 126 | 127 | 128 | 129 | == Crash recovery 130 | 131 | DBMS 起動時にやるべきことは、Crash recovery です。 132 | Crash recovery は、Commit の返事をした(および Commit だと判定した)全てのトランザクションの実行結果が 133 | 反映されたデータベース状態をメモリ上に再構築し、新規トランザクションを受けつけられる状態にすることです。 134 | 前回の Checkpointing 時のデータベースファイルがあればメモリに読み出し、 135 | WAL ファイルの中身を Redo してメモリ上に正しいデータベース状態を再構築します。 136 | このとき、同じログを複数回適用するハメになるかもしれないことに気をつけてください。 137 | 典型的には、同じログを何回適用しても大丈夫なように作る必要があります。 138 | もしくは、二回目以降はスキップできるような仕組みを用意する必要があります。どちらにするか、 139 | それも設計の選択肢です。 140 | 141 | 142 | == Checkpointing 143 | 144 | Checkpointing は出来るだけ簡単なものにしたいので、 145 | トランザクションが動いていないときのみ、いやいやもっと極端に起動時のみにやることにしましょう。 146 | 起動時、Crash recovery 直後は、メモリ上には正しいデータベース状態がありますが、 147 | その直後にまた Crash したら、せっかく Crash recovery したのに同じことをやりなおす必要があります。 148 | そこで、今再構築したばかりのデータベース状態を、Snapshot としてファイルとして 149 | 書き出して永続化してあげましょう。これを Dump 操作といいます@{footnote_load}。 150 | 永続化が完了したら、次の Crash recovery はこの Snapshot から始めればよいので、 151 | 今ある WAL ファイルの中身はもう必要ありませんので、消してしまいましょう。 152 | それぞれの操作について、永続化も含めて順番には気をつけましょう。 153 | いついかなるときに Crash するか分かりませんから、常に備える必要があります。 154 | これが一番ナイーブですが一番簡単な Checkpointing だと思います。 155 | また、Dump 操作においては、Atomic にファイル全体を書くことを忘れないようにしましょう。 156 | 157 | //footnote[footnote_load][Crash recovery で必要とした、Snapshot ファイルをメモリに読み込む操作を Load と呼びます。Dump/load はお互いに逆の操作なのでセットで考えます。] 158 | 159 | 160 | == トランザクションとワークロード 161 | 162 | Read/insert/update/delete (もしくは Get/put)、そして Commit/abort を実行する API を用意して、 163 | 簡単な動作確認をするためのトランザクションを実装してください。 164 | トランザクションロジックが呼び出すデータベース操作 API はライブラリとして実装しましょう。 165 | ネットワークなどを介した専用プロトコルを用意するより簡単です。 166 | それを組み込み DBMS というのでしたね。 167 | トランザクションやそれを呼び出すワークロードも同じプログラミング言語で書いて DBMS 実装に 168 | 組み込んでしまいましょう。 169 | とはいえ、たとえコンパイルで同一バイナリになるとしても、 170 | インターフェースをきちんと定義して境界を意識して設計実装してください。 171 | DBMS が起動し、初期化が終わってトランザクションを受けつけられる状態になったら、 172 | トランザクションを有限個または無限個実行するような 173 | コード(ワークロード実装)を用意しておいて動かしてみてください。 174 | 175 | Crash test がしたい場合は、外部から強制的に DBMS が動いているホストを落とすとき、 176 | トランザクションが実行中であるようにしておく必要がありますね。 177 | 性能測定がしたい場合は、時間を測ったり実行できたトランザクションの個数を数えたり 178 | するコードもアプリケーションとして一緒に書いてしまいましょう。 179 | 180 | 181 | == 作って動かしてみる 182 | 183 | 184 | 以上で、大体のアーキテクチャは固まってきました。 185 | 細かいところは自分で考えてみてください。また、ご自分の興味に従って設計を変更しても大丈夫です。 186 | 187 | まずは出来るだけ簡単だけど、動くものを作ることが学びのモチベーション維持のためにも大切です。 188 | だから本章は最小限の設計を指針としています。 189 | 出来るだけ少なくて簡単な要件から始めましょう。 190 | そして、要件を満たす仕様を考えていきます。 191 | そして、仕様に沿って実装して動かしてみましょう。 192 | 要件、仕様、そして実装をいったりきたりすると思います。これが出戻りというやつですが、 193 | 大いにいったりきたりしましょう。やってみないと分からないことはあるものです。 194 | トランザクションシステムは、特に設計の選択肢が多いと思います。 195 | どんな選択をすれば、どんなメリットやデメリットがあり、どんな制約が発生するか、 196 | 考えながら設計しましょう。 197 | どんな選択をしたか、何故そうしたか、それらを整理することを心がけてください。 198 | 人に説明できることが重要です。 199 | 本書を使って学ぶみなさんには、要件と仕様をきちんと定めていくこと、定めようとすることが 200 | ソフトウェアの品質向上にどれだけ寄与するかということも学んで欲しいと思います。 201 | @{memo|sec-requirements-and-specification} 202 | も参考にしてください。 203 | 204 | コーディングについては、単に動けば良いというわけではなく、読みやすさに気をつけましょう。 205 | @{memo|sec-readable-code}を参考にしてください。 206 | 207 | 208 | 209 | == テスト 210 | 211 | 作ったプログラムが正しく動いていることを確認するにはテストをすることが欠かせません。 212 | 正常系として、データ操作をしたらそれが反映されているか、などの基本動作についてテストします。 213 | プロダクションで動かすことを考えていくならエラー処理(そして異常系テスト)がとても重要ですが、 214 | 学習用のプログラムなので、ある程度は目をつぶりましょう。 215 | しかし、異常系の中で DBMS としてひとつだけ絶対に押さえておかなければならないテストがあります。 216 | それが Crash test です。 217 | トランザクション実行中にマシンの電源を落として、Crash recovery できることを確認してください。 218 | Virtual Machine を使って仮想的に電源を落とすのが良いでしょう。 219 | Commit の返事をしたのに反映されていないことがないかどうか、 220 | 中途半端な状態になっていないか、データとして壊れていないかどうかを確認してください。 221 | @{memo|sec-about-test}も参考にしてください。 222 | -------------------------------------------------------------------------------- /catalog.yml: -------------------------------------------------------------------------------- 1 | PREDEF: 2 | - preface.re 3 | 4 | CHAPS: 5 | - data.re 6 | - transaction.re 7 | - indexes.re 8 | - wal.re 9 | - concurrency-control.re 10 | - file-io.re 11 | - atomic-write.re 12 | - basic-design.re 13 | - next-step.re 14 | - memo.re 15 | 16 | APPENDIX: 17 | 18 | POSTDEF: 19 | - conclusion.re 20 | -------------------------------------------------------------------------------- /cfp.re: -------------------------------------------------------------------------------- 1 | #@# https://github.com/kmuto/review/blob/master/doc/format.ja.md 2 | 3 | = セキュリティキャンプ 2018 全国大会 募集要項 4 | 5 | == データベースシステムとは 6 | 7 | データベースは,データの集まりで,使う人が必要に応じて検索したり,データを追加,編集,削除したり,まとめて加工集計をしたりするためのものです. 8 | 9 | 独りで使うデータベースであれば,用途にもよりますが,Excel などの表計算ソフトでも足りることが多いと思います.複数人で,組織で,Web サービスで,ひとつのデータベースを管理したい場合は,もっと良い方法があります.それがデータベースシステムを使うことです. 10 | 11 | データベースシステムといったとき,皆さんは具体的にどんなソフトウェアを思い浮べるでしょうか?オープンソースのデータベースシステムだけとっても,MySQL,PostgreSQL など,商用だと Oracle,DB2,SQL Server,Google Spannar など,たくさんあります.これらは SQL という操作インターフェースを供えており,トランザクション処理が可能です.また,大量のデータを加工,集計する処理も,規模や状況によりますが可能です. 12 | 13 | もう少し広い範囲でも見てみることにします. 14 | 15 | Hadoop や Spark などは,細かい単位でのデータ操作,とりわけトランザクション処理には対応していない代わりに,SQL でいうところの SELECT 文の実行,つまり,データを大量に加工,集計する用途に特化しています.また,ひとつのホストでは保持できない程の大量のデータを扱えるように,複数のホストをまたがって動作する分散システムです. 16 | etcd などは,設定情報を分散システムで安全に共有する目的で使われることが多いデータベースシステムです.Key-value store という分類をされ,ソフトウェアによって違いはありますが,主に key を指定して,value を検索,操作する, 17 | SQL と比べてより単純なインターフェースを持っています. 18 | 同様の key-value store として,memcached や Redis などは,データの永続化をある程度犠牲にしてでも,高速に処理したいデータを扱う目的で使われることが多いようです. 19 | 20 | 以上のように,目的,用途に応じて様々なデータベースシステムがあります. 21 | 22 | 23 | == トランザクション処理 24 | 25 | 本ゼミは,上記で紹介した様々なデータベースシステムが持っている機能の中で,トランザクション処理を対象とします.トランザクション処理は,世の中になくてはならない処理のひとつです.皆さん(のご両親)が銀行 ATM でお金を入出金したり,送金したりする度に,裏でトランザクションが実行されます.ATM での操作が「きちんと」実行されないと困りますよね.また,皆さん(のご両親)がインターネット上の販売サイトで何かを購入するときに,決済(支払い)の画面で,決済ボタンを押す度に,裏でトランザクションが実行されます.これらの操作も「きちんと」処理してくれないと困りますよね.お金のやりとりにはほぼ必須ですが,それに限らず様々なやりとりでトランザクション処理は必要とされています.世の中には「きちんと」処理してくれないと困る処理が多いからです.「きちんと」がどういう意味を持つかについて理解するには,ACID の理解が必要となります.応募時の設問にもなってますので,分からない方,興味を持った方は自分で調べてみてください. 26 | 27 | トランザクション処理の重要性についてつらつらと書きましたが,本ゼミに取り組む一番の理由は、講師である私が,トランザクション処理をおもしろい,と思っているからです. 28 | 29 | 30 | == トランザクション処理を学ぶべき人 31 | 32 | アルゴリズムとデータ構造を学ぶのがおもしろいと思う方は,トランザクション処理もおもしろいと感じる適性が間違いなくあると思います.何故ならトランザクション処理は,メモリ上,ディスク上で,様々なデータ構造を必要とし,その操作に伴うアルゴリズムを必要とするからです. 33 | 並列プログラミングが大好きな人も,トランザクション処理の性能を高めるために,それを駆使する必要があるので,楽しいと思います. 34 | 35 | 36 | == データベースゼミの目的 37 | 38 | 本ゼミを通じて、データベースシステムの仕組みを理解し、作る側の人間になれる人を増やしたいと思っています。 39 | 40 | セキュリティという観点では、SQL インジェクションはトピックのひとつかと思いますが、本ゼミでは SQL を扱いません。SQL はデータベースシステムを操作するインターフェースのひとつ(デファクトスタンダードではあります)でしかありません. 41 | 42 | SQL についての情報はたくさんあります.本もたくさん出ています.それはデータベースシステムを使う人のための知識で,データベースシステムを使う人はたくさんいるからです. 43 | また,アプリケーションを設計実装する人達は,一般に,単に良い性質を持つ一連のデータベース操作としてトランザクションを設計するだけで済むことが多いと思います. 44 | それほどトランザクションという概念のもたらす抽象化は良く出来ていると思います.トランザクションは万能である一方,その皺寄せはトランザクション処理を実行するデータベースシステムの方に来ているのです. 45 | データベースシステムを作れるであろう人は,私が思うに少ないです.もちろん,使う人ほど多くなくていいですが,作る人がもっといて欲しいなと思います.さらに,データベースの作り方を教える人も少ないと思います.というわけで,微力ではありますが多少の心得がある私が本ゼミを担当することになり,応募を検討されている皆さんに向けてこの文章を書いているというわけです. 46 | 47 | 48 | == データベースゼミで学んでもらうこと 49 | 50 | 本ゼミでは,トランザクションを処理する仕組みについて開発を通じて学んでもらいます. 51 | 52 | トランザクションを実行するのに必要な主な仕組みは,メモリ上およびディスク上のデータ構造に加えて,ログ先行書き込み (Write-ahead logging, WAL) と並行実行制御(Concurrency Control) です. 53 | 54 | 私がおもしろく,そして難しいと思うところは,トランザクションを並列に実行するための,様々な仕組みです.Concurrency Control がまずそれにあたります.Concurrency Control とひとくちに言っても様々な手法がありますが,S2PL という手法が基本中の基本だと思ってもらって良いと思います.ただ,Concurrency Control をデータベースシステム上で実現するにあたって,本当に複数の CPU コアを使って複数のスレッド/プロセスでトランザクションを並列に実行する場合,複数スレッド/プロセスからひとつのデータ構造にアクセスするので,並列プログラミングと言われている技術が必要になります.具体的には lock や latch などデータの適切な排他を行う仕組みが,場合によっては,lock-free だとか mutex-free と呼ばれているような技術もです. 55 | 56 | 皆さん次第ではありますが,本ゼミに与えられた時間でそこまで到達するのは簡単ではないと思っています.そこで,並列処理にチャレンジする前に到達して欲しいマイルストーンとして,逐次プログラミングによるデータベースシステムの開発を目指して欲しいと思います.逐次プログラミングに対象を絞ったときは,初めに,ディスク,つまり永続ストレージの特性と操作について,次にデータ構造(主にアクセスメソッドとしてのインデクス),そして WAL について学んでもらいます.ここまで来ると,クラッシュリカバリができるようになります. 57 | 次なるステップとして,逐次プログラミングでもできる Concurrency Control について学んでもらおうと思っています.これにより,ディスク IO の間に,別のトランザクションを実行できるようになります. 58 | 59 | ここまでくると,その先に,楽しい並列プログラミングの世界が待っています. 60 | 61 | 62 | == 前提となる知識と経験 63 | 64 | プログラミング言語として主に Python3 を使いますので,Python でプログラムを書いたことがない人には,ハードルが高いと思います.データベースシステムは,C/C++ などのよりメモリや CPU を直接扱いやすい言語で書いてあることが多いですし,最近ですと,golang や Java で書かれているものもあるようです.ただ、本ゼミでは,基本的な知識を学んでもらうことを目的としていますし,データベースシステムを作るために必要な知識を持っていないであろう方を対象としていますので,必ずしも高速なプログラムを作ってもらおうというわけではありません.そこで,私が思うに,おそらく多くの人が使ったことがあって,比較的簡単に扱えるであろう Python を選びました.他の言語が使いたいという方は,場合によっては認めますが,講師が用意した教材を改造するのではなく,イチから作ってもらうことになります.もちろん,それなりのプログラミング能力を持っている場合に限ります. 65 | 66 | 知識について,基本的なアルゴリズムとデータ構造について理解していれば,足りないということはありません.Tree 構造と hash 構造の特性の違いだとか,search と sort アルゴリズムだとか.分からなければ,そのときに学べば良いです.ただ,時間の制約から,そのあたりの知識が少ない方は,本ゼミの恩恵をあまり受けられないかも知れません. 67 | 68 | 69 | == 応募を考えている人達へ 70 | 71 | 皆さんには,本ゼミで学んだことを生かして,いずれ,是非オリジナルの特徴を持ったデータベースシステムの開発にチャレンジしていただきたいですし,実用的なデータベースシステムを作る側の人になって活躍して欲しいです.もちろん,データベースシステムを使う側の人になったとしても,ここで得た知識は多いに役立つでしょう.どのような仕組みになっているかを知らないで使うのと,知っていて使うのでは,大きな差が出ます.データベースシステムの気持ちを知らない人は,データベースをうまく使うことが出来ません.また,研究の分野でもまだまだ課題はたくさんあり,それらを解決に向かわせる新しい手法を探求していって欲しいとも思います. 72 | 73 | データベースシステムは,秘密にしなければならないデータを格納することも多いので,データの漏洩や改竄をされないように厳重に守らなければなりません.その第一歩は,バグを出来るだけ少なくするような設計実装であることはいうまでもありませんが,データベースシステムそのものだけでなく,周辺環境,使う人達のことも含めて,セキュリティを十分意識して頂きたいと思います. 74 | -------------------------------------------------------------------------------- /conclusion.re: -------------------------------------------------------------------------------- 1 | = おわりに 2 | 3 | 本書はよくある入門書と異なり、写経用のソースコードが出てきません。 4 | 理由はみっつあります。 5 | ひとつ目は、当初リファレンス実装を書こう書こうと思っていたけれどついぞ書かなかったからです。 6 | ふたつ目は、具体的なリファレンス実装があると 7 | 自分で考えながら設計の選択肢を選んでいく楽しさを邪魔してしまうかも知れないと思うようになったからです。 8 | みっつ目は、使うプログラミング言語すら自由に選んでもらいたいなと思うようになったからです。 9 | @{basic-design}には設計案の概要しか書いてありません。 10 | 細かいところはどうすればいいんだ?という読者の皆さんの疑問もごもっともですが、 11 | それを自分で考えるという体験こそが私が皆さんにしてもらいたいことなのです。 12 | 13 | 一方で、自分で考えてもらった設計やその実装について私のレビューやツッコミによるフィードバックがあることを 14 | 想定して本書は書かれていることも確かで、 15 | 自分で作ってみるだけでは片手落ちかも知れないという気持ちもあります。 16 | 残念ながら本書を読んでくださって設計実装した全ての方に私がフィードバックするのは現実的ではありません。 17 | しかし、設計選択肢のトレードオフについて、議論を通じて考察を深めてもらえれば目的は達成されると思いますので、 18 | 作ったものについての説明を聞いてくれそうな人、コードを見てくれそうな人がいたらお願いしてみてください。 19 | 20 | また、セキュリティ・キャンプ全国大会などで本書の草稿を使って勉強して 21 | くださった先輩方の公開してくれているコードがありますので、 22 | それらを眺めて本書で足りないものを補ってもらえると助かります。 23 | 以下、GitHub レポジトリの URL を列挙します(2022 年 8 月現在、有効なリンクであることを確認しています): 24 | 25 | * @{https://github.com/tiger19816/camp_learning} 26 | * @{https://github.com/hideh1231/database} 27 | * @{https://github.com/m1kit/mikrodb} 28 | * @{https://github.com/momohatt/seccampDB} 29 | * @{https://github.com/2lu3/SecurityCamp2019} 30 | * @{https://github.com/kawasin73/txngo} 31 | * @{https://github.com/KodaiD/seccamp_db_golang} 32 | * @{https://github.com/Mojashi/trivialDB} 33 | * @{https://github.com/yujixr/database} 34 | * @{https://github.com/kappybar/mydb} 35 | * @{https://github.com/kanade9/trivialdb} 36 | * @{https://github.com/kgtkr/tkvs} 37 | * @{https://github.com/yuki2501/yuki-rust-transaction} 38 | 39 | 40 | 本書を読み実践することが皆さんの糧になることを願っています。 41 | 42 | 43 | 44 | 45 | == 謝辞 46 | 47 | セキュリティ・キャンプ全国大会の2018年〜2022年に 48 | 私が講師を勤めたデータベースゼミにおいて、 49 | 受講生の皆さんには本書の草稿を読んでもらい、 50 | 実際に@{basic-design}および@{next-step}に沿って 51 | プログラムを作りながら学んでもらいました。 52 | その過程で本書の内容についての様々なフィードバックを頂きました。 53 | 一人一人のお名前を挙げるのは控えますが、 54 | この場を借りて御礼申し上げます。 55 | 56 | 57 | == 著作権表示 58 | 59 | @{(C) 2018 Takashi HOSHINO} 60 | 61 | 62 | == ライセンス 63 | 64 | 本書 (PDF) および本書のソースコードは、 65 | 66 | * @{https://github.com/starpos/develop-transaction-system.git} 67 | 68 | にて公開され、@{CC BY-NC-SA 4.0} ライセンス@{footnote_cc_by_nc_sa} の元で 69 | 利用できます。 70 | 71 | //footnote[footnote_cc_by_nc_sa][@{https://creativecommons.org/licenses/by-nc-sa/4.0/deed.ja}] 72 | 73 | 74 | == 更新履歴 75 | 76 | * 2022-08-16 v1.0 公開。 77 | * 2022-06-22 v0.4 セキュリティ・キャンプ全国大会2022向け。 78 | * 2021-08-09 v0.3 セキュリティ・キャンプ全国大会2021向け。 79 | * 2019-08-14 v0.2 セキュリティ・キャンプ全国大会2019と2020向け。 80 | * 2018-08-28 v0.1 セキュリティ・キャンプ全国大会2018向け。 81 | -------------------------------------------------------------------------------- /concurrency-control.re: -------------------------------------------------------------------------------- 1 | = 並行実行制御 2 | 3 | 4 | 並行実行制御は英語では Concurrency control (CC) といいます。 5 | 並行実行制御とは、並行/並列にトランザクションを実行することを前提として、 6 | Isolation (ACID の I) を担保するために必要な処理です。 7 | Isolation は独立性とか分離性などと訳されますが、 8 | 要はトランザクション同士の実行が混ざらない性質を言います。 9 | 混ざらないとは何かについて厳密に考え始めると、Serializability の話になります。 10 | Serializability については後述します。 11 | 12 | 並行実行制御を行う手法を CC protocol と呼びます。 13 | CC protocol の仕事は、各トランザクションが要求する Read/write 実行の排他制御したり、 14 | (Multi-version CC protocol の場合は)指定されたレコードのどの Version を読むかを決めたり、 15 | Commit 処理時に Isolation が守られているかチェックをしてダメなら System abort させたりする処理などです。 16 | 具体的にどのようなデータ構造とアルゴリズムを用いるかは CC protocol によって異なります。 17 | 18 | 19 | == 直列実行ではもったいない 20 | 21 | トランザクションを直列にひとつずつ実行すれば Isolation は完璧です。 22 | 昔は直列で良かったんじゃないかと思われそうですが、 23 | CPU がひとつしかなくても HDD が遅かったため、IO 実行中に CPU がヒマしているのは 24 | リソースがもったいないわけで、並行実行したいというモチベーションはありました。 25 | 現代はひとつのサーバだけ見ても CPU コアがたくさんあり、メモリも潤沢で、 26 | 永続ストレージも Flash memory やら NVRAM やら高速な選択肢が豊富になりました。 27 | 現代のアーキテクチャにおいて直列実行しかできないのでは、 28 | 昔にも増してリソースがもったいないということになります。 29 | 30 | 31 | == 並行実行制御の難しさ 32 | 33 | 直感的には、まったく異なるレコードにアクセスするなら並列に実行しても 34 | 何の問題もなさそうじゃないか、と思います。はい、そのとおりです。 35 | では、アクセスするレコード集合(ここでは Read/write set と呼びます) 36 | がトランザクション実行前に把握できるでしょうか。 37 | 実は、限られたものを除けば、難しいです。 38 | 仮に One-shot トランザクションのように外部とのやりとりが途中でないようなトランザクションに限ったとしても、 39 | 一般に、Read/write set はデータベースの状態に依存して決まります。 40 | トランザクションロジックは、 41 | Database 状態と入力を引数にとり、変更後の Database 状態と出力を返り値とする 42 | 副作用のない関数、 43 | @{function do_transaction(before_database, input)} @{\rightarrow} @{(after_database, output)} と解釈できます。 44 | @{do_transaction} の中身を静的解析 (@{before_database} なしで分かる情報を得るという意味です) 45 | できたとしても、限られたケース以外では Read/write set を決定するのは無理ですね。 46 | 限られたケースとは、@{before_database} の状態に依存せずに Read/write set が確定する場合です。 47 | 例えば @{do_transaction} 内に条件分岐があり、それによって Read/write set が変わるとしたら、 48 | 実行前に確定させるのは無理です。 49 | 50 | もし @{before_database} も使えれば Read/write set はトランザクション実行開始前に 51 | ほぼ把握できるはずだ、と思ったあなた、それは正しいです。 52 | しかし、@{before_database} を使うということは、 53 | トランザクションロジックを実行してみることと多くの場合同じではないでしょうか。 54 | グレーゾーンはありますが、典型的には、@{before_database} に依存しないトランザクションロジックであれば、 55 | トランザクション開始時に Read/write set を確定させることができます。 56 | そのような仮定の元で動作する DBMS を Deterministic DBMS と呼びます。 57 | 読んだデータに基づく条件分岐や @{join} 操作など、Deterministic DBMS の仮定が成り立たない 58 | ロジックはすぐに思いつきますので、Deterministic DBMS の適用範囲は相対的に狭くなります。 59 | Deterministic DBMS ではない、すなわち、トランザクション開始時には 60 | Read/write set が不明であるとの立場をとる DBMS を Non-deterministic DBMS と呼びます。 61 | 静的解析だとか過去のワークロードから Read/write set を推定などする手法も、 62 | 広い意味での Non-derministic DBMS に分類することにしましょう。 63 | 64 | 本書は Non-deterministic DBMS を作ることを想定します。 65 | Non-deterministic DBMS の CC protocol は 66 | @{do_transaction} を実行しながら Isolation を担保するために頑張る必要があります。 67 | 68 | 69 | == Serializability 70 | 71 | Serializablility と日本語では「直列化可能性」と訳されます。 72 | トランザクションの理論では、各トランザクションは有限の長さの Read/write オペレーション列を持っています。 73 | CC を処理する機構を Scheduler とよび、Scheduler は複数のトランザクションの 74 | オペレーション同士を適切に順序づけたり、 75 | Multi-version の場合は Read-from 関係を決定したりします。 76 | Scheduler が生成したオペレーション順序(や Read-from 関係)を Schedule もしくは History といいます。 77 | 78 | ここで、Mono-version model および Multi-version model について説明します。 79 | Mono-version model は、各 Record がひとつしか Version を保持しない、という仮定を置き、 80 | Write operation は前の Version を上書きしてそれ以降読めなくすること、 81 | Read operation はそのとき存在する Version を読むこととする、理論上のデータベースとその処理モデルです。 82 | すなわち Mono-version model は、オペレーション順序から Reads-from 関係が一意に決まるモデルで、 83 | 具体的には直前に同一 Record を書いたオペレーションが Reads-from 関係の対象となります。 84 | よって、Mono-version model においては、オペレーション順序情報を Schedule として扱います。 85 | 一方で、Multi-version model は、各 Record につき、無限の Version を持てるという仮定を置き、 86 | Write operation は新しい Version を生成し、 87 | Read operation は過去に書かれたどんな Version でも読めるとする、理論上のモデルです。 88 | 現実にはもちろん無限には持てませんが。 89 | Multi-version model は、Reads-from 関係が重要であり、その情報を Schedule として扱います。 90 | CC protocol は Mono-version もしくは Multi-version のいずれかの世界を想定して設計されます。 91 | 92 | ある Schedule が Serializable であるとは、 93 | 同じトランザクション集合を直列に実行した(Trivial schedule と呼びます。 94 | Trivial schedule における Reads-from 関係は Mono-version model の考え方を用いて 95 | 直前に書かれたものを読むものとします) のと「等価」であることと定義されます。 96 | トランザクションの数が @{N} 個あれば、Trivial schedule は 97 | トランザクションの並べ方の数すなわち @{N!} 個存在しますが、 98 | そのなかに与えられた Schedule と「等価」なものが存在すれば良いわけです。 99 | 100 | 「等価」とは何でしょうか。これには複数の考え方があります。 101 | 102 | 103 | === View Serializability 104 | 105 | まず取りあげるのが、View が同じなら「等価」とみなす考え方です。 106 | View とは、各トランザクションが、どのトランザクションの書いた値を読んだか、 107 | すなわち Reads-from 関係を指します。 108 | ある Schedule について、View が同じ Trivial schedule が存在するとき、 109 | その Schedule は View-serializable であるといい、 110 | View-serializable な Schedule からなる集合を VSR といいます。 111 | Multi-version schedule の場合は Multi-version view serializable および MVSR と呼ばれます。 112 | MVSR はこれまで考えられている中で一番広い Serializable な Schedule 空間です。 113 | しかし、MVSR/VSR には扱いづらい点があります。 114 | それは View 等価である Trivial schedule を同定するのが難しいとことです。 115 | 理論上では、与えられた Schedule が (Multi-version) view serializable であるかどうかを決定する問題は NP-complete です。 116 | ただし、次々に実行されるトランザクションを処理していくオンラインスケジューラであれば、 117 | この理論に基づく Protocol が必ずしも非現実的とは限りません。 118 | 119 | 120 | === Conflict Serializability 121 | 122 | MVSR や VSR が難しいとすると、 123 | 現実的な CC protocol が作れる良い性質はないだろうかという話になります。 124 | そのような性質は、あります。それは、競合関係(Conflicts)が同じなら「等価」とみなす考え方です。 125 | 具体的には、同一レコードにアクセスするトランザクションの関係のうち、 126 | 片方が Write をするものを競合関係と定義します。 127 | 競合関係は、オペレーションの実行順序を考慮して、 128 | Write-read (w-r)、Write-write (w-w)、Read-write (r-w) の 3 つです。 129 | これらは、それぞれ Flow dependency、Output dependency、Anti-dependency、とも呼ばれます。 130 | Read-read (r-r) は競合とはみなしません。 131 | 132 | ある Schedule と競合関係が同じ Trival schedule が存在するとき、 133 | その Schedule は Conflict serializable であるといい、 134 | Conflict serializable な Schedule からなる集合を CSR といいます。 135 | CSR に含まれる Schedule は VSR や MVSR にもまた含まれます。 136 | 任意の Mono-version schedule について、 137 | Conflict serializable であれば、View serializable であることが導けます。 138 | 139 | CSR は原則として Mono-version model を前提としますが、 140 | Multi-version model においても、Reads-from 関係を Write-read 関係とみなし、 141 | 上書きに相当する関係を Write-write とみなし、それに準じて Read-write 関係も定義することで、 142 | 同様に扱うことができます。 143 | Conflict serializable は競合等価となる候補の Trivial scheudler が一意に決まるので、 144 | 判定問題が NP-complete ではなく P に属します。 145 | よって、比較的扱いやすいです。 146 | 147 | 148 | ====[column] Multi-version model と Mono-version model の関係について 149 | 150 | Serializability についての理論研究は Mono-version model から始まり、 151 | 後に Multi-version model に拡張されました。 152 | Mono-version schedule では各トランザクションがどの Record を Read/write したか、という情報すなわち 153 | Operation 集合、およびそれらの全順序 (半順序として扱う流儀もあります) すなわち Operation order を考え、 154 | Reads-from 関係 (Version function という写像で表現することもあります)は 155 | Operation order から一意に決まる (Standard version function と呼ばれます) 156 | ものとして議論されていました。 157 | 一方で、Multi-version model の世界になると、Reads-from 関係こそが Serializability を判定するための 158 | 主な情報で、Operation order は必ずしも必要ではなくなりますが、 159 | Mono-version model を拡張したという歴史的経緯でそれが残ってしまっています。 160 | 161 | 本来 Mono-version model は Multi-version model に含まれます。 162 | よって、Operation order がない状況で Serializability や Recoverability などの議論をすべきです。 163 | その端緒となる「Multiversion View Serializability の簡潔な定義」@{simple_mvsr_definition} 164 | という記事を書きました。その記事には、 165 | 本来必要な Order とは Operation order ではなく、Transaction order であるということが書いてあります。 166 | Trivial schedule も Transaction order を用いて定義します。 167 | 168 | ====[/column] 169 | 170 | //footnote[simple_mvsr_definition][@{https://qiita.com/starpoz/items/266ab514bbc308d438a6}] 171 | 172 | 173 | === Anomaly 174 | 175 | (Multi-version) view serializable ではないということは、 176 | View 等価な Trivial schedule がひとつも存在しないことを意味します。 177 | ということは、どんな Transaction schedule を持ってきても、 178 | それとは View が一致しない、すなわち読むべき Version を読めていない Read operation が存在するということです。 179 | この Read operation の View は正常ではない、すなわち異常です。 180 | これを Anomaly と呼び、Dirty read とか、Lost update など、典型的パターンには名前がついています。 181 | しかし、名前がついている Anomaly を列挙していけば、View の異常を全て網羅できるとは考えない方が良いでしょう。 182 | 詳細が気になる人は、「いろんなAnomaly」@{footnote_various_anomaly} という記事がありますので参考にしてください。 183 | 184 | //footnote[footnote_various_anomaly][いろんなAnomaly: @{https://qiita.com/kumagi/items/5ef5e404546736ebac49}] 185 | 186 | 187 | 188 | == CC protocol の分類 189 | 190 | CC procotol は大きく分けて 4 種類あります。 191 | 2PL 系、TO 系、SI 系、Graph 系です。ひとつずつ説明します。 192 | 193 | 194 | === 2PL 系 CC protocol 195 | 196 | 2PL は Two-Phase Locking の略です。 197 | MySQL InnoDB や Google Spanner などで採用されています。 198 | 名前の通り、各レコードを Lock (典型的には Reader-writer lock) を使って排他する方式です。 199 | 典型的な 2PL およびその亜種はレコード毎に Mutex object を用意し、Reader-writer lock を用いて排他制御を行います。 200 | あるトランザクションがアクセスする Record は必ず Read ロックまたは Write ロックを取って、 201 | 他のトランザクションが触れないようにします。 202 | Read ロック同士は共存できます。つまり、競合関係にあるトランザクション同士は排他制御されるというわけです。 203 | 2PL のルールは 1 つだけです。 204 | トランザクションの実行はロックの成長(Growing)フェーズと縮退(Shrinking)フェーズが 205 | それぞれひとつだけ存在することが求められます。 206 | トランザクション実行中にひとつでも Unlock したらそれ以降 Lock はできません、ということです。 207 | 2PL を使って生成できる Schedule は理論上 CSR と等しいです。 208 | 2PL は Mono-version model 前提のプロトコルで、 209 | 「最新」のデータ以外を読むことは想定されません。 210 | 似た名前の 2V2PL とか MV2PL などという Multi-version のプロトコルが(少なくとも研究としては)あるようですが、 211 | 別のプロトコルです。 212 | 213 | Read locking をする代わりに楽観的に読む OCC (Optimistic Concurrency Protocol) も、 214 | 広義には 2PL に含まれます。 215 | OCC は Commit 処理のときに、Read set の内容を検証 (Verify) することで、 216 | 読んでから Verify までの間に他のトランザクションによって更新されなかったかどうかをチェックします。 217 | もし更新されていなかったら結果としてその期間 Read lock していたのと同じ効果を得られたと解釈できます。 218 | Write については Commit 処理まで遅らせたりはしますが、Lock します。 219 | OCC にも Growing phase と Shrinking phase は存在します。 220 | 2013 年に研究論文として発表された Silo というプロトコルが、洗練されています。 221 | 222 | 223 | === TO 系 CC protocol 224 | 225 | TO とは Timestamp ordering の略です。 226 | CochroachDB などで採用されています。 227 | Timestamp ordering の基本は、トランザクション開始時に、 228 | ユニークな Timestamp を付与し、その Timestamp order を Serializable order とするように 229 | 実行する方式です。 230 | @{ts(t)} はトランザクション @{t} に付与された Timestamp を表すとします。 231 | トランザクション @{t_1} が @{t_2} の書いた @{x} を読むときは、 232 | @{ts(t_2) < ts(t_1)} を満たす必要があります。自分より新しい Timestamp のついているトランザクションの 233 | 書き込んだ値を読んではいけないということです。 234 | また、@{ts(t_2) < ts(t_3) < ts(t_1)} となるような @{x} を書いた @{t_3} が存在しないようにする必要があります。 235 | これは、タイミングやプロトコルの詳細次第ではありますが、 236 | @{t_3} 側を排除するか、@{t_1} 側を排除するか、どちらかによって達成されます。 237 | TO は Mono-version model の制約の元で設計することもあるし、 238 | Multi-version model 用のもの (MVTO と呼ばれます) もあります。 239 | 240 | 241 | === SI 系 CC protocol 242 | 243 | SI とは Snapshot isolation の略です。 244 | Oracle、PostgreSQL、TiDB などで採用されています。 245 | Snapshot isolation とは、トランザクション開始時に確定している Snapshot を読むことにして、 246 | 書くときは Write-write の競合のみを排除するという方式です。 247 | 残念ながら素の SI protocol は Serializable ではありません。 248 | SI に専用の検証器 (Certifier) を組み合わせて Serializable にする取り組みが、 249 | SSI (Serializable Snapshot Isolation) や SSN (Serial Safety Net) などの手法で提案されています。 250 | 検証器の仕事は、SI では通るけど Serializable にならないようなトランザクションを選んで排除することです。 251 | 多くの場合、Anti-dependency (r-w 競合) を記録し、その情報に基いて 252 | リスクのある構造を検知することで対象トランザクションを排除します。 253 | 254 | 255 | === Graph 系 CC protocol 256 | 257 | 実用 DBMS では見たことがありませんが、 258 | トランザクションを Vertex、その依存関係や制約を edge とする 259 | Graph 構造をメンテナンスしながら、処理を進めるプロトコルです。 260 | 261 | Conflicts に基づくのであれば、対応する Conflict graph という構造を、 262 | View に基づくのであれば、MVSG という Graph 構造を使います。 263 | どちらも、循環(Cycle)存在しないように Graph を構成できれば Serializable なので、 264 | 循環になる(なりそうな)トランザクションを排除 (Abort) させて処理を進めます。 265 | Graph 構造のメンテナンスはオーバーヘッドの大きさが懸念されたり、 266 | 古い Vertex の GC など、難しさがあります。 267 | 268 | 269 | == S2PL プロトコル 270 | 271 | CC protocol の具体例として、長らくデファクトスタンダードとして使われてきた 272 | S2PL (Strict two-phase locking) について紹介します。 273 | 274 | S2PL プロトコルは、2PL に従いますが、Write lock の解放を Commit 完了後に行う制約を追加で守る必要のあるプロトコルです。 275 | これにより、S2PL は(適切に WAL 手法と連携する必要はありますが) Strictness も満たします。 276 | もっと強い制約を要求する SS2PL (strong strict two-phase locking) というプロトコルは、 277 | Write lock だけでなく Read lock の解放を Commit 完了後に行う制約を守る必要のあるプロトコルです。 278 | SS2PL は Rigorousness も満たします。 279 | つまり、2PL に対して、S2PL や SS2PL は 永続化 (WAL) のことも考慮されているプロトコルといえます。 280 | 281 | S2PL のもう少し具体的な設計方針は、@{next-step|sec-direction-for-concurrency-control}に書きましたので 282 | 参考にしてください。 283 | 284 | なお、先に紹介した Silo は、CC protocol の仕事と Logging の永続化を分離しており 285 | (元々は Early Lock Release という名前で議論された手法の本質を Silo は取り入れたと解釈して良いでしょう)、 286 | 永続化の遅延がスループットに影響を与えないような工夫をしながらも Strong recoverability 相当の保証を実現します。 287 | CC protocol と Logging の分離が今後の標準的な手法になるのは間違いないと私は思います。 288 | 289 | 290 | 291 | == Serializable ではないプロトコル 292 | 293 | 世の中の DBMS 実装においては、 294 | 性能が出ないなどの理由で、Isolation の性質を完全には満たさない、 295 | すなわち Serializable とはいえず、ワークロードによっては Anomaly が発生してしまう 296 | CC protocol が多くの場所で使われています。 297 | 例えば、Read committed と呼ばれるプロトロルです。SI もそうでしたね。 298 | Read committed は Read lock について 2PL のルールを満たさない S2PL を指すことがほとんどです。 299 | アプリケーションによってはそれらのプロトコルでも問題とならないケースは確かにあると思いますが、 300 | アプリケーションの設計者が問題が起きないように注意深く検討するべきなのは言うまでもありません。 301 | 302 | 303 | == CC protocol の実装について 304 | 305 | CC protocol はトランザクションの並行/並列実行を前提としますので、 306 | インデクスも並列アクセスに対応しているデータ構造を使う必要があります。 307 | 2PL で使われる Reader-writer lock は CC のためのレコードアクセスの排他制御であって、 308 | インデクスを構成するデータ構造への並列アクセスには専用の排他制御が必要になります。 309 | 310 | CC プロトコル以外の実装を極力サボってプロトタイプを作りたい場合は、 311 | ごくごく単純なデータベースとしてレコードの配列を用意して配列インデクスを Key と見做せば 312 | データ構造専用の排他制御が不要で、とりあえず並列に動かすことはできます。 313 | ただし、Key は @{\\{0, 1, ..., N - 1\\\}} で固定ですし、 314 | Insert や Delete 操作にも対応できませんので、 315 | ごくごく単純なベンチマーク(YCSB など)しか実行できません。 316 | Thread-unsafe なデータ構造を使いながら、並行実行させるためには、 317 | 例えば User-level thread (Green thread とも呼ばれます) が使えます。 318 | C++ だと素朴にはできないのですが(coroutine のサポートがなされようとしているので期待)、 319 | 非同期実行の仕組みがあれば可能です。 320 | Thread-safe なデータ構造を使ってしまえば、並列実行ができます。 321 | 自作するのは大変なので、既存の Concurrent hash table や Concurrent balanced tree を使うのも手ですね。 322 | 323 | 324 | == その他の話題 325 | 326 | これだけだと実用的なプロトコルには足りないのですが、キーワードのみを書いておきます。 327 | 気になった人は是非調べてみてください。 328 | 329 | * Deadlock prevension 330 | * Phantom protection 331 | * Early lock release 332 | * Starvation 333 | * Garbage collection (for multi-version CC protocol) 334 | * External consistency (non-stale reads) 335 | -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | # review-epubmaker向けの設定ファイルの例。 2 | # yamlファイルをRe:VIEWファイルのある場所に置き、 3 | # 「review-epubmaker yamlファイル」を実行すると、.epubファイルが 4 | # 生成されます。 5 | # このファイルはUTF-8エンコーディングで記述してください。 6 | 7 | # この設定ファイルでサポートするRe:VIEWのバージョン番号。 8 | # major versionが違うときにはエラーを出す。 9 | review_version: 5.5 10 | 11 | # ほかの設定ファイルの継承を指定できる。同じパラメータに異なる値がある場合は、 12 | # 呼び出し元の値が優先される。 13 | # A.yml、B.ymlのパラメータを継承する例。A.ymlとB.ymlに同じパラメータがある 14 | # 場合、B.ymlの値が優先される。さらに今このファイルに同じパラメータがあるなら、 15 | # その値がB.ymlよりも優先される。 16 | # 同様にA.yml、B.yml内でさらにinherit:パラメータを使うこともできる。 17 | # inherit: ["A.yml", "B.yml"] 18 | 19 | # ブック名(ファイル名になるもの。ASCII範囲の文字を使用) 20 | bookname: book 21 | # 記述言語。省略した場合はja 22 | language: ja 23 | 24 | # 書名 25 | # 読みを入れる例 booktitle: {name: "Re:VIEW EPUBサンプル", file-as: "リビューイーパブサンプル"} 26 | booktitle: データベースシステム自作入門 v1.0 27 | 28 | # 著者名。「, 」で区切って複数指定できる 29 | # 読みを入れる例 aut: [{name: "青木峰郎", file-as: "アオキミネロウ"}, {name: "武藤健志", file-as: "ムトウケンシ"}, {name: "高橋征義", file-as: "タカハシマサヨシ"}, {name: "角征典", file-as: "カドマサノリ"}] 30 | aut: [{name: "星野 喬", file-as: "ホシノ タカシ"}] 31 | 32 | # 以下はオプション 33 | # 以下はオプション(autと同じように配列書式で複数指定可能)。 34 | # 読みの指定はaut:の例を参照。 35 | # a-が付いているものはcreator側、 36 | # 付いていないものはcontributor側(二次協力者)に入る 37 | # a-adp, adp: 異なるメディア向けに作り直した者 38 | # a-ann, ann: 注釈記述者 39 | # a-arr, arr: アレンジした者 40 | # a-art, art: グラフィックデザインおよび芸術家 41 | # a-asn, asn: 関連・かつての所有者・関係者 42 | # a-aqt, aqt: 大きく引用された人物 43 | # a-aft, aft: 後書き・奥付の責任者 44 | # a-aui, aui: 序論・序文・前書きの責任者 45 | # a-ant, ant: 目録責任者 46 | # a-bkp, bkp: メディア制作責任者 47 | # a-clb, clb: 限定参加または補足者 48 | # a-cmm, cmm: 解釈・分析・考察者 49 | # a-csl, csl: 監修者 50 | # a-dsr, dsr: デザイナ 51 | # a-edt, edt: 編集者 52 | # a-ill, ill: イラストレータ 53 | # a-lyr, lyr: 歌詞作成者 54 | # a-mdc, mdc: メタデータセットの一次的責任者 55 | # a-mus, mus: 音楽家 56 | # a-nrt, nrt: 語り手 57 | # a-oth, oth: その他 58 | # a-pht, pht: 撮影責任者 59 | # a-pbl, pbl: 出版社(発行所) 60 | # a-prt, prt: 印刷所 61 | # a-red, red: 項目の枠組起草者 62 | # a-rev, rev: 評論者 63 | # a-spn, spn: 援助者 64 | # a-ths, ths: 監督者 65 | # a-trc, trc: 筆記・タイプ作業者 66 | # a-trl, trl: 翻訳者 67 | 68 | # 刊行日(省略した場合は実行時の日付) 69 | date: 2022-08-16 70 | # 発行年月。YYYY-MM-DD形式による配列指定。省略した場合はdateを使用する 71 | # 複数指定する場合は次のように記述する 72 | # [["初版第1刷の日付", "初版第2刷の日付"], ["第2版第1刷の日付"]] 73 | # 日付の後ろを空白文字で区切り、任意の文字列を置くことも可能。 74 | history: [["2018-08-28"], ["2019-08-14"], ["2021-08-22"], ["2022-06-21"], ["2022-08-16"]] 75 | # 権利表記(配列で複数指定可) 76 | # rights: (C) 2016 Re:VIEW Developers 77 | # description: 説明 78 | # subject: 短い説明用タグ(配列で複数指定可) 79 | # type: 書籍のカテゴリーなど(配列で複数指定可) 80 | # format: メディアタイプおよび特徴(配列で複数指定可) 81 | # source: 出版物生成の重要なリソース情報(配列で複数指定可) 82 | # relation: 補助的リソース(配列で複数指定可) 83 | # coverage: 内容の範囲や領域(配列で複数指定可) 84 | rights: (C) 2018 HOSHINO Takashi (licensed by CC BY-NC-SA 4.0) 85 | 86 | 87 | # デバッグフラグ。nullでないときには一時ファイルをカレントディレクトリに作成し、削除もしない 88 | #debug: null 89 | debug: 1 90 | 91 | # 固有IDに使用するドメイン。指定しない場合には、時刻に基づくランダムUUIDが入る 92 | # urnid: urn:uid:http://example.com/book-title/ 93 | # 94 | # ISBN。省略した場合はurnidが入る 95 | # isbn: null 96 | # 97 | # HTMLファイルの拡張子(省略した場合はhtml) 98 | # htmlext: html 99 | # 100 | # CSSファイル(配列で複数指定可) 101 | stylesheet: ["style.css"] 102 | 103 | # ePUBのバージョン (2か3) 104 | # epubversion: 2 105 | # 106 | # HTMLのバージョン (4か5。epubversionを3にしたときには5にする) 107 | # htmlversion: 4 108 | 109 | # 目次として抽出する見出しレベル 110 | toclevel: 3 111 | 112 | # 採番の設定。採番させたくない見出しには「==[nonum]」のようにnonum指定をする 113 | # 114 | # 本文でセクション番号を表示する見出しレベル 115 | secnolevel: 2 116 | 117 | # 以下のsecnolevelはまだ未実装。 118 | # 前付でセクション番号を表示する見出しレベル(未実装) 119 | # pre_secnolevel: 0 120 | # 121 | # 後付(付録)でセクション番号を表示する見出しレベル(未実装) 122 | # post_secnolevel: 1 123 | # 124 | # 部番号を表示する見出しレベル(未実装) 125 | # part_secnolevel: 1 126 | 127 | # 本文中に目次ページを作成するか。省略した場合はnull (作成しない) 128 | toc: true 129 | 130 | # EPUB2標準の目次(NCX)以外に物理目次ファイルを作成するか。省略した場合はnull (作成しない) 131 | # ePUB3においてはこの設定によらず必ず作成される 132 | # mytoc: true 133 | 134 | # 表紙にするHTMLファイル。ファイル名を指定すると表紙として入る 135 | # cover: null 136 | # 137 | # 表紙に配置し、書籍の影絵にも利用する画像ファイル。省略した場合はnull (画像を使わない)。画像ディレクトリ内に置いてもディレクトリ名は不要(例: cover.jpg) 138 | #coverimage: cover.jpg 139 | coverimage: null 140 | # 141 | # 表紙の後に大扉ページを作成するか。省略した場合はtrue (作成する) 142 | # titlepage: true 143 | # 144 | # 自動生成される大扉ページを上書きするファイル。ファイル名を指定すると大扉として入る (PDFMaker向けにはLaTeXソース断片、EPUBMaker向けにはHTMLファイル) 145 | # titlefile: null 146 | # 147 | # 原書大扉ページにするHTMLファイル。ファイル名を指定すると原書大扉として入る (PDFMaker向けにはLaTeXソース断片、EPUBMaker向けにはHTMLファイル) 148 | # originaltitlefile: null 149 | # 150 | # 権利表記ページファイル。ファイル名を指定すると権利表記として入る (PDFMaker向けにはLaTeXソース断片、EPUBMaker向けにはHTMLファイル) 151 | # creditfile: null 152 | 153 | # 奥付を作成するか。デフォルトでは作成されない。trueを指定するとデフォルトの奥付、ファイル名を指定するとそれがcolophon.htmlとしてコピーされる 154 | # colophon: null 155 | 156 | # 裏表紙データファイル (PDFMaker向けにはLaTeXソース断片、EPUBMaker向けにはHTMLファイル) 157 | # backcover: null 158 | 159 | # プロフィールページファイル (PDFMaker向けにはLaTeXソース断片、EPUBMaker向けにはHTMLファイル)。ファイル名を指定すると著者紹介として入る 160 | # profile: null 161 | # プロフィールページの目次上の見出し 162 | # profiletitle: 著者紹介 163 | 164 | # 広告ファイル。ファイル名を指定すると広告として入る (PDFMaker向けにはLaTeXソース断片、EPUBMaker向けにはHTMLファイル) 165 | # advfile: null 166 | 167 | # 取り込む画像が格納されているディレクトリ。省略した場合は以下 168 | # imagedir: images 169 | 170 | # 取り込むフォントが格納されているディレクトリ。省略した場合は以下 171 | # fontdir: fonts 172 | 173 | # imagedir内から取り込まれる対象となるファイル拡張子。省略した場合は以下 174 | # image_ext: ["png", "gif", "jpg", "jpeg", "svg", "ttf", "woff", "otf"] 175 | 176 | # fontdir内から取り込まれる対象となるファイル拡張子。省略した場合は以下 177 | # font_ext: ["ttf", "woff", "otf"] 178 | 179 | # ソースコードハイライトを利用する (rouge,pygmentsには外部gemが必要) 180 | # highlight: 181 | # html: "rouge" 182 | # latex: "listings" 183 | 184 | # カタログファイル名を指定する 185 | # catalogfile: catalog.yml 186 | 187 | # 1ページの行数文字数と1kbごとのページ数を用紙サイズで指定する(A5 or B5)。 188 | # page_metric: A5 189 | # 190 | # あるいは、配列で指定することもできる 191 | # 各数字の意味は、順にリストの行数、リストの1行字数、テキストの行数、テキストの1行字数、1kバイト毎のページ数 192 | # page_metric: [40,80,40,80,2] 193 | 194 | # ページ送りの送り方向、page-progression-directionの値("ltr"|"rtl"|"default") 195 | # direction: "ltr" 196 | 197 | # EPUBのOPFへの固有の追加ルール 198 | # 要素に追加する名前空間 199 | # opf_prefix: {ebpaj: "http://www.ebpaj.jp/"} 200 | # 追加する要素のプロパティとその値 201 | # opf_meta: {"ebpaj:guide-version": "1.1.3"} 202 | 203 | # 以下のパラメータを有効にするときには、 204 | # epubmaker: 205 | # パラメータ: 値 206 | # パラメータ: 値 207 | # ... 208 | # という構成にする必要がある(インデントさせる) 209 | 210 | epubmaker: 211 | # HTMLファイルの拡張子 212 | htmlext: xhtml 213 | # 214 | # 目次を要素の階層表現にしない。省略した場合(null)は階層化する。 215 | # 特に部扉が入るなどの理由で、構成によっては階層化目次でepubcheckに 216 | # パスしない目次ができるが、そのようなときにはこれをtrueにする 217 | # flattoc: null 218 | # 219 | # 目次のインデントレベルをスペース文字で表現する(flattocがtrueのときのみ) 220 | # flattocindent: true 221 | # 222 | # NCX目次の見出しレベルごとの飾り(配列で設定)。EPUB3ではNCXは作られない 223 | # ncxindent: 224 | #- 225 | #- - 226 | # フックは、各段階で介入したいときのプログラムを指定する。自動で適切な引数が渡される 227 | # プログラムには実行権限が必要 228 | # ファイル変換処理の前に実行するプログラム。スタイルシートのコンパイルをしたいときなどに利用する。 229 | # 渡される引数1=作業用展開ディレクトリ 230 | # hook_beforeprocess: null 231 | # 232 | # 前付の作成後に実行するプログラム。作業用展開ディレクトリにある目次ファイル(toc-html.txt)を操作したいときなどに利用する。 233 | # 渡される引数1=作業用展開ディレクトリ 234 | # hook_afterfrontmatter: null 235 | # 236 | # 本文の変換後に実行するプログラム。作業用展開ディレクトリにある目次ファイル(toc-html.txt)を操作したいときなどに利用する。 237 | # 渡される引数1=作業用展開ディレクトリ 238 | # hook_afterbody: null 239 | # 240 | # 後付の作成後に実行するプログラム。作業用展開ディレクトリにある目次ファイル(toc-html.txt)を操作したいときなどに利用する。 241 | # 渡される引数1=作業用展開ディレクトリ 242 | # hook_afterbackmatter: null 243 | # 244 | # 画像およびフォントをコピーした後に実行するプログラム。別の画像やフォントを追加したいときなどに利用する。 245 | # 渡される引数1=作業用展開ディレクトリ 246 | # hook_aftercopyimage: null 247 | # 248 | # ePUB zipアーカイブ直前に実行するプログラム。メタ情報などを加工したいときなどに利用する。 249 | # 渡される引数1=ePUB準備ディレクトリ 250 | # hook_prepack: null 251 | # 252 | # 変換したHTMLファイルおよびCSSを解析して厳密に使用している画像ファイルだけを取り込むか。デフォルトはnull(imagesディレクトリすべてを取り込む) 253 | # なお、フォント、カバー、広告についてはこの設定によらずディレクトリ内のものがすべて取り込まれる 254 | # verify_target_images: null 255 | # 256 | # verify_target_imagesがtrueの状態において、解析で発見されなくても強制的に取り込むファイルの相対パスの配列 257 | # force_include_images: [] 258 | # 259 | # 画像ファイルの縦x横の最大ピクセル数許容値 260 | # image_maxpixels: 4000000 261 | # 262 | # Re:VIEWファイル名を使わず、前付にpre01,pre02...、本文にchap01,chap02l...、後付にpost01,post02...という名前付けルールにするか 263 | # rename_for_legacy: null 264 | # 265 | # ePUBアーカイブの非圧縮実行 266 | # zip_stage1: "zip -0Xq" 267 | # 268 | # ePUBアーカイブの圧縮実行 269 | # zip_stage2: "zip -Xr9Dq" 270 | # 271 | # ePUBアーカイブに追加するパス(デフォルトはmimetype、META-INF、OEBPS) 272 | # zip_addpath: null 273 | # 274 | # EPUBで表紙をコンテンツに含めるか。デフォルトでは作成されない。yesにするとiBooks等でも最初に表紙が表示されるようになる 275 | # cover_linear: null 276 | # 277 | # @タグでの外部リンクを禁止し、地の文にする(falseで禁止する) 278 | # externallink: true 279 | # 280 | # epubmaker:階層を使うものはここまで 281 | 282 | # LaTeX用のスタイルファイル(styディレクトリ以下に置くこと) 283 | texstyle: reviewmacro 284 | # 285 | # LaTeX用のdocumentclassを指定する 286 | # texdocumentclass: ["jsbook", "uplatex,oneside"] 287 | texdocumentclass: ["review-jsbook", "media=ebook,paper=b5,gutter=26mm,head_space=35mm"] 288 | # 289 | # LaTeX用のコマンドを指定する 290 | # texcommand: "uplatex" 291 | # 292 | # LaTeXのコマンドに渡すオプションを指定する 293 | # texoptions: null 294 | # 295 | # LaTeX用のdvi変換コマンドを指定する(dvipdfmx) 296 | # dvicommand: "dvipdfmx" 297 | # 298 | # LaTeX用のdvi変換コマンドのオプションを指定する 299 | # dvioptions: "-d 5" 300 | 301 | # 以下のパラメータを有効にするときには、 302 | # pdfmaker: 303 | # パラメータ: 値 304 | # パラメータ: 値 305 | # ... 306 | # という構成にする必要がある(インデントさせる) 307 | # 308 | pdfmaker: 309 | # 310 | # TeXコンパイル前に実行するプログラム。変換後のTeXソースを調整したいときに使用する。 311 | # 渡される引数1=作業用展開ディレクトリ、引数2=呼び出しを実行したディレクトリ 312 | # hook_beforetexcompile: null 313 | # 314 | # TeXコンパイル後に実行するプログラム。索引作業をして再度コンパイルしたいときなどに使用する。 315 | # 渡される引数1=作業用展開ディレクトリ、引数2=呼び出しを実行したディレクトリ 316 | # hook_aftertexcompile: null 317 | # 318 | # PDF(book.pdf)作成後に実行するプログラム。PDFに加工を施したいときに使用する。 319 | # 渡される引数1=作業用展開ディレクトリ、引数2=呼び出しを実行したディレクトリ 320 | # hook_afterdvipdf: null 321 | # 322 | # 画像のscale=X.Xという指定を画像拡大縮小率からページ最大幅の相対倍率に変換する 323 | # image_scale2width: true 324 | # 325 | # PDFやIllustratorファイル(.ai)の画像のBoudingBoxの抽出に指定のボックスを採用する 326 | # cropbox(デフォルト), mediabox, artbox, trimbox, bleedboxから選択する。 327 | # Illustrator CC以降のIllustratorファイルに対してはmediaboxを指定する必要がある 328 | # bbox: mediabox 329 | # 330 | # 奥付を作成するか。trueを指定するとデフォルトの奥付、ファイル名を指定するとそれがcolophon.htmlとしてコピーされる 331 | #colophon: true 332 | colophon: false 333 | # pdfmaker:階層を使うものはここまで 334 | 335 | mathformat: mathml 336 | -------------------------------------------------------------------------------- /data.re: -------------------------------------------------------------------------------- 1 | = データ 2 | 3 | 4 | 「データベースシステム」って何でしょうか。「システム」は、複数の要素が 5 | 集まっていてそれらが相互作用しながら何らかの共通した目的のために動いて 6 | いるものです。ここでの「システム」は、「データベース」を保持管理処理す 7 | るためのソフトウェアやハードウェア全体のことを指すと考えるのが良いでしょ 8 | う。それでは、「データベース」って何でしょうか。「ベース」という言葉は 9 | 基地という意味なので、「データ」がたくさん整理された状態で管理されてい 10 | るものだと考えるのが良いでしょう。それでは、「データ」って何でしょうか。 11 | この章では「データ」とは何かについて考えます。 12 | 13 | 14 | 15 | == 基本データ型とその等価性および順序 16 | 17 | データのそれ以上分解できない基本的な単位を考えましょう。 18 | それは、例えば、整数であったり、文字列であったり、日付だったり、バイナリデータだったりというものです。 19 | それらを基本データ型(Primitive data type)と呼びます。 20 | 基本データ型である整数型の具体的な値は例えば 1 や 2 です。これらの値は整数型のインスタンスともいいます。 21 | 皆さんはデータベースシステムよりプログラミング言語の知識をたくさん持っていると思いますから、 22 | プログラミング言語を語るときに用いられる用語を適宜使って説明します。 23 | 24 | 同じ型の値をどうやって区別しましょうか。 25 | それには等価性(Equality)を使います。 26 | 型 @{A} のデータ(変数) @{a1, a2} の値が等しいとき @{a1 == a2} とかき、 27 | 等しくないとき @{a1 != a2} と書くことにしましょう。多くのプログラミング言語でこう書きますからね。 28 | 基本データ型であれば値が同じ(値は何らかのバイト列もしくはビット列で表現されるとして、 29 | それがまったく同じという意味です)であれば等しい、違えば等しくない、 30 | という定義でまず実用上差し支えないでしょう。 31 | たとえば、整数の 1 と 2 があったとき、@{1 == 1} で @{1 != 2} ですね。 32 | 文字列だと、@{"aaa" == "aaa"、"aaa" != "aab"、"aaa" != "aaaa"} などです。 33 | 34 | 等価性に加えて、同じ型の値同士でよく用いられる関係が、順序(Order) です。 35 | 特に全順序がよく使われます。 36 | 整数は全順序集合ですね。@{1 < 2} ですし、@{2 < 3} です。 37 | 日付も過去より現在、現在より未来が新しいという順序を持っています。 38 | 文字列は辞書順で大小関係を扱うことが多いです。@{"aa" < "aab" < "ab" < "abc" < "ba" < "bb"} など。 39 | 自分で作った構造体に任意の順序をつけることもありますね。 40 | 等価性は前提とすることが多いので @{==} が定義されており、さらに @{<} という演算子が 41 | 適切に定まれば全順序を定義できます。@{cpp-order} 42 | //footnote[cpp-order][C++ などでは、@{!(a < b) && !(b < a)} ならば @{a == b} とする考え方もあります。これは Total order を前提としているのはいうまでもありません。] 43 | 44 | 45 | データベースを扱うときにデータの等価性はまず間違いなく必須ですが、 46 | 順序については必ずしも必要ありません。 47 | あるデータ型の値の部分集合を考えるとき、 48 | 順序を持つデータ型は範囲で表現することができ、 49 | これを利用した範囲検索という効率的なアクセスメソッドを提供できるのが 50 | 特徴的です。 51 | 一方で、順序を持たない(定義しない)データ型については、 52 | 等価性を用いた検索と、全件検索(いわゆるフルスキャン)のみが可能です。 53 | 54 | 55 | 56 | == Record、Table、Key 57 | 58 | 59 | データとは基本データ型の値の集合といえます。 60 | Relational database systems (関係データベースシステム、以後 RDBMS とかきます)では、 61 | 基本データ型を複数まとめて、Record 型というものを定義して使います。 62 | Record 型の値は Record (または Record 値) と呼ばれます。Tuple 型、Tuple (Tuple 値) と呼ばれることもあります。 63 | Tuple という言い方はプログラミング言語でもそのまま使いますね。 64 | Record 型の中の要素を区別するために、それらを Column とか Field と呼び、 65 | 区別しやすいように名前をつけます。それらの名前は Column 名、Field 名と呼びます。 66 | Record 型は、プログラミング言語でいうところの構造体 (struct) に相当します。 67 | 68 | RDBMS では Record 集合を管理するために、Table という概念が使われます。 69 | ひとつの Table には同じ Record 型のデータが複数格納されます。 70 | RDBMS では Record 型には名前をつけ(られ)ず、Table に名前をつけることで、事実上 71 | Record 型に名前をつけたのとほとんど同じ使い方ができます。 72 | ただし、同じ Record 型を複数の Table で管理できる点で少し異なります 73 | (構造が同じだけれど、別物として扱うということ)。 74 | 75 | RDBMS では Record 型を入れ子 (Nested) にして定義することは想定されない場合が多いようです。 76 | これはデータ重複を防ぐ「正規形」の考え方があるためだと思われます。 77 | 「正規形」「正規化」はデータベーススキーマ 78 | (Record 型、Table、Key やその他の制約等の定義をまとめたもの)が持っているべき性質 79 | やスキーマの正規形への変換方法を意味します。 80 | トランザクション処理とは直接関係ないので、ここではこれ以上説明しません。 81 | トランザクション処理においては、ひとつの Record (場合によってはその中の Column)を 82 | それ以上分割されないデータアクセスの最小単位と考えます。 83 | 84 | 以下にスキーマとその Record の例を示しました: 85 | 86 | //list[schema_example][]{ 87 | Schema example: 88 | 89 | Table Human: 90 | (id: integer, last_name: string, first_name: string, birthday: date) 91 | 92 | 93 | Records of Human table: 94 | (1, 'tanaka', 'ichiro', 2000-01-01) 95 | (2, 'yamada', 'hanako', 2001-08-08) 96 | (3, 'suzuki', 'jiro', 2000-01-01) 97 | //} 98 | 99 | @{Human} table がひとつ定義されています。@{Human} の Record 型は 100 | @{id, last_name, first_name, birthday} という 4 つの Column から構成されます。 101 | それぞれの Column は基本データ型の @{integer, string, string, date} という型です。 102 | 3 つの Record が具体例として挙げられています。 103 | Record 型に含まれる複数の Column 型にはそれぞれ名前がついているので、 104 | 順序がないと考えることも可能ですが、 105 | ここでは、プログラミング言語における struct に倣って順序を含めることにしましょう。 106 | 107 | 同じ Record 型のふたつの Record が等しいとは、素朴には全 Column の値が等しいことを意味しますが、 108 | 常にその条件で Record 同士を区別したいわけではありません。 109 | 同一性には関係ない補足情報を含めたいときもあるでしょう。 110 | 一般に Record の区別をするときには Key という概念が使われます。 111 | Key は Record を入力とする関数もしくはその関数値と考えることができます。 112 | より狭義には、Record 型に含まれる一部の Column 型からなる列を考え、 113 | 対応する Record (値) から該当する Column (値) 列を作る関数(一般に Projection と呼ばれます)を 114 | 考えます。この場合は関数そのものよりも、元になる Column 型の列で表現することが多いので、 115 | これを、便宜上 Key 型と呼ぶことにします。Key 型を使って、Record から Projection によって作られた値を、 116 | Key もしくは Key 値と呼ぶことにします。 117 | 型と値は区別されますが、文脈でどちらのことを指しているのか分かる場合は、単に Key と呼びます。 118 | 例えば Table A の Key と言ったときは、Table A の Record 型から作られる何らかの Key 型を指すものとし、 119 | ある Record の Key と言ったときは、その Record における何らかの Key 値を指す、などです。 120 | 121 | ひとつの Table について Column の選び方やその順序によって Key 型は複数存在します。 122 | 一般に、Record と Key の対応は N:1 の関係があります(ここでの N は一般に複数という意味)。 123 | アプリケーションの視点で、ある Key が Record を区別するのに十分な情報を持っていると見做せるとき、 124 | すなわち、Record と Key の対応に 1:1 の関係があると見做せるとき、 125 | その Key (型) は Unique key (型) といいます。 126 | 127 | 以下に Key の例を示しました: 128 | 129 | //list[key_example][]{ 130 | 131 | Name key of Human table: 132 | (last_name, first_name) 133 | 134 | Birthday key of Human table: 135 | (birthday) 136 | 137 | NameAndBirthday key of Human table: 138 | (last_name, first_name, birthday) 139 | 140 | Id unique key of Human table: 141 | (id) 142 | 143 | 144 | Name key of records: 145 | ('tanaka', 'ichiro') id = 1 146 | ('yamada', 'hanako') id = 2 147 | ('suzuki', 'jiro') id = 3 148 | 149 | Birthday key of records: 150 | (2000-01-01) id = 1,3 151 | (2001-08-08) id = 2 152 | 153 | NameAndBirthday key of records: 154 | ('tanaka', 'ichiro', 2000-01-01) id = 1 155 | ('yamada', 'hanako', 2001-08-08) id = 2 156 | ('suzuki', 'jiro', 2000-01-01) id = 3 157 | 158 | ID key of records: 159 | (1) 160 | (2) 161 | (3) 162 | //} 163 | 164 | 165 | @{Human} table に 4 つの Key (型)を定義しています。 166 | @{Name, Birthday, NameAndBirthday}, そして @{Id} です。 167 | RDBMS では Key の定義は対応するインデクスの作成指示を意味しますが、 168 | ここでは単にこのような Key 型を考えてみるという意味で捉えてください。 169 | @{Name} key は @{(last_name, first_name)} と書いてありますが、 170 | これは、@{Name} key は、@{Human} 型の Record を入力とし、 171 | その Column のうち @{last_name} および @{first_name} のみ取り出して Tuple を生成し、 172 | それを出力とする Projection 関数と考えます。 173 | 174 | ここで @{Name} は Unique key でしょうか? 例の 3 つの Record を見る限りでは 175 | 重複しているものはなさそうなので、いまのところ Unique になっているようですが、 176 | 今後 Record が追加された場合は Unique 性が担保されなくなってしまうかも知れません。 177 | 実は Unique key というのは、現状そうなっているという性質のことではなくスキーマに与える制約のことなのです。 178 | @{Human} table (型) を定義した人、ここでは管理者とします、が想定する潜在的な Record 集合が 179 | どのようなものかによって決まります。 180 | つまり、管理者がその Key の Unique 性を担保したい場合、システムに制約の指示を与えます。 181 | Unique key 制約が与えられた場合、たとえば、@{Name} key が Unique だと管理者が指定した場合、 182 | システムは @{(last_name, first_name)} の組が Unique でなくなるような操作を許しません。 183 | たとえば、既に @{('tanaka', 'ichiro')} という Name key を持つ Record が存在するのに、同じ 184 | @{('tanaka', 'ichiro')} を持つ別の Record は、例え他の Column が異なっていても追加できなくなります。 185 | (逆に、典型的な RDBMS は Unique 制約がなければ何もかも同じ Record を複数登録できます。) 186 | もちろん一般には @{Name} は Unique ではないので、Name を Unique key にすると不便です。 187 | ならば @{NameAndBirthday} はどうでしょうか。 188 | 実際に同性同名で誕生日も同じの人がいる可能性はかなり低いでしょうがゼロというわけでもないでしょう。 189 | 190 | Table ひとつにつき、その Unique key の中で主要なものひとつを Primary key と呼びます。 191 | 明示的な Primary key がない場合は、隠し Column が用意され、 192 | Table 内で Unique な整数が割り当てられ、Primary key として扱われることが多いです。 193 | (例えば MySQL InnoDB はそのような実装となっています。) 194 | これをサロゲートキー、もしくは代理キーと呼びます。 195 | この例では @{Id} key が代理キーです。 196 | 197 | //note{ 198 | 今の時代なら皆さんにはマイナンバーが割り当てられていますね。 199 | 原則として個人にひとつ Unique な番号を割り当てられることを仕組みとして担保しているので、 200 | 国や自治体が国民/住民のデータベースを管理する場合は、 201 | システム毎に代理キー @{Id} を割り振るよりも、 202 | Primary key としてマイナンバーを使った方が良いと考える向きもあるかも知れません。 203 | しかし、データベースの扱いにおいて個人情報の保護など別の社会的法律的な制約が発生したり、 204 | 未来永劫同じ番号が使い回されることがないのかなど、 205 | Primary key として採用することに疑問がないわけではありません。 206 | アプリケーション・データベース設計において、Primary key をどうするかひとつとっても、 207 | 要件に立ち返って考える必要があったりしますので注意したいところです。 208 | //} 209 | 210 | 211 | ある Table について、任意の Key 値を指定すれば、複数の Record がマッチし得ます。 212 | もちろん存在しない Key 値を指定すればマッチするのは 0 個です。 213 | 例えば、Human table から Birthday key の値として @{2000-01-01} を指定すると、 214 | @{id 1} と @{3} の 2 つの Record がマッチします。 215 | Unique key の場合は高々 1 つの Record がマッチします。 216 | データベースにおいてデータを指定する最も基本的な操作が、ある Table において 217 | Key 値を与えて Table を構成する Record 集合の部分集合を指定する操作です。 218 | 等価性を用いる場合は、指定したい等価な Key 値の集合を与えます。 219 | 順序を用いる場合は、指定したい Key 値の範囲を与えます。 220 | 演算子や関数を使ってより複雑な条件を指定することも出来ますが、 221 | 最終的には、複数 Table の複数 Record に何らかの順番でアクセスすることになります。 222 | 223 | 複数の Column 値から構成される Key 値の順序を考える場合、典型的には辞書順を用います。 224 | 2 つの整数型からなる Key があって、@{(a, b)} と表すとき、 225 | その Key の順序は例えば @{(1, 1) < (1, 2) < (2, 1)} となります。 226 | もちろん、任意の Key について任意の順序を定義し得るわけですが、 227 | 自動的に決まる順序として辞書順が採用されるシステムが多いです。 228 | 逆に、ある順序で扱いたいから辞書順でそうなるように Key を定義することもあるでしょう。 229 | RDBMS の実装によっては、基本データ型が持つ自然な順序を Ascending (昇順)、 230 | その逆順を Descending (降順) としてそれぞれ @{ASC, DESC} の演算子で扱えるものがあります。 231 | 232 | 233 | ====[column] 順序と全順序 234 | 235 | 集合が順序や全順序を持つためにはある性質を満たす必要があります。 236 | 具体的には集合の元についての二項関係を表す演算子 @{\le} が反射律、反対称律、推移律を満たせば(半)順序であり、 237 | 加えて、集合の任意の 2 つの元が @{\le} で比較可能である場合に全順序といいます。 238 | 239 | 単純なルールとして、その型の任意の値をユニークな整数や実数に割り当てる関数(単射写像)を用意すれば、全順序の性質を満たします。 240 | 整数や実数の Tuple に割り当てても良いです。全順序の型で作る Tuple 型は辞書順を考えれば全順序となります。 241 | Record がそうだったように Key は基本データ型の Tuple でした。 242 | Key 型を構成する全ての Column の型が全順序の性質を持っていれば、 243 | その Key 値にも(辞書順を用いた)自然な全順序が定義されます。 244 | 245 | ====[/column] 246 | 247 | 248 | 249 | == データの関係とポインタ 250 | 251 | 252 | データは構造(関係)を持っています。 253 | プログラミング言語では、基本データ型に加えて、 254 | 構造体とポインタ(参照)型があれば、 255 | 任意のデータ構造を表現することが出来るでしょう@{footnote_array_type}。 256 | 257 | //footnote[footnote_array_type][配列型もありますよ、というツッコミはナシでお願いします……] 258 | 259 | RDBMS はポインタ型を直接的には扱わない特徴があります。 260 | では RDBMS でデータ同士の関係を表すにはどうすれば良いのでしょうか。 261 | それは、共通の部分データを持つことで表現します。 262 | R1, R2 という Record 型があり、それぞれが C1 という共通の Column 型を持つものとします。 263 | R1.C1 (R1 型における C1 column という意味) と R2.C1 が等しいレコード同士、 264 | すなわち、@{R1.C1 == R2.C1} である R1 型の record と R2 型の Record は 265 | 関係があるという見做せます。 266 | 同一 Record 型 (もしくは 同一 Table 内)の Record 同士に関連を持たせたい場合は、 267 | R1.C1 と R1.C2 という二つの Column を定義しておいて、@{R1.C1 == R1.C2} という形で関係を持たせることができます。 268 | これらの関係は、一般に、1:1、1:N、M:N という 3 種類のパターンに分類して考えます。 269 | 270 | 無理矢理ポインタでどのような表現になるかを考えてみましょう。 271 | 1:1 の関係は、Record と Record をお互いがお互いを指している状態を表します。 272 | 1:N は、N 側の各 Record が 1 側の Record を指している状態、1 側は配列などを持っていて、そこに N 側を指すポインタが 273 | 複数格納されている状態を表します。 274 | M:N は M 側の各 Record が配列などで N 側の Record を指すポインタを複数保持している状態と、 275 | N 側の各 Record が配列などで M 側の Record を指すポインタを複数保持している状態と考えることができます。 276 | こんな複雑な関係をポインタで管理したくないですね:) 277 | 278 | ポインタの参照外し(Dereference)に相当する操作は、内部結合 (Inner join) です。 279 | アプリケーションが自分でやっても良いですが、RDBMS に任せた方が原理的には高速です。 280 | (ただし、SQL は宣言的言語なので、Query optimizer が必ずしも良い実行計画を 281 | 選んでくれるわけではないという辛さがあります。 282 | そこでごにょごにょと Join の順番や使う Index を指定するなどのチューニングをすることで対応します。) 283 | 284 | 何故ポインタを使わないか、という問いには歴史的経緯があるようなので、 285 | 興味のある人は調べてみてください@{footnote_no_pointer}。 286 | ポインタを扱わないことで、多少窮屈ですが Dangling pointer がない世界に住むことができます。 287 | 288 | //footnote[footnote_no_pointer][「ぜひ押さえておきたいデータベースの教科書」 (Leo's Chronicle, @{http://leoclock.blogspot.com/2009/01/blog-post_07.html}) という記事によると、Readings in Database Systems (Red Book, @{http://www.redbook.io/}) の中の解説記事に書いてあるようです。2022 年現在 Red Book 5th Edition がオンラインで気軽に読めますが、目的の記事はおそらく過去の Edition にあった The Roots という章だと思いますが、私は確認できていません。おそらく Relational model と対比すべく Hierarchical(階層型)/Network(ネットワーク型) data model についての話が書いてあると推察します。] 289 | 290 | 291 | 292 | 293 | 294 | 295 | == 最も単純なデータベースのスキーマ 296 | 297 | 最も単純なデータベースについて考えてみましょう。 298 | まず Table がひとつしかありません。 299 | その Table が採用する Record 型は Key を表す型と Key 以外のデータ Value を表す型の 2 つの Column で定義されます。 300 | Key は Primary key すなわち Unique key です。 301 | これは key-value store と呼ばれるものですね。 302 | Key は文字列型で、Value はバイト列であることが多いです。 303 | Key は文字列型として自然に定義される等価性と順序(辞書順)をサポートしています。 304 | 305 | アプリケーションが数値型を必要とするなら、数値型を文字列型に変換して使います。 306 | 10進数を用いて数字を文字として足りない分を 0 埋めして桁数を固定した文字列に変換すれば、 307 | 辞書順と数値の昇順は一致させることができます。 308 | もちろん文字集合における順序が @{'0' < '1' < ... < '9'} を満たすことが前提となります。 309 | 例えば 10 桁固定にするとして 1 は '0000000001' に変換され、10 は '0000000010' に変換されます。 310 | '0000000001' < '0000000010' ですね。 311 | 負の数を扱いたいならコンピュータがそうしているように、符号を表す桁を一番最初に追加して、 312 | 補数を用いることで順序を保存したまま変換できます。 313 | 314 | スキーマを自在に定義できるデータベースシステムはもちろん有用かつ必要ですが、 315 | 学習用として最初に作るべきは、このような単純な key-value を管理するシステムが望ましいです。 316 | なぜなら、これらの複雑さは本質的な仕組みにはあまり関係ないからです @{secondary-index}。 317 | 318 | //footnote[secondary-index][Secondary index と呼ばれるものは、本質的な仕組みに関係あるかも知れません。] 319 | 320 | 321 | 322 | ====[column] NULL について 323 | 324 | RDBMS の Column はデフォルトで NULL 値が許容されているものがほとんどです。 325 | Haskell でいうところの Maybe 型、Rust でいうところの Option 型です。 326 | Unique 制約と同様に、Column に NOT NULL 制約を指定することはできます。 327 | 外部結合(Outer join)をするためには NULL 値が必要なのですが、 328 | 現代のプログラミングの常識から考えると、デフォルトは NOT NULL にして欲しいものですね。 329 | NULL を考慮し忘れると演算が想定外の結果になってしまうことがあります。 330 | 二項演算子やユーティリティ関数の引数にひとつでも NULL を渡すと結果の多くは NULL になり、 331 | これが罠となります。NULL を含めた演算ルールは三値論理という立派な名前がついているのですが、 332 | 多くの場合に我々が期待する演算は、NULL (Nothing, None) 値を何らかのデフォルト値に変換して演算することなのですね。 333 | 和のときは 0、積のときは 1、文字列連結のときは空文字列など。 334 | 335 | ====[/column] 336 | 337 | 338 | ====[column] コンピュータサイエンスで使う数学 339 | 340 | あなたがデータ構造やアルゴリズムについて考察したり、生み出したりしたとき、 341 | 定式化をする必要が出てくることがあります。 342 | コンピュータサイエンスで用いる数学について学ぶのに良い資料として、 343 | 344 | * Mathematics for Computer Science. 345 | ** Eric Lehman, F Thomson Leighton, and Albert R Meyer. 346 | ** @{https://courses.csail.mit.edu/6.042/spring18/mcs.pdf} 347 | 348 | を挙げておきます。 349 | 特に、集合の基礎と述語論理のところをきちんと学んで使えるように練習することが、 350 | 論文を読むときの定理や証明を理解するための第一歩であり、自分で定式化するときの道具にもなると思います。 351 | 私はごく最近(2018年現在)この資料の存在を知って勉強したので偉そうなことは言えません。 352 | この資料のライセンスは CC BY-SA 3.0 だそうです。太っ腹ですね。 353 | 354 | ====[/column] 355 | -------------------------------------------------------------------------------- /doc/catalog.ja.md: -------------------------------------------------------------------------------- 1 | # Re:VIEW カタログファイル ガイド 2 | 3 | Re:VIEW のカタログファイル catalog.yml について説明します。 4 | 5 | このドキュメントは、Re:VIEW 2.0 に基づいています。 6 | 7 | ## カタログファイルとは 8 | 9 | カタログファイルは、Re:VIEW フォーマットで記述された各ファイルを1冊の本(たとえば PDF や EPUB)にまとめる際に、どのようにそれらのファイルを構造化するかを指定するファイルです。現在はカタログファイルと言えば catalog.yml のことを指します。 10 | 11 | ## catalog.yml を用いた場合の設定方法 12 | 13 | catalog.yml 内で、`PREDEF`(前付け)、`CHAPS`(本編)、`APPENDIX`(付録、連番あり)、`POSTDEF`(後付け、連番なし)を記述します。CHAPS のみ必須です。 14 | 15 | ```yaml 16 | PREDEF: 17 | - intro.re 18 | 19 | CHAPS: 20 | - ch01.re 21 | - ch02.re 22 | 23 | APPENDIX: 24 | - appendix.re 25 | 26 | POSTDEF: 27 | - postscript.re 28 | ``` 29 | 30 | 本編に対して、「部」構成を加えたい場合、`CHAPS` を段階的にして記述します。部の指定については、タイトル名でもファイル名でもどちらでも使えます。 31 | 32 | ```yaml 33 | CHAPS: 34 | - ch01.re 35 | - 第1部: 36 | - ch02.re 37 | - ch03.re 38 | - pt02.re: 39 | - ch04.re 40 | ``` 41 | 42 | ## 古いバージョンについて 43 | 1.2 以前の Re:VIEW ではカタログファイルとして PREDEF, CHAPS, POSTDEF, PART という独立した4つのファイルを使用していました。古いカタログファイルを変換するツールとして、`review-catalog-converter` を提供しています。 44 | 45 | このコマンドにドキュメントのパスを指定して実行後、生成された catalog.yml の内容が正しいか確認してください。 46 | -------------------------------------------------------------------------------- /doc/catalog.md: -------------------------------------------------------------------------------- 1 | # Re:VIEW catalog.yml Guide 2 | 3 | This article describes Re:VIEW catalog file catalog.yml. 4 | 5 | ## What's catalog.yml 6 | 7 | Catalog file shows the structure of files to generate books (such as PDF or EPUB) in Re:VIEW format. 8 | Now we use catalog.yml as catalog file. 9 | 10 | ## How to write catalog.yml 11 | 12 | In catalog.yml, you can write `PREDEF`(frontmatter), `CHAPS`(bodymatter), `APPENDIX`(appendix) and `POSTDEF`(backmater). `CHAPS` is required. 13 | 14 | ```yaml 15 | PREDEF: 16 | - intro.re 17 | 18 | CHAPS: 19 | - ch01.re 20 | - ch02.re 21 | 22 | APPENDIX: 23 | - appendix.re 24 | 25 | POSTDEF: 26 | - postscript.re 27 | ``` 28 | 29 | You can add parts in body to use `CHAPS` in a hierarchy. You can use both title name and file name to specify parts. 30 | 31 | ```yaml 32 | CHAPS: 33 | - ch01.re 34 | - TITLE_OF_PART1: 35 | - ch02.re 36 | - ch03.re 37 | - pt02.re: 38 | - ch04.re 39 | ``` 40 | 41 | (For old version user: there is no `PART`. You write them in `CHAPS`.) 42 | 43 | ## About earlier version 44 | 45 | In version 1.x, Re:VIEW use 4 files PREDEF, CHAPS, POSTDEF, PART as catalog files. 46 | 47 | You can convert there files with `review-catalog-converter`. 48 | When using it, you should compare with these files and the generated file `catalog.yml`. 49 | -------------------------------------------------------------------------------- /doc/customize_epub.ja.md: -------------------------------------------------------------------------------- 1 | # EPUB ローカルルールへの対応方法 2 | Re:VIEW の review-epubmaker が生成する EPUB ファイルは IDPF 標準に従っており、EpubCheck を通過する正規のものです。 3 | 4 | しかし、ストアによってはこれに固有のローカルルールを設けていることがあり、それに合わせるためには別途 EPUB ファイルに手を入れる必要があります。幸い、ほとんどのルールは EPUB 内のメタ情報ファイルである OPF ファイルにいくつかの情報を加えることで対処できます。 5 | 6 | Re:VIEW の設定ファイルは config.yml を使うものとします。 7 | 8 | ## 電書協ガイドライン 9 | * http://ebpaj.jp/counsel/guide 10 | 11 | 電書協ガイドラインの必須属性を満たすには、次の設定を config.yml に加えます。 12 | 13 | ```yaml 14 | opf_prefix: {ebpaj: "http://www.ebpaj.jp/"} 15 | opf_meta: {"ebpaj:guide-version": "1.1.3"} 16 | ``` 17 | 18 | これは次のように展開されます。 19 | 20 | ```xml 21 | 22 | …… 23 | 1.1.3 24 | ``` 25 | 26 | ただし、Re:VIEW の生成する EPUB は、ファイルやフォルダの構成、スタイルシートの使い方などにおいて電書協ガイドラインには準拠していません。 27 | 28 | ## iBooks ストア 29 | デフォルトでは、iBooks で EPUB を見開きで開くと、左右ページの間に影が入ります。 30 | これを消すには、次のように指定します。 31 | 32 | ```yaml 33 | opf_prefix: {ibooks: "http://vocabulary.itunes.apple.com/rdf/ibooks/vocabulary-extensions-1.0/"} 34 | opf_meta: {"ibooks:binding": "false"} 35 | ``` 36 | 37 | すでにほかの定義があるときには、たとえば次のように追加してください。 38 | 39 | ```yaml 40 | opf_prefix: {ebpaj: "http://www.ebpaj.jp/", ibooks: "http://vocabulary.itunes.apple.com/rdf/ibooks/vocabulary-extensions-1.0/"} 41 | opf_meta: {"ebpaj:guide-version": "1.1.3", "ibooks:binding": "false"} 42 | ``` 43 | -------------------------------------------------------------------------------- /doc/customize_epub.md: -------------------------------------------------------------------------------- 1 | # Supporting local rules of EPUB files 2 | 3 | EPUB files that generated by Re:VIEW (review-epubmaker) should be valid in eubcheck in IDPF. 4 | 5 | But some e-book stores have their own rules, so they reject EPUB files by Re:VIEW. To pass their rules, you can customize OPF file with config.yml. 6 | 7 | ## EBPAJ EPUB 3 File Creation Guide 8 | 9 | * http://ebpaj.jp/counsel/guide 10 | 11 | EBPAJ, the Electronic Book Publishers Association of Japan, releases the guide for publishers to create EPUB files that make nothing of trouble in major EPUB readers. 12 | 13 | To pass their guide, you can add some settings into config.yml: 14 | 15 | ```yaml 16 | opf_prefix: {ebpaj: "http://www.ebpaj.jp/"} 17 | opf_meta: {"ebpaj:guide-version": "1.1.3"} 18 | ``` 19 | 20 | With this settings, Re:VIEW generates OPF files with epbaj attributes: 21 | 22 | ```xml 23 | 24 | ... 25 | 1.1.3 26 | ``` 27 | 28 | But EPUB files that Re:VIEW generates are not the same of name and structure to EBPAJ guide. 29 | 30 | 31 | ## iBookStore 32 | 33 | Without special setting, iBooks has a margin between right page and left page in double-page spread. 34 | 35 | To remove it, you can add some settings in config.yml. 36 | 37 | ```yaml 38 | opf_prefix: {ibooks: "http://vocabulary.itunes.apple.com/rdf/ibooks/vocabulary-extensions-1.0/"} 39 | opf_meta: {"ibooks:binding": "false"} 40 | ``` 41 | 42 | If you have already some settings, merge them: 43 | 44 | ```yaml 45 | opf_prefix: {ebpaj: "http://www.ebpaj.jp/", ibooks: "http://vocabulary.itunes.apple.com/rdf/ibooks/vocabulary-extensions-1.0/"} 46 | opf_meta: {"ebpaj:guide-version": "1.1.3", "ibooks:binding": "false"} 47 | ``` 48 | -------------------------------------------------------------------------------- /doc/format_idg.ja.md: -------------------------------------------------------------------------------- 1 | # Re:VIEW フォーマット InDesign XML 形式拡張 2 | 3 | Re:VIEW フォーマットから、Adobe 社の DTP ソフトウェア「InDesign」で読み込んで利用しやすい XML 形式に変換できます (通常の XML とほぼ同じですが、文書構造ではなく見た目を指向した形態になっています)。実際には出力された XML を InDesign のスタイルに割り当てるフィルタをさらに作成・適用する必要があります。 4 | 5 | 基本のフォーマットのほかにいくつかの拡張命令を追加しています。 6 | 7 | このドキュメントは、Re:VIEW 2.0 に基づいています。 8 | 9 | ## 追加したブロック 10 | これらのブロックは基本的に特定の書籍向けのものであり、将来廃棄する可能性があります。 11 | 12 | * `//insn[タイトル]{ 〜 //}` または `//box[タイトル]{ 〜 //}` : 書式 13 | * `//planning{ 〜 //}` または `//planning[タイトル]{ 〜 //}` : プランニング 14 | * `//best{ 〜 //}` または `//best[タイトル]{ 〜 //}` : ベストプラクティス 15 | * `//security{ 〜 //}` または `//security[タイトル]{ 〜 //}` : セキュリティ 16 | * `//expert{ 〜 //}` : エキスパートに訊く 17 | * `//point{ 〜 //}` または `//point[タイトル]{ 〜 //}` : ワンポイント 18 | * `//shoot{ 〜 //}` または `//shoot[タイトル]{ 〜 //}` : トラブルシューティング 19 | * `//term{ 〜 //}` : 用語解説 20 | * `//link{ 〜 //}` または `//link[タイトル]{ 〜 //}` : 他の章やファイルなどへの参照説明 21 | * `//practice{ 〜 //}` : 練習問題 22 | * `//reference{ 〜 //}` : 参考情報 23 | 24 | ## 相互参照 25 | 26 | `//label[〜]` でラベルを定義し、`@{〜}` で参照します。XML としては `