SDK 2.0.0.1, Connection Pooling & Connection Timeouts

#1

I am running so load tests against some code that has 100 virtual users hitting a page that initially adds some documents to couchbase and then the remaining connections simply get the documents from couchbase.

I have the connection pooling on the bucket configured (using web.config) to have a min of 1 and max of 5, the timeout settings are left as the default values.

When the test ends it seems to take up to 15 minutes for all the connections to close. The code is calling CloseBucket, and there are never more than 100 virtual users.

Should it take so long before all the connections are closed? (even if CloseBucket is not called correctly)

#2

Did you ever figure out how to do connection pooling? I was trying to use a similar coding architecture for couchbase as I did for SQL connection pooling, but the openbucket calls are very slow. What did you find?

#3

@k_maynard -

It’s really hard to say, because I don’t know enough about your usage of the client. Let me explain a bit how the client works with respect to CloseBucket.

When you open a bucket a ref counter will be incremented, when you close it will be decremented. If you open a bucket 100 times, you will need to close it 100 times. This is so that one thread doesn’t close a bucket that being used by another thread. That being said, if a bucket is opened it can be used by N number of threads concurrently. This is client specific behavior.

It’s really hard to tell, but what may be happening is that the reference to the Cluster and Bucket are not being cleaned up until the App Pool refreshes? Is that something you can verify?

#4

The client uses connection pooling internally, there is no need to use additional pooling in the app layer. That being said, the app layer needs to maintain long-lived references to the Cluster and Bucket.

1 Like
#5

I see. I attaching from a WCF service, so I need to figure out how to keep open long lived references within the services.

#6

In the end I dropped couchbase :frowning:

thanks for the reply jeff, my application is basically SaaS, 3-tiers- presentation (asp.net ajax), application/business logic (wcf services) and database (ms-sql). We run 3 front end web servers, 2 app servers and a database cluster.

I was implementing couchbase in the presentation layers, around 1000 active users, with about 400 requests per second. Most requests are processed on the server in under 600ms, but some are more complex and can take up to 20 seconds.

In my global.asax on application start I created/opened the connection to the Cluster, then for each http request I created/opened a bucket and used that until I returned the result to the caller (basically Page_Init until Page_Render). In the page dispose method I also disposed the bucket.

Having read your comments and the comments from another thread it seems like I would be better opening both the cluster and bucket in the global.asax ?

Are their any code examples of best practices for this type of situation??

#7

@envitraux - for a WCF service, you could use a private static property with a public getter that checks if the private is null, if it is, initialize the connection to couchbase cluster & use this to set the private property. If its not null, return the private property.

This will both be shared by all connections to the service AND long lived as it is (normally) only disposed when the apppool is shutdown / recycled.

Be aware that opening the connection to the cluster can take a few seconds, in my code I used a flag to inidicate that the connection was being initialized and allowed the other requests to fail - at which point I would go direct to a sql database to get the data I needed.

Depending upon how you are using couchbase this may or may not be valid option for you - I was using couchbase only as a distributed cache rather than my only document store.

1 Like
Use singleton or not, it is a good idea
#8

@k_maynard -

Yeah, you would your bucket to be static and long-lived as well. If your opening and closing it your going to incur the cost of recreating the connections, which will decrease performance. One thing to note, is we do have a ClusterHelper class which is a lazy-loaded singleton wrapper for the Cluster, which makes the storing the static instance in global.asax not required.

-Jeff

#9

Why did you drop couchbase?

#10

…basically, time…

Performance in the test environment was impressive, load testing was ok against a single server where couchbase was local to the server, but as soon as I shipped it to a staging environment and put real customer load onto the system I started to have issues and was unable to investigate further.

So, I ran out of time before the delivery deadline - the software had to ship, and without being able to resolve the issues I was having couchbase had to be dropped.

1 Like
#11

That helper class would certainly be helpful !

To avoid confusion (mine & anyone else reading this later) - do you have the full class name & an example of how we should use this…

#12

@k_maynard

The class is Couchbase.ClusterHelper and here is an example of usage:

//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(); 

You would then open a bucket and use it accordingly; note that your bucket should be cached as well. I am working on a blog post with an example of how to use this using a DAO or Repository pattern.

Perhaps the helper class needs another method: ClusterHelper.OpenBucket(bucketName) which internally caches and maintains the bucket instance?

-Jeff

1 Like
#13

Jeff,

I eagerly await your blog post - I found the current documentation is somewhat vague, and combined with the information available by googling the topic.

Thanks a lot for the example, this is far simpler than the code I have - which is based on the documentation here:
http://docs.couchbase.com/developer/dotnet-2.0/hello-couchbase.html

An additional helper class to handle the buckets is a great idea, and if it can handle the case where we want to open more than one bucket on the cluster that would be great…

#14

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.

#15

@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

#16

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:

#17

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

CouchbaseClusterHelper Initialize
#18

Are you using a singleton or static class?

#19

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…

#20

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
    }
}