Showing posts with label NoSQL. Show all posts
Showing posts with label NoSQL. Show all posts

Monday, November 11, 2013

Display MongoDB Data Using ASP.NET MVC

In last post I presented a logging server solution for the mobile platform using node.js and MongoDB. Today I will build a ASP.NET MVC application to display the logged data stored in MongoDB.

First download MongoDB latest driver from github. There're a few download options out there. I just used the msi installer, and two key dlls, MongoDB.Driver.dll and MongoDB.Bson.dll, were installed in my C:\Program Files (x86)\MongoDB\CSharpDriver 1.8.3\ folder. Then we are ready to create a ASP.NET MVC 3 or MVC 4 project using basic template in Visual Studio 2010, and add MongoDB.driver.dll and MongoDB.Bson.dll to the project References.

The mongoDB database can be running inside the same machine or in a separate machine. The only difference is the connection string, which follows the format of "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]". In my example here I have one MongoDB instance running locally so my connection string is "mongodb://localhost:27017".

Next we create a log item model and a repository to interact with MongoDB with three actions available: search logs within a time span, get original log detail by its ID, and delete a log by its ID (for simplicity reason exception handling is skipped):

using System;
using System.Collections.Generic;
using System.Linq;
 
using MongoDB.Driver;
using MongoDB.Bson;
using MongoDB.Driver.Builders;
 
namespace LogsReporting
{
    public class LogItem
    {
        public ObjectId ID { get; set; }
        public DateTime Date { get; set; }
        public string OS { get; set; }
        public string Version { get; set; }
        public string Level { get; set; }
        public string Manufacturer { get; set; }
        public string Model { get; set; }
        public string ScreenSize { get; set; }
        public string Language { get; set; }
        public string Orientation { get; set; }
        public string Timezone { get; set; }
        public string Message { get; set; }
    }
 
    public interface ILogsRepository 
    {
        string GetOriginalLog(string id);
        void DeleteLog(string id);
        IEnumerable<LogItem> GetLogItems(DateTime from, DateTime to);
    }
 
    public class LogsRepository : ILogsRepository
    {
        private MongoCollection<BsonDocument> _collection; 
        public LogsRepository()
        {
            string connectionString = "mongodb://localhost:27017";
            MongoServer server = new MongoClient(connectionString).GetServer();
            MongoDatabase db = server.GetDatabase("Data");
            _collection = db.GetCollection("Logs");
        }
 
        public string GetOriginalLog(string id)
        {
            ObjectId bsonId;
            if (ObjectId.TryParse(id, out bsonId))
            {
                var item = _collection.FindOneById(bsonId);
                if (item != null)
                    return item.ToString();
            }
            return string.Empty;
        }
 
        public void DeleteLog(string id)
        {
            ObjectId bsonId;
            if (ObjectId.TryParse(id, out bsonId))
            {
                _collection.Remove(Query.EQ("_id", bsonId));
            }
        }
 
        public IEnumerable<LogItem> GetLogItems(DateTime from, DateTime to)
        {
            List<LogItem> logs = new List<LogItem>();
            var docs = _collection.Find(Query.And(Query.GTE("time", from), Query.LTE("time", to)))
                .SetSortOrder(SortBy.Descending("time"));
            foreach (var doc in docs)
            {
                LogItem log = new LogItem()
                {
                    ID = doc.GetValue("_id").AsObjectId,
                    Date = TimeZone.CurrentTimeZone.ToLocalTime(doc.GetValue("time").ToUniversalTime()),
                    OS = doc.GetValue("os", "").ToString(),
                    Version = doc.GetValue("version", "").ToString(),
                    Manufacturer = doc.GetValue("manufacturer", "").ToString(),
                    Model = doc.GetValue("model", "").ToString(),
                    Language = doc.GetValue("lang", "").ToString(),
                    ScreenSize = doc.GetValue("screen", "").ToString(),
                    Orientation = doc.GetValue("orientation", "").ToString(),
                    Timezone = doc.GetValue("timezone", "").ToString(),
                    Level = doc.GetValue("level", "").ToString(),
                    Message = doc.GetValue("log", "").ToString()
                };
                if (!string.IsNullOrEmpty(log.Message) && log.Message.Length > 40)
                    log.Message = log.Message.Substring(0, 40) + " ...";
 
                logs.Add(log);
            }
            return logs;
        }
    }
}
MongoDB is schemaless, and can store arbitrary format of data. For convenience let's assume the logged data has following fields: time, os, version, manufacturer, model, language, screen-size, orientation, time-zone, log-level, and log message (refer to Windows 8 logging & reporting). Only "time" field (time stamp) is mandatory here so we could safely filter log entries by time.

The controller provides three actions: show recent logs, show the detail of a given log and delete a log. For demo purpose we only show data logged in the past week:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
 
namespace LogsReporting
{
    public class LogsController : Controller
    {
        private static readonly ILogsRepository _logs = new LogsRepository();
 
        public ActionResult Index()
        {
            var logs = _logs.GetLogItems(DateTime.Now.AddDays(-7), DateTime.Now);
            return View(logs);
        }
 
        public string Detail(string id)
        {
            var originalLog = _logs.GetOriginalLog(id);
            return originalLog;
        }
 
        public ActionResult Delete(string id)
        {
            _logs.DeleteLog(id);
            return RedirectToAction("Index");
        }
    }
}
The presentation layer is pretty straightforward: a grid view shows the logs' info with "Detail" and "Delete" links. Clicking "Detail" link will open up a new window to display the raw log message. A confirmation popup prompts for a "Delete" action:
@model IEnumerable<LogsReporting.LogItem>
 
<script type="text/javascript">
    function showDetailPopup(id) {
        var url = '@Url.Action("Detail")' + '/' + id;
        window.open(url, "detailWindow", 'width=600px,height=400px');
    }
</script> 
 
<h2 style="text-align: center;">Logs Reporting</h2>
 
<table cellpadding="5px">
    <tr>
        <th>@Html.DisplayNameFor(model => model.Date)</th>
        <th>@Html.DisplayNameFor(model => model.OS)</th>
        <th>@Html.DisplayNameFor(model => model.Version)</th>
        <th>@Html.DisplayNameFor(model => model.Manufacturer)</th>
        <th>@Html.DisplayNameFor(model => model.Model)</th>
        <th>@Html.DisplayNameFor(model => model.ScreenSize)</th>
        <th>@Html.DisplayNameFor(model => model.Language)</th>
        <th>@Html.DisplayNameFor(model => model.Orientation)</th>
        <th>@Html.DisplayNameFor(model => model.Timezone)</th>
        <th>@Html.DisplayNameFor(model => model.Level)</th>
        <th>@Html.DisplayNameFor(model => model.Message)</th>
        <th></th>
    </tr>
@{int i = 0;}
@foreach (var item in Model) {
    <tr style='font-size: 9pt; @(i++%2==0 ? "background-color: #bbbbbb" : "")'> 
        <td>@Html.DisplayFor(modelItem => item.Date)</td>
        <td>@Html.DisplayFor(modelItem => item.OS)</td>
        <td>@Html.DisplayFor(modelItem => item.Version)</td>
        <td>@Html.DisplayFor(modelItem => item.Manufacturer)</td>
        <td>@Html.DisplayFor(modelItem => item.Model)</td>
        <td>@Html.DisplayFor(modelItem => item.ScreenSize)</td>
        <td>@Html.DisplayFor(modelItem => item.Language)</td>
        <td>@Html.DisplayFor(modelItem => item.Orientation)</td>
        <td>@Html.DisplayFor(modelItem => item.Timezone)</td>
        <td>@Html.DisplayFor(modelItem => item.Level)</td>
        <td>@Html.DisplayFor(modelItem => item.Message)</td>
        <td>
            <a href="#" onclick="showDetailPopup('@item.ID');">Details</a> |
            @Html.ActionLink("Delete", "Delete", new { id = item.ID }, 
                new { onclick = "return confirm('Are you sure to delete this log entry?');" })
        </td>
    </tr>
}
</table>

Following screen-shot shows how the view looks like:

Updated on Nov. 12 Just for fun I created a traditional ASP.NET web form .aspx page to show the exact same view:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="LogReporting.aspx.cs" Inherits="LogsReporting.LogReporting" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Logs Reporting</title>
    <style type="text/css">
        h2 {text-align: center;}
        table {text-align: left; padding: 5px;}
        table td, .detail {font-size: 9pt; padding: 5px;}
    </style>
    <script src="Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(document).ready(function () {
            $("tr:odd").css("background-color", "#bbbbbb");
        });
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Panel ID="gridPanel" runat="server">
            <h2>Logs Reporting</h2>
            <asp:GridView ID="gvLogs" runat="server" AutoGenerateColumns="False" GridLines="None"
                OnRowDataBound="gvLogs_RowDataBound" EmptyDataText="No logs available">
                <Columns>
                    <asp:BoundField DataField="Date" HeaderText="Date"></asp:BoundField>
                    <asp:BoundField DataField="OS" HeaderText="OS"></asp:BoundField>
                    <asp:BoundField DataField="Version" HeaderText="Version"></asp:BoundField>
                    <asp:BoundField DataField="Language" HeaderText="Language"></asp:BoundField>
                    <asp:BoundField DataField="Manufacturer" HeaderText="Manufacturer"></asp:BoundField>
                    <asp:BoundField DataField="Model" HeaderText="OS"></asp:BoundField>
                    <asp:BoundField DataField="ScreenSize" HeaderText="Version"></asp:BoundField>
                    <asp:BoundField DataField="Level" HeaderText="Level"></asp:BoundField>
                    <asp:BoundField DataField="Message" HeaderText="Message"></asp:BoundField>
                    <asp:TemplateField>
                        <ItemTemplate>
                            <asp:LinkButton ID="lbtDetail" runat="server" Text="Detail"></asp:LinkButton>
                            <asp:LinkButton ID="lbtDelete" runat="server" Text="Delete" OnClick="lbtDelete_Click"
                                OnClientClick="return confirm('Are you sure to delete this log entry?');"></asp:LinkButton>
                        </ItemTemplate>
                    </asp:TemplateField>
                </Columns>
            </asp:GridView>
        </asp:Panel>
 
        <asp:Panel ID="detailPanel" runat="server" CssClass="detail">
            <asp:Literal ID="logDetail" runat="server"></asp:Literal>
        </asp:Panel>
    </div>
    </form>
</body>
</html>
The code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Configuration;
 
using MongoDB.Driver;
using MongoDB.Bson;
using MongoDB.Driver.Builders;
 
namespace LogsReporting
{
    public partial class LogReporting : System.Web.UI.Page
    {
        private ILogsRepository _repo = new LogsRepository();
 
        protected void Page_Load(object sender, EventArgs e)
        {
            bool showDetail = !string.IsNullOrEmpty(Request.QueryString["id"]);
            detailPanel.Visible = showDetail;
            gridPanel.Visible = !showDetail;
 
            if (showDetail)
            {
                logDetail.Text = _repo.GetOriginalLog(Request.QueryString["id"]);
            } 
            else if (!IsPostBack)
            {
                var dataSource = _repo.GetLogItems(DateTime.Today.AddDays(-7), DateTime.Now);
                bindDataSource(dataSource);
            } 
        }
 
        private void bindDataSource(IEnumerable<LogItem> logs)
        {
            gvLogs.DataSource = logs;
            gvLogs.DataBind();
        }
 
        protected void gvLogs_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.RowType == DataControlRowType.DataRow)
            {
                LogItem item = e.Row.DataItem as LogItem;
                LinkButton lbtDetail = e.Row.FindControl("lbtDetail") as LinkButton;
                LinkButton lbtDelete = e.Row.FindControl("lbtDelete") as LinkButton;
                string detailUrl = string.Format("{0}?id={1}", Request.Url.AbsolutePath, item.ID);
                lbtDetail.Attributes.Add("onClick", "window.open('" + detailUrl + "', '', 'width=600,height=400')");
                lbtDelete.CommandArgument = item.ID.ToString();
              }
        }
 
        protected void lbtDelete_Click(object sender, EventArgs e)
        {
            var logID = Convert.ToString(((LinkButton)sender).CommandArgument);
            _repo.DeleteLog(logID);
            var dataSource = _repo.GetLogItems(DateTime.Today.AddDays(-7), DateTime.Now);
            bindDataSource(dataSource);
        }
    }
}

Thursday, November 07, 2013

A Simple Logging Server Implementation Using Node.js and MongoDB

In my previous post I built a logging & reporting module for Windows 8 store apps. In this post I am going to build a simple while fast and scalable logging server.

Open source node.js is used to serve as http logging server. Node.js is a JavaScript server-side solution. It's light, efficient, and using event-driven and non-blocking I/O. These features make it a good fit for logging JSON objects sent by the client.

Traditionally the logging service would just save logs to file system. However it's hard to do data analysis and poor performance to do query over big size of logged data if using plain text file logs. Alternatively we can use document type NoSQL as the data store. In our use case, each log is an independent information unit, and document stores works extremely well in such scenario. Data insert and query would be faster and more efficient than traditional SQL database. We pick MongoDB as it's easy to use in different platform, well documented and has native support to JSON data.

Node.js requires a driver or module to connect to MongoDB database. We use mongojs in our example:

npm install mongojs

The whole logging server implementation is less than 100-line of code:

var http = require('http');
var url = require('url');

var mongojs = require('mongojs');
var db = mongojs('Data');
var collection = db.collection('Logs');

var contentType = {'Content-Type': 'text/html'};
var OK = 'OK', BAD = 'BAD REQUEST';
var MAX_URL_LENGTH = 2048, MAX_POST_DATA = 10240;

function createLog(request) {
    var logObj = {};
    try {
        logObj.time = new Date();
        logObj.method = request.method;
        logObj.host = request.headers.host;
        logObj.url = request.url;
        var queryStrings = url.parse(request.url, true).query;
        // Check if it's an empty object
        if (Object.keys(queryStrings).length) {
            logObj.query = queryStrings;
            for (var attrname in queryStrings) { 
                logObj[attrname] = queryStrings[attrname]; 
            }
        }
    } catch (ex) {}
    return logObj;
}

http.createServer(function (request, response) {
    // Bypass request with URL larger than 2K
    if (request.url.length > MAX_URL_LENGTH) {
        response.writeHead(414, contentType);
        response.end(BAD);
    } else if (request.method == 'GET') {  // HTTP GET
        var logObj = createLog(request);
        if (logObj.query) {
            delete logObj.query;
            collection.save(logObj);
            response.writeHead(200, contentType);
            response.end(OK);
        } else {  // Empty request
            response.writeHead(400, contentType);
            response.end(BAD);
            request.connection.destroy();
        }
    } else if(request.method == 'POST') {  // HTTP POST
        var logObj = createLog(request);
        var postData = '';
        request.on('data', function(data) {
            postData += data;
            // Bypass request with POST data larger than 10K
            if(postData.length > MAX_POST_DATA) {
                postData = "";
                response.writeHead(413, contentType);
                response.end(BAD);
                request.connection.destroy();
            }
        });
        request.on('end', function() {
            if (!postData && !logObj.query) {  // Empty request
                response.writeHead(400, contentType);
                response.end(BAD);
                request.connection.destroy();
            } else {
                if (postData) {
                    try {
                        postObjs = JSON.parse(postData);
                        for (var attrname in postObjs) { 
                            logObj[attrname] = postObjs[attrname]; 
                        }    
                    } catch (ex) {
                        logObj.postData = postData;
                    }
                }
                if (logObj.query) {
                    delete logObj.query;
                }
                collection.save(logObj);
                response.writeHead(200, contentType);
                response.end(OK);
            } 
        });
    }
}).listen(process.env.PORT || 8080);

Monday, July 26, 2010

MongoDB E1100 Duplicate Key On Update Error

It's caused by duplicate "_id" from MongoDB documentation http://www.mongodb.org/display/DOCS/Error+Codes.

I found this error when accidentally set duplicate value to a unique indexed field.

Saturday, May 15, 2010

SQL Server Vs MongoDB - Performance Perspective

Relational database such as SQL Server would be slower than NoSQL solutions. That's understandable. Relational database has its focus and ACID has its cost. But I didn't expect their performance gap is so big until I did some real tests.

The test machine is the same I tested on MongoDB last time:
  • OS: Windows Server 2008 64-bit SP2
  • CPU: Intel i5 750
  • Memory: 6G
  • DB: SQL Server 2008 Enterprise edition SP1.
Similar I did the test on MongoDB last time, I create a simple test database with only three columns:
  id    -- uniqueidentifier (Clustered index)
key -- varchar(40) (Unique non-clustered index)
value -- varchar(40) (no indexing)
MongoDB test code was modified to test SQL Server. I ran the console and was eager to see the results. But the first test case of inserting 1-million records in local database seemed to be never completed.

The insert operations were just too slow. Is there anything going wrong with my machine? I installed all Windows/SQL Server important updates, and disable almost all unnecessary processes, deflagment disk, and redid the test. But the result is still very disappointing, less than 400 insert per second.

Then I tried a few options:

1. Change id to be non-clustered index: no big difference
2. Change id type from uniqueidentifier to varchar(40): no big difference
3. Add an extra identity integer column (primary key): no big difference
4. Change key/value from varchar(40) to char(40): no big difference
5. Change key's index fillfactor to 10, 30, 60, 80, 100: slightly difference among them
6. Do insertion using SQL Script:
    DECLARE @id uniqueidentifier
DECLARE @Count int, @Max int
SET @Count = 0
SET @Max = 1000000
WHILE @Count < @Max
BEGIN
SET @id = NEWID()
INSERT INTO testTable (id, key, value) VALUES (@id, @id, @id)
SET @Count = @Count + 1
END
no big difference
8. Use stored procedure: improve a little
9. Change Key's unique non-clustered index to non-clustered index: improve a little.
10. Change database recovery model from full to simple: improve a little

The maximum insertion speed could reach around 900 per second after all kind of tuning in my local machine. That's not even close to MongoDB, and I didn't do any extra work to achieve 200000+ insertions/sec.

Googled and found that actually many people are having the same problem. The article SQL Server Slow Performance on Insert compiles a few resources on the topic of slow insertion in SQL Server.

Anyway I was patient enough to wait the 1-million records being inserted. Then I did the rest of the test with following results (local test):
Insert: 900/Second
Update: 1500/Second
Search on clustered-indexed column: 9000/Second
Search on non-clusterd indexed column: 2000/Second
Search on Non-indexed column: 27/Second
Besides the non-indexed column search (table scan), only the search on clustered-indexed column in SQL Server is comparable to MongoDB's.

Sunday, May 02, 2010

Test MongoDB In Windows Environment

NoSQL movement becomes more and more popular. Nowadays most NoSQL product environments are Unix-based. MongoDB is one of few solutions that provide Windows installation and .NET provider. This exercise evalutes MongoDB's performance in Windows platform.

The test machines' configuration:
Local tests and remote tests are both examined in this exercise. Local tests are conducted inside one physical box. MongoDB server and client process are running in separated machines in remote tests, and connection between server and client machines are 100 Mbit/Sec.

A simple document with key (unique-indexed) value (non-indexed) pair is used for testing. All fields (including internal _id field) will store GUID value:
{
_id: "12345678-1234-1234-1234-123456789012", // Internal field
key: "12345678-1234-1234-1234-123456789012", // Unique-indexed
value: "12345678-1234-1234-1234-123456789012" // Non-indexed
}
Manually handling "_id" field without using Oid format is not recommended. The reason of doing that in our tests is efficiently do update and delete operations without extra search.

Local tests are conducted inside one physical machine. MongoDB server and client process are running in separated machines in remote tests, and connection between server and client machines are 100Mbit/Sec.

Local test results:


(All in document actions per second, 1M=1000000)




























































Document number
1M 2M 4M 8M 10M
Insert 32210 29540 27160 22100 20530
Update 18560 18160 18010 17900 17540
Delete 12660 12510 11980 11610 11550
Search on _id 13340 13330 13350 12990 12130
Search on key 9190 9280 9300 9110 9010
Search on value 7.2 3.6 1.8 0.9 0.7


Remote test results:





























































Document number 1M 2M 4M 8M 10M
Insert 31450 30700

22420 18810 17600

Update 18180 18160 17400 18100 17830
Delete 12550 12270 11730 11680 11530
Search on _id 2410

2440

2410

2420

2420

Search on key 1470

1470

1450

1450

1440

Search on value 7.2 3.6 1.8 0.9 0.7


Findings:
  1. Fastest operation is data insert.
  2. Search on _id is faster than search on other indexed field.
  3. Search on indexed field seems in constant time, not proportional to size of the data set.
  4. Search on non-indexed field shows very poor performance, and the time spent is proportional to the size the data set. This is similar to a table scan scenario in a relational database.
  5. There's no big gap in insert/update/delete between local test and remote test.
  6. Remote search is significantly slower than local search.
By digging into source code of MongDB-CSharp driver, I found that insert, update and delete functions in the client side are implemented by one-way IO writing without any acknowledgement, i.e. keep sending data to TCP NetworkStream. Streamed data can be efficiently buffered in both client and server side. From client point of view it's asynchronous process. That explains why there's no big difference between remote and local tests in batch insert/update/delete scenarios. On the other hand, read or search (find related functions) requires two-way communication. The client sends a request to the server then waits the response to be completed. The server process time plus the round-trip network delay are all counted for one request action. That's why remote search are significantly slower the tests conducted in local box.

I thought the relatively low speed of indexed search in our test results could be caused by my single-threaded test application. So I modified the code and did the tests with 2, 4, 6, 8 and 10 threads. But surprisingly the performance did not improve at all.

The test code is listed below. It is simple and may be not accurate enough. But it gave me an idea how it performs in general with Windows environment.

Test code:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Threading;
using MongoDB.Driver;

public class Program
{
struct TimeUnit
{
public int NumberOfRecords;
public long MilliSeconds;
public TimeUnit(int num, long ms)
{
NumberOfRecords = num;
MilliSeconds = ms;
}
}
const int MaxDocuments = 1000000;
const int NonIndexSearchCount = 1000;
const int ThreadCount = 1;
const string DBConnectionString = "Server=127.0.0.1";
const string DBName = "TestMongoDB";
const string DBCollectionName = "TestCollection";
static string[] testContent = new string[MaxDocuments];
static object lockObject = new object();
static Dictionary<string, List<TimeUnit>> resultTimes = new Dictionary<string, List<TimeUnit>>();

static void Main(string[] args)
{
// Initialize data
for (int i = 0; i < MaxDocuments; i++)
testContent[i] = Guid.NewGuid().ToString();

// Start Threads
List<Thread> threads = new List<Thread>();
for (int i = 0; i < ThreadCount; i++)
{
int max = MaxDocuments / ThreadCount;
int insertStart = i * max;
int searchStart = insertStart + max / 2;
int searchEnd = searchStart + max / 10;
Thread thread = new Thread(() => TestMongoDB(insertStart, searchStart, searchEnd, max));
thread.Start();
threads.Add(thread);
//ThreadPool.QueueUserWorkItem(o => TestMongoDB(insertStart, searchStart, searchEnd, max));
}
Console.WriteLine();
// Wait threads to be completed
foreach (Thread thread in threads)
thread.Join();

// Display results
Console.WriteLine("Results:");
foreach (var key in resultTimes.Keys)
{
List<TimeUnit> times = resultTimes[key];
Console.Write(key + ":");
long totalTime = 0, totalNumber = 0;
foreach (TimeUnit st in times)
{
totalTime += st.MilliSeconds;
totalNumber += st.NumberOfRecords;
Console.Write(st.MilliSeconds + " ");
}
Console.WriteLine(string.Format("{0} speed is : {1}/sec", key, totalNumber * 1000.0 / totalTime));
}
Console.WriteLine("Done");
Console.Read();
}

/// <summary>
/// MongoDB test. Document format:
/// {
/// _id, // internal field
/// key, // indexed field
/// value // non-indexed field
/// }
/// </summary>
static void TestMongoDB(int insertStart, int searchStart, int searchEnd, int max)
{
// Init MongoDB
Mongo mongo = new Mongo(DBConnectionString);
IMongoDatabase db;
IMongoCollection documents;
Document doc = new Document();
Stopwatch watch = new Stopwatch();

mongo.Connect();
db = mongo[DBName];
documents = db.GetCollection(DBCollectionName);

// Setup Index on key field
lock (lockObject)
{
if (!documents.MetaData.Indexes.ContainsKey("key"))
{
Document indexDoc = new Document { { "key", 1 } };
documents.MetaData.CreateIndex(indexDoc, true);
}
}

// Insert data
watch.Start();
for (int i = insertStart; i < insertStart + max; i++)
{
Document newdoc = new Document();
newdoc["_id"] = newdoc["key"] = newdoc["value"] = testContent[i];
documents.Insert(newdoc);
}
watch.Stop();
AddTimeUnit("insert", new TimeUnit(max, watch.ElapsedMilliseconds));
Console.WriteLine(string.Format("MongoDB insert {0} records: {1} ms",
max, watch.ElapsedMilliseconds.ToString()));
watch.Reset();

// Search on id field
watch.Start();
for (int i = searchStart; i < searchEnd; i++)
{
doc["_id"] = testContent[i];
var doc1 = documents.FindOne(doc);
string value = doc1["value"].ToString();
}
watch.Stop();
AddTimeUnit("search-id", new TimeUnit(searchEnd - searchStart, watch.ElapsedMilliseconds));
Console.WriteLine(string.Format("MongoDB {0} search on id field over {1} records: {2} ms",
searchEnd - searchStart, documents.Count(), watch.ElapsedMilliseconds));
watch.Reset();
doc = new Document();

// Search on indexed "key" field
watch.Start();
for (int i = searchStart; i < searchEnd; i++)
{
doc["key"] = testContent[i];
var doc1 = documents.FindOne(doc);
string value = doc1["value"].ToString();
}
watch.Stop();
AddTimeUnit("search-key", new TimeUnit(searchEnd - searchStart, watch.ElapsedMilliseconds));
Console.WriteLine(string.Format("MongoDB {0} search on indexed field over {1} records: {2} ms",
searchEnd - searchStart, documents.Count(), watch.ElapsedMilliseconds));
watch.Reset();
doc = new Document();

// Search on non-indexed "value" field
watch.Start();
for (int i = searchStart; i < searchStart + NonIndexSearchCount; i++)
{
doc["value"] = testContent[i];
var doc1 = documents.FindOne(doc);
string value = doc1["value"].ToString();
}
watch.Stop();
AddTimeUnit("search-value", new TimeUnit(NonIndexSearchCount, watch.ElapsedMilliseconds));
Console.WriteLine(string.Format("MongoDB {0} search on non-indexed field over {1} records: {2} ms",
NonIndexSearchCount, documents.Count(), watch.ElapsedMilliseconds));
watch.Reset();
doc = new Document();

// Update test
watch.Start();
for (int i = searchStart; i < searchEnd; i++)
{
doc["_id"] = testContent[i];
//var doc1 = documents.FindOne(doc);
doc["key"] = testContent[i];
doc["value"] = i.ToString();
documents.Update(doc);
}
watch.Stop();
AddTimeUnit("update", new TimeUnit(searchEnd - searchStart, watch.ElapsedMilliseconds));
Console.WriteLine(string.Format("MongoDB {0} update over {1} records: {2} ms",
searchEnd - searchStart, documents.Count(), watch.ElapsedMilliseconds));
watch.Reset();
doc = new Document();

// Delete test
watch.Start();
for (int i = searchStart; i < searchEnd; i++)
{
doc["_id"] = testContent[i];
//var doc1 = documents.FindOne(doc);
documents.Delete(doc);
}
watch.Stop();
AddTimeUnit("delete", new TimeUnit(searchEnd - searchStart, watch.ElapsedMilliseconds));
Console.WriteLine(string.Format("MongoDB {0} delete over {1} records: {2} ms",
searchEnd - searchStart, documents.Count(), watch.ElapsedMilliseconds));
}

private static void AddTimeUnit(string key, TimeUnit st)
{
lock (lockObject)
{
if (resultTimes.ContainsKey(key))
{
List<TimeUnit> times = resultTimes[key];
times.Add(st);
}
else
{
List<TimeUnit> times = new List<TimeUnit>();
times.Add(st);
resultTimes.Add(key, times);
}
}
}
}