Skip to content

DOCS-2455: Split up the Isolate Sequence of Operations tutorial #2011

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions config/htaccess.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4826,6 +4826,13 @@ from: /core/multikey-index-bounds
to: /core/indexes/
code: 303
type: redirect
outputs:
- 'before-v2.4'
---
from: /tutorial/update-if-current
to: /tutorial/isolate-sequence-of-operations
code: 303
type: redirect
outputs:
- 'before-v2.4'
...
6 changes: 5 additions & 1 deletion source/includes/toc-crud-tutorials.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,14 @@ description: |
---
file: /tutorial/isolate-sequence-of-operations
description: |
Use the :operator:`<isolation> isolated` operator to *isolate* a
Use the :update:`$isolated` operator to *isolate* a
single write operation that affects multiple documents, preventing
other operations from interrupting the sequence of write operations.
---
file: /tutorial/update-if-current
description: |
Update a document only if it has not changed since it was last read.
---
file: /tutorial/create-an-auto-incrementing-field
description: |
Describes how to create an incrementing sequence number for the
Expand Down
127 changes: 33 additions & 94 deletions source/tutorial/isolate-sequence-of-operations.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,109 +13,48 @@ more than one collection.

When a single write operation modifies multiple documents, the
operation as a whole is not atomic, and other operations may
interleave. The modification of a single document, or record, is always
atomic, even if the write operation modifies multiple sub-documents
*within* the single record.
interleave. The modification of a single document is always atomic, even if
the write operation modifies multiple sub-documents *within* the single record.

No other operations are atomic; however, you can *isolate* a
No other operations are atomic. However, you can *isolate* a
single write operation that affects multiple documents using the
:doc:`isolation operator </reference/operator/update/isolated>`.
:doc:`isolation operator </reference/operator/update/isolated>`. Using this
operator, you can update a set of documents without allowing other processes
to update in between your writes.

This document describes one method of updating documents *only* if the
local copy of the document reflects the current state of the document
in the database. In addition the following methods provide a way to
manage isolated sequences of operations:
It is important to understand that this is *not* true atomicity, because a
failed write will not roll back the preceding writes. Additionally, this
operator does *not* work on sharded clusters.

- the :method:`~db.collection.findAndModify()`
provides an isolated update and return operation.
Approaches
----------

- :doc:`/tutorial/perform-two-phase-commits`
``findAndModify()``
~~~~~~~~~~~~~~~~~~~

- Create a :ref:`unique index <index-type-unique>`, to ensure that a
key doesn't exist when you insert it.
The :method:`~db.collection.findAndModify()` method provides an isolated
update-and-return operation. This allows you to update a document and get back
the updated state, without having to worry about another process having mutated
it.

.. _tutorial-atomic-update-if-current:
Two Phase Commit
~~~~~~~~~~~~~~~~

Update if Current
-----------------

In this pattern, you will:

- query for a document,

- modify the fields in that document

- and update the fields of a document *only if* the fields have not
changed in the collection since the query.

Consider the following example in JavaScript which attempts to update
the ``qty`` field of a document in the ``products`` collection:

.. versionchanged:: 2.6
The :method:`db.collection.update()` method now returns a
:method:`WriteResult()` object that contains the status of
the operation. Previous versions required an extra
:method:`db.getLastErrorObj()` method call.

.. code-block:: javascript

var myCollection = db.products;
var myDocument = myCollection.findOne( { sku: 'abc123' } );
:doc:`Two Phase Commit </tutorial/perform-two-phase-commits>` is a general
protocol for multi-document transactions that you may implement in your
application logic.

if (myDocument) {
Unique Indexes
~~~~~~~~~~~~~~

var oldQty = myDocument.qty;
Create a :ref:`unique index <index-type-unique>` to ensure that each document
has a distinct value for a specific field. In situations where you assign a
value that does not already exist in a collection, this prevents a second
process from assigning the same value elsewhere at the same time.

if (myDocument.qty < 10) {
myDocument.qty *= 4;
} else if ( myDocument.qty < 20 ) {
myDocument.qty *= 3;
} else {
myDocument.qty *= 2;
}

var results = myCollection.update(
{
_id: myDocument._id,
qty: oldQty
},
{
$set: { qty: myDocument.qty }
}
);

if ( results.hasWriteError() ) {
print("unexpected error updating document: " + tojson( results ));
} else if ( results.nMatched == 0 ) {
print("No update: no matching document for { _id: " + myDocument._id + ", qty: " + oldQty + " }")
}

}

Your application may require some modifications of this pattern, such
as:

- Use the entire document as the query in the
:method:`~db.collection.update()` operation, to generalize the
operation and guarantee that the original document was not modified,
rather than ensuring that as single field was not changed.

- Add a version variable to the document that applications increment
upon each update operation to the documents. Use this version
variable in the query expression. You must be able to ensure that
*all* clients that connect to your database obey this constraint.

- Use :update:`$set` in the update expression to modify only your
fields and prevent overriding other fields.

- Use one of the methods described in :doc:`/tutorial/create-an-auto-incrementing-field`.
Update if Current
~~~~~~~~~~~~~~~~~

.. Maybe incorporate the blurb: "MongoDB does not
support traditional locking and complex transactions for a number of
reasons: First, in sharded environments, distributed locks could be
expensive and slow. MongoDB's goal is to be lightweight and fast. We
dislike the concept of deadlocks. We want the system to be simple and
predictable without these sort of surprises. We want MongoDB to work
well for realtime problems. If an operation may execute which locks
large amounts of data, it might stop some small light queries for an
extended period of time."
:doc:`Update if Current </tutorial/update-if-current>` is a pattern for
changing a document without potentially overwriting the changes of other
processes.
106 changes: 106 additions & 0 deletions source/tutorial/update-if-current.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
==========================
Update Document if Current
==========================

.. default-domain:: mongodb

Overview
------------

To isolate a group of document modifications on a set of documents, always
attempt to use a combination of unique indexes and a single
:method:`db.collection.update()` operation. Because all modifications are atomic
within a single document, a well-designed data model allows applications to
group related operations together.

Nevertheless there are cases where a logical data modification operation requires
application-based logic. Because MongoDB cannot isolate application-based
update logic, your code must ensure that its multi-stage update operations
are logically valid and not affected by other concurrent operations in your
system.

.. _tutorial-atomic-update-if-current:

Update if Current Pattern
-------------------------

Overview
~~~~~~~~

In the :ref:`Update if Current
<tutorial-atomic-update-if-current>` pattern, you will:

- query for a document,

- modify the fields in that document,

- and update the fields of a document *only if* the fields have not
changed in the collection since the query.

Example
~~~~~~~

Consider the following example in the MongoDB shell which attempts to update
the ``qty`` field of a document in the ``products`` collection:

.. versionchanged:: 2.6
The :method:`db.collection.update()` method now returns a
:method:`WriteResult()` object that contains the status of
the operation. Previous versions required an extra
:method:`db.getLastErrorObj()` method call.

.. code-block:: javascript

// Update a document only if it is safe to do so
function updateIfCurrent( collection, query, modifyFunc ) {
var myDocument = collection.findOne( query );

if ( myDocument ) {
var newField = modifyFunc( myDocument );
var updateDoc = { $set: newField }

// This will fail to match any documents if the underlying document
// has some field different from myDocument.
var results = collection.update( myDocument, updateDoc );

if ( results.hasWriteError() ) {
print( "unexpected error updating document: " + tojson( results ) );
} else if ( results.nMatched === 0 ) {
print( "No update: no matching document for " + tojson( query ) );
}
}
}

// Apply an update function to a db.products document, or fail.
updateIfCurrent( db.products, { sky: "abc123" }, function( doc ) {
var qty = doc.qty

if ( qty < 10 ) {
qty *= 4;
} else if ( qty < 20 ) {
qty *= 3;
} else {
qty *= 2;
}

return { qty: qty };
} );

Additional Information
----------------------

A different approach is to add a ``version`` field to the document that
applications increment upon each update operation to the documents. Use this
version variable in the query expression. You must be able to ensure that
*all* clients that connect to your database obey this constraint.

If you need to associate increasing numbers with documents in a collection,
you can use one of the methods described in
:doc:`/tutorial/create-an-auto-incrementing-field`.

Use :update:`$set` in the update expression to modify only your fields and
prevent overriding other fields.

:doc:`Two Phase Commit </tutorial/perform-two-phase-commits>` is a general
protocol for multi-document transactions that you may implement in your
application logic.