Blog

Error Handling - MongoDB .NET Driver

Friday, January 5, 2018 Comments (0)

Since you want to open an online store, you need to think about security and performance. There is a lot of requirements which you should meet to create a successfull and powerfull e-commerce system. Error handling using the .NET driver is one of the requirements for a fault-tolerant system. The main advantage of MongoDB is fact that there is a high availability of replica set. Using automatic failover, MongoDBs replica sets provide high availability. What does it mean for you? Failover allows a secondary member to become a primary if the current one becomes unavailable. But you should know that it's not enough to think that your ecommerce application is immune to errors. 

Profesionally, almost all errors will occure while performing an operation on the server. Basically, you won't receive an error when you will construct a MongoClient, try to get database or try to get collection. The reason of that situation is very simple, we are trying to connect to servers in the background, if problem occur, application will continually try to reconnect. Errors will appear only when you will try to  perform a specified operation.

In MongoDB .NET driver you will find a few types of errors - I will describe them below.

MongoDB Compatibility in GrandNode

Sometimes you can find the following error:
Setup failed: Command insert failed: Client Error: bad object in message: Cannot use decimal BSON type when the featureCompatibilityVersion is 3.2. See http://dochub.mongodb.org/core/3.4-feature-compatibility

In this case, you need to do a very simple thing. Just open your database and use following command:
db.adminCommand( { setFeatureCompatibilityVersion: "3.4" } )

Server Selection Errors

You need to remember that sometimes, even when servers are available and you can try to send request, it may cause problem to finish request. For example, you can face that problem when using tag sets in a read preference when no server exists with used tags or attempting to write to a replica set when the Primary replica is unavailable. In the case of mentioned situations, they will cause TimeoutException. Below you can find an example of exception, of course formatted for readability, when attempting to insert into a replica set without a primary replica available. 

System.TimeoutException: A timeout occured after 30000ms selecting a server using
CompositeServerSelector{
    Selectors =
        WritableServerSelector,
        LatencyLimitingServerSelector{ AllowedLatencyRange = 00:00:00.0150000 }
}.

Client view of cluster state is
{
    ClusterId : "1",
    Type : "ReplicaSet",
    State : "Connected",
    Servers : [
        {
            ServerId: "{ ClusterId : 1, EndPoint : "Unspecified/clover:30000" }",
            EndPoint: "Unspecified/clover:30000",
            State: "Disconnected", Type: "Unknown"
        },
        { 
            ServerId: "{ ClusterId : 1, EndPoint : "Unspecified/clover:30001" }",
            EndPoint: "Unspecified/clover:30001",
            State: "Connected", Type: "ReplicaSetSecondary",
            WireVersionRange: "[0, 3]"
        },
        {
            ServerId: "{ ClusterId : 1, EndPoint : "Unspecified/clover:30002" }",
            EndPoint: "Unspecified/clover:30002",
            State: "Disconnected",
            Type: "Unknown"
         },
    ]
}.

If you will inspect this error, you should notice that we were attempting to find a writable server. As you may notice, we have 3 recognized servers - clover:30000, clover 30001 and clover 30002. Of these, clover:30000 and clover:30002 are disconnected, clover:30001 is connected and a secondary. By default, we wait for 30 seconds to finish that request. During this time, we are trying to connect to clover:30000 and clover:30002, we do it in the hope that one of them becomes available and will take the role of primary. In mentioned case, unfortunately, we timed out waiting. 

Connection Errors

Reasons why the server might be unavailable are hundreds. Unfortunately, each of these reasons will occur as a TimeoutException. 

You can try to enable network tracing on the System.Net.Sockets source because it may help you discover the problem. 

IMPORTANT THING

"There is nothing that can be done at runtime by your application to resolve these problems. The best thing to do is to catch them, log them, and raise a notification so that someone in your ops team can handle them." - MongoDB

Server is unavailable

It's simple, when server is not available, the driver can't connect to it.

System.TimeoutException: A timeout occured after 30000ms selecting a server using
CompositeServerSelector{
    Selectors =
        WritableServerSelector,
        LatencyLimitingServerSelector{ AllowedLatencyRange = 00:00:00.0150000 }
}.

Client view of cluster state is
{
    ClusterId : "1",
    Type : "Unknown",
    State : "Disconnected",
    Servers : [
        {
            ServerId: "{ ClusterId : 1, EndPoint : "Unspecified/localhost:27017" }",
            EndPoint: "Unspecified/localhost:27017",
            State: "Disconnected",
            Type: "Unknown",
HeartbeatException: "MongoDB.Driver.MongoConnectionException: An exception occurred while opening a connection to the server.
---> System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it 127.0.0.1:27017
at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult).
... snip ..."
        }
    ]
}

The reason is simple, so the solution also is very simple. Just start a server at the specified location or restart your app with the correct host or port. 

DNS Problems

When DNS is misconfigured, or the hostname provided is not registered, resolution from hostname to IP address may fail.

System.TimeoutException: A timeout occured after 30000ms selecting a server using
CompositeServerSelector{
    Selectors =
        WritableServerSelector,
        LatencyLimitingServerSelector{ AllowedLatencyRange = 00:00:00.0150000 }
}.

Client view of cluster state is
{
    ClusterId : "1",
    Type : "Unknown",
    State : "Disconnected",
    Servers : [
        {
            ServerId: "{ ClusterId : 1, EndPoint : "Unspecified/idonotexist:27017" }",
            EndPoint: "Unspecified/idonotexist:27017",
            State: "Disconnected",
            Type: "Unknown",
            HeartbeatException: "MongoDB.Driver.MongoConnectionException: An exception occurred while opening a connection to the server.
---> System.Net.Sockets.SocketException: No such host is known
at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult)
... snip ..."
        }
    ]
}

As you can see, we are attempting to host which doesn't exist. 

You can fix it simple, by your DNS modification.

Replica Set Misconfiguration

If you will misconfigure a replica set, it's huge probability that you will see problems with DNS. It is crucial that the hosts in your replica set configuration be DNS resolvable from the client. Just because the replica set members can talk to one another does not mean that your application servers can also talk to the replica set members. 

Taking too long to respond

When the latency between the driver and the server is too great, the driver may give up. Example below:

System.TimeoutException: A timeout occured after 30000ms selecting a server using
CompositeServerSelector{
    Selectors =
        WritableServerSelector,
        LatencyLimitingServerSelector{ AllowedLatencyRange = 00:00:00.0150000 }
}.

Client view of cluster state is
{
    ClusterId : "1",
    Type : "Unknown",
    State : "Disconnected",
    Servers : [
        {
            ServerId: "{ ClusterId : 1, EndPoint : "Unspecified/somefaroffplace:27017" }",
            EndPoint: "Unspecified/somefaroffplace:27017",
            State: "Disconnected", Type: "Unknown",
            HeartbeatException: "MongoDB.Driver.MongoConnectionException: An exception occurred while opening a connection to the server.
---> System.TimeoutException: Timed out connecting to Unspecified/somefaroffplace:27017. Timeout was 00:00:30.
at MongoDB.Driver.Core.Connections.TcpStreamFactory.<ConnectAsync>d__7.MoveNext()
... snip ...
        }
    ]
}

Request failed, as we can see, because: “Timed out connecting to Unspecified/somefaroffplace:27017. Timeout was 00:00:30.”

The default connection timeout is 30 seconds and you can change it using the MongoClientSettings.ConnectTimeout property or the connectTimeout option on a connection string.

Authentication errors

Application will fail to connect when you will enter wrong credentials. 

System.TimeoutException: A timeout occured after 30000ms selecting a server using
CompositeServerSelector{
    Selectors =
        WritableServerSelector,
        LatencyLimitingServerSelector{ AllowedLatencyRange = 00:00:00.0150000 }
}.

Client view of cluster state is
    {
        ClusterId : "1",
        Type : "Unknown", State : "Disconnected",
        Servers : [
            {
                ServerId: "{ ClusterId : 1, EndPoint : "Unspecified/localhost:27017" }",
                EndPoint: "Unspecified/localhost:27017",
                State: "Disconnected",
                Type: "Unknown",
                HeartbeatException: "MongoDB.Driver.MongoConnectionException: An exception occurred while opening a connection to the server.
---> MongoDB.Driver.MongoAuthenticationException: Unable to authenticate using sasl protocol mechanism SCRAM-SHA-1.
---> MongoDB.Driver.MongoCommandException: Command saslStart failed: Authentication failed..
... snip ...
        }
    ]
}

Fixing this problem either involves adding the specified user to the server or restarting the application with the correct credentials and mechanisms.

Operation errors in MongoDB

After successfully selecting a server to run an operation against, errors are still possible. Unlike connection errors, it is sometimes possible to take action at runtime.

NOTE

"Most of the exceptions that are thrown from an operation inherit from MongoException. In many cases, they also inherit from MongoServerException. The server exception contains a ConnectionId which can be used to tie the operation back to a specific server and a specific connection on that server. It is then possible correllate the error you are seeing in your application with an error in the server logs." - MongoDB Docs

Connection errors

A server may go down after it was selected, but before the operation was executed. These will always manifest as a MongoConnectionException>. Inspecting the inner exception will provide the actual details of the error.

MongoDB.Driver.MongoConnectionException: An exception occurred while receiving a message from the server.
---> System.IO.IOException: Unable to read data from the transport connection: Anestablished connection was aborted by the software in your host machine.
---> System.Net.Sockets.SocketException: An established connection was aborted by the software in your host machine
    at System.Net.Sockets.Socket.BeginReceive(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags, AsyncCallback callback, Object state) at System.Net.Sockets.NetworkStream.BeginRead(Byte[] buffer, Int32 offset, Int32 size, AsyncCallback callback, Object state)
    --- End of inner exception stack trace ---
... snip ...

Simply getting an exception of this type doesn’t give any insight into whether the operations was received by the server or what happened in the server if it was received.

Write exceptions

When performing a write, it is possible to receive a MongoWriteException. This exception has two important properties, WriteError and WriteConcernError.

Write Error

A write error means that there was an error applying the write. The cause could be many different things. The WriteError contains a number of properties which may help in the diagnosis of the problem. The Code property will indicate specifically what went wrong. For general categories of errors, the driver also provides a helpful Category property which classifies certain codes.

MongoDB.Driver.MongoBulkWriteException`1[MongoDB.Bson.BsonDocument]: A bulk write operation resulted in one or more errors.
    E11000 duplicate key error index: test.people.$_id_ dup key: { : 0 }
        at MongoDB.Driver.MongoCollectionImpl`1.<BulkWriteAsync>d__11.MoveNext() in c :\projects\mongo-csharp-driver\src\MongoDB.Driver\MongoCollectionImpl.cs:line 166

WriteConcernError

A write concern error indicates that the server was unable to guarantee the write operation to the level specified.

Bulk write exceptions

A MongoBulkWriteException will occur when using the InsertManyAsync or BulkWriteAsync. This exception is just a rollup of a bunch of individual write errors. It also includes a mentioned before WriteConcernError.

Ordered writes

Bulk writes are allowed to be processed either in order or without order. When processing in order, the first error that occurs will stop the processing of all subsequent writes. In this case, all of the unprocessed writes will be available via the UnprocessedRequests property.

Source: http://mongodb.github.io/mongo-csharp-driver/2.3/reference/driver/error_handling/

Leave your comment