SDK 2.0.0.1, Connection Pooling & Connection Timeouts

This thread has been very helpful. I think it the documentation is poor on telling one how to set up connection pooling, and without connection pooling, Couchbase loses a lot of its advantages with performance over SQL databases.

In my case, I did create a helper class. I DO keep a static reference to the cluster but DO NOT keep a static reference to the bucket, and hence it does not seem to keep the connection open. I also want to make sure that the class is lazy loaded as well which I am not sure it is. I will post my code if I get it working.

@k_maynard -

I made a ticket to track this: https://issues.couchbase.com/browse/NCBC-786

This is what the usage looks like:

ClusterHelper.Initialize();
var bucket1 = ClusterHelper.GetBucket("default");
var bucket2 = ClusterHelper.GetBucket("default");

Assert.AreEqual(bucket1, bucket2);

Note that if you had another bucket defined on the server, say “authenticated” which took a password “secret”, you would use this overload:

var bucket3 = ClusterHelper.GetBucket("authenticated", "secret");

Assert.AreNotEqual(bucket2, bucket3);

It will be pushed through code review and available on github (master branch) soon and released with 2.0.2 next month.

@envitraux
This should help you as well :smile:

Thanks!

-Jeff

That’s great Jeff, thanks …

So, just to sum things up, in the ASP.net world implementation (in 2.0.2 or greater) is now something like:

In global.asax.cs -> Application_Start (or on first required usage of the cluster) we simply set the client configuration and initialize the cluster helper:

//create a config in this case it's the default config
var config = new ClientConfiguration();

//Initialize the ClusterHelper
ClusterHelper.Initialize(config);

//Lazily get the Cluster instance
var cluster = ClusterHelper.Get(); 

Then, on each page - on Page_Load (or first required usage)/ service call, we get a reference to a bucket:

var bucket1 =  ClusterHelper.GetBucket("default"); 
// or CluserHelper.GetBucket ("default", "password")

…simple and straightforward - great improvement :slight_smile:

Yup, in Application_Start though you won’t have to Get() the cluster instance. Also, in Application_End you should probably call ClusterHelper.Close() to clean things up faster.

-Jeff

Are you using a singleton or static class?

I was using a static property, a singleton seems to be both unnecessary extra code and in many respects duplication of what is done within the SDK. I really need to rebuild my code with the latest information I have from this thread and give it another try, but my thoughts are:

As you are using services, I would be tempted to use a singleton class…,

Make sure to use the ClusterHelper to initialize & get the connection to the cluster & define a static for the bucket (until the next release)…

I would just expose methods for add / delete / update / exists -

Additionally, I would add a method to “reset” everything, close & reopen the close the bucket - close & reopen the cluster connection etc -

…the wrapper will also help when the new versions of the SDK are available you should be able to easily upgrade and as a bonys you also have an object that can easily be tested etc…

Borrowing from @k_maynard’s static class, I made a singleton class. See below. I am sure it can be improved.

  public sealed class CouchbaseManager
{
    static CouchbaseManager _instance;

    private Couchbase.Configuration.Client.ClientConfiguration _config;
    private Cluster _cluster = null;
    private System.Collections.Concurrent.ConcurrentDictionary<string, Couchbase.Core.IBucket> buckets = null;

    public static CouchbaseManager Instance
    {
        get { return _instance ?? (_instance = new CouchbaseManager()); }
    }
    public static void CloseInstance()
    {
        _instance.Close();
        _instance = null;
    }

    public Cluster cluster { 
        get { 
            if (_cluster == null)
                _cluster = new Cluster(_config);
            return _cluster; 
        }
    }

    public Couchbase.Core.IBucket GetBucket(string bucketName)
    {
        if (buckets == null)
            buckets = new System.Collections.Concurrent.ConcurrentDictionary<string, Couchbase.Core.IBucket>();

        return buckets.GetOrAdd(bucketName, (name =>	
        {	
            var bucket = cluster.OpenBucket(bucketName);	
            return bucket;	
        }));
    }
    public void Close()
    {
        if (_cluster == null) return;
        foreach (var bucket in buckets.Values)
        {
            bucket.Dispose();
        }
        buckets.Clear();
        if (_cluster == null) return;
        _cluster.Dispose();
        _cluster = null;
    }
    public void RemoveBucket(string bucketName)
    {
        Couchbase.Core.IBucket bucket;
        if (buckets.TryRemove(bucketName, out bucket))
        {
            bucket.Dispose();
        }
    }
    public int Count()
    {
        return buckets.Count;
    }

    private CouchbaseManager()
    {
        _config = new Couchbase.Configuration.Client.ClientConfiguration
        {
            Servers = new List<Uri> {
                  // put in your own server URI's
                        },
            BucketConfigs = new Dictionary<string, Couchbase.Configuration.Client.BucketConfiguration>
                        {
			// put in your own config
                        }

        };
        // Open the buckets in global.asax
    }
}

@envitraux

Cool, that is pretty much what the CouchbaseHelper class looks like now: https://github.com/couchbase/couchbase-net-client/commit/f421cc0d9fb61260293c9cbbbaaa8b42a0b1f995

Note that the client is open source, so you can always send a pull request if you want to improve on something. It may not get excepted, but it will get the conversation going towards a better solution.

Thanks!

-Jeff

Hi Jeff,

So, if i was correctly watching this discussion you say its not good to open and close bucket as its written in the most of CB examples for .NET:

using(var mybucket=MyClaster.OpenBucket()){....some routine...}

?

You should definitely not be following the examples - whilst they show the correct order for doing things, and are functionally correct, the overhead for each OpenBucket call is very high…

I also found that when you call OpenBucket it seems to automatically open the minimum number of connections as defined on the pooling settings…

What basically seems to happen is that the number of connections grows with each call to OpenBucket, and when you call CloseBucket it takes around 30 minutes for the unused connections to close…

Thanks for the answer!

What would be the best solution then? Keep one bucket per app? or per user session (in case of web app)?

I started with things as they are in the documentation, then I tried with one per page request and that still gave me performance issues.

One per session might be ok, but I would start with just a single bucket per application (which is the way I am trying now).

As I understand it the SDK should manage its own connection pool internally, if you find you have lots of simultaneous requests you may want to adjust the connection pool properties on the bucket settings within the web.config.

See also Using Couchbase Buckets pattern

Is the SDK client written in C#?

Yes, you can find it on GitHub - here:

That’s actually not correct! When you open a bucket, only a single reference is cached internally by the Cluster object. You can verify this by opening the same bucket twice and comparing the instances; they should be the same.

Also, when you open the bucket, it will create the ConnectionPool.MinSize connections, whatever that value is. By default it’s 1, which is probably not ideal for high-performance scenarios. You can increase the MaxSize, which defaults to 2, and then client will lazily add connections up to that value. In many cases, keeping a low min and bit higher max is ideal, because the client can tune itself. This is really something that requires measuring though to get right.

The reason it’s taking 30 minutes, is that you probably have references hanging around to do reference counting done internally - i.e. you probably opened the bucket with n+1 threads and only closed it n times. This is a feature to keep one thread from disposing of the client while another is using it. The ClusterHelper will fix this, but I am thinking their needs to be a way for the caller to control this.

Yes, as @k_maynard said, those are to show the correct order of steps, but not necessarily the best way to use the client in practice. This is especially true in a server environment, such as ASP.NET.

Using a singleton wrapper such as @k_maynard posted above or using the ClusterHelper ( NCBC-786: Add GetBucket(nm), GetBucket(nm, pw), RemoveBucket(nm) to C… · couchbase/couchbase-net-client@f421cc0 · GitHub) are probably the best way to use the client within an ASP.NET application without creating and tearing down the client after every use.

Thanks!

-Jeff

I checked many times to make sure each bucket was closed, but it was hard to track. Maybe having the ability to access the current count would help with the debugging - or being able to add a name or reference string when calling open bucket (which could then be includes in the list of buckets not closed)…

Ok, to follow up conversation. Solution proposed by @k_maynard would keep 1 bucket opened per application. How good is this solution in terms of well crowded sites? Whould not it be better to keep 1 opened bucket per user session and close it when user leaves the site?

In case of single bucket per app solution, do i need to put max connection to some reasonable big value?

This should be fine. If you opened one bucket per session then you would end up using way to many connections. The client is thread safe, so it’s intended to be shared by many threads, such as in a ASP.NET environment.

It depends upon the amount of traffic you are serving. If you start getting timed out responses on the client-side (ResponseStatus.ClientFailure), then bumping up the MaxSize should help there, maybe to 5 or 10 or even higher.

Check this one, More about…global.asax

Darren