Building

Akumuli uses CMake build system. CMake 2.8 or higher is required. Dependencies:

  • Boost libraries version 1.52 or higher.
  • Apache Portable Runtime (libapr).

All dependencies can be installed in ubuntu 14.04 using prerequisites.sh script. When all dependencies where installed, akumuli can be built using these commands:

> cd build_dir
> cmake path_to_sources -G "Unix Makefiles"
> make

Working with library

Akumuli headers are located in include directory. Header “akumuli.h” contains all function definitions, “akumuli_def.h” - macrodefinitions, “version.h” - version definitions and “config.h” - configuration structure definition.

All Akumuli functions start with aku prefix (macros starts with the same uppercase prefix). You need to call aku_initialize first, before you call any other function.

Creating new database

apr_status_t result = aku_create_database("test", "/tmp", "/tmp", 8, 
                                          nullptr, nullptr, nullptr, nullptr);

if (result != APR_SUCCESS) { exit(1); }

This function will create new database instance on disk. First argument of the call is database name, second - path to directory, third - path to volumes directory. In this case database will be called test and all data will be placed in /tmp directory (it must be created beforehand). Fourth parameter is more interesting, this is database size. In this example size is eight, this means that eight volumes will be created. Each volume is 4Gb and resulting database size will be 32Gb. After call in tmp directory 8 files with “volume” extension will be created, each is 4Gb in size. Disk space will be reserved beforehand, during call to aku_create_database.

Last four parameters can be used to specify optional configuration parameters, we don’t need it at this moment.

This call returns APR status, it can be compared to APR_SUCCESS constant and examined with libapr function apr_strerror to get human readable error message.

Now we can open this database and do something useful with it.

aku_FineTuneParams = {
  0, 1000, 0x1000000, nullptr
};
aku_Database* db = aku_open_database("/tmp/test.akumuli", params);
// check error!
aku_Status status = aku_open_status(db);
if (status != AKU_SUCCESS) {
  aku_close_database(db);
  exit(1);
}

Function aku_open_database can open database that already exists. Structure params must contain some useful parameters, we interested in second one - 1000, this is a window size. After call to aku_open_database we can check its state with aku_open_status this function returns status code for the open operation. Variable db will always contain pointer to database instance, no matter what it is, even if file doesn’t exist. In this case we can check for error using aku_open_error function.

Writing data

Let’s write some data to our new database!

for(uint64_t i = 0; i < 1000000; i++) {
  aku_MemRange memr;
  memr.address = (void*)&1;
  memr.length = sizeof(1);
  aku_Status status = aku_write(db, 42, i, memr);
  if (status != AKU_SUCCESS && status != AKU_EBUSY) {
    exit(1);
  }
  if (status == AKU_EBUSY) {
    status = aku_write(db, 42, i, memr);
    if (status != AKU_SUCCESS) {
      exit(1);
    }
  }
}

This code will write one million values to the database. First parameter to aku_write function is previously opened database instance, second parameter is a parameter id (sequence id), third parameter i is a timestamp, and last memr is memory range that points to payload - useful data that we can send to database.

If we get AKU_EBUSY error - we need to rewrite the data, but only ones! This happens when we try to write the data while merging or syncing.

Reading

Let’s build a query and run it.

aku_ParamId params[] = {42};
aku_TimeStamp begin = 1000000ul;
aku_TimeStamp end = 0ul;
aku_SelectQuery* query = aku_make_select_query( begin
                                              , end
                                              , 1
                                              , params);
aku_Cursor* cursor = aku_select(db, query);

We introduce three new variables. The first one - params is a list of parameter ids that we want to read, in this case we need to send only one parameter id. Second and third parameters define time range. Note that begin variable is larger that end. This is because we want to read the data in reverse direction, from larger timestamps to lower.

This code doesn’t read data from disk, it just creates cursor object. We can read data from disk using this cursor. Let’s try to actually read data!

const int NUM_ELEMENTS = 0x100;
while(!aku_cursor_is_done(cursor)) {
    aku_Status err = AKU_SUCCESS;
    if (aku_cursor_is_error(cursor, &err)) {
        std::cout << aku_error_message(err) << std::endl;
        return false;
    }
    aku_TimeStamp timestamps[NUM_ELEMENTS];
    aku_PData pointers[NUM_ELEMENTS];
    uint32_t lengths[NUM_ELEMENTS];
    int n_entries = aku_cursor_read_columns(cursor, timestamps, 
                                            nullptr, pointers, 
                                            lengths, NUM_ELEMENTS);
    for (int i = 0; i < n_entries; i++) {
        uint64_t value = *static_cast<const uint64_t*>(pointers[i]);
        assert(timestamp[i] == value);
    }
}

In first line we check that there is some data to read using aku_cursor_is_done function. After that we checking cursor for errors using aku_cursor_is_error function. If it returns true, than we get an error that can be converted to human readable form using aku_error_message function.

After that we can read data to three arrays - timestamps, pointers and lengths. Note that there is no array for parameter ids! We pass nullptr to the function instead of it because we don’t need it. All parameter ids will be the same anyway. After call to aku_cursor_read_columns this arrays will be filled with data elements ordered by timestamp (in reverse order). We can compare timestamp to stored value to see that everything is OK.

After that we need to close cursor and database.

aku_close_cursor(cursor);
aku_close_database(db);

And finally we can remove the database completely.

aku_remove_database("/tmp/test.akumuli", nullptr);