Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB C# Driver: API vs Linq Performance

I am trying to make a MongoDB web application with the IRepository pattern (C#, MVC5) to make it easier to unit test. Just wondering if someone can give me information on why this is faster.

This is using the newest MongoDB c# drivers.

In my IRepository class I have the following

IQueryable<T> SearchFor();

List<T> SearchFor(FilterDefinition<T> filter);

Found a SO post which suggested using IQueryable for speed over using IEnumerable.

Here is the code from the MongoRepository class.

public IQueryable<T> SearchFor() {
    return _collection.AsQueryable<T>();
}

public List<T> SearchFor(FilterDefinition<T> filter) {
    return _collection.Find(filter).ToList();
}

As far as I can tell the Filter definition is how you would usually code a query to the DB.

Here are the calls to get data from the DB

IQueryable<Client> asd4 = collection.SearchFor().Where(x => x.ClientDesc == "<search text>");

FilterDefinition<Client> filter1 = Builders<Client>.Filter.Eq("ClientDesc", "<search text>");
List<Client> asd10 = collection.SearchFor(filter1).ToList<Client>();

Please note that I know I should probably use the IQueryable and Linq route just for the pure fact that IRepository should not contain technology dependant classess (like FilterDefinition).

When tested against a collection with 30k simple documents and testing the speed of the different methods I get the following results.

Using IQueryable completes in 3ms and FilterDefinition completes in 43ms.

I am wondering why a Linq query on IQueryable is faster than using the API to send a request just to return the specific value?


UPDATE: From the suggestion by @lenkan I have added a for each loop for the IQueryable.

public void PerformanceTest(IRepository<Client> collection) {
    Stopwatch sw = new Stopwatch();

    // Delete all records
    // ******************
    System.Diagnostics.Debug.WriteLine("*****************");

    sw.Start();
    collection.DeleteAll();
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Deleting all records: " + sw.Elapsed);


    // Create 30k Records
    // ******************
    System.Diagnostics.Debug.WriteLine("*****************");

    sw.Reset();
    sw.Start();
    // Create 30k records
    for (int i = 0; i < 30000; i++) {
        Client testclient = new Client() {
            ClientDesc = "hahahahahahahahah " + i
        };
        collection.Add(testclient);
    }
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Created: 30k rows: " + sw.Elapsed);


    // Test IQueryable & LINQ
    // **********************
    System.Diagnostics.Debug.WriteLine("*********************");
    System.Diagnostics.Debug.WriteLine("* IQueryable & LINQ *");
    System.Diagnostics.Debug.WriteLine("*********************");

    sw.Reset();
    sw.Start();
    IQueryable<Client> asd4 = collection.SearchFor().Where(x => x.ClientDesc == "hahahahahahahahah 10");
    foreach (Client item in asd4) {
        string aaaaaa = item.ClientDesc;
    }
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Find one from start: " + sw.Elapsed);

    sw.Reset();
    sw.Start();
    IQueryable<Client> asd7 = collection.SearchFor().Where(x => x.ClientDesc == "hahahahahahahahah 10");
    foreach (Client item in asd7) {
        string aaaaaa = item.ClientDesc;
    }
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Find one from start: " + sw.Elapsed);

    sw.Reset();
    sw.Start();
    IQueryable<Client> asd5 = collection.SearchFor().Where(x => x.ClientDesc == "hahahahahahahahah 29999");
    foreach (Client item in asd5) {
        string bbbbbb = item.ClientDesc;
    }
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Find one from end: " + sw.Elapsed);

    sw.Reset();
    sw.Start();
    for (int i = 10000; i < 10050; i++) {
        IQueryable<Client> asd6 = collection.SearchFor().Where(x => x.ClientDesc == "hahahahahahahahah " + i);
        foreach (Client item in asd6) {
            string aaaaaa = item.ClientDesc;
        }
    }
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Find in loop of 50: " + sw.Elapsed);


    // Test Filter & LINQ
    // ***********************
    System.Diagnostics.Debug.WriteLine("*****************");
    System.Diagnostics.Debug.WriteLine("* List & Filter *");
    System.Diagnostics.Debug.WriteLine("*****************");

    sw.Reset();
    sw.Start();
    FilterDefinition<Client> filter1 = Builders<Client>.Filter.Eq("ClientDesc", "hahahahahahahahah 10");
    List<Client> asd10 = collection.SearchFor(filter1).ToList<Client>();
    foreach (Client item in asd10) {
        string aaaaaa = item.ClientDesc;
    }
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Find one from start: " + sw.Elapsed);

    sw.Reset();
    sw.Start();
    FilterDefinition<Client> filter2 = Builders<Client>.Filter.Eq("ClientDesc", "hahahahahahahahah 29999");
    List<Client> asd11 = collection.SearchFor(filter2).ToList<Client>();
    foreach (Client item in asd11) {
        string cccccc = item.ClientDesc;
    }
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Find one from end: " + sw.Elapsed);

    sw.Reset();
    sw.Start();
    for (int i = 10000; i < 10050; i++) {
        FilterDefinition<Client> filter3 = Builders<Client>.Filter.Eq("ClientDesc", "hahahahahahahahah " + i);
        List<Client> asd12 = collection.SearchFor(filter3).ToList<Client>();
    }
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Find in loop of 50: " + sw.Elapsed);

    // Delete all records
    // ******************
    System.Diagnostics.Debug.WriteLine("*****************");

    sw.Start();
    collection.DeleteAll();
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Deleting all records: " + sw.Elapsed);

}

Here are the results now. So it seems enumerating with IQueryable has an initial hit in performance but then things seem to speed up when you call later searches i.e.

*****************
Deleting all records: 00:00:00.0670336
*****************
Created: 30k rows: 00:00:04.6829844
*********************
* IQueryable & LINQ *
*********************
Find one from start: 00:00:00.0878309
Find one from start: 00:00:00.0120098
Find one from end: 00:00:00.0116334
Find in loop of 50: 00:00:00.5890532
*****************
* List & Filter *
*****************
Find one from start: 00:00:00.0248407
Find one from end: 00:00:00.0118345
Find in loop of 50: 00:00:00.5377828
*****************
Deleting all records: 00:00:00.7029368
like image 947
Dwiea Avatar asked Dec 11 '22 19:12

Dwiea


1 Answers

Your initial question was why LINQ was so much faster than using the API. The answer to that question is because LINQ is deferred (lazy) execution and the query wasn't actually done. The query won't be done until you actually try and iterate the result (foreach/.ToList()/etc).

You likely timed this statement:

IQueryable<Client> asd4 = collection.SearchFor().Where(x => x.ClientDesc == "<search text>");

when you should have timed this statement:

List<Client> asd4 = collection.SearchFor().Where(x => x.ClientDesc == "<search text>").ToList();

The performance numbers you show during your update seem reasonable. LINQ is actually slightly slower than using the direct API, as it adds abstraction to the query. This abstraction would allow you to easily change MongoDB for another data source (SQL Server/Oracle/MySQL/XML/etc) without many code changes, but you pay for that abstraction in a slight performance hit.

like image 60
Robert McKee Avatar answered Jan 30 '23 05:01

Robert McKee