BerkeleyDB - 윈디하나의 솔라나라

목차

개요

설치

db-*.gz 파일은 Oracle Berkeley DB Downloads에서 다운로드 할 수 있다. 오라클 계정으로 로그인이 필요하지만 가입은 무료다. 설치에 대한 설명은 Chapter 7. Building Berkeley DB for UNIX/POSIX을 참고하자.
windy@wl ~/src $ tar xvfz db-18.1.32.tar.gz
windy@wl ~/src $ cd db-18.1.32
windy@wl ~/src/db-18.1.32 $ cd build_unix
windy@wl ~/src/db-18.1.32/build_unix $ ../dist/configure \
  --enable-dtrace \
  --enable-dbm \
  --enable-sql \
  --enable-o_direct \
  --enable-localization \
  --enable-dtrace \
  --enable-umrw \
  --enable-cxx \
  --enable-perfmon-statistics \
  --enable-atomicfileread \
  --enable-sql_compat \
  --enable-perfmon-statistics \
  CFLAGS="-I/usr/local/ssl/include -L/usr/local/ssl/lib -DSQLITE_DIRECT_OVERFLOW_READ -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_RTREE -DSQLITE_USE_ALLOCA -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_RBU" 1)
windy@wl ~/src/db-18.1.32/build_unix $ make
windy@wl ~/src/db-18.1.32/build_unix $ sudo make install
windy@wl ~/src/db-18.1.32/build_unix $ cd /usr/local
windy@wl /usr/local $ sudo ln -s BerkeleyDB.18.1 db 2)
windy@wl /usr/local $ vi /etc/profile
...
LD_LIBRARY_PATH=/usr/local/db/lib:$LD_LIBRARY_PATH
1) 64비트로 설치하려면 CFLAGS="-m64" CXXFLAGS="-m64"를 붙여 컴파일한다.
2) 버클리DB가 버전마다 디렉토리가 다른 관계로, 솔라나라에서는 /usr/local/db를 사용해 버클리DB와 연동한다.

명령어

C 프로그램 예제

기본예제

Oracle Berkeley DB 12c Release 1문서의 Getting Started with Berkeley DB의 내용을 일부 발췌했으며 대부분의 코드는 매뉴얼에서 가져왔다. Berkeley DB Tutorial and Reference Guide, Version 4.1.24도 참고하면 좋겠다.
berkeleydb.c
(6,222 바이트)
/*
	BerkeleyDB Sample Code
	WindyHana's Solanara: BerkeleyDB http://www.solanara.net/solanara/berkeleydb
	cc -R/usr/local/db/lib -L/usr/local/db/lib -ldb -I/usr/local/db/include -o berkeleydb berkeleydb.c
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "db.h"
#define  DBFILE "berkeleydb.db"
#define  ENV_DIRECTORY "./"

DB_ENV* dbenv;
DB* dbp;
//DB_MPOOL_STAT* dbmpoolstat;

int putData(char * key, char * val) {
    DBT dbkey, dbval;

    memset(&dbkey, 0, sizeof(dbkey));
    memset(&dbval, 0, sizeof(dbval));
    dbkey.data = key;
    dbkey.size = sizeof(key);
    dbval.data = val;
    dbval.size = sizeof(val);

	return dbp->put(dbp, NULL, &dbkey, &dbval, 0);
}

int genSampleData() {
    putData("apple", "red");
    putData("banana", "yellow");
    putData("grape", "purple");
}

void env_open(DB_ENV **dbenvp) {
	DB_ENV *dbenv;
	int flags;
	int ret;

	/* Create the environment handle. */
	if ((ret = db_env_create(&dbenv, 0)) != 0) {
		fprintf(stderr, "txnapp: db_env_create: %s\n", db_strerror(ret));
		exit (1);
	}

	/* Set up error handling. */
	dbenv->set_errpfx(dbenv, "데이터베이스 정보/오류");

	/* Do deadlock detection internally. */
	if ((ret = dbenv->set_lk_detect(dbenv, DB_LOCK_DEFAULT)) != 0) {
		dbenv->err(dbenv, ret, "set_lk_detect: DB_LOCK_DEFAULT");
		exit (1);
	}

	/* * Open a transactional environment: * create if it doesn't exist * free-threaded handle * run recovery * read/write owner only */
	
	flags = DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | DB_RECOVER | DB_THREAD;
	if ((ret = dbenv->open(dbenv, ENV_DIRECTORY, flags, S_IRUSR | S_IWUSR)) != 0) {
		dbenv->err(dbenv, ret, "dbenv->open: %s", ENV_DIRECTORY);
		exit (1);
	}

	*dbenvp = dbenv;
}

void * checkpoint_thread(void *arg) {
	DB_ENV *dbenv;
	int ret;
	pthread_t ptid;
	
	dbenv = arg;
	printf("체크포인트 쓰레드(%lu) ", (u_long)pthread_self());

	/* Checkpoint once a minute. */
	for (;; sleep(60)) if ((ret = dbenv->txn_checkpoint(dbenv, 0, 0, 0)) != 0) {
		dbenv->err(dbenv, ret, "checkpoint thread");
		exit (1);
	}
}

/* 필요 없는 로그 파일 삭제 */
void * logfile_thread(void *arg) {
	DB_ENV *dbenv;
	int ret;
	char **begin, **list;
	dbenv = arg;
	printf("로그 삭제 쓰레드(%lu) ", (u_long)pthread_self());

	/* Check once every 5 minutes. */
	for (;; sleep(300)) {
		/* Get the list of log files. */
		if ((ret = dbenv->log_archive(dbenv, &list, DB_ARCH_ABS)) != 0) {
			dbenv->err(dbenv, ret, "DB_ENV->log_archive");
			exit (1);
		}
		/* Remove the log files. */
		if (list != NULL) {
			for (begin = list; *list != NULL; ++list) {
				if ((ret = remove(*list)) != 0) {
					dbenv->err(dbenv, ret, "remove %s", *list);
					exit (1);
				}
			}
			free (begin);
		} 
	}
}

/* 사용하는 로그 파일 보임 */
void log_archlist(DB_ENV *dbenv)
{
	int ret;
	char **begin, **list;

	/* Get the list of database files. */
	if ((ret = dbenv->log_archive(dbenv, &list, DB_ARCH_ABS | DB_ARCH_DATA)) != 0) {
		dbenv->err(dbenv, ret, "DB_ENV->log_archive: DB_ARCH_DATA");
		exit (1);
	}

	if (list != NULL) {
		for (begin = list; *list != NULL; ++list) printf("database file: %s\n", *list);
		free (begin);
	}

	/* Get the list of log files. */
	if ((ret = dbenv->log_archive(dbenv, &list, DB_ARCH_ABS | DB_ARCH_LOG)) != 0) {
		dbenv->err(dbenv, ret, "DB_ENV->log_archive: DB_ARCH_LOG");
		exit (1);
	}

	if (list != NULL) {
		for (begin = list; *list != NULL; ++list) printf("데이터베이스 로그파일: %s\n", *list);
		free (begin);
	}
}

int main() {
    DBT key, val;
    int ret;
    pthread_t ptid;

    printf("데이터베이스 환경 오픈\n");
	env_open(&dbenv);

    printf("데이터베이스 체크 포인트 쓰레드 시작\n");
	if ((ret = pthread_create(&ptid, NULL, checkpoint_thread, (void *)dbenv)) != 0) {
		fprintf(stderr, "checkpoint: failed spawning checkpoint thread: %s\n", db_strerror(ret));
		exit (1);
	}

    fflush(stdout); printf("데이터베이스 로그 삭제 쓰레드 시작\n");
	if ((ret = pthread_create(&ptid, NULL, logfile_thread, (void *)dbenv)) != 0) {
		fprintf(stderr, "txnapp: failed spawning log file removal thread: %s\n", db_strerror(ret));
		exit (1);
	}

    printf("데이터베이스 생성 (버전: %s)\n", DB_VERSION_STRING);
    if ((ret = db_create(&dbp, dbenv, 0)) != 0) {
        fprintf(stderr, "db_create: %s\n", db_strerror(ret));
        exit(1);
    }

    printf("데이터 베이스 오픈(%s): ", DBFILE);
    if ((ret = dbp->open(dbp, NULL, DBFILE, NULL, DB_BTREE, DB_CREATE, 0664)) != 0) {
        printf("Fail - "); fflush(stdout); dbp->err(dbp, ret, "db->open(%s)", DBFILE);
        goto done;
    }
    printf("성공\n");
    
    memset(&key, 0, sizeof(key));
    memset(&val, 0, sizeof(val));
    key.data = "key1";
    key.size = sizeof("key1");
    val.data = "val1";
    val.size = sizeof("val1");
    printf("데이터 넣기 (%s, %s): ", key.data, val.data);
    if ((ret = dbp->put(dbp, NULL, &key, &val, 0)) != 0) {
        printf("Fail - "); fflush(stdout); dbp->err(dbp, ret, "db->put");
        goto done;
    }
    printf("성공\n");
    
    memset(&val, 0, sizeof(val));
    printf("데이터 얻기 (%s): ", key.data);

    key.flags = DB_DBT_REALLOC;
    val.flags = DB_DBT_REALLOC;
	if ((ret = dbp->get(dbp, NULL, &key, &val, 0)) != 0) {
		printf("Fail - "); fflush(stdout); dbp->err(dbp, ret, "db->get");
		goto done;
	}
	printf("성공 - %s\n", val.data);
                 
	printf("데이터 삭제 (키: %s): ", key.data);
	if ((ret = dbp->del(dbp, NULL, &key, 0)) != 0) {
		printf("Fail - "); fflush(stdout); dbp->err(dbp, ret, "DB->del");
		goto done;
	}
	printf("성공\n");

	printf("삭제된 데이터 얻기(실패해야함): ", key.data);
	if ((ret = dbp->get(dbp, NULL, &key, &val, 0)) != 0) {
		printf("Fail - "); fflush(stdout); dbp->err(dbp, ret, "DB->get");
		goto done;
	}
	printf("성공\n"); // Cannot be reached.

done:
	genSampleData();
	printf("샘플 데이터 삽입됨.\n");
	log_archlist(dbenv);

    if (dbp != NULL) { ret = dbp->close(dbp, 0); }
    if (dbenv != NULL) { ret = dbenv->close(dbenv, 0); }
	printf("데이터 베이스 닫음.\n");
    exit(ret);
}
# ./berkeleydb
데이터베이스 환경 오픈
데이터베이스 체크 포인트 쓰레드 시작
데이터베이스 로그 삭제 쓰레드 시작
데이터베이스 생성 (버전: Berkeley DB 6.2.32: (April  5, 2017))
로그 삭제 쓰레드(3) 데이터 베이스 오픈(berkeleydb.db): 체크포인트 쓰레드(2) 성공
데이터 넣기 (key1, val1): 성공
데이터 얻기 (key1): 성공 - val1
데이터 삭제 (키: key1): 성공
삭제된 데이터 얻기(실패해야함): Fail - 데이터베이스 정보/오류: DB->get: BDB0073 DB_NOTFOUND: No matching key/data pair found
샘플 데이터 삽입됨.
데이터베이스 로그파일: /root/./log.0000000001
데이터 베이스 닫음.
# ls -al
# ll
...
-rw-------   1 windy    staff       184K  9월 26일  14:57 __db.001
-rw-------   1 windy    staff        32K  9월 26일  14:57 __db.002
-rw-------   1 windy    staff        48K  9월 26일  14:57 __db.003
-rwxr-xr-x   1 windy    staff        18K  9월 26일  14:55 berkeleydb*
-rw-r--r--   1 windy    staff       6.1K 2014년  9월 15일 berkeleydb.c
-rw-r--r--   1 windy    staff        32K  9월 26일  14:57 berkeleydb.db
-rw-------   1 windy    staff        10M  9월 26일  14:57 log.0000000001

SQLite

SQLite 에 대한 대체 라이브러리(Drop-in Replacement)를 제공한다. (BerkeleyDB에서 SQL을 구현할때 SQLite 와 호환되도록 만든것으로 알고 있다) 이를 이용해 SQLite 로 작성된 프로그램은 소스 수정 없이 BerkeleyDB를 사용하도록 할 수 있다. 예제 소스코드는 윈디하나의 솔라나라: SQLite - C 예제를 수정없이 사용했다.
windy@wl ~ $ wget http://www.solanara.net/contents/includes/sqlite_init.sql
windy@wl ~ $ /usr/local/db/bin/sqlite3 userdb.sqlite ".read sqlite_init.sql" 1)
SQLite 3.18.2
0|0|0|SEARCH TABLE authinfo AS a USING COVERING INDEX sqlite_autoindex_authinfo_1 (userid=?)
0|1|1|SEARCH TABLE userinfo AS u USING INTEGER PRIMARY KEY (rowid=?)
root|루트|010-111-1111|1.0|2019-05-16 01:59:28
windy|윈디하나|010-000-0000|1.0|2019-05-16 01:59:28
2019-05-16 01:59:28|2
Error: near line 49: no such table: dbstat
windy@wl ~ $ wget http://www.solanara.net/contents/includes/sqlite.c
windy@wl ~ $ cc -R/usr/local/db/lib -L/usr/local/db/lib -lsqlite3 -lmd5 -m64 -lnsl -lsocket -I/usr/local/db/include -o sqlite sqlite.c 2)
windy@wl ~ $ ./sqlite
SQLite 3.18.2
SQLite Lib 3.18.2

SQLITE_LIMIT_LENGTH = 1000000000
SQLITE_LIMIT_COLUMN = 2000
SQLITE_LIMIT_EXPR_DEPTH = 1000
SQLITE_LIMIT_COMPOUND_SELECT = 500
SQLITE_LIMIT_VDBE_OP = 250000000
SQLITE_LIMIT_FUNCTION_ARG = 127
SQLITE_LIMIT_ATTACHED = 10
SQLITE_LIMIT_LIKE_PATTERN_LENGTH = 50000
SQLITE_LIMIT_VARIABLE_NUMBER = 999
SQLITE_LIMIT_TRIGGER_DEPTH = 1000

SQL: select md5('한글') "한글(UTF-8)MD5해시"
        한글(UTF-8)MD5해시
1       52b8c54ab4ea672ee6cdfdfef0a31db4
.......SQL: select first(username) fun from userinfo
        fun
1       루트
.SQL: select * from userlist
        userid  username        tel1    grade   rdate
1       root    루트    010-111-1111    1.0     2019-05-16 01:59:28
.2      windy   윈디하나        010-000-0000    1.0     2019-05-16 01:59:28
1) BerkeleyDB에 설치된 SQLite 라이브러리가 아닌 시스템에 설치된 SQLite 라이브러리가 로드된 경우 아래와 같이 SQLite header and source version mismatch 메시지가 나온다
# /usr/local/db/bin/sqlite3
SQLite header and source version mismatch
2019-04-16 19:49:53 884b4b7e502b4e991677b53971277adfaf0a04a284f8e483e2553d0f83156b50
2017-06-17 09:59:36 036ebf729e4b21035d7f4f8e35a6f705e6bf99887889e2dc14ebf2242e7930dd
이런 경우 LD_LIBRARY_PATH에 정의되어있는 라이브러리 검색 디렉토리 순서를, 시스템에 설치되어있는 SQLite 라이브러리 디렉토리 보다 BerkeleyDB 에 설치된 SQLite 라이브러리 경로를 우선하게 조절하면 해결할 수 있다.
2) SQLite 는 소켓 라이브러리를 필요로 하지 않지만, BerlekenyDB 는 기본적으로 소켓 라이브러리를 필요로 한다. 소스는 수정 안해도 되지만, 빌드시 소켓 라이브러리 부분은 추가해야 한다.
3) SQLite 헤더는 3.18.2 이지만 SQLite Lib 는 시스템에 설치된 라이브러리 버전이 표시될 수 있다. 따라서 헤더 버전과 라이브러리 버전이 다를 수 있다. 윈디하나의 솔라나라: SQLite - 설치대로 설치하고 실행해보면 달리 나올 것이다.
RSS ATOM XHTML 5 CSS3