MonetDBを調べてみる

awsのredshiftみたいな話もあったので、カラム指向DBとか列指向DBとか呼ばれる技術をMonetDBで調べてみました。

カラム指向DB

wikipediaに書いてある内容を見ていただくのが手っ取り早いと思いますが、普通のRDBMSだと1ブロックにたいして、行を詰められるだけ詰めるという仕組みだと思いますが、カラム指向DBの場合は行ではなく、ある列だけをひとまとめにするような仕組みっぽいです。

ちなみにpostgresの場合は、こんな感じでデータを詰め込んでいます。

ビルド

当然デバッグビルドします。シンボルつけて、最適化なしでビルドします。なお、ビルドの際にはlibxml-dev(ubuntuの場合)が必要です。

[sourcecode]

% CFLAGS="-g -O0" ./configure --prefix=$HOME

% make

% make install

[/sourcecode]

動かし方

mserver5というのがインストールされているので、とりあえずこれを起動すればokです。

[sourcecode]

% mserver5

# MonetDB 5 server v11.15.1 "Feb2013"

# Serving database 'demo', using 2 threads

# Compiled for x86_64-unknown-linux-gnu/64bit with 64bit OIDs dynamically linked

# Found 999.285 MiB available main-memory.

# Copyright (c) 1993-July 2008 CWI.

# Copyright (c) August 2008-2013 MonetDB B.V., all rights reserved

# Visit http://www.monetdb.org/ for further information

# Listening for connection requests on mapi:monetdb://127.0.0.1:50000/

# MonetDB/JAQL module loaded

# MonetDB/SQL module loaded

>

[/sourcecode]

クライアントはmclientというものを使います。初期ユーザとパスワードは"monetdb"のようです。

[sourcecode]

% mclient

user(y-asaba):monetdb

password:

Welcome to mclient, the MonetDB/SQL interactive terminal (Feb2013)

Database: MonetDB v11.15.1 (Feb2013), 'demo'

Type \q to quit, \? for a list of available commands

auto commit mode: on

sql>

[/sourcecode]

あとはmclient上でSQLをテーブル作ったりを普通にできます。

アーキテクチャ

http://www.monetdb.org/Documentation/Manuals/MonetDB/Architecture

こちらを見ても正直よくわからないところですが、MALというVMを実装していて、SQLを実行する際には最終的にはMALのinstructionを実行することになります。

例えば、create table aiueo (a int, b int)というテーブルを作った場合に、insert into aiueo values (1, 2)というのを実行すると、

  1. aiueoというテーブルのaというカラムに対して値をappendするfunction callする
  2. aiueoというテーブルのbというカラムに対して値をappendするfunction callする
  3. affected_rowsを返すfunction callする
  4. commitする

みたいなinstructionが生成され、実行されます。

/MonetDB-11.15.1/monetdb5/mal/mal_interpreter.c:

[sourcecode language="cpp" firstline="568"]

if (!RECYCLEentry(cntxt, mb, stk, pci)){

/* The interpreter loop

* The interpreter is geared towards execution a MAL procedure together

* with all its decendant invocations. As such, it provides the

* MAL abtract machine processor.

[/sourcecode]

[sourcecode language="cpp" firstline="636"]</pre>

case PATcall:

if (pci->fcn == NULL) {

ret = createScriptException(mb, stkpc, MAL, NULL,

"address of pattern %s.%s missing", pci->modname, pci->fcnname);

} else

ret = (str)(*pci->fcn)(cntxt, mb, stk, pci); <-- ここ

break;

[/sourcecode]

appendの場合は、mvc_appendが呼ばれます。

[sourcecode]</pre>

(gdb) bt

#0 BUNappend (b=0x279c540, t=0x296bd80, force=1 '\001') at gdk_bat.c:1319

#1 0x00007f8139c36d3f in delta_append_val (bat=0x14f2900, i=0x296bd80) at bat_storage.c:326

#2 0x00007f8139c36e2c in append_col (tr=0x22532d0, c=0x27682b0, i=0x296bd80, tpe=5) at bat_storage.c:343

#3 0x00007f8139b68597 in mvc_append_wrap (cntxt=0x107dc68, mb=0x2483290, stk=0x296bcc0, pci=0x28fbc40) at sql.mx:3261

#4 0x00007f813d2e9b98 in runMALsequence (cntxt=0x107dc68, mb=0x2483290, startpc=1, stoppc=0, stk=0x296bcc0, env=0x0, pcicaller=0x0) at mal_interpreter.c:641

#5 0x00007f813d2e9182 in callMAL (cntxt=0x107dc68, mb=0x2483290, env=0x7f81390f4cf0, argv=0x7f81390f4cb0, debug=0 '\000') at mal_interpreter.c:469

#6 0x00007f8139b32a04 in SQLexecutePrepared (c=0x107dc68, be=0x221ea00, q=0x10b4330) at sql_scenario.c:1773

#7 0x00007f8139b32da2 in SQLengineIntern (c=0x107dc68, be=0x221ea00) at sql_scenario.c:1840

#8 0x00007f8139b332c0 in SQLengine (c=0x107dc68) at sql_scenario.c:1941

#9 0x00007f813d313587 in runPhase (c=0x107dc68, phase=4) at mal_scenario.c:522

#10 0x00007f813d313751 in runScenarioBody (c=0x107dc68) at mal_scenario.c:567

#11 0x00007f813d313860 in runScenario (c=0x107dc68) at mal_scenario.c:586

#12 0x00007f813d314744 in MSserveClient (dummy=0x107dc68) at mal_session.c:431

#13 0x00007f813b02d9ca in start_thread (arg=<value optimized out>) at pthread_create.c:300

#14 0x00007f813ad8a21d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:112

#15 0x0000000000000000 in ?? ()

[/sourcecode]

物理ファイル

[sourcecode]

sql>select * from storage() where "table" = 'aiueo';

+--------+-------+--------+------+----------+-------+-----------+------------+----------+---------+--------+

| schema | table | column | type | location | count | typewidth | columnsize | heapsize | indices | sorted |

+========+=======+========+======+==========+=======+===========+============+==========+=========+========+

| sys | aiueo | a | int | 15/1532 | 18 | 4 | 72 | 0 | 0 | false |

| sys | aiueo | b | int | 15/1531 | 18 | 4 | 72 | 0 | 0 | false |

+--------+-------+--------+------+----------+-------+-----------+------------+----------+---------+--------+

[/sourcecode]

locationというのが実際の物理ファイルで、ここに書き込まれていきます。普通のRDBMSであれば、1テーブル1ファイル(テーブルサイズがでかいと分割することもありますが)な構成に対して、1カラム1ファイルになるようです。で、どんな感じに圧縮されるのかを見たかったのですが、データサイズが小さいためか圧縮されなかったので、これはまた後日見るということで。

[sourcecode]

insert into aiueo values (9, 9)をしたあと。"¥t¥0¥0¥0"が9

% od -c 1531.tail

0000000 002 \0 \0 \0 003 \0 \0 \0 003 \0 \0 \0 003 \0 \0 \0

0000020 003 \0 \0 \0 003 \0 \0 \0 320 \a \0 \0 005 \0 \0 \0

0000040 005 \0 \0 \0 005 \0 \0 \0 005 \0 \0 \0 005 \0 \0 \0

*

0000100 005 \0 \0 \0 005 \0 \0 \0 \t \0 \0 \0

0000114

[/sourcecode]

まとめ

実用に耐えられるかどうかは全く検証していませんが、とりあえず簡単にMonetDBを眺めてみました。面白そうなのでもうちょっとソースコードを追いかけてみたいと思います。