Monday, February 4, 2013

Accessing Couchbase from PHP on your Mac!


Given that I've already blogged about how to use PHP from IIS on Windows I figured I should do another blog post for people using Mac OSX. Luckily for us Mac OSX ships with both Apache2 and PHP, so its fairly easy to start using it.

Our PHP extension is built on top of libcouchbase, so the first thing we need to do is to install that. I guess most Mac users are already using homebrew to get stuff onto their Mac so installing libcouchbase is simply a matter of:

trond@ok ~> brew install libcouchbase

The next thing we need to do is to download the PHP extension and install it somewhere
locally. Personally I do like to put such modules in it's own directory, so I'm going to use /opt/couchbase/lib for now:

trond@ok ~> cd /tmp
trond@ok /tmp> wget http://packages.couchbase.com/clients/php/php-ext-couchbase-1.1.2-macosx-x86_64.tar.gz
trond@ok tmp> tar xfz php-ext-couchbase-1.1.2-macosx-x86_64.tar.gz
trond@ok /tmp> sudo mkdir -p /opt/couchbase/lib
trond@ok /tmp> sudo cp php-ext-couchbase/couchbase.so /opt/couchbase/lib

With the plugin in place we need to tell php to load it. To do so add the following line in /etc/php.ini:

extension=/opt/couchbase/lib/couchbase.so

This should be enough to use Couchbase from PHP, so lets go ahead and verify that it works:

trond@ok ~> php -i | grep couchbase
couchbase
couchbase support => enabled
couchbase.compression_factor => 1.3 => 1.3
couchbase.compression_threshold => 2000 => 2000
couchbase.compressor => none => none
couchbase.durability_default_poll_interval => 100000 => 100000
couchbase.durability_default_timeout => 40000000 => 40000000
couchbase.serializer => php => php
couchbase.view_timeout => 75 => 75

Yay! We're almost there. To enable PHP in Apache2 we need to uncomment the following line in /etc/apache2/httpd.conf

#LoadModule php5_module libexec/apache2/libphp5.so

Remove the # and save the file. We need to start (or restart) apache2 for the changes to take effect:

trond@ok ~> sudo apachectl start

So lets check that it works! Go ahead and create the following file in $HOME/Sites/phpinfo.php

<?php phpinfo(); ?>

and access it: http://localhost/~your-username/phpinfo.php

You should get a page with a lot of text, and if you search the page you should find a section with information about the Couchbase extension.

Now that we've got something that works, lets create a small example page that actually use the extension. Go ahead and create a file named $HOME/Sites/index.php with the following content:

<html>
    <head>
        <title>Yay</title>
    </head>
    <body>
        <h1>This page has been accessed:
            <?php
            try {
                $cb = new Couchbase();
                print($cb->increment("counter", 1, true, 0, 0));
            } catch (CouchbaseException $ex) {
                var_dump($ex);
            }
            ?>
        </h1>
    </body>
</html>

If you have a Couchbase server running on the same machine you should be able to access this page through http://localhost/~yourname/ and for each time you access the page the counter should increase.

I'll write another blog post with examples that utilize the API.

Tuesday, December 11, 2012

IIS, PHP and Windows

In this short blog post I’m going to show you how to use PHP to access your Couchbase cluster on your Windows machine. I have a limited set of hardware at home, so I used a laptop with Microsoft Windows 7 home premium edition to test this example. Unfortunately for you that laptop came with a Norwegian version of Windows, so I can't tell you the exact name for the various menu items.

The first thing we need to do is to install Microsoft Internet Information Services (I'm going to refer to this as IIS) on your computer. You do so by selecting the "enable/disable windows features", and check the box for "Internet Information Services". Expand that selection and locate and expand "Webservices", and then locate and expand something like "application development" (sorry for not having an english copy of Windows ;)). In this category check the CGI checkbox. At the level right beneath the Internet Information Services you should have an entry containing something like "tools for web management", and in this sub category you should check "IIS-management console".


So lets go ahead and verify that the IIS installation works, and that it doesn’t support PHP already (that would render this blogpost pretty useless right ;)) Go ahead and put the following into c:\inetpub\webroot\test.php:

   <?php
   phpinfo();
   ?>


If you try to connect to http://localhost/test.php, you should either a page with an error message. If you get something else your IIS installation may already be configured (and hopefully we'll make it work).


The next thing we need to do is to install PHP and the Couchbase extension. At the time of writing we don’t have a binary package you can install, but it isn’t really hard to build it yourself. If you follow the steps I outlined in http://trondn.blogspot.no/2012/11/building-php-extension-for-couchbase-on.html you should be up’n’running in no time. I followed that instruction when I wrote this example, but I used a slightly different configure command:


configure --disable-all --enable-cli --with-couchbase=shared --enable-json --enable-cgi --with-config-file-scan-dir=C:\php\conf.d


You should now create C:\php\conf.d\couchbase.ini with the following content:



extension=php_couchbase.dll


Now that we’ve got IIS and PHP installed its time to look at the magic to glue them together. Open up the IIS Service Management Console (as I’ve already said I've got Norwegian windows so I may have used the wrong translation there) and select “Handler Mappings”, and in the actions pane (upper right corner) select “Add Module Mapping”. Fill in the following details starting from the top: “*.php”, “FastCgiModule”, “C:\php\php-cgi.exe” and whatever you want as the description. Be sure to answer “Yes” on the next dialog that asks if it should create a FastCGI application for this executable (if not it won’t work). You may also want to enter the “default document” setting and add index.php as an alternative. By doing that IIS will search for a file named index.php if the URL specifies a directory.

With this in place it’s time to retry our test page. Tune your browser back to http://localhost/test.php, and this time you should see a nice page with a lot of info. Scroll down and you should locate a section with information about the Couchbase extension.

Yay!!! We made that work, but being able to run PHP from IIS isn’t that fun all by itself.. Let’s see if we can get the Couchbase beersample for php working!

Go ahead and download and install Couchbase server 2.0 from http://www.couchbase.com/download, and choose that you want to install the beer sample database during configuration.
With that in place you can download the PHP example program I wrote earlier today (I’ll describe that in another blog post) from https://github.com/trondn/beersample-php/archive/master.zip. Unzip that program in C:\inetpub\webroot. Before we can try the sample application you have to create two new views in Couchbase. The first one should be in a design document named brewery and named by_name and look like:



    function (doc, meta) {
        if (doc.type == "brewery") {
            emit(meta.id, doc.name);
        }
    }


The next one is located in the beer design document and named by_name and looks like:


function (doc, meta) { if (doc.type == "beer") { emit(doc.name, doc.brewery_id); } }


Make sure you remember to press the publish button after you create them, or things won't work.


You should now be able to direct your browser to http://localhost/beersample-php-master and start browsing breweries :)

Happy hacking!

Thursday, November 1, 2012

Building the PHP extension for Couchbase on Microsoft Windows!


I've got a pretty good background from doing cross platform work. My first job as a software developer was to port a system to Trusted Solaris (and add privilege bracketing), but I've been porting stuff to Windows, HP-UX, Linux etc to name a few later on. It is important to me that the software should be portable, and that was one of the fixed requirement I had when I started libcouchbase back in the days.

Two days ago I sat down with Matt planning what I should focus on for the SDK team, and we both felt that we had to give the Windows support for some of our projects a makeover. Given PHPs popularity I figured that I should start with that.

I was kind of surprised to see how easy it was to build PHP with support for Couchbase on windows, but relax; we are going to ship binary versions for you so that you won't have to do this yourself, but being a geek I thought you might find it interesting to read how to do it yourself.

The first thing you have to do is to get set up a development environment. I initially used the description from PHP wiki, with some minor modifications. Feel free to use the steps outlined in that link, but I'll this is however how I did it. First I installed the following software:


  • Windows server 2008 r2
  • Visual studio 2008 professional
  • Windows SDK 6.1
  • git source control system
  • winrar


With that installed, you can open up a "windows sdk" shell, and get execute the following commands:

setenv /x86 /xp /release
mkdir c:\php-sdk

By using winrar I then extracted the php-sdk-binary-tools-20110915.zip into that directory. I could then create the directories and get ready to build the source.

cd c:\php-sdk
bin\phpsdk_setvars.bat
bin\phpsdk_buildtree.bat php-src

Start up a "git-bash shell", and navigate to c:\php-src\vc9\x86, and check out the code. Unfortunately this won't work as of today for you because not all of the patches for the source code have been pushed through yet:

git clone git://github.com/php/php-src
git clone git://github.com/couchbase/libcouchbase
cd php-src/ext
git clone git://github.com/couchbase/php-ext-couchbase couchbase

We're now ready to build the source! The first thing we need to do is to build libcouchbase (the C library that provides the functionality to talk to the Couchbase cluster):

cd libcouchbase
nmake -f NMakefile all install

With that installed we're ready to build the PHP module that expose the PHP functionality:

cd ..\php-src
buildconf
configure --disable-all --enable-cli --with-couchbase=shared
nmake all install
copy ../deps/bin/libcouchbase.dll c:\php

As you see I'm disabling all other modules than the Couchbase plugin (and I'm making that as a shared object). You would have to populate the "deps" directory with other libraries if you wanted to add support for them (but that's beyond the intention of this blog post).

So let's go ahead and try our new php extension. If you don't have a Couchbase cluster running already, now is the time to install one :) With the Couchbase cluster running, you can create a simple php.ini file that tells php to load our module:

extension=c:\php\php_couchbase.dll

I'm pretty sure you all know PHP way better than me, so please forgive me for the stupid code we'll use to show that it's working. I created the file test.php with the following content:

<?php
   $cb = new Couchbase("","","","default");
   $cb->set("hello", "world");
   var_dump($cb->get("hello"));
?>

Running the program with:

cd c:\php
php -f test.php

Should print out:

string(5) "world"

Thats all for this time! Happy hacking!

Shift of focus!


I really like going to conferences, not necessarily because I want to attend the different sessions, but because its a great arena to meet up with users and people interested in the same stuff as I am. Yesterday I attended CouchConf in Berlin, which was a lot of fun. I got to meet old friends, people I've only talked to on IRC (so fun to get a face behind the nick) and new people!

As part of attending CouchConf I spent some time talking to Matt Ingenthron about our SDKs, so I'm happy to announce that as of today I'm going to spend a significant amount of time working on our clients. Up until now I've done most of my contributions to the clients on my spare time, so I'm really looking forward to be able to spend my entire day trying to make your life easier.

Friday, September 28, 2012

YANFS - Yet Another Network File System

In the last few blog posts we've been exploring different kinds of how you might want to utilize libcouchbase from your app. To me it has always been really hard to come up with good example apps, so I always end up creating pretty boring applications.

Those of you who attended CouchConf in San Francisco really missed out if you didn't watch Couchbase Labs demo sessions, where they showed off CBFS. I'm not going to go into any details about what CBFS is (hopefully Dustin Sallings will create a blog post about that), but you can think cbfs as a large object store analogous to S3 (but meant to run in your own environment).

CBFS is implemented in the go language (if you haven't looked at that yet, put that on your todo list!), and it's shipped with it's own client to perform operations (upload files, list files etc). This gave me a great idea for todays blog post! FUSE is a project that allows you to create a filesystem implementation in user space, so the topic for today is an example where we utilize libcouchbase in our implementation for our FUSE driver.

As usual I won't explain the entire code, but I'll try to comment on the important bits. You'll find the source code for the entire project at http://github.com/trondn/mount_cbfs

The readme in the project should tell you how to install fuse and build the stuff, so in this blog post I'm only going to focus on how it works.

Creating a full featured filesystem driver is a lot of work, so I figured it would be better to start with a minimal implementation with a lot of limitations we could fix up later on (or I would never get around building it ;))


  • It is fully single threaded. This should be pretty easy to fix up later on by using a pool of libcouchbase instances, no shared data etc..
  • Read Only. This limits the number of entry points we need to implement.
So let's start looking at the code and how it all fits together. In main we register the struct containing all of the function pointers to the functions our filesystem support:

static struct fuse_operations cbfs_oper = {
    .getattr = cbfs_getattr,
    .open = cbfs_open,
    .read = cbfs_read,
    .readdir = cbfs_readdir,
};

This means that whenever someone tries to open a file in our filesystem, cbfs_open is called etc. So let's walk through an example here: What is the flow when you're typing "ls -l" in your shell.

The first things that happens is that cbfs_getattr is called. Its responsibility is to populate a stats struct with information about the file. Let's skip the details for now, and just assume that we return that this is a directory. Now cbfs_readdir is called with the directory to receive all of the entries in the directory, before cbfs_gettattr is called for every file to get the details of the files.

So how does our cbfs_readdir work like (after all this blog is about libcouchbase, not fuse ;)). CBFS use a http interface so we can request the list of files in a directory through the following URL:

http://cbfsserver:8484/.cbfs/list/path

It returns a JSON document looking something like:

{"files":{"file1":{}},"dirs":{},"path":"/foo"}

All we need to do is to decode the JSON and populate the information to FUSE. So how do we do this through libcouchbase? Through the http interface:

static lcb_error_t uri_execute_get(const char *uri, struct SizedBuffer *sb) {
    lcb_http_cmd_t cmd = {
        .version = 1,
        .v.v1 = {
            .path = uri,
            .npath = strlen(uri),
            .body = NULL,
            .nbody = 0,
            .method = LCB_HTTP_METHOD_GET,
            .chunked = 0,
            .content_type = "application/x-www-form-urlencoded",
            .host = cfg->cbfs_host,
            .username = cfg->cbfs_username,
            .password = cfg->cbfs_password
        }
    };

    return lcb_make_http_request(instance, sb, LCB_HTTP_TYPE_RAW, &cmd, NULL);
}

Since I'm using the synchronous interface to libcouchbase that call will block until we've received the response from the server. In my response handler I just copy whatever data the server returned to me:

static void complete_http_callback(lcb_http_request_t req, lcb_t instance,
                                   const void *cookie, lcb_error_t error,
                                   const lcb_http_resp_t *resp)
{
    struct SizedBuffer *sb = (void*)cookie;

    if (error == LCB_SUCCESS) {
        /* Allocate one byte extra for a zero term */
        sb->data = malloc(resp->v.v0.nbytes + 1);
        sb->size = resp->v.v0.nbytes;
        memcpy(sb->data, resp->v.v0.bytes, resp->v.v0.nbytes);
        sb->data[resp->v.v0.nbytes] = '\0';
    }
}

Both cbfs_read and cbfs_getattr utilize the same function from libcouchbase, but they're only hitting different URL's to get their data.

Adding supports for write operations should be no worse than finding the correct URL and do a PUT etc.

Happy Hacking :)



Saturday, September 22, 2012

Extend a Couchbase Server with your own commands!

I was having a beer with Sergey after yesterday's CouchConf in San Francisco and we started discussing command extensions for memcached. I guess I haven't been very good at advertising about what I've been doing, because I added this to memcached a couple of years ago as part of the plugin extension framework.

I thought that it might be interesting to write up a small blog post that shows you how you may write your own commands to access a Couchbase cluster. Please note that what I'm going to show you in this blog post is abusing Couchbase Server, and nothing you should be doing in production. If you have special needs you should connect with us to let us work with you to make it happen (and ensure that you don't break stuff).

So lets imagine that you're storing relatively big objects with data into a cluster, and you only need fragments of each object at a time. The current SDK only allows you to read and write a complete item at a time, so we're going to extend our Couchbase cluster to be able to give us a fragment of the data at a time.

Luckily, I wrote this extension for memcached a couple of years ago, and it is actually bundled with the Couchbase Server. This does however not imply that it is a supported extension to be using from your Couchbase Server. So let's go ahead and enable the extension on the server:

# cd /opt/couchbase/bin
# mv memcached memcached.bin
# cat > memcached << ...
#! /bin/sh
exec `dirname $0`/memcached.bin \
            -X /opt/couchbase/lib/memcached/fragment_rw.so "$@"
...
# chmod +x memcached

If you are running a cluster you need to do this on all of your nodes (and you need to restart all of your nodes for them to load the module).

You don't learn anything unless we're walking through how the extension actually works. You'll find the module at https://github.com/trondn/memcached/tree/engine-pu/extensions/protocol in the files fragment_rw.c and fragment_rw.h.

So let's look into the details there. During startup memcached will load all shared objects specified with the -X parameter, and try to look up the function named memcached_extensions_initialize. This is where you should initialize your engine, and add it to the system. If you look at the signature for the method, you might wonder where you may specify the configuration. The "per engine" configuration is specified as part of the -X parameter. The fragment module allows you to specify the command id for read and write through "r" and "w", so we could write -X fragment.so,r=5,w=6 to tell it to use the command code 5 and 6 (please note that it would be a conflict with the existing commands ;))

Setting up a command extension happens in two different phases. The first thing you need to do is to registering your command descriptor:


server->extension->register_extension(EXTENSION_BINARY_PROTOCOL, &descriptor);

The descriptor for protocol extensions is pretty simple:

static EXTENSION_BINARY_PROTOCOL_DESCRIPTOR descriptor = {
    .get_name = get_name,
    .setup = setup
};

get_name is a function that just returns the name of the extension, whereas setup is the interesting function:


static void setup(void (*add)(EXTENSION_BINARY_PROTOCOL_DESCRIPTOR *descriptor,
                              uint8_t cmd,
                              BINARY_COMMAND_CALLBACK new_handler))
{
    add(&descriptor, read_command, handle_fragment_rw);
    add(&descriptor, write_command, handle_fragment_rw);
}

So what is happening here. The function is called with a parameter that is a function you may call in order to register handlers for various command codes. The first parameter is you should pass to this callback is the descriptor you registered. The second parameter is the command opcode you want to install a handler for, and the third parameter is the function that implements the logic.

So let's go ahead and look at how the function looks:

static ENGINE_ERROR_CODE handle_fragment_rw(EXTENSION_BINARY_PROTOCOL_DESCRIPTOR *descriptor,
                                            ENGINE_HANDLE* handle,
                                            const void* cookie,
                                            protocol_binary_request_header *request,
                                            ADD_RESPONSE response)

The descriptor is the descriptor you registered, the handle is the handle to the engine keeping all of the data (so that you may interact with the backing store). Cookie is a reference to the client who connected to you. Request is the complete package the client sent for the request, and response is a callback function you may call to send data back to the client. If you look at the implementation for handle_fragment_rw you'll see that it's not that hard to interact with the engine.


The only thing left for us now is to create create client functionality to connect to a server and send such a packet :) I leave that up to you folks for the moment (or if people would like me to post it here I'd be happy to do so. let me know).

Happy hacking!


Monday, September 17, 2012

libcouchbase is async, but my app isn't...

In the previous example I showed you how to hook libcouchbase into your own event loop, but not everyone wants to use it asynchronously. The best part of an asynchronous library is that it's pretty easy to make a synchronous library on top of it. All you need to do is to call the function and wait for it to complete, but in libcouchbase I went the extra mile. You may use it fully asynchronous, you may use it fully synchronous or you may use it somewhat in the middle.

The easiest way to use libcouchbase in a synchronous way is to toggle it's internals to the syncmode setting by calling:

lcb_behavior_set_syncmode(instance, LCB_SYNCHRONOUS);

One "drawback" with the syncmode setting is that you can't execute a chunk of commands concurrently. Lets look at the following example:

lcb_store(instance, NULL, 15, store_commands);
lcb_get(instance, NULL, 2, get_commands);

With syncmode enabled all of the store commands must have been executed and the response received before any of the get commands is sent (even if they don't hit the same servers). It would improve the latency of your application if you could shuffle as much data as possible over the network before blocking to wait for the result. That's when you want to set the syncmode to LCB_ASYNCHRONOUS (the default) and use lcb_wait() instead:

lcb_store(instance, NULL, 15, store_commands);
lcb_get(instance, NULL, 2, get_commands);
lcb_wait(instance);

when lcb_wait() returns we know that all of the above commands are executed (and the appropriate callbacks called).

So let's whip up a small program that shows you how to use the syncmode setting. To keep the example as small as possible I'm going to skip error recovery etc. Let's jump straight to the main() method:

int main(void)
{
    lcb_error_t error;
    lcb_t instance = create_instance();

    if ((error = lcb_connect(instance)) != LCB_SUCCESS) {
        fprintf(stderr, "Failed to connect to cluster: %s\n",
                lcb_strerror(instance, error));
        exit(EXIT_FAILURE);
    }

    set_key(instance);
    get_key(instance);

    lcb_destroy(instance);
    exit(EXIT_SUCCESS);
}

As you see, the program is pretty simple. First we create a libcouchbase instance and connect to the cluster; set and get a key before we clean up and exits.

Let's look inside create_instance:

static lcb_t create_instance(void)
{
    lcb_t instance;
    struct lcb_create_st copt;
    lcb_error_t error;

    memset(&copt, 0, sizeof(copt));
    copt.v.v0.host = "myserver:8091";

    if ((error = lcb_create(&instance, &copt)) != LCB_SUCCESS) {
        fprintf(stderr, "Failed to create libcuchbase instance: %s\n",
                lcb_strerror(NULL, error));
        exit(EXIT_FAILURE);
    }

    lcb_behavior_set_syncmode(instance, LCB_SYNCHRONOUS);
    lcb_set_error_callback(instance, error_handler);
    lcb_set_store_callback(instance, store_handler);
    lcb_set_get_callback(instance, get_handler);

    return instance;
}

This looks pretty much like how we normally do stuff except for the line I've marked as bold. From that line all of the lcb_ functions will block until we've received a reply from the server for the requested operation. You might be curious about how my handler functions looks like, and they are pretty boring:

static void error_handler(lcb_t instance, lcb_error_t err, const char *info)
{
    fprintf(stderr, "FATAL! an error occured: %s (%s)\n",
            lcb_strerror(instance, err), info ? info : "none");
    exit(EXIT_FAILURE);
}

static void store_handler(lcb_t instance, const void *cookie,
                          lcb_storage_t operation, lcb_error_t error,
                          const lcb_store_resp_t *resp)
{
    (void)cookie; (void)operation;

    if (error != LCB_SUCCESS) {
        fprintf(stderr, "Failed to store the key on the server: %s\n",
                lcb_strerror(instance, error));
        exit(EXIT_FAILURE);
    }

    if (resp->version == 0) {
        fprintf(stdout, "Successfully stored \"");
        fwrite(resp->v.v0.key, 1, resp->v.v0.nkey, stdout);
        fprintf(stdout, "\"\n");
    }
}

static void get_handler(lcb_t instance, const void *cookie,
                        lcb_error_t error, const lcb_get_resp_t *resp)
{
    (void)cookie;

    if (error != LCB_SUCCESS) {
        fprintf(stderr, "Failed to read the key from the server: %s\n",
                lcb_strerror(instance, error));
        exit(EXIT_FAILURE);
    }

    /* Validate that I read the correct key and value back */
    if (resp->version != 0) {
        fprintf(stderr,
                "WARNING: I don't support this version of libcouchbase\n");
        exit(EXIT_FAILURE);
    }

    fprintf(stdout, "I received \"");
    fwrite(resp->v.v0.key, 1, resp->v.v0.nkey, stdout);
    fprintf(stdout, "\" with the value: [");
    fwrite(resp->v.v0.bytes, 1, resp->v.v0.nbytes, stdout);
    fprintf(stdout, "]\n");
}

If we jump back to our main function the next instructions to execute would be:

    if ((error = lcb_connect(instance)) != LCB_SUCCESS) {
        fprintf(stderr, "Failed to connect to cluster: %s\n",
                lcb_strerror(instance, error));
        exit(EXIT_FAILURE);
    }

Now that we've enabled the LCB_SYNCHRONOUS mode in libcouchbase this will be a blocking call (I fixed that bug today Sept 17th), so that when the method returns we know the topology of the Couchbase cluster and we're about to store our object there in the set_key method that looks like:

static const char const *key = "mykey";
static const char const *value = "myvalue";

static void set_key(lcb_t instance)
{
    lcb_store_cmd_t cmd;
    lcb_error_t error;
    const lcb_store_cmd_t * const commands[] = { &cmd };

    memset(&cmd, 0, sizeof(cmd));
    cmd.v.v0.key = key;
    cmd.v.v0.nkey = strlen(key);
    cmd.v.v0.bytes = value;
    cmd.v.v0.nbytes = strlen(value);
    cmd.v.v0.operation = LCB_SET;

    if ((error = lcb_store(instance, NULL, 1, commands)) != LCB_SUCCESS) {
        fprintf(stderr, "Failed to store key: %s\n",
                lcb_strerror(instance, error));
        exit(EXIT_FAILURE);
    }
}


This looks exactly like how we would have done it in our asynchronous application, and here is a very important detail. The return code of the function is not the return code of the actual operation! It means the exact same thing as it used to: if we encountered any problems initiating the operation. It is the callback function that will tell you the result of the operation from the server!

To make the example complete let's look at the get_key function as well:

static void get_key(lcb_t instance)
{
    lcb_get_cmd_t cmd;
    lcb_error_t error;
    const lcb_get_cmd_t * const commands[] = { &cmd };

    memset(&cmd, 0, sizeof(cmd));
    cmd.v.v0.key = key;
    cmd.v.v0.nkey = strlen(key);

    if ((error = lcb_get(instance, NULL, 1, commands)) != LCB_SUCCESS) {
        fprintf(stderr, "Failed to get key: %s\n",
                lcb_strerror(instance, error));
        exit(EXIT_FAILURE);
    }
}

Pretty simple and straight forward.

Happy hacking!