<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>C# | Jacob Aloysious</title><link>https://jacobaloysious.in/tag/c/</link><atom:link href="https://jacobaloysious.in/tag/c/index.xml" rel="self" type="application/rss+xml"/><description>C#</description><generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>en-us</language><lastBuildDate>Sun, 30 May 2021 00:00:00 +0000</lastBuildDate><image><url>https://jacobaloysious.in/images/icon_hu4591c05f594249c11c1e99a3a8f1f246_3759739_512x512_fill_lanczos_center_2.png</url><title>C#</title><link>https://jacobaloysious.in/tag/c/</link></image><item><title>Concurrent Dictionary and Delay - C#</title><link>https://jacobaloysious.in/post/tech_concruent_dictionary/</link><pubDate>Sun, 30 May 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_concruent_dictionary/</guid><description>&lt;p>Reference:
&lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2?view=net-5.0" target="_blank" rel="noopener">ConcurrentDictionary&lt;/a>&lt;/p>
&lt;p>In a high throuput and low latency enviorment - we had a peice of code in the critical path - which helped in throttling. After version #2.0 of the software was released (internally) - we saw the througput has gone down significantly almost 3X - compared to previous Version #1.0 - 4mins to 11 minutes.&lt;/p>
&lt;p>We looked at the history/change sets between the versions: it was huge, 100s of commits and software was running with 100s of threads - on a 2Milliion LOC software stack. So, we were unsure which peice of code nor thread - was causing the delay. We were all over the place, trying to see if its a system issue or memory trend or some fragmentation vs memory/back pressure vs excess logging etc&amp;hellip;. And, after 3 day (yes: 3days slog) - we found its a delay caused by the &lt;strong>ConcurrentDictionary&lt;/strong> class!!! 😳&lt;/p>
&lt;p>It was an unpleasant suprise, since we didn&amp;rsquo;t expect such a huge delay cause due to a &lt;em>.Net Framework class&lt;/em>.&lt;/p>
&lt;h4 id="1-version-10--4mins">1. Version 1.0: [4mins]&lt;/h4>
&lt;p>One API with its backing data store with one Dictionary.&lt;/p>
&lt;pre>&lt;code> private Dictionary&amp;lt;int, User&amp;gt; userInfos;
public List&amp;lt;User&amp;gt; GetUsers() {
return userInfos.Values;
}
&lt;/code>&lt;/pre>
&lt;h4 id="2-version-20--11-minutes">2. Version 2.0: [11 minutes]&lt;/h4>
&lt;p>In version #2: We changed the data store, which was holding the user info from a simple dictionary to a two level &lt;em>ConcurrentDictionay&lt;/em>.&lt;/p>
&lt;p>There was some obvious reason for using &lt;em>ConcurrentDictionay&lt;/em> - I won&amp;rsquo;t go into the details. But, after changing the data store- we ended up blindly updating all usages to consume from the new data store.&lt;/p>
&lt;p>And the API (GetUsers) implementation was updated, interface remained the same - but: it was in the critical path.&lt;/p>
&lt;pre>&lt;code> private ConcurrentDictionary&amp;lt;int, ConcurrentDictionary&amp;lt;int, User&amp;gt;&amp;gt; userInfos;
public List&amp;lt;User&amp;gt; GetUsers(){
var users = new List&amp;lt;User&amp;gt;();
for(var userGroup in userInfos.Values){
for(var user in userGroup.Values){
users.add(user)
}
return users;
}
}
&lt;/code>&lt;/pre>
&lt;h4 id="3-version-30--4-minutes">3. Version 3.0: [4 minutes]&lt;/h4>
&lt;p>After looking into the usages &lt;strong>&amp;ldquo;in the critical path&amp;rdquo;&lt;/strong> - we only required the count of the active user groups.&lt;/p>
&lt;p>So, we wrote another API to just return the count. With this change, we were able to bring it back the throughput back to #Version 1.0. Huff!! 😌&lt;/p>
&lt;pre>&lt;code> private ConcurrentDictionary&amp;lt;int, ConcurrentDictionary&amp;lt;int, User&amp;gt;&amp;gt; userInfos;
public List&amp;lt;User&amp;gt; GetActiveUserGroups(){
return userInfos.Count;
}
&lt;/code>&lt;/pre>
&lt;h3 id="learning">Learning:&lt;/h3>
&lt;p>On highsight, the fix looks trivial and straight forward. But the challenge was the huge debugging effort to find the root cause. And little did we suspect a .Net Framework class can cause such a huge delay and slow down overall execution time. It was a great learning for us as a team - involved in debugging. BTW, we are talking about a code base of about few Million lines of code.&lt;/p>
&lt;p>BTW: Concurrent dictionary exists for a reason: Usage of it as our data store was the right choice.&lt;/p>
&lt;blockquote>
&lt;p>Represents a thread-safe collection of key/value pairs that can be accessed by multiple threads concurrently.&lt;/p>
&lt;/blockquote>
&lt;p>If a class should be thread safe, then there has to be a lock/Mutex implemented. A lock has its own tradeoff in terms of execution time (Thread Scheduling, Prempt etc..) - so think carefully on its usages.&lt;/p></description></item><item><title>MongoDB - Hybrid Schema</title><link>https://jacobaloysious.in/post/tech_mogodb-hybridschemadesign/</link><pubDate>Sun, 14 Mar 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_mogodb-hybridschemadesign/</guid><description>&lt;p>I recently ran into a situation where in we have a session and each Session produces a lot of messages (sync) and each message has an associated timestamp and order.
This is similar to a blog post (session) and its comments (messages).&lt;/p>
&lt;p>Since the messages are unbounded (not sure how many would be produced). In my initial design - I had a separate collection for Messages. Each Session would have one corresponding MessageW (wrapper) document and each MesssageW document would have an array of messages (acutal messages).&lt;/p>
&lt;p>With all the available data I had - the size of the MessageW document never went above 1MB - so we were all good. But unfortunately, we had a new use case which broke this assumption 😟. The session was running for hours and there was tons of messages getting generated and yes - we hit the 16MB ceiling 😓.&lt;/p>
&lt;p>Obvious solution was to create to create a new one doucment for every new message. So, that we never hit the size limit (16MB) of a mongoDB document. But, since we are looking at unbounded message - there could huge (million message docs) number of documents.&lt;/p>
&lt;p>So, we choose to go with the &lt;strong>Hybrid Approach&lt;/strong>&lt;/p>
&lt;p>In the hybrid approach, we go with our initial option - where we have a MessageW (wrapper) document - which has an array of Messages. Then we add a constrain on the document i.e. the size of the array should not be more than a given arbitary number. For every new message we increment the &lt;em>count&lt;/em>. If the number count crossed the constraint - then create a new MessageW document, inc the page number and also update the Session (&lt;em>NumberOfMsgPages&lt;/em>). The reader would get the &lt;em>NumberOfMsgPages&lt;/em> and read backwards - until he hit page 0. Voila!! 😍&lt;/p>
&lt;p>&lt;strong>Important Items to note:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Session Object : will have a &lt;em>NumberOfMsgPages&lt;/em> property&lt;/li>
&lt;li>MessageW Object: would have a &lt;em>Page&lt;/em> and &lt;em>Count&lt;/em> property.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Operators:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>
&lt;a href="https://docs.mongodb.com/manual/reference/operator/update/inc/" target="_blank" rel="noopener">$inc&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://docs.mongodb.com/manual/reference/operator/update/set/" target="_blank" rel="noopener">$set&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://docs.mongodb.com/manual/reference/operator/query/lt/" target="_blank" rel="noopener">$lt&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://docs.mongodb.com/manual/reference/method/db.collection.update/#update-upsert" target="_blank" rel="noopener">upsert&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Since: A code is worth a thousand words. Here is the code 😊&lt;/p>
&lt;pre>&lt;code> public void InsertMessage(Message message, ObjectId parentId)
{
var builder = Builders&amp;lt;Message&amp;gt;.Filter;
// Find MessageObject: given parentId and CurrentPageNo
// And: Constraint: Number of messages is less than 1000.
var filter = builder.Eq(&amp;quot;parentId&amp;quot;, parentId)
&amp;amp; builder.Lt(&amp;quot;Count&amp;quot;, 1000)
&amp;amp; builder.Eq(&amp;quot;Page&amp;quot;, CurrentMessagePageCount);
// Try and Insert into existing Message Document array And Increment the Count.
var update = Builders&amp;lt;Message&amp;gt;.Update
.AddToSet(x =&amp;gt; x.Messages, message)
.Inc(&amp;quot;Count&amp;quot;, 1);
// If the constraint with &amp;quot;Count&amp;quot; matches.
// ModifiedCount would be greater than Zero
var result = MsgCollection.UpdateOne(filter, update);
if (result.IsAcknowledged)
{
if(result.ModifiedCount== 0)
{
// Increment the Page count
CurrentMessagePageCount += 1;
// Update: NumberOfMsgPages in Session Collection
var sessionFilter = Builders&amp;lt;MySession&amp;gt;.Filter.Eq(&amp;quot;_id&amp;quot;, parentId);
var updateSession = Builders&amp;lt;MySession&amp;gt;.Update
.Inc(&amp;quot;NumberOfMsgPages&amp;quot;, 1);
SessionsCollection.UpdateOne(sessionFilter, updateSession);
// Find MessageObject: given parentId and CurrentPageNo-which is obviously not found.
// So, we will use &amp;quot;IsUpsert=true&amp;quot; to add a new document.
// And: Number of messages is less than 20.
var filter2 = builder.Eq(&amp;quot;parentId&amp;quot;, parentId)
&amp;amp; builder.Eq(&amp;quot;Page&amp;quot;, CurrentMessagePageCount);
// Try: Insert into existing Message And Increment the Count
var update2 = Builders&amp;lt;Message&amp;gt;.Update
.AddToSet(x =&amp;gt; x.Messages, message)
.Set(&amp;quot;Page&amp;quot;, CurrentMessagePageCount)
.Inc(&amp;quot;Count&amp;quot;, 1);
MsgCollection.UpdateOne(filter2, update2, new UpdateOptions() { IsUpsert = true });
}
}
}
&lt;/code>&lt;/pre>
&lt;p>A obvious reaction after reading the code is - Ok, whats the big deal here?&lt;/p>
&lt;p>Lets take a code walk through: (think how would you achive this in your traditional SQL DB&amp;rsquo;s)&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Code never check if the MessageW document &lt;strong>exits&lt;/strong> before Update.&lt;/p>
&lt;ul>
&lt;li>How did it work: &lt;strong>&lt;em>&amp;ldquo;$IsUpsert=true&amp;rdquo;&lt;/em>&lt;/strong> is the magic word.&lt;/li>
&lt;li>&lt;em>$IsUpsert=True&lt;/em>: MongoDB would internally insert a &lt;strong>new&lt;/strong> document - if the document doesn&amp;rsquo;t exists.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Updating &lt;strong>counter&lt;/strong> is happening inside MongoDB:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;em>$inc&lt;/em>&lt;/strong> mongoDB operator - increments a specific field - in our case &lt;em>count&lt;/em>&lt;/li>
&lt;li>Hence, Client doesn&amp;rsquo;t have to read the document to know the actual count and then increment/update.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Constraint Check:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;em>$lt&lt;/em>&lt;/strong> MongoDB operator would handle the constraints.&lt;/li>
&lt;li>Update goes through if the constraint is met.&lt;/li>
&lt;li>As part of the result, we could check if any doc was modified&lt;/li>
&lt;li>Cool part - the second half of the code runs only once in 1000 inserts&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>How did we come up with the arbitary constraint number? well, I dont think we have a thumb rule here. Since we don&amp;rsquo;t track the actual document size - you would have to look at your data and come up with the sizing.&lt;/p>
&lt;p>BTW, I am new to MongoDB and learning. If there are better approaches - feel free to write back to me. Thanks.&lt;/p>
&lt;p>Reference:
&lt;a href="https://www.oreilly.com/library/view/mongodb-applied-design/9781449340056/" target="_blank" rel="noopener">MongoDB Applied Design Patterns, by Rick Copeland&lt;/a>&lt;/p></description></item></channel></rss>