18
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

RustでDBを自作しよう!!

Last updated at Posted at 2022-01-31

概要

以下の一般的なRDBMSのアーキテクチャの3~6の部分を実装をする。

機能 説明 実装有無
構文解析器 SQLを解析 ×
クエリプランナ 実行計画作成 ×
クエリエクスキュータ 実行計画に従いアクセスメソッドを呼び出し
アクセスメソッド データ構造をたどりクエリエクスキュータの要求通り結果を返す(B+Treeを利用)
バッファプールマネージャ データをメモリにキャッシュ
ディスクマネージャ データをディスクに書込&データをディスクから読込

構文解析器や実行計画作成ないのにどうやって実行するの?
→ SQLではなく、クエリエクスキュータを実行する

元ネタ

元ネタは以下
WEB+DB PRESS Vol.122の特集「Rustで実装!作って学ぶRDBMSのしくみ」

上記の雑誌を参考に記載しています
なるべく一般的な内容&公開されているプログラムコードから判断できることのみを記載しています
詳細な説明を知りたい場合は、雑誌を買いましょう!!めちゃくちゃ良い巻の認識!!

環境構築

DBの作成にあたり環境構築を行う

Visual Studio C++ Build toolsのダウンロード&インストール

Rustを使いはじめるには、Visual Studio C++ Build toolsが必要なので、以下のURLからダウンロード
https://visualstudio.microsoft.com/visual-cpp-build-tools/

「C++によるデスクトップ開発」にチェックを入れ、インストール
image.png

Rustをダウンロード&インストール

以下のURLからRUSTUP-INIT.EXE(64-BIT)をダウンロード
https://www.rust-lang.org/ja/learn/get-started

rustup-init.exeを実行
※デフォルトの設定でインストール

コマンドプロンプトで以下を実行してバージョンが確認できれば成功

C:\>rustc --version
rustc 1.58.1 (db9d1b20b 2022-01-20)

visual studio codeをダウンロード&インストール(任意)

以下からイントーラーをダウンロードして、画面に従ってインストール(任意)
https://code.visualstudio.com/download
※デフォルトの設定でインストール

コード補完の拡張機能(rust-lang.rust)をインストール(任意)
image.png

デバックの拡張機能(vadimcn.vscode-lldb)をインストール(任意)

実装(というか配置)

githubの
https://github.com/KOBA789/relly
をクローンorダウンロードしてきて配置

image.png

main.rsがなく、lib.rsなので、ライブラリとして作成されていますね

visual studio codeを起動し、C:\dev\relly-wdbフォルダをOPENする

ディスクマネージャはどのソース?

・以下がディスクマネージャの実装
https://github.com/KOBA789/relly/blob/wdb/src/disk.rs

・ポイント
ファイルは、ヒープファイル構造
ファイルの読み書きしている
※ヒープファイル(ファイルをページと呼ばれる固定の長さごとに区切る)


//ページ単位は4K
pub const PAGE_SIZE: usize = 4096;

//構造
pub struct DiskManager {
    heap_file: File,
    next_page_id: u64,
}

impl DiskManager {
    pub fn new(heap_file: File) -> io::Result<Self> {
        let heap_file_size = heap_file.metadata()?.len();
        let next_page_id = heap_file_size / PAGE_SIZE as u64;
        Ok(Self {
            heap_file,
            next_page_id,
        })
    }

    //省略

    // 読んだり
    pub fn read_page_data(&mut self, page_id: PageId, data: &mut [u8]) -> io::Result<()> {
        //省略
    }

    // 書いたり
    pub fn write_page_data(&mut self, page_id: PageId, data: &[u8]) -> io::Result<()> {
        //省略
    }

    //省略

バッファプールマネージャはどのソース?

・以下がバッファプールマネージャの実装
https://github.com/KOBA789/relly/blob/wdb/src/buffer.rs

・ポイント1
ディスクの内容をメモリ上にキャッシュすることで、ディスクの遅さを回避
キャッシュになかったら、ディスク読み込む


//構造
pub struct BufferPoolManager {
    disk: DiskManager,
    pool: BufferPool,
    page_table: HashMap<PageId, BufferId>,
}

impl BufferPoolManager {
    pub fn new(disk: DiskManager, pool: BufferPool) -> Self {
        let page_table = HashMap::new();
        Self {
            disk,
            pool,
            page_table,
        }
    }

    pub fn fetch_page(&mut self, page_id: PageId) -> Result<Rc<Buffer>, Error> {
        //キャッシュにある場合
        if let Some(&buffer_id) = self.page_table.get(&page_id) {
            let frame = &mut self.pool[buffer_id];
            frame.usage_count += 1;
            return Ok(Rc::clone(&frame.buffer));
        }
        //キャッシュにない場合
        let buffer_id = self.pool.evict().ok_or(Error::NoFreeBuffer)?;
        let frame = &mut self.pool[buffer_id];
        let evict_page_id = frame.buffer.page_id;
        {
            let buffer = Rc::get_mut(&mut frame.buffer).unwrap();
            if buffer.is_dirty.get() {
                self.disk
                    .write_page_data(evict_page_id, buffer.page.get_mut())?;
            }
            buffer.page_id = page_id;
            buffer.is_dirty.set(false);
            self.disk.read_page_data(page_id, buffer.page.get_mut())?;
            frame.usage_count = 1;
        }
        let page = Rc::clone(&frame.buffer);
        self.page_table.remove(&evict_page_id);
        self.page_table.insert(page_id, buffer_id);
        Ok(page)
    }

    //省略

・ポイント2
どのバッファを捨てるかのアルゴリズムはClock-sweepを利用
※Clock-sweepはポスグレでも採用されている
※くるくる回して、いらないキャッシュを探索

    fn evict(&mut self) -> Option<BufferId> {
        let pool_size = self.size();
        let mut consecutive_pinned = 0;
        // くるくる回しまーす
        let victim_id = loop {
            let next_victim_id = self.next_victim_id;
            let frame = &mut self[next_victim_id];
            // バッファの利用回数0の場合
            if frame.usage_count == 0 {      
                // いらないキャッシュのバッファIDを返却
                break self.next_victim_id;
            }
            if Rc::get_mut(&mut frame.buffer).is_some() {
                frame.usage_count -= 1;
                consecutive_pinned = 0;
            } else {
                consecutive_pinned += 1;
                //捨てられるバッファがない場合
                if consecutive_pinned >= pool_size {
                    return None;
                }
            }
            self.next_victim_id = self.increment_id(self.next_victim_id);
        };
        Some(victim_id)
    }

アクセスメソッドはどのソース?

・以下がアクセスメソッドの実装
https://github.com/KOBA789/relly/blob/wdb/src/btree.rs

・ポイント
B+Treeをアルゴリズムとして利用
B+Treeの実装は難しいので、以下で概念を理解するのが良さそう
https://qiita.com/kiyodori/items/f66a545a47dc59dd8839

・サンプル

以下にB+Treeの動作確認の為、サンプルあり
C:\dev\relly-wdb\examples

B+Treeにデータ挿入するコード

C:\dev\relly-wdb>cargo run --example btree-create

B+Treeのデータを検索

C:\dev\relly-wdb>cargo run --example btree-query
   Compiling relly v0.1.0 (C:\dev\relly-wdb)
    Finished dev [unoptimized + debuginfo] target(s) in 1.12s
     Running `target\debug\examples\btree-query.exe`
[48, 79, 6f, 67, 6f] = [4b, 6f, 62, 65]

他にも色々なサンプルコードあり

クエリエクスキュータはどのソース?

・以下がクエリエクスキュータの実装
https://github.com/KOBA789/relly/blob/wdb/src/query.rs

・ポイント
クエリエクスキュータは、さまざまな種類(今回は、シーケンススキャン、フィルター、インデックススキャン)がある
このクエリエクスキュータの組み合わせ方を定義するのが実行計画

クエリエクスキュータの例(フィルター)
くるくる回して、条件に合致するか判別

impl<'a> Executor for ExecFilter<'a> {
    fn next(&mut self, bufmgr: &mut BufferPoolManager) -> Result<Option<Tuple>> {
        // データくるくる
        loop {
            match self.inner_iter.next(bufmgr)? {
                Some(tuple) => {
                    // 条件に一致した場合
                    if (self.cond)(&tuple) {
                        return Ok(Some(tuple));
                    }
                }
                None => return Ok(None),
            }
        }
    }
}

実行計画の例(フィルター)

impl<'a> PlanNode for Filter<'a> {
    fn start(&self, bufmgr: &mut BufferPoolManager) -> Result<BoxExecutor> {
        let inner_iter = self.inner_plan.start(bufmgr)?;
        Ok(Box::new(ExecFilter {
            inner_iter,
            cond: self.cond,
        }))
    }
}

クエリエクスキュータを実行してみる

・以下が実装例
https://github.com/KOBA789/relly/blob/wdb/examples/simple-table-plan.rs

・ポイント
SeqScanをFilterで入れ子にして、select * 省略 where id >= 'w' and id < 'z' and first_name < 'Dave'
を表現している

fn main() -> Result<()> {
    let disk = DiskManager::open("simple.rly")?;
    let pool = BufferPool::new(10);
    let mut bufmgr = BufferPoolManager::new(disk, pool);
    // フィルター
    let plan = Filter {
        // 検索条件
        cond: &|record| record[1].as_slice() < b"Dave",
        // シーケンススキャン
        inner_plan: &SeqScan {
            table_meta_page_id: PageId(0),
            // 検索条件
            search_mode: TupleSearchMode::Key(&[b"w"]),
            while_cond: &|pkey| pkey[0].as_slice() < b"z",
        },
    };
    let mut exec = plan.start(&mut bufmgr)?;

    while let Some(record) = exec.next(&mut bufmgr)? {
        println!("{:?}", tuple::Pretty(&record));
    }
    Ok(())
}

事前作業(テーブル作成&データ挿入)

C:\dev\relly-wdb>cargo run --example simple-table-create

実行

C:\dev\relly-wdb>cargo run --example simple-table-plan
   Compiling relly v0.1.0 (C:\dev\relly-wdb)
    Finished dev [unoptimized + debuginfo] target(s) in 1.19s
     Running `target\debug\examples\simple-table-plan.exe`
Tuple("x" [78], "Bob" [42, 6f, 62], "Johnson" [4a, 6f, 68, 6e, 73, 6f, 6e])
Tuple("y" [79], "Charlie" [43, 68, 61, 72, 6c, 69, 65], "Williams" [57, 69, 6c, 6c, 69, 61, 6d, 73])
18
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?