<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Aniruddha on Medium]]></title>
        <description><![CDATA[Stories by Aniruddha on Medium]]></description>
        <link>https://medium.com/@i0exception?source=rss-4bfcc415995f------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*g8aONIC21Hcx7QbV.jpg</url>
            <title>Stories by Aniruddha on Medium</title>
            <link>https://medium.com/@i0exception?source=rss-4bfcc415995f------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 06 May 2026 15:34:38 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@i0exception/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Rendezvous Hashing]]></title>
            <link>https://medium.com/i0exception/rendezvous-hashing-8c00e2fb58b0?source=rss-4bfcc415995f------2</link>
            <guid isPermaLink="false">https://medium.com/p/8c00e2fb58b0</guid>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[consistent-hashing]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[data-structures]]></category>
            <category><![CDATA[kafka]]></category>
            <dc:creator><![CDATA[Aniruddha]]></dc:creator>
            <pubDate>Tue, 07 Jan 2020 07:45:11 GMT</pubDate>
            <atom:updated>2020-01-08T04:58:09.130Z</atom:updated>
            <content:encoded><![CDATA[<h3>Rendezvous Hashing: an alternative to Consistent Hashing</h3><p>In any kind of stateful distributed system, the problem of mapping a key to a set of machines is pretty common. Even if a distributed system is stateless, you might still want to map a key to the same set of machines for better locality of processing. In its essence, this is very similar to how hash tables work — map a set of <em>k </em>keys to <em>n </em>buckets.</p><p>The simplest way to do this is to use modular operations. <a href="https://en.wikipedia.org/wiki/Hash_function">Hash</a> your key to get a fixed length value, then compute the modulo with <em>n </em>and pick the machine in that slot. For a uniform hash function, this works well if the number of endpoints doesn’t change very frequently and if the cost of re-mapping keys between endpoints is low. If either of those two is not true, this performs very poorly because all of your keys could get remapped if the size of the list changes.</p><p>These days, the standard way to limit the number of keys being re-mapped is to use <a href="https://en.wikipedia.org/wiki/Consistent_hashing">consistent hashing</a>. Most major distributed databases use it in some form or another. Consistent hashing is a special kind of hashing where on an average, <em>K/n </em>keys are remapped whenever the list of endpoints changes (<em>K </em>is the total number of keys). The term <em>consistent hashing</em> first appeared in literature in 1997 in <a href="https://dl.acm.org/doi/10.1145/258533.258660">this paper</a>. In consistent hashing, both the keys and the buckets are hashed onto a circle. A key maps to the first bucket that is encountered in the clockwise direction (or counter-clockwise — it doesn’t really matter). Searching for the bucket responsible for a key is pretty simple — pre compute the hash values for all buckets and sort them, hash the key and then run a binary search (in<em> O(log(n))</em>) to find the lowest value that’s higher than the hash of the key. When the buckets are resized, some keys move over to the closest new bucket. On average, the number of keys that need to move is <em>K/n — </em>which is ideal.</p><p>One of the biggest drawbacks of consistent hashing is that keys can be imbalanced across buckets. This is mainly because of how resizing is handled. For example, if a bucket is removed, all keys mapped to that bucket move over to the next one (similar for the case where a bucket is added). Ideally, these keys would be distributed equally across all the remaining buckets. To overcome this problem, most implementations divide each physical machine into multiple virtual nodes. Even then, the keys now spread out over as many virtual nodes you assign to a physical machine instead of the ideal state of the load spreading out over all of them. If the number of virtual nodes is not higher than the number of machines, the load can be distributed unevenly.</p><p>Rendezvous hashing predates consistent hashing <a href="http://www.eecs.umich.edu/techreports/cse/96/CSE-TR-316-96.pdf">by a year</a> and takes a very different approach to solving these problems, while maintaining the <em>K/n</em> re-mapping invariant. Unfortunately, it’s not as well known as consistent hashing. It’s also known as <em>Highest Random Weight</em> hashing, because of how it’s implemented. Conceptually and practically, it’s much simpler to understand and implement. You hash the <em>key </em>and the <em>machine </em>together and then pick the one with the highest hash value.</p><pre>type router struct {<br>  endpoints []*Endpoint<br>}</pre><pre>func (r *router) Get(key string) *Endpoint {<br>  var ep *Endpoint<br>  hashVal := -INF</pre><pre>  for _, e := range r.endpoints {<br>    h = hash(key, e)<br>    if h &gt; hashVal {<br>      ep = e<br>      hashVal = h<br>    }<br>  }<br>  return ep<br>}</pre><p>In case of a uniform hash function, if the buckets change, the keys (on an average, <em>K/n</em> keys) get spread out over all other buckets instead of just one or the number of virtual nodes that were assigned to a machine. The biggest drawback of rendezvous hashing is that it runs in <em>O(n) </em>instead of <em>O(log(n))</em>. However, because you don’t typically have to break each node into multiple virtual nodes, <em>n </em>is typically not large enough for the run-time to be a significant factor.</p><p>We actually used this at Twitter in our internal pub/sub platform, EventBus. EventBus was modeled similar to Kafka — there were topics, and topics had subscriptions. A group of clients together consumed a subscription. We called this smallest unit, a stream. Unlike Kafka, EventBus had separate storage and serving layers — so you could scale out the serving layer horizontally. More importantly, any machine could serve a stream. Also, unlike Kafka, we supported a mode where all clients within a subscription could choose to receive a full copy of the stream and implement their own filtering.</p><p>Initially, we randomly assigned these streams to different serving machines. This worked fine when the number of streams was in the low hundreds. However, over time, some of our most popular topics (like the one with tweets) gathered streams numbering in the tens of thousands, many with client-side streaming enabled. Because the serving layer kept a local cache of events for each stream and different streams could be reading data at different offsets, every machine started keeping a large amount of data in memory — leading to horrendous GC pressure. We needed an easy way for a group of clients to independently converge on the same serving machine for a particular stream so that an item, once cached, could be sent to multiple clients. We used rendezvous hashing to do this with pretty good results. The clients would select a machine while starting consumption and then periodically rebalance every 5–10 minutes till the throughput across different machines stabilized.</p><p>Sometimes, elegant and obscure algorithms tend to outperform conventional wisdom.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8c00e2fb58b0" width="1" height="1" alt=""><hr><p><a href="https://medium.com/i0exception/rendezvous-hashing-8c00e2fb58b0">Rendezvous Hashing</a> was originally published in <a href="https://medium.com/i0exception">i0exception</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Sampling — the good, the bad, and the ugly]]></title>
            <link>https://medium.com/i0exception/sampling-the-good-the-bad-and-the-ugly-4b5f85e8ce2?source=rss-4bfcc415995f------2</link>
            <guid isPermaLink="false">https://medium.com/p/4b5f85e8ce2</guid>
            <category><![CDATA[mixpanel]]></category>
            <category><![CDATA[analytics]]></category>
            <category><![CDATA[data]]></category>
            <dc:creator><![CDATA[Aniruddha]]></dc:creator>
            <pubDate>Wed, 16 Oct 2019 08:25:17 GMT</pubDate>
            <atom:updated>2019-10-16T08:25:17.874Z</atom:updated>
            <content:encoded><![CDATA[<h3>Sampling — the good, the bad, and the ugly</h3><p>Benjamin Franklin once said — “Those who give up essential accuracy for temporary speed deserve neither speed nor accuracy”. In the real world, however, you frequently have to trade off one for the other. This tradeoff becomes increasingly appealing as your data volume increases. In this post we’ll discuss some common sampling techniques and ways to get the most out of your data, especially as it relates to <a href="https://mixpanel.com/behavioral-analytics/">product or behavioral user analytics</a>.</p><p>Before we look at how to sample, it’s important to understand what the data being sampled looks like. In most cases you’re going to collect <a href="https://support.google.com/analytics/answer/1033068?hl=en">events</a>. These are an immutable record of user interactions. Each event typically has a timestamp and a user identifier associated with it. Optionally, you might want to collect some <a href="https://help.mixpanel.com/hc/en-us/articles/115004708186-Event-Properties-Super-Properties-People-Properties">metadata</a> with each event. Typically, once you add instrumentation to your apps or websites (or use a tool that automatically collects everything), these events are generated in response to every user interaction. So, if you’re already generating these events — why sample?</p><p>There are two main reasons why you might want to consider looking at a smaller subset of your data for insights — <strong><em>speed </em></strong>and <strong><em>cost</em></strong>. In some cases, you can get faster results if you decide to spend more on computation. However, not all computations are infinitely parallelizable.</p><h3>How to sample</h3><p>Whether you decide to sample by dropping data during collection or at query time, how you choose to ignore data matters. It’s important to have the sampling be random — otherwise you’ll run into <a href="https://en.wikipedia.org/wiki/Sampling_bias">sampling bias</a>, which makes analysis hard. There are a few ways to do this.</p><h4>Sample every event</h4><p>This is the most naive way to sample data, but it works well if you only care about aggregates. Here, the decision to sample is independent of the event or user being tracked. When you run aggregates, you can multiply by the inverse of the sampling factor to get an approximate value. The biggest drawback of this approach is that any kind of analysis that depends on a sequence of events (like a funnel report) is usually incorrect.</p><h4>Sample high volume events</h4><p>Not all events are made equal. You are likely to have a few outliers that contribute the most to event volume. Random sampling for these outliers and collecting all other events un-sampled works well in practice. This has the same drawbacks as the previous approach, but the impact is restricted to analysis that spans the outliers.</p><h4>Sample all users</h4><p>For user analytics, every event is likely to have an associated user identifier. This represents the individual you are tracking information about. The goal of this approach is to keep all activity for a small sample of users and discard all activity for the others. If the user identifiers you keep data for are selected at random, you can extrapolate the results to get accurate numbers. The good part about this approach is that it works for aggregates as well as for any analysis that depends on a sequence of events, so long as it is per-user.</p><h4>Sample high volume events by user</h4><p>Here, we take the good parts of approaches 2 and 3 and combine them. We sample events by user but only restrict the sampling to high volume events. Any analysis that you do on events that don’t involve outliers gets full fidelity whereas anything that’s done across outliers still has accurate numbers as long as the analysis is done per-user. Most <a href="http://mixpanel.com">major analytics providers</a> let you do this.</p><h4>Sample by users but always track certain populations</h4><p>This is basically the same as the previous approach, except you have some way to always track a specific set of users based on some pre-defined criteria. Say, you want to track everything about users who pay you more than $1000 per month — you can do that by always including every event for any of these users. This works well if this sample is relatively small compared to the rest of your user base and you are careful about addressing the edge cases of tracking when someone enters or exits this special population.</p><h3>User Identity Management</h3><p>Before we look at where to sample, it’s important to look at one of the biggest challenges with user based sampling. In this day and age, people have multiple devices that they might interact with your application or website on. In addition, users might do the bulk of their activity while logged out and only identify themselves when they have to. This flexibility makes it hard to decide which user identifiers should be in the sample and which shouldn’t — mainly because it’s a chicken and egg problem.</p><h4>Anonymous and Logged In activity</h4><p>For most applications and websites, some kind of user identification is required to interact with the product. There are exceptions (as we’ll see later), but for a majority of products, Login acts as the great filter. Once a user logs in, the decision of whether to include it in the sample can mostly be made on the basis of whatever unique identifier your database has for that user. It’s also possible to tie back any of their anonymous activity to the identified user based either on heuristics or actual knowledge (if someone logs in on a device where you previously tracked anonymous activity, there is a good chance that the anonymous activity can be tied to the logged-in user).</p><p>However, there are entire verticals where a bulk of your users are going to be anonymous. Travel, e-commerce, search, video etc. see a lot of anonymous activity and users don’t necessarily identify themselves during an interaction with your product.</p><h4>Users with multiple accounts</h4><p>The other challenge that some products run into is where users have different identifiers on different platforms. You might use a phone number to identify on the app and an email address to identify on a website. Additionally, you might also allow your users to identify using social media accounts. Tying all these activities back to the same user typically requires some kind of heuristics or best-effort matching and it usually happens long after you’ve been tracking information with these different identifiers.</p><h3>Where to sample</h3><p>User identity management plays a big role in determining the utility of your approach to sampling, because once you decide <em>how</em> you want to sample your data, the other important decision you’ll have to make is <em>where</em> to do this.</p><p>Broadly speaking, you have 2 choices —</p><ol><li>collect everything and sample when you run queries.</li><li>drop data during collection and run queries on the sampled data.</li></ol><p>If you’re considering sampling as a way to reduce costs, it’s helpful to understand the 3 types of costs associated with data —</p><p><strong><em>Collection</em></strong> costs are those associated with tracking and processing the data all the way up to the point where you can decide whether to include the event in the sample or not.</p><p><strong><em>Storage</em></strong> costs are what you pay for keeping the data around at rest. These costs compound over time as the data footprint increases.</p><p><strong><em>Query</em></strong> costs are what you pay for processing the sampled data to get meaningful insights.</p><p>Here’s what the costs look like based on the approach you take</p><pre>+----------------------+-------------+----------+----------+<br>|        Option        |  Collection |  Storage |   Query  |<br>+----------------------+-------------+----------+----------+<br>| Sample at query      |  Full       |  Full    |  Sampled |<br>| Sample at collection |  Full       |  Sampled |  Sampled |<br>+----------------------+-------------+----------+----------+</pre><h4>Sample at query</h4><p>This is a little more expensive than the second one because you pay full storage costs and your collection costs <em>might</em> be a little higher depending on how early in your collection process you can determine whether a user falls in the sample or not. However, it has very few drawbacks because you don’t drop any of the data so you can merge user activity as and when you discover connections between anonymous, logged-in and users with multiple accounts. If you can afford to, collect everything.</p><h4>Sample at collection</h4><p>If you absolutely must drop data, there are a few ways to try to minimize the impact —</p><h4>Sampling Technique</h4><p>Always sample just the high volume events by the user identifier and keep everything around at full fidelity. This reduces the impact of sampling to any analysis that involves the outliers in terms of volume.</p><h4>Anonymous vs. Logged In users</h4><p>If your product has low anonymous activity and most users identify themselves before any interaction, you might be able to get by with keeping a copy of all the anonymous data and only sample data for users who have identified themselves. This gives you full visibility into any anonymous activity and at the same time, any analysis that spans anonymous and logged-in usage is correct.</p><p>If your product has high anonymous activity and low logged in activity, flip the two — sample all the anonymous data and keep all the logged in activity around for analysis. The drawback of doing this is that analysis spanning anonymous and logged-in usage will be incorrect.</p><p>Unfortunately, for most other cases, sampling at collection results in either incomplete or inaccurate data and there’s no real way to counter that.</p><h3>Conclusion</h3><p>Sampling plays an important role in improving the speed of analysis and, in some cases, reducing costs. If you can afford to pay the additional processing and storage costs, always sample while running queries. The costs for running queries are typically much higher than the other two, especially as you scan the data multiple times for different kinds of analysis. Keeping a full copy of all the data also lets you use that data for any kind of analysis that involves machine learning or statistical modeling. It also lets you run exploratory analysis on a sample while still retaining the ability to run more important queries on the full dataset.</p><p>If you absolutely can’t afford to keep a full copy, try to minimize the impact of sampling by reducing the scope of user identity management challenges on your choice of sampling technique and make sure that you factor in all the corner cases when interpreting the results of your queries.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4b5f85e8ce2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/i0exception/sampling-the-good-the-bad-and-the-ugly-4b5f85e8ce2">Sampling — the good, the bad, and the ugly</a> was originally published in <a href="https://medium.com/i0exception">i0exception</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Map iteration in Go]]></title>
            <link>https://medium.com/i0exception/map-iteration-in-go-275abb76f721?source=rss-4bfcc415995f------2</link>
            <guid isPermaLink="false">https://medium.com/p/275abb76f721</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[golang]]></category>
            <dc:creator><![CDATA[Aniruddha]]></dc:creator>
            <pubDate>Sat, 27 Jul 2019 04:29:45 GMT</pubDate>
            <atom:updated>2019-07-28T23:37:01.187Z</atom:updated>
            <content:encoded><![CDATA[<h3>Iterating over maps in Go</h3><p>While the <a href="https://golang.org/ref/spec">Go programming language specification</a> only states that the iteration order over maps is not guaranteed to be the same across invocations, <a href="https://blog.golang.org/go-maps-in-action">Go maps in action</a> goes a step further to say that the order is randomized. So, when someone at work asked how I would design a set of integers that returned a random entry on every get , I suggested this neat trick</p><pre>type intSet map[int]struct{}</pre><pre>func (s intSet) put(v int) {<br>        s[v] = struct{}{}<br>}</pre><pre>func (s intSet) get() (int, bool) {<br>        for k := range s {<br>                return k, true<br>        }<br>        return 0, false<br>}</pre><p>Turns out that this approach is incorrect because, while it returns a “random” number on every get, the probability for every element is not the same.</p><p>To test this implementation, let’s actually fill up a map with some values and see the distribution over a million runs.</p><pre>func main() {<br>        s := make(intSet)<br>        for i := 0; i &lt; 8; i++ {<br>                s.put(i)<br>        }</pre><pre>        counts := make(map[int]int)<br>        for i := 0; i &lt; 1024*1024; i++ {<br>                v, ok := s.get()<br>                if !ok {<br>                        return<br>                }<br>                counts[v]++<br>        }</pre><pre>        for k, v := range counts {<br>                fmt.Printf(&quot;Value: %v, Count: %v\n&quot;, k, v)<br>        }<br>}</pre><p>This is the output you get on running this</p><pre>code|⇒ ./code<br>Value: 1, Count: 131026<br>Value: 7, Count: 130957<br>Value: 3, Count: 131064<br>Value: 5, Count: 131288<br>Value: 2, Count: 131080<br>Value: 0, Count: 130813<br>Value: 4, Count: 131137<br>Value: 6, Count: 131211</pre><p>That’s good, right? The distribution of each number is roughly equal. Let’s change the numbers a bit and see what happens. For the next run, I added the numbers 0 to 4 instead.</p><pre>code|⇒ ./code<br>Value: 1, Count: 131175<br>Value: 2, Count: 131593<br>Value: 3, Count: 130904<br>Value: 0, Count: 654904</pre><p>While the counts for 1 , 2 and 3 are roughly the same, 0 occurs almost 5 times as often. A truly random distribution would have been around 250000 occurrences of each number.</p><p>To explain this anomaly, it’s important to understand how maps are implemented in go. Unsurprisingly, maps are implemented using go. The <a href="https://github.com/golang/go/blob/master/src/runtime/map.go">map.go</a> file in <a href="https://github.com/golang/go/tree/master/src/runtime">src/runtime</a> contains the common parts of the implementation (there are some optimized map implementations for common types like integers and strings). The comments in map.go help lay out the structure of a map</p><pre>// A map is just a hash table. The data is arranged<br>// into an array of buckets. Each bucket contains up to<br>// 8 key/value pairs. The low-order bits of the hash are<br>// used to select a bucket. Each bucket contains a few<br>// high-order bits of each hash to distinguish the entries<br>// within a single bucket.<br>//<br>// If more than 8 keys hash to a bucket, we chain on<br>// extra buckets.</pre><p>Let’s take a look at what happens when you’re iterating over a map. If you disassemble the for loop, you’ll see something like this.</p><pre>TEXT main.intSet.get(SB) /home/aniruddha/code/main.go<br>  ...<br>  main.go:10  0x488e56  4889442408   MOVQ AX, 0x8(SP)<br>  main.go:10  0x488e5b  488d442418   LEAQ 0x18(SP), AX<br>  main.go:10  0x488e60  4889442410   MOVQ AX, 0x10(SP)<br>  main.go:10  0x488e65  e8763df8ff   CALL runtime.mapiterinit(SB)<br>  main.go:10  0x488e6a  488b442418   MOVQ 0x18(SP), AX<br>  ...<br>  main.go:11  0x488e93  c3    RET</pre><p>The call to mapiterinit is what sets up the iterator and then calls the mapiternext function to get the first element in the map. Here’s the part of the code in mapiterinit that actually computes where to start iterating —</p><pre>r := uintptr(fastrand())<br>if h.B &gt; 31-bucketCntBits {<br>  r += uintptr(fastrand()) &lt;&lt; 31<br>}<br>it.startBucket = r &amp; bucketMask(h.B)<br>it.offset = uint8(r &gt;&gt; h.B &amp; (bucketCnt - 1))<br>it.bucket = it.startBucket</pre><p>We generate a random number using fastrand() and then use it to get the starting bucket and a random offset within that bucket (remember, maps in go are implemented as an array of buckets with 8 elements in each bucket). mapiternext then iterates over the elements to return the first valid entity — while doing so, it skips over any empty ones</p><pre>for ; i &lt; bucketCnt; i++ {<br>  offi := (i + it.offset) &amp; (bucketCnt - 1)<br>  if isEmpty(b.tophash[offi]) || b.tophash[offi] == evacuatedEmpty {<br>    // TODO: emptyRest is hard to use here, as we start iterating<br>    // in the middle of a bucket. It&#39;s feasible, just tricky.<br>        continue<br>  }<br>  ...<br>}</pre><p>Because the element we start with could be empty, the probability of getting a valid element is actually dependent on the number of empty buckets and elements immediately preceding it. For example, if there is 1 bucket with 2 valid entities like in the example below —</p><pre>[NULL, NULL, 10, NULL, NULL, NULL, NULL, 20]</pre><p>We’ll get 10 if we start with elements 0, 1 or 2 and 20 if we start with 3, 4, 5, 6 or 7. So the perceived probability of getting a 10 is 3/8 and for 20 is 5/8 .</p><p>While this was a toy problem that I was trying to solve, the broader learning for me was to not base solutions on ones interpretation of library documentation. It’s almost always a good idea to test how things behave in practice even if the documentation feels clear and correct.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=275abb76f721" width="1" height="1" alt=""><hr><p><a href="https://medium.com/i0exception/map-iteration-in-go-275abb76f721">Map iteration in Go</a> was originally published in <a href="https://medium.com/i0exception">i0exception</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Common traps while using defer in go]]></title>
            <link>https://medium.com/i0exception/some-common-traps-while-using-defer-205ebbdc0a3b?source=rss-4bfcc415995f------2</link>
            <guid isPermaLink="false">https://medium.com/p/205ebbdc0a3b</guid>
            <category><![CDATA[golang]]></category>
            <dc:creator><![CDATA[Aniruddha]]></dc:creator>
            <pubDate>Tue, 20 Mar 2018 22:02:24 GMT</pubDate>
            <atom:updated>2019-12-04T03:04:29.648Z</atom:updated>
            <content:encoded><![CDATA[<p>The <em>defer</em> statement in <em>go</em> is really handy in improving code readability. However, in some cases its behavior is confusing and not immediately obvious. Even after writing <em>go</em> for over 2 years, there are times when a <em>defer</em> in the wild leaves me scratching my head. My goal is to compile a list of behaviors which have stumped me in the past, mainly as a note to myself.</p><h4>Defer scopes to a function, not a block</h4><p>A variable exists only within the scope of a code block. However, a <em>defer</em> statement within a block is only executed when the enclosing function returns. I’m not sure what the rationale for this is, but it can catch you off guard if you’re, say, allocating resources in a loop but <em>defer</em> the deallocation.</p><pre>func do(files []string) error {<br>  for _, file := range files {<br>    f, err := os.Open(file)<br>    if err != nil {<br>      return err<br>    }<br>    defer f.Close() // <strong>This is wrong!!</strong><br>    // use f<br>  }<br>}</pre><h4>Chaining methods</h4><p>If you chain methods in a <em>defer</em> statement, everything except the last function will be evaluated at call time. <em>defer</em> expects a function as the “<em>argument”.</em></p><pre>type logger struct {}<br>func (l *logger) Print(s string) {<br>  fmt.Printf(&quot;Log: %v\n&quot;, s)<br>}</pre><pre>type foo struct {<br>  l *logger<br>}</pre><pre>func (f *foo) Logger() *logger {<br>  fmt.Println(&quot;Logger()&quot;)<br>  return f.l<br>}</pre><pre>func do(f *foo) {<br>  defer f.Logger().Print(&quot;done&quot;)<br>  fmt.Println(&quot;do&quot;)<br>}<br> <br>func main() {<br>  f := &amp;foo{<br>    l: &amp;logger{},<br>  }<br>  do(f)<br>}</pre><p>Prints —</p><pre>Logger()<br>do<br>Log: done</pre><p>The Logger() function is called before any of the work in do() is executed.</p><h4>Function arguments</h4><p>Okay, but what if the last method in the chain takes an argument? Surely, if it is executed after the enclosing function returns, any changes made to the variables will be captured.</p><pre>type logger struct {}<br>func (l *logger) Print(err error) {<br>  fmt.Printf(&quot;Log: %v\n&quot;, err)<br>}</pre><pre>type foo struct {<br>  l *logger<br>}</pre><pre>func (f *foo) Logger() *logger {<br>  fmt.Println(&quot;Logger()&quot;)<br>  return f.l<br>}</pre><pre>func do(f *foo) (err error) {<br>  defer f.Logger().Print(err)<br>  fmt.Println(&quot;do&quot;)<br>  return fmt.Errorf(&quot;ERROR&quot;)<br>}<br> <br>func main() {<br>  f := &amp;foo{<br>    l: &amp;logger{},<br>  }<br>  do(f)<br>}</pre><p>Guess what this prints?</p><pre>Logger()<br>do<br>Log: &lt;nil&gt;</pre><p>The value of err is captured at call time. Any changes made to this variable are not captured by the <em>defer</em> statement because they don’t point to the same value.</p><h4>Calling methods on non-pointer types</h4><p>We saw how chained methods behave in a <em>defer</em> statement. Exploring this further, if the called method is not defined on a pointer receiver type, calling it in a <em>defer</em> will actually make a copy of the instance.</p><pre>type metrics struct {<br>  success bool<br>  latency time.Duration<br>}</pre><pre>func (m metrics) Log() {<br>  fmt.Printf(&quot;Success: %v, Latency: %v\n&quot;, m.success, m.latency)<br>}</pre><pre>func foo() {<br>  var m metrics<br>  defer m.Log()</pre><pre>  start := time.Now()<br>  // Do something<br>  time.Sleep(2*time.Second)<br>  <br>  m.success = true<br>  m.latency = time.Now().Sub(start)<br>}</pre><p>This prints —</p><pre>Success: false, Latency: 0s</pre><p>m is copied when <em>defer</em> is called. m.Foo() is basically shorthand for Foo(m)</p><h4>Conclusion</h4><p>If you’ve spent enough time writing <em>go</em>, these might not feel like “<em>traps</em>”. But for someone new to the language, there are definitely a lot of places where the <em>defer </em>statement does not satisfy the <a href="https://en.wikipedia.org/wiki/Principle_of_least_astonishment">principle of least astonishment</a>. There are a <a href="http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/">bunch</a> <a href="https://blog.learngoprogramming.com/gotchas-of-defer-in-go-1-8d070894cb01">of</a> <a href="https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa">other</a> <a href="https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-iii-36a1ab3d6ef1">places</a> that go into more detail about some other common mistakes while writing <em>go</em>. Do check them out.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=205ebbdc0a3b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/i0exception/some-common-traps-while-using-defer-205ebbdc0a3b">Common traps while using defer in go</a> was originally published in <a href="https://medium.com/i0exception">i0exception</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Runtime overhead of using defer in go]]></title>
            <link>https://medium.com/i0exception/runtime-overhead-of-using-defer-in-go-7140d5c40e32?source=rss-4bfcc415995f------2</link>
            <guid isPermaLink="false">https://medium.com/p/7140d5c40e32</guid>
            <category><![CDATA[golang]]></category>
            <dc:creator><![CDATA[Aniruddha]]></dc:creator>
            <pubDate>Wed, 07 Mar 2018 08:37:40 GMT</pubDate>
            <atom:updated>2018-03-07T08:37:40.289Z</atom:updated>
            <content:encoded><![CDATA[<p>Golang has a pretty nifty keyword named defer. As explained <a href="https://blog.golang.org/defer-panic-and-recover">here</a>, a defer statement pushes a function call onto a list. The list of saved calls is executed after the surrounding function returns. Defer is commonly used to simplify functions that perform various clean-up actions.</p><p>Using defer, however, is not free. Using go’s benchmarking support, we can try to quantify this overheard.</p><p>The following two functions do the same work, but one calls a function in a defer statement while the other doesn’t</p><pre>package main</pre><pre>func doNoDefer(t *int) {<br>  func() {<br>    *t++<br>  }()<br>}</pre><pre>func doDefer(t *int) {<br>  defer func() {<br>    *t++<br>  }()<br>}</pre><p>Let’s benchmark these —</p><pre>package main</pre><pre>import (<br>  &quot;testing&quot;<br>)</pre><pre>func BenchmarkDeferYes(b *testing.B) {<br>  t := 0<br>  for i := 0; i &lt; b.N; i++ {<br>    doDefer(&amp;t)<br>  }<br>}</pre><pre>func BenchmarkDeferNo(b *testing.B) {<br>  t := 0<br>  for i := 0; i &lt; b.N; i++ {<br>    doNoDefer(&amp;t)<br>  }<br>}</pre><p>Running this with go -bench on an 8 core google cloud VM gives us</p><pre>⇒ go test -v -bench BenchmarkDefer -benchmem<br>goos: linux<br>goarch: amd64<br>pkg: cmd<br>BenchmarkDeferYes-8  20000000   62.4 ns/op  0 B/op  0 allocs/op<br>BenchmarkDeferNo-8   500000000  3.70 ns/op  0 B/op  0 allocs/op</pre><p>As expected, both these functions don’t allocate any memory. But doDefer is roughly <strong><em>16 times</em></strong> more expensive than doNoDefer. To understand why defer is this expensive, let’s look at the disassembled code.</p><p>The disassembly for the actual functions called inside doDefer and doNoDefer is the same</p><pre>main.go:10   MOVQ 0x8(SP), AX<br>main.go:11   MOVQ 0(AX), CX<br>main.go:11   INCQ CX<br>main.go:11   MOVQ CX, 0(AX)<br>main.go:12   RET</pre><p>The doNoDefer sets up the necessary registers and then calls main.doNoDefer.func1</p><pre>TEXT main.doNoDefer(SB) main.go<br>main.go:3  MOVQ FS:0xfffffff8, CX<br>main.go:3  CMPQ 0x10(CX), SP<br>main.go:3  JBE 0x450b65<br>main.go:3  SUBQ $0x10, SP<br>main.go:3  MOVQ BP, 0x8(SP)<br>main.go:3  LEAQ 0x8(SP), BP<br>main.go:3  MOVQ 0x18(SP), AX<br>main.go:6  MOVQ AX, 0(SP)<br>main.go:6  CALL main.doNoDefer.func1(SB)<br>main.go:7  MOVQ 0x8(SP), BP<br>main.go:7  ADDQ $0x10, SP<br>main.go:7  RET<br>main.go:3  CALL runtime.morestack_noctxt(SB)<br>main.go:3  JMP main.doNoDefer(SB)</pre><p>The doDefer function also sets up registers, but there are additional function calls — the first one to runtime.deferproc which sets up the deferred function to be called. The second one is to runtime.deferreturn — which in turn calls itself for every defer statement encountered in the function.</p><pre>TEXT main.doDefer(SB) main.go<br>main.go:9    MOVQ FS:0xfffffff8, CX<br>main.go:9    CMPQ 0x10(CX), SP<br>main.go:9    JBE 0x450bd3<br>main.go:9    SUBQ $0x20, SP<br>main.go:9    MOVQ BP, 0x18(SP)<br>main.go:9    LEAQ 0x18(SP), BP<br>main.go:9    MOVQ 0x28(SP), AX<br>main.go:12   MOVQ AX, 0x10(SP)<br>main.go:10   MOVL $0x8, 0(SP)<br>main.go:10   LEAQ 0x218e3(IP), AX<br>main.go:10   MOVQ AX, 0x8(SP)<br>main.go:10   CALL runtime.deferproc(SB)<br>main.go:10   TESTL AX, AX<br>main.go:10   JNE 0x450bc3<br>main.go:13   NOPL<br>main.go:13   CALL runtime.deferreturn(SB)<br>main.go:13   MOVQ 0x18(SP), BP<br>main.go:13   ADDQ $0x20, SP<br>main.go:13   RET<br>main.go:10   NOPL<br>main.go:10   CALL runtime.deferreturn(SB)<br>main.go:10   MOVQ 0x18(SP), BP<br>main.go:10   ADDQ $0x20, SP<br>main.go:10   RET<br>main.go:9    CALL runtime.morestack_noctxt(SB)<br>main.go:9    JMP main.doDefer(SB)</pre><p><a href="https://golang.org/src/runtime/panic.go?s=1703:1741#L63">deferproc</a> and <a href="https://golang.org/src/runtime/panic.go?s=8427:8457#L306">deferreturn</a> are both non-trivial functions and they do a bunch of accounting and setup at entry and exit. In short, don’t use defer in hot code paths. The overhead is non-trivial and not obvious.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7140d5c40e32" width="1" height="1" alt=""><hr><p><a href="https://medium.com/i0exception/runtime-overhead-of-using-defer-in-go-7140d5c40e32">Runtime overhead of using defer in go</a> was originally published in <a href="https://medium.com/i0exception">i0exception</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Memory Mapped Files]]></title>
            <link>https://medium.com/i0exception/memory-mapped-files-5e083e653b1?source=rss-4bfcc415995f------2</link>
            <guid isPermaLink="false">https://medium.com/p/5e083e653b1</guid>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Aniruddha]]></dc:creator>
            <pubDate>Sat, 03 Feb 2018 09:27:15 GMT</pubDate>
            <atom:updated>2018-02-03T20:45:45.424Z</atom:updated>
            <content:encoded><![CDATA[<p>Memory mapping of files is a very powerful abstraction that many operating systems support out of the box. Linux does this via the mmap system call. In most cases where an application reads (or writes) to a file at arbitrary positions, using mmap is a solid alternative to the more traditional read/write system calls. We’ve used it in the analytics database at Mixpanel to improve performance or make code more readable and I wanted to spend some time figuring out what actually happens under the hood.</p><p>At a high level, the mmap system call lets you read and write to a file as if you were accessing an array in memory. There are two main modes in which files can be mapped — MAP_PRIVATE and MAP_SHARED. In MAP_PRIVATE, any changes that you make to the file are in memory and not written back to it. In MAP_SHARED, changes made to the file are visible to other memory mappings of that file and are eventually committed to disk.</p><p>To understand what happens on calling mmap, it’s important to understand two things — how linux handles files and how memory addressing works.</p><p>You can open a file for reading or writing using the open system call. This returns a file descriptor. Linux maintains a global file descriptor table and adds an entry to it representing the opened file. This entry is represented by the <a href="https://elixir.free-electrons.com/linux/v4.15/source/include/linux/fs.h#L852">file</a> structure which is local to the process. Internally, linux uses the <a href="https://elixir.free-electrons.com/linux/v4.15/source/include/linux/fs.h#L570">inode</a> struct to represent the file. The file struct has a pointer to this and linux ensures that multiple file descriptors that touch the same file point to the same inode so that their changes are visible to each other. The i_mapping field on the inode struct is what’s used to get the right set of pages from the page cache for an offset in the file.</p><p>In linux, processes have a virtual memory address space that’s, well, virtual. This memory is not usually backed by physical memory unless you’re actually reading or writing to some part of it. Linux further divides the memory space into equal sized pages and a page is the unit of access as far as the kernel is concerned. So, when a process calls mmap, the short answer is that nothing really happens. The kernel simply reserves some part of this virtual memory address space and returns the address. The <a href="https://elixir.free-electrons.com/linux/v4.15/source/mm/mmap.c#L1321">do_mmap</a> function is what eventually gets called after some bookkeeping and does most of the work for allocating this virtual memory in the process’ address space. This function stores a pointer to the file struct in the <a href="https://elixir.free-electrons.com/linux/v4.15/source/include/linux/mm_types.h#L280">vm_area_struct</a> struct that represents the returned address.</p><p>When the process accesses the address, a page fault occurs. The page fault handler locates the vm_area_struct struct in the process’s address space and eventually finds the pages in the page cache that map to the file offsets being accessed. These pages are marked as dirty if there’s a write and mapped directly to user space — this way there is no need to copy data from kernel to user space.</p><p>Once you’re done using the memory mapped area, the munmap system call can be used to free up the memory. Any data written to the page cache is periodically committed to disk, although you can force it with msync. While mmap is useful, it definitely has drawbacks. Misses in the page cache always result in the page being read into the cache even if a write is going to overwrite the contents. Offsets need to be aligned to page boundaries. Error handling happens via signals because there is no way to indicate otherwise. And finally, you can’t mmap all types of file descriptors(pipes for example). As usual, conditions apply — so make sure you don’t use mmap indiscriminately.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5e083e653b1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/i0exception/memory-mapped-files-5e083e653b1">Memory Mapped Files</a> was originally published in <a href="https://medium.com/i0exception">i0exception</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Writing tests in Go]]></title>
            <link>https://medium.com/i0exception/some-thoughts-on-testing-in-go-d5fdf58fa471?source=rss-4bfcc415995f------2</link>
            <guid isPermaLink="false">https://medium.com/p/d5fdf58fa471</guid>
            <category><![CDATA[golang]]></category>
            <dc:creator><![CDATA[Aniruddha]]></dc:creator>
            <pubDate>Tue, 23 Jan 2018 10:32:16 GMT</pubDate>
            <atom:updated>2019-11-20T15:40:19.154Z</atom:updated>
            <content:encoded><![CDATA[<p>Recently, I bumped into <a href="https://segment.com/blog/5-advanced-testing-techniques-in-go/">this</a> article by Segment’s engineering team. It has a lot of good advice and some helpful links about writing good tests in Go. I wanted to discuss a few more things that I’ve found useful in the two-ish years that I’ve been using the language.</p><p>For those of you who haven’t used Go, consider using it for your next side project. It’s a very opinionated programming language with a spec that you can mostly hold in your head and a fairly comprehensive standard library. Also, unlike most other languages, you don’t have to deal with a ton of testing frameworks. Although the standard library provides good support for writing tests, I’ve found the following techniques useful in writing better testable code.</p><h4>It’s okay to test unexposed functions</h4><p>The default package layout in Go encourages housing code and tests in the same package. Tests can access “private” functions and members — this is okay. I’ve mostly encountered this in the context of helper functions that are only used within a package. The alternative is to expose them publicly, which has its drawbacks.</p><h4>Control time</h4><p>Avoid using the time package to block or schedule execution. Consider using something like <a href="https://github.com/jonboulle/clockwork">clockwork</a> to pass in a fake, controllable clock in unit tests. Controlling time lets you write more deterministic unit tests. This is useful when you’re testing behavior that depends on time — timeouts, retries, scheduled runs etc.</p><h4>Use Go’s race detector</h4><p>Data access races are really hard to debug. Fortunately, Go has support for detecting them — so use it. <a href="https://golang.org/doc/articles/race_detector.html">This</a> is a good starting point to understand how to use the race detector. Remember that it will only test the code paths that your tests execute. So you still need to write a test that exercises the race.</p><h4>Write benchmarks</h4><p>Go makes writing benchmarks easy. <a href="https://golang.org/pkg/testing/#hdr-Benchmarks">This</a> is a good starting point to understand how to write them. Make sure you have benchmarks for the performance sensitive parts of your code.</p><h4>Use setup functions</h4><p>This is useful if you want to setup some external state that is used by the function or implementation being tested. An example would be something that operates on a directory. Instead of having every test function create a temporary directory and clean up after itself, write a generator function that does this.</p><pre>func withTempDir(t *testing.T, f func(d string)) {<br> dir, err := ioutil.TempDir(...)<br> if assert.NoError(err) {<br>  defer os.RemoveAll(dir)<br>  f(dir)<br> }<br>}</pre><pre>func Test(t *testing.T) {<br> withTempDir(t, func(dir string) {<br>  // use dir in test<br> })<br>}</pre><h4>Accept interfaces, return structs</h4><p>Interfaces can be mocked; structs cannot. Having interfaces as member variables makes it easy to mock their behavior. Returning structs (concrete implementations) means that the caller gets to decide how to use the returned value.</p><p>That said, use mocks carefully. With mocks, you’re testing your understanding of the interface, at the time the test was written. While this is ideal, it’s not always practical — especially in high velocity codebases. If you think the underlying implementation is unstable, test it in a separate package to avoid diverging.</p><p>Lastly, use a mock generator like <a href="https://github.com/vektra/mockery">mockery</a> instead of writing them yourself.</p><h4>Use self referential interfaces</h4><p>This is a neat trick that I’ve found useful for testing behavior that is either non-deterministic or doesn’t fit well in a unit test because it makes network calls or depends on an external service. Let’s say you want to test the behavior of a function A() on a struct of type Foo that makes a non-deterministic function call that uses a member variable (like a network connection) in Foo . An easy way to do this is to move the non-determinism into a function B() on Foo and introduce a new member variable on Foo that satisfies an interface exposed by B() and call B() on this member. The actual code can use an instance of Foo as the member variable and the tests can provide a mock. The code below should make things clearer.</p><pre>package main</pre><pre>import (<br> &quot;fmt&quot;<br>)</pre><pre>type doer interface {<br> B()<br>}</pre><pre>type Foo struct {<br> msg string<br> d doer<br>}</pre><pre>func (f *Foo) B() {<br> fmt.Printf(&quot;i am non deterministic: %v\n&quot;, f.msg)<br>}</pre><pre>func (f *Foo) A() {<br> f.d.B()<br> fmt.Println(&quot;test me&quot;)<br>}</pre><pre>func main() {<br> x := &amp;Foo{<br>  msg:&quot;go&quot;,<br> }<br> x.d = x // x.d = MockDoer() in tests<br> x.A()<br>}</pre><p>Although many of these techniques are useful, deciding where to use them is always a judgement call. Choose wisely!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d5fdf58fa471" width="1" height="1" alt=""><hr><p><a href="https://medium.com/i0exception/some-thoughts-on-testing-in-go-d5fdf58fa471">Writing tests in Go</a> was originally published in <a href="https://medium.com/i0exception">i0exception</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Kinesis Blue]]></title>
            <link>https://medium.com/i0exception/kinesis-blue-e1df5f03e14?source=rss-4bfcc415995f------2</link>
            <guid isPermaLink="false">https://medium.com/p/e1df5f03e14</guid>
            <category><![CDATA[keyboard]]></category>
            <category><![CDATA[mechanical-keyboards]]></category>
            <dc:creator><![CDATA[Aniruddha]]></dc:creator>
            <pubDate>Fri, 01 Jul 2016 05:50:04 GMT</pubDate>
            <atom:updated>2023-03-21T17:12:20.038Z</atom:updated>
            <content:encoded><![CDATA[<p>I’ve used mechanical keyboards off and on for around 4 years and I always wanted to give the Kinesis Advantage a shot. About a month ago, I gave in to temptation and bought one off Ebay.</p><h3>Aniruddha on Twitter: &quot;Finally gave in and bought the kinesis keyboard @kinesisergo pic.twitter.com/lJLFQssj8V / Twitter&quot;</h3><p>Finally gave in and bought the kinesis keyboard @kinesisergo pic.twitter.com/lJLFQssj8V</p><p>For the first few days, typing on the Advantage was difficult. As a touch typist, going from 100 WPM to 30 was very frustrating.</p><h3>Aniruddha on Twitter: &quot;Hunt and peck programmers, how do you live with yourself? / Twitter&quot;</h3><p>Hunt and peck programmers, how do you live with yourself?</p><p>But within a week, I regained most of my original speed. My wrists felt a lot better and going back to a regular keyboard caused noticeable stiffness around my shoulders.</p><h3>Aniruddha on Twitter: &quot;One week with the @kinesisergo and I&#39;m up to 84 WPM (I usually type at &gt; 100) pic.twitter.com/6XsBMwf9Zw / Twitter&quot;</h3><p>One week with the @kinesisergo and I&#39;m up to 84 WPM (I usually type at &gt; 100) pic.twitter.com/6XsBMwf9Zw</p><p>Although I could type without having to look up keys on every alternate keypress, I kept <a href="https://geekhack.org/index.php?topic=8180.0">bottoming out</a> and in general disliked the feel of the keyboard. I knew why — it was the switches.</p><p>The Advantage uses <a href="https://deskthority.net/wiki/Cherry_MX_Brown">Cherry MX Brown</a> switches. In fact, Cherry made them specifically for the original contoured Kinesis keyboard. While they are tactile, the browns aren’t clicky like the <a href="https://deskthority.net/wiki/Cherry_MX_Blue">blues</a>. I’ve been a huge fan of the blues ever since I got my hands on the <a href="http://www.amazon.in/Bharat-Gold-PS2-Wired-Keyboard/dp/B00A17MNHU">TVS Bharat Gold</a> keyboard. What I wanted was a Kinesis Advantage, but with the clicky blues.</p><p>Kinesis doesn’t make the Advantage with blues (they’re exploring a <a href="https://twitter.com/kinesisergo/status/746915444133502976">limited run</a> for the Advantage2). I sent a DM to their Twitter account to explore possibilities and they suggested that I get in touch with their support team.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8yqbMIDGJcafexQkSOBsHg.png" /></figure><p>Their support team is amazing and super responsive. Within 10 minutes of me sending an email, they responded. I wasn’t the first one trying to modify an Advantage. They sent me links to blogs and part numbers for pieces I needed to make this happen.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*i3rhxe-uRPodqNkx80D6ow.png" /></figure><p>I was surprised at how cheap the pieces were — especially given that a <a href="https://www.amazon.com/Kinesis-KB500USB-BLK-Advantage-Contoured-Keyboard/dp/B000LVJ9W8/ref=sr_1_1?ie=UTF8&amp;qid=1467346030&amp;sr=8-1&amp;keywords=kinesis+advantage">new Advantage</a> costs over $250. The only problem was that my experience with soldering was limited to what I did in school, which was around 10 years ago. I spoke to a few people, who assured me that it isn’t as hard as it sounds and that watching a few <a href="https://www.youtube.com/watch?v=fYz5nIHH0iY">youtube</a> <a href="https://www.youtube.com/watch?v=x1irVrAl3Ts">videos</a> should be enough.</p><p>So I went ahead and ordered the 2 key-wells and their PCBs (the first 4 parts in the email). I didn’t order the thumb cluster because the switches are <a href="https://www.keychatter.com/2015/02/12/pcb-vs-plate-mounted-keyboards-whats-the-difference/">PCB-mounted</a> and I could simply swap them. I also ordered a <a href="https://www.amazon.com/gp/product/B01G90U73E/ref=oh_aui_detailpage_o00_s00?ie=UTF8&amp;psc=1">cheap soldering kit</a> from Amazon along with <a href="https://www.amazon.com/gp/product/B00030AP48/ref=oh_aui_detailpage_o00_s00?ie=UTF8&amp;psc=1">solder</a> and <a href="https://www.amazon.com/gp/product/B0149K5WH2/ref=oh_aui_detailpage_o00_s00?ie=UTF8&amp;psc=1">diodes</a>.</p><p>Next, I had to source the switches. Original Cherry MX Blues are expensive! I needed roughly 70 of them and the <a href="https://mechanicalkeyboards.com/shop/index.php?l=product_detail&amp;p=650">cheapest ones</a> I found were still around $50 with shipping. I’d heard a lot of good things about Gateron Blues from the friendly folks over at <a href="http://www.reddit.com/r/MechanicalKeyboards/">/r/MechanicalKeyboards</a> and <a href="http://www.switchtop.co/product/gateron-switches">Switchtop</a> sells them for $0.275 each (I’m fairly happy with the folks at switchtop for shipping the switches within a day!). In 4 days I had all the things I needed to make the “switch”.</p><p>Soldering was pretty daunting at first. I opened up an old alarm clock and tinkered with the PCB. I desoldered a bunch of components and tried to solder them back. I thought I did everything right, but the clock refused to start. Not a good first step. I did, however, get a hang of using the soldering iron and the solder. Now, the worst thing I could do was screw up a PCB worth $7.50.</p><p>I started by mounting the switches on the right hand key-well. Some switches were duds, so I made sure I tested the clicky-ness (if that’s even a word) before using a switch. This was the easy part.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/750/1*ZDD6mlmLTWnCdBCdm2P-zA.jpeg" /></figure><p>Next up was adding the PCB and soldering the switches. Given the contoured shape of the key-wells, the PCB is flexible and, at times, difficult to align with the switch leads. The bottom row switches would keep falling out, so I removed them and decided to deal with the rest of the switches first. What worked best was soldering one column at a time, starting with the outermost one. I used a crocodile clip (the picture below shows the left key-well instead of the right, but you get the idea) to hold the PCB in place while I soldered away to glory.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/750/1*oCa5MDljnSdT-KJvv3CEBg.jpeg" /></figure><p>I added the bottom row at the very end and unsurprisingly, this went a lot smoother now that the rest of the PCB was in place.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/750/1*pANs2t8F3UZ4i-iWmLLXAA.jpeg" /></figure><p>I then started soldering the diodes. With diodes, it’s important to make sure that you get the orientation right. On the Kinesis, the black part of the diode is oriented towards the left if the switch leads are to the top. This is true for both key-wells. I tested the board right after soldering one diode by removing the ribbon cable from the original PCB and plugging it into the new one.</p><h3>Aniruddha on Twitter: &quot;One key out of the 28 currently works. @kinesisergo #excited pic.twitter.com/IC0PVUGurE / Twitter&quot;</h3><p>One key out of the 28 currently works. @kinesisergo #excited pic.twitter.com/IC0PVUGurE</p><p>At this point, I was somewhat confident of being on the right track. I soldered the rest of the diodes</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/750/1*1Oylbulb5nsMFkxuIYMuZQ.jpeg" /></figure><p>and replaced the old key-well.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/750/1*O0EIaT-d2MXIZldYBK-vfQ.jpeg" /></figure><p>I tested all the switches using <a href="http://www.keyboardtester.com">www.keyboardtester.com</a> to make sure they registered and then added the keycaps. Doing the second key-well was much faster now that I had a good idea of what needed to be done, and in what order. It took me around 8 hours from start to finish, but that included time off to get dinner.</p><h3>Aniruddha on Twitter: &quot;And done with key wells! I now have a clicky kinesis advantage! pic.twitter.com/d1MpsW4Pio / Twitter&quot;</h3><p>And done with key wells! I now have a clicky kinesis advantage! pic.twitter.com/d1MpsW4Pio</p><p>I haven’t modified the thumb cluster yet, mainly because I’m planning to add Gateron greens instead of blues. I’m also waiting for blank <a href="https://deskthority.net/wiki/Keycap_construction">PBT</a> keycaps that I ordered for cheap from China. All in all, this was a fun project and completely worth the hassle. I’ll post a photo of the keyboard once I get the new keycaps.</p><p>Update — I got the Chinese PBT keycaps.</p><h3>Aniruddha on Twitter: &quot;New blank keycaps on my kinesis advantage! pic.twitter.com/gMPWKwUkao / Twitter&quot;</h3><p>New blank keycaps on my kinesis advantage! pic.twitter.com/gMPWKwUkao</p><p>Update #2— SA profile keycaps look great on the Kinesis</p><h3>Aniruddha on Twitter: &quot;Absolutely loving the SA profile keycaps. Can&#39;t use a regular keyboard, so I put them on my Kinesis @kinesisergo pic.twitter.com/NevEX4ewXZ / Twitter&quot;</h3><p>Absolutely loving the SA profile keycaps. Can&#39;t use a regular keyboard, so I put them on my Kinesis @kinesisergo pic.twitter.com/NevEX4ewXZ</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e1df5f03e14" width="1" height="1" alt=""><hr><p><a href="https://medium.com/i0exception/kinesis-blue-e1df5f03e14">Kinesis Blue</a> was originally published in <a href="https://medium.com/i0exception">i0exception</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>