{"id":7494,"date":"2015-09-28T21:14:17","date_gmt":"2015-09-28T18:14:17","guid":{"rendered":"http:\/\/www.webcodegeeks.com\/?p=7494"},"modified":"2018-01-05T16:54:51","modified_gmt":"2018-01-05T14:54:51","slug":"deploying-and-optimizing-couchdb","status":"publish","type":"post","link":"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/","title":{"rendered":"Deploying and Optimizing CouchDB"},"content":{"rendered":"<p><em>This article is part of our Academy Course titled <a href=\"http:\/\/www.webcodegeeks.com\/2015\/09\/couchdb-database-for-the-web\/\">CouchDB \u2013 Database for the Web<\/a>.<\/p>\n<p>This is a hands-on course on CouchDB. You will learn how to install and configure CouchDB and how to perform common operations with it. Additionally, you will build an example application from scratch and then finish the course with more advanced topics like scaling, replication and load balancing. Check it out <a href=\"http:\/\/www.webcodegeeks.com\/2015\/09\/couchdb-database-for-the-web\/\">here<\/a>!<\/em><br \/>\n&nbsp;<br \/>\n&nbsp;<br \/>\n&nbsp;<br \/>\n&nbsp;<br \/>\n[ulp id=&#8217;tgm4cmEWcKUikZNn&#8217;]<\/p>\n<div class=\"toc\">\n<h4>Table Of Contents<\/h4>\n<dl>\n<dt><a href=\"#scaling\">1. Scaling<\/a><\/dt>\n<dd>\n<dl>\n<dt><a href=\"#scaling_read_requests\">1.1. Scaling Read Requests<\/a><\/dt>\n<dt><a href=\"#scaling_write_requests\">1.2. Scaling Write Requests<\/a><\/dt>\n<dt><a href=\"#scaling_data\">1.3. Scaling Data<\/a><\/dt>\n<\/dl>\n<\/dd>\n<dt><a href=\"#replication\">2. Replication<\/a><\/dt>\n<dd>\n<dl>\n<dt><a href=\"#algorithm\">2.1. Algorithm<\/a><\/dt>\n<dt><a href=\"#simple_replication_with_interface\">2.2. Simple Replication with the Admin Interface<\/a><\/dt>\n<dt><a href=\"#replication_in_detail\">2.3. Replication in Detail<\/a><\/dt>\n<\/dl>\n<\/dd>\n<dt><a href=\"#conflict_management\">3. Conflict management<\/a><\/dt>\n<dt><a href=\"#http:\/\/guide.couchdb.org\/draft\/balancing.html#balancing\">4. Load Balancing<\/a><\/dt>\n<dd>\n<dl>\n<dt><a href=\"#having_a_backup\">4.1. Having a Backup<\/a><\/dt>\n<\/dl>\n<\/dd>\n<dt><a href=\"#clustering\">5. Clustering<\/a><\/dt>\n<dd>\n<dl>\n<dt><a href=\"#introducing_couchDB_lounge\">5.1. Introducing CouchDB Lounge<\/a><\/dt>\n<dt><a href=\"#consistent_hashing\">5.2. Consistent Hashing<\/a><\/dt>\n<\/dl>\n<\/dd>\n<dt><a href=\"#distributed_load_testing\">6. Distributed load testing<\/a><\/dt>\n<\/dl>\n<\/div>\n<h2><a name=\"scaling\"><\/a>1. Scaling<\/h2>\n<p>Scaling, or scalability, doesn\u2019t refer to a specific technique or technology, but rather is an attribute of a specific architecture.<\/p>\n<p>In this lesson we shall cover the scaling of CouchDB, a popular NoSQL database. For CouchDB, we can scale three general properties:<\/p>\n<ol>\n<li>Read requests<\/li>\n<li>Write requests<\/li>\n<li>Data<\/li>\n<\/ol>\n<h3><a name=\"scaling_read_requests\"><\/a>1.1. Scaling Read Requests<\/h3>\n<p>A read request retrieves a piece of information from the database. It follows these stations within CouchDB: First, the HTTP server module needs to accept the request. For that, it opens a socket to send over the data. The next station is the HTTP request handle module, which analyzes the request and directs it to the appropriate submodule inside CouchDB. For single documents, the request then gets passed to the database module where the data for the document is looked up on the filesystem and returned all the way up again.<\/p>\n<p>All this takes processing time and additionally there must be enough sockets (or file descriptors) available. The storage backend of the server must be able to fulfill all these read requests. There are a few more things that can limit a system to accept more read requests; the basic point here is that a single server can process only so many concurrent requests.<\/p>\n<p>The nice thing about read requests is that they can be cached. Often-used items can be held in memory and can be returned at a much higher speed. Requests that can use this cache don\u2019t ever hit database and are thus less IO Operation intensive.<\/p>\n<h3><a name=\"scaling_write_requests\"><\/a>1.2. Scaling Write Requests<\/h3>\n<p>A write request is similar to a read request which reads a piece of data from disk, but it writes it back after modifying it. Remember, the nice thing about reads is that they\u2019re cacheable. A cache must be notified when a write changes the underlying data, or the clients must be notified to not use the cache. If we have multiple servers for scaling reads, a write must occur on all servers.<\/p>\n<h3><a name=\"scaling_data\"><\/a>1.3. Scaling Data<\/h3>\n<p>The third way of scaling is scaling data. Today\u2019s hard drives are cheap and provie a lot of capacity, and they will only get better in the future, but there is only so much data a single server can make sensible use of. It must also maintain one or more indexes to the data, thus it uses even moredisk space.<\/p>\n<p>The solution is to chop the data into manageable chunks and put each chunk on a separate server. In this way, all servers with a chunk will form a cluster that holds all your data.<\/p>\n<p>While we are taking separate looks at scaling of reads, writes, and data, these rarely occur isolated. Decisions to scale one will affect the others.<\/p>\n<h2><a name=\"replication\"><\/a>2. Replication<\/h2>\n<p>A replicator simply connects to two DBs as a client, then reads from one and writes to the other. Push replication is reading the local data and updating the remote DB; pull replication is vice versa.<\/p>\n<ol>\n<li>The replicator is actually an independent Erlang application, running on its own process. It connects to both CouchDBs, then reads records from one and writes them to the other.<\/li>\n<li>CouchDB has no way of knowing who is a normal client and who is a replicator (let alone whether the replication is push or pull). It all looks like client connections. Some of them read records, some of them write records.<\/li>\n<\/ol>\n<p>The CouchDB Replication protocol is a synchronization protocol for synchronizing documents between 2 peers over HTTP\/1.1.<\/p>\n<h3><a name=\"algorithm\"><\/a>2.1. Algorithm<\/h3>\n<p>The replication algorithm can be explained as follows:<\/p>\n<ol>\n<li>Assign an unique identifier to the source Database. Most of the time it will be the URI.<\/li>\n<li>Save this identifier in a special Document named _local\/&lt;uniqueid&gt; on the Target database. This document isn&#8217;t replicated. It will collect the last Source sequence ID, the Checkpoint, from the previous replication process.<\/li>\n<li>Get the Source changes feed by passing it the Checkpoint using the since parameter by calling the \/&lt;source&gt;\/_changes URL. The changes feed only return a list of current revisions.<\/li>\n<\/ol>\n<p><strong>Note:<\/strong> This step can be performed continuously using the feed=longpoll or feed=continuous parameters. Then the feed will continuously get the changes.<\/p>\n<p>Collect a group of Document\/Revisions ID pairs from the changes feed and send them to the target databases on the \/&lt;target&gt;\/_revs_diffs URL. The result will contain the list of revisions NOT in the Target database.<\/p>\n<p>GET each revision from the source Database by calling the URL \/&lt;source&gt;\/&lt;docid&gt;?revs=true&amp;rev=&lt;revision&gt; . This will get the document with the parent revisions. Also don&#8217;t forget to get attachements that aren&#8217;t already stored at the target. As an optimization, we can use the HTTP multipart API to retrieve them all.<\/p>\n<p>Collect a group of revisions fetched at the previous step and store them on the target database using the Bulk Docs API with the new_edit: false JSON property to preserve their revisions ID.<\/p>\n<p>After the group of revision is stored on the Target Database, save the new Checkpoint on the Source database.<\/p>\n<p><strong>Note:<\/strong> Even if some revisions have been ignored, the sequence should be taken into consideration for the Checkpoint.<\/p>\n<p>To compare non numeric sequence ordering, we will have to keep an ordered list of the sequences IDS as they appear in the _changes feed and compare their indices.<\/p>\n<p>One thing to keep in mind is that the _users database, the design documents and the security attributes of the databases are not being replicated.<\/p>\n<p>For, the _users database and the design documents, there is solution: We just need to run the process as administrator in order to replicate them.<\/p>\n<p>Only server and database admins can create design docs and access views:<\/p>\n<pre class=\"brush:bash\">curl -H 'Content-Type: application\/json' -X POST http:\/\/localhost:5984\/_replicate -d ' {\"source\": \"http:\/\/admin:admin_password@production:5984\/foo\", \"target\": \"http:\/\/admin:admin_password@stage:5984\/foo\", \"create_target\": true, \"continuous\": true} '<\/pre>\n<p>This POST request will also work with the _users database.<\/p>\n<p>Replication is a one-off operation: we send an HTTP request to CouchDB that includes a source and a target database, and CouchDB will send the changes from the source to the target. That is all. Granted, calling something world-class and then only needing one sentence to explain it does seem odd. But part of the reason why CouchDB\u2019s replication is so powerful lies in its simplicity.<\/p>\n<p>Let\u2019s see what replication looks like:<\/p>\n<pre class=\"brush:bash\">POST \/_replicate HTTP\/1.1\r\n{\"source\":\"database\",\"target\":\"http:\/\/example.org\/database\"} -H \"Content-Type: application\/json\"<\/pre>\n<p>This call sends all the documents in the local database database to the remote database http:\/\/example.org\/database. A database is considered \u201clocal\u201d when it is on the same CouchDB instance you send the POST \/_replicate HTTP request to. All other instances of CouchDB are \u201cremote.\u201d<\/p>\n<p>To send changes from the target to the source database, just make the same HTTP requests, only with source and target database swapped.<\/p>\n<pre class=\"brush:bash\">POST \/_replicate HTTP\/1.1\r\nContent-Type: application\/json\r\n{\"source\":\"http:\/\/example.org\/database\",\"target\":\"database\"}<\/pre>\n<p>A remote database is identified by the same URL we use to talk to it. CouchDB replication works over HTTP using the same mechanisms that are available to us. This example shows that replication is a unidirectional process. Documents are copied from one database to another and not automatically vice versa. If we want bidirectional replication, we trigger two replications with source and target swapped.<\/p>\n<p>When we ask CouchDB to replicate one database to another, it will go and compare the two databases to find out which documents on the source differ from the target and then submit a batch of the changed documents to the target until all changes are transferred. Changes include new documents, changed documents, and deleted documents. Documents that already exist on the target in the same revision are not transferred; only newer revisions are.<\/p>\n<p>Databases in CouchDB have a sequence number that gets incremented every time the database is changed. CouchDB remembers what changes came with which sequence number. That way, CouchDB can answer questions like, \u201cWhat changed in database A between sequence number 53 and now?\u201d by returning a list of new and changed documents. Finding the differences between databases this way is an efficient operation. It also adds to the robustness of replication.<\/p>\n<p>We can use replication on a single CouchDB instance to create snapshots of our databases to be able to test code changes without risking data loss or to be able to refer back to older states of our database. But replication gets really fun if we use two or more different computers, potentially geographically spread out.<\/p>\n<p>With different servers, potentially hundreds or thousands of miles apart, problems are bound to happen. Servers crash, network connections break off, things go wrong. When a replication process is interrupted, it leaves two replicating CouchDBs in an inconsistent state. Then, when the problems are gone and we trigger replication again, it continues where it left off.<\/p>\n<h3><a name=\"simple_replication_with_interface\"><\/a>2.2. Simple Replication with the Admin Interface<\/h3>\n<p>We can run replication from your web browser using Futon, CouchDB\u2019s built-in administration interface. Start CouchDB and open the url to http:\/\/127.0.0.1:5984\/_utils\/. On the righthand side, there is a list of things to visit in Futon. Click on \u201cReplication.\u201d<\/p>\n<p>Futon will show an interface to start replication. We can specify a source and a target by either picking a database from the list of local databases or filling in the URL of a remote database.<\/p>\n<p>Click on the Replicate button, wait a bit, and have a look at the lower half of the screen where CouchDB gives us some statistics about the replication run or, if an error occurred, an explanatory message.<\/p>\n<h3><a name=\"replication_in_detail\"><\/a>2.3. Replication in Detail<\/h3>\n<p>So far, we\u2019ve skipped over the result from a replication request. Here\u2019s an example:<\/p>\n<pre class=\"brush:js\">{\r\n  \"ok\": true,\r\n  \"source_last_seq\": 10,\r\n  \"session_id\": \"c7a2bbbf9e4af774de3049eb86eaa447\",\r\n  \"history\": [\r\n    {\r\n      \"session_id\": \"c7a2bbbf9e4af774de3049eb86eaa447\",\r\n      \"start_time\": \"Mon, 24 Aug 2009 09:36:46 GMT\",\r\n      \"end_time\": \"Mon, 24 Aug 2009 09:36:47 GMT\",\r\n      \"start_last_seq\": 0,\r\n      \"end_last_seq\": 1,\r\n      \"recorded_seq\": 1,\r\n      \"missing_checked\": 0,\r\n      \"missing_found\": 1,\r\n      \"docs_read\": 1,\r\n      \"docs_written\": 1,\r\n      \"doc_write_failures\": 0,\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<p>The &#8220;ok&#8221;: true part, similar to other responses, tells us everything went well. source_last_seq includes the source\u2019s update_seq value that was considered by this replication. Each replication request is assigned a session_id, which is just a UUID.<\/p>\n<p>The next bit is the replication history. CouchDB maintains a list of history sessions for future reference. The history array is currently capped at 50 entries. Each unique replication trigger object (the JSON string that includes the source and target databases as well as potential options) gets its own history.<\/p>\n<p>The session_id is recorded here again for convenience. The start and end time for the replication session are also recorded. The _last_seq denotes the update_seqs that were valid at the beginning and the end of the session. recorded_seq is the update_seq of the target again. It\u2019s different from end_last_seq if a replication process dies in the middle and is restarted. missing_checked is the number of docs on the target that are already there and don\u2019t need to be replicated. missing_found is the number of missing documents on the source.<\/p>\n<p>The last three\u2014docs_read, docs_written, and doc_write_failures\u2014show how many documents we read from the source, wrote to the target, and how many failed. If all is well, _read and _written are identical and doc_write_failures is 0. If not, something went wrong during replication. Possible failures are a server crash on either side, a lost network connection, or a validate_doc_update function rejecting a document write.<\/p>\n<p>One common scenario is triggering replication on nodes that have admin accounts enabled. Creating design documents is restricted to admins, and if the replication is triggered without admin credentials, writing the design documents during replication will fail and be recorded as doc_write_failures. We have admins and need to include the credentials in the replication request:<\/p>\n<pre class=\"brush:bash\">&gt; curl -X POST http:\/\/127.0.0.1:5984\/_replicate  -d '{\"source\":\"http:\/\/example.org\/database\", \"target\":\"http:\/\/admin:password@127.0.0.1:5984\/database\"}' -H \"Content-Type: application\/json\"<\/pre>\n<h4>2.3.1 Continuous Replication<\/h4>\n<p>When we add &#8220;continuous&#8221;: true to the replication trigger object, CouchDB will not stop after replicating all missing documents from the source to the target. It will listen on CouchDB\u2019s _changes API  and automatically replicate over any new docs as they come into the source to the target. In fact, they are not replicated right away; there\u2019s a complex algorithm determining the ideal moment to replicate for maximum performance.<\/p>\n<pre class=\"brush:bash\">&gt; curl -X POST http:\/\/127.0.0.1:5984\/_replicate -d '{\"source\":\"db\", \"target\":\"db-replica\", \"continuous\":true}' -H \"Content-Type: application\/json\"<\/pre>\n<p>CouchDB doesn\u2019t remember continuous replications over a server restart. For the time being, we need to trigger them again when you restart CouchDB. In the future, CouchDB will allow us to define permanent continuous replications that survive a server restart without having to do anything.<\/p>\n<h2><a name=\"conflict_management\"><\/a>3. Conflict management<\/h2>\n<p>CouchDB has a mechanism to maintain continuous replication, so one can keep a whole set of computers in sync with the same data, whenever a network connection is available.<\/p>\n<p>When we replicate two databases in CouchDB and we face conflicting changes, CouchDB will detect this and will flag the affected document with the special attribute &#8220;_conflicts&#8221;:true. Next, CouchDB determines which of the changes will be stored as the latest revision (remember, documents in CouchDB are versioned). The version that gets picked to be the latest revision is the winning revision. The losing revision gets stored as the previous revision.<\/p>\n<p>CouchDB does not attempt to merge the conflicting revision. Our application dictates how the merging should be done. The choice of picking the winning revision is arbitrary.<\/p>\n<p>Replication guarantees that conflicts are detected and that each instance of CouchDB makes the same choice regarding winners and losers, independent of all the other instances. Here a deterministic algorithm determines the order of the conflicting revision. After replication, all instances taking part have the same data. The data set is said to be in a consistent state. If we ask any instance for a document, we will get the same answer regardless which one we ask.<\/p>\n<p>Whether or not CouchDB picked the version that our application needs, we need to go and resolve the conflict, just as we need to resolve a conflict in a version control system like Subversion by merging them and save it as the now latest revision. Replicate again and our resolution will populate over to all other instances of CouchDB. Our conflict resolving on one node could lead to further conflicts, all of which will need to be addressed, but eventually, we will end up with a conflict-free database on all nodes.<\/p>\n<h2><a name=\"http:\/\/guide.couchdb.org\/draft\/balancing.html#balancing\"><\/a>4. Load Balancing<\/h2>\n<h3><a name=\"having_a_backup\"><\/a>4.1. Having a Backup<\/h3>\n<p>Whatever the cause is, we want to make sure that the service we are providing is resilient against failure. The road to resilience is a road of finding and removing single points of failure. A server\u2019s power supply can fail. To keep the server from turning off during such an event, most come with at least two power supplies. To take this further, we could get a server where everything is duplicated (or more). It is much cheaper to get two similar servers where the one can take over if the other has a problem. However, we need to make sure both servers have the same set of data in order to switch them without a user noticing.<\/p>\n<p>Removing all single points of failure will give us a highly available or a fault-tolerant system. The order of tolerance is restrained only by our budget. If we can\u2019t afford to lose a customer\u2019s shopping cart in any event, we need to store it on at least two servers in at least two far apart geographical locations.<\/p>\n<p>Before we dive into setting up a highly available CouchDB system, let\u2019s look at another situation. Suppose that an Online Shopping Site suddenly faces a lot more traffic than usual and that the customers are complaining for the site being \u201cslow\u201d. Now, a probable solution for such a scenerio would be to setup a second server which will take some load from first server when the load exceeds a certain threshold.<\/p>\n<p>The solution to the outlined problem looks a lot like the earlier one for providing a fault-tolerant setup: install a second server and synchronize all data. The difference is that with fault tolerance, the second server just sits there and waits for the first one to fail. In the server-overload case, a second server helps answer all incoming requests. This case is not fault-tolerant: if one server crashes, the other will get all the requests and will likely break down, or provide a very slow service, neither of which is acceptable.<\/p>\n<p>Keep in mind that although the solutions look similar, high availability and fault tolerance are not the same. We\u2019ll get back to the second scenario later on, but first we will take a look at how to set up a fault-tolerant CouchDB system.<\/p>\n<h2><a name=\"clustering\"><\/a>5. Clustering<\/h2>\n<p>In this chapter we\u2019ll be dealing with the aspect of putting together a partitioned or sharded cluster that will have to grow at an increasing rate over time from day one.<\/p>\n<p>We\u2019ll look at request and response dispatch in a CouchDB cluster with stable nodes. Then we\u2019ll cover how to add redundant hot-failover twin nodes, so there is no worry about losing machines. In a large cluster, we should plan for 5\u201310% of our machines to experience some sort of failure or reduced performance, so cluster design must prevent node failures from affecting reliability. Finally, we\u2019ll look at adjusting cluster layout dynamically by splitting or merging nodes using replication.<\/p>\n<h3><a name=\"introducing_couchDB_lounge\"><\/a>5.1. Introducing CouchDB Lounge<\/h3>\n<p>CouchDB Lounge is a proxy-based partitioning and clustering application, originally developed for Meebo, a web-based instant messaging service. Lounge comes with two major components: one that handles simple GET and PUT requests for documents, and another that distributes view requests.<\/p>\n<p>The dumbproxy handles simple requests for anything that isn\u2019t a CouchDB view. This comes as a module for nginx, a high-performance reverse HTTP proxy. Because of the way reverse HTTP proxies work, this automatically allows configurable security, encryption, load distribution, compression, and, of course, aggressive caching of our database resources.<\/p>\n<p>The smartproxy handles only CouchDB view requests, and dispatches them to all the other nodes in the cluster so as to distribute the work, making view performance a function of the cluster\u2019s cumulative processing power. This comes as a daemon for Twisted, a popular and high-performance event-driven network programming framework for Python.<\/p>\n<h3><a name=\"consistent_hashing\"><\/a>5.2. Consistent Hashing<\/h3>\n<p>CouchDB\u2019s storage model uses unique IDs to save and retrieve documents. Sitting at the core of Lounge is a simple method of hashing the document IDs. Lounge then uses the first few characters of this hash to determine which shard to dispatch the request to. We can configure this behavior by writing a shard map for Lounge, which is just a simple text configuration file.<\/p>\n<p>Because Lounge allocates a portion of the hash (known as a keyspace) to each node, we can add as many nodes as we like. Because the hash function produces hexadecimal strings that bear no apparent relation to our DocIDs, and because we dispatch requests based on the first few characters, we ensure that all nodes see roughly equal load. And because the hash function is consistent, Lounge will take any arbitrary DocID from an HTTP request URI and point it to the same node each time.<\/p>\n<p>This idea of splitting a collection of shards based on a keyspace is commonly illustrated as a ring, with the hash wrapped around the outside. Each tic mark designates the boundaries in the keyspace between two partitions. The hash function maps from document IDs to positions on the ring. The ring is continuous so that we can always add more nodes by splitting a single partition into pieces. With four physical servers, we can allocate the keyspace into 16 independent partitions by distributing them across the servers like so:<\/p>\n<div class=\"wp-caption aligncenter\">\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td width=\"19\">A<\/td>\n<td width=\"122\">0,1,2,3<\/td>\n<\/tr>\n<tr>\n<td width=\"19\">B<\/td>\n<td width=\"122\">4,5,6,7<\/td>\n<\/tr>\n<tr>\n<td width=\"19\">C<\/td>\n<td width=\"122\">8,9,a,b<\/td>\n<\/tr>\n<tr>\n<td width=\"19\">D<\/td>\n<td width=\"122\">c,d,e,f<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p class=\"wp-caption-text\">Table 1<\/p>\n<\/div>\n<p>If the hash of the DocID starts with 0, it would be dispatched to shard A. Similarly for 1, 2, or 3. Whereas, if the hash started with c, d, e, or f, it would be dispatched to shard D. As a full example, the hash 71db329b58378c8fa8876f0ec04c72e5 is mapped to the node B, database 7 in the table just shown. This could map to http:\/\/B.couches.local\/db-7\/ on our backend cluster. In this way, the hash table is just a mapping from hashes to backend database URIs. Don\u2019t worry if this all sounds very complex; all we have to do is provide a mapping of shards to nodes and Lounge will build the hash ring appropriately\u2014so no need to get our hands dirty if we don\u2019t want to.<\/p>\n<p>To frame the same concept with web architecture, because CouchDB uses HTTP, the proxy can partition documents according to the request URL, without inspecting the body. This is a core principle behind REST and is one of the many benefits using HTTP affords us. In practice, this is accomplished by running the hash function against the request URI and comparing the result to find the portion of the keyspace allocated. Lounge then looks up the associated shard for the hash in a configuration table, forwarding the HTTP request to the backend CouchDB server.<\/p>\n<p>Consistent hashing is a simple way to ensure that we can always find the documents we saved, while balancing storage load evenly across partitions. Because the hash function is simple (it is based on CRC32), we are free to implement our own HTTP intermediaries or clients that can similarly resolve requests to the correct physical location of our data.<\/p>\n<p><!--\n\n\n<h2><a name=\"distributed_load_testing\"><\/a>6. Distributed load testing<\/h2>\n\n\n\nThere are many tools available that allow us to create tests customized for our application. However, when creating a distributed system it can be difficult to actually generate enough load to push our system to its maximum capacity.\n\nIn order to stress test a distributed system, we will need a distributed load testing tool. Tsung is a distributed load and stress testing tool that we will use for the example this chapter. We will be using Tsung on Ubuntu, but these steps can be easily adapted to other platforms. Tsung can generate GET and POST HTTP requests and PUT and DELETE HTTP requests. Some of Tsung\u2019s features include:\n\n\n\n<ul>\n\n\n<li>Monitoring of client operating systems\u2019 CPU, memory, and network traffic<\/li>\n\n\n\n\n<li>Simulation of dynamic sessions, described in an XML configuration file<\/li>\n\n\n\n\n<li>Randomized traffic patterns based on defined probabilities<\/li>\n\n\n\n\n<li>Recording of HTTP sessions for later playback during a test<\/li>\n\n\n<\/ul>\n\n\n\n<strong>HTML reports and graphs<\/strong>\n\nLike CouchDB, Tsung is developed in Erlang. Depending on the number of testing servers used, Tsung can simulate hundreds of thousands of concurrent users. Given enough servers, we could even simulate millions of concurrent users. In addition to being able to test HTTP servers, Tsung can also test WebDAV, SOAP, PostgreSQL, MySQL, LDAP, and Jabber\/XMPP servers.\n\n\n\n<h3><a name=\"installing_tsung\"><\/a>6.1. Installing Tsung<\/h3>\n\n\n\nIn the following examples, we will have two testing clients with domain names of test- a.example.com and test-b.example.com, and we will be testing our couch-proxy.example.com (load balancer), couch-master.example.com (CouchDB master node), couch- a.example.com (CouchDB read-only node), couch-b.example.com (CouchDB read-only node), couch-c.example.com (CouchDB read-only node) servers.\n\nInstall Erlang on both test-a.example.com and test-b.example.com:\n\n\n\n<pre class=\"brush:bash\">sudo aptitude install erlang<\/pre>\n\n\n\nInstall gnuplot on both test-a.example.com and test-b.example.com:\n\n\n\n<pre class=\"brush:bash\">sudo aptitude install gnuplot<\/pre>\n\n\n\nInstall Perl\u2019s Template Toolkit on both test-a.example.com and test-b.example.com:\n\n\n\n<pre class=\"brush:bash\">sudo aptitude install libtemplate-perl<\/pre>\n\n\n\nInstall Python\u2019s Matplotlib on both test-a.example.com and test-b.example.com:\n\n\n\n<pre class=\"brush:bash\">sudo aptitude install python-matplotlib<\/pre>\n\n\n\nDownload the latest version of Tsung on both test-a.example.com and test-b.example.com. As of this writing, this was version 1.5.0:\n\n\n\n<pre class=\"brush:bash\">wget http:\/\/tsung.erlang-projects.org\/dist\/tsung-1.5.0.tar.gz<\/pre>\n\n\n\nExtract the downloaded file on both test-a.example.com and test-b.example.com:\n\n\n\n<pre class=\"brush:bash\">tar -xzf tsung-1.5.0.tar.gz<\/pre>\n\n\n\nOn both test-a.example.com and test-b.example.com, change into the tsung-1.5.0 directory:\n\n\n\n<pre class=\"brush:bash\">cd tsung-1.5.0<\/pre>\n\n\n\nConfigure on both test-a.example.com and test-b.example.com:\n\n\n\n<pre class=\"brush:bash\">sudo .\/configure<\/pre>\n\n\n\nMake on both test-a.example.com and test-b.example.com:\n\n\n\n<pre class=\"brush:bash\">sudo make<\/pre>\n\n\n\nInstall on both test-a.example.com and test-b.example.com:\n\n\n\n<pre class=\"brush:bash\">sudo make install<\/pre>\n\n\n\nWe will be launching our tests from test-a.example.com, so one will need to be able to login from this client to test-b.example.com without using a password. We will do this using public key authentication, but one could instead use ssh-agent or rsh.\n\nInstall the OpenSSH server on test-a.example.com and test-b.example.com, if it is not already installed:\n\n\n\n<pre class=\"brush:bash\">sudo aptitude install openssh-server<\/pre>\n\n\n\nGenerate an SSH key on test-a.example.com:\n\n\n\n<pre class=\"brush:bash\">ssh-keygen<\/pre>\n\n\n\nPick the default file in which to save the key, likely ~\/.ssh\/id_rsa. Enter a passphrase.\n\nFrom test-a.example.com, copy the ~\/.ssh\/id_rsa public key to test-b.example.com:\n\n\n\n<pre class=\"brush:bash\">scp ~\/.ssh\/id_rsa.pub test-b.example.com:~<\/pre>\n\n\n\nIf this is the first time using SSH to connect from test-a.example.com to test-b.exam ple.com, we will need to accept the RSA key fingerprint. Enter the user\u2019s password and we should see output indicating that the file has been copied.\n\nLog into test-b.example.com and add the public key copied from test-a.example.com to test-b.example.com\u2019s list of authorized keys:\n\ncat ~\/id_rsa.pub >> ~\/.ssh\/authorized_keys Still on test-b.example.com, remove the public key that was copied over from test- a.example.com: rm ~\/id_rsa.pub\n\nTo test, try logging into test-b from test-a.example.com, accepting the RSA key fingerprint if prompted, and it should not be prompted for a password: ssh test-b\n\nIf there is additional testing clients, repeat the above steps for each, setting up test- a.example.com to be able to log into each testing machine without using a password. The more testing servers you have, the more simulated load can be generated.\n\nOddly enough, test-a.example.com will also need to be able to log into itself without using a password. To add its own public key to its list of authorized keys, from test- a.example.com:\n\n\n\n<pre class=\"brush:bash\">cat ~\/.ssh\/id_rsa.pub &gt;&gt; ~\/.ssh\/authorized_keys<\/pre>\n\n\n\nTo test, try logging into test-a from test-a.example.com (yes, from itself), accepting the RSA key fingerprint if prompted, and it should not be prompted for a password: ssh test-a\n\n\n\n<h3><a name=\"configuring_tsung\"><\/a>6.2. Configuring Tsung<\/h3>\n\n\n\nTsung comes with an example configuration file for doing distributed HTTP testing, which we\u2019ll find in \/usr\/share\/doc\/tsung\/examples\/http_distributed.xml. We will create our own configuration file, saved to ~\/http_distributed_couch_proxy.xml (see the Tsung User\u2019s manual at http:\/\/tsung.erlang-projects.org\/user_manual.html):\n\n\n\n<pre class=\"brush:xml\">&lt;?xml version=\"1.0\"?&gt; &lt;!DOCTYPE tsung SYSTEM \"\/usr\/share\/tsung\/tsung-1.0.dtd\"&gt; &lt;tsung loglevel=\"notice\" version=\"1.0\"&gt;\r\n&lt;!-- Client side setup --&gt; &lt;clients&gt;\r\n&lt;client host=\"test-a\" weight=\"1\" maxusers=\"10000\" cpu=\"4\"\/&gt;\r\n&lt;client host=\"test-b\" weight=\"1\" maxusers=\"10000\" cpu=\"4\"\/&gt; &lt;\/clients&gt;\r\n&lt;!-- Server side setup --&gt; &lt;servers&gt;\r\n&lt;server host=\"couch-proxy\" port=\"80\" type=\"tcp\"\/&gt; &lt;\/servers&gt;\r\n&lt;!-- Load setup --&gt; &lt;load&gt;\r\n&lt;arrivalphase phase=\"1\" duration=\"5\" unit=\"minute\"&gt; &lt;users arrivalrate=\"200\" unit=\"second\"&gt;&lt;\/users&gt;\r\n&lt;\/arrivalphase&gt; &lt;\/load&gt;\r\n&lt;!-- Sessions setup --&gt; &lt;sessions&gt;\r\n&lt;session name=\"post_get\" probability=\"2.5\" type=\"ts_http\"&gt; &lt;thinktime value=\"10\" random=\"true\"\/&gt; &lt;setdynvars sourcetype=\"random_number\" start=\"2008\" end=\"2011\"&gt;\r\n&lt;var name=\"yyyy\"\/&gt; &lt;\/setdynvars&gt; &lt;setdynvars sourcetype=\"random_number\" start=\"10\" end=\"12\"&gt;\r\n&lt;var name=\"mm\"\/&gt; &lt;\/setdynvars&gt; &lt;setdynvars sourcetype=\"random_number\" start=\"10\" end=\"28\"&gt;\r\n&lt;var name=\"dd\"\/&gt; &lt;\/setdynvars&gt; &lt;request subst=\"true\"&gt;\r\n&lt;match do=\"abort\" when=\"nomatch\"&gt;201 Created&lt;\/match&gt; &lt;dyn_variable name=\"id\" jsonpath=\"$.id\"\/&gt; &lt;dyn_variable name=\"rev\" jsonpath=\"$.rev\"\/&gt; &lt;http\r\n&gt;\r\nmethod=\"POST\" url=\"\/api\" content_type=\"application\/json\" contents=\"{\r\n&amp;quot;date&amp;quot;:[ &amp;quot;%%_yyyy%%&amp;quot;, &amp;quot;%%_mm%%&amp;quot;, &amp;quot;%%_dd%%&amp;quot;\t]\r\n}\"\r\n&lt;http_header name=\"Accept\" value=\"application\/json\"\/&gt; &lt;\/http&gt;\r\n&lt;\/request&gt; &lt;for from=\"0\" to=\"9\" incr=\"1\" var=\"x\"&gt;\r\n&lt;thinktime value=\"10\" random=\"true\"\/&gt; &lt;request subst=\"true\"&gt;\r\n&lt;match do=\"abort\" when=\"nomatch\"&gt;304 Not Modified&lt;\/match&gt; &lt;http method=\"GET\" url=\"\/api\/%%_id%%\"&gt;\r\n&lt;http_header name=\"If-None-Match\" value=\"&amp;quot;%%_rev%%&amp;quot;\"\/&gt;\r\n&lt;http_header name=\"Accept\" value=\"application\/json\"\/&gt; &lt;\/http&gt;\r\n&lt;\/request&gt; &lt;\/for&gt;\r\n&lt;\/session&gt;\r\n&lt;session name=\"put_get\" probability=\"2.5\" type=\"ts_http\"&gt; &lt;thinktime value=\"10\" random=\"true\"\/&gt; &lt;setdynvars sourcetype=\"random_string\" length=\"32\"&gt;\r\n&lt;var name=\"id\"\/&gt; &lt;\/setdynvars&gt; &lt;setdynvars sourcetype=\"random_number\" start=\"2008\" end=\"2011\"&gt;\r\n&lt;var name=\"yyyy\"\/&gt; &lt;\/setdynvars&gt; &lt;setdynvars sourcetype=\"random_number\" start=\"10\" end=\"12\"&gt;\r\n&lt;var name=\"mm\"\/&gt; &lt;\/setdynvars&gt; &lt;setdynvars sourcetype=\"random_number\" start=\"10\" end=\"28\"&gt;\r\n&lt;var name=\"dd\"\/&gt; &lt;\/setdynvars&gt;\r\n&lt;request subst=\"true\"&gt; &lt;match do=\"abort\" when=\"nomatch\"&gt;201 Created&lt;\/match&gt; &lt;dyn_variable name=\"rev\" jsonpath=\"$.rev\"\/&gt; &lt;http\r\nmethod=\"PUT\" url=\"\/api\/%%_id%%\" content_type=\"application\/json\" contents=\"{\r\n&amp;quot;date&amp;quot;:[ &amp;quot;%%_yyyy%%&amp;quot;, &amp;quot;%%_mm%%&amp;quot;, &amp;quot;%%_dd%%&amp;quot;\r\n] }\"\r\n&lt;http_header name=\"Accept\" value=\"application\/json\"\/&gt; &lt;\/http&gt;\r\n&lt;\/request&gt; &lt;for from=\"0\" to=\"9\" incr=\"1\" var=\"x\"&gt;\r\n&lt;thinktime value=\"10\" random=\"true\"\/&gt; &lt;request subst=\"true\"&gt;\r\n&lt;match do=\"abort\" when=\"nomatch\"&gt;304 Not Modified&lt;\/match&gt; &lt;dyn_variable name=\"rev\" jsonpath=\"$._rev\"\/&gt; &lt;http method=\"GET\" url=\"\/api\/%%_id%%\"&gt;\r\n&lt;http_header name=\"If-None-Match\" value=\"&amp;quot;%%_rev%%&amp;quot;\"\/&gt;\r\n&lt;http_header name=\"Accept\" value=\"application\/json\"\/&gt; &lt;\/http&gt;\r\n&lt;\/request&gt; &lt;\/for&gt;\r\n&lt;\/session&gt;\r\n&lt;session name=\"view_pagination\" probability=\"20\" type=\"ts_http\"&gt; &lt;thinktime value=\"10\" random=\"true\"\/&gt; &lt;request subst=\"true\"&gt;\r\n&lt;http method=\"GET\" url=\"\/api\/_design\/default\/_view\/dates?reduce=false&amp;amp;skip=0&amp;amp;limit=10\"\r\n&gt;\r\n&lt;http_header name=\"Accept\" value=\"application\/json\"\/&gt; &lt;\/http&gt;\r\n&lt;\/request&gt; &lt;for from=\"10\" to=\"90\" incr=\"10\" var=\"skip\"&gt;\r\n&lt;thinktime value=\"10\" random=\"true\"\/&gt; &lt;request subst=\"true\"&gt;\r\n&lt;http method=\"GET\" url=\"\/api\/_design\/default\/_view\/dates?reduce=false&amp;amp;skip=%%_skip%%\r\n&amp;amp;limit=10&amp;amp;stale=ok\" &gt;\r\n&lt;http_header name=\"Accept\" value=\"application\/json\"\/&gt; &lt;\/http&gt;\r\n&lt;\/request&gt; &lt;\/for&gt;\r\n&lt;\/session&gt;\r\n&lt;session name=\"view_grouped\" probability=\"75\" type=\"ts_http\"&gt; &lt;thinktime value=\"10\" random=\"true\"\/&gt; &lt;request&gt;\r\n&lt;http method=\"GET\" url=\"\/api\/_design\/default\/_view\/dates?group_level=1\"\r\n&gt;\r\n&lt;http_header name=\"Accept\" value=\"application\/json\"\/&gt; &lt;\/http&gt;\r\n&lt;\/request&gt; &lt;thinktime value=\"10\" random=\"true\"\/&gt; &lt;request&gt;\r\n&lt;http method=\"GET\" url=\"\/api\/_design\/default\/_view\/dates?group_level=2&amp;amp;stale=ok\"\r\n&gt;\r\n&lt;http_header name=\"Accept\" value=\"application\/json\"\/&gt; &lt;\/http&gt;\r\n&lt;\/request&gt; &lt;\/session&gt;\r\n&lt;\/sessions&gt; &lt;\/tsung&gt;\r\n<\/pre>\n\n\n\nLet\u2019s walk through some parts of this configuration file. First, the clients element:\n\n\n\n<pre class=\"brush:xml\">&lt;!-- Client side setup --&gt; &lt;clients&gt;\r\n&lt;client host=\"test-a\" weight=\"1\" maxusers=\"10000\" cpu=\"4\"\/&gt;\r\n&lt;client host=\"test-b\" weight=\"1\" maxusers=\"10000\" cpu=\"4\"\/&gt; &lt;\/clients&gt;\r\n<\/pre>\n\n\n\nThis clients element contains a list of clients from which tests may be launched. The more clients, the greater the simulated load that can be generated. Each client needs to be configured using its local hostname or IP address using the host attribute. The weight attribute assigns a relative weight to the client since some clients may be faster and able to start more sessions than other clients. The maxusers attribute defines a maximum number of users to simulate on this client. The cpu attribute declares how many Erlang virtual machines Tsung should use and should be the same as the number of CPUs that are available to the client.\n\n<strong>The servers element:<\/strong>\n\n\n\n<pre class=\"brush:xml\">&lt;!-- Server side setup --&gt; &lt;servers&gt;\r\n&lt;server host=\"couch-proxy\" port=\"80\" type=\"tcp\"\/&gt; &lt;\/servers&gt;<\/pre>\n\n\n\nThe servers element contains a list of servers to be tested. Each server needs to be configured using its local hostname or IP address using the host attribute. The port attribute indicates the TCP\/IP port number to use. The type attribute can either be tcp or udp. Since HTTP uses TCP, we\u2019re using tcp as the value here.\n\n<strong>The load element:<\/strong>\n\n\n\n<pre class=\"brush:xml\">&lt;!-- Load setup --&gt; &lt;load&gt;\r\n&lt;arrivalphase phase=\"1\" duration=\"5\" unit=\"minute\"&gt; &lt;users arrivalrate=\"200\" unit=\"second\"&gt;&lt;\/users&gt;\r\n&lt;\/arrivalphase&gt; &lt;\/load&gt;\r\n<\/pre>\n\n\n\nThe load element contains a list of arrivalphase elements, each simulating various types of load. The arrivalphase element\u2019s phase attribute represents the sequential number of the arrival phase. Here we are only defining one arrival phase. The duration attribute defines how long the arrival phase should last and the unit attribute defines the unit by which to measure the duration. Possible values for the unit element are second, minute, or hour.\n\nWithin the arrivalphase element is a users element. The arrivalrate attribute of the users element defines the number of arrivals within the timeframe defined by the unit element. Possible values for the unit element are second, minute, or hour. Here we are telling Tsung to start 200 \u201carrivals\u201d every second for 5 minutes.\n\n<strong>The sessions element:<\/strong>\n\n\n\n<pre class=\"brush:xml\">&lt;!-- Sessions setup --&gt; &lt;sessions&gt;\r\n... &lt;\/sessions&gt;\r\n<\/pre>\n\n\n\nThe sessions element contains a list of session elements. These each represent user sessions which may be simulated. We can define multiple sessions and each can have its own probability, but the total probability of all sessions must add up to 100. Let\u2019s take a look at each session individually.\n\nThe session element with the name attribute value of post_get:\n\n\n\n<pre class=\"brush:xml\">&lt;session name=\"post_get\" probability=\"2.5\" type=\"ts_http\"&gt; ...\r\n&lt;\/session&gt;\r\n<\/pre>\n\n\n\nThis session element contains a name attribute with the value of post_get. This name will be used in reports to identify the session. The session element\u2019s probability attribute indicates the percent probability of this session being used for any given user. Remember, the total probability of all sessions must add up to 100. The session element\u2019s type attribute can be either ts_http, ts_jabber, or ts_mysql. Since we\u2019re using HTTP, the type is ts_http.\n\n<strong>The thinktime element:<\/strong>\n\n\n\n<pre class=\"brush:xml\">&lt;thinktime value=\"10\" random=\"true\"\/&gt;[xml][\/xml]\r\n\r\nThe thinktime element defines an amount of time to wait, or \"think\", before continuing. This is helpful when trying to more realistically simulate load. The thinktime element's value attribute is the amount of time, in seconds, to wait. Setting the thinktime ele- ment's random attribute to a value of true tells Tsung to randomize the wait time, using the value attribute's value as the mean.\r\n\r\n&lt;strong&gt;The setdynvars elements:&lt;\/strong&gt;\r\n\r\n[xml][\/xml]\r\n&lt;setdynvars sourcetype=\"random_number\" start=\"2008\" end=\"2011\"&gt; &lt;var name=\"yyyy\"\/&gt;\r\n&lt;\/setdynvars&gt; &lt;setdynvars sourcetype=\"random_number\" start=\"10\" end=\"12\"&gt;\r\n&lt;var name=\"mm\"\/&gt; &lt;\/setdynvars&gt; &lt;setdynvars sourcetype=\"random_number\" start=\"10\" end=\"28\"&gt;\r\n&lt;var name=\"dd\"\/&gt; &lt;\/setdynvars&gt;\r\n<\/pre>\n\n\n\nEach of the setdynvars elements sets a dynamic variable. The sourcetype attribute value of random_number tells Tsung to generate a random number. The start and end attributes indicate the starting and ending values, respectively, to use when generating the random number. The nested var element actually instantiates the variable, using the variable name defined in the name attribute. Here we are generating random year, month, and day values which we will use later in the session.\n\n<strong>A request element:<\/strong>\n\n\n\n<pre class=\"brush:xml\">&lt;request subst=\"true\"&gt; &lt;match do=\"abort\" when=\"nomatch\"&gt;201 Created&lt;\/match&gt; &lt;dyn_variable name=\"id\" jsonpath=\"$.id\"\/&gt; &lt;dyn_variable name=\"rev\" jsonpath=\"$.rev\"\/&gt; &lt;http\r\n&gt;\r\nmethod=\"POST\" url=\"\/api\" content_type=\"application\/json\" contents=\"{\r\n&amp;quot;date&amp;quot;:[ &amp;quot;%%_yyyy%%&amp;quot;, &amp;quot;%%_mm%%&amp;quot;, &amp;quot;%%_dd%%&amp;quot;\r\n] }\"\r\n&lt;http_header name=\"Accept\" value=\"application\/json\"\/&gt; &lt;\/http&gt;\r\n&lt;\/request&gt;\r\n<\/pre>\n\n\n\nA request element defines a request to be made as part of the session. Since we\u2019ll be using the dynamic variables defined earlier, we need set the request element\u2019s subst attribute\u2019s value to true. This tells Tsung to substitute variables for their values, when encountered.\n\nThe match element tells Tsung to \u201cmatch\u201d on a certain condition. The do attribute value of abort tells Tsung to abort the session if the match condition is true. Possible values for the do attribute are continue, log, abort, restart, or loop. The \"when\" attribute can either be match or nomatch. The text of the match element is the text to match or not match on. In this case, if the text 201 Created is not found in the response (i.e., the document was not created) then we abort the session.\n\nThe two dyn_variable elements define dynamic variables that will be based on the server\u2019s response. The name attribute defines the name of the variable to use. Tsung allows matching using a limited subset of JSONPath (XPath for JSON), using the jsonpath attribute. These two variables will contain the ID and revision of the created document (once the response has been received).\n\nThe http element initiates an HTTP request. The method attribute specifies the HTTP method to use for the request (e.g., GET, POST, PUT, DELETE). The url attribute specifies the URL to which to make the request. This can be relative to the host set up earlier in the servers element, or a full URL. The content_type attribute specifies the value of the Content-Type HTTP header. The contents attribute specifies the contents of a POST or PUT request body. Here we are using a JSON object as the request body. The JSON object contains one field, date, with its value being an array of year, month, and day values (using the random dynamic variables created earlier).\n\n<strong>A \"for\" element:<\/strong>\n\n\n\n<pre class=\"brush:xml\">&lt;for from=\"0\" to=\"9\" incr=\"1\" var=\"x\"&gt; &lt;thinktime value=\"10\" random=\"true\"\/&gt; &lt;request subst=\"true\"&gt;\r\n&lt;match do=\"abort\" when=\"nomatch\"&gt;304 Not Modified&lt;\/match&gt; &lt;http method=\"GET\" url=\"\/api\/%%_id%%\"&gt;\r\n&lt;http_header name=\"If-None-Match\" value=\"&amp;quot;%%_rev%%&amp;quot;\"\/&gt;\r\n&lt;http_header name=\"Accept\" value=\"application\/json\"\/&gt; &lt;\/http&gt;\r\n&lt;\/request&gt; &lt;\/for&gt;<\/pre>\n\n\n\nA \"for\" element will tell Tsung to repeat the enclosed directives a specified number of times. Here we are using a from value of 0, a to value of 9, an incr value of 1, and using a var (variable) with a name of x. This means that the variable x will start out with the value of 0, increment by 1 in each iteration, and the loop will stop when x has reached the value of 9.\n\nThe contained thinktime, request, match, and http elements should look familiar. Within the http element, we\u2019ll see two http_header elements. As we may have guessed, these specify the name and value of HTTP headers to send as part of the request.\n\nThe If-None-Match HTTP header allows us to use conditional caching and the Accept header tells CouchDB that our client can handle content of type application\/json.\n\nThe remaining sessions in the configuration file should be self-explanatory.\n\n\n\n<h3><a name=\"running_tsung\"><\/a>6.3. Running Tsung<\/h3>\n\n\n\nFirst, we need to create the view that is used in the above configuration file. This is simply a view of dates (as an array of year, month, and day) from our documents:\n\n\n\n<pre class=\"brush:js\">curl -X PUT http:\/\/couch-proxy.example.com\/api\/_design\/default -d \\\\\r\n'{\r\n\"language\": \"javascript\", \"views\": {\r\n\"dates\": { \"map\":\r\n\"function(doc) { if (doc.date) {\r\n} }\",\r\nemit(doc.date);\r\n}\r\n\"reduce\": \"_count\"\r\n}'\r\n}\r\n<\/pre>\n\n\n\nThe response:\n\n\n\n<pre class=\"brush:js\">{ }\r\n\"ok\":true, \"id\":\"_design\/default\", \"rev\":\"1-edb41165ec8e4839dd7918e88e2125fa\"<\/pre>\n\n\n\nStart Tsung, telling it to use the above configuration file:\n\n\n\n<pre class=\"brush:bash\">tsung -f ~\/http_distributed_couch_proxy.xml start<\/pre>\n\n\n\nNote that Tsung will wait for all sessions to complete before finishing, even if it takes longer than the duration of all phases. Tsung will let us know what directory it has logged to, for example:\n\n\n\n<pre class=\"brush:bash\">\"Log directory is: \/home\/bradley-holt\/.tsung\/log\/20110221-23:26\"<\/pre>\n\n\n\nChange into the log directory and generate the HTML and graph reports using the tsung_stats.pl script package with Tsung:\n\n\n\n<pre class=\"brush:bash\">\/usr\/lib\/tsung\/bin\/tsung_stats.pl<\/pre>\n\n\n\nIf everything works correctly, a report.html file will be created in this same directory. Open this report and we will see several statistics and graphs. Under the statistics reports, the main statistics table shows the highest 10 second mean, lowest 10 second mean, highest rate, mean, and count for each part of the HTTP connection.\n\nTsung allows us to group requests into transactions. A transaction might be useful when testing an HTML page as we could group the requests for the HTML and all related assets (e.g., JavaScript, CSS, and images) into one transaction. We have not done this here, so our transactions statistics table will be empty. The network through- put table lets us see the size of the network traffic received and sent.\n\nThere are also several graphs reports available. For all graphs, the x-axis represents a progression of time throughout the test. The first graph represents mean transaction response time. The y-axis for this graph represents the mean number of milliseconds that the transaction response took during a given moment in the test.\n\n\n\n<h2><a name=\"identifying_bottlenecks\"><\/a>7. Identifying Bottlenecks<\/h2>\n\n\nBased on the results of the above tests, we can attempt to make a few conclusions. First, some analysis:\n\n\n\n<ul>\n\n\n<li>The CPU utilization percentages on the read-only slave nodes, the write-only master node, and the proxy server are all quite low. It appears that none of these nodes are CPU bound.<\/li>\n\n\n\n\n<li>The free memory amounts on the read-only slave nodes, the write-only master node, and the proxy server never drops critically low. It looks like none of the nodes ever run out of memory, so excessive swapping should not be an issue.<\/li>\n\n\n\n\n<li>The server load averages on the read-only slave nodes and the write-only master node are reasonable.<\/li>\n\n\n\n\n<li>The server load average on the proxy server is quite high.<\/li>\n\n\n<\/ul>\n\n\n\nBased on this analysis, we might conclude that the proxy server is a potential bottleneck in our system. If we look back at the counter statistics, we\u2019ll see that the maximum number of connections reached was 2797. However, the maximum number of con- nections allowed to each read-only node was 4. With three read-only nodes, this gives us a total of 12 maximum connections to the backend CouchDB nodes. The write-only node did not have a limit, but our test scenarios were read-heavy. It appears that the proxy server is effectively queuing requests for the backend CouchDB nodes, which could account for the high server load.\n\nBased on the above hypothesis, adding more read-only CouchDB nodes might actually lessen the load on the proxy server.\n--><\/p>\n<h2><a name=\"distributed_load_testing\"><\/a>6. Distributed load testing<\/h2>\n<p>There are many tools available that allow us to create tests for application. In order to  stress test a distributed system, we will need a distributed environment and distributed load testing tool. Tsung is a distributed load and stress testing tool that we will use for the example this chapter. <\/p>\n<p>Here our steps will be:<\/p>\n<ol>\n<li>Create a master slave replication in CouchDB.<\/li>\n<li>Install and Configure tsung<\/li>\n<li>Test both the master and slave CouchDBwith tsung.<\/li>\n<li>Evaluate the Testing results<\/li>\n<li>Create proxy-server for CouchDB.<\/li>\n<li>Run tsung for the proxy server environment.<\/li>\n<li>Identify the performance parameters.<\/li>\n<\/ol>\n<p>Let&#8217;s see those in detail.<\/p>\n<ol>\n<li>First we are creating the CouchDB database in both server in our network. For example, our two couchdb server machines are in on IPs 192.168.19.216 and 192.168.19.155\n<p>For ubuntu, the couchdb installation command is:<\/p>\n<pre class=\"brush:bash\">\r\nsudo apt-get install couchdb\r\n<\/pre>\n<p>To test the couchdb server running or not, we check with an HTTP request:<\/p>\n<pre class=\"brush:bash\">\r\ncurl localhost:5984\r\n<\/pre>\n<p>After installing couchdb on both machines, we need to bind the ip address by editing the <code>local.ini<\/code> file<\/p>\n<p>The command to edit the couchdb configuration is the following:<\/p>\n<pre class=\"brush:bash\">\r\nsudo gedit \/etc\/couchdb\/local.ini\r\n<\/pre>\n<p>We need to edit the file as follows:<\/p>\n<pre class=\"brush:bash\">\r\n[httpd]\r\nport = 5984\r\nbind_address = 192.168.19.216\r\n<\/pre>\n<p>and<\/p>\n<pre class=\"brush:bash\">\r\n[httpd]\r\nport = 5984\r\nbind_address = 192.168.19.155\r\n<\/pre>\n<p>and save those.<\/p>\n<p>Restart couchdb in both machines with the command:<\/p>\n<pre class=\"brush:bash\">\r\nsudo \/etc\/init.d\/couchdb restart\r\n<\/pre>\n<p>Now we will create couchdb database from ubuntu terminal of machines:<\/p>\n<pre class=\"brush:bash\">\r\ncurl -X PUT http:\/\/192.168.19.216:5984\/ourdb\r\n<\/pre>\n<p>Response:<\/p>\n<pre class=\"brush:js\">\r\n{\"ok\":true}\r\n<\/pre>\n<p>Similarly:<\/p>\n<pre class=\"brush:bash\">\r\ncurl -X PUT http:\/\/192.168.19.155:5984\/ourdb\r\n<\/pre>\n<p>Response:<\/p>\n<pre class=\"brush:js\">\r\n{\"ok\":true}\r\n<\/pre>\n<p>In the above stage, our databases are created on the servers.<\/p>\n<p>To create the pull replication from one machine to another, the command is:<\/p>\n<pre class=\"brush:bash\">\r\ncurl -X POST http:\/\/192.168.19.155:5984\/_replicate \\\\\r\n\r\n-H \"Content-Type: application\/json\" \\\\\r\n\r\n-d \\\\\r\n\r\n'{\r\n\r\n   \"source\":\"http:\/\/192.168.19.216:5984\/ourdb\",\r\n\r\n   \"target\":\"ourdb\",\r\n\r\n   \"continuous\":true\r\n\r\n}'\r\n<\/pre>\n<p>Couchdb response:<\/p>\n<pre class=\"brush:js\">\r\n{\"ok\":true,\"_local_id\":\"e6118c2930eabde4ab06df8873a0994b+continuous\"} (In every machine this response will be different)\r\n<\/pre>\n<p>Here in the above statement, the pull replication from 192.168.19.216 to 192.168.19.155 machine will be created.<\/p>\n<p>Now we will insert a json document in 192.168.19.216 and we will get automatic replication in 192.168.19.155.<\/p>\n<pre class=\"brush:js\">\r\ncurl -X POST http:\/\/192.168.19.216:5984\/ourdb -H \"Content-Type: application\/json\" -d '{\"_id\":\"U001\",\"name\":\"John\"}'\r\n<\/pre>\n<p>Couchdb response:<\/p>\n<pre class=\"brush:js\">\r\n{\"ok\":true,\"id\":\"U001\",\"rev\":\"1-7f570a3bb28cc04b130c3fbb95c7a513\"}\r\n<\/pre>\n<p>(Please note that in every machine this response will be different)<\/p>\n<p>To see the results, the commands is:<\/p>\n<pre class=\"brush:bash\">\r\ncurl -X GET http:\/\/192.168.19.155:5984\/ourdb\/U001\r\n<\/pre>\n<p>Couchdb response:<\/p>\n<pre class=\"brush:js\">\r\n{\"_id\":\"U001\",\"_rev\":\"1-7f570a3bb28cc04b130c3fbb95c7a513\",\"name\":\"John\"}\r\n<\/pre>\n<p>(In every machine this response will be different)<\/p>\n<pre class=\"brush:bash\">\r\ncurl -X GET http:\/\/192.168.19.216:5984\/ourdb\/U001\r\n<\/pre>\n<pre class=\"brush:js\">\r\n{\"_id\":\"U001\",\"_rev\":\"1-7f570a3bb28cc04b130c3fbb95c7a513\",\"name\":\"John\"}\r\n<\/pre>\n<p>(In every machine this response will be different)\n<\/li>\n<li>We will install tsung for our Distributed Testing Tool.\n<p>To install tsung in ubuntu first we need to install the erlang and helper libraries. This is done with the following command:<\/p>\n<pre class=\"brush:bash\">\r\nsudo apt-get install build-essential debhelper     erlang-nox erlang-dev     python-matplotlib gnuplot     libtemplate-perl\r\n<\/pre>\n<p>Next we have to download tsung:<\/p>\n<pre class=\"brush:bash\">\r\nwget https:\/\/github.com\/processone\/tsung\/archive\/v1.5.0.tar.gz\r\n<\/pre>\n<p>Next, we have to configure tsung using following commands &#8211;<\/p>\n<pre class=\"brush:bash\">\r\ntar -xvzf v1.5.0.tar.gz\r\n\r\ncd tsung-1.5.0\r\n\r\n.\/configure\r\n\r\nmake\r\n\r\nmake deb\r\n\r\ncd ..\r\n\r\nsudo dpkg -i tsung_1.5.0-1_all.deb\r\n<\/pre>\n<p>Run tsung -v to check that tsung is installed.\n<\/li>\n<li>To test the individual servers of couchdb, we need to add an xml file, named <code>tsung_load_test.xml<\/code> with following configurations:\n<pre class=\"brush:xml\">\r\n&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\r\n\r\n&lt;!DOCTYPE tsung SYSTEM \"\/usr\/share\/tsung\/tsung-1.0.dtd\" []&gt;\r\n\r\n&lt;tsung loglevel=\"warning\"&gt;\r\n\r\n  &lt;clients&gt;\r\n\r\n    &lt;client host=\"localhost\" cpu=\"2\" maxusers=\"30000000\"\/&gt;\r\n\r\n  &lt;\/clients&gt;\r\n\r\n  &lt;servers&gt;\r\n\r\n    &lt;server host=\"192.168.19.155\" port=\"5984\" type=\"tcp\"\/&gt;\r\n\r\n  &lt;\/servers&gt;\r\n\r\n  &lt;load&gt;\r\n\r\n    &lt;arrivalphase phase=\"1\" duration=\"1\" unit=\"minute\"&gt;\r\n\r\n      &lt;users arrivalrate=\"5\" unit=\"second\" maxnumber=\"1000\"\/&gt;\r\n\r\n    &lt;\/arrivalphase&gt;\r\n\r\n  &lt;\/load&gt;\r\n\r\n  &lt;sessions&gt;\r\n\r\n    &lt;session name=\"es_load\" weight=\"1\" type=\"ts_http\"&gt;\r\n\r\n\t&lt;for from=\"1\" to=\"1000\" incr=\"1\" var=\"counter\"&gt; \r\n\r\n\t\t&lt;request&gt; &lt;http url=\"\/ourdb\/U001\" version=\"1.1\" content_type='application\/json' method='GET'&gt;&lt;\/http&gt;&lt;\/request&gt;\r\n\r\n\t&lt;\/for&gt;\r\n\r\n    &lt;\/session&gt;\r\n\r\n  &lt;\/sessions&gt;\r\n\r\n&lt;\/tsung&gt;\r\n<\/pre>\n<p>In the above configuration, the <code>client<\/code> element is the tsung test client which will run on localhost. The <code>server<\/code> element is the actual couchdb server to which the load testing will be done.<\/p>\n<p>In the <code>arrivalphase<\/code> element, we configure to run the test for 1 minute and in every second, 5 concurrent user will be triggered.<\/p>\n<p>In <code>session<\/code> element, we define the actual request which will run 1000 times.<\/p>\n<p>For more information about tsung test element configuration, please refer to the <a href=\"http:\/\/tsung.erlang-projects.org\/user_manual\/index.html\">User Manual<\/a>.<\/p>\n<p>To run the test, we will need to run the following:<\/p>\n<pre class=\"brush:bash\">\r\ntsung -f tsung_load_test.xml start\r\n<\/pre>\n<p>It will show some directory and log file, where tsung log will be generated.<\/p>\n<p>The same process will be followed for the 192.168.19.216 machine.\n<\/li>\n<li>To view the tsung test results, we have some built-in report of tsung. We can get the report by using:\n<pre class=\"brush:bash\">\r\n\/usr\/lib\/tsung\/bin\/tsung_stats.pl --stats \/home\/piyas\/.tsung\/log\/20150413-1835\/tsung.log chromium graph.html\r\n<\/pre>\n<p>Here the log was generated in the <code>\/home\/piyas\/.tsung\/log\/20150413-1835\/<\/code> folder.<\/p>\n<p>The graphical report will be found in <code>graph.html<\/code>.<\/p>\n<p>We have included 2 sample results for the above indicated 2 machines as a zip file.\n<\/li>\n<li>Before creating the proxy server, we have created the couchdb server in another machine (192.168.19.122). Additionally, we have tested the server with tsung as we have described above.\n<p>To make the proxy server, we had installed and configured apache on 192.168.19.216.<\/p>\n<p>First we downloaded apache in our machine:<\/p>\n<pre class=\"brush:bash\">\r\nsudo apt-get install apache2 \r\n<\/pre>\n<p>To enable proxy, proxy_http and proxy_balancer, we have to use the following commands:<\/p>\n<pre class=\"brush:bash\">\r\nsudo apt-get install libapache2-mod-proxy-html  \r\nsudo a2enmod proxy \r\nsudo a2enmod proxy_http \r\nsudo a2enmod proxy_balancer \r\nsudo a2enmod headers \r\nsudo a2enmod rewrite\r\n<\/pre>\n<p>We have installed apache 2.4.7 in our ubuntu machine. Here the configurations are little different.<\/p>\n<p>First we have to edit the <code>000-default.conf<\/code> file in the sites-enabled folder:<\/p>\n<pre class=\"brush:bash\">\r\nsudo gedit \/etc\/apache2\/sites-enabled\/000-default.conf \r\n<\/pre>\n<p>And we need to put the following configuration within virtualroot element i.e. at the end within the <code><virtualhost><\/virtualhost><\/code> tag<\/p>\n<pre class=\"brush:bash\">\r\nHeader append Vary Accept \r\nHeader add Set-Cookie \"NODE=%{BALANCER_WORKER_ROUTE}e; path=\/ourdb\" \\\\ \r\nenv=BALANCER_ROUTE_CHANGED \r\n<\/pre>\n<pre class=\"brush:xml\">\r\n&lt;Proxy balancer:\/\/couch-slave&gt; \r\n    BalancerMember http:\/\/192.168.19.155:5984\/ourdb route=a max=10\r\n    BalancerMember http:\/\/192.168.19.122:5984\/ourdb route=b max=10 \r\n    ProxySet stickysession=NODE \r\n    ProxySet timeout=5 \r\n&lt;\/Proxy&gt;\r\n<\/pre>\n<pre class=\"brush:bash\">\r\nRewriteEngine On \r\nRewriteCond %{REQUEST_METHOD} ^(POST|PUT|DELETE|MOVE|COPY)$ \r\nRewriteRule ^\/ourdb(.*)$ http:\/\/192.168.19.216:5984\/ourdb$1 [P] \r\nRewriteCond %{REQUEST_METHOD} ^(GET|HEAD|OPTIONS)$ \r\nRewriteRule ^\/ourdb(.*)$ balancer:\/\/couch-slave$1 [P] \r\nProxyPassReverse \/ ourdb http:\/\/192.168.19.216:5984\/ ourdb\r\nProxyPassReverse \/ ourdb balancer:\/\/couch-slave \r\nRewriteEngine On \r\nRewriteOptions inherit \r\n<\/pre>\n<p>Here we have configured 192.168.19.155 and 192.168.19.122 machine as our couch-slave and made  192.168.19.216 as couch-master.<\/p>\n<p>So when a http request will come through proxy server, it will be replicated to  192.168.19.155 and 192.168.19.122 from 192.168.19.216<\/p>\n<p>We can see that only the POST, PUT, DELETE, MOVE, COPY commands will be allowed through Proxy but no GET method is allowed here.<\/p>\n<p>Now we restart apache:<\/p>\n<pre class=\"brush:bash\">\r\nsudo service apache2 restart\r\n<\/pre>\n<p>Troubleshooting &#8211;<\/p>\n<p>If user get the error in  <\/p>\n<pre class=\"brush:bash\">\r\n\/var\/log\/apache2\/error.log \r\n<\/pre>\n<p>Cannot find LB Method: byrequests <\/p>\n<p>Then he should enable the modules using in apache: <\/p>\n<pre class=\"brush:bash\">\r\nsudo ln -s ..\/mods-available\/lbmethod_byrequests.load lbmethod_byrequests.load \r\nsudo ln -s ..\/mods-available\/lbmethod_bytraffic.load lbmethod_bytraffic.load \r\nsudo ln -s ..\/mods-available\/lbmethod_heartbeat.load lbmethod_heartbeat.load \r\nsudo ln -s ..\/mods-available\/lbmethod_bybusyness.load lbmethod_bybusyness.load \r\n<\/pre>\n<p>Again to restart apache2: <\/p>\n<pre class=\"brush:bash\">\r\nsudo service apache2 restart\r\n<\/pre>\n<p>Now if we take a test of our proxy server:<\/p>\n<pre class=\"brush:bash\">\r\ncurl -X GET http:\/\/192.168.19.216\/ourdb\r\n<\/pre>\n<p>We should get the couchdb response- <\/p>\n<pre class=\"brush:js;wrap-lines:false\">\r\n{\"db_name\":\"ourdb\",\"doc_count\":3,\"doc_del_count\":0,\"update_seq\":3,\"purge_seq\":0, \r\n\"compact_running\":false,\"disk_size\":24678,\"data_size\":1002,\"instance_start_time\":\"1428993076631193\", \r\n\"disk_format_version\":6,\"committed_update_seq\":3}\r\n<\/pre>\n<p>Which means the proxy server is running successfully. <\/p>\n<p>Now we can perform some test using the proxy server and POST requests:<\/p>\n<pre class=\"brush:bash\">\r\ncurl -X POST http:\/\/192.168.19.216\/ourdb \\\\ \r\n-H \"Content-Type: application\/json\" \\\\ \r\n-d '{ \r\n   \"_id\":\"U003\", \r\n   \"name\":\"Steve\"\t \r\n}'\r\n<\/pre>\n<pre class=\"brush:bash\">\r\ncurl -X POST http:\/\/192.168.19.216\/ ourdb \\\\ \r\n-H \"Content-Type: application\/json\" \\\\ \r\n-d '{ \r\n   \"_id\":\"U004\", \r\n   \"name\":\"Mark\"\t \r\n}'\r\n<\/pre>\n<\/li>\n<li>For the load testing through the proxy server, we will use the same tsung command as the one we described above. The configuration file for the testing will be:\n<pre class=\"brush:xml\">\r\n&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt; \r\n&lt;!DOCTYPE tsung SYSTEM \"\/usr\/share\/tsung\/tsung-1.0.dtd\" []&gt; \r\n&lt;tsung loglevel=\"warning\"&gt; \r\n \r\n  &lt;clients&gt; \r\n    &lt;client host=\"localhost\" cpu=\"2\" maxusers=\"30000000\"\/&gt; \r\n  &lt;\/clients&gt; \r\n \r\n  &lt;servers&gt; \r\n    &lt;server host=\"192.168.19.216\" port=\"80\" type=\"tcp\"\/&gt; \r\n  &lt;\/servers&gt; \r\n \r\n  &lt;load&gt; \r\n    &lt;arrivalphase phase=\"1\" duration=\"1\" unit=\"minute\"&gt; \r\n      &lt;users arrivalrate=\"5\" unit=\"second\" maxnumber=\"1000\"\/&gt; \r\n    &lt;\/arrivalphase&gt; \r\n  &lt;\/load&gt; \r\n \r\n  &lt;sessions&gt; \r\n    &lt;session name=\"es_load\" weight=\"1\" type=\"ts_http\"&gt; \r\n      \r\n&lt;for from=\"1\" to=\"1000\" incr=\"1\" var=\"counter\"&gt; &lt;request&gt; &lt;http url=\"\/ourdb\/U001\" version=\"1.1\" content_type='application\/json' method='GET'&gt;&lt;\/http&gt;&lt;\/request&gt;&lt;\/for&gt; \r\n \r\n    &lt;\/session&gt; \r\n  &lt;\/sessions&gt; \r\n&lt;\/tsung&gt;\r\n<\/pre>\n<p>The results for the proxy server testing is also attached in the ZIP files.\n<\/li>\n<li>Identifying the performance parameters:\n<ol>\n<li>If the machines have the same hardware configuration, the couchdb servers will function properly for the given tsung load testing parameters (no downtime).<\/li>\n<li>The proxy server environment testing is also running fine. Each slave machine is serving 10 requests in a round-robin manner as per the proxy server scheme. Thus, by using more slave machines for serving requests, the request queuing will be shorter.<\/li>\n<\/ol>\n<\/li>\n<p>You may download the stress test result files <a href=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/09\/tsung_stress_results.zip\"><strong>here<\/strong><\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This article is part of our Academy Course titled CouchDB \u2013 Database for the Web. This is a hands-on course on CouchDB. You will learn how to install and configure CouchDB and how to perform common operations with it. Additionally, you will build an example application from scratch and then finish the course with more &hellip;<\/p>\n","protected":false},"author":18,"featured_media":7450,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[108],"class_list":["post-7494","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-web-development","tag-databases"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Deploying and Optimizing CouchDB - Web Code Geeks - 2026<\/title>\n<meta name=\"description\" content=\"This article is part of our Academy Course titled CouchDB \u2013 Database for the Web. This is a hands-on course on CouchDB. You will learn how to install and\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Deploying and Optimizing CouchDB - Web Code Geeks - 2026\" \/>\n<meta property=\"og:description\" content=\"This article is part of our Academy Course titled CouchDB \u2013 Database for the Web. This is a hands-on course on CouchDB. You will learn how to install and\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/\" \/>\n<meta property=\"og:site_name\" content=\"Web Code Geeks\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/webcodegeeks\" \/>\n<meta property=\"article:author\" content=\"http:\/\/www.facebook.com\/phlocblogger\" \/>\n<meta property=\"article:published_time\" content=\"2015-09-28T18:14:17+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2018-01-05T14:54:51+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/09\/couchdb-logo-.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"150\" \/>\n\t<meta property=\"og:image:height\" content=\"150\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Piyas De\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@https:\/\/twitter.com\/phloxblog\" \/>\n<meta name=\"twitter:site\" content=\"@webcodegeeks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Piyas De\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"27 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/\"},\"author\":{\"name\":\"Piyas De\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/7aab6e040a06f0dfe0d60c27768aa424\"},\"headline\":\"Deploying and Optimizing CouchDB\",\"datePublished\":\"2015-09-28T18:14:17+00:00\",\"dateModified\":\"2018-01-05T14:54:51+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/\"},\"wordCount\":4492,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/09\/couchdb-logo-.jpg\",\"keywords\":[\"Databases\"],\"articleSection\":[\"Web Dev\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/\",\"name\":\"Deploying and Optimizing CouchDB - Web Code Geeks - 2026\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/09\/couchdb-logo-.jpg\",\"datePublished\":\"2015-09-28T18:14:17+00:00\",\"dateModified\":\"2018-01-05T14:54:51+00:00\",\"description\":\"This article is part of our Academy Course titled CouchDB \u2013 Database for the Web. This is a hands-on course on CouchDB. You will learn how to install and\",\"breadcrumb\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/#primaryimage\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/09\/couchdb-logo-.jpg\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/09\/couchdb-logo-.jpg\",\"width\":150,\"height\":150},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.webcodegeeks.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Web Dev\",\"item\":\"https:\/\/www.webcodegeeks.com\/category\/web-development\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Deploying and Optimizing CouchDB\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"name\":\"Web Code Geeks\",\"description\":\"Web Developers Resource Center\",\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.webcodegeeks.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\",\"name\":\"Exelixis Media P.C.\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"width\":864,\"height\":246,\"caption\":\"Exelixis Media P.C.\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/webcodegeeks\",\"https:\/\/x.com\/webcodegeeks\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/7aab6e040a06f0dfe0d60c27768aa424\",\"name\":\"Piyas De\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/eadd6728b7b5be23f0d6585da1a953926e49c6f2369703d6cb4f1147d4dd2203?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/eadd6728b7b5be23f0d6585da1a953926e49c6f2369703d6cb4f1147d4dd2203?s=96&d=mm&r=g\",\"caption\":\"Piyas De\"},\"description\":\"Piyas is Sun Microsystems certified Enterprise Architect with 10+ years of professional IT experience in various areas such as Architecture Definition, Define Enterprise Application, Client-server\/e-business solutions.Currently he is engaged in providing solutions for digital asset management in media companies.He is also founder and main author of \\\"Technical Blogs (Blog about small technical Know hows)\\\"\",\"sameAs\":[\"http:\/\/www.phloxblog.in\",\"http:\/\/www.facebook.com\/phlocblogger\",\"http:\/\/in.linkedin.com\/in\/piyasde\",\"https:\/\/x.com\/https:\/\/twitter.com\/phloxblog\"],\"url\":\"https:\/\/www.webcodegeeks.com\/author\/piyas-de\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Deploying and Optimizing CouchDB - Web Code Geeks - 2026","description":"This article is part of our Academy Course titled CouchDB \u2013 Database for the Web. This is a hands-on course on CouchDB. You will learn how to install and","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/","og_locale":"en_US","og_type":"article","og_title":"Deploying and Optimizing CouchDB - Web Code Geeks - 2026","og_description":"This article is part of our Academy Course titled CouchDB \u2013 Database for the Web. This is a hands-on course on CouchDB. You will learn how to install and","og_url":"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/","og_site_name":"Web Code Geeks","article_publisher":"https:\/\/www.facebook.com\/webcodegeeks","article_author":"http:\/\/www.facebook.com\/phlocblogger","article_published_time":"2015-09-28T18:14:17+00:00","article_modified_time":"2018-01-05T14:54:51+00:00","og_image":[{"width":150,"height":150,"url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/09\/couchdb-logo-.jpg","type":"image\/jpeg"}],"author":"Piyas De","twitter_card":"summary_large_image","twitter_creator":"@https:\/\/twitter.com\/phloxblog","twitter_site":"@webcodegeeks","twitter_misc":{"Written by":"Piyas De","Est. reading time":"27 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/#article","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/"},"author":{"name":"Piyas De","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/7aab6e040a06f0dfe0d60c27768aa424"},"headline":"Deploying and Optimizing CouchDB","datePublished":"2015-09-28T18:14:17+00:00","dateModified":"2018-01-05T14:54:51+00:00","mainEntityOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/"},"wordCount":4492,"commentCount":0,"publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/09\/couchdb-logo-.jpg","keywords":["Databases"],"articleSection":["Web Dev"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/","url":"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/","name":"Deploying and Optimizing CouchDB - Web Code Geeks - 2026","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/#primaryimage"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/09\/couchdb-logo-.jpg","datePublished":"2015-09-28T18:14:17+00:00","dateModified":"2018-01-05T14:54:51+00:00","description":"This article is part of our Academy Course titled CouchDB \u2013 Database for the Web. This is a hands-on course on CouchDB. You will learn how to install and","breadcrumb":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/#primaryimage","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/09\/couchdb-logo-.jpg","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/09\/couchdb-logo-.jpg","width":150,"height":150},{"@type":"BreadcrumbList","@id":"https:\/\/www.webcodegeeks.com\/web-development\/deploying-and-optimizing-couchdb\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.webcodegeeks.com\/"},{"@type":"ListItem","position":2,"name":"Web Dev","item":"https:\/\/www.webcodegeeks.com\/category\/web-development\/"},{"@type":"ListItem","position":3,"name":"Deploying and Optimizing CouchDB"}]},{"@type":"WebSite","@id":"https:\/\/www.webcodegeeks.com\/#website","url":"https:\/\/www.webcodegeeks.com\/","name":"Web Code Geeks","description":"Web Developers Resource Center","publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.webcodegeeks.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.webcodegeeks.com\/#organization","name":"Exelixis Media P.C.","url":"https:\/\/www.webcodegeeks.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","width":864,"height":246,"caption":"Exelixis Media P.C."},"image":{"@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/webcodegeeks","https:\/\/x.com\/webcodegeeks"]},{"@type":"Person","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/7aab6e040a06f0dfe0d60c27768aa424","name":"Piyas De","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/eadd6728b7b5be23f0d6585da1a953926e49c6f2369703d6cb4f1147d4dd2203?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/eadd6728b7b5be23f0d6585da1a953926e49c6f2369703d6cb4f1147d4dd2203?s=96&d=mm&r=g","caption":"Piyas De"},"description":"Piyas is Sun Microsystems certified Enterprise Architect with 10+ years of professional IT experience in various areas such as Architecture Definition, Define Enterprise Application, Client-server\/e-business solutions.Currently he is engaged in providing solutions for digital asset management in media companies.He is also founder and main author of \"Technical Blogs (Blog about small technical Know hows)\"","sameAs":["http:\/\/www.phloxblog.in","http:\/\/www.facebook.com\/phlocblogger","http:\/\/in.linkedin.com\/in\/piyasde","https:\/\/x.com\/https:\/\/twitter.com\/phloxblog"],"url":"https:\/\/www.webcodegeeks.com\/author\/piyas-de\/"}]}},"_links":{"self":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/7494","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/users\/18"}],"replies":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/comments?post=7494"}],"version-history":[{"count":0,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/7494\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media\/7450"}],"wp:attachment":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media?parent=7494"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/categories?post=7494"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/tags?post=7494"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}