dbの3態

Table of Contents

apt using Berkeley DB

前回はFreeBSDのパッケージングシステムを調べた。RDBM/SQLだった。じゃ、 他のOSは?

OpenBSDが一番初期の形だ。それにあきたらなくなって、FreeBSDはsqliteを取 り入れたんだった。

vbox$ cd /var/db/pkg
vbox$ ls -l emacs-28.2-no_x11/
total 1000
-rw-r--r--  1 root  wheel  489364 Nov  3 05:47 +CONTENTS
-rw-r--r--  1 root  wheel    1298 Nov  3 05:46 +DESC
-rw-r--r--  1 root  wheel      52 Nov  3 05:47 +REQUIRING
vbox$ cat emacs-28.2-no_x11/+REQUIRING
gnutls-3.7.7
gmp-6.2.1p0
libxml-2.10.3
jansson-2.14

こんな具合にテキストベースで全て賄われている。

一方ArchLinux/pacmanに有るパッケージ情報も見てみる。

[sakae@arch local]$ pwd
/var/lib/pacman/local
[sakae@arch local]$ file emacs-nox-28.2-2/*
emacs-nox-28.2-2/desc:  ASCII text
emacs-nox-28.2-2/files: ASCII text
emacs-nox-28.2-2/mtree: gzip compressed data, from Unix, original size modulo 2^32 818561

descは、いわゆるパッケージのinfo。filesはファイル名のリスト。mtreeは、 それにシグネチャを加えたもの。サイズが大きいので、tgzになってる。素直 な仕組みで、OpenBSDのそれと似ている。

デビアンのパッケージングシステムがどうなってるか、調べてみた。そしたら、

sakae@deb:/var/lib/apt$ file listchanges.db
listchanges.db: Berkeley DB (Hash, version 9, native byte-order)

dpkg/ とかも関係してくるみたいだけど、深くは追求しない。それより、バー クレーですよ。リナも憧れのバークレーですよ。スタンフォードでもいいんだ ろうけど、ソフトの世界じゃバークレーかMITが聖地ですから。

Berkeley DB を見ると、ボラクルに売却されて随分昔から、 Oracle Berkeley DB になってるようだ。知らなかったぞ。

dbopen btree …

DBFile - Berkeley DB バージョン 1.xへのPerl5アクセス

btree – 単純な BTree データベース for MicroPython

bsddb – Berkeley DB ライブラリへのインタフェース

何の気もなく

man -k database
btree(3) - btree database access method
dbopen(3) - database access methods

なんてやったら、面白いのが出てきた。中身を眺めてみると、

NAME
     btree – btree database access method
DESCRIPTION
     The dbopen() routine is the library interface to database files.  One of
     the supported file formats is btree files.  The general description of
     the database access methods is in dbopen(3).  This manual page describes
     only the btree specific information.
SEE ALSO
     dbopen(3), hash(3), recno(3)

どうやら標準で提供されるデータベース構築用のルーチンみたいだ。dbopenす る時に、データベースの性格をB木にするか、ハッシュにするか、レコードに するか選ぶみたい。

in libc/db

とりあえず、ソースに対面かな。README

#       $OpenBSD: README,v 1.5 2015/04/05 14:35:16 tedu Exp $
#       @(#)README      8.27 (Berkeley) 9/1/94

This is version 1.85 of the Berkeley DB code.

ボラクルに買収される前の由緒正しきコードとな。

注目なのは、hash/ndbm.c なんてのが混っている事。そう、あのndbmですよ。 これに対抗する為、gdbmなんてのが作られた。

かの昔、CGIが出初めた頃、スケジュールWEBを作ろうとした。どっちのdbmが 優秀なんだろうかねと散々悩んだものだ。こんな所で出会うとはね。

コードをざっと 見。

static DBM *
_dbm_open(const char *file, const char *suff, int flags, mode_t mode)
{
  :
        info.bsize = 4096;
        info.ffactor = 40;
        info.nelem = 1;
        info.cachesize = 0;
        info.hash = NULL;
        info.lorder = 0;
        return ((DBM *)__hash_open(path, flags, mode, &info, 0));
}

hashを使っているんだね。

in many commands

で、dbopenとかは、どう使えばいいの? こういう時は実例に当たるのが一番 だろう。提供されてるソースを横断的に探してみる。リナでこれをやろうとす ると、とっても大変だろう。検索の目印は、dbopenで十分だろう。

vbox$ find . -name '*.c' | xargs grep -l dbopen
./distrib/special/libstubs/db.c
./lib/libc/db/db/db.c
./lib/libc/gen/devname.c
./lib/libc/gen/getcap.c
./lib/libc/gen/getnetgrent.c
./lib/libc/gen/getpwent.c
./lib/libc/gen/ttyname.c
  :
./usr.bin/cap_mkdb/cap_mkdb.c
./usr.bin/vacation/vacation.c
./usr.bin/vi/common/exf.c
./usr.bin/vi/common/log.c
./usr.sbin/dev_mkdb/dev_mkdb.c
./usr.sbin/kvm_mkdb/kvm_mkdb.c
./usr.sbin/kvm_mkdb/testdb.c
./usr.sbin/netgroup_mkdb/netgroup_mkdb.c
./usr.sbin/pwd_mkdb/pwd_mkdb.c
./usr.sbin/rpc.statd/statd.c
./usr.sbin/sa/pdb.c
./usr.sbin/sa/usrdb.c
./usr.sbin/smtpd/makemap.c
./usr.sbin/smtpd/table_db.c
./usr.sbin/spamdb/spamdb.c
./usr.sbin/ypserv/common/ypdb.c
./usr.sbin/ypserv/makedbm/db.c

vacation

なにを調べてもいいんだけど、昭和のおじさんは懐しいロンバケって事で、 バケーションコマンドに狙いを定める。このコマンドは、メールの自動応答用 ね。

vacation returns a message to the sender of a message telling them that
you are currently not reading your mail.  The intended use is in a
.forward file.  For example, your .forward file might have:

      \eric, "|/usr/bin/vacation -a allman eric"

The people who have sent you messages are maintained as a Berkeley DB
database in the file .vacation.db in your home directory.

vacation expects a file .vacation.msg, in your home directory, containing
a message to be sent back to each sender.  It should be an entire message
(including headers).  For example, it might contain:

      From: eric@CS.Berkeley.EDU (Eric Allman)
      Subject: I am on vacation
      Delivered-By-The-Graces-Of: The Vacation program
      Precedence: bulk

      I am on vacation until July 22.
      If you have something urgent,
      please contact Keith Bostic <bostic@CS.Berkeley.EDU>.
      --eric

のどかな時代の名残。今こんなのを運用すれば、家は留守ですと公言してる事 になり、空き巣の格好の餌食になる。大丈夫、もうメールなんて使わないですっ て人が多いのかな。いや、臆面もなくツイターしてるじゃん。スマホだと、どこまでも追跡される。不便な世の中 だ。

で、主要な部分は、下記。open -> put/get -> close 定番の流れだ。

DB *db;

        if (iflag == 1 || (stat(VDB, &sb) == 0 && sb.st_size == (off_t)0))
                flags = O_CREAT|O_RDWR|O_TRUNC;
        else
                flags = O_CREAT|O_RDWR;
        db = dbopen(VDB, flags, S_IRUSR|S_IWUSR, DB_HASH, NULL);
        (void)(db->close)(db);

setinterval(time_t interval)
{
        DBT key, data;

        key.data = VIT;
        key.size = sizeof(VIT);
        data.data = &interval;
        data.size = sizeof(interval);
        (void)(db->put)(db, &key, &data, 0);
}


small test

簡単な奴を作ってみる。仕様は、与えた文字列をkeyにして、それを倍に連結して登録するやつ。

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <db.h>
#include <fcntl.h>
#include <limits.h>

DB *db;

void reg_kv(char *mykey) {
    char dst[128];
    DBT key, data;

    snprintf(dst, sizeof dst, "< %s_%s >", mykey, mykey);
    key.data = (char *)mykey;
    key.size = strlen(mykey) + 1;
    data.data = (char *)dst;
    data.size = strlen(dst) + 1;
    (void)(db->put)(db, &key, &data, 0);
}

int main(){
  db = dbopen("z.db", O_CREAT|O_RDWR, 0644, DB_BTREE, NULL);
  reg_kv("hogefuga");
  reg_kv("hello");
  reg_kv("HACKING");
  (void)(db->close)(db);
  return 0;
}

読み出しのコードは、まだなんで、ちょいとズルしてみた。キーも値も、取り 敢えず保存されたっぽい。そこはかとなく、辞書順になってる。まあ断定は出 来無いけど。

vbox$ strings z.db
HACKING
< HACKING_HACKING >
hello
< hello_hello >
hogefuga
< hogefuga_hogefuga >

キーを指定して値を表示するコード例。

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <db.h>
#include <fcntl.h>
#include <limits.h>

DB *db;

void chkV(char *mykey) {
    DBT key, data;

    key.data = (char *)mykey;
    key.size = strlen(mykey) + 1;
    if ((db->get)(db, &key, &data, 0) == 0)
      printf("%s\n", (char *)data.data);
    else
      printf("Not found\n");
}

int main(){
  char ck[64];

  db = dbopen("z.db", O_RDONLY, 0644, DB_BTREE, NULL);
  for ( ; ; ){
    printf("key? "); scanf("%s", ck);
    chkV(ck);
  }
  (void)(db->close)(db);
  return 0;
}

動かして確認してみる。

vbox$ ./a.out
key? HACKING
< HACKING_HACKING >
key? hello
< hello_hello >
key? hacking
Not found

for big data

大きなデータを登録すると、ツリー版とハッシュ版で、どんな差が出るのだろ う? 大きなデータの代表として、下記の規模の単語帖を利用してみる。

vbox$ wc /usr/share/dict/words
  235971  235971 2493843 /usr/share/dict/words

上のサンプロのmainだけを、入れ替え。

int main(){
  char buf[128];
  FILE *fp;

  db = dbopen("z.db", O_CREAT|O_RDWR, 0644, DB_HASH, NULL);
  fp = fopen("/usr/share/dict/words", "r");
  while(fgets(buf, sizeof buf, fp) != NULL) {
    buf[strlen(buf) - 1] = '\0';     // delete \n
    reg_kv(buf);
  }
  fclose(fp);
  (void)(db->close)(db);
  return 0;
}

DB_HASH , DB_BTREE に変更しながら実験。まるでスクリプト言語の乗り で楽しんでるな。

vbox$ time ./a.out
    0m13.36s real     0m00.97s user     0m11.99s system
vbox$ file z.db
z.db: Berkeley DB 1.85 (Hash, version 2, native byte-order)
vbox$ ls -l z.db
-rw-r--r--  1 sakae  wheel  20971520 Mar 19 14:47 z.db
vbox$ time ./a.out
    0m00.53s real     0m00.21s user     0m00.30s system
vbox$ file z.db
z.db: Berkeley DB 1.85/1.86 (Btree, version 3, little-endian)
vbox$ ls -l z.db
-rw-r--r--  1 sakae  wheel  13410304 Mar 19 14:50 z.db

この結果からツリー版の方が優秀じゃんと早合点しないように。。。 そもそも辞書ってソートしてあるじゃん。そんなデータとbtreeは相性が良す ぎないか。競技は公平にが原則だろう。元データをグチャグチャに混ぜてしま え。こういう時にも使えるのがsortです。

vbox$ sort -R /usr/share/dict/words | head -4
decimole
Nannette
geographics
vaalite
vbox$ sort -R /usr/share/dict/words | head -4
reconciliator
bankrider
yield
susception

グチャグチャなデータでの作成結果。

vbox$ time ./hash_use
    0m13.11s real     0m00.88s user     0m11.48s system
vbox$ time ./btree_use
    0m13.10s real     0m00.55s user     0m11.79s system

btreeの優位性が失くなった。それにしてもsystem時間が甚大だな。何をやっ ているんだろう?

0m04.48s real     0m00.21s user     0m04.27s system  # use hash
0m06.67s real     0m00.40s user     0m06.27s system  # use btree

こちらは、全件数の読み出しにかかった時間。勿論、キーは乱数的に指定して、 公平を期している。やっぱりhashが速いんだな。

recno

もう一つの形式であるrecnoは、オイラーの琴線に触れないので省略する。興 味ある方は、こちらを参照。 recno - レコード番号データベースへのアクセスメソッド

で、リナのglibcでは、上のコードがエラーでコンパイル出来ない。基幹のラ イブラリィーを途中で変更しちゃうというトンデモOSじゃけんのう!

どうしてもやりたかったら、libdbってやつを入れて、それに合致するように 変更する事。バークレーDBの新版を味わえるぞ。

現代風の実行時間測定

gettimeofdayを使うかと思っていたら、便利なものが有った。

#include <time.h>
#include <sys/time.h>

int main(){
  char buf[128];
  FILE *fp;
  int cnt=0;
  struct timespec pst, start, stop;

  db = dbopen("z.db", O_CREAT|O_RDWR|O_TRUNC, 0644, DB_HASH, NULL);
  fp = fopen("./SEED", "r");
  clock_gettime(CLOCK_MONOTONIC, &start);
  while(fgets(buf, sizeof buf, fp) != NULL){
    buf[strlen(buf) - 1] = '\0';
    reg_kv(buf);
    cnt++;
    if (cnt % 10000 == 0){
      clock_gettime(CLOCK_MONOTONIC, &stop);
      timespecsub(&stop, &start, &pst);
      printf("%d %lld.%09ld\n", cnt, (long long)pst.tv_sec, pst.tv_nsec);
    }
  }
  fclose(fp);
  (void)(db->close)(db);
  return 0;
}
10000 0.342093758
20000 0.814867812
 :
220000 12.170719463
230000 12.939625008

gnuplot "log" w l で、簡単にグラフ化。件数が増えるとグラフが直線から剃 れて立ってくる風。ちょいと上のプログラムを変更すれ ば、微分した結果をグラフ表示できるぞ。

vbox$ seq -f p%.6fq  0 0.000997 1000 | head -5
p0.000000q
p0.000997q
p0.001994q
p0.002991q
p0.003988q
vbox$ seq -f p%.6fq  0 0.000997 1000 | wc
 1003010 1003010 12928798

こんな風に人口データを作成して、試してみると面白いかな。

see internal of db

(gdb) b hash_put
Function "hash_put" not defined.
Breakpoint 1 (hash_put) pending.
(gdb) r
Starting program: /tmp/a.out

Breakpoint 1, hash_put (dbp=0x7c002940, key=0xcf7dbd04, data=0xcf7dbcfc, flag=0\
) at /usr/src/lib/libc/db/hash/hash.c:523
523             hashp = (HTAB *)dbp->internal;
(gdb) n
524             if (flag && flag != R_NOOVERWRITE) {
(gdb) p *hashp
$2 = {
  hdr = {
    magic = 398689,
    version = 2,
    lorder = 1234,
    bsize = 8192,
    bshift = 13,
    dsize = 256,
    ssize = 256,
    sshift = 8,
    ovfl_point = 11,
    last_freed = 167,
    max_bucket = 1522,
    :

これが管理情報か。次はハッシュの真髄。

(gdb) b __default_hash
Breakpoint 2 at 0x782a464: file /usr/src/lib/libc/db/hash/hash_func.c, line 52.
(gdb) c
Continuing.

Breakpoint 2, __default_hash (key=0x1ada33be, len=9) at /usr/src/lib/libc/db/hash/hash_func.c:52
52              if (len > 0) {
(gdb) bt
#0  __default_hash (key=0x1ada33be, len=9) at /usr/src/lib/libc/db/hash/hash_func.c:52
#1  0x07809493 in __call_hash (hashp=0x4c413e00, k=0x1ada33be "hogefuga", len=9) at /usr/src/lib/libc/db/hash/hash.c:848
#2  hash_access (hashp=<optimized out>, action=HASH_PUT, key=0xcf7d40d4, val=0xcf7d40cc) at /usr/src/lib/libc/db/hash/hash.c:574
#3  0x07808bb6 in hash_put (dbp=0x4c3f7f00, key=0xcf7d40d4, data=0xcf7d40cc, flag=0) at /usr/src/lib/libc/db/hash/hash.c:532
#4  0x1ada48c2 in reg_kv (mykey=0x1ada33be "hogefuga") at mydb.c:19
#5  0x1ada4968 in main () at mydb.c:24

python dbm

dbm — Unix "データベース" へのインターフェース

Pythonのdbmの使い方

In [1]: import dbm
   ...: db = dbm.open('/tmp/dbm', 'c')
   ...: db['foo'] = 'bar'
   ...: db['hoge'] = 'meta'
   ...: db.close()
(venv) [sakae@fb /tmp]$ file dbm.db
dbm.db: Berkeley DB 1.85 (Hash, version 2, native byte-order)
(venv) [sakae@fb /tmp]$ strings dbm.db
barfoo
metahoge

アローって格好いいな。

前回の資料をみていたら、矢印が使われていた。それから、一行野郎も出てい た。初めての体験で新鮮だな。

# key の検索。ヒットすれば True、しなければ False
def member(self, key) -> bool:
    t = self.root
    :
	    return False

# キーのリスト
def keys(self) -> list: return keys_sub(self.root)

そして、遂にでてきたか。 Check out Codon: A Python compiler if you have a need for C/C++ speed

組み込み関数も、対象でしょうね。


This year's Index

Home