Loading TOC...
Node.js Application Developer's Guide (PDF)

Node.js Application Developer's Guide — Chapter 6

Managing Transactions

This chapter covers the following topics related to transaction management using the Node.js Client API.

Transaction Overview

This section gives a brief introduction to the MarkLogic Server transaction model as it applies to the Node.js Client API. For a full discussion of the MarkLogic transaction model, see Understanding Transactions in MarkLogic Server in the Application Developer's Guide..

By default, each operation on the database is equivalent to a single statement transaction. That is, the operation is evaluated as single transaction. For example, when you update one or more documents in the database using DatabaseClient.documents.write, the server-side handler effectively creates a new transaction, updates the document(s), commits the transaction, and then sends back a response. The updated documents are visible in the database and available to other operations once the write operation completes successfully. If an error occurs in during the update of one of the documents, the entire operation fails.

The Node.js Client API also enables your application to take direct control of transaction boundaries so that multiple operations can be evaluated in the same transaction context. This is equivalent to the multi-statement transactions described in Multi-Statement Transaction Concept Summary in the Application Developer's Guide.

Using multi-statement transactions, you can execute several operations and commit them as a single transaction, ensuring either all or none of the related updates appear in the database. The document manipulation and search capabilities of the Node.js Client API support multi-statement transactions through the DatabaseClient.transactions interface, plus the ability to pass a transaction object to most operations.

To use multi-statement transactions:

  1. Create a multi-statement transaction using DatabaseClient.transactions.open. This operation returns a transaction object. See Creating a Transaction.
  2. Perform one or more operations in the context of the transaction by including the transaction object for the txid parameter. See Associating a Transaction with an Operation.
  3. Commit the transaction using DatabaseClient.transactions.commit, or rollback the transaction using DatabaseClient.transactions.rollback. See Committing a Transaction and Rolling Back a Transaction.

If your application interacts with MarkLogic Server through a load balancer, you might need to include a HostId cookie in your requests to preserve session affinity. For details, see Managing Transactions When Using a Load Balancer.

When you explicitly create a transaction, you must explicitly commit it or roll it back. Failure to do so leaves the transaction open until the request or transaction timeout expires. Open transactions can hold locks and consume system resources, so it is important to close transactions when they are complete.

If the request or transaction timeout expires before a transaction is committed, the transaction is automatically rolled back and all updates are discarded. Configure the request timeout of the App Server using the Admin UI. Configure the timeout of a single transaction by setting the timeLimit request parameter during transaction creation.

Creating a Transaction

Use DatabaseClient.transactions.open to create a multi-statement transaction. For example:

var txObj = null;
  .then(function(response) {
    txObj = response

Passing 'true' in tells the operation to return a stateful transaction object. This is the preferred method of creating a transaction as it encapsulates state needed to preserve host affinity across the transaction, even in the presence of a load balancer.

Multi-statement transactions must be explicitly committed or rolled back. Failure to commit or rollback the transaction before the request timeout expires causes an automatic rollback. You can assign a shorter time limit to a transaction by supplying a time limit (in seconds) to open: For example, the following sets the time limit to tlimit and returns a stateful transaction object:

db.transactions.open({timeLimit: tlimit, withState: true})

You should not depend on the time limit rolling back your transaction. The limit is only a failsafe. Instead, you should explicitly rollback your transaction when appropriate.

You can also provide a symbolic name when you create a transaction. You must still use the transaction object (or id) in all operations that accept a transaction parameter, but the name can be used with DatabaseClient.transactions.read and will show up in the Admin Interface and other transaction status displays.

For example, the following call provides both a time limit and a name, using an input call object with appropriate property names:

  timeLimit: 45,
  transactionName: 'mySpecialTxn',
  withState: true

Associating a Transaction with an Operation

Once you create a transaction using DatabaseClient.transactions.open, you can pass the resulting transaction object (or id) to various operations to perform the operation in the context of a specific transaction.

For example, to update a document in the context of a specific multi-statement transaction, include a transaction id in the DatabaseClient.documents.write call:

var txnObj = null;
  .then(function(response) {
     txnObj = response;
     return db.documents.write({
       uri: '/my/documents.json',
       content: {some: 'content'},
       contentType: 'application/json',
       txid: txnObj

Updates associated with a multi-statement transaction are visible to subsequent operations using the same transaction, but they are not visible outside the transaction until the transaction is committed.

You can have multiple transactions open at the same time, and/or other users can be using the same database concurrently. To prevent conflicts, whenever an update occurs in a transaction, the document is locked until the transaction either commits or rolls back. Therefore, you should commit or roll back your transactions as soon as possible to avoid resource contention.

The database context in which you perform an operation must be the same as the database context in which the transaction was created. Consistency is assured if you're only using a single DatabaseClient configuration.

You can intermix operations that are not part of a transaction with operations that are. Any operation without a txid parameter or call object property is not part of a multi-statement transaction. However, you usually group operations in the same transaction together so you can commit or roll back the transaction in a timely fashion.

Committing a Transaction

Use DatabaseClient.transactions.commit to commit a multi-statement transaction. Supply the transaction object (or id) from DatabaseClient.transactions.open in your commit call. For example:


Once a transaction is committed, it cannot be rolled back, and the transaction object (or id) can no longer be used. To perform another transaction, obtain a new transaction by calling open.

The database context in which you commit or roll back a transaction must be the same as the database context in which the transaction was created. Consistency is assured if you're only using a single DatabaseClient configuration.

Rolling Back a Transaction

In case of an error or exception, you can roll back an open transaction using DatabaseClient.transactions.rollback.


Calling rollback cancels the remainder of the transactions and reverts the database to its state prior to the transaction start. It is better to explicitly roll back a transaction than wait for a timeout.

You must have the rest-writer or rest-admin role or equivalent privileges to roll back a transaction.

The database context in which you commit or roll back a transaction must be the same as the database context in which the transaction was created. Consistency is assured if you're only using a single DatabaseClient configuration.

When working with multi-statement transactions, you should ensure your transaction is rolled back expliciting in the event of an error by including a catch clause that calls rollback. For example:

var txnObj = null;
  then(function(response) {
    txnObj = response;
    return db.documents.read({uris: oldUri, txid: txnObj}).result();
  catch(function() {

Example: Using Promises With a Multi-Statement Transaction

The following function demonstrates how you can use the Promise pattern to synchronize operations within a multi-statement transaction. To learn more about Promises, see Promise Result Handling Pattern.

This function 'moves' a document by reading the contents from the initial URI, inserting the contents into the database with the new URI, and then removing the original document. The function initially creates a transaction, then executes the read, write, and remove operations in the context of that transaction. When these operations complete, the transaction is committed. If an error occurs, the transaction is rolled back.

function transactionalMove(oldUri, newUri) {
  var txnObj = null;
    then(function(response) {
      txnObj = response;
      return db.documents.read({uris: oldUri, txid: txnObj}).result();
    then(function(documents) {
      documents[0].uri = newUri;
      return db.documents.write(
        {documents: documents, txid: txnObj}).result();
    then(function(response) {
      return db.documents.remove({uri: oldUri, txid: txnObj}).result();
    then(function(response) {
      return db.transactions.commit(txnObj).result();
    catch(function(error) {
      console.log('ERROR: ' + JSON.stringify(error));

Checking Transaction Status

Use DatabaseClient.transactions.read to query the status of a transaction. For example:


Managing Transactions When Using a Load Balancer

This section applies only to client applications that use multi-statement transactions and interact with a MarkLogic Server cluster through a load balancer.

When you use a load balancer, it is possible for requests from your application to MarkLogic Server to be routed to different hosts, even within the same session. This has no effect on most interactions with MarkLogic Server, but operations that are part of the same multi-statement transaction need to be routed to the same host within your MarkLogic cluster. This consistent routing through a load balancer is called session affinity.

To properly preserve session affinity, you must call Transaction.open in a way that returns a transaction object, rather than a simple string transaction id. That is, you must ensure the withState parameter (or call object property) is true. For example, use one of these forms:


...db.transactions.open({withState: true, ...});

When you open a transaction in this way, a HostId cookied is cached on the returned transaction object. Whenever your application passes the transaction object to an operation that sends a request to MarkLogic Server, the HostId cookie is automatically included in the request. You can configure your load balancer to use the HostId cookie to preserve session affinity.

The exact steps required to configure your load balancer to use the HostId cookie for session affinity depend upon your load balancer. Consult your load balancer documentation for details.

If a request is not routed through a load balancer, the HostId cookie is ignored. The Node.js Client API does not persist the HostId cookie. The cookie does not include any session state. The cookie value is not used by the API for anything else.

« Previous chapter
Next chapter »