プロジェクト:https://github.com/CN-GuoZiyang/MYDB
はじめに(ちょっとした無駄話)
車輪の再発明が癖になったのか、それともデータベースの基本原理を勉強し直したかっただけなのか。仕事が終わってから深夜までの時間を使い、半月あまりでこのプロジェクトを作り上げました。
データベースとは、少しばかり因縁があります。大学でデータベースシステムの授業があった頃、私はちょうど深圳でインターンをしていました。そこでオンライン授業は、仕事中に堂々とサボるための口実に。講義を聴く以外のことは何でもやっていました。同じ時期にOSの授業もありましたが、OSにはそれなりに興味があったので、DBほどひどい理解度ではありませんでした。
そのツケはすぐに回ってきました。ByteDanceの二次面接でデータベースについてどのくらい知っているか聞かれ、率直かつ明確に答えるという方針で「まったく知りません」と答えました。続いてredisについて聞かれても、「それも知りません」としか言えません。幸い、面接官は気にせず通してくれました。ただ、この2回の「知りません」で面接評価がひどいことになっていなかったかは気になります……。
就職後のチームの仕事はデータベースとほぼ無関係だったので、これでDBやCRUDとは縁が切れたと思っていました。ところが状況はあっという間に変わります。隣の資産管理チームが深刻な人手不足で、部門に応援を要請したため、私が手伝いに行くことになりました。資産管理では整合性がかなり重視されます。以前のように何でもredisに放り込み、保存できなくてもまあいいか、とはいかないわけです……。
きっかけ
ある日GitHubを眺めていて、@qw4990さんのデータベースプロジェクト、NYADB2を偶然見つけました。Goで実装されたシンプルなデータベースで、レイヤーの分け方がとてもよく、コードも読みやすいものでした。Javaへの未練があった私は、このプロジェクトの基本構成をもとにJava版のDBを書くことにしました。実装の細部も、かなり参考にしています。
恐れ入ることに、これは作者が学部生の頃に趣味で作ったプロジェクトだそうです。できる人は違いますね(逃)。
RESPECT
全体構成
MYDBはバックエンドとフロントエンドに分かれ、両者はソケット経由でやり取りします。フロントエンド(クライアント)の仕事は単純で、ユーザーの入力を読み、バックエンドに送って実行させ、返ってきた結果を表示して次の入力を待ちます。バックエンドはSQLを解析し、正しいSQLなら実行を試み、その結果を返します。パーサーを除くと、バックエンドは5つのモジュールに分かれます。それぞれに役割があり、自分に依存するモジュールへインターフェースを通してメソッドを提供します。5つのモジュールは次のとおりです。
- トランザクションマネージャー(TM)
- データマネージャー(DM)
- バージョンマネージャー(VM)
- インデックスマネージャー(IM)
- テーブルマネージャー(TBM)
モジュール間の依存関係は次のようになります。

この依存関係をトポロジカルソートすれば、実装の順番がわかります。このチュートリアルでは、TM -> DM -> VM -> IM -> TBMの順で実装します。
各モジュールの役割は次のとおりです。
- TMはXIDファイルでトランザクションの状態を管理し、ほかのモジュールが状態を問い合わせるためのインターフェースを提供します。
- DMはデータベースのDBファイルとログファイルを直接管理します。主な役割は、1)DBファイルをページ単位で管理してキャッシュすること、2)エラー発生時にログから復旧できるようログファイルを管理すること、3)DBファイルをDataItemとして抽象化して上位モジュールに提供し、そのキャッシュも行うことです。
- VMは二相ロック(2PL)でスケジュールの直列化可能性を実現し、MVCCで読み取りと書き込みの相互ブロックを解消します。また、2種類の分離レベルも実装します。
- IMはB+木によるインデックスを実装します。ちなみに、現在whereで使えるのはインデックスのあるフィールドだけです。
- TBMはフィールドとテーブルを管理します。また、SQL文を解析し、その内容に応じてテーブルを操作します。
開発環境と実行例
開発にはWSL2とJDK11を使いました。Windowsで実行する場合は、起動引数のパスをWindows用に置き換えてください。JDKは11以上が必要で、JDK 8には対応していません(互換性のないメソッドを探して対応するものに置き換えてもかまいません。該当するのは数個だけのはずです)。
現在はJDK 8にも対応しています。
ほぼすべてのモジュールとサブモジュールに、testフォルダー内の対応するユニットテストがあります。皆さんもユニットテストはしっかり書いてください。後になって全体を動かしたとき、どこからバグが出ているのかわからなくなってしまいます。
テストをサボればその場は楽、バグが出たら修羅場です(
まずpom.xmlのコンパイルバージョンを調整してください。IDEにインポートする場合も、使っているJDKに合わせてプロジェクトのコンパイルバージョンを変更します。
最初に、次のコマンドでソースコードをコンパイルします。
mvn compile
続いて、/tmp/mydbをパスとしてデータベースを作成します。
mvn exec:java -Dexec.mainClass="top.guoziyang.mydb.backend.Launcher" -Dexec.args="-create /tmp/mydb"
次のコマンドで、デフォルトのパラメーターを使ってデータベースサーバーを起動します。
mvn exec:java -Dexec.mainClass="top.guoziyang.mydb.backend.Launcher" -Dexec.args="-open /tmp/mydb"
これでローカルの9999番ポートでデータベースサーバーが起動しました。別のターミナルを開き、次のコマンドでクライアントを起動して接続します。
mvn exec:java -Dexec.mainClass="top.guoziyang.mydb.client.Launcher"
対話型のコマンドラインが開き、SQLに似た構文を入力できます。Enterを押すと文がサーバーに送信され、実行結果が表示されます。
実行例です。

