diff --git a/Makefile b/Makefile index bc368e1..02a5906 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ CFLAGS= -std=c99 -D_POSIX_C_SOURCE=200809L -Wall -Wextra -Wpedantic -Wnull-dereference -pthread LDFLAGS= -lpthread -all: tests coverage cppcheck valgrind example +all: tests example performance tests: logdb.h tests.c $(CC) -g $(CFLAGS) -DRUNNING_ON_VALGRIND -o tests tests.c $(LDFLAGS) diff --git a/README.md b/README.md index e960877..8599418 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,20 @@ # logdb A simple log-structured database. +It is a header-only C embeded database with no dependencies. -It is a header-only C embeded database with no dependencies. -The logdb goal is to cover the following case: +Logdb is a simple database with the following characteristics: -* Need to persist sequentially ordered data -* Most operations are write type -* Data is rarely read or searched -* Allow to revert last entries (rollback) -* Eventually purge obsolete entries (purge) +* Variable length record type +* Records uniquely identified by a sequential number (seqnum) +* Records are indexed by timestamp (monotonic non-decreasing field) +* There are no other indexes other than seqnum and timestamp. +* Records can be appended, read, and searched +* Records can not be updated nor deleted +* Allows to revert last entries (rollback) +* Allows to remove obsolete entries (purge) +* Read-write concurrency supported (multi-thread) +* Automatic data recovery on catastrofic event * Minimal memory footprint Use cases: @@ -19,19 +24,8 @@ Use cases: ## Description -Logdb is a simple database with the following characteristics: - -* Records have variable length (non-fixed record size) -* Record identifier is a sequential number -* Record are indexed by timestamp (monotonic non-decreasing field) -* Only append function is supported (no update, no delete) -* Just after insertion data is flushed to disk (no delayed writes) -* Automatic data recovery on catastrofic event -* Records can be read (retrieved by seqnum) -* Records can be searched by id (seqnum) -* Records can be searched by timestamp -* Rollback means to remove X records from top -* Can be purged (removing X records from bottom) +Basically, logdb is an append-only data file (\*.dat) with an index file (\*.idx) used to speed up lookups. No complex data structures, no sofisticated algorithms, only basic file +access. We rely on the filesystem cache (managed by the operating system) to ensure read performance. ### dat file format @@ -58,7 +52,31 @@ Logdb is a simple database with the following characteristics: Drop off [`logdb.h`](logdb.h) in your project and start using it. -See [`example.c`](example.c). +``` +#define LDB_IMPL +#include "logdb.h" + +ldb_db_t db = {0}; +ldb_entry_t wentries[MAX_ENTRIES] = {{0}}; +ldb_entry_t rentries[MAX_ENTRIES] = {{0}}; + +ldb_open("/my/directory", "example", &db, true); + +// on write-thread +fill_entries(wentries, MAX_ENTRIES); +ldb_append(&db, wentries, MAX_ENTRIES, NULL); + +// on read-thread +ldb_read(&db, 1, rentries, MAX_ENTRIES, NULL); +process_entries(rentries, MAX_ENTRIES); + +ldb_free_entries(rentries, MAX_ENTRIES); +ldb_close(&db); +``` + +Read functions documentation in `logdb.h`.
+See [`example.c`](example.c) for basic function usage.
+See [`performance.c`](performance.c) for concurrent usage. ## Contributors diff --git a/example.c b/example.c index 0c3277a..2262f3d 100644 --- a/example.c +++ b/example.c @@ -57,8 +57,8 @@ int main(void) int rc = 0; // don't mix read and write entries, they have distinct memory-owner - ldb_entry_t wentries[MAX_ENTRIES] = {0}; - ldb_entry_t rentries[MAX_ENTRIES] = {0}; + ldb_entry_t wentries[MAX_ENTRIES] = {{0}}; + ldb_entry_t rentries[MAX_ENTRIES] = {{0}}; ldb_entry_t wentry = {0}; ldb_entry_t rentry = {0}; diff --git a/logdb.h b/logdb.h index 4524888..472bc59 100644 --- a/logdb.h +++ b/logdb.h @@ -35,30 +35,28 @@ SOFTWARE. /** * Logdb is a simple database with the following characteristics: - * - Records have variable length (non-fixed record size) - * - Record identifier is a sequential number - * - Record are indexed by timestamp (monotonic non-decreasing field) - * - Only append function is supported (no update, no delete) - * - Just after insertion data is flushed to disk (no delayed writes) - * - Automatic data recovery on catastrofic event - * - Records can be read (retrieved by seqnum) - * - Records can be searched by id (seqnum) - * - Records can be searched by timestamp - * - Rollback means to remove X records from top - * - Can be purged (removing X records from bottom) - * - * Logdb is intended in the following case: - * - Need to persist sequentially ordered data - * - Most operations are write type - * - Data is rarely read or searched + * - Variable length record type + * - Records uniquely identified by a sequential number (seqnum) + * - Records are indexed by timestamp (monotonic non-decreasing field) + * - There are no other indexes other than seqnum and timestamp. + * - Records can be appended, read, and searched + * - Records can not be updated nor deleted * - Allows to revert last entries (rollback) - * - Eventually purge obsolete entries (purge) + * - Allows to remove obsolete entries (purge) + * - Read-write concurrency supported (multi-thread) + * - Automatic data recovery on catastrofic event * - Minimal memory footprint * * Use cases: * - Storage engine in a raft library (fault-tolerant distributed applications) * - Storage engine for journal-based apps * + * Basically, logdb is an append-only data file (\*.dat) with + * an index file (\*.idx) used to speed up lookups. No complex + * data structures, no sofisticated algorithms, only basic file + * access. We rely on the filesystem cache (managed by the operating + * system) to ensure read performance. + * * dat file format * --------------- * @@ -74,7 +72,6 @@ SOFTWARE. * etc checksum1 checksum2 * length1 length2 * - * * idx file format * --------------- * @@ -126,8 +123,8 @@ SOFTWARE. * └ search() R R */ -#define LDB_VERSION_MAJOR 0 -#define LDB_VERSION_MINOR 5 +#define LDB_VERSION_MAJOR 1 +#define LDB_VERSION_MINOR 0 #define LDB_VERSION_PATCH 0 #define LDB_OK 0 @@ -474,9 +471,12 @@ typedef struct { #define LDB_INLINE /**/ #endif -#if __has_attribute(__fallthrough__) - # define fallthrough __attribute__((__fallthrough__)) -#else +#if defined __has_attribute + #if __has_attribute(__fallthrough__) + # define fallthrough __attribute__((__fallthrough__)) + #endif +#endif +#ifndef fallthrough # define fallthrough do {} while (0) /* fallthrough */ #endif diff --git a/performance.c b/performance.c index 184dc8b..def1f56 100644 --- a/performance.c +++ b/performance.c @@ -142,7 +142,9 @@ static void * run_write(void *args) uint64_t time0 = ldb_get_millis(); size_t num = 0; - // all records have always the same content + // Logdb supports records of variable length. + // In this case we use fixed-length records filled with 0's + // to avoid to deal with memory alloc and fill it with random content. for (size_t i = 0; i < num_entries; i++) { entries[i].metadata_len = 0; entries[i].metadata = NULL; diff --git a/tests.c b/tests.c index cac3e09..a797094 100644 --- a/tests.c +++ b/tests.c @@ -37,10 +37,26 @@ void append_entries(ldb_db_t *db, uint64_t seqnum1, uint64_t seqnum2) } } -void test_version(void) { +void test_version(void) +{ const char *version = ldb_version(); TEST_ASSERT(version != NULL); - TEST_CHECK(strcmp(version, "0.5.0") == 0); + + size_t len = strlen(version); + TEST_ASSERT(len >= 5); + + TEST_ASSERT(version[0] != '.'); + TEST_ASSERT(version[len-1] != '.'); + + size_t num_dots = 0; + for (size_t i = 0; i < len; i++) { + if (version[i] == '.') + num_dots++; + else if (!isdigit(version[i])) + TEST_ASSERT(false); + } + + TEST_CHECK(num_dots == 2); } void test_strerror(void) @@ -991,7 +1007,7 @@ bool check_entry(ldb_entry_t *entry, uint64_t seqnum, const char *metadata, cons void test_read_invalid_args(void) { ldb_db_t db = {0}; - ldb_entry_t entries[3] = {0}; + ldb_entry_t entries[3] = {{0}}; TEST_ASSERT(ldb_read(NULL, 1, entries, 3, NULL) == LDB_ERR_ARG); TEST_ASSERT(ldb_read(&db, 1, NULL, 3, NULL) == LDB_ERR_ARG); @@ -1002,7 +1018,7 @@ void test_read_invalid_args(void) void test_read_empty_db(void) { ldb_db_t db = {0}; - ldb_entry_t entries[3] = {0}; + ldb_entry_t entries[3] = {{0}}; size_t num = 10; remove("test.dat"); @@ -1025,7 +1041,7 @@ void test_read_empty_db(void) void test_read_nominal_case(void) { ldb_db_t db = {0}; - ldb_entry_t entries[10] = {0}; + ldb_entry_t entries[10] = {{0}}; size_t num = 0; remove("test.dat");