Emulate secondary index

Hello,

OS: Centos 7.
FDB: 5.1.7.

i’m trying to implement secondary index with C API, but i can’t make it work (entire code at end):

    const std::string key = "try29:";
    for (int i = 0; i < 10; i++)
    {   
        auto [res, ts] = getKeyTS(db, key);
        std::cout << key << " = [" << res << "]; ts = " << std::hex << ts << std::dec << std::endl;
        res += std::to_string(i);
        res.append(",");
        updateKeyTS(db, key, ts, res);
        sleep(1);
    }   

i make a loop and updating the same key 10 times,
actually i got an output:

try29: = []; ts = 0
try29: = []; ts = 0
try29: = [0,]; ts = 5afd78dc
try29: = [1,]; ts = 5afd78dd
try29: = [0,2,]; ts = 5afd78de
try29: = [1,3,]; ts = 5afd78df
try29: = [0,2,4,]; ts = 5afd78e0
try29: = [1,3,5,]; ts = 5afd78e1
try29: = [0,2,4,6,]; ts = 5afd78e2
try29: = [1,3,5,7,]; ts = 5afd78e3

this is not expected, i hope to see one variable collecting all digits.

if i remove sleep - output will be something like that:

try30: = []; ts = 0
try30: = []; ts = 0
try30: = []; ts = 0
try30: = []; ts = 0
try30: = []; ts = 0
try30: = []; ts = 0
try30: = []; ts = 0
try30: = []; ts = 0
try30: = []; ts = 0
try30: = []; ts = 0

this result is trange as well, why it differ from previous one ?

i must be wrong somewhere,
any help ?

PS:
full code

#include <chrono>
#include <cstring>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <sstream>

#include <unistd.h>

#define FDB_API_VERSION 510
#include <fdb_c.h>

/*
 g++ t1.cpp -I/usr/include/foundationdb -lfdb_c -O3 -ggdb -pthread -std=c++17
*/

void checkError(fdb_error_t errorNum)
{
    if(errorNum) { fprintf(stderr, "Error (%d): %s\n", errorNum, fdb_get_error(errorNum)); exit(errorNum); }
}

void waitAndCheckError(FDBFuture *future)
{
    checkError(fdb_future_block_until_ready(future));

    fdb_error_t r = fdb_future_get_error(future);
    if (0 != r)
        checkError(r);
}

void openDatabase(FDBCluster **cluster, FDBDatabase **db)
{
    FDBFuture *clusterFuture = fdb_create_cluster("/etc/foundationdb/fdb.cluster");
    waitAndCheckError(clusterFuture);

    checkError(fdb_future_get_cluster(clusterFuture, cluster));
    fdb_future_destroy(clusterFuture);

    std::cout << "got cluster" << std::endl;

    FDBFuture *dbFuture = fdb_cluster_create_database(*cluster, (const uint8_t*)"DB", 2);
    waitAndCheckError(dbFuture);
    checkError(fdb_future_get_database(dbFuture, db));
    fdb_future_destroy(dbFuture);

    std::cout << "got database" << std::endl;
}

void closeDatabase(FDBCluster *cluster, FDBDatabase *db)
{
    fdb_database_destroy(db);
    fdb_cluster_destroy(cluster);
}

// key + timestamp

std::pair<std::string, time_t> getKeyTS(FDBDatabase *db, const std::string& key)
{
    time_t ts = 0;
    std::string result;

    FDBTransaction *tr;
    checkError(fdb_database_create_transaction(db, &tr));

    std::string end_key = key + "z";    // no `z` char in timestamp

// FDBTransaction* transaction,
// uint8_t const* begin_key_name, int begin_key_name_length, fdb_bool_t begin_or_equal, int begin_offset,
// uint8_t const* end_key_name, int end_key_name_length fdb_bool_t end_or_equal, int end_offset,
// int limit, int target_bytes,
// FDBStreamingMode mode, int iteration,
// fdb_bool_t snapshot, fdb_bool_t reverse
    FDBFuture *getFuture = fdb_transaction_get_range(tr,
                                                     FDB_KEYSEL_FIRST_GREATER_THAN((const uint8_t*)key.c_str(), key.size()),
                                                     FDB_KEYSEL_LAST_LESS_THAN((const uint8_t*)end_key.c_str(), end_key.size()),
                                                     10, 0,
                                                     FDB_STREAMING_MODE_WANT_ALL, 0,
                                                     0, 0);
// fdb_transaction_get_range(tr, FDB_KEYSEL_LAST_LESS_OR_EQUAL(keys[0], keySize), FDB_KEYSEL_LAST_LESS_OR_EQUAL(keys[numKeys], keySize),
// numKeys, 0, 0, 1, 0, 0);

    waitAndCheckError(getFuture);

    fdb_bool_t valuePresent;
    FDBKeyValue const* res;
    int count = 0;
    fdb_bool_t more;
    checkError(fdb_future_get_keyvalue_array(getFuture, &res, &count, &more));
    //std::cout << "found " << count << " entries" << std::endl;
    if (count > 0)
    {
        std::string key((const char*)res->key, res->key_length);
        if (key.size() > 8)
            ts = std::strtol(key.c_str() + key.size() - 8, 0, 16);
        result.assign((const char*)res->value, res->value_length);
    }

    fdb_future_destroy(getFuture);
    fdb_transaction_destroy(tr);

    return std::make_pair(result, ts);
}

std::pair<std::string, std::string> makeKeyTS(const std::string& key, const time_t ts = ::time(0))
{
    std::stringstream stream;
    stream << std::hex << ts;
    const std::string hextime = stream.str();
    const std::string norm_key = key + hextime;
    const std::string rev_key  = hextime + key;
    return std::make_pair(norm_key, rev_key);
}

void updateKeyTS(FDBDatabase *db, const std::string& key, const time_t ts, const std::string& value)
{
    FDBTransaction *tr;
    checkError(fdb_database_create_transaction(db, &tr));

    // rm old
    {
        auto [norm_key, rev_key] = makeKeyTS(key, ts);
        fdb_transaction_clear(tr, (const uint8_t*)norm_key.c_str(), (int)norm_key.size());
        fdb_transaction_clear(tr, (const uint8_t*)rev_key.c_str(), (int)rev_key.size());
    }

    // put new
    {
        auto [norm_key, rev_key] = makeKeyTS(key);
        fdb_transaction_set(tr,
                            (const uint8_t*)norm_key.c_str(), (int)norm_key.size(),
                            (const uint8_t*)value.c_str(), (int)value.size());
        fdb_transaction_set(tr,
                            (const uint8_t*)rev_key.c_str(), (int)rev_key.size(),
                            (const uint8_t*)0, 0);
    }

    FDBFuture *getFuture = fdb_transaction_commit(tr);
    waitAndCheckError(getFuture);
    fdb_future_destroy(getFuture);
    fdb_transaction_destroy(tr);

}

void f3(void)
{
    FDBCluster *cluster;
    FDBDatabase *db;
    openDatabase(&cluster, &db);
    const std::string key = "try30:";

    for (int i = 0; i < 10; i++)
    {
        auto [res, ts] = getKeyTS(db, key);
        std::cout << key << " = [" << res << "]; ts = " << std::hex << ts << std::dec << std::endl;
        res += std::to_string(i);
        res.append(",");
        updateKeyTS(db, key, ts, res);
        sleep(1);
    }

    closeDatabase(cluster, db);
}

int main(void)
{
    checkError(fdb_select_api_version(FDB_API_VERSION));
    checkError(fdb_setup_network());

    std::thread fdbt([]{
        checkError(fdb_run_network());
    });
    std::cerr << "sleeping" << std::endl;
    sleep(1);

    f3();

    std::cerr << "stopping" << std::endl;
    checkError(fdb_stop_network());
    std::cerr << "joining" << std::endl;
    fdbt.join();

    return 0;
}

At a glance, guessing your problem is in your call to fdb_transaction_get_range(). You are passing two key selectors, which are first resolved to keys that are present in the database, and then those keys define a range [begin,end).

In your case, after the first iteration, it looks like you are trying to read a key like “try30:12345678”. But the range you are defining will resolve to [“try30:12345678”,“try30:12345678”) which is by definition empty. (The same key is both the first greater than “try30:” and the last less than “try30:z”.)

Haven’t read your code well enough to understand the logic of your index, but you need to use key selectors to describe the first key that is not part of your range as the end point of your range read (either as an offset from the beginning, or as the first greater than/greater than or equal to a constructed key.

i have try with FDB_KEYSEL_FIRST_GREATER_OR_EQUAL/FDB_KEYSEL_LAST_LESS_OR_EQUAL.

FDB_KEYSEL_FIRST_GREATER_OR_EQUAL((const uint8_t*)key.c_str(), key.size()),
FDB_KEYSEL_LAST_LESS_OR_EQUAL((const uint8_t*)end_key.c_str(), end_key.size()),

but nothing changed.

That’s because FDB_KEYSEL_LAST_LESS_OR_EQUAL still resolves to the wrong key. You initially will have no keys present that begin with “try30:” but after your first iteration will have inserted one, e.g. “try30:12345678”. The first key greater than “try30:” is “try30:12345678”, so your range read will begin at that key. The last key less than or eqaul to “try30:z” is “try30:12345678” (the same key!), so your range read will end at that key as well.

Remember that all ranges are inclusive/exclusive (or open/closed), so the range is empty, and you don’t successfully read the key you’re looking for.

You want:

FDB_KEYSEL_FIRST_GREATER_THAN((const uint8_t*)key.c_str(), key.size()),
FDB_KEYSEL_FIRST_GREATER_THAN((const uint8_t*)end_key.c_str(), end_key.size()),

The first key greater than “try30:z” may actually exist, or there may be nothing (in which case the key selector resolves to “\xFF”), but it doesn’t matter. Because the end of the range is excluded from the range, and we know there are no other keys between “try30:z” and whatever it does resolve to, you’ll only read the keys you were looking for.

1 Like

thanx a lot! it’s working as expected now.