データベース

Oracle BerkeleyDBのインストール/設定/利用方法

Oracle BerkeleyDBとは何か

Oracle BerkeleyDBとは、アプリケーションに組み込んで使うKV型データベースです。
近頃は機能が拡張され、SQLを扱う事も出来ます。

データベースとしては、Hash又はBTREE又はQueue又はReconoの何れかの構造に基づくKey Value型DBです。
データベースへのアクセスはKey Value型アクセスの他、SQLやXMLでのアクセスもサポートされるようになりました。

ネットワーク通信が必要ない、同じアプリケーション空間内で作動するDBなので、当然速度的には最高レベルのパフォーマンスを発揮します。
また、結果として、組み込み系ソフトのDBとしての利用にも向いています。
但し、アプリケーションと同じメモリ空間で動く為、同時アクセスの時の制御等、簡単そうで扱いが難しい面もあるので、同時書込の制御等には注意が必要です。

ここではそのBerleyDBのインストール方法についてまとめます。


BerkeleyDBのライセンス形態

https://www.oracle.com/technetwork/database/berkeleydb/downloads/licensing-098979.html
がソース元になりますが、2つのライセンス形態があります

オープンソースモデル: オープンソースへの利用または非配布型ソフトでの利用を許可
コマーシャルモデル: アプリケーションのコードを公開する必要がなく、配布するソフトウェアに組み込む事もできる。


Oracle BerkeleyDBのインストール

古いバージョンで良ければ、BerkeleyDBは大概どんなデータベースにも入っています。
/usr/lib64/libdb.so
又は
/usr/lib/libdb.so
がそれにあたります。

但し、Oracleが買収してから、手入れしているOracle DBの最新版は入っていないので、最新版を入れたい場合には、ソースコードから自分でビルドしてインストールする必要があります。


ソースコードのダウンロード

https://www.oracle.com/database/technologies/related/berkeleydb-downloads.html
にアクセスしして、ソースコードをダウンロード。
基本的に最新バージョンをダウンロードすれば良いかと思います。

なお、ダウンロードするには、Oracleのサイトの会員になる必要がありますが、無料でアカウントの取得は可能です。


環境変数の用意

まず

echo $LD_LIBRARY_PATH;

と打って、/usr/local/libが先の方に出てこないのなら

vi ~/.bash_profile;

を打って

LD_LIBRARY_PATH=/usr/local/lib64:/usr/local/lib:/usr/lib64:/usr/lib:/lib64:/lib
export LD_LIBRARY_PATH

という行を追加して、

. ~/.bash_profile;

と打って反映。
再度

echo $LD_LIBRARY_PATH;

と打って環境変数への反映を確認


BerkeleyDBのビルドとインストール

ソースコードを解凍したディレクトリにcdしてから

cd build_unix;
make clean;
../dist/configure --prefix=/usr/local --enable-cxx --enable-sql;
make;
make install;

システム全体で共有ライブラリとして使えるように

echo /usr/local/lib > /etc/ld.so.conf.d/usr-local-lib.conf

こうするとapache2.4とかでもBerkeleyDBを見た時にインストールされた版の方を見るようになる


各言語での実装


C言語での使い方

#include <sys/types.h>
#include <stdio.h>
#include <db.h>
#define DATABASE "access.db"

int main(){
  DB *dbp;
  DBT key, data;
  int ret, t_ret;
  if((ret = db_create(&dbp, NULL, 0)) != 0){
    fprintf(stderr, "Db_create: %s\n", db_strerror(ret));
    exit(1);
  }

  if((ret=dbp->open(
    dbp, NULL, DATABASE, NULL, DB_BTREE, DB_CREATE, 0644))!=0){
    dbp->err(dbp, ret, "%s", DATABASE);
    goto err;
  }

  memset(&key, 0, sizeof(key));
  memset(&data, 0, sizeof(data));
  key.data="fruit";
  key.size=sizeof("fruit");
  
  data.data="apple";
  data.size=sizeof("apple");

  if((ret=dbp->put(dbp, NULL, &key, &data, 0))==0){
    printf("db: %s: key stored.\n", (char *)key.data);
  }
  else{
    dbp->err(dbp, ret, "DB->put");
    goto err;
  }
  
  if((ret=dbp->get(dbp, NULL, &key, &data, 0))==0){
    printf("db: %s: key retrieved: data was %s.\n",
           (char *)key.data,(char *)data.data);
  }
  else{
    dbp->err(dbp, ret, "DB->get");
    goto err;
  }

  if((ret=dbp->del(dbp, NULL, &key, 0))==0){
    printf("db: %s: key was deleted.\n", (char *)key.data);
  }
  else{
    dbp->err(dbp, ret, "DB->del");
    goto err;
  }

err: if((t_ret = dbp->close(dbp, 0))!=0 && ret==0){
    ret=t_ret;
  }
  
  exit(ret);
}

Perlからの使い方

ダウンロードしたソースコードのディレクトリで

cd lang/perl/BerkeleyDB;
vi config.in

INCLUDE	= /usr/local/include
LIB	= /usr/local/lib

が効くようにコメントアウト。

/usr/lib64/libdb.so
があったら

mv /usr/lib64/libdb.so /usr/lib64/libdb.so.nouse

で移動

そしたら

perl Makefile.PL;
make test;
make;
make install;

Perlでのプログラム例
BDB::Wrapperを追加インターフェースとして使用
Tokyo::Cabinetを使ったらの部分も混ざっていますが、そこはコメントアウトされています。

#!/usr/local/bin/perl -w
package bench_bdb;

=pod
Copryright: 1st class, inc.
Script by: Hajime Kurita
URL: http://www.accessup.org/

Preparation: 
perl -MCPAN -e shell "install BerkeleyDB";
perl -MCPAN -e shell "install BDB::Wrapper"; # This module is distributed at http://search.cpan.org/~hikarine/ (Author of this script)

Purpose:
Check the speed of hdd
Check how much cache is best for the BDB
Check whether hash or btree is good for your system
Check the difference of the speed of each version of BerkeleyDB
=cut

use strict;
use BDB::Wrapper;
# use TokyoCabinet;
use Benchmark;

my $pro=new bench_bdb;
$pro->run();

sub new(){
  my $self={};
  return bless $self;
}

sub run(){
  my $self=shift;
  $self->init_vars();
  $self->check_bdb_speed();
  $self->report();
}

sub init_vars(){
  my $self=shift;
  $self->{'hash'}=$self->{'max'}=0;
  my $cache='';
  my $no_lock=0;
  my $ram=0;
  my $len=8124;
  $self->{'type'}='';
  foreach my $ARGV (@ARGV){
    if($ARGV=~ m!^--max=(\d+)!){
      $self->{'max'}=$1;
    }
    elsif($ARGV=~ m!^--hash!){
      $self->{'hash'}=1;
    }
    elsif($ARGV=~ m!^--ram=(\d+)!){
      $ram=$1;
    }
    elsif($ARGV=~ m!^--len=(\d+)!){
      $len=$1;
    }
    elsif($ARGV=~ m!^--cache=(\d+)!){
      $cache=$1;
      if($cache>1000000000){
        print "To avoide system failure by mistype, you cannot set the cache which is over than 1GB.";
        print "You tried to set ".$self->add_digit($cache)." bytes. If you really want to do it, please modify this script by your hand.";
        exit;
      }
    }
    elsif($ARGV=~ m!^--no_lock=(\d+)!){
      $no_lock=$1;
    }
    elsif($ARGV=~ m!^--type=(.+)$!){
      $self->{'type'}=$1;
    }
    else{
      die "Invalid option";
    }
  }
  
  $self->{'put_str'}='';
  for(my $i=0;$i<$len;$i++){
    $self->{'put_str'}.='a';
  }
  
  $self->{'max'}=100000 unless $self->{'max'};
  my $pwd=`pwd`;
  $pwd=~ s!\s!!gs;
  $self->{'bdb'}=$pwd.'/speed.bdb';
  $self->{'bdb'}.='.'.$self->{'type'} if($self->{'type'});
  unlink $self->{'bdb'} if -f $self->{'bdb'};
  if($self->{'type'}){
=pod
    $self->{'tch'}=TokyoCabinet::BDB->new();
    tchdbtune(hdb, rnum * 3, 0, 0, 0);
    $self->{'tch'}->tchdbsetxmsiz($cache) if $self->{'cache'};
=cut
  }
  else{
    system('rm -rf /dev/shm/bdb_home'.$pwd.'/speed');
    system('rm -rf /www/ichiji/bdb_home'.$pwd.'/speed');
    print 'no_lock='.$no_lock.' ram='.$ram.' cache='.$cache."\n";
    $self->{'bdbw'}=new BDB::Wrapper({'no_lock'=>$no_lock, 'ram'=>$ram, 'cache'=>$cache});
  }
  $self->{'time'}={};
}

sub check_bdb_speed(){
  my $self=shift;
  $self->check_time_to_write();
  $self->check_time_to_update();
  $self->check_time_to_read();
}

sub check_time_to_write(){
  my $self=shift;
  my $start=new Benchmark;
  if($self->{'type'}){
=pod
    if(!$self->{'tch'}->open($self->{'bdb'}, $self->{'tch'}->OWRITER | $self->{'tch'}->OCREAT)){
      my $ecode = $self->{'tch'}->ecode();
      printf STDERR ("open error: %s\n", $self->{'tch'}->errmsg($ecode));
    }
     store records
      for(my $i=0;$i<$self->{'max'}+1;$i++){
      if(!$self->{'tch'}->put($i, $self->{'put_str'})){
        my $ecode = $self->{'tch'}->ecode();
        printf STDERR ("put error: %s\n", $self->{'tch'}->errmsg($ecode));
      }
    }
    if(!$self->{'tch'}->close()){
      my $ecode = $self->{'tch'}->ecode();
      printf STDERR ("close error: %s\n", $self->{'tch'}->errmsg($ecode));
    }
=cut
  }
  else{
    my $bdbh=$self->{'bdbw'}->create_write_dbh($self->{'bdb'}, {'hash'=>$self->{'hash'}}) or die "Failed to write to ".$self->{'bdb'}."\n";
    for(my $i=0;$i<$self->{'max'}+1;$i++){
      unless($bdbh->db_put($i, $self->{'put_str'})==0){
        die "Failed to write to ".$self->{'bdb'};
      }
    }
    $bdbh->db_close();
  }
  
  my $end=new Benchmark;
  $self->{'time'}->{'write'}=timestr(timediff($end, $start));
  print "Write\t".$self->{'time'}->{'write'}."\n";
}

sub check_time_to_update(){
  my $self=shift;
  my $start=new Benchmark;
  if($self->{'type'}){
=pod
    if(!$self->{'tch'}->open($self->{'bdb'}, $self->{'tch'}->OWRITER | $self->{'tch'}->OCREAT)){
      my $ecode = $self->{'tch'}->ecode();
      printf STDERR ("open error: %s\n", $self->{'tch'}->errmsg($ecode));
    }
    # store records
    for(my $i=0;$i<$self->{'max'}+1;$i++){
      if(!$self->{'tch'}->put($i, $self->{'put_str'})){
        my $ecode = $self->{'tch'}->ecode();
        printf STDERR ("put error: %s\n", $self->{'tch'}->errmsg($ecode));
      }
    }
    if(!$self->{'tch'}->close()){
      my $ecode = $self->{'tch'}->ecode();
      printf STDERR ("close error: %s\n", $self->{'tch'}->errmsg($ecode));
    }
=cut
  }
  else{
    my $bdbh=$self->{'bdbw'}->create_write_dbh($self->{'bdb'}, {'hash'=>$self->{'hash'}}) or die "Failed to write to ".$self->{'bdb'}."\n";
    for(my $i=0;$i<$self->{'max'}+1;$i++){
      unless($bdbh->db_put($i, $self->{'put_str'})==0){
        $bdbh->db_close();
        die "Failed to update ".$self->{'bdb'};
      }
    }
    $bdbh->db_close();
  }
  my $end=new Benchmark;
  $self->{'time'}->{'update'}=timestr(timediff($end, $start));
  print "Update\t".$self->{'time'}->{'update'}."\n";
}

sub check_time_to_read(){
  my $self=shift;
  my $str='';
  my $start=new Benchmark;
  if($self->{'type'}){
=pod
    if(!$self->{'tch'}->open($self->{'bdb'}, $self->{'tch'}->OREADER)){
      my $ecode = $self->{'tch'}->ecode();
      printf STDERR ("open error: %s\n", $self->{'tch'}->errmsg($ecode));
    }
    # store records
    for(my $i=0;$i<$self->{'max'}+1;$i++){
      $str=$self->{'tch'}->get($i);
      unless(defined($str)){
        my $ecode = $self->{'tch'}->ecode();
        printf STDERR ("get error: %s\n", $self->{'tch'}->errmsg($ecode));
      }
    }
    if(!$self->{'tch'}->close()){
      my $ecode = $self->{'tch'}->ecode();
      printf STDERR ("close error: %s\n", $self->{'tch'}->errmsg($ecode));
    }
=cut
  }
  else{
    my $bdbh=$self->{'bdbw'}->create_read_dbh($self->{'bdb'}, {'hash'=>$self->{'hash'}}) or die "Cannot open file 'fruit': $!\n";
    for(my $i=0;$i<$self->{'max'}+1;$i++){
      unless($bdbh->db_get($i,$str)==0){
        $bdbh->db_close();
        die "Failed to read ".$self->{'bdb'};
      }
    }
    $bdbh->db_close();
  }
  my $end=new Benchmark;
  $self->{'time'}->{'read'}=timestr(timediff($end, $start));
  print "Read\t".$self->{'time'}->{'read'}."\n";
}

sub report(){
  my $self=shift;
  foreach my $key (reverse sort keys %{$self->{'time'}}){
    print $key."\t";
  }
  print "\n";
  foreach my $key (reverse sort keys %{$self->{'time'}}){
    my $time=$self->{'time'}->{$key}."\t";
    $time=~ s!\swall.*!!;
    print $time."\t";
  }
  print "\n\n";
  my $size=-s $self->{'bdb'};
  print 'Size'."\t".$self->add_digit($size)."\n";
}

sub add_digit{
  my $self=shift;
  my $number=shift;
  my $keta_num=0;
  my $kugiri='';
  my @te=();
  my $pm='';
  my $sita='';
  
  if($number=~ m!^([\+\-])?\d+(\.\d+)?$!){
    $pm=$1 || '';
    $sita=$2 || '';#. must be in this
    unless(length($sita)){
      $sita='';
    }
    if($pm ne ''){
      $number=~ s!^[\+\-]!!;
    }
    if($sita ne ''){
      $number=~ s!\.\d+$!!;
    }
  }
  else{
    return undef;
  }
  
  $keta_num=3;
  $kugiri = ',';
  
  my $m='';
  my $kazu = 0;
  my $len = length($number);
  if($len != 0){
    $kazu = sprintf("%.0f",$len / $keta_num);
    my $amari = $len % $keta_num;
    if(0 > $len - $kazu * $keta_num){
      $kazu--;
    }
    if(0 == $len % $keta_num){
      $kazu--;
      $amari = $keta_num;
    }
    if($kazu != 0){
      for(my $i=0;$i<$kazu+1;$i++){
        if($i == $kazu){
          $te[$i] = substr($number ,-$keta_num * $i - $amari,$amari);
        }
        else{
          $te[$i] = substr($number ,-$keta_num * ($i + 1),$keta_num);
        }
      }
      for(my $i=$kazu;$i > -1;$i--){
        if($i==0){
          $m = $m.$kugiri.$te[$i];
        }
        elsif($i==$kazu){
          $m = $te[$i];
        }
        else{
          $m = $m.$kugiri.$te[$i];
        }
      }
      $number=$m;
    }
  }
  if($pm ne ''){
    $number=$pm.$number;
  }
  if($sita ne ''){
    $number.=$sita;
  }
  return $number;
}

SQLでアクセスしてみる

dbsql test.bdb

と打って、test.bdbというDBファイルを生成しながらSQLコンソールを開く。
そして

create table t(a integer, b string);
insert into t values (1, 'one');
insert into t values (2, 'two');
select * from t;

と一通りSQLが使える事を確認する。


その他言語からの使い方

https://docs.oracle.com/database/bdb181/html/index.html
をご参照下さい。
C、C++、C#、Java、TCL向けの文章が提供されています。