1.3. Eventual Consistency

In the previous document Why CouchDB?, we saw that CouchDB’s flexibilityallows us to evolve our data as our applications grow and change. In this topic,we’ll explore how working “with the grain” of CouchDB promotes simplicity inour applications and helps us naturally build scalable, distributed systems.

1.3.1. Working with the Grain

A distributed system is a system that operates robustly over a wide network.A particular feature of network computing is that network links canpotentially disappear, and there are plenty of strategies for managing thistype of network segmentation. CouchDB differs from others by acceptingeventual consistency, as opposed to putting absolute consistency ahead of rawavailability, like RDBMS or Paxos. What these systems have in common isan awareness that data acts differently when many people are accessing itsimultaneously. Their approaches differ when it comes to which aspects ofconsistency, availability, or partition tolerance they prioritize.

Engineering distributed systems is tricky. Many of the caveats and “gotchas”you will face over time aren’t immediately obvious. We don’t have all thesolutions, and CouchDB isn’t a panacea, but when you work with CouchDB’sgrain rather than against it, the path of least resistance leads you tonaturally scalable applications.

Of course, building a distributed system is only the beginning. A websitewith a database that is available only half the time is next to worthless.Unfortunately, the traditional relational database approach to consistencymakes it very easy for application programmers to rely on global state,global clocks, and other high availability no-nos, without even realizingthat they’re doing so. Before examining how CouchDB promotes scalability,we’ll look at the constraints faced by a distributed system. After we’ve seenthe problems that arise when parts of your application can’t rely on beingin constant contact with each other, we’ll see that CouchDB provides anintuitive and useful way for modeling applications around high availability.

1.3.2. The CAP Theorem

The CAP theorem describes a few different strategies for distributingapplication logic across networks. CouchDB’s solution uses replication topropagate application changes across participating nodes. This is afundamentally different approach from consensus algorithms and relationaldatabases, which operate at different intersections of consistency,availability, and partition tolerance.

The CAP theorem, shown in Figure 1. The CAP theorem,identifies three distinct concerns:

  • Consistency:All database clients see the same data, even with concurrent updates.
  • Availability:All database clients are able to access some version of the data.
  • Partition tolerance:The database can be split over multiple servers.
    Pick two.

The CAP theorem
Figure 1. The CAP theorem

When a system grows large enough that a single database node is unable tohandle the load placed on it, a sensible solution is to add more servers.When we add nodes, we have to start thinking about how to partition databetween them. Do we have a few databases that share exactly the same data?Do we put different sets of data on different database servers?Do we let only certain database servers write data and let others handlethe reads?

Regardless of which approach we take, the one problem we’ll keep bumping intois that of keeping all these database servers in sync. If you write someinformation to one node, how are you going to make sure that a read requestto another database server reflects this newest information? These eventsmight be milliseconds apart. Even with a modest collection of databaseservers, this problem can become extremely complex.

When it’s absolutely critical that all clients see a consistent view of thedatabase, the users of one node will have to wait for any other nodes to comeinto agreement before being able to read or write to the database.In this instance, we see that availability takes a backseat to consistency.However, there are situations where availability trumps consistency:

Each node in a system should be able to make decisions purely based onlocal state. If you need to do something under high load with failuresoccurring and you need to reach agreement, you’re lost. If you’reconcerned about scalability, any algorithm that forces you to runagreement will eventually become your bottleneck. Take that as a given.—Werner Vogels, Amazon CTO and Vice President

If availability is a priority, we can let clients write data to one node ofthe database without waiting for other nodes to come into agreement.If the database knows how to take care of reconciling these operations betweennodes, we achieve a sort of “eventual consistency” in exchange for highavailability. This is a surprisingly applicable trade-off for many applications.

Unlike traditional relational databases, where each action performed isnecessarily subject to database-wide consistency checks,CouchDB makes it really simple to build applications that sacrifice immediateconsistency for the huge performance improvements that come with simpledistribution.

1.3.3. Local Consistency

Before we attempt to understand how CouchDB operates in a cluster,it’s important that we understand the inner workings of a single CouchDB node.The CouchDB API is designed to provide a convenient but thin wrapper aroundthe database core. By taking a closer look at the structure of the databasecore, we’ll have a better understanding of the API that surrounds it.

1.3.3.1. The Key to Your Data

At the heart of CouchDB is a powerful B-tree storage engine.A B-tree is a sorted data structure that allows for searches, insertions,and deletions in logarithmic time. As Figure 2. Anatomy of a view requestillustrates, CouchDB uses this B-tree storage engine for all internal data,documents, and views. If we understand one, we will understand them all.

Anatomy of a view request
Figure 2. Anatomy of a view request

CouchDB uses MapReduce to compute the results of a view. MapReduce makes useof two functions, “map” and “reduce”, which are applied to each document inisolation. Being able to isolate these operations means that view computationlends itself to parallel and incremental computation. More important,because these functions produce key/value pairs, CouchDB is able to insertthem into the B-tree storage engine, sorted by key. Lookups by key,or key range, are extremely efficient operations with a B-tree,described in big O notation as O(log N) and O(log N + K),respectively.

In CouchDB, we access documents and view results by key or key range.This is a direct mapping to the underlying operations performed on CouchDB’sB-tree storage engine. Along with document inserts and updates,this direct mapping is the reason we describe CouchDB’s API as being a thinwrapper around the database core.

Being able to access results by key alone is a very important restrictionbecause it allows us to make huge performance gains. As well as the massivespeed improvements, we can partition our data over multiple nodes,without affecting our ability to query each node in isolation.BigTable, Hadoop, SimpleDB, and memcached restrict object lookupsby key for exactly these reasons.

1.3.3.2. No Locking

A table in a relational database is a single data structure. If you want tomodify a table – say, update a row – the database system must ensurethat nobody else is trying to update that row and that nobody can read fromthat row while it is being updated. The common way to handle this uses what’sknown as a lock. If multiple clients want to access a table, the first clientgets the lock, making everybody else wait. When the first client’s request isprocessed, the next client is given access while everybody else waits,and so on. This serial execution of requests, even when they arrived inparallel, wastes a significant amount of your server’s processing power.Under high load, a relational database can spend more time figuring out whois allowed to do what, and in which order, than it does doing any actual work.

Note

Modern relational databases avoid locks by implementing MVCC underthe hood, but hide it from the end user, requiring them to coordinateconcurrent changes of single rows or fields.

Instead of locks, CouchDB uses Multi-Version Concurrency Control (MVCC) tomanage concurrent access to the database. Figure 3. MVCC means no lockingillustrates the differences between MVCC and traditional locking mechanisms.MVCC means that CouchDB can run at full speed, all the time,even under high load. Requests are run in parallel, making excellent use ofevery last drop of processing power your server has to offer.

MVCC means no locking
Figure 3. MVCC means no locking

Documents in CouchDB are versioned, much like they would be in a regularversion control system such as Subversion. If you want to changea value in a document, you create an entire new version of that documentand save it over the old one. After doing this, you end up with two versionsof the same document, one old and one new.

How does this offer an improvement over locks? Consider a set of requestswanting to access a document. The first request reads the document.While this is being processed, a second request changes the document.Since the second request includes a completely new version of the document,CouchDB can simply append it to the database without having to wait for theread request to finish.

When a third request wants to read the same document, CouchDB will point itto the new version that has just been written. During this whole process,the first request could still be reading the original version.

A read request will always see the most recent snapshot of your database atthe time of the beginning of the request.

1.3.4. Validation

As application developers, we have to think about what sort of input weshould accept and what we should reject. The expressive power to do this typeof validation over complex data within a traditional relational databaseleaves a lot to be desired. Fortunately, CouchDB provides a powerful way toperform per-document validation from within the database.

CouchDB can validate documents using JavaScript functions similar to thoseused for MapReduce. Each time you try to modify a document,CouchDB will pass the validation function a copy of the existing document,a copy of the new document, and a collection of additional information,such as user authentication details. The validation function now has theopportunity to approve or deny the update.

By working with the grain and letting CouchDB do this for us,we save ourselves a tremendous amount of CPU cycles that would otherwise havebeen spent serializing object graphs from SQL, converting them into domainobjects, and using those objects to do application-level validation.

1.3.5. Distributed Consistency

Maintaining consistency within a single database node is relatively easy formost databases. The real problems start to surface when you try to maintainconsistency between multiple database servers. If a client makes a writeoperation on server A, how do we make sure that this is consistent withserver B, or C, or D? For relational databases, this is a very complexproblem with entire books devoted to its solution. You could usemulti-master, single-master, partitioning, sharding, write-through caches,and all sorts of other complex techniques.

1.3.6. Incremental Replication

CouchDB’s operations take place within the context of a single document.As CouchDB achieves eventual consistency between multiple databases by usingincremental replication you no longer have to worry about your databaseservers being able to stay in constant communication. Incremental replicationis a process where document changes are periodically copied between servers.We are able to build what’s known as a shared nothing cluster of databaseswhere each node is independent and self-sufficient, leaving no single pointof contention across the system.

Need to scale out your CouchDB database cluster? Just throw in another server.

As illustrated in Figure 4. Incremental replication between CouchDB nodes, with CouchDB’s incrementalreplication, you can synchronize your data between any two databases howeveryou like and whenever you like. After replication, each database is ableto work independently.

You could use this feature to synchronize database servers within a clusteror between data centers using a job scheduler such as cron,or you could use it to synchronize data with your laptop for offline work asyou travel. Each database can be used in the usual fashion,and changes between databases can be synchronized later in both directions.

Incremental replication between CouchDB nodes
Figure 4. Incremental replication between CouchDB nodes

What happens when you change the same document in two different databases andwant to synchronize these with each other? CouchDB’s replication systemcomes with automatic conflict detection and resolution. When CouchDB detectsthat a document has been changed in both databases, it flags this documentas being in conflict, much like they would be in a regular version controlsystem.

This isn’t as troublesome as it might first sound. When two versions of adocument conflict during replication, the winning version is saved as themost recent version in the document’s history. Instead of throwing the losingversion away, as you might expect, CouchDB saves this as a previous versionin the document’s history, so that you can access it if you need to. Thishappens automatically and consistently, so both databases will make exactlythe same choice.

It is up to you to handle conflicts in a way that makes sense for yourapplication. You can leave the chosen document versions in place,revert to the older version, or try to merge the two versions and save theresult.

1.3.7. Case Study

Greg Borenstein, a friend and coworker, built a small library for convertingSongbird playlists to JSON objects and decided to store these in CouchDB aspart of a backup application. The completed software uses CouchDB’s MVCC anddocument revisions to ensure that Songbird playlists are backed up robustlybetween nodes.

Note

Songbird is a free software media player with an integrated web browser,based on the Mozilla XULRunner platform. Songbird is available for MicrosoftWindows, Apple Mac OS X, Solaris, and Linux.

Let’s examine the workflow of the Songbird backup application,first as a user backing up from a single computer, and then using Songbird tosynchronize playlists between multiple computers. We’ll see how documentrevisions turn what could have been a hairy problem into something that justworks.

The first time we use this backup application, we feed our playlists to theapplication and initiate a backup. Each playlist is converted to a JSONobject and handed to a CouchDB database. As illustrated inFigure 5. Backing up to a single database, CouchDB hands back the document ID andrevision of each playlist as it’s saved to the database.

Backing up to a single database
Figure 5. Backing up to a single database

After a few days, we find that our playlists have been updated and we want toback up our changes. After we have fed our playlists to the backupapplication, it fetches the latest versions from CouchDB,along with the corresponding document revisions. When the application handsback the new playlist document, CouchDB requires that the document revisionis included in the request.

CouchDB then makes sure that the document revision handed to it in therequest matches the current revision held in the database. Because CouchDBupdates the revision with every modification, if these two are out of sync itsuggests that someone else has made changes to the document between the timewe requested it from the database and the time we sent our updates. Makingchanges to a document after someone else has modified it without firstinspecting those changes is usually a bad idea.

Forcing clients to hand back the correct document revision is the heart ofCouchDB’s optimistic concurrency.

We have a laptop we want to keep synchronized with our desktop computer.With all our playlists on our desktop, the first step is to“restore from backup” onto our laptop. This is the first time we’ve done this,so afterward our laptop should hold an exact replica of our desktop playlistcollection.

After editing our Argentine Tango playlist on our laptop to add a few newsongs we’ve purchased, we want to save our changes. The backup applicationreplaces the playlist document in our laptop CouchDB database and a newdocument revision is generated. A few days later, we remember our new songsand want to copy the playlist across to our desktop computer. As illustratedin Figure 6. Synchronizing between two databases, the backup application copies the new documentand the new revision to the desktop CouchDB database. Both CouchDB databasesnow have the same document revision.

Synchronizing between two databases
Figure 6. Synchronizing between two databases

Because CouchDB tracks document revisions, it ensures that updates like thesewill work only if they are based on current information. If we had mademodifications to the playlist backups between synchronization,things wouldn’t go as smoothly.

We back up some changes on our laptop and forget to synchronize. A few dayslater, we’re editing playlists on our desktop computer, make a backup,and want to synchronize this to our laptop. As illustrated inFigure 7. Synchronization conflicts between two databases, when our backup application tries to replicatebetween the two databases, CouchDB sees that the changes being sent from ourdesktop computer are modifications of out-of-date documents and helpfullyinforms us that there has been a conflict.

Recovering from this error is easy to accomplish from an applicationperspective. Just download CouchDB’s version of the playlist and provide anopportunity to merge the changes or save local modifications into a newplaylist.

Synchronization conflicts between two databases
Figure 7. Synchronization conflicts between two databases

1.3.8. Wrapping Up

CouchDB’s design borrows heavily from web architecture and the lessonslearned deploying massively distributed systems on that architecture.By understanding why this architecture works the way it does,and by learning to spot which parts of your application can be easilydistributed and which parts cannot, you’ll enhance your ability to designdistributed and scalable applications, with CouchDB or without it.

We’ve covered the main issues surrounding CouchDB’s consistency model andhinted at some of the benefits to be had when you work with CouchDB and notagainst it. But enough theory – let’s get up and running and see what all thefuss is about!

原文: http://docs.couchdb.org/en/stable/intro/consistency.html