BerkeleyDB(BDB)는 1992년경 캘리포니아 대학 버클리에서 개발한 임베디드 DB다. 유닉스 프로그램에서 보통 DB라 하면 이 버클리 DB를 말할정도로 대중적인 임베디드 데이터베이스이다. 처음엔 UCB(캘리포티아대 버클리)에서 관리하던 버클리DB를, 좀 더 체계적인 관리를 목적으로 1996년 SleepyCat 이라는 법인이 설립되어 이어져오다가, 2006년 2월 오라클에 합병되었다. 사용 하기도 쉽고, 성능도 좋은데다, 버그 없이 잘 구현되어있어 널리 사용되고 있다. 비슷한 임베디드 DB로는 SQLite가 있다. SQLite에 대해서는 윈디하나의 솔라나라: SQLite를 참고하자.
DB라고는 하지만, SQL과 같은 쿼리 언어를 지원하지 않고, 테이블 개념도 없다. 그렇지만 락이나 트랜잭션은 지원하며 Join나 Replication, In-Memory 데이터베이스도 지원한다. BerkeleyDB에 SQL 분석기를 붙이고 인덱스 구성 가능하게 만든게 초기의 MySQL 이다.
BerkeleyDB의 장점중 하나는 이식성이다. C/C++/C#/STL/TCL/JAVA등의 다양한 언어와 다양한 운영체제에서 임베드 되어 사용할 수 있다. C/C++를 지원하기 때문이 이 기반의 다른 언어들, Ruby나 Python, PHP등의 언어도 BerkeleyDB API를 제공하고 있다. (Java 언어는 BerkeleyDB Java로써 지원된다) 최근에는 XML지원을 위한 BerkeleyDB XML도 나와있다.
이 문서에서는 BerkeleyDB의 설치와 간단한 프로그램 예제 그리고 BerkeleyDB를 사용하다 발생하는 문제 해결 방법을 다룬다.
root@wl ~/src # tar xvfz db-5.1.25.tar.gz
root@wl ~/src # cd db-5.1.25
root@wl ~/src/db-5.1.25 # cd build_unix
root@wl ~/src/db-5.1.25/build_unix # ../dist/configure --enable-pthread_api --enable-dtrace --enable-dbm
root@wl ~/src/db-5.1.25/build_unix # make
root@wl ~/src/db-5.1.25/build_unix # make install
root@wl ~/src/db-5.1.25/build_unix # cd /usr/local
root@wl /usr/local # ln -s BerkeleyDB.5.1 db 1)
root@wl /usr/local # vi /etc/profile
...
LD_LIBRARY_PATH=/usr/local/db/lib:$LD_LIBRARY_PATH
1) 버클리DB가 버전마다 디렉토리가 다른 관계로, 솔라나라에서는 /usr/local/db 를 사용해 버클리DB와 연동한다.
명령어
/usr/local/db/bin 디렉토리에 각종 프로그램이 있다.
db_archive: 더이상 사용되지 않는 로그 파일 표시
db_checkpoint: 데이터 베이스 체크포인트및 모니터에 사용되는 데몬. 프로그램 내부에서 쓰레드를 생성해 txn_checkpoint 를 호출하는 방법 대신 사용할 수 있다.
db_deadlock: 대드락이 감지되었을 때, 락을 제거하기 위해 사용하는 데몬
db_dump: 데이터 베이스 파일을 db_load에서 사용할 수 있는 텍스트 형식의 파일로 변환
db_hotbackup: hot backup 또는 hot failover 스냅샷을 생성
db_load: db_dump 로 덤프된 파일을 이용해 데이터 베이스 생성
db_printlog: 데이터베이스 로그 파일을 읽을 수 있도록 변환
db_recover: 트랜잭션의 비정상 종료로 인해 깨진 데이터베이스 파일을 복구. 스냅샷이나 아카이브가 있을때 -c옵션을 붙여 실행하면 쉽게 복구된다.
db_stat: 데이터베이스 파일의 통계를 표시
db_upgrade: 데이터 베이스 파일을 업그레이드
db_verify: 데이터 베이스 파일 무결성 검사
아래의 예제에서는 berkeleydb.db 파일을 예로 든다. 이 파일은 문서 하단의 berkeleydb.c 를 컴파일 해서 실행시키면 얻을 수 있다. (BerkeyelDB는 샘플 데이터 베이스를 제공하지 않는다)
db_stat
root@wl ~ # /usr/local/db/bin/db_stat -d berkeleydb.db
Thu Aug 26 00:00:00 2010 Local time
53162 Btree magic number
9 Btree version number
Little-endian Byte order
Flags
2 Minimum keys per-page
16384 Underlying database page size
4079 Overflow key/data size
1 Number of levels in the tree
3 Number of unique keys in the tree
3 Number of data items in the tree
0 Number of tree internal pages
0 Number of bytes free in tree internal pages (0% ff)
1 Number of tree leaf pages
16298 Number of bytes free in tree leaf pages (0% ff)
0 Number of tree duplicate pages
0 Number of bytes free in tree duplicate pages (0% ff)
0 Number of tree overflow pages
0 Number of bytes free in tree overflow pages (0% ff)
0 Number of empty pages
0 Number of pages on the free list
db_verify
root@wl ~ # /usr/local/db/bin/db_verify berkeleydb.db
db_verify: Page 1: btree leaf page has incorrect level 221
db_verify: Page 1: bad HOFFSET 16605, appears to be 16384
db_verify: Page 1: item order check unsafe: skipping
db_verify: berkeleydb.db: DB_VERIFY_BAD: Database verification failed
db_archive, db_checkpoint, db_recover
주) 테스트 못해봄 ^^
root@wl ~ # /usr/local/db/bin/db_recover
root@wl ~ # ls -alh
-rw-r--r-- 1 root root 32K 8월 26일 00:00 berkeleydb.db
-rw-r----- 1 root root 10M 8월 26일 00:00 log.0000000001
/*
BerkeleyDB Sample Code
WindyHana's Solanara: BerkeleyDB http://www.solanara.net/solanara/berkeleydb
cc -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);
}
root@wl ~ # ./berkeleydb
데이터베이스 환경 오픈
데이터베이스 체크 포인트 쓰레드 시작
데이터베이스 로그 삭제 쓰레드 시작
데이터베이스 생성 (버전: Berkeley DB 5.0.26: (June 25, 2010))
데이터 베이스 오픈(berkeleydb.db): 체크포인트 쓰레드(2) 로그 삭제 쓰레드(3) 성공
데이터 넣기 (key1, val1): 성공
데이터 얻기 (key1): 성공 - val1
데이터 삭제 (키: key1): 성공
삭제된 데이터 얻기(실패해야함): Fail - 데이터베이스 정보/오류: DB->get: DB_NOTFOUND: No matching key/data pair found
샘플 데이터 삽입됨.
데이터베이스 로그파일: /export/home/windy/test/bdb/./log.0000000001
데이터 베이스 닫음.
root@wl ~ #