Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@giriraj-singh-couchbase
Copy link

@giriraj-singh-couchbase giriraj-singh-couchbase commented Aug 13, 2025

What problem does this PR solve?

Currently, brpc supports various protocols like memcached, Redis, and HTTP, but lacks native support for Couchbase, a popular NoSQL document database. Users who want to integrate Couchbase with brpc applications can now directly use the builtin wrapper functions, this would help in writing less code and build the applications even faster.

What is changed and the side effects?

Changed:

  1. Coucbase Wrapper files in src/brpc/couchbase.h && src/brpc/couchbase.cpp
  2. Added Examples to use the couchbase wrapper functions in example/couchbase_example/client.cpp, multi_threaded_client.cpp
  3. Changes in Makefile of the bRPC so that it can search for necessary libraries to help compile the couchbase SDK.
  4. Added a comprehensive couchbase_client.md file to get the better understanding of using the couchbase c++ SDK wrapper functions and the features provided by the couchbase c++ sdk wrapper.

Side effects:

  • Performance effects:

    -     Positive: Thread-safe connection reuse across multiple threads reduces connection overhead
    -     Positive: Efficient JSON handling with tao::json library
    -     Positive: Connection pooling handled automatically by underlying Couchbase SDK
    -     Minimal overhead: Thin wrapper around official Couchbase C++ SDK
    
  • Breaking backward compatibility:

    -     this is a new feature addition
    -     However, building brpc now requires some additional dependencies such as couchbase-cxx-library and its dependencies
    -     Existing brpc functionality remains unchanged
    

std::vector<bthread_t> bids;
for (int i = 0; i < 5; i++) {
bthread_t bid;
if (bthread_start_background(&bid, nullptr, threaded_example, &params[i]) !=

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the threads sharing a single cluster connection, all the threads are communicating to different buckets and performing CRUD operations.

.adhoc(false);

// add positional parameters, you can also use named parameters and other
// query options that might be required.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

query functions is overloaded with query_options so that end-user can have full control over the query. Here, we have used placeholders '$1' to test the working of the query_options.

void CloseCouchbase();

// query helper functions
std::pair<bool, std::vector<std::string>> Query(std::string statement);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cluster level query without any query_options. Simple string type queries, without any parameters.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

// query helper functions
std::pair<bool, std::vector<std::string>> Query(std::string statement);
std::pair<bool, std::vector<std::string>> Query(
std::string statement, couchbase::query_options& q_opts);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cluster level query supporting query_options.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great this is here.

std::string statement, couchbase::query_options& q_opts);
std::pair<bool, std::vector<std::string>> Query(
std::string statement, const std::string& bucket_name,
const std::string& scope = "_default");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scope level query. However in the query(statement) the collection is needed to specified i.e. inside the string itself.

const std::string& scope = "_default");
std::pair<bool, std::vector<std::string>> Query(
std::string statement, const std::string& bucket_name,
const std::string& scope, couchbase::query_options& q_opts);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scope level query with query_options.

<< std::endl;
start = std::chrono::high_resolution_clock::now();
std::string scoped_query =
"SELECT META().id, email FROM _default WHERE email LIKE '%@%'"; // Here

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When running a scoped query then we need to give a collection in the FROM field. So the _default is needed inside the query.

@giriraj-singh-couchbase giriraj-singh-couchbase added the enhancement New feature or request label Aug 13, 2025
@giriraj-singh-couchbase giriraj-singh-couchbase marked this pull request as ready for review August 13, 2025 19:06
@ingenthr ingenthr requested a review from DemetrisChr August 22, 2025 13:54
Copy link
Collaborator

@ingenthr ingenthr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a completely thorough review, but I think there are a couple places to examine:

  1. the initialization might need to consider a case where the connection won't work right now, but might work in the future. it's a bit binary at the moment, but a couchbase SDK instance isn't binary with respect to a 'connection'. I'm not sure the best way to reconcile this. Options might be to expose the ping/health_check or to put an optional waitUntilReady() in the initialization.
  2. I'm not sure about the error handling. If this aligns to how other projects in bRPC do it, then I think it's fine, but we might need to populate some other closed over or thread local variable with error details. Can you have a thought on this?


All operations return boolean values or pairs indicating success/failure:

- **CRUD operations**: Return `true` on success, `false` on failure
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so, in the 'false' case, wouldn't a user need more information? If it's a CAS mismatch, I'll want to backoff/retry, but if it's an add() case and the doc is already there, no point in retrying.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not boolean for all the CRUD operations. I will fix the doc. So, currently get is having a (bool and the error) as the return types so that client can know what error has actually come. Other operations(add,upsert,delete) are having the boolean return type which I should change as well following your later comment on the functions.

@@ -0,0 +1,141 @@
#include <bthread/bthread.h>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice that we have a multithread sample!

std::string key = "item::" + std::to_string(i);
std::string value =
R"({"name": "John Doe", "age": 31, "email": "[email protected]", "updated": true})";
if (couchbase_client->CouchbaseAdd(key, value, bucket_name)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've not looked, but is this how the memcached handles errors @giriraj-singh-couchbase ? Is there an easy way to have this if() branching but still have details on failures? This one is related to my other feedback that we may need to know when the add() fails, is it because of something unusual (in which case I'll want to retry) or because of something that will never work (because some other client added the doc first)

Copy link
Author

@giriraj-singh-couchbase giriraj-singh-couchbase Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, okay. I was printing the errors inside the functions itself and then simply returning false. Client might want to keep different logic based on the errors. So over here, the changes that I am thinking is to return the boolean true or false and the error with it too if any. and based upon the error, user can handle the client code logic.

}

// Query operation at cluster level without query options
std::pair<bool, vector<std::string>> CouchbaseWrapper::Query(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice that you got queries in here!

void CloseCouchbase();

// query helper functions
std::pair<bool, std::vector<std::string>> Query(std::string statement);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

auto [connect_err, cluster] =
couchbase::cluster::connect(connection_string, options).get();

if (connect_err) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a section that probably requires some thought. Generally with the SDKs there is no such thing as a "connection failure". once initialized, it tries to connect forever. There are pros and cons to this, but it's where we are at the moment.

The code here reads as if it's looking for "a connection" which isn't really how it works. Maybe it should be a waituntilready(). I've added @DemetrisChr as a reviewer. He uses this SDK from multiple places, so he may be able to offer some advice.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The C++ SDK behaves a little differently compared to other SDKs here. cluster::connect blocks until a memcached connection is established and a config is seen on that connection, up until the bootstrap timeout.

So a wait_until_ready is not as necessary, and why it it hasn't been added to the SDK yet (although wait_until_ready can also be used for checking the readiness of services, so we probably need to add it eventually).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hii @DemetrisChr, Can you tell me how is bootstrap timeout different from connect timeout? As far as I can think of bootstrap timeout means that the total time that can be taken for full initialisation and setup of clusters whereas connect timeout is for establishing of sockets only. Is it correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's right.

The connect timeout is the timeout for establishing each individual memcached connection, used here. This includes connections made both during the initial bootstrap phase & any connections made later on (for example, connections made when opening buckets).

The bootstrap timeout (used here) covers the entire bootstrap phase, until a single connection is established successfully and a config received, which can involve a number of connection retries.


// Upsert (insert/update) document
bool CouchbaseUpsert(const std::string& key, const std::string& value,
const std::string& bucket_name,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want to file an improvement issue for the future to use fully qualified buckets/scopes/collections. This offers protection from a delete-recreate of a collection being logically different. I think this is fine for now though.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do mention it as it may change the API in the future. I guess we could add something that isn't a string.

Copy link
Author

@giriraj-singh-couchbase giriraj-singh-couchbase Aug 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please tell me more about fully qualified buckets/scopes/collection? I was not able to get the context of what is the meaning of that. However, we can do operation at any level: buckets, scopes and collections all are supported in this very function.

// query helper functions
std::pair<bool, std::vector<std::string>> Query(std::string statement);
std::pair<bool, std::vector<std::string>> Query(
std::string statement, couchbase::query_options& q_opts);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great this is here.

…ased upon the errors the code can be driven, tested and did changes to work on linux.
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

license-eye has totally checked 1730 files.

Valid Invalid Ignored Fixed
865 4 861 0
Click to see the invalid file list
  • example/couchbase_example/client.cpp
  • example/couchbase_example/multi_threaded_client.cpp
  • src/brpc/couchbase.cpp
  • src/brpc/couchbase.h

@@ -0,0 +1,394 @@
#include <gflags/gflags.h>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include <gflags/gflags.h>
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <gflags/gflags.h>

@@ -0,0 +1,161 @@
#include <bthread/bthread.h>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include <bthread/bthread.h>
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <bthread/bthread.h>

@@ -0,0 +1,299 @@
#include "brpc/couchbase.h"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include "brpc/couchbase.h"
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "brpc/couchbase.h"

@@ -0,0 +1,82 @@
#pragma once

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#pragma once
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#pragma once

} else {
//based on the returned error do actual error handling
if(add_response.err.ec() == couchbase::errc::key_value::document_exists){
//handle the error
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much better-- it's great when these kinds of examples show at least rudimentary error handling.

```

**Note**: If scope and collection are not specified, they default to `"_default"`.
**Note**: If scope and collection are not specified, they set to `"_default"`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind of funny English here to my en_US eyes. Instead "are set" might be better.

DEFINE_string(username, "selfdb", "Couchbase username");
DEFINE_string(password, "Selfdb@1", "Couchbase password");
DEFINE_string(couchbase_host, "couchbase://localhost", "Couchbase server host");
DEFINE_string(username, "Administrator", "Couchbase username");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: I tend to not like to use Administrator/password everywhere, as it encourages bad security behavior. They should have a normal user. Not necessary to change though unless you think about it.

"Couchbase server host");
DEFINE_string(username, "selfdb", "Couchbase username");
DEFINE_string(password, "Selfdb@1", "Couchbase password");
DEFINE_string(couchbase_host, "couchbase://localhost", "Couchbase server host");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: examples like this are often great places to drop in a few commented out examples of common things a user might want to do. For example, switch on TLS. Or add the wan development profile.

See:
https://docs.couchbase.com/cxx-sdk/current/howtos/managing-connections.html#connecting-to-a-cluster

No need to change unless you want to though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants