Write concern is a client setting used to describe how safely a write should be stored before the application continues. By default, inserts, removes, and updates wait for a database response—did the write succeed or not?—before continuing. Generally, clients will throw an exception (or whatever the language’s version of an exception is) on failure.
There are a number of options available to tune exactly what you want the application to wait for. The two basic write concerns are acknowledged or unacknowledged writes.
Acknowledged writes are the default: you get a response that tells you whether or not the database successfully processed your write. Unacknowledged writes do not return any response, so you do not know if the write succeeded or not.
In general, applications should stick with acknowledged writes. However, for low-value data (e.g., logs or bulk data loading), you may not want to wait for a response you don’t care about. In these situations, use unacknowledged writes.
Although unacknowledged writes will not return database errors, they do not eliminate the need for error checking in your application. If the socket was closed or there was an error writing to it, attempting a write will cause an exception.
One type of error that is easy to miss when using unacknowledged writes is inserting invalid data. For example, if we attempt to insert two documents with the same "_id", the shell will throw an exception:
> db.foo.insert({"_id" : 1})
> db.foo.insert({"_id" : 1})
E11000 duplicate key error index: test.foo.$_id_ dup key: { : 1.0 }
Were the second write sent with “unacknowledged” write concern, the second insert would not throw an exception. Duplicate key exceptions are a common source of errors, but there are many others, from invalid $-modifiers to running out of disk space.
The shell does not actually support write concerns in the same way that the client li‐
braries do: it does unacknowledged writes and then checks that the last operation was successful before drawing the prompt. Thus, if you do a series of invalid operations on a collection, finishing with a valid operation, the shell will not complain:
> db.foo.insert({"_id" : 1}); db.foo.insert({"_id" : 1}); db.foo.count() 1
You can manually force a check in the shell by calling getLastError, which checks for an error on the last operation:
> db.foo.insert({"_id" : 1}); db.foo.insert({"_id" : 1}); print(
... db.getLastError()); db.foo.count()
E11000 duplicate key error index: test.foo.$_id_ dup key: { : 1.0 } 1
This can be helpful when scripting for the shell.
Setting a Write Concern | 51
There are actually several other write concern options that are covered in later chapters:
Chapter 11 covers write concern for multiple servers and Chapter 19 covers committing to disk on a per-write basis.
The default write concern was changed in 2012, so legacy code may behave differently. Prior to the change, writes were unacknowledged by default.
Fortunately, there is an easy way to tell if you’re using code written before or after the write concern switch: all of the drivers began using a class called MongoClient when they began defaulting to safe writes. If your program is using a connection object called Mongo or Connec tion or something else, you are using the old, default-unsafe API. No language used MongoClient as a class name prior to the switch, so if your code is using that, your writes are safe.
If you are using non-MongoClient connections, you should changed unacknowledged writes to acknowledged writes wherever possible in old code.
52 | Chapter 3: Creating, Updating, and Deleting Documents
CHAPTER 4
Querying
This chapter looks at querying in detail. The main areas covered are as follows:
• You can perform ad hoc queries on the database using the find or findOne functions and a query document.
• You can query for ranges, set inclusion, inequalities, and more by using
$-conditionals.
• Queries return a database cursor, which lazily returns batches of documents as you need them.
• There are a lot of metaoperations you can perform on a cursor, including skipping a certain number of results, limiting the number of results returned, and sorting results.
Introduction to find
The find method is used to perform queries in MongoDB. Querying returns a subset of documents in a collection, from no documents at all to the entire collection. Which documents get returned is determined by the first argument to find, which is a docu‐
ment specifying the query criteria.
An empty query document (i.e., {}) matches everything in the collection. If find isn’t given a query document, it defaults to {}. For example, the following:
> db.c.find()
matches every document in the collection c (and returns these documents in batches).
When we start adding key/value pairs to the query document, we begin restricting our search. This works in a straightforward way for most types: numbers match numbers, booleans match booleans, and strings match strings. Querying for a simple type is as 53
easy as specifying the value that you are looking for. For example, to find all documents where the value for "age" is 27, we can add that key/value pair to the query document:
> db.users.find({"age" : 27})
If we have a string we want to match, such as a "username" key with the value "joe", we use that key/value pair instead:
> db.users.find({"username" : "joe"})
Multiple conditions can be strung together by adding more key/value pairs to the query document, which gets interpreted as “condition1 AND condition2 AND … AND conditionN.” For instance, to get all users who are 27-year-olds with the username “joe,”
we can query for the following:
> db.users.find({"username" : "joe", "age" : 27})