<?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:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Ketan's Newsletter]]></title><description><![CDATA[I write about databases and product engineering: lessons that will come handy everyday in your job. No junk, I only write when I have something substantial to share.]]></description><link>https://www.ketanbhatt.com</link><image><url>https://substackcdn.com/image/fetch/$s_!adSA!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e37e6d-f289-4a21-95bd-008a89277566_426x426.png</url><title>Ketan&apos;s Newsletter</title><link>https://www.ketanbhatt.com</link></image><generator>Substack</generator><lastBuildDate>Wed, 29 Apr 2026 22:46:42 GMT</lastBuildDate><atom:link href="https://www.ketanbhatt.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Ketan Bhatt]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[ktbt@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[ktbt@substack.com]]></itunes:email><itunes:name><![CDATA[Ketan Bhatt]]></itunes:name></itunes:owner><itunes:author><![CDATA[Ketan Bhatt]]></itunes:author><googleplay:owner><![CDATA[ktbt@substack.com]]></googleplay:owner><googleplay:email><![CDATA[ktbt@substack.com]]></googleplay:email><googleplay:author><![CDATA[Ketan Bhatt]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Do you really need a Vector Search Database?]]></title><description><![CDATA[Reflections on Intercom&#8217;s decision to stick with Elasticsearch]]></description><link>https://www.ketanbhatt.com/p/do-you-really-need-a-vector-search</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/do-you-really-need-a-vector-search</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Thu, 07 Aug 2025 09:10:57 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/55bae1d9-3e2a-498c-9598-99201838aef4_1292x499.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Originally published on <a href="https://fin.ai/research/do-you-really-need-a-vector-search-database/">fin.ai/research</a>.</p><div><hr></div><p>Vector databases have exploded in the past two years. There are both open-source and managed options, including Pinecone, Milvus, Qdrant, and Weaviate, which often claim greater scale, flexibility, and speed than traditional search platforms.</p><p>When we set out to design our AI retrieval systems, we began with first principles, not vendor promises. Our research showed that Elasticsearch struck the right balance of performance, cost, operability, and long-term maintainability &#8211; at least for our needs, and given our experience operating it at scale.</p><p>In this post we share our rationale, and two years of operational results running Fin, the market&#8217;s most advanced AI agent.</p><h2><strong>Initial System: In-Memory Retrieval</strong></h2><p>In early versions, each customer&#8217;s content and embeddings lived on S3. For each inference request, we:</p><ol><li><p>Retrieved the customer&#8217;s S3 object</p></li><li><p>Loaded all vectors into memory</p></li><li><p>Ran a brute-force KNN search</p></li></ol><p>This design was a good starting point:</p><ul><li><p>The &#8220;database&#8221; could handle any number of requests or spikes</p></li><li><p>Iteration was simple</p></li><li><p>Our scientists could experiment easily, leveraging familiar tools like notebooks and pandas</p></li></ul><p>It also was battle-tested to some degree, having served our previous &#8216;Resolution Bot&#8217; product for years (which used much smaller datasets).</p><p>But as we added support for <a href="https://www.intercom.com/help/en/articles/9357928-overview-of-content-types-and-when-to-use-them">new content types</a>, and began <a href="https://www.intercom.com/help/en/articles/10260362-use-content-from-conversations-beta">generating content from conversations</a>, the number of embeddings for each customer soared. Soon, S3 download and deserialization times began to dominate request latency&#8212; we started to see spikes of 15 seconds just to load vectors for our largest customers, before even starting the search or LLM inference.</p><h2><strong>Requirements &amp; Constraints</strong></h2><p>Prior to committing to a particular technology, we defined concrete constraints:</p><ul><li><p>Scale: Target of 100M+ embeddings of 768 dimensions, accounting for accelerating growth</p></li><li><p>Cost: Consider infrastructure and ongoing operational burden</p></li><li><p>Filtering support: Must be able to filter retrieval (by language, content type, audience, etc. to support our platform and permissions)</p></li></ul><p>Other nice-to-haves included:</p><ul><li><p><a href="https://www.intercom.com/blog/run-less-software/">Run Less Software</a>: We want to avoid undifferentiated heavy lifts</p></li><li><p>Full text search: We might want to move to hybrid search in the future</p></li><li><p>Predictability: Known scaling properties, failure modes, and monitoring</p></li></ul><p>Looking back, even our optimistic scale estimate of 100 million embeddings was much too low!</p><h2><strong>Surveying the Landscape</strong></h2><p>We evaluated Pinecone, Milvus, Qdrant, Weaviate, and Elasticsearch. All had the basic features we required, so cost initially seemed like the main differentiator. At the time, we had about 20 million embeddings; we projected costs for 5x that scale&#8212;100 million embeddings.</p><p>Several promising open-source solutions offered theoretical top-end performance, but with potentially increased operability risks. And the managed services tended to be more expensive &#8212; Pinecone and Milvus, for example, were estimated to be 2x the cost of their open-source alternatives.</p><p>In the end, the primary differentiator for us was operational familiarity and proven reliability. Elasticsearch, already core to several business-critical Intercom systems, presented clear advantages here.</p><h2><strong>Benchmarking: Vector Search in Elasticsearch</strong></h2><p>We began to look at standard benchmarks:</p><ul><li><p><a href="https://elasticsearch-benchmarks.elastic.co/#tracks/so_vector/nightly/default/90d">Elasticsearch nightly vector search benchmarks</a> (2M embeddings, 768D): search latency between 100ms (nightly-so_vector-script-score-query-match-all-latency) and 200ms (nightly-so_vector-script-score-query-acceptedAnswerId-filter71%-latency).</p></li><li><p><a href="https://ann-benchmarks.com/">ANN-Benchmarks</a>: Elasticsearch sat in the center of the pack, neither the fastest nor the slowest.</p></li></ul><p>And verified these against our in-house benchmarks on production-like datasets, which gave similar results:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!a6n0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab153216-390b-49f9-9dba-1e096dbb842a_1594x370.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!a6n0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab153216-390b-49f9-9dba-1e096dbb842a_1594x370.png 424w, https://substackcdn.com/image/fetch/$s_!a6n0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab153216-390b-49f9-9dba-1e096dbb842a_1594x370.png 848w, https://substackcdn.com/image/fetch/$s_!a6n0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab153216-390b-49f9-9dba-1e096dbb842a_1594x370.png 1272w, https://substackcdn.com/image/fetch/$s_!a6n0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab153216-390b-49f9-9dba-1e096dbb842a_1594x370.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!a6n0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab153216-390b-49f9-9dba-1e096dbb842a_1594x370.png" width="1456" height="338" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ab153216-390b-49f9-9dba-1e096dbb842a_1594x370.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:338,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:147846,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.ketanbhatt.com/i/170341782?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab153216-390b-49f9-9dba-1e096dbb842a_1594x370.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!a6n0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab153216-390b-49f9-9dba-1e096dbb842a_1594x370.png 424w, https://substackcdn.com/image/fetch/$s_!a6n0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab153216-390b-49f9-9dba-1e096dbb842a_1594x370.png 848w, https://substackcdn.com/image/fetch/$s_!a6n0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab153216-390b-49f9-9dba-1e096dbb842a_1594x370.png 1272w, https://substackcdn.com/image/fetch/$s_!a6n0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab153216-390b-49f9-9dba-1e096dbb842a_1594x370.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Overall, this performance was good enough for us. For an AI Agent, the bottleneck will generally be the LLM&#8217;s latency, which is measured in seconds. Even if search latency dropped to zero, we only stand to save ~200ms. This would be imperceptible to the user in most cases.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.ketanbhatt.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Ketan's Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>Decision Criteria: Why We Chose Elasticsearch</strong></h2><p>Beyond raw benchmark numbers, the following factored heavily in the final decision:</p><h3>Low Infrastructure Cost</h3><p>Just accounting for the infrastructure, we estimated Elasticsearch would cost us the least to run and the gap would widen as we scaled up, especially compared to the Managed offerings.</p><p>Initially, it would be even cheaper to get started as we planned to run on a shared large scale Elasticsearch cluster that Intercom used for multiple use cases. And we could migrate to a separate infrastructure when necessary.</p><h3>Low Onboarding Cost</h3><p>Since we would not be running a new distributed system, we didn&#8217;t need new runbooks and could avoid common scaling/maintenance issues and failure modes.</p><p>We could also reuse most of the tooling around running a database: disaster recovery, snapshots, replication.</p><h3>Subject-Matter Expertise</h3><p>Elasticsearch is one of the &#8220;core technologies&#8221; at Intercom. We have experience running it and have subject matter experts within the company (<a href="https://www.ketanbhatt.com/p/building-search-with-elasticsearch-at-intercom">1</a>, <a href="https://www.intercom.com/blog/core-technologies-team/">2</a>) who have <a href="https://www.intercom.com/blog/optimising-elasticsearch-at-intercom/">familiarity with how ES scales</a>. They can proactively manage cluster health, be able to tweak capacity or performance by playing with multiple levers like the number of shards in an index, size of the data nodes etc.</p><h3>Filterable Hybrid Search</h3><p>The ability to compose vector and structured filters with existing ES query DSL is a requirement most vector-first DBs do not easily fulfill. With Elasticsearch, we can combine structured filters and vector search with full-text search to improve relevance of the results in the future.</p><h3>Possibility to move to Approximate KNN in the Future</h3><p>Since Exact KNN performed adequately for us, we could start with it and avoid having to worry about recall. This simplified the migration. In the future, we could move to ANN for faster results.</p><h2><strong>Production Results</strong></h2><p>Two years later, our scale exceeded even our most ambitious forecasts by 3x&#8212;a welcome problem.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BeMe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b489650-9119-4154-b97c-555d5acfac1e_1590x964.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BeMe!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b489650-9119-4154-b97c-555d5acfac1e_1590x964.png 424w, https://substackcdn.com/image/fetch/$s_!BeMe!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b489650-9119-4154-b97c-555d5acfac1e_1590x964.png 848w, https://substackcdn.com/image/fetch/$s_!BeMe!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b489650-9119-4154-b97c-555d5acfac1e_1590x964.png 1272w, https://substackcdn.com/image/fetch/$s_!BeMe!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b489650-9119-4154-b97c-555d5acfac1e_1590x964.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BeMe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b489650-9119-4154-b97c-555d5acfac1e_1590x964.png" width="1456" height="883" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2b489650-9119-4154-b97c-555d5acfac1e_1590x964.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:883,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:417955,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.ketanbhatt.com/i/170341782?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b489650-9119-4154-b97c-555d5acfac1e_1590x964.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BeMe!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b489650-9119-4154-b97c-555d5acfac1e_1590x964.png 424w, https://substackcdn.com/image/fetch/$s_!BeMe!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b489650-9119-4154-b97c-555d5acfac1e_1590x964.png 848w, https://substackcdn.com/image/fetch/$s_!BeMe!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b489650-9119-4154-b97c-555d5acfac1e_1590x964.png 1272w, https://substackcdn.com/image/fetch/$s_!BeMe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b489650-9119-4154-b97c-555d5acfac1e_1590x964.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SvEL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1d02574-4d12-45b9-8519-b129a461db2a_1159x416.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SvEL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1d02574-4d12-45b9-8519-b129a461db2a_1159x416.png 424w, https://substackcdn.com/image/fetch/$s_!SvEL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1d02574-4d12-45b9-8519-b129a461db2a_1159x416.png 848w, https://substackcdn.com/image/fetch/$s_!SvEL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1d02574-4d12-45b9-8519-b129a461db2a_1159x416.png 1272w, https://substackcdn.com/image/fetch/$s_!SvEL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1d02574-4d12-45b9-8519-b129a461db2a_1159x416.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SvEL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1d02574-4d12-45b9-8519-b129a461db2a_1159x416.png" width="1159" height="416" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f1d02574-4d12-45b9-8519-b129a461db2a_1159x416.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:416,&quot;width&quot;:1159,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!SvEL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1d02574-4d12-45b9-8519-b129a461db2a_1159x416.png 424w, https://substackcdn.com/image/fetch/$s_!SvEL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1d02574-4d12-45b9-8519-b129a461db2a_1159x416.png 848w, https://substackcdn.com/image/fetch/$s_!SvEL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1d02574-4d12-45b9-8519-b129a461db2a_1159x416.png 1272w, https://substackcdn.com/image/fetch/$s_!SvEL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1d02574-4d12-45b9-8519-b129a461db2a_1159x416.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><em>Ingestion Requests per minute</em></figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ycFS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a33d8e9-df24-40fa-ae04-4336a352cb71_1167x419.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ycFS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a33d8e9-df24-40fa-ae04-4336a352cb71_1167x419.png 424w, https://substackcdn.com/image/fetch/$s_!ycFS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a33d8e9-df24-40fa-ae04-4336a352cb71_1167x419.png 848w, https://substackcdn.com/image/fetch/$s_!ycFS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a33d8e9-df24-40fa-ae04-4336a352cb71_1167x419.png 1272w, https://substackcdn.com/image/fetch/$s_!ycFS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a33d8e9-df24-40fa-ae04-4336a352cb71_1167x419.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ycFS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a33d8e9-df24-40fa-ae04-4336a352cb71_1167x419.png" width="1167" height="419" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5a33d8e9-df24-40fa-ae04-4336a352cb71_1167x419.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:419,&quot;width&quot;:1167,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!ycFS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a33d8e9-df24-40fa-ae04-4336a352cb71_1167x419.png 424w, https://substackcdn.com/image/fetch/$s_!ycFS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a33d8e9-df24-40fa-ae04-4336a352cb71_1167x419.png 848w, https://substackcdn.com/image/fetch/$s_!ycFS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a33d8e9-df24-40fa-ae04-4336a352cb71_1167x419.png 1272w, https://substackcdn.com/image/fetch/$s_!ycFS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a33d8e9-df24-40fa-ae04-4336a352cb71_1167x419.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Search Requests per minute</figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!N5vE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32c83edb-26af-4ea2-bcfe-880ad50e785b_1166x607.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!N5vE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32c83edb-26af-4ea2-bcfe-880ad50e785b_1166x607.png 424w, https://substackcdn.com/image/fetch/$s_!N5vE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32c83edb-26af-4ea2-bcfe-880ad50e785b_1166x607.png 848w, https://substackcdn.com/image/fetch/$s_!N5vE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32c83edb-26af-4ea2-bcfe-880ad50e785b_1166x607.png 1272w, https://substackcdn.com/image/fetch/$s_!N5vE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32c83edb-26af-4ea2-bcfe-880ad50e785b_1166x607.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!N5vE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32c83edb-26af-4ea2-bcfe-880ad50e785b_1166x607.png" width="1166" height="607" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/32c83edb-26af-4ea2-bcfe-880ad50e785b_1166x607.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:607,&quot;width&quot;:1166,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!N5vE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32c83edb-26af-4ea2-bcfe-880ad50e785b_1166x607.png 424w, https://substackcdn.com/image/fetch/$s_!N5vE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32c83edb-26af-4ea2-bcfe-880ad50e785b_1166x607.png 848w, https://substackcdn.com/image/fetch/$s_!N5vE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32c83edb-26af-4ea2-bcfe-880ad50e785b_1166x607.png 1272w, https://substackcdn.com/image/fetch/$s_!N5vE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32c83edb-26af-4ea2-bcfe-880ad50e785b_1166x607.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><em>Vector Search Latency</em></figcaption></figure></div><p><strong>Note</strong>: The actual number of embeddings we have is ~600 Million as we are continually experimenting with different embedding models or chunking strategies. The cost here is for this total architecture: 600 Million embeddings, replicated once, with raw data and metadata. Since we don&#8217;t currently create an index for ANN, which usually requires as much memory/disk space as the size of embeddings, we use 300 Million as the number for a fair cost comparison with other vendors.</p><p>Compared to other vendors, this setup costs us at least 3 times less (comparing just the unblended cost for fairness). For example: <a href="https://cloud.qdrant.io/calculator?provider=aws&amp;region=us-east-1&amp;vectors=300000000&amp;dimension=1024&amp;storageOptimized=false&amp;replicas=2&amp;quantization=None&amp;storageRAMCachePercentage=35">Qdrant&#8217;s estimate is ~$30k/month</a> (not accounting for capacity needed for experimentation). Pinecone would be even costlier.</p><h2><strong>Specifics of our Architecture</strong></h2><ul><li><p>Architecture</p><ul><li><p>Data nodes: 9 x i4g.4xlarge</p></li><li><p>Client/Master nodes: 3 each x c6g.xlarge</p></li></ul></li><li><p>Index</p><ul><li><p>Embeddings are partitioned into 15 indexes, each with 30 primary shards and a replica.</p></li><li><p>Refresh Interval: 2 seconds</p></li><li><p>The embeddings field is a dense_vector with index=False.</p></li></ul></li><li><p>Ingestion</p><ul><li><p>Ingest on every create/update.</p></li><li><p>Average request processes 3 contents (articles, file etc.), which are chunked and bulk-ingested.</p></li><li><p>In the future, we can buffer content updates for a few seconds before processing them to improve ingestion efficiency if needed.</p></li></ul></li><li><p>Querying</p><ul><li><p>Exact vector-search using script_score and <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-script-score-query#vector-functions">functions for vector fields</a>.</p></li><li><p>Queries use filters for locale, content that should be visible/invisible to a user etc.</p></li></ul></li></ul><h2><strong>Business Impact</strong></h2><p>The migration from S3/in-memory to Elasticsearch drove customer-visible response time improvements:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!H1xT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc009b024-78b8-423c-b556-fa4d420a222a_1024x335.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!H1xT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc009b024-78b8-423c-b556-fa4d420a222a_1024x335.png 424w, https://substackcdn.com/image/fetch/$s_!H1xT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc009b024-78b8-423c-b556-fa4d420a222a_1024x335.png 848w, https://substackcdn.com/image/fetch/$s_!H1xT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc009b024-78b8-423c-b556-fa4d420a222a_1024x335.png 1272w, https://substackcdn.com/image/fetch/$s_!H1xT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc009b024-78b8-423c-b556-fa4d420a222a_1024x335.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!H1xT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc009b024-78b8-423c-b556-fa4d420a222a_1024x335.png" width="1024" height="335" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c009b024-78b8-423c-b556-fa4d420a222a_1024x335.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:335,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!H1xT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc009b024-78b8-423c-b556-fa4d420a222a_1024x335.png 424w, https://substackcdn.com/image/fetch/$s_!H1xT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc009b024-78b8-423c-b556-fa4d420a222a_1024x335.png 848w, https://substackcdn.com/image/fetch/$s_!H1xT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc009b024-78b8-423c-b556-fa4d420a222a_1024x335.png 1272w, https://substackcdn.com/image/fetch/$s_!H1xT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc009b024-78b8-423c-b556-fa4d420a222a_1024x335.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The speedup was acutely felt by the customers who entrusted Fin with more of their content, where the response times were slashed by half.</figcaption></figure></div><p>The system has also absorbed 10x increases in customer data volume and query traffic without architectural change, cluster downtime, or significant operational incident. Most importantly, all operational practices&#8212;from tuning, upgrades, snapshotting, disaster recovery, to scaling&#8212;were well understood from prior ES experience.</p><h2><strong>Conclusion: Practical Takeaways</strong></h2><p>When picking tools or infrastructure&#8212;whether it&#8217;s for vector search, databases, or anything else&#8212;these principles worked well for us:</p><ul><li><p><strong>Leverage what your team already knows.</strong> Familiar tools let you move faster, reduce onboarding, incidents, and painful outages. If your team already has expertise in a given technology, that&#8217;s a huge advantage.</p></li><li><p><strong>Expect trade-offs</strong>. Cutting-edge technology (like vector databases) might promise slightly lower latency or higher throughput, but come with new operational risks, documentation gaps, and complex migrations. Make sure those trade-offs are worth it for your business.</p></li><li><p><strong>Don&#8217;t underestimate the costs outside infrastructure</strong>. The time spent learning, debugging, and supporting a new system often outweighs the dollar savings or benchmark wins.</p></li></ul><p>Our experience is not a call to avoid new technology that&#8217;s better suited for your situation, but to reassure you that a &#8220;boring&#8221; choice might not necessarily be the wrong one.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.ketanbhatt.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Ketan's Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Catch Up: Lessons Senior Engineers Know about ORMs]]></title><description><![CDATA[Think you know your ORM? Discover the subtle bugs, race conditions, and performance pitfalls many engineers miss. Learn expert strategies for Rails, Django, and Laravel!]]></description><link>https://www.ketanbhatt.com/p/orm-best-practices-for-senior-engineers</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/orm-best-practices-for-senior-engineers</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Fri, 09 May 2025 17:39:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f107e32-fb85-471f-ad6c-90ba9dd1c555_1218x828.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://www.ketanbhatt.com/about">We all are Senior Engineers now</a>. Many of us weren&#8217;t ready for it. Let&#8217;s play catchup and start learning things a senior engineer knows. <br>In this post, I share lessons about avoiding race conditions by getting to know your ORMs better. I use code examples from Rails but the concepts are applicable to most frameworks and ORMs.</p><h2>Learn when ORMs actually evaluate queries</h2><p>Look at the Rails code below and choose the right option: </p><pre><code>engineers = Employee.where(role: 'engineer').all

if engineers.count &gt; 0
    send_welcome_email(employee_ids: engineers.map(&amp;:id))
end</code></pre><div class="poll-embed" data-attrs="{&quot;id&quot;:313164}" data-component-name="PollToDOM"></div><p>You can skip to the next section if you answered correctly<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></p><p>When you construct a query to retrieve objects, ORMs do not immediately run a query on the database. So, the below line of Rails code has no effect:</p><pre><code>engineers = Employee.where(role: 'engineer').all</code></pre><p><strong>This code returns a &#8220;Constructed Query&#8221; object. This is an `ActiveRecord::Relation` in Rails, a `QuerySet` in Django etc.</strong> (readers with PHP experience, help me with the Laravel equivalent). ORMs allow you to chain, filter, slice these &#8220;Constructed Query&#8221; objects further without running a database query until you force the query to be evaluated. This evaluation is forced when you actually try to access the rows. For example, here are some ways to force an evaluation:</p><pre><code># convert the object to a list
engineers.to_a

# print the rows
puts "#{engineers}"

# iterate over the rows
engineers.each do { |engineer| do_something(engineer) }</code></pre><p>The takeaway:</p><div class="pullquote"><p><strong>&#10024; ORMs are lazy &#10024;</strong></p></div><p>Most methods that fetch multiple records don&#8217;t evaluate until forced. Most methods that fetch a single value/record evaluate immediately. Please read the documentation for your framework to learn more: <a href="https://guides.rubyonrails.org/active_record_querying.html#retrieving-objects-from-the-database">Rails</a>, <a href="https://docs.djangoproject.com/en/5.2/ref/models/querysets/#when-querysets-are-evaluated">Django</a>.</p><p>Back to the <code>send_welcome_email</code> example: the correct answer is 2 queries. </p><pre><code>engineers = Employee.where(role: 'engineer').all

# 1st query: COUNT(*)
if engineers.count &gt; 0
    # 2nd query: SELECT *
    send_welcome_email(employee_ids: engineers.map(&amp;:id))
end</code></pre><p>This knowledge about ORMs being lazy is not crucial because you can reduce the number of queries that hit your database, <strong>but because you can inadvertently create subtle bugs if you don&#8217;t know this</strong>. Here are two examples that I have seen in production:</p><h4>1. No welcome emails</h4><p>In our <code>send_welcome_email</code> example, if a concurrent request updated the role for all engineers AFTER you check the count, but BEFORE you fetch IDs, you will send an empty list to `send_welcome_email`. This could have unintended effects: maybe the method doesn&#8217;t handle empty lists.</p><pre><code><code>engineers = Employee.where(role: 'engineer').all

if engineers.count &gt; 0
    # --&gt; concurrent requests updated all role=engineer to role=agent
    # --&gt; EXCEPTION! send_welcome_email doesn't handle empty list
    send_welcome_email(employee_ids: engineers.map(&amp;:id))
end</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cKeM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F735e5508-2a70-4a9f-a554-4ce68234b41a_793x704.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cKeM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F735e5508-2a70-4a9f-a554-4ce68234b41a_793x704.png 424w, https://substackcdn.com/image/fetch/$s_!cKeM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F735e5508-2a70-4a9f-a554-4ce68234b41a_793x704.png 848w, https://substackcdn.com/image/fetch/$s_!cKeM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F735e5508-2a70-4a9f-a554-4ce68234b41a_793x704.png 1272w, https://substackcdn.com/image/fetch/$s_!cKeM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F735e5508-2a70-4a9f-a554-4ce68234b41a_793x704.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cKeM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F735e5508-2a70-4a9f-a554-4ce68234b41a_793x704.png" width="605" height="537.0996216897856" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/735e5508-2a70-4a9f-a554-4ce68234b41a_793x704.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:704,&quot;width&quot;:793,&quot;resizeWidth&quot;:605,&quot;bytes&quot;:63610,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.ketanbhatt.com/i/161862987?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F735e5508-2a70-4a9f-a554-4ce68234b41a_793x704.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cKeM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F735e5508-2a70-4a9f-a554-4ce68234b41a_793x704.png 424w, https://substackcdn.com/image/fetch/$s_!cKeM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F735e5508-2a70-4a9f-a554-4ce68234b41a_793x704.png 848w, https://substackcdn.com/image/fetch/$s_!cKeM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F735e5508-2a70-4a9f-a554-4ce68234b41a_793x704.png 1272w, https://substackcdn.com/image/fetch/$s_!cKeM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F735e5508-2a70-4a9f-a554-4ce68234b41a_793x704.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I like to remember this class of bugs by the acronym: <a href="https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use">TOCTTOU</a>. More technically, this is the <a href="https://www.ketanbhatt.com/i/159636816/phantoms">Phantom effect</a>.</p><h4>2. No farewell emails</h4><p>Look at the below code, and see if you can find a problem with it:</p><pre><code># Select all undeleted employees who exited before today
past_employees = Employee.where(deleted: false, exit_ts: ..Time.now)

past_employees.update_all(deleted: true)
send_farewell_email(employee_ids: past_employees.map(&amp;:id))</code></pre><p>We can see now that no employee will be receiving a farewell email. That&#8217;s because <code>past_employees</code> is not a fixed list of records. Let me explain: </p><pre><code>past_employees = Employee.where(deleted: false, exit_ts: ..Time.now)</code></pre><p>This doesn't immediately fetch records from the database&#8212;<strong>it returns an </strong><code>ActiveRecord::Relation</code>, which is lazy and only evaluated when needed, like during <code>.each</code>, <code>.to_a</code>, <code>.map</code> etc.</p><p>Then, when we do this:</p><pre><code>past_employees.update_all(deleted: true)</code></pre><p>This performs the <strong>update directly in the database</strong>, setting <code>deleted: true</code> for all matching employees at that moment.</p><pre><code>send_farewell_email(employee_ids: past_employees.map(&amp;:id))</code></pre><p>This is the point when the <code>past_employees</code> relation is actually evaluated&#8212;but it now <strong>no longer matches any records</strong>, because they&#8217;ve all just been marked <code>deleted: true</code>. It is easy to make such mistakes if you unaware of ORM&#8217;s laziness.</p><p>One solution here would be to materialise the relation before we update the records, like:</p><pre><code>past_employees = Employee.where(deleted: false, exit_ts: ..Time.now).to_a</code></pre><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.ketanbhatt.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">If you enjoy finding bugs, you will love my posts! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><h2>Learn what your ORM does on <code>.save()</code></h2><p>Consider this scenario and choose the right option:</p><pre><code>* Fetch an employee record
* Change any column's value. Eg: employee.salary = 50000
* employee.save()</code></pre><div class="poll-embed" data-attrs="{&quot;id&quot;:313387}" data-component-name="PollToDOM"></div><p>There is no one correct answer, which makes it more dangerous! The answer will depend on your framework/ORM:</p><ul><li><p>Rails tracks the fields you change and only updates them by default.</p></li><li><p>Django&#8217;s default ORM updates all the fields by default (you can <a href="https://docs.djangoproject.com/en/5.2/ref/models/instances/#specifying-which-fields-to-save">specify which fields to update</a> if you want)</p><ul><li><p><a href="https://docs.sqlalchemy.org/en/20/tutorial/orm_data_manipulation.html#updating-orm-objects-using-the-unit-of-work-pattern">SQLAlchemy</a>, although, has the same behaviour as Rails</p></li></ul></li><li><p>Laravel has the same behaviour as Rails.</p></li></ul><p>My take is that Rails&#8217; default is &#8220;<a href="https://en.wikipedia.org/wiki/Principle_of_least_astonishment">less surprising</a>&#8221;, and makes for fewer headaches in a large codebase. Imagine your team built an admin UI that allows employees to change their name. This UI is backed by some code like this:</p><pre><code>employee = Employee.find(id: x)
employee.name = new_name
employee.save</code></pre><p>Unknown to you, the finance team has built a UI for Human Resource personnel to update an employee&#8217;s salary. That code looks like:</p><pre><code>employee = Employee.find(id: x)
employee.salary = new_salary
employee.save</code></pre><p>Now pretend that we serve a request for each of these UIs concurrently. Both the requests will receive the same data for the Employee, change the fields and run the <code>.save()</code> request. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GfRb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05dd4f95-1551-4d01-8d01-18c5f8aa1aa9_791x697.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GfRb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05dd4f95-1551-4d01-8d01-18c5f8aa1aa9_791x697.png 424w, https://substackcdn.com/image/fetch/$s_!GfRb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05dd4f95-1551-4d01-8d01-18c5f8aa1aa9_791x697.png 848w, https://substackcdn.com/image/fetch/$s_!GfRb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05dd4f95-1551-4d01-8d01-18c5f8aa1aa9_791x697.png 1272w, https://substackcdn.com/image/fetch/$s_!GfRb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05dd4f95-1551-4d01-8d01-18c5f8aa1aa9_791x697.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GfRb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05dd4f95-1551-4d01-8d01-18c5f8aa1aa9_791x697.png" width="596" height="525.173198482933" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/05dd4f95-1551-4d01-8d01-18c5f8aa1aa9_791x697.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:697,&quot;width&quot;:791,&quot;resizeWidth&quot;:596,&quot;bytes&quot;:68190,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.ketanbhatt.com/i/161862987?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05dd4f95-1551-4d01-8d01-18c5f8aa1aa9_791x697.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!GfRb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05dd4f95-1551-4d01-8d01-18c5f8aa1aa9_791x697.png 424w, https://substackcdn.com/image/fetch/$s_!GfRb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05dd4f95-1551-4d01-8d01-18c5f8aa1aa9_791x697.png 848w, https://substackcdn.com/image/fetch/$s_!GfRb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05dd4f95-1551-4d01-8d01-18c5f8aa1aa9_791x697.png 1272w, https://substackcdn.com/image/fetch/$s_!GfRb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05dd4f95-1551-4d01-8d01-18c5f8aa1aa9_791x697.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>With Rails, there is no conflict by default: each request updates the field they changed.<br>With Django&#8217;s default, depending on the update that hit the database last, the employee will either have the new name (but old salary) or the new salary (but old name). This defect is classed as <a href="https://www.ketanbhatt.com/i/159636816/lost-update">Lost Update</a> (or TOCTTOU). </p><p>The takeaway</p><div class="pullquote"><p>&#10024; Know your ORM&#8217;s save behaviour &#10024;</p></div><p>Django&#8217;s default isn&#8217;t wrong, it just means that one needs to be more careful when updating whole objects. Wherever possible, use <a href="https://docs.djangoproject.com/en/5.2/ref/models/instances/#specifying-which-fields-to-save">update_fields</a> to only send the changed fields to your database. Also, race conditions will need to be top-of-mind for you every time. You can protect your important writes with locks, but you might end up with a lot of them in a large codebase. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ktQD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f107e32-fb85-471f-ad6c-90ba9dd1c555_1218x828.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ktQD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f107e32-fb85-471f-ad6c-90ba9dd1c555_1218x828.png 424w, https://substackcdn.com/image/fetch/$s_!ktQD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f107e32-fb85-471f-ad6c-90ba9dd1c555_1218x828.png 848w, https://substackcdn.com/image/fetch/$s_!ktQD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f107e32-fb85-471f-ad6c-90ba9dd1c555_1218x828.png 1272w, https://substackcdn.com/image/fetch/$s_!ktQD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f107e32-fb85-471f-ad6c-90ba9dd1c555_1218x828.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ktQD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f107e32-fb85-471f-ad6c-90ba9dd1c555_1218x828.png" width="462" height="314.0689655172414" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0f107e32-fb85-471f-ad6c-90ba9dd1c555_1218x828.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:828,&quot;width&quot;:1218,&quot;resizeWidth&quot;:462,&quot;bytes&quot;:1146442,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.ketanbhatt.com/i/161862987?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f107e32-fb85-471f-ad6c-90ba9dd1c555_1218x828.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ktQD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f107e32-fb85-471f-ad6c-90ba9dd1c555_1218x828.png 424w, https://substackcdn.com/image/fetch/$s_!ktQD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f107e32-fb85-471f-ad6c-90ba9dd1c555_1218x828.png 848w, https://substackcdn.com/image/fetch/$s_!ktQD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f107e32-fb85-471f-ad6c-90ba9dd1c555_1218x828.png 1272w, https://substackcdn.com/image/fetch/$s_!ktQD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f107e32-fb85-471f-ad6c-90ba9dd1c555_1218x828.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>Learn when you can avoid locks</h2><p>Locks are convenient but shouldn&#8217;t be used willy-nilly. Locking doesn&#8217;t come for free. There are other ways to make updates safely without race conditions. Let&#8217;s look at an example.</p><p>You run a gaming website and maintain a high score for each game. Your website is busy and you get many concurrent requests for updating the high score. After a few lost updates, you started using locks to prevent it from happening again:</p><pre><code>def update_high_score(game, new_high_score)
    game.with_lock("FOR UPDATE") do
        return if game.high_score &lt; new_high_score

        game.update!(high_score: new_high_score)
    end
end</code></pre><div class="poll-embed" data-attrs="{&quot;id&quot;:313578}" data-component-name="PollToDOM"></div><p>This is called a &#8220;<a href="https://stackoverflow.com/a/129397">Pessimistic Lock</a>&#8221;: you prevent any other process to update the record while you are updating it. Pessimistic locks are simple to understand but can cause deadlocks or slow down your application (because requests are waiting for the lock to be released). <br>You can flip this to an Optimistic Lock by checking for the state as you commit your change. Optimistic locks are even more useful in cases where we don&#8217;t need to retry a failed update: if there is already a higher score than the one you were updating, you don&#8217;t need to retry. </p><p>Our <code>update_high_score</code> method can be changed to use an Optimistic Locking strategy called a &#8220;Conditional Update&#8221;. That way, we can remove the lock and move the condition &#8220;update only if the new high score is greater than the current one&#8221; to the database directly: </p><pre><code><code>def update_high_score(game, new_high_score)
    game.class.where(id: game.id).where(
        "high_score &lt; #{new_high_score}"
    ).update_all(
        "high_score = #{new_high_score}"
    )
end</code></code></pre><p>The takeaway</p><div class="pullquote"><p>&#10024; Embrace Optimism &#10024;</p></div><p>If you are not used to them, optimistic locks can seem backward. Pessimistic locks are simpler, you lock the row and do what you want. But once you get the hang of it, you will start seeing it like solving a puzzle and enjoy changing your locks where it makes sense. Most databases support conditional updates. For example: <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ConditionExpressions.html#Expressions.ConditionExpressions.SimpleComparisons">DynamoDB</a>.</p><h4>Atomic updates</h4><p>This is not an example for Optimistic locking, but I have sometimes seen locks being used to increment values when no locking is required. It seems obvious to use some atomic operations in cache like databases (like <a href="https://redis.io/docs/latest/commands/incr/">Redis</a> or Memcached), but might not be the first thing that comes to mind for non-cache databases even though most of them support atomic operations.</p><p>In our gaming website example, if you want to increment the number of times a game has been played, you can handle race conditions using a Pessimistic lock:</p><pre><code>def increment_played_count(game)
    game.with_lock("FOR UPDATE") do
        game.played_count += 1
        game.save
    end
end</code></pre><p>But you can also just increment the value in the database directly by running an atomic update:</p><pre><code>def increment_played_count(game)
    game.class.where(id: game.id).update_all(
        "played_count = played_count + 1"
    )
end</code></pre><p></p><p>Fin.</p><p></p><p>P.S. - If you enjoy reading about race conditions and how to cause subtle bugs, I wrote a post about it that you will like: </p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;8082f9d7-582b-4341-8309-04a93537bb19&quot;,&quot;caption&quot;:&quot;I came across defects caused by race conditions that might occur when we work with databases, while reading Designing Data-Intensive Applications (DDIA), by Martin Kleppmann. It turns out that if we aren&#8217;t careful, concurrent transactions can cause a lot of headaches, to say the least. Finding it easy to forget or confuse these defects for each other, I&#8230;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Race Conditions/Concurrency Defects in Databases: A Catalogue&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:30722970,&quot;name&quot;:&quot;Ketan Bhatt&quot;,&quot;bio&quot;:&quot;Staff Product Engineer, AI Group @ Intercom | Loves commenting \&quot;have you thought about concurrency\&quot; in Pull Requests&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6ecabb4e-fed3-4d80-8892-de3c4696618d_3072x3072.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2020-07-03T00:00:00.000Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa09c283b-3077-4705-8c67-fdd2739116e7_590x305.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.ketanbhatt.com/p/db-concurrency-defects&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:159636816,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Ketan's Newsletter&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e37e6d-f289-4a21-95bd-008a89277566_426x426.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Correct answer: 2 queries</p></div></div>]]></content:encoded></item><item><title><![CDATA[Avoid Common Elasticsearch Pitfalls]]></title><description><![CDATA[Useful Tips from building Conversations' Search at Intercom]]></description><link>https://www.ketanbhatt.com/p/building-search-with-elasticsearch-at-intercom</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/building-search-with-elasticsearch-at-intercom</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Thu, 25 Jan 2024 00:00:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f15f12f7-276d-49ac-866c-201d58dc93e5_1467x769.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello. Thanks for arriving at this page, even though there is no mention of LLMs and ChatGPT here.</p><p>I spent my last two years in Intercom working with the <a href="https://www.intercom.com/blog/core-technologies-team/">Core Technologies team</a>, with Elasticsearch being the core technology I focussed on. As part of my work, I got to build and consult other teams building search experiences in the Intercom product. If you use Intercom, and you have searched for conversations, you have come across my work. To get an idea of what I helped build, this article about Intercom&#8217;s Inbox Search is useful: <a href="https://www.intercom.com/help/en/articles/6516006-inbox-search-and-filter">Inbox search and filter</a>.</p><p>I have now moved on to the AI Group in Intercom, but I wanted to note down learnings from my time building these search experiences. In addition to other people finding these useful, I find myself coming back to these articles when I need to in the future.</p><p>Even though we use Elasticsearch at Intercom, the advice here will hold for Opensearch too.</p><h2>Start with a contract</h2><p>This is the most important learning. <strong>Write down the cases that you are and aren&#8217;t covering.</strong></p><p>In most areas of building software, you start with functional and non-functional requirements. If you are building a webpage, you have a design you have to implement. If you are building the backend for a website, you first agree on the API that you will use to interact with the frontend.</p><p>With search interfaces, I have found that the requirements tend to be more hand-wavy. On the surface, the goal is easy: The results should be relevant. But how is &#8220;relevance&#8221; defined? As we start digging, we start defining &#8220;relevance&#8221; for our use-case:</p><ul><li><p>Should we support spelling errors?</p></li><li><p>How should we handle special characters?</p></li><li><p>For a multi-term input, should results where the terms exist in the same order as the input be ranked higher?</p></li><li><p>And how much weight should the &#8220;age&#8221; of a result play in the ordering?</p></li></ul><p>I enjoy this process of figuring out what&#8217;s important for the user. It is crucial to note down the conclusions you arrive at after disambiguating the requirements. You might have to make tradeoffs as you build the experience. <strong>The contract helps you document the tradeoffs you have made and why.</strong></p><p>Once you can see all the requirements together, you also have a better chance at designing your mapping and queries to support the requirements in the best manner.</p><h4>Add tests for the contract</h4><p>I have found that it is easy to cause regressions by seemingly small changes to the query/mapping. Simple tests that ingest a few dummy documents and confirm that the results are as expected can do wonders for reducing these regressions and allowing you to iterate with speed.</p><h2>Understand the Text Analysis pipeline</h2><p>Elasticsearch performs text analysis when indexing or searching <code>text</code> fields. Based on your requirements, you might have to tune the analysis pipeline for your specific needs: Should you remove stop words? Should you tokenize on whitespace? Should you convert your tokens to n-grams?</p><p>Elasticsearch has a helpful section in their documentation about Text Analysis: <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis.html">Text analysis</a>. My advice is to get familiar with this part of the documentation, specially <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer-anatomy.html">Anatomy of an analyzer</a>. I created a simple diagram to remind me of the concept. This is how text analysis works:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!11K9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30a83ba7-828d-4e6a-a7c2-a659a223f69c_590x227.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!11K9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30a83ba7-828d-4e6a-a7c2-a659a223f69c_590x227.png 424w, https://substackcdn.com/image/fetch/$s_!11K9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30a83ba7-828d-4e6a-a7c2-a659a223f69c_590x227.png 848w, https://substackcdn.com/image/fetch/$s_!11K9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30a83ba7-828d-4e6a-a7c2-a659a223f69c_590x227.png 1272w, https://substackcdn.com/image/fetch/$s_!11K9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30a83ba7-828d-4e6a-a7c2-a659a223f69c_590x227.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!11K9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30a83ba7-828d-4e6a-a7c2-a659a223f69c_590x227.png" width="590" height="227" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/30a83ba7-828d-4e6a-a7c2-a659a223f69c_590x227.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:227,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Summary of Text Analysis&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Summary of Text Analysis" title="Summary of Text Analysis" srcset="https://substackcdn.com/image/fetch/$s_!11K9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30a83ba7-828d-4e6a-a7c2-a659a223f69c_590x227.png 424w, https://substackcdn.com/image/fetch/$s_!11K9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30a83ba7-828d-4e6a-a7c2-a659a223f69c_590x227.png 848w, https://substackcdn.com/image/fetch/$s_!11K9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30a83ba7-828d-4e6a-a7c2-a659a223f69c_590x227.png 1272w, https://substackcdn.com/image/fetch/$s_!11K9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30a83ba7-828d-4e6a-a7c2-a659a223f69c_590x227.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Given an input, this is how the output will be generated.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MCXt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab773cfc-eaf9-484b-87d0-f5698a2fc26b_590x109.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MCXt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab773cfc-eaf9-484b-87d0-f5698a2fc26b_590x109.png 424w, https://substackcdn.com/image/fetch/$s_!MCXt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab773cfc-eaf9-484b-87d0-f5698a2fc26b_590x109.png 848w, https://substackcdn.com/image/fetch/$s_!MCXt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab773cfc-eaf9-484b-87d0-f5698a2fc26b_590x109.png 1272w, https://substackcdn.com/image/fetch/$s_!MCXt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab773cfc-eaf9-484b-87d0-f5698a2fc26b_590x109.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MCXt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab773cfc-eaf9-484b-87d0-f5698a2fc26b_590x109.png" width="590" height="109" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ab773cfc-eaf9-484b-87d0-f5698a2fc26b_590x109.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:109,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Example of how text analysis works&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Example of how text analysis works" title="Example of how text analysis works" srcset="https://substackcdn.com/image/fetch/$s_!MCXt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab773cfc-eaf9-484b-87d0-f5698a2fc26b_590x109.png 424w, https://substackcdn.com/image/fetch/$s_!MCXt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab773cfc-eaf9-484b-87d0-f5698a2fc26b_590x109.png 848w, https://substackcdn.com/image/fetch/$s_!MCXt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab773cfc-eaf9-484b-87d0-f5698a2fc26b_590x109.png 1272w, https://substackcdn.com/image/fetch/$s_!MCXt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab773cfc-eaf9-484b-87d0-f5698a2fc26b_590x109.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Knowing how the text analysis works will help you decide the schema of your fields and writing a search query that works for you. It also helps you debug your queries and search results when someone complains in the future (<em>&#8220;why is this document not being returned&#8221;</em>).</p><h2>Do you really need to support Typos?</h2><p>When we think of supporting misspelled words, we think it will help people find <code>apple</code> when they typed <code>aple</code> by mistake. But it also means that you could return <code>bat</code> when someone actually needed <code>bar</code>. Typo tolerance can also return irrelevant results if the user input can be a number: So if someone searches for <code>98765</code>, they probably don&#8217;t want <code>88765</code>.</p><p>Supporting typos is useful in many cases but it comes with a cost: queries handling misspellings are usually slower to run and can lead to lower quality of results.</p><p><strong>My suggestion is to avoid typo tolerance unless your use-case would really benefit from it.</strong> If you do have to support typos, consider setting the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#fuzziness">fuzziness</a> such that only mistakes in longer words are supported, as people tend to make fewer mistakes in shorter words.</p><h2>Don&#8217;t be afraid to use multiple fields</h2><p>As you go over the different cases you uncovered while creating the contract, it will become clear that ingesting the data into a single field might not be enough.</p><p>For example: If a user searches for <code>running</code>, you want to return results containing <code>run</code>. But when the user searches for <code>"running"</code>, you want to return results that contain the word <code>running</code> exactly. This could be difficult to do using a single field.</p><p>Usually something I have seen working is having one field that indexes your text as is (without even removing stop words), and another field analyzed for similarity matching (whatever similarity means for you: removing stop words, stemming, etc.).</p><h2>Not all text is the same</h2><p>Your user might be expecting different behaviour based on the text they are searching over. Smaller text fields (like blog titles, movie titles) require different analysis than longer text fields (like blog content, movie synopsis).</p><p>For example: if the user knows they have a title like <code>The legend of Tarzan</code>, and they input <code>the</code>, they would expect Tarzan to be in the results. But if you removed stop words during the text analysis, the legend of Tarzan will remain a myth. This can be frustrating for the user.</p><p><strong>For searching over smaller text fields, a simple WYSIWYG approach might be good enough and it might make sense to skip most of the text analysis</strong>: skip stemming, removing stop words, removing punctuations etc. Also consider using an edge-n-gram Index analyser and standard Search analyser for such a use-case. (Elasticsearch usually advises against using different analysers for indexing and search, but mentions this specific case as a good candidate for going against the suggestion: <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-analyzer.html#:~:text=Sometimes%2C%20though%2C%20it%20can%20make%20sense%20to%20use%20a%20different%20analyzer%20at%20search%20time%2C%20such%20as%20when%20using%20the%20edge_ngram%20tokenizer%20for%20autocomplete">Here</a>).</p><p>For longer text fields, running a more involved text analysis can improve results quality. And if a user&#8217;s search for <code>the</code> doesn&#8217;t return all the blogs that contain the word <code>the</code> in them, the behaviour is still intuitive for them.</p><h2>Increasing relevance by using multiple clauses</h2><p>For improving user experience, you will want to rank more relevant results higher. For example: for a multi-term input, you may want to show those results first where all the terms are present in the same order as in the input.</p><p>You can use <code>should</code> clauses in your query to add weight to more relevant results. For this particular example, you can add a <code>should</code> clause with a <code>phrase</code> match to boost the relevance of documents that contain the words in the same order.</p><h2>When you can&#8217;t depend on score</h2><p>If you can sort results by score, things become easier. Your search query could still match some results with low-relevance, but that might be okay because they would be buried deep behind more relevant results.</p><p>But some use-cases require results to be sorted by other fields (frequently, <code>created_at</code>). For example: while searching for blog titles, it will be easier for the user to go through all the results if they are sorted by their published date. But if your query returns both highly and somewhat-relevant blogs, and the results were sorted by <code>published date</code>, you might end up showing irrelevant (but recent) results at the top!</p><p>In such cases, you want to tighten your query to only return results that are perfect matches for the user input. This allows for the results to be sorted by any field and still be relevant for the user.</p><h3>Minimum Should Match</h3><p>One of the ways of only matching relevant results is by using <code>minimum_should_match</code> and using a value like <code>2&lt;90%</code> which would only return those documents where most of the words from the input are present. Documentation: <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-minimum-should-match.html">minimum_should_match parameter</a>.</p><p>Hope it helps &#128075;</p>]]></content:encoded></item><item><title><![CDATA[Race Conditions/Concurrency Defects in Databases: A Catalogue]]></title><description><![CDATA[Or, how to create bugs that are hard to find]]></description><link>https://www.ketanbhatt.com/p/db-concurrency-defects</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/db-concurrency-defects</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Fri, 03 Jul 2020 00:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa09c283b-3077-4705-8c67-fdd2739116e7_590x305.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I came across defects caused by race conditions that might occur when we work with databases, while reading <a href="https://www.goodreads.com/book/show/23463279-designing-data-intensive-applications">Designing Data-Intensive Applications (DDIA)</a>, by <a href="https://martin.kleppmann.com/">Martin Kleppmann</a>. It turns out that if we aren&#8217;t careful, concurrent transactions can cause a lot of headaches, to say the least. Finding it easy to forget or confuse these defects for each other, I decided to catalogue them here so they might help others and future me.</p><p>I have derived a bulk of these learnings and examples from DDIA itself, and all the credit for that work goes to Martin Kleppmann.</p><h3>First, some basics</h3><p>Before we start, I want to quickly go over some basics so that a wider audience can follow along more easily.</p><ul><li><p><strong>Transaction</strong></p><ul><li><p>A &#8220;transaction&#8221; groups several reads and writes together into one logical unit. Conceptually, all the reads and writes in a transaction are executed as one operation: either the entire transaction succeeds (commits successfully) or it fails (aborts and rolls back).</p></li><li><p>For a longer explanation, this might be helpful: <a href="https://stackoverflow.com/questions/974596/what-is-a-database-transaction">What is a database transaction?</a></p></li></ul></li><li><p><strong>Commit and Rollback</strong></p><ul><li><p>From <a href="http://itdoc.hitachi.co.jp/manuals/3020/3020635100e/W3510158.HTM">this doc</a>:</p><blockquote><p>The process of placing into effect in the database the updates made by a transaction is called commit. The process of invalidating the updates made by a transaction is called rollback</p></blockquote></li></ul></li><li><p><strong>Race Conditions or Concurrency Defects</strong></p><ul><li><p>Roughly, if two or more transactions simultaneously access (read or write) the same data, these transactions will be considered concurrent. Based on what operations these transactions perform on the data, we might run into anomalous behaviour like losing writes, or reporting incorrect data, or permanently making the data inconsistent.</p></li><li><p>To get a better idea, take a look at this example: <a href="https://en.wikipedia.org/wiki/Race_condition#Example">Race condition example</a></p></li></ul></li></ul><p>I have strictly kept the scope of this article to be an introduction to defects that can occur because of concurrency. Resolving or avoiding these defects is a larger topic that I might discuss in a later post. For now, I just wanted to make the reader aware of these problems. Knowing that a problem exists is often the first step towards dealing with it.</p><h1>Let&#8217;s begin</h1><h2>Dirty Read</h2><p><strong>Tl;dr: If a transaction can read data written by another transaction that is not yet committed (or aborted), this read is called a &#8220;Dirty Read&#8221;.</strong></p><p>Changes made by an ongoing transaction only become permanent once the transaction commits. If a Database allows other transactions to read updates made by a still uncommitted/incomplete transaction, it can lead to unintended outcomes:</p><ol><li><p>If an in-progress transaction makes several changes to the database, and dirty reads are not prevented, other concurrent transactions might be able to see some of these changes while the rest are still being made. This is confusing to users and could cause other transactions to take incorrect decisions.</p></li><li><p>A transaction can abort, requiring its writes to be rolled back. If the database allows dirty reads, another transaction might see data that was later rolled back. This can cause bugs and issues that are hard to reason about.</p></li></ol><p><br>Let's look at an example: Nancy has $1000 in her bank, split equally between two accounts:</p><ul><li><p>Account A: Her general account that she uses for receiving and spending money</p></li><li><p>Account B: Her savings account that she uses for saving money every month (she was saving for a trip to Rome, but we all know nobody is going anywhere in 2020).</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Pilu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57c586bf-bff1-4acf-b2ff-6b74c39cbdfc_590x379.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Pilu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57c586bf-bff1-4acf-b2ff-6b74c39cbdfc_590x379.png 424w, https://substackcdn.com/image/fetch/$s_!Pilu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57c586bf-bff1-4acf-b2ff-6b74c39cbdfc_590x379.png 848w, https://substackcdn.com/image/fetch/$s_!Pilu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57c586bf-bff1-4acf-b2ff-6b74c39cbdfc_590x379.png 1272w, https://substackcdn.com/image/fetch/$s_!Pilu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57c586bf-bff1-4acf-b2ff-6b74c39cbdfc_590x379.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Pilu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57c586bf-bff1-4acf-b2ff-6b74c39cbdfc_590x379.png" width="590" height="379" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/57c586bf-bff1-4acf-b2ff-6b74c39cbdfc_590x379.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:379,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Nancy's bank accounts&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Nancy's bank accounts" title="Nancy's bank accounts" srcset="https://substackcdn.com/image/fetch/$s_!Pilu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57c586bf-bff1-4acf-b2ff-6b74c39cbdfc_590x379.png 424w, https://substackcdn.com/image/fetch/$s_!Pilu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57c586bf-bff1-4acf-b2ff-6b74c39cbdfc_590x379.png 848w, https://substackcdn.com/image/fetch/$s_!Pilu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57c586bf-bff1-4acf-b2ff-6b74c39cbdfc_590x379.png 1272w, https://substackcdn.com/image/fetch/$s_!Pilu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57c586bf-bff1-4acf-b2ff-6b74c39cbdfc_590x379.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Nancy decides to transfer 100$ from Account A to Account B. The Bank makes this transfer by carrying out a transaction with two operations:</p><ol><li><p>Subtract 100$ from Account A</p></li><li><p>Add 100$ to Account B</p></li></ol><p>Let&#8217;s assume that Nancy has her Bank&#8217;s app open and she is looking at her Account balances while the transfer is happening. Thankfully, her Bank&#8217;s database prevents dirty reads. This is what she sees:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!obAX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa09c283b-3077-4705-8c67-fdd2739116e7_590x305.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!obAX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa09c283b-3077-4705-8c67-fdd2739116e7_590x305.png 424w, https://substackcdn.com/image/fetch/$s_!obAX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa09c283b-3077-4705-8c67-fdd2739116e7_590x305.png 848w, https://substackcdn.com/image/fetch/$s_!obAX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa09c283b-3077-4705-8c67-fdd2739116e7_590x305.png 1272w, https://substackcdn.com/image/fetch/$s_!obAX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa09c283b-3077-4705-8c67-fdd2739116e7_590x305.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!obAX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa09c283b-3077-4705-8c67-fdd2739116e7_590x305.png" width="590" height="305" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a09c283b-3077-4705-8c67-fdd2739116e7_590x305.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:305,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Nancy's transfer when the database prevents read skew&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Nancy's transfer when the database prevents read skew" title="Nancy's transfer when the database prevents read skew" srcset="https://substackcdn.com/image/fetch/$s_!obAX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa09c283b-3077-4705-8c67-fdd2739116e7_590x305.png 424w, https://substackcdn.com/image/fetch/$s_!obAX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa09c283b-3077-4705-8c67-fdd2739116e7_590x305.png 848w, https://substackcdn.com/image/fetch/$s_!obAX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa09c283b-3077-4705-8c67-fdd2739116e7_590x305.png 1272w, https://substackcdn.com/image/fetch/$s_!obAX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa09c283b-3077-4705-8c67-fdd2739116e7_590x305.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>While looking at the app, Nancy always only sees that data that is committed to the database, and never a partial state.</p><p>But if the database did not prevent dirty reads, this is what she could see:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!U9Du!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70d89b86-2d8b-4484-9912-73600aa0944c_590x305.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!U9Du!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70d89b86-2d8b-4484-9912-73600aa0944c_590x305.png 424w, https://substackcdn.com/image/fetch/$s_!U9Du!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70d89b86-2d8b-4484-9912-73600aa0944c_590x305.png 848w, https://substackcdn.com/image/fetch/$s_!U9Du!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70d89b86-2d8b-4484-9912-73600aa0944c_590x305.png 1272w, https://substackcdn.com/image/fetch/$s_!U9Du!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70d89b86-2d8b-4484-9912-73600aa0944c_590x305.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!U9Du!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70d89b86-2d8b-4484-9912-73600aa0944c_590x305.png" width="590" height="305" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/70d89b86-2d8b-4484-9912-73600aa0944c_590x305.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:305,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Nancy's transfer when the database does not prevent read skew&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Nancy's transfer when the database does not prevent read skew" title="Nancy's transfer when the database does not prevent read skew" srcset="https://substackcdn.com/image/fetch/$s_!U9Du!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70d89b86-2d8b-4484-9912-73600aa0944c_590x305.png 424w, https://substackcdn.com/image/fetch/$s_!U9Du!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70d89b86-2d8b-4484-9912-73600aa0944c_590x305.png 848w, https://substackcdn.com/image/fetch/$s_!U9Du!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70d89b86-2d8b-4484-9912-73600aa0944c_590x305.png 1272w, https://substackcdn.com/image/fetch/$s_!U9Du!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70d89b86-2d8b-4484-9912-73600aa0944c_590x305.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>For a moment there, she gets back an incorrect balance which is lower than what she should have. This happened because the database made the partial changes from her transfer transaction visible to Nancy&#8217;s transaction, even before it was committed.</p><p>In this particular case, there wasn&#8217;t any lasting harm done. She would see her correct balance if she were to refresh again, but not all cases would be this harmless.</p><p>Thankfully, dirty reads are prevented by default by all major databases.</p><h4>Related reads:</h4><ul><li><p><a href="https://www.postgresql.org/docs/current/transaction-iso.html#XACT-READ-COMMITTED">Read Committed in Postgresql</a></p></li><li><p><a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html#isolevel_read-committed">Read Committed in MySQL</a></p></li><li><p><a href="https://docs.mongodb.com/manual/core/read-isolation-consistency-recency/#read-uncommitted">MongoDB doesn&#8217;t prevent dirty reads by default. Check out Read Uncommitted</a></p></li></ul><h2>Dirty Write</h2><p><strong>Tl;dr: If a transaction can overwrite data written by another transaction that is not yet committed (or aborted), this read is called a &#8220;Dirty Write&#8221;.</strong></p><p>Similar to dirty reads, dirty writes happen when a transaction can access and overwrite data written by a still uncommitted/incomplete transaction. Dirty writes should be prevented because they can lead to messed up partial states caused by the intermingling of writes made by multiple transactions. These states would be difficult to reason about and could cause terrible things to happen.</p><p><br>Let's look at an example: Ginika and Ranyinudo are software engineers. Their managers (different people) want to set up a meeting with them. The meeting system makes two database writes while booking a room:</p><ol><li><p>It sets the room&#8217;s name to reflect the meeting</p></li><li><p>It adds the participant to the room</p></li></ol><p>Now, Ginika&#8217;s manager wanted to schedule a regular 1-1 with her, whereas Ranyinudo&#8217;s manager wanted to tell her that she is getting promoted (she is going to be a Senior)! They both proceed to book the same room for the same time. As these examples go, their requests for booking the room were concurrent, and their database does not prevent dirty writes. This is what happens:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tG3_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd78818-2e05-465f-a36d-e43074b8fa3f_590x302.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tG3_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd78818-2e05-465f-a36d-e43074b8fa3f_590x302.png 424w, https://substackcdn.com/image/fetch/$s_!tG3_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd78818-2e05-465f-a36d-e43074b8fa3f_590x302.png 848w, https://substackcdn.com/image/fetch/$s_!tG3_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd78818-2e05-465f-a36d-e43074b8fa3f_590x302.png 1272w, https://substackcdn.com/image/fetch/$s_!tG3_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd78818-2e05-465f-a36d-e43074b8fa3f_590x302.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tG3_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd78818-2e05-465f-a36d-e43074b8fa3f_590x302.png" width="590" height="302" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cfd78818-2e05-465f-a36d-e43074b8fa3f_590x302.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:302,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Dirty write causes intermingling of data&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Dirty write causes intermingling of data" title="Dirty write causes intermingling of data" srcset="https://substackcdn.com/image/fetch/$s_!tG3_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd78818-2e05-465f-a36d-e43074b8fa3f_590x302.png 424w, https://substackcdn.com/image/fetch/$s_!tG3_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd78818-2e05-465f-a36d-e43074b8fa3f_590x302.png 848w, https://substackcdn.com/image/fetch/$s_!tG3_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd78818-2e05-465f-a36d-e43074b8fa3f_590x302.png 1272w, https://substackcdn.com/image/fetch/$s_!tG3_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd78818-2e05-465f-a36d-e43074b8fa3f_590x302.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Because the database allows transactions to overwrite uncommitted data written by other transactions, we have reached this incorrect partial state. Ginika&#8217;s Manager overwrote the Room&#8217;s name, and Ranyinudo&#8217;s Manager overwrote the participant. In this case, disaster was averted because humans are involved, Ranyinudo&#8217;s manager told her of the database mishap and all was fine. But in the wild, this could lead to a lot problems.</p><p>Again, thankfully, dirty writes are prevented by default by all major databases.</p><h4>Is preventing dirty writes enough?</h4><p>At this point, astute readers might notice that even if dirty write was prevented, one of the transaction would still overwrite the other. And that is correct. But at least the final state would not be a partial state.</p><p>In our example, if dirty writes are prevented, the room will be set for an &#8220;Appraisal&#8221; with Ranyinudo as a participant because Ranyinudo&#8217;s transaction committed last. If Ginika&#8217;s transaction would have committed after Ranyinudo&#8217;s, the room would say &#8220;1-1&#8221; with Ginika as the participant. Notice how the final state isn&#8217;t an inter-mingled mess of these 4 writes.</p><p>Even though this works out fine for us in this case, this overwriting is still a problem. We will explore this more in <a href="https://www.ketanbhatt.com/i/159636816/lost-update">Lost Update</a>.</p><h4>Related reads:</h4><ul><li><p><a href="https://www.postgresql.org/docs/current/transaction-iso.html#XACT-READ-COMMITTED">Read Committed in Postgresql</a></p></li><li><p><a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html#isolevel_read-committed">Read Committed in MySQL</a></p></li><li><p><a href="https://docs.mongodb.com/manual/core/read-isolation-consistency-recency/#read-uncommitted">Read Uncommitted in MongoDB</a></p></li></ul><h2>Read Skew</h2><p><strong>Tl;dr: A transaction (typically, long running) may read different parts of the database at different points in time causing a Read Skew.</strong></p><p>If each query in a transaction can always see the latest committed data, some queries would read an older version of the data, whereas some would read from a newer version. This can result in inconsistencies best explained by an example.</p><p>Let&#8217;s go back to Nancy&#8217;s example where she was transferring 100$ from Account A to B. Now let&#8217;s assume that while this transfer is happening, the bank is running a scheduled daily backup of the database. This backup transaction is carried out by querying for all the account balances in the bank. Since a bank usually has a lot of accounts, the backup takes hours to complete.</p><p>In a database susceptible to Read Skew, this scenario might occur:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!L_ev!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4681b72d-2041-4f07-b789-76ae23f0e50d_590x309.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!L_ev!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4681b72d-2041-4f07-b789-76ae23f0e50d_590x309.png 424w, https://substackcdn.com/image/fetch/$s_!L_ev!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4681b72d-2041-4f07-b789-76ae23f0e50d_590x309.png 848w, https://substackcdn.com/image/fetch/$s_!L_ev!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4681b72d-2041-4f07-b789-76ae23f0e50d_590x309.png 1272w, https://substackcdn.com/image/fetch/$s_!L_ev!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4681b72d-2041-4f07-b789-76ae23f0e50d_590x309.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!L_ev!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4681b72d-2041-4f07-b789-76ae23f0e50d_590x309.png" width="590" height="309" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4681b72d-2041-4f07-b789-76ae23f0e50d_590x309.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:309,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Read skew caused during database backup&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Read skew caused during database backup" title="Read skew caused during database backup" srcset="https://substackcdn.com/image/fetch/$s_!L_ev!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4681b72d-2041-4f07-b789-76ae23f0e50d_590x309.png 424w, https://substackcdn.com/image/fetch/$s_!L_ev!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4681b72d-2041-4f07-b789-76ae23f0e50d_590x309.png 848w, https://substackcdn.com/image/fetch/$s_!L_ev!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4681b72d-2041-4f07-b789-76ae23f0e50d_590x309.png 1272w, https://substackcdn.com/image/fetch/$s_!L_ev!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4681b72d-2041-4f07-b789-76ae23f0e50d_590x309.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>According to this horrible horrible backup (we are the bank), Nancy has a total of $1100 instead of $1000. This happened because when the backup transaction read the balance in Account B, it read the updated version of the balance. Since Account A&#8217;s balance was read before the transfer, we now have an inconsistency. If this backup is needed to be restored, this inconsistency will become permanent.</p><p>Similar to this backup example, Read Skew can cause nonsensical data to be returned by analytical queries that often need to scan over a large number of rows.</p><p><strong>Unfortunately, not all databases prevent this defect by default and you should consider verifying this for your database.</strong></p><h4>Related reads:</h4><ul><li><p><a href="https://www.postgresql.org/docs/current/transaction-iso.html#XACT-REPEATABLE-READ">Postgresql doesn&#8217;t prevent this by default. Check out Repeatable Read.</a></p></li><li><p><a href="https://dev.mysql.com/doc/refman/5.6/en/innodb-transaction-isolation-levels.html#isolevel_repeatable-read">MySQL&#8217;s InnoDB engine prevents it by default. Check out Repeatable Read.</a></p></li></ul><h2>Lost Update</h2><p><strong>Tl;dr: Two or more concurrent transactions might read the same value from the database, modify it, and write it back, not including the modification made by the other concurrent transactions. This could lead to updates getting lost.</strong></p><p>Lost updates is one of the most common defect that happens due to race conditions. If you have been developing software for a few years, there is a high chance you have caused or experienced bugs because of this problem.</p><p>It is an easy defect to cause. Luckily, it is also easy to resolve in many cases. Any situation where we read a value from the database, modify it, and then set it back, could fall prey to this defect. This set of operations can be called a &#8220;read-modify-write&#8221; cycle. I like to remember it by the fun abbreviation: <a href="https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use">TOCTTOU</a>.</p><p>A few scenarios where updates can get lost are:</p><ol><li><p>Incrementing a counter</p></li><li><p>Updating an Account Balance</p></li><li><p>Updating a complex value like a JSON or a list.</p></li></ol><p><br>Without troubling Nancy further, let's look at a common example. Assume that we store a list of IDs in redis (or memcached, or any other database). There are two concurrent processes trying to add another ID to this list. Sounds like a recipe for Lost Updates:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ruo9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223813a6-9dc3-4eea-850a-a4799697454e_590x290.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ruo9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223813a6-9dc3-4eea-850a-a4799697454e_590x290.png 424w, https://substackcdn.com/image/fetch/$s_!Ruo9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223813a6-9dc3-4eea-850a-a4799697454e_590x290.png 848w, https://substackcdn.com/image/fetch/$s_!Ruo9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223813a6-9dc3-4eea-850a-a4799697454e_590x290.png 1272w, https://substackcdn.com/image/fetch/$s_!Ruo9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223813a6-9dc3-4eea-850a-a4799697454e_590x290.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ruo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223813a6-9dc3-4eea-850a-a4799697454e_590x290.png" width="590" height="290" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/223813a6-9dc3-4eea-850a-a4799697454e_590x290.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:290,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;TOCTTOU while appending to a list&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="TOCTTOU while appending to a list" title="TOCTTOU while appending to a list" srcset="https://substackcdn.com/image/fetch/$s_!Ruo9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223813a6-9dc3-4eea-850a-a4799697454e_590x290.png 424w, https://substackcdn.com/image/fetch/$s_!Ruo9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223813a6-9dc3-4eea-850a-a4799697454e_590x290.png 848w, https://substackcdn.com/image/fetch/$s_!Ruo9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223813a6-9dc3-4eea-850a-a4799697454e_590x290.png 1272w, https://substackcdn.com/image/fetch/$s_!Ruo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F223813a6-9dc3-4eea-850a-a4799697454e_590x290.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Both the processes here read the same list, appended their IDs and then tried writing it back to the Database. Since Process A updated the value last, the update made by Process B will get lost. This tactic of &#8220;Last Write Wins&#8221; is often used by databases for resolving such conflicts.</p><p>Most of the times it is up to the application developer to identify and prevent these kind of lost updates. Being a common problem, there are multiple ways to avoid it:</p><ol><li><p>using atomic operations (like <a href="https://redis.io/commands/incr">INCR in Redis</a>),</p></li><li><p>doing a &#8220;compare-and-set&#8221; operation (<code>set id_list = new-value if id_list = old-value</code>)</p></li><li><p>or taking an explicit lock on the value you are trying to modify.</p></li></ol><h2>Write Skew</h2><p><strong>Tl;dr: Write Skew is a generalisation of the Lost Update problem. It is also caused by a &#8220;read-modify-write&#8221; cycle, but the objects getting modified could be different.</strong></p><p>A generalisation of the lost update problem is when two or more concurrent transactions read the same objects, and then update some of those objects (the transactions might be updating different objects). If the transactions update the same object, a lost update can happen. If they are updating different objects, a write skew can happen. Write skews are also a very common defect that you would have come across in the wild.</p><p><br>It is easier to explain with an example. Omphile wants to go to the supermarket and she requests for a cab using the popular ride-hailing app, Sober. The app broadcasts her request to all available drivers, and any driver can accept the request. When a driver accepts a request, the app first checks if an ongoing ride for the customer already exists. If not, the app adds a ride for the given combination of customer and driver.</p><p>Conveniently, two drivers accepted Omphile&#8217;s request at the same time. If the application developers aren&#8217;t careful, this is how a write skew can occur:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!S_rj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a85a9bc-a067-43a1-9829-6d9ef3bfffe2_590x310.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!S_rj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a85a9bc-a067-43a1-9829-6d9ef3bfffe2_590x310.png 424w, https://substackcdn.com/image/fetch/$s_!S_rj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a85a9bc-a067-43a1-9829-6d9ef3bfffe2_590x310.png 848w, https://substackcdn.com/image/fetch/$s_!S_rj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a85a9bc-a067-43a1-9829-6d9ef3bfffe2_590x310.png 1272w, https://substackcdn.com/image/fetch/$s_!S_rj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a85a9bc-a067-43a1-9829-6d9ef3bfffe2_590x310.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!S_rj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a85a9bc-a067-43a1-9829-6d9ef3bfffe2_590x310.png" width="590" height="310" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a85a9bc-a067-43a1-9829-6d9ef3bfffe2_590x310.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:310,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Write Skew while booking a cab&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Write Skew while booking a cab" title="Write Skew while booking a cab" srcset="https://substackcdn.com/image/fetch/$s_!S_rj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a85a9bc-a067-43a1-9829-6d9ef3bfffe2_590x310.png 424w, https://substackcdn.com/image/fetch/$s_!S_rj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a85a9bc-a067-43a1-9829-6d9ef3bfffe2_590x310.png 848w, https://substackcdn.com/image/fetch/$s_!S_rj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a85a9bc-a067-43a1-9829-6d9ef3bfffe2_590x310.png 1272w, https://substackcdn.com/image/fetch/$s_!S_rj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a85a9bc-a067-43a1-9829-6d9ef3bfffe2_590x310.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Here, when the drivers checked if an ongoing ride exists or not, both got the right response: No ride exists. Both of them then proceeded to add a ride. Write skew.</p><p>Other common scenarios where this problem can surface is when booking a meeting room in a similar fashion to our ride-hailing app, or assigning a username for a user etc. Some of these problems can be resolved by adding a database constraint or taking a lock on the search results. But, like in our example, it is not always possible to take a lock because our query checks for the absence of a row, and therefore there is nothing to take a lock on.</p><h4>Phantoms</h4><p>A related concept here is the Phantom effect: It occurs when a write in one transaction changes the result of a search query in another transaction. In our example, If Driver A&#8217;s write would have occurred before Driver B&#8217;s search, the result would be different.</p><h1>&#128075;</h1><p>We discussed a lot of defects that can be caused if concurrent requests are executed. In reality, the situation is not as grim as it might look. There are a lot of reliable ways to avoid these defects. My aim here was to give you the vocabulary that helps you identify potential scenarios where these defects can occur in your systems. I hope this has helped.</p><p>If this topic fascinates you, I highly recommend you give DDIA a read.</p>]]></content:encoded></item><item><title><![CDATA[Get your code reviewed by Martin Fowler, kinda]]></title><description><![CDATA[I picked up Refactoring, by Martin Fowler, and I am learning a lot.]]></description><link>https://www.ketanbhatt.com/p/martin-fowler-refactoring-code-review</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/martin-fowler-refactoring-code-review</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Tue, 30 Jun 2020 00:00:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ec139efa-aa6a-4fef-a665-b225d4dfe1e6_480x270.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I picked up <a href="https://martinfowler.com/books/refactoring.html">Refactoring, by Martin Fowler</a>, and I am learning a lot. The book is written like a catalogue that you can refer back to later. And, for those who are seeking, <strong>the book is an excellent chance to get your code reviewed by Martin Fowler!!</strong> &#128558;</p><p>Martin starts the book with an example that he goes on to refactor in the first chapter. Sensing an insane opportunity, I stopped right away and copied this original code in Ruby. I then went ahead, wrote down my thoughts and refactored the code like I would if it was an actual thing I was working on. Once I was done, I went back to the book and compared Martin&#8217;s notes against mine, and learnt from his thought process and code. I think this is the best way to read this chapter.</p><p>If you are someone who enjoys this, I highly recommend you give this a try too. And if not the book, use this blog to power your review.</p><h1>The Original Code, and provided Context</h1><p><em><strong>(Psssst&#8230; You can find the full original code here: <a href="https://github.com/ketanbhatt/refactoring-rb/tree/master/chapter-1/a_first_example/original">Github</a>)</strong></em></p><p>We have a Theatre Company that perform plays in events. They charge customers based on the type of the play, and also provide a &#8220;Volume Credit&#8221; for future discounts. The Company stores data about their plays (<a href="https://github.com/ketanbhatt/refactoring-rb/blob/master/chapter-1/a_first_example/original/plays.json">plays.json</a>) and bills (<a href="https://github.com/ketanbhatt/refactoring-rb/blob/master/chapter-1/a_first_example/original/invoices.json">invoices.json</a>) in JSON files.</p><p>The simple code below is used to print a bill from an invoice. This is the code we will attempt to refactor. <strong>Assume that this code is part of a much larger system and we are refactoring while adding a feature to print bills in HTML. Moreover, the Company has plans to perform more types of plays in the future.</strong></p><pre><code># statement.rb

require 'json'

def statement(invoice, plays)
  total_amount = 0
  volume_credits = 0
  result = "Statement for #{invoice['customer']}\n"

  invoice['performances'].each do |perf|
    play = plays[perf['playID']]

    this_amount = 0

    case play['type']
    when 'tragedy'
      this_amount = 40000
      if perf['audience'] &gt; 30
        this_amount += 1000 * (perf['audience'] - 30)
      end
    when 'comedy'
      this_amount = 30000
      if perf['audience'] &gt; 20
        this_amount += 10000 + 500 * (perf['audience'] - 20)
      end
      this_amount += 300 * perf['audience']
    else
      raise Exception("Unknown type: #{play['type']}")
    end

    volume_credits += [perf['audience'] - 30, 0].max
    # Add extra credit for every 10 comedy attendees
    if 'comedy' == play['type']
      volume_credits += (perf['audience'] / 5).floor
    end

    result += "  #{play['name']}: $#{this_amount / 100} (#{perf['audience']} seats)\n"
    total_amount += this_amount
  end

  result += "Amount owed is $#{total_amount / 100}\n"
  result += "You earned #{volume_credits} credits\n"
end

plays_json = JSON.parse(File.read("./plays.json"))
invoices_json = JSON.parse(File.read("./invoices.json"))
result = statement(invoices_json.first, plays_json)
puts result</code></pre><h4>output</h4><pre><code>Statement for
  Hamlet: $650 (55 seats)
  As You Like It: $580 (35 seats)
  Othello: $500 (40 seats)
Amount owed is $1730
You earned 47 credits</code></pre><h4>Note: If you are planning to do this yourself first, stop here. Come back once you have refactored the original code.</h4><h1>My attempt at Refactoring</h1><p><em><strong>(Psssst&#8230; You can find the refactored code here: <a href="https://github.com/ketanbhatt/refactoring-rb/tree/master/chapter-1/a_first_example/ketanbhatt">Github</a>)</strong></em></p><p>Reading through the code, this is what stood out for me:</p><ol><li><p>The <code>statement</code> method returns a formatted text right now. I would like it to return a structured statement that I can format later as I wish, for example: for sending Slack notifications, emails. Additionally, it simplifies writing tests for this and the formatter methods.</p></li><li><p>The code that adds extra <code>volume_credits</code> for <code>comedy</code> plays, the comment states that we add extra credit for every &#8220;10&#8221; attendees, but the code adds a credit for every &#8220;5&#8221; attendees. Either this comment has gone stale, or there is a bug in the code. Either way, we can make the code more readable and remove the comment. (Aside: I like <a href="http://antirez.com/news/124">antirez&#8217;s blog about writing comments</a> a lot.)</p></li><li><p>Overall, it is difficult to understand what is going on in the code. I think we can make it more readable easily.</p></li><li><p>The method is doing too much, calculating <code>volume_credit</code> and <code>amount</code> for each type of play, and creating the text. It would be better if I can hand over this calculation to a different class that would do the needful, and my method just creates the text.</p></li><li><p>Because of this &#8220;doing too much&#8221;, the code has become messy. It will become still messier if the calculation was different for each Play too (and not same for Plays of the same type).</p></li><li><p>Also, looks like that the calculation is in cents and is being converted to dollars in the text. The code should make this explicit. Maybe I could rename the variable to be <code>amount_in_cents</code>?</p></li><li><p>The code right now assumes <code>audience</code> to be present and to be a number. Maybe we should put in validations that <code>asserts</code> for this truth? Although it seems out of scope for our current exercise.</p></li></ol><h3>Extract out the Calculation logic</h3><p>I moved the code for calculating amount and volume credits to a different class and file. I also separated the calculation for the two types of plays.</p><p>In the future, adding new play types will just need a new class to be added.</p><pre><code># statement_calculators.rb

class PlayTypeBaseStatementCalculator
  def initialize(play_id:, audience:)
    @play_id = play_id
    @audience = audience
  end

  def amount
    raise NotImplementedError
  end

  def volume_credits
    raise NotImplementedError
  end
end

class TragedyStatementCalculator &lt; PlayTypeBaseStatementCalculator
  ...
end

class ComedyStatementCalculator &lt; PlayTypeBaseStatementCalculator
  ...
end</code></pre><p><br>I also implemented the calculation in a more verbose manner. I think this improves the readability of the code.</p><pre><code># statement_calculators.rb

...

class TragedyStatementCalculator &lt; PlayTypeBaseStatementCalculator
  def amount
    fixed_charge = 40_000
    total_additional_charge = 0

    included_attendee_count = 30
    charge_per_extra_person = 1000

    if @audience &gt; included_attendee_count
      extra_attendee = @audience - included_attendee_count
      total_additional_charge += charge_per_extra_person * extra_attendee
    end

    fixed_charge + total_additional_charge
  end

  def volume_credits
    min_attendee_count = 30

    @audience &gt; min_attendee_count ? @audience - 30 : 0
  end
end

class ComedyStatementCalculator &lt; PlayTypeBaseStatementCalculator
  def amount
    fixed_charge = 30_000
    additional_charge_per_person = 300
    total_additional_charge = additional_charge_per_person * @audience

    included_attendee_count = 20
    charge_per_extra_person = 500
    additional_fixed_charge_if_extra_attendee = 10_000

    if @audience &gt; included_attendee_count
      extra_attendee = @audience - included_attendee_count
      total_additional_charge += (charge_per_extra_person * extra_attendee) + additional_fixed_charge_if_extra_attendee
    end

    fixed_charge + total_additional_charge
  end

  def volume_credits
    min_attendee_count = 30
    extra_credit_for_every_n_attendee = 5

    credits = @audience &gt; min_attendee_count ? @audience - 30 : 0
    credits += (@audience / extra_credit_for_every_n_attendee).floor

    credits
  end
end</code></pre><h3>Return the right calculator for a Play</h3><p>I wanted to separate out the logic behind fetching the right calculator for a play, so I created a getter for it.</p><pre><code># statement_calculators.rb

...

def get_calculator(play, performance)
  audience = performance['audience']
  play_id = performance['play_id']

  case play['type']
  when 'tragedy'
    TragedyStatementCalculator.new(play_id: play_id, audience: audience)
  when 'comedy'
    ComedyStatementCalculator.new(play_id: play_id, audience: audience)
  else
    raise Exception
  end
end</code></pre><h3>Calculate the Statement</h3><p>Keeping calculation out of the formatting logic, I created a method that loops over the invoices, and generates a structure containing information about the bill.</p><p>This structured statement can now be used by any formatter.</p><pre><code># statement_calculators.rb

...

def calculate_statement(invoice, plays)
  statement_hash = { performances: [] }

  invoice['performances'].each do |performance|
    play_id = performance['playID']
    play = plays[play_id]

    calculator = get_calculator(play, performance)

    statement_hash[:performances].push(
      {
        play_id: play_id,
        play_name: play['name'],
        audience: performance['audience'],
        amount: calculator.amount,
        volume_credits: calculator.volume_credits,
      }
    )
  end

  statement_hash[:customer] = invoice['customer']
  statement_hash[:total_amount] = statement_hash[:performances].reduce(0) { |sum, perf| sum + perf[:amount] }
  statement_hash[:total_volume_credits] = statement_hash[:performances].reduce(0) { |sum, perf| sum + perf[:volume_credits] }

  statement_hash
end</code></pre><h3>Finally, generate the Bill</h3><p>The original <code>statement</code> method is now <code>text_statement</code> and all it does is format the bill as needed. A new method, <code>html_statement</code> can now be defined similarly to format the bill in HTML.</p><pre><code># statement.rb

require 'json'

require './statement_calculators'

def text_statement(invoice, plays)
  statement_data = calculate_statement(invoice, plays)

  lines = ["Statement for #{statement_data['customer']}"]

  statement_data[:performances].each do |perf|
    lines.push("  #{perf[:play_name]}: $#{perf[:amount] / 100} (#{perf[:audience]} seats)")
  end

  lines.push("Amount owed is $#{statement_data[:total_amount] / 100}")
  lines.push("You earned #{statement_data[:total_volume_credits]} credits")

  lines.join("\n")
end

def html_statement(invoice, plays)
  nil
end</code></pre><p><br>I will call the refactoring done at this point.</p><p>Although, I can&#8217;t seem to shake off this feeling that there is something more that can be done. I don&#8217;t think I am completely happy with the changes &#128533; Let&#8217;s see what Sir Martin has to say. <br><br></p><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BQAn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc77b23cc-0935-4246-bc84-ebcb01c6a7a1_480x270.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BQAn!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc77b23cc-0935-4246-bc84-ebcb01c6a7a1_480x270.gif 424w, https://substackcdn.com/image/fetch/$s_!BQAn!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc77b23cc-0935-4246-bc84-ebcb01c6a7a1_480x270.gif 848w, https://substackcdn.com/image/fetch/$s_!BQAn!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc77b23cc-0935-4246-bc84-ebcb01c6a7a1_480x270.gif 1272w, https://substackcdn.com/image/fetch/$s_!BQAn!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc77b23cc-0935-4246-bc84-ebcb01c6a7a1_480x270.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BQAn!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc77b23cc-0935-4246-bc84-ebcb01c6a7a1_480x270.gif" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c77b23cc-0935-4246-bc84-ebcb01c6a7a1_480x270.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Excited Anticipation GIF&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Excited Anticipation GIF" title="Excited Anticipation GIF" srcset="https://substackcdn.com/image/fetch/$s_!BQAn!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc77b23cc-0935-4246-bc84-ebcb01c6a7a1_480x270.gif 424w, https://substackcdn.com/image/fetch/$s_!BQAn!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc77b23cc-0935-4246-bc84-ebcb01c6a7a1_480x270.gif 848w, https://substackcdn.com/image/fetch/$s_!BQAn!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc77b23cc-0935-4246-bc84-ebcb01c6a7a1_480x270.gif 1272w, https://substackcdn.com/image/fetch/$s_!BQAn!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc77b23cc-0935-4246-bc84-ebcb01c6a7a1_480x270.gif 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><h1>Code Review, courtesy Martin Fowler</h1><p>The process that Martin followed for refactoring the original code, in his words, was:</p><blockquote><ol><li><p>Decomposing the original function into a set of nested functions</p></li><li><p>Separate calculating and printing code, the two phases.</p></li><li><p>Introducing a polymorphic calculator for the calculation logic.</p></li></ol></blockquote><p>From what I have learnt in this chapter, in the <strong>hypothetical scenario</strong> in which Martin Fowler does a code review of my changes, this is what he would have to say:</p><ol><li><p><em>&#8220;I like that you are using an intermediate structure to pass the calculation from one &#8216;phase&#8217; to another. I also like that both of us are calling the new class a <code>Calculator</code>&#8221;</em> - Thank you Martin, thank you. I try.</p></li><li><p><em>&#8220;Your methods might just be too verbose. Try implementing them without all that verbosity? Same with the class names.&#8221;</em> - &#128466;</p></li><li><p><em>&#8220;You could also maybe implement the <code>volume_credits</code> method in the base class as there seems to be some code that can be reused&#8221;</em> - &#128466;</p></li></ol><h1>Code after addressing Martin&#8217;s comments</h1><p><em><strong>(Psssst&#8230; You can find the final refactored code here: <a href="https://github.com/ketanbhatt/refactoring-rb/tree/master/chapter-1/a_first_example/ketanbhatt_martin_reviewed">Github</a>)</strong></em></p><p>Based on Martin&#8217;s comments and how he went about his refactoring, these are the changes I made to my code</p><h3>Rename the Calculator classes</h3><p>I renamed the calculator classes. My names were too verbose and I didn&#8217;t like them a lot. I liked the names Martin gave his classes better.</p><pre><code># statement_calculators.rb

class StatementCalculator
  ...
end

class TragedyCalculator &lt; StatementCalculator
  ...
end

class ComedyCalculator &lt; StatementCalculator
  ...
end

...</code></pre><h3>Pass complete objects to the Calculator class</h3><p>I am now passing complete <code>performance</code> and <code>play</code> objects to the calculator class. This is easier for the callers now as they can simply pass what they have, and the calculation logic can make use of what it needs.</p><ol><li><p>I had previously opted to explicitly pass only those arguments that the <code>Calculator</code> class needed, and there is some benefit to it. For example: It becomes clearer what information <code>Calculator</code> needs. But then it can be argued that the caller doesn&#8217;t need to know how the <code>Calculator</code> class works?</p></li><li><p>If we are fearful of introducing breaking changes by omitting certain values from the passed object, values that the class might need, I don&#8217;t think making the caller do extra work is the solution. Either the <code>Calculator</code> class should validate the input it is getting, or this validation should be done while creating these objects, or both.</p></li></ol><pre><code># statement_calculators.rb

class StatementCalculator
  def initialize(play, performance)
    @play_id = play['play_id']
    @audience = performance['audience']
  end
  ...
end

...

def get_calculator(play, performance)
  case play['type']
  when 'tragedy'
    TragedyCalculator.new(play, performance)
  when 'comedy'
    ComedyCalculator.new(play, performance)
  else
    raise Exception
  end
end</code></pre><h3>Simplify <code>amount</code> and <code>volume_credits</code> implementations</h3><p>I re-implemented <code>amount</code> and <code>volume_credits</code> methods in the <code>Calculator</code> classes. I think I went overboard with the verbosity earlier and didn&#8217;t strike a good balance. I opted for longer names, that &#8220;felt&#8221; more readable/explanatory but, they had the opposite effect when I zoomed out.</p><p>The actual calculations were simple enough and just the expressions would do a good job explaining what is happening. I tried making it more readable by introducing variables to make the code self-documenting, but I now think that it was counterproductive.</p><p>I also realise that this is closer to the original definition of these methods, and I wasted my time for nothing. But hey, you wouldn&#8217;t know until you tried.</p><pre><code># statement_calculators.rb

...

class TragedyCalculator &lt; StatementCalculator
  def amount
    result = 40_000
    result += 1000 * (@audience - 30) if @audience &gt; 30

    result
  end

  def volume_credits
    [@audience - 30, 0].max
  end
end

class ComedyCalculator &lt; StatementCalculator
  def amount
    result = 30_000
    result += 10_000 + 500 * (@audience - 20) if @audience &gt; 20
    result += 300 * @audience

    result
  end

  def volume_credits
    base_credits = [@audience - 30, 0].max
    base_credits + (@audience / 5).floor
  end
end

...</code></pre><h3>Implement <code>volume_credits</code> in the parent Calculator class</h3><p>I changed the base <code>volume_credits</code> method to implement the part that is common to both types of play.</p><ol><li><p>I had previously opted to not do this because I thought it might be over-abstraction.</p></li><li><p>But after seeing Martin&#8217;s implementation, I find this current version to be much better. It makes the code easier to understand. The reader can just say, <em>&#8220;Okay, so we are doing what we do for all the types PLUS this extra thing.&#8221;</em></p></li></ol><pre><code># statement_calculators.rb

class StatementCalculator
  ...
  def volume_credits
    [@audience - 30, 0].max
  end
end

class TragedyCalculator &lt; StatementCalculator
  ...
  # remove the definition of `volume_credits` here
end

class ComedyCalculator &lt; StatementCalculator
  ...
  def volume_credits
    super + (@audience / 5).floor
  end
end

...</code></pre><h3>Calculate totals within the loop in <code>calculate_statement</code></h3><p>While in flow of making these above mentioned changes, I also made some minor modifications to the <code>calculate_statement</code> method.</p><ol><li><p>Instead of looping over all the statements at the end to calculate the <code>total_sum</code> and <code>total_volume_credits</code>, I simply maintained a variable that I updated in each iteration of the loop and added that to my dictionary at the end.</p></li><li><p>This I think makes the intention clearer, and the reader won&#8217;t have to go through the <code>reduce</code> loop over again. <code>total_amount += perf_statement[:amount]</code> is beautifully simple.</p></li></ol><p>Yes, I should have done this the first time itself &#129335;&#8205;&#9794;&#65039;</p><pre><code># statement_calculators.rb

...

def calculate_statement(invoice, plays)
  statement_hash = { customer: invoice['customer'], performances: [] }

  total_amount = 0
  total_volume_credits = 0

  invoice['performances'].each do |perf|
    play_id = perf['playID']
    play = plays[play_id]

    calculator = get_calculator(play, perf)
    perf_statement = {
      play_id: play_id,
      play_name: play['name'],
      audience: perf['audience'],
      amount: calculator.amount,
      volume_credits: calculator.volume_credits,
    }

    total_amount += perf_statement[:amount]
    total_volume_credits += perf_statement[:volume_credits]

    statement_hash[:performances].push(perf_statement)
  end

  statement_hash.merge(
    total_amount: total_amount, total_volume_credits: total_volume_credits
  )
end</code></pre><p>&#128524; I can now shake off that bad feeling from earlier. I think we did good today &#128077;</p><h1>In Closing</h1><p>Call me delusional, but it sure feels great to go through this process and learn from one of the foremost authority on the subject.</p><p>Another takeaway for me is around creating impact. Martin Fowler could teach people all that he has learnt one by one. It would be good for those few who were fortunate enough to be working with him directly and people like me would never get this chance. But he spent the time, wrote a book, and now his knowledge can educate thousands of people. That&#8217;s the kind of impact I hope to make in my life someday.</p><p>I would love it if someone gets inspired by this and decides to go through this exercise. Reach out if you do!</p>]]></content:encoded></item><item><title><![CDATA[The Nature of Software Development, by Ron Jeffries]]></title><description><![CDATA[Learnings about building Software, and Product.]]></description><link>https://www.ketanbhatt.com/p/nature-of-software-development</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/nature-of-software-development</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Sat, 06 Jun 2020 00:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d1ef58-1b75-4cde-b9d9-3cbd44c90b65_590x437.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I just finished reading <a href="https://www.goodreads.com/book/show/23016056-the-nature-of-software-development">The Nature of Software Development, by Ron Jeffries</a>. While the book didn&#8217;t teach me a whole lot of new things, it reminded me of the way things are supposed to be done. The author&#8217;s writing style makes you feel you are talking to a mentor over coffee who is sharing his knowledge and experience with you, sketching on a napkin whenever necessary.</p><p>Ron introduces a simple notion of <strong>&#8220;The Natural Way&#8221;</strong> of Software Development: <strong>Focus on delivering value early and often</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EZdy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F307dbd05-5d9a-43f9-a532-43e31a987041_590x390.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EZdy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F307dbd05-5d9a-43f9-a532-43e31a987041_590x390.jpeg 424w, https://substackcdn.com/image/fetch/$s_!EZdy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F307dbd05-5d9a-43f9-a532-43e31a987041_590x390.jpeg 848w, https://substackcdn.com/image/fetch/$s_!EZdy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F307dbd05-5d9a-43f9-a532-43e31a987041_590x390.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!EZdy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F307dbd05-5d9a-43f9-a532-43e31a987041_590x390.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EZdy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F307dbd05-5d9a-43f9-a532-43e31a987041_590x390.jpeg" width="590" height="390" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/307dbd05-5d9a-43f9-a532-43e31a987041_590x390.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:390,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Software is Lava, but a natural way exists&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Software is Lava, but a natural way exists" title="Software is Lava, but a natural way exists" srcset="https://substackcdn.com/image/fetch/$s_!EZdy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F307dbd05-5d9a-43f9-a532-43e31a987041_590x390.jpeg 424w, https://substackcdn.com/image/fetch/$s_!EZdy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F307dbd05-5d9a-43f9-a532-43e31a987041_590x390.jpeg 848w, https://substackcdn.com/image/fetch/$s_!EZdy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F307dbd05-5d9a-43f9-a532-43e31a987041_590x390.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!EZdy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F307dbd05-5d9a-43f9-a532-43e31a987041_590x390.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Building software is like walking in a room where the floor is lava, but there is a path of cool, green, grass that you can walk on and be safe. This path exists and is the &#8220;Natural Way&#8221;. We can&#8217;t stay on this Natural Way all the time, but it is good to know that this path exists so you can try and come back to it when you wander too far off.</p><h2>The Pyramid of Value</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!19JE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceb60d6f-9005-433c-a523-0bc6d0860f4d_590x425.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!19JE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceb60d6f-9005-433c-a523-0bc6d0860f4d_590x425.jpeg 424w, https://substackcdn.com/image/fetch/$s_!19JE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceb60d6f-9005-433c-a523-0bc6d0860f4d_590x425.jpeg 848w, https://substackcdn.com/image/fetch/$s_!19JE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceb60d6f-9005-433c-a523-0bc6d0860f4d_590x425.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!19JE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceb60d6f-9005-433c-a523-0bc6d0860f4d_590x425.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!19JE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceb60d6f-9005-433c-a523-0bc6d0860f4d_590x425.jpeg" width="590" height="425" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ceb60d6f-9005-433c-a523-0bc6d0860f4d_590x425.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:425,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Value Pyramid&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Value Pyramid" title="Value Pyramid" srcset="https://substackcdn.com/image/fetch/$s_!19JE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceb60d6f-9005-433c-a523-0bc6d0860f4d_590x425.jpeg 424w, https://substackcdn.com/image/fetch/$s_!19JE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceb60d6f-9005-433c-a523-0bc6d0860f4d_590x425.jpeg 848w, https://substackcdn.com/image/fetch/$s_!19JE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceb60d6f-9005-433c-a523-0bc6d0860f4d_590x425.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!19JE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceb60d6f-9005-433c-a523-0bc6d0860f4d_590x425.jpeg 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Value: What we want</h2><p>Value starts when we ship the software. What if we shipped some valuable part sooner than the rest? Most users don&#8217;t use every feature of the product. Since most users don&#8217;t use all the features, a smaller set of feature can provide real value, and provide it sooner.</p><p>After that first release, we may need to follow up with the rest of the product. But there can be times when you released the first bit and then stopped. Releasing early gives Information which is value as well. Sometimes the most important information we can get is that we are doing the wrong thing. Fail fast.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IX3v!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c64b04-dd39-4118-90d1-4b77b3da1098_590x542.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IX3v!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c64b04-dd39-4118-90d1-4b77b3da1098_590x542.jpeg 424w, https://substackcdn.com/image/fetch/$s_!IX3v!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c64b04-dd39-4118-90d1-4b77b3da1098_590x542.jpeg 848w, https://substackcdn.com/image/fetch/$s_!IX3v!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c64b04-dd39-4118-90d1-4b77b3da1098_590x542.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!IX3v!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c64b04-dd39-4118-90d1-4b77b3da1098_590x542.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IX3v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c64b04-dd39-4118-90d1-4b77b3da1098_590x542.jpeg" width="590" height="542" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/86c64b04-dd39-4118-90d1-4b77b3da1098_590x542.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:542,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Work on High Value - Low Effort features first&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Work on High Value - Low Effort features first" title="Work on High Value - Low Effort features first" srcset="https://substackcdn.com/image/fetch/$s_!IX3v!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c64b04-dd39-4118-90d1-4b77b3da1098_590x542.jpeg 424w, https://substackcdn.com/image/fetch/$s_!IX3v!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c64b04-dd39-4118-90d1-4b77b3da1098_590x542.jpeg 848w, https://substackcdn.com/image/fetch/$s_!IX3v!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c64b04-dd39-4118-90d1-4b77b3da1098_590x542.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!IX3v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c64b04-dd39-4118-90d1-4b77b3da1098_590x542.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>While choosing what to work on, choose work that is low effort but high value. When we work like that, we can start delivering value early, and in most of the cases, end up providing more net value because we might choose to work on other high ROI tasks for some other product, or realise we don&#8217;t need some low ROI features etc.</p><p><strong>Best value comes from small, value-focused features, delivered frequently.</strong> MMF: Minimal Marketable Features: Pieces that make sense to us, and to our users. Features: More granular than MMF.</p><h2>Guiding goes better &#8220;feature by feature&#8221;</h2><p>Tl;dr: Don&#8217;t do waterfall. This chapter was not particularly useful since a lot of the modern teams follow the practices described here by default.</p><p>Two types of &#8220;Guiding&#8221;</p><ol><li><p><strong>Activity based phases (Waterfall)</strong>:</p><ol><li><p>Analysis &#8212;&gt; Design &#8212;&gt; Coding &#8212;&gt; Testing.</p></li><li><p>You end up planning for features your might not get to or which are not useful.</p></li></ol></li><li><p><strong>Feature by feature</strong>:</p><ol><li><p>More visibility as your project progresses, since you start seeing the software. You will not know how your project is going until you start seeing value.</p></li><li><p>Sometimes you might even change features etc. based on the information you are receiving from the features you already released</p></li></ol></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HP-d!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5155d04-70c1-4b79-b512-31b23b136884_590x427.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HP-d!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5155d04-70c1-4b79-b512-31b23b136884_590x427.jpeg 424w, https://substackcdn.com/image/fetch/$s_!HP-d!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5155d04-70c1-4b79-b512-31b23b136884_590x427.jpeg 848w, https://substackcdn.com/image/fetch/$s_!HP-d!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5155d04-70c1-4b79-b512-31b23b136884_590x427.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!HP-d!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5155d04-70c1-4b79-b512-31b23b136884_590x427.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HP-d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5155d04-70c1-4b79-b512-31b23b136884_590x427.jpeg" width="590" height="427" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b5155d04-70c1-4b79-b512-31b23b136884_590x427.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:427,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Build incrementally for better visibility of your progress&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Build incrementally for better visibility of your progress" title="Build incrementally for better visibility of your progress" srcset="https://substackcdn.com/image/fetch/$s_!HP-d!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5155d04-70c1-4b79-b512-31b23b136884_590x427.jpeg 424w, https://substackcdn.com/image/fetch/$s_!HP-d!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5155d04-70c1-4b79-b512-31b23b136884_590x427.jpeg 848w, https://substackcdn.com/image/fetch/$s_!HP-d!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5155d04-70c1-4b79-b512-31b23b136884_590x427.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!HP-d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5155d04-70c1-4b79-b512-31b23b136884_590x427.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Teams decide a deadline, and they want &#8220;everything&#8221; done by that deadline. <strong>Years of experience has taught us that this never happens.</strong> You end up either delivering lesser value, or deliver it late, or both. When we know this truth, why not work for it in a manner that makes it possible for us to deliver value early, and gives us better visibility over the progress (and not spring surprises at the last moment).</p><h2>Organising</h2><p>Feature teams &gt;&gt; Function teams</p><h2>Planning feature by feature</h2><blockquote><p>Plans are useless, but planning is indispensable</p><p><em>- General Eisenhower</em></p></blockquote><p>Visions are grand ideas, vague yet enticing. But how do you go from there to small detailed features? Planning shouldn&#8217;t involve a detailed list of what needs to happen and at what time. Planning should be:</p><ol><li><p>Identify key feature that we will need to have early</p></li><li><p>Features we can&#8217;t live without</p></li><li><p>Defer implementing low-value ideas indefinitely. Let&#8217;s not waste time thinking about and keeping track of them.</p></li></ol><p><strong>Planning is important. We probably have to consider a lot of bad ideas to get a few good ones. Plan, but stay loose and ready for change.</strong></p><p>Software people are terrible at estimating because humans are terrible at estimating. Let&#8217;s not try harder to estimate, let&#8217;s find a better way:</p><ol><li><p>Set a time and money budget: <a href="https://basecamp.com/shapeup/1.2-chapter-03#setting-the-appetite">Appetite (Shape Up)</a></p></li><li><p>Produce the most valuable features first</p></li><li><p>Keep the product ready to ship at any time</p></li><li><p>Stop when the clock runs out.</p></li></ol><p>How do you get started on a project?</p><ol><li><p>Get a team, do a time bound spike, and</p></li><li><p>figure out what we can do to provide value and about how long will it take.</p></li></ol><p><strong>Don&#8217;t just plan in the beginning, plan all the time</strong>. Work in sprints. And work with stories.</p><p>Important: <strong>Don&#8217;t take a big story and break it down into smaller technical items, called tasks.</strong> Using tasks mean Business people don&#8217;t have clear visibility over how things are going, and often don&#8217;t know how to help. Stick with stories. Break down stories into smaller stories, each making sense to business-side people, rather than technical steps.</p><h3>How much should the team take on</h3><ol><li><p>The team should decide this</p></li><li><p><strong>Yesterday&#8217;s Weather</strong>. You will probably get as much done this iteration as you did in the last iteration</p></li></ol><h3>Sprint planning</h3><p>Discuss what all needs done, roughly discuss what will it take to get those things done. Estimate roughly (number of days?) on an aggregate level. Don&#8217;t estimate individual work pieces etc. Remember, the point isn&#8217;t to make good estimates, but to do good work at a consistent pace.</p><p><strong>Fight off the desire to estimate things strongly. With estimates comes the need to &#8220;improve&#8221; them or compare them. Both these practices are pernicious.</strong></p><p>On stretch goals: They are meant to be stretch goals, but people tend to try to meet them. Eager to please, they will unconsciously hurry, leave out a few tests, leave the code not quiet as clean as they might have etc. just to squeeze in one more feature. This creates pressure. Avoid it.</p><p>Generally speaking, estimates are likely to be wrong, and they focus our attention on the cost of things rather than on value. Consider de-emphasizing or eliminating cost estimates and steering to success by a focus on value.</p><h2>Building the product, feature by feature</h2><p>Each iteration is a full product development cycle. People giving the requirements (product managers, Business people) also need to learn how to break down their big ideas into smaller requirements. As you go, it means more product understanding, tightening what is a must have and what are nice to haves.</p><h2>Build features and foundations in parallel</h2><p>Ok, so you are building everything feature by feature. That&#8217;s cool. But how do you build the foundation feature by feature? By this I mean the overall system design.</p><p>You could build out the whole infra/design before hand and then build features, or build each feature completely, with its own infra one at a time. Both means that you will end up delivering lesser value in the given time.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5toK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76764b4-8844-42f7-9e7e-29ad70016a4b_590x415.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5toK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76764b4-8844-42f7-9e7e-29ad70016a4b_590x415.jpeg 424w, https://substackcdn.com/image/fetch/$s_!5toK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76764b4-8844-42f7-9e7e-29ad70016a4b_590x415.jpeg 848w, https://substackcdn.com/image/fetch/$s_!5toK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76764b4-8844-42f7-9e7e-29ad70016a4b_590x415.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!5toK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76764b4-8844-42f7-9e7e-29ad70016a4b_590x415.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5toK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76764b4-8844-42f7-9e7e-29ad70016a4b_590x415.jpeg" width="590" height="415" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e76764b4-8844-42f7-9e7e-29ad70016a4b_590x415.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:415,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Build features and foundations in parallel&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Build features and foundations in parallel" title="Build features and foundations in parallel" srcset="https://substackcdn.com/image/fetch/$s_!5toK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76764b4-8844-42f7-9e7e-29ad70016a4b_590x415.jpeg 424w, https://substackcdn.com/image/fetch/$s_!5toK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76764b4-8844-42f7-9e7e-29ad70016a4b_590x415.jpeg 848w, https://substackcdn.com/image/fetch/$s_!5toK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76764b4-8844-42f7-9e7e-29ad70016a4b_590x415.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!5toK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe76764b4-8844-42f7-9e7e-29ad70016a4b_590x415.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Ron suggests that we create simple and functional features and designs to begin with, and keep improving it with every feature. This helps because you can now deliver the most value in the time + you are just learning about your product. There is no point over spending time on the foundation or a particular feature, when we might learn later that this is not what we wanted.</p><p><strong>Build simple and functional versions first.</strong></p><p>Of course this will take skill. For the PM to understand what mix of features do they want first. And for the devs, how much to design so that when needed, those designs can be changed or modified to handle other requirements. If the design needs to be scrapped, then it is desirable that you have sent less time on it etc. This is a skill that the team will need to learn.</p><h2>Bug free</h2><p>Write automated tests.</p><h2>Assorted notes</h2><h3>Don&#8217;t whip the ponies, help them improve</h3><ul><li><p>Don&#8217;t try make more progress by asking developers to &#8220;work harder&#8221;. &#8220;Work Smarter&#8221; instead, and that means you need to help the team work smarter. &#8220;Work smarter, not harder&#8221; means that we have to help them get smarter.</p></li><li><p>Before focussing on individual productivity, work on team productivity. Analyse the sources of delays. Make sure the team has all the skills needed to do the work. Team members with key skills should be full time, not shared. Use specialists to increase other individual&#8217;s abilities. Value working together.</p></li><li><p>Increase individual productivity by increasing capability, and not by urging people to work harder.</p></li></ul><h3>Twisty little passages: Technical Debt</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HQiK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d1ef58-1b75-4cde-b9d9-3cbd44c90b65_590x437.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HQiK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d1ef58-1b75-4cde-b9d9-3cbd44c90b65_590x437.jpeg 424w, https://substackcdn.com/image/fetch/$s_!HQiK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d1ef58-1b75-4cde-b9d9-3cbd44c90b65_590x437.jpeg 848w, https://substackcdn.com/image/fetch/$s_!HQiK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d1ef58-1b75-4cde-b9d9-3cbd44c90b65_590x437.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!HQiK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d1ef58-1b75-4cde-b9d9-3cbd44c90b65_590x437.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HQiK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d1ef58-1b75-4cde-b9d9-3cbd44c90b65_590x437.jpeg" width="590" height="437" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/76d1ef58-1b75-4cde-b9d9-3cbd44c90b65_590x437.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:437,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Technical debt adds twisty passages, that makes you take more time going from one place to another&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Technical debt adds twisty passages, that makes you take more time going from one place to another" title="Technical debt adds twisty passages, that makes you take more time going from one place to another" srcset="https://substackcdn.com/image/fetch/$s_!HQiK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d1ef58-1b75-4cde-b9d9-3cbd44c90b65_590x437.jpeg 424w, https://substackcdn.com/image/fetch/$s_!HQiK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d1ef58-1b75-4cde-b9d9-3cbd44c90b65_590x437.jpeg 848w, https://substackcdn.com/image/fetch/$s_!HQiK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d1ef58-1b75-4cde-b9d9-3cbd44c90b65_590x437.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!HQiK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d1ef58-1b75-4cde-b9d9-3cbd44c90b65_590x437.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ul><li><p>The time needed to build a feature comes from two main components: its inherent difficulty, and the accidental difficulty of putting it into whatever code already exists. <strong>Teams are good at estimating the inherent difficulty. What makes us erratic, what makes us slow down, is the accidental difficulty. We call this difficulty &#8220;bad code.&#8221;</strong></p></li><li><p>If we allow code quality to decline, some features go in easily, sailing right through. Others that seem similar get entangled in twisty little passages of bad code. Similar work starts taking radically different amounts of time.</p></li><li><p>To keep progress steady, we have to avoid building twisty little passages, and when we do build then, we need to straighten them out.</p></li></ul><p>Fin.</p>]]></content:encoded></item><item><title><![CDATA[Build Systems with Speed and Confidence by Closing the Loop First!]]></title><description><![CDATA[I re-learnt something recently: the importance of closing the loop on a system you are trying to build, as quickly as possible, and then adding the juicy bits later (Thank you Kesha for helping me with the concept &#128522;).]]></description><link>https://www.ketanbhatt.com/p/close-the-loop</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/close-the-loop</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Mon, 24 Feb 2020 00:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c04e2e1-64bc-4ce2-9d98-38f33bf1137e_590x567.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I re-learnt something recently: the importance of closing the loop on a system you are trying to build, as quickly as possible, and then adding the juicy bits later (Thank you Kesha for helping me with the concept &#128522;).</p><p>A completely finished &#8220;loop&#8221; is when you can provide the required input to your system, and it produces the desired output (or side effects, if that&#8217;s how you like it). The &#8220;Close the loop first&#8221; technique is about closing this loop as fast as possible by creating a barebones version of it first, providing all or some required inputs, and generating a partial form of the desired output. Once we have closed this barebones loop, we can then begin implementing behaviours from the inside out, so that with each new change our loop starts looking more like the actual system we want.</p><p>Sure, this is nothing new, right? We have all heard of this advice in various forms: build a proof of concept as quickly as possible; validate the unknowns first; if you want to deliver a car, deploy a skateboard first, etc. This is similar, but I am talking today purely from a &#8220;programming&#8221; point of view. <strong>In addition to helping you fail fast, &#8220;closing the loop&#8221; first also lets you build systems with more speed.</strong></p><h2>How exactly does it help? &#129300;</h2><p>Let&#8217;s look at what I am trying to say using a simple example.</p><p><em>You have some data in Comma Separated Value (CSV) format. You have to read the file and for each row, clean a few columns, check if the row exists in a database table. If it does, update it, otherwise create a new row in the database. We also want to create a new CSV file that has an extra column specifying if the row was created or updated in the database.</em></p><p>We know how we will roughly do it. Read the CSV file, and for each row:</p><ol><li><p>Clean columns as necessary</p></li><li><p>Check if row is already in the database</p><ol><li><p>If it is already present, update it</p></li><li><p>Else, create a new row</p></li></ol></li><li><p>Add this row to a new CSV file with an extra column for creation/updation information.</p></li></ol><p>We can go about writing our code in this exact order, or we can &#8220;close the loop&#8221; first, and then add capabilities to our code.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PAl-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c04e2e1-64bc-4ce2-9d98-38f33bf1137e_590x567.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PAl-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c04e2e1-64bc-4ce2-9d98-38f33bf1137e_590x567.png 424w, https://substackcdn.com/image/fetch/$s_!PAl-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c04e2e1-64bc-4ce2-9d98-38f33bf1137e_590x567.png 848w, https://substackcdn.com/image/fetch/$s_!PAl-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c04e2e1-64bc-4ce2-9d98-38f33bf1137e_590x567.png 1272w, https://substackcdn.com/image/fetch/$s_!PAl-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c04e2e1-64bc-4ce2-9d98-38f33bf1137e_590x567.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PAl-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c04e2e1-64bc-4ce2-9d98-38f33bf1137e_590x567.png" width="590" height="567" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6c04e2e1-64bc-4ce2-9d98-38f33bf1137e_590x567.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:567,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Loop is Open&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Loop is Open" title="Loop is Open" srcset="https://substackcdn.com/image/fetch/$s_!PAl-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c04e2e1-64bc-4ce2-9d98-38f33bf1137e_590x567.png 424w, https://substackcdn.com/image/fetch/$s_!PAl-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c04e2e1-64bc-4ce2-9d98-38f33bf1137e_590x567.png 848w, https://substackcdn.com/image/fetch/$s_!PAl-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c04e2e1-64bc-4ce2-9d98-38f33bf1137e_590x567.png 1272w, https://substackcdn.com/image/fetch/$s_!PAl-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c04e2e1-64bc-4ce2-9d98-38f33bf1137e_590x567.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We start with an open loop</p><h3>Step 1: Close the Loop!</h3><p><strong>Read the CSV and add each row to another CSV, with a new column &#8220;operation&#8221;</strong></p><p>To be able to close the loop quickly, we will just add a static value <code>"created"</code> to the new <code>"operation"</code> column. Also add tests to check that each row in the input file is present in the output file, and that a new column exists.</p><pre><code>import csv

def close_the_loop():
    input_file = open('data.csv', 'r')
    output_file = open('output.csv', 'w')

    input_csv = csv.DictReader(input_file)
    headers = input_csv.fieldnames + ['operation']
    output_csv = csv.DictWriter(output_file, headers)

    for row in input_csv:
        row['operation'] = 'created'
        output_csv.writerow(row)

    input_file.close(), output_file.close()</code></pre><p><em>* I wanted to make the code more readable, for a blog, by having less indents. Otherwise a better way to read files in Python is by opening the file using <code>with</code></em>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Au7f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28602f27-5cdc-4bf9-a1ff-5ed04e8796ff_590x610.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Au7f!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28602f27-5cdc-4bf9-a1ff-5ed04e8796ff_590x610.png 424w, https://substackcdn.com/image/fetch/$s_!Au7f!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28602f27-5cdc-4bf9-a1ff-5ed04e8796ff_590x610.png 848w, https://substackcdn.com/image/fetch/$s_!Au7f!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28602f27-5cdc-4bf9-a1ff-5ed04e8796ff_590x610.png 1272w, https://substackcdn.com/image/fetch/$s_!Au7f!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28602f27-5cdc-4bf9-a1ff-5ed04e8796ff_590x610.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Au7f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28602f27-5cdc-4bf9-a1ff-5ed04e8796ff_590x610.png" width="590" height="610" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/28602f27-5cdc-4bf9-a1ff-5ed04e8796ff_590x610.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:610,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Step 1 - Close the loop&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Step 1 - Close the loop" title="Step 1 - Close the loop" srcset="https://substackcdn.com/image/fetch/$s_!Au7f!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28602f27-5cdc-4bf9-a1ff-5ed04e8796ff_590x610.png 424w, https://substackcdn.com/image/fetch/$s_!Au7f!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28602f27-5cdc-4bf9-a1ff-5ed04e8796ff_590x610.png 848w, https://substackcdn.com/image/fetch/$s_!Au7f!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28602f27-5cdc-4bf9-a1ff-5ed04e8796ff_590x610.png 1272w, https://substackcdn.com/image/fetch/$s_!Au7f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F28602f27-5cdc-4bf9-a1ff-5ed04e8796ff_590x610.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Aaaaaaand&#8230;the loop is closed!</p><h3>Step 2: Add juicy bits</h3><p><strong>For each row, call a dummy method that will later implement the DB operation, but right now returns &#8220;updated/created&#8221;.</strong></p><p>Update your test to check the correct value of the <code>operation</code> column based on what you mocked it with.</p><pre><code>...

def update_or_create(row):
    return 'created'

def close_the_loop():
    ...

    for row in input_csv:
        row['operation'] = update_or_create(row)
        output_csv.writerow(row)

    ...</code></pre><p><strong>Actually implement the <code>update_or_create</code> method</strong></p><p>Add a new unit test for <code>update_or_create</code>. By this point, you are almost done.</p><p><strong>Add a method to do some data cleaning before writing to the output file.</strong></p><p>Also update your tests to account for this change.</p><pre><code>...

def clean_row(row):
    row['name'] = row['name'].strip

def close_the_loop():
    ...

    for row in input_csv:
        clean_row(row)
        row['operation'] = update_or_create(row)
        output_csv.writerow(row)

    ...</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!t_R8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78a7e55f-5f21-49cd-9cda-f151154d86fb_590x475.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!t_R8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78a7e55f-5f21-49cd-9cda-f151154d86fb_590x475.png 424w, https://substackcdn.com/image/fetch/$s_!t_R8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78a7e55f-5f21-49cd-9cda-f151154d86fb_590x475.png 848w, https://substackcdn.com/image/fetch/$s_!t_R8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78a7e55f-5f21-49cd-9cda-f151154d86fb_590x475.png 1272w, https://substackcdn.com/image/fetch/$s_!t_R8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78a7e55f-5f21-49cd-9cda-f151154d86fb_590x475.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!t_R8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78a7e55f-5f21-49cd-9cda-f151154d86fb_590x475.png" width="590" height="475" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/78a7e55f-5f21-49cd-9cda-f151154d86fb_590x475.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:475,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Step 2 - Work is done&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Step 2 - Work is done" title="Step 2 - Work is done" srcset="https://substackcdn.com/image/fetch/$s_!t_R8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78a7e55f-5f21-49cd-9cda-f151154d86fb_590x475.png 424w, https://substackcdn.com/image/fetch/$s_!t_R8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78a7e55f-5f21-49cd-9cda-f151154d86fb_590x475.png 848w, https://substackcdn.com/image/fetch/$s_!t_R8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78a7e55f-5f21-49cd-9cda-f151154d86fb_590x475.png 1272w, https://substackcdn.com/image/fetch/$s_!t_R8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78a7e55f-5f21-49cd-9cda-f151154d86fb_590x475.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Aaaaaaaaand&#8230;work is done!</p><h2>How does this work?</h2><p>Even though it sounds like the same advice, I instantly visualised it when Kesha brought it up and said &#8220;let&#8217;s close the loop first&#8221;. I am a very visual person, and this created an image in my mind of a loop that needed closing. And when that loop gets closed, that&#8217;s another <strong>extremely</strong> satisfying image. For me, this image is important because we regularly hear about a lot of best practices and design patterns, but what really matters is how many of them can we remember to apply to our situation when we actually sit down to implement stuff. Visualising a concept helps me remember it for a longer time.</p><p><em>Photo by&nbsp;<a href="https://unsplash.com/@huzy_sheikh?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">HUZAIFA SHEIKH</a>&nbsp;on&nbsp;<a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></em></p><p>Other than allowing you to build you system quickly, piece by piece, there are other related benefits with this approach:</p><ol><li><p>You get to &#8220;executing&#8221; code quickly, which helps you add more code faster and with more confidence (as you can quickly check if it is working or not by running your code, hopefully using the basic test you wrote).</p></li><li><p>Instead of getting 20 failures, across the entirety of those 2000 lines of code that you changed, you get fewer errors as you build your system bit by bit. Most of these errors would be around your latest changes and, thus, easier to debug and fix.</p></li><li><p>If you plan correctly, you can prioritise and selectively add capabilities to your system (maybe do the optimisations after initial deployment?) as you build. This helps you deliver basic capabilities as fast as possible, and sometimes that&#8217;s all what is needed (*cough* startups *cough*).</p></li><li><p>And of course, all the benefits of failing fast or uncovering the unknowns first are still valid: If your assumptions prove out to be wrong, your time investment is minimal at this point, and you can still figure out a way to work around this new-found information.</p></li></ol><h3>Caveats</h3><p>Of course, this isn&#8217;t a standalone best practice that you can implement in isolation. You need to already have a clearer picture of what you are trying to build. And then, of course, some people might like it better to implement end to end in one go. And then there are things that are better implemented that way.</p><p>All this is, is a tool. Use it where you think it can be useful.</p><p>Ciao!</p>]]></content:encoded></item><item><title><![CDATA[Intercom's Interview Process and My Experience]]></title><description><![CDATA[Based on my interview for a Product Engineering role in 2019]]></description><link>https://www.ketanbhatt.com/p/intercom-interview-process</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/intercom-interview-process</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Wed, 01 Jan 2020 00:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6275398-4427-400d-99a1-aba62c99deeb_590x707.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I will be starting my Intercom journey on December 2nd, 2019 &#128516; While preparing, I got a lot of help from <a href="https://www.glassdoor.co.in/Overview/Working-at-Intercom-EI_IE1035935.11,19.htm">Glassdoor</a> and I thought of adding my interview experience to the internet too, for helping people in the future.</p><p><strong>Update September 2020:</strong> The world has changed a lot ever since I interviewed with Intercom. In order to compensate for this new normal, the interview process has been changed a little. The rounds, by themselves, will still be a lot similar to what I have mentioned below, but their arrangement might have changed. Intercom might also have replaced/merged/broken down rounds to make it easier for candidates applying remotely.</p><p>Intercom&#8217;s was one of the most thorough interview processes that I have experienced/heard about, and it&#8217;s a good thing! It made me confident of the team I am going to be working with if all goes well. From what I could decipher from the rounds, Intercom is not only testing you technically, they also want to test you for your product skills and are judging if you will be a good addition to the team. They were also very responsive throughout the process, and the result of a round was shared within a week all the time (mostly within 3-4 days).</p><p>Intercom has also written a lot of resources to help you prepare for the interview. For starters, you should checkout this: <a href="https://www.intercom.com/blog/software-engineering-interview-questions/">How to prepare for software engineering interview questions</a>. I will share links to Intercom&#8217;s blogs wherever relevant.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!igBm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c6806d1-9737-4463-874a-b23dca0f999f_590x300.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!igBm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c6806d1-9737-4463-874a-b23dca0f999f_590x300.jpeg 424w, https://substackcdn.com/image/fetch/$s_!igBm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c6806d1-9737-4463-874a-b23dca0f999f_590x300.jpeg 848w, https://substackcdn.com/image/fetch/$s_!igBm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c6806d1-9737-4463-874a-b23dca0f999f_590x300.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!igBm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c6806d1-9737-4463-874a-b23dca0f999f_590x300.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!igBm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c6806d1-9737-4463-874a-b23dca0f999f_590x300.jpeg" width="590" height="300" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8c6806d1-9737-4463-874a-b23dca0f999f_590x300.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:300,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Cliffs of Moher&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Cliffs of Moher" title="Cliffs of Moher" srcset="https://substackcdn.com/image/fetch/$s_!igBm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c6806d1-9737-4463-874a-b23dca0f999f_590x300.jpeg 424w, https://substackcdn.com/image/fetch/$s_!igBm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c6806d1-9737-4463-874a-b23dca0f999f_590x300.jpeg 848w, https://substackcdn.com/image/fetch/$s_!igBm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c6806d1-9737-4463-874a-b23dca0f999f_590x300.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!igBm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c6806d1-9737-4463-874a-b23dca0f999f_590x300.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>Remote Rounds</h2><p>These rounds happen either asynchronously or over audio/video call (they used Google Hangouts for all my interviews).</p><h3>Take Home Test</h3><p>This was a standard filtering round. Intercom gives you a small problem (which they have mentioned in a <a href="https://www.intercom.com/blog/how-we-hire-engineers-part-1/">blog post</a>) that you need to submit the solution for. They have also written a blog post about what you should take care of, how do they assess the activity etc. here: <a href="https://www.intercom.com/blog/engineer-interview-assignments/">How to prepare for engineering interview assignments</a>.</p><h3>HR Screener</h3><p>This is just a call to dig into your background, motivations and interests etc. You can take this time out to also ask them about the interview process.</p><h3>Technical Screener + Cultural Contribution</h3><p>Two-thirds of the call is &#8220;technical&#8221; and consists of basic Algorithmic and Data Structure based questions. My suggestion here will be to cover as many &#8221;<a href="https://leetcode.com/problemset/all/?difficulty=Easy&amp;listId=wpwgkgt">Easy, Top Interview</a>&#8221; questions as possible from <a href="https://leetcode.com/">Leetcode</a>. The remaining call is focussed on &#8220;culture contribution&#8221; which was similar to other culture contribution rounds (discussed later).</p><h3>Culture Contribution</h3><p>Intercom has multiple rounds where they want to assess &#8220;What contribution will you make to Intercom&#8217;s culture&#8221;. I liked that they call this round &#8220;Culture Contribution&#8221; and not &#8220;Culture Fit&#8221; because doing the latter could lead to a lot of biased decisions as they explain in their <a href="https://www.intercom.com/blog/how-we-hire-engineers-part-2-culture-contribution/">blog post</a>. They also do a good job of explaining what you can expect in the interview and how do they evaluate candidates.</p><p>My suggestion here is that do not try to &#8220;wing it&#8221; in this round. It gets difficult to extract helpful examples from your past to show, for example, how you respond to negative feedback, on the spot! You need to be prepared beforehand and go deeper into your past experience and come up with evidence/anecdotes which you think is going to be relevant for the interview.</p><p>Some questions that I prepared for, or encountered in the interviews were:</p><ul><li><p>Tell me about a time when you built a product and it failed. Why do you think you failed?</p></li><li><p>Can you think of an instance where you made a mistake while working with a customer? How did you handle it?</p></li><li><p>In busy times, how do you prioritise your customers and interactions?</p></li><li><p>Tell me about a challenging project, lessons learned and what would you do differently next time</p></li><li><p>Tell me about a time you went beyond what was expected</p></li><li><p>Tell me about one time you improved something without being told to</p></li><li><p>Tell me about a project that succeeded and why do you think it did</p></li><li><p>Tell me about a disagreement you had with someone and how you handled it</p></li><li><p>How do you handle conflict?</p></li><li><p>A recent decision you made that you would like to change? Why?</p></li><li><p>Tell me about a time you got some feedback that you didn&#8217;t agree with.</p></li><li><p>What do you do when you get feedback that you don&#8217;t understand?</p></li></ul><h2>Onsite Rounds</h2><p>If you clear all the previous rounds, Intercom invites you to their office in Dublin. This is a good opportunity to meet a lot of the team in-person, see the office. For people who haven&#8217;t been to Dublin before, it is also a great opportunity to see how you like the city (get the feelzzz).</p><h3>Pair Programming: Minicom</h3><p>This is a 1.5 hours long round in which you are given a small project (called Minicom) and you have to extend the codebase to add functionality. You are given 30 minutes to work on it by yourself, and then 30 minutes each to pair program with two Intercom developers. At the end, you are expected to do a little demo of the feature that you built and answer a few questions such as, &#8220;What other features would you add if you had more time&#8221;, etc.</p><p>The rounds are &#8220;Pair Programming&#8221; in the real sense. I am not that well versed with Javascript and there were a few frontend changes. The people who worked with me in the pair programming round helped me debug issues, and with Javascript syntax as well.</p><p>Intercom explains in depth about this round in their <a href="https://www.intercom.com/blog/building-minicom-engineering-interviews/">blog post</a>.</p><h3>Technical Design</h3><p>This is a typical technical design round in which you are expected to design the backend for the Minicom task you worked on, go deeper into it and add a lot of features. You are expected to be able to communicate your design by creating rough diagrams on the board.</p><h3>Problem Solving</h3><p>This is a typical Data Structures and Algorithmic round, and involves solving 1-2 problems and writing code on a whiteboard in 45 minutes. The questions were still &#8220;Leetcode Easy&#8221; level, but they were more difficult than the ones asked in the technical screener at the beginning of the process.</p><h3>Product Chat</h3><p>The goal here is to figure out if you can think about the Product too and offer suggestions/opinions while you are working on Intercom. They try to figure this out by talking to you about a product that you like, and why do you like it.</p><p>There is no right or wrong answer in this round, I think they just want to hear you out, and understand how you think. Again, be prepared with a few products that you can talk about. You can get some followup questions, like what would you do if you were the CEO of that product? What do you think is the largest threat for them? Who are their competitors?</p><p>I will also suggest that you prepare for a few products that you are sure the interviewer will know about too. This makes the conversation interesting because the interviewer also gets a chance to share their opinions, and dive deeper.</p><h3>Values Deep Dive</h3><p>This is similar to the Culture Contribution round, but certainly more gruelling for me (could be because I was tired by this time &#128517;).</p><h3>Role Chat</h3><p>This isn&#8217;t an interview round, but a time you get to ask as many questions about the role, the company etc. that you want.</p><p>Role chat: not an interview, you get to ask questions, but by now I was too tired. Still, I liked everyone involved in the rounds. Helpful and nice people.</p><div><hr></div><p>All in all, I had a great time interviewing with Intercom. I got to meet around 7-8 people, which really helped me in getting a better idea about the people I could be working with in the future. Everyone that I had a chance to meet with was extremely warm and helpful.</p><p>PS: I had completely forgotten, but it turns out I had tried applying for an Internship 4 years ago (some audacity!). Such a fun thing to find &#128513; Destiny then?</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mNZi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6275398-4427-400d-99a1-aba62c99deeb_590x707.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mNZi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6275398-4427-400d-99a1-aba62c99deeb_590x707.png 424w, https://substackcdn.com/image/fetch/$s_!mNZi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6275398-4427-400d-99a1-aba62c99deeb_590x707.png 848w, https://substackcdn.com/image/fetch/$s_!mNZi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6275398-4427-400d-99a1-aba62c99deeb_590x707.png 1272w, https://substackcdn.com/image/fetch/$s_!mNZi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6275398-4427-400d-99a1-aba62c99deeb_590x707.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mNZi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6275398-4427-400d-99a1-aba62c99deeb_590x707.png" width="590" height="707" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e6275398-4427-400d-99a1-aba62c99deeb_590x707.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:707,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Screenshot of the time I applied for an internship at Intercom&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Screenshot of the time I applied for an internship at Intercom" title="Screenshot of the time I applied for an internship at Intercom" srcset="https://substackcdn.com/image/fetch/$s_!mNZi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6275398-4427-400d-99a1-aba62c99deeb_590x707.png 424w, https://substackcdn.com/image/fetch/$s_!mNZi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6275398-4427-400d-99a1-aba62c99deeb_590x707.png 848w, https://substackcdn.com/image/fetch/$s_!mNZi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6275398-4427-400d-99a1-aba62c99deeb_590x707.png 1272w, https://substackcdn.com/image/fetch/$s_!mNZi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6275398-4427-400d-99a1-aba62c99deeb_590x707.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Ciao!</p>]]></content:encoded></item><item><title><![CDATA[Postgres: Recreating Indexes supporting Unique, Foreign Key and Primary Key Constraints]]></title><description><![CDATA[I have frequently found myself in situations when I had to reindex a few indexes (because the index got bloated a lot), and I always have to lookup the exact commands, after searching quite a bit, just to be sure that I am doing the right thing and not making a mistake.]]></description><link>https://www.ketanbhatt.com/p/postgres-recreate-index</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/postgres-recreate-index</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Fri, 16 Aug 2019 07:11:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!adSA!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e37e6d-f289-4a21-95bd-008a89277566_426x426.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have frequently found myself in situations when I had to reindex a few indexes (because the index got bloated a lot), and I always have to lookup the exact commands, after searching quite a bit, just to be sure that I am doing the right thing and not making a mistake. In the past, I have referred to the articles I have written multiple times, and I thought I need to create another Reference Guide for myself for this. Hopefully, others on the internet will also found this reference useful.</p><h4><strong>Some basic tips:</strong></h4><ol><li><p>If you have a large and/or a table which gets a lot of traffic, remember that a plain <code>REINDEX</code> command will take a lock on the table that won&#8217;t allow any <code>write</code> operations on the table till the command completes. Reindexing <code>CONCURRENTLY</code> is almost always a better option out. You can read more about it in the official docs: <a href="https://www.postgresql.org/docs/current/sql-createindex.html#SQL-CREATEINDEX-CONCURRENTLY">Building Indexes Concurrently</a>.</p></li><li><p>Remember to <code>set statement_timeout = 0;</code> before running the reindex command since <code>CREATE INDEX</code> commands are also taken as <code>statements</code> by Postgres, and they will be killed if they go above a decided threshold.</p></li><li><p>To figure out indexes that a table has and the corresponding bloat percentage for each of them, you can use <a href="https://gist.github.com/mbanck/9976015/71888a24e464e2f772182a7eb54f15a125edf398">this query</a> (we picked it up from <a href="https://github.com/ankane/pghero/blob/f1183eae03a0f6fca408b899c41476c9cebc627b/lib/pghero/methods/indexes.rb#L187">PgHero&#8217;s codebase</a>). We add a <code>table_name = 'my_sweet_table'</code> to the <code>WHERE</code> clause at the end of the query to only get the indexes for our table, but that is completely optional.</p></li><li><p>You can also use a <a href="https://gist.github.com/ketanbhatt/fdbd6246b4b1b7bb32009de5e468ed57">simple query to get the definition of all the indexes for a table</a>. These definitions can be used as is when we want to recreate them.</p></li></ol><h2><strong>Recreating Indexes with Foreign Key constraints</strong></h2><p><em>Indexes that are not created for a constraint can be reindexed in the same way.</em></p><p>We have the definition of the original index, we can just replace the name with a temporary name and use <code>CONCURRENTLY</code>:</p><pre><code><code>CREATE INDEX CONCURRENTLY
  new_idx
ON my_sweet_table USING
  btree (my_fk_column);</code></code></pre><p>Now you can safely drop the original index. You can optionally use <code>CONCURRENTLY</code> here as well, read more about it in the docs: <a href="https://www.postgresql.org/docs/current/sql-dropindex.html">Drop Index</a>.</p><pre><code><code>DROP INDEX my_lovely_index;</code></code></pre><p>You could also rename the new index to the original name (some frameworks, like Django, autogenerate index names using the table name, the app&#8217;s name, and a hash of both of these plus the columns of the model. You might want to preserve that name).</p><pre><code><code>ALTER INDEX
  new_idx
RENAME TO
  my_lovely_index;</code></code></pre><p>Also, if the index you are recreating is a unique index, you can add the keyword <code>UNIQUE</code> to the <code>CREATE INDEX</code> command.</p><h2><strong>Recreating Indexes with Unique constraints</strong></h2><p>Recreate the Index, with the keyword <code>UNIQUE</code>.</p><pre><code><code>CREATE UNIQUE INDEX CONCURRENTLY
  new_uniq_idx
ON my_sweet_table USING
  btree (col_a, col_b);</code></code></pre><p>Now, we want the constraint to use this new index. For that, we drop the original constraint, and add a new unique constraint that uses our new index. This is done in one atomic statement so that there is no time when there is no constraint on the table. We don&#8217;t have to rename the index this time as Postgres automatically renames it to the name of the constraint.</p><pre><code><code>ALTER TABLE
  my_sweet_table
DROP CONSTRAINT
  uniq_constraint_777,
ADD CONSTRAINT
  uniq_constraint_777 UNIQUE
USING INDEX
  new_uniq_idx;</code></code></pre><h2><strong>Recreating Indexes with Primary Key constraints</strong></h2><p>This is achieved in the same manner as we did for recreating the index for a unique constraint. The only difference is that this time the constraint that we add is a <code>PRIMARY KEY</code> constraint, of course :D</p><pre><code><code>CREATE UNIQUE INDEX CONCURRENTLY
  new_pkey_idx
ON my_sweet_table USING
  btree (id);

ALTER TABLE
  my_sweet_table
DROP CONSTRAINT
  my_sweet_table_pkey,
ADD CONSTRAINT
  my_sweet_table_pkey PRIMARY KEY
USING INDEX
  new_pkey_idx;</code></code></pre><p>That&#8217;s it! Tadaaaa.</p>]]></content:encoded></item><item><title><![CDATA[Avoid breaking habits with help from Economics]]></title><description><![CDATA[Recently, while reading &#8220;How Will You Measure Your Life?&#8221;, I came across the concept of Marginal Thinking and I thought that it could be used as a mental model for motivating people who struggle with building new habits (like this guy I know, me).]]></description><link>https://www.ketanbhatt.com/p/marginal-thinking</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/marginal-thinking</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Wed, 29 May 2019 00:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ZfDI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0ee1d4c-d450-4194-83ef-ee2a5061978c_590x737.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently, while reading <a href="https://www.goodreads.com/book/show/13425570-how-will-you-measure-your-life?ac=1&amp;from_search=true">&#8220;How Will You Measure Your Life?&#8221;</a>, I came across the concept of <strong>Marginal Thinking</strong> and I thought that it could be used as a mental model for motivating people who struggle with building new habits (like this guy I know, me).</p><p>Aside: I highly recommend reading <a href="https://www.goodreads.com/book/show/13425570-how-will-you-measure-your-life?ac=1&amp;from_search=true">&#8220;How Will You Measure Your Life?&#8221;</a> and then <a href="https://www.goodreads.com/book/show/30008950-the-power-of-meaning?ac=1&amp;from_search=true">&#8220;Power of Meaning&#8221;</a>, in that order, if ever you come across such a problem in your life. I faced it recently, and these books helped me a lot.</p><h2>Marginal Thinking and its Trap</h2><p>Marginal Thinking is thinking in terms of a little bit more, or a little bit less. It talks about the &#8220;additional&#8221; or &#8220;incremental&#8221; cost of a decision, and it&#8217;s benefits.</p><blockquote><p>Has any of you ever been in a situation where you were deciding whether or not to have another piece of pie at a buffet? Or whether to hit that snooze button on the alarm and get a little bit more sleep before getting up?&nbsp;&#8230;</p><p>The additional piece of pie is the marginal piece of pie. The additional half hour is the marginal half an hour of sleep.</p><p>Howard Baetjer (<a href="https://www.libertarianism.org/guides/lectures/marginal-thinking">source</a> - visit for a deeper introduction)</p></blockquote><p>Marginal thinking is considered to be a good way of taking decision in Economics, but it is not as straightforward because it is easy to get the &#8220;cost&#8221; and the &#8220;benefits&#8221; of the &#8220;marginal whatever&#8221; wrong, and end up making a bad decision. For example, what is the marginal cost of eating that extra pie? No, it is not $4.</p><blockquote><p><em>What&#8217;s the benefit of one more piece of pie? It tastes good. What&#8217;s the cost of it? I&#8217;m already overweight, I should be trimming back on my calories.</em></p><p><em><strong>Howard Baetjer</strong></em></p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZfDI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0ee1d4c-d450-4194-83ef-ee2a5061978c_590x737.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZfDI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0ee1d4c-d450-4194-83ef-ee2a5061978c_590x737.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ZfDI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0ee1d4c-d450-4194-83ef-ee2a5061978c_590x737.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ZfDI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0ee1d4c-d450-4194-83ef-ee2a5061978c_590x737.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ZfDI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0ee1d4c-d450-4194-83ef-ee2a5061978c_590x737.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZfDI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0ee1d4c-d450-4194-83ef-ee2a5061978c_590x737.jpeg" width="590" height="737" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d0ee1d4c-d450-4194-83ef-ee2a5061978c_590x737.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:737,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Tasty looking Pie&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Tasty looking Pie" title="Tasty looking Pie" srcset="https://substackcdn.com/image/fetch/$s_!ZfDI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0ee1d4c-d450-4194-83ef-ee2a5061978c_590x737.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ZfDI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0ee1d4c-d450-4194-83ef-ee2a5061978c_590x737.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ZfDI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0ee1d4c-d450-4194-83ef-ee2a5061978c_590x737.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ZfDI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0ee1d4c-d450-4194-83ef-ee2a5061978c_590x737.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><em>Photo by <a href="https://unsplash.com/photos/sT5OXh429qk?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Food Photographer | Jennifer Pallian</a> on <a href="https://unsplash.com/search/photos/small-pie?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></em></figcaption></figure></div><p>We often consider the &#8220;marginal cost&#8221; to be the, almost always, tiny effort/investment needed to get the marginal benefit. We think that skipping a day in the gym &#8220;just&#8221; means not getting the day&#8217;s exercise, or sleeping for those extra 10 minutes &#8220;just&#8221; takes 10 minutes off our day, or spending that extra dime was &#8220;just&#8221; a dime over our planned budget, or scrolling through Instagram for those extra 10 minutes between our meetings were &#8220;only&#8221; some useless 10 minutes we utilised.</p><p>In reality, all these things are not &#8220;just&#8221; what they look like on the surface. They represent things like not meeting our savings target at the end of the month, and then having lesser capital when we need it the most, or finding that we have spent a total of 60 minutes scrolling Instagram because some meetings started late and some ended early, or continually missing breakfast and not being able to work effectively because we were feeling hungry.</p><p>In &#8220;How Will You Measure Your Life?&#8221;, the authors talk about how we end up taking morally wrong decisions by misjudging the marginal cost because those incremental decisions &#8220;felt&#8221; so harmless.</p><blockquote><p>The marginal cost of doing something &#8220;just this once&#8221; always seems to be negligible, but the full cost will typically be much higher. Yet unconsciously, we will naturally employ the marginal-cost doctrine in our personal lives. A voice in our head says, &#8220;Look, I know that as a general rule, most people shouldn&#8217;t do this. But in this particular extenuating circumstance, just this once, it&#8217;s okay.&#8221; And the voice in our head seems to be right; the price of doing something wrong &#8220;just this once&#8221; usually appears alluringly low. It suckers you in, and you don&#8217;t see where that path is ultimately headed or the full cost that the choice entails.</p><p>How Will You Measure Your Life?</p></blockquote><p>What they are referring to as the &#8220;full cost&#8221; is actually the &#8220;full marginal cost&#8221; that we glazed over while making the decision. <strong>That is how we end up breaking our habits too</strong>! It&#8217;s okay if I go have a few drinks today as well, right? It&#8217;s okay if I didn&#8217;t read for an hour today like I had planned to. What is it going to cost?</p><h3>So, whenever you are going to break a habit &#8230;</h3><h4>Remember to live by the Rule of Rational Life</h4><p>Every time you are going to break a habit because of marginal thinking, ask yourself what is the &#8220;full marginal cost&#8221; of the decision, and what are the marginal benefits? If the cost outweighs the benefit, is it worth it?</p><p>The rule of rational life, as shared by Jerry German, is that &#8220;As long as the marginal benefit exceeds the marginal cost, do it.&#8221; Here, as long as you are judging the &#8220;marginal cost&#8221; and &#8220;marginal benefit&#8221; correctly, you would do okay :)</p><h4>And account for this additional cost every time</h4><p>Other than the full marginal cost of making a habit-breaking decision, there is another cost that you are incurring. <strong>You are actually making it easier for yourself to break the habit again.</strong></p><blockquote><p>It&#8217;s easier to hold to your principles 100 percent of the time than it is to hold to them 98 percent of the time. The boundary&#8212;your personal moral line&#8212;is powerful, because you don&#8217;t cross it; if you have justified doing it once, there&#8217;s nothing to stop you doing it again.</p><p><em><strong>How Will You Measure Your Life?</strong></em></p></blockquote><p>Even though the authors meant it for maintaining integrity, I think it is true for building good habits too: <strong>&#8220;100 percent of the time is easier than 98 percent of the time&#8221;</strong>.</p><div><hr></div><p><em>Follow the discussion on Reddit:</em> <em><a href="https://www.reddit.com/r/getdisciplined/comments/buu0mo/method_avoid_breaking_habits_because_of_falling/">/r/getdisciplined</a></em></p>]]></content:encoded></item><item><title><![CDATA[Why do we make irrational decisions?]]></title><description><![CDATA[We often get surprised by the decisions taken by those around us.]]></description><link>https://www.ketanbhatt.com/p/bounded-rationality</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/bounded-rationality</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Tue, 15 Jan 2019 00:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!N28U!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff056408d-6832-400e-919b-d7cb2821300d_590x393.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We often get surprised by the decisions taken by those around us. <em>&#8220;But how could you not see that this whole Bitcoin thing was a bubble?&#8221;</em>, or <em>&#8220;how can you pass on that job opportunity?&#8221;</em>, or <em>&#8220;did you not know that there are more parts that need to be built here, and so this project will take more than the estimated time?&#8220;.</em> Turns out, people make decisions based on their <strong>Bounded Rationality</strong> (and so do you).</p><blockquote><p>Bounded rationality means that people make quite reasonable decisions based on the information they have. But they don&#8217;t have perfect information, especially about more distant parts of the system.</p><p>Donella H. Meadows, <a href="https://www.goodreads.com/book/show/3828902-thinking-in-systems">Thinking in Systems</a></p></blockquote><p>As a simple example, think of a fishing ecosystem. The fishermen benefit directly from how many fishes they catch every day. Ideally, the fish population that is lost by fishing or other causes (pollution, ageing etc.) gets replenished by natural reproduction. But the fishermen start overfishing to maximise their benefits, bringing the fish population below a critical number and natural reproduction alone is no more sufficient to replenish the fish population. The fishermen end up destroying their own livelihood.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!N28U!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff056408d-6832-400e-919b-d7cb2821300d_590x393.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!N28U!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff056408d-6832-400e-919b-d7cb2821300d_590x393.jpeg 424w, https://substackcdn.com/image/fetch/$s_!N28U!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff056408d-6832-400e-919b-d7cb2821300d_590x393.jpeg 848w, https://substackcdn.com/image/fetch/$s_!N28U!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff056408d-6832-400e-919b-d7cb2821300d_590x393.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!N28U!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff056408d-6832-400e-919b-d7cb2821300d_590x393.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!N28U!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff056408d-6832-400e-919b-d7cb2821300d_590x393.jpeg" width="590" height="393" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f056408d-6832-400e-919b-d7cb2821300d_590x393.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:393,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Fishermen by a lake&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Fishermen by a lake" title="Fishermen by a lake" srcset="https://substackcdn.com/image/fetch/$s_!N28U!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff056408d-6832-400e-919b-d7cb2821300d_590x393.jpeg 424w, https://substackcdn.com/image/fetch/$s_!N28U!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff056408d-6832-400e-919b-d7cb2821300d_590x393.jpeg 848w, https://substackcdn.com/image/fetch/$s_!N28U!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff056408d-6832-400e-919b-d7cb2821300d_590x393.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!N28U!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff056408d-6832-400e-919b-d7cb2821300d_590x393.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><em>Fishermen tend to overfish to maximise their benefits. Photo by <a href="https://unsplash.com/photos/MhcWRAtSsro?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Iva Rajovi&#263;</a> on <a href="https://unsplash.com/search/photos/fishermen?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></em></figcaption></figure></div><p>Why couldn&#8217;t the fishermen foresee this? The more you fish, the lesser there will be left, isn&#8217;t that intuitive? No. Because the fishermen don&#8217;t know how much fish is there in the pond, or how many fishes will be caught by other fishermen the same day. They have a bounded view of the reality (the same example is also suitable for describing <a href="https://en.wikipedia.org/wiki/Tragedy_of_the_commons">&#8220;Tragedy of the Commons&#8221;</a>).</p><h2>We would probably take the same decisions we criticize right now</h2><p>However much we criticize the decisions made by someone else in a different position than ours, <strong>we tend to make the same decisions when put in their roles, subject to the same information flows, incentives, disincentives and goals as they are</strong>. As Donella Meadows notes, <em>&#8220;We teach this point by playing games in which students are put into situations in which they experience the realistic, partial information streams seen by various actors in real systems. As simulated fishermen, they over fish. As ministers of simulated developing nations, they favour the needs of their industries over the needs of their people. As the upper class, they feather their own nests; as the lower class, they become apathetic or rebellious. <strong>So would you.</strong>&#8221;</em></p><p>But:</p><blockquote><p><strong>Seeing how individual decisions are rational within the bounds of the information available does not provide an excuse for narrow-minded behaviour.</strong> It provides an understanding of why that behaviour arises. Within the bounds of what a person in that part of the system can see and know, the behaviour is reasonable. Taking out one individual from a position of bounded rationality and putting in another person is not likely to make much difference. <strong>Blaming the individual rarely helps create a more desirable outcome.</strong></p><p>Donella H. Meadows</p></blockquote><p>In order to not get surprised by the outcomes of our/others&#8217; &#8220;rational&#8221; decisions, I think it is valuble to:</p><ol><li><p>Keep in mind that the decision you are going to take might have effects you don&#8217;t know of/want because you have a bounded view of the problem. Go out of your way then to understand what other knowledge exists and how can you incorporate that in your decision.</p></li><li><p>When criticizing any decision (that feature that someone built?), make sure you take a moment to think of what all information was available when the decision was made (I touch on taking and giving feedback in <a href="https://ketanbhatt.com/2018/12/10/on-taking-feedback-well/">another blog post</a>).</p></li></ol><h2>Create systems that function well despite bounded rationality</h2><p>So does it mean that we will always end up making the wrong decisions? No. Some systems, like our liver/heart, are structured to function well despite bounded rationality. <strong>The right feedback gets to the right place at the right time.</strong> What if we could structure our teams/organisation in the same way? What if each of the stakeholders got at least all the information they need to make an &#8220;informed&#8221; decision? Doesn&#8217;t that also explain why we call for diversity in teams? Because diversity is one way of ensuring you get a lot of different perspectives/information flows that help you make better, more inclusive, decisions as a team.</p><blockquote><p><strong>The bounded rationality of each actor in a system may or may not lead to decisions that further the welfare of the system as a whole.</strong> If they do not, putting new actors into the same system will not improve the system&#8217;s performance. <strong>What makes a difference is redesigning the system to improve the information, incentives,</strong> disincentives, goals, stresses, <strong>and constraints that have an effect on specific actors.</strong></p><p>Donella H. Meadows</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!g6EE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b5f83-8d2c-495b-bd8a-9b464a056b9c_590x393.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!g6EE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b5f83-8d2c-495b-bd8a-9b464a056b9c_590x393.jpeg 424w, https://substackcdn.com/image/fetch/$s_!g6EE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b5f83-8d2c-495b-bd8a-9b464a056b9c_590x393.jpeg 848w, https://substackcdn.com/image/fetch/$s_!g6EE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b5f83-8d2c-495b-bd8a-9b464a056b9c_590x393.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!g6EE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b5f83-8d2c-495b-bd8a-9b464a056b9c_590x393.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!g6EE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b5f83-8d2c-495b-bd8a-9b464a056b9c_590x393.jpeg" width="590" height="393" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/129b5f83-8d2c-495b-bd8a-9b464a056b9c_590x393.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:393,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Fishes in a lake&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Fishes in a lake" title="Fishes in a lake" srcset="https://substackcdn.com/image/fetch/$s_!g6EE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b5f83-8d2c-495b-bd8a-9b464a056b9c_590x393.jpeg 424w, https://substackcdn.com/image/fetch/$s_!g6EE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b5f83-8d2c-495b-bd8a-9b464a056b9c_590x393.jpeg 848w, https://substackcdn.com/image/fetch/$s_!g6EE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b5f83-8d2c-495b-bd8a-9b464a056b9c_590x393.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!g6EE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129b5f83-8d2c-495b-bd8a-9b464a056b9c_590x393.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><em>Nature often creates self-regulatory systems. Photo by <a href="https://unsplash.com/photos/3Fbk3q1tMaQ?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Charles Postiaux</a> on <a href="https://unsplash.com/search/photos/fish?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></em></figcaption></figure></div><h2>In closing</h2><p>The concept is simple, and we might also have an understanding of it on the subconscious level, but having a term for it in our vocabulary means we are more aware of its existence, and therefore much more enabled to account for it.</p><blockquote><p>To paraphrase a common prayer: God grant us the serenity to exercise our bounded rationality freely in the systems that are structured appropriately, the courage to restructure the systems that aren&#8217;t, and the wisdom to know the difference!</p><p>Donella H. Meadows</p></blockquote>]]></content:encoded></item><item><title><![CDATA[On taking feedback well]]></title><description><![CDATA[A colleague and I were discussing how it is difficult to take critical feedback well.]]></description><link>https://www.ketanbhatt.com/p/feedback</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/feedback</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Mon, 10 Dec 2018 00:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!adSA!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e37e6d-f289-4a21-95bd-008a89277566_426x426.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A colleague and I were discussing how it is difficult to take critical feedback well. The conversation was textual and I ended up writing a lot of thoughts. In a bid to <a href="https://jamesclear.com/keystrokes">save my keystrokes from dying</a>, I thought I will pen down my thoughts here so that I can share them with peeps whenever I want to, plus it makes it easier for me to come back to these ideas.</p><p>Before we begin, this doesn&#8217;t mean I have perfected this. These are just some ideas I try to keep in mind to make sure I am not unnecessarily defending myself or my actions. All of us falter.</p><h2>This: Avoid having an emotional reaction to feedback</h2><blockquote><p>You will continue to suffer if you have an emotional reaction to everything that is said to you. True power is sitting back and observing things with logic. True power is restraint. If words control you that means everyone else can control you. Breathe and allow things to pass.</p><p>Anonymous</p></blockquote><p>Thank god for the internet,&nbsp;<a href="https://www.inc.com/justin-bariso/fake-warren-buffetts-twitter-feed-is-suddenly-sharing-secrets-to-a-fulfilling-life.html">and impostor accounts</a>! This quote has become a go-to for me whenever I feel that I am getting affected too much by what people have to say to me. Read it slooooooowly, and absoooooooorb it.</p><p>Based on this insight, I started giving myself time to absorb critical feedback. A very common scenario is receiving a code review. Sometimes when I find myself having an emotional reaction to a review, I will just go through all the comments and not respond. I will take some time (maybe a few hours) and then come back to the review. Having the time to absorb the feedback and getting my emotions out of the way, I find myself become more accepting of both objective and subjective suggestions, and just be a nicer person overall &#128556;</p><h2>It is okay to be wrong</h2><p>Phew. The pressure! I forget this often (maybe you too?).&nbsp;</p><p>You proposed an idea, but the idea had loopholes. It is okay to propose bad ideas. You suggested a design, but the design will break for these edge cases. It is okay that you missed thinking of those edge cases. You acted a certain way, and it wasn&#8217;t the best that could have been done. It is okay.</p><blockquote><p>It is completely okay to be wrong. Once I understand that, I calm down.</p></blockquote><p>Let&#8217;s say I am leading a team. I might feel that if I am wrong, I will be respected less (not a hypothetical situation). But on the contrary, accepting that I might have been wrong lets people come closer. Because now there is no pressure on them as well to be always right, and then they can voice their opinions and ideas unfiltered, and that&#8217;s how good solutions are found, right?</p><h2>Let go of the Ego</h2><p>Another reason that stops us from accepting a mistake is Ego. Ego doesn&#8217;t let us say, &#8220;Oops, I totally missed that!&#8220;. The trick (I think), is to know that ego creeps up unannounced, and it is pertinent that you listen to the other person, without reacting to the words like it is an attack on you (<a href="https://ketanbhatt.com/2018/08/12/dont-be-the-alpha-geek-your-team-deserves-better/">&#8220;Don&#8217;t be the Alpha Geek&#8221;</a>).</p><p>Ryan Holiday&#8217;s <a href="https://www.goodreads.com/book/show/27036528-ego-is-the-enemy">Ego is the Enemy</a> is a good read for creating the awareness.</p><h2>Hear out the other side</h2><p>Another thing I do is, I try to see where the other person might be coming from. As in, why does the person think they are right or what they are suggesting is fair. If you understand the motive behind someone&#8217;s actions, it is also easy to reason with them, and/or understand their point (<em>the idea was discussed in an unrelated book:</em> <em><a href="https://www.goodreads.com/book/show/26156469-never-split-the-difference">Never Split the Difference</a></em>).&nbsp;</p><p>Related to this is the idea that if you want to buy something you already like, let&#8217;s say the iPhone, don&#8217;t search for &#8220;why should I buy the iPhone&#8221;. Rather, search for &#8220;why should I <em><strong>not</strong></em> buy the iPhone?&#8221; because that is a perspective you don&#8217;t have.&nbsp;Once you understand the arguments from both the sides, you can now make a more informed decision.</p><p>Learning from this, when someone disagrees with me, I try to think &#8220;What might be the reason that makes this person believe they are right? What information/insight do they have that I might be lacking?&#8220;. This also helps me ask better questions to clarify the opposing argument. This is much better than thinking &#8220;Why can&#8217;t they understand my argument?&#8220;.</p><p>I will be happy to know about what you do to take critical feedback well, and if you think I need to relearn a few things.</p>]]></content:encoded></item><item><title><![CDATA[Don’t be the Alpha Geek: Your team deserves better]]></title><description><![CDATA[Alpha Geek?]]></description><link>https://www.ketanbhatt.com/p/alpha-geek</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/alpha-geek</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Sun, 12 Aug 2018 00:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!adSA!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e37e6d-f289-4a21-95bd-008a89277566_426x426.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Alpha Geek?</h2><p>I recently came across the term &#8220;Alpha Geek&#8221; in &#8220;<a href="https://www.goodreads.com/book/show/33369254-the-manager-s-path">The Manager&#8217;s Path</a>&#8221;.</p><blockquote><p>The Alpha geek is usually an excellent, effective engineer who is driven to always have the right answers, and solve hard problems.</p></blockquote><p>Alrighty! I want to be one! Where do I sign up?</p><blockquote><p>He knows exactly why that thing you&#8217;re trying to do won&#8217;t work, and when it doesn&#8217;t, believe me, he&#8217;ll remind you how he told you so. If only you had listened to him and done things his way! At their worst, alpha geeks can&#8217;t let anyone else get any glory without claiming some of it for themselves. They are origin of any good ideas but had no part in creating the bad ideas, except that they knew they would fail.</p></blockquote><p>Ummm&#8230; I don&#8217;t&#8230; think&#8230; I wanted&#8230; this?</p><blockquote><p>The alpha geek can be very rigid about how things should be done and closed off to new ideas that he didn&#8217;t come up with. Alpha geeks can&#8217;t stand people complaining about systems they built or criticize their past technical decision&#8230; Are you eagerly seeking out the gotcha, hunting for mistakes, reluctant to admit that someone else has had a good idea or has written good code?</p></blockquote><p>Ok, wait. Am I one? <em><strong>Do I come out as one?</strong></em></p><p>I love design discussions, I love contesting solutions and have had some very livid arguments with my colleagues on subjective choices. Do I come out as rigid? Someone who takes offence if his mistakes are pointed out?</p><h2>Revelation</h2><p>After getting introduced to the concept, I am now actively thinking if I accept my mistakes or not. It is a fun exercise. It is not like I think I am an alpha geek, but I think I might be unconsciously exhibiting some of the bad characteristics of one. <strong>And the problem here is, you will never know you are becoming one until it is too late.</strong></p><p>As my friend, and colleague, <a href="https://priyankvex.wordpress.com/">Priyank Verma</a> pointed out:</p><blockquote><p><strong>We have this partial knowledge of the world, but we have formed very strong opinions based on this partial knowledge. We haven&#8217;t been here long enough to see how our decisions in the past have played out (has that multiple inheritance thingy we did last year, which looked super sexy at that time, made it a mess to change stuff in future?). And then we contest design and subjective decisions based on these strong opinions.</strong></p></blockquote><p><strong>And he is right.</strong></p><p>I do tend to defend my decisions around code and design and give excuses, where in reality I could have just said that a) I couldn&#8217;t think of it at that time, or b) I was wrong.</p><p>Although I am not <em>that</em> bad, trust me. If someone does prove objectively as to how this other way is better, I happily accept it. I tell the person he is right and the suggested solution is much better. Yes, let&#8217;s do this! But in the moments before getting proven wrong, do I behave rigidly like I am the all-knowing senior engineer who is often right?</p><h2>Changing the way I go about discussions</h2><p>Rigid. This is not who I am, and I don&#8217;t want to be coming off like this unconsciously as well. So now whenever I get into a discussion, I am actively evaluating my stance:</p><ol><li><p>Am I right, or am I defending myself?</p></li><li><p><strong>Am I right, or am I just scared to say I was wrong?</strong></p></li><li><p>Am I right, or am I biased against the other person?</p></li></ol><p>Another area where I have started doing this is <strong>Code Reviews. I realised that, more often than not, I took the review comments a little too personally</strong>, or was affected by them. Which isn&#8217;t needed or right. <strong>I give hard feedback, I will get it back too, there is nothing personal about it.</strong></p><p>At their worst, alpha geeks are like a bad fish in the pond. The rotten apple that can damage the whole crop. You owe it to the people you work with to create and preserve a culture where everyone is open to new ideas and finding the best way out, together :)</p><p>Here&#8217;s to better designs and healthier discussions &#127867;</p><p><em>(Originally <a href="https://hackernoon.com/dont-be-the-alpha-geek-your-team-deserves-better-28f97630e89e">posted on my Medium account</a>)</em></p>]]></content:encoded></item><item><title><![CDATA[How I stopped being awful at managing: Leadership lessons from a Dev]]></title><description><![CDATA[At Squad, we have been following the concept of having small inter-disciplinary teams, which we call Solver Teams (the concept is beautifully explained by Spotify, we took a lot of inspiration from them).]]></description><link>https://www.ketanbhatt.com/p/leadership-lessons</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/leadership-lessons</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Sun, 06 May 2018 00:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!zCXx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295707a2-cb25-4d76-b2de-16c5ac9d444d_590x590.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>At <a href="https://www.squadplatform.com/">Squad</a>, we have been following the concept of having small inter-disciplinary teams, which we call Solver Teams (the concept is beautifully <a href="https://labs.spotify.com/2014/03/27/spotify-engineering-culture-part-1/">explained by Spotify</a>, we took a lot of inspiration from them). As fate would have it, I got to lead a team responsible for building the Machine Learning platform for Squad, from scratch.</p><p>While I have been working on large projects for over 2 years now, this was my first time taking on a more formal leadership role. A solver team lead is more a Project Manager than a People Manager, so I was not just managing, but also executing. This probably made the much-dreaded transition easier. I enjoyed and learnt a great deal these past few months and <strong>I totally sucked in the beginning</strong>. But I am glad to announce that by the end I was much less awful than I was at the start.</p><p>How did I become less awful? By learning from my own experience, but more so from the feedback that I got from the team. I recently collated these lessons and 1&#8211;1 notes during my quarterly retrospection (ignore the fact that it was my first time trying it, and stay impressed), and realised that it would make for a good read for people who are/were/will be in the same boat as me.</p><p>Here are some ideas I found useful:</p><h2>Nurture an environment where all ideas are worth sharing</h2><p>A team is a cocktail of all the different personalities that the members carry. Not everyone will be equally comfortable in sharing an idea they might have. Some people might not be confident about their ideas (which might not be a correct judgement on their part), some would avoid speaking unless directly spoken to, while some are not just used to speaking amongst that particular group of people yet (maybe it&#8217;s a new team). Despite all this, how do you get everyone to share ideas?</p><p>What I have learnt is that <strong>you can get people to share ideas that they have by asking the right questions</strong>. Questions that I frequently ask, when we have a problem at hand, are:</p><ol><li><p>What do you suggest we do?</p></li><li><p>What do you think are some ways we can solve it?</p></li><li><p>Do you think there is a case where the solution will fail?</p></li></ol><p><strong>Ask these questions, and then pause</strong>. Give them time to think. More than that, <strong>let silence encourage the other person to speak</strong>. Chris Voss describes this as &#8220;<a href="http://blog.blackswanltd.com/the-edge/how-to-use-silence-to-your-advantage">effective pause</a>&#8221; in his book &#8220;<a href="https://www.goodreads.com/book/show/26156469-never-split-the-difference">Never Split the Difference</a>&#8221;. It is not in the same context, but I have seen it work because the premise remains the same. Effective pauses make people gather their thoughts and think. A typical conversation at the office goes like:</p><blockquote><p><strong>John Doe (hypothetical teammate)</strong>: So, there is this issue. What do you think should be done?</p><p><strong>Ketan Bhatt</strong>: Interesting&#8230; What do you think should be done?</p><p><strong>JD</strong>: &#8230;?</p><p><strong>KB</strong>: &#8230; :)</p><p><strong>JD</strong>: &#8230;</p><p><strong>KB</strong>: &#8230;</p><p><strong>JD</strong>: Well&#8230; I guess we could try doing X?</p><p><strong>KB</strong>: Makes sense, what do you think the situation would look like if we do X?</p><p><strong>JD</strong>: There might be some issues with this particular thing, but that can be handled if we do a little tweak to X.</p><p><strong>KB</strong>: Looks good, will the design hold when we also have to do this other thing we had in the backlog?</p><p><strong>JD</strong>: Hmm, let me think more and get back to you?</p><p><strong>KB</strong>: Sure!</p></blockquote><p>Everyone who faces a problem also knows at least one way to solve it. Sometimes that way is all that is needed, sometimes it might need some tweaking. Otherwise, you can always suggest something you think might work and then discuss the solution further.</p><p>You do this enough number of times, people automatically start doing this on their own, refining their ideas, and then finally coming to you to share what they have got. And since now there are more people sharing their ideas, your solution bank just grew larger, and the quality of your team&#8217;s problem solving, as a unit, goes up. Woot woot!</p><h2>Build an Event Loop</h2><p>We have all been there. The project kicks off with meticulous plans, timelines, and metrics that we vow to keep track of. A few weeks into the quarter, the timelines and the metrics aren&#8217;t being tracked with the same punctuality, if at all. I fell victim to this as well.</p><p>We, as engineers, have an inherent inclination towards execution. It becomes easy to forget/procrastinate updating the timelines, backlog, skip stand-ups, planning for the coming weeks, etc. What follows is a loss of visibility:</p><ol><li><p>Your team doesn&#8217;t know where it is heading, will we meet our goals, are we on the right track?</p></li><li><p>The other teams lose sight over what is going on with your team</p></li><li><p>You catch hold of issues, that can jeopardise the goals, too late. Course correction becomes harder.</p></li></ol><p>Learning from mistakes and guidance from my lead, <strong>here is a suggestion: build yourself an <a href="https://s3.amazonaws.com/marquee-test-akiaisur2rgicbmpehea/dgVXCQ87Ry2aeL7OKJyu_Screen%20Shot%202015-07-16%20at%2011.43.16%20AM.png">Event Loop</a>, and block your time explicitly for following it</strong>. We got this from a <a href="http://firstround.com/review/this-90-day-plan-turns-engineers-into-remarkable-managers/">FirstRound Post by David Loftesness</a> which is a must read if you are making the transition for a developer to a managerial role.</p><p>A lot of us follow something of this sort already, I found that making it explicit just works better for me.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LYj4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf637888-edef-4dbc-8022-51490e96d5c7_590x153.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LYj4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf637888-edef-4dbc-8022-51490e96d5c7_590x153.png 424w, https://substackcdn.com/image/fetch/$s_!LYj4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf637888-edef-4dbc-8022-51490e96d5c7_590x153.png 848w, https://substackcdn.com/image/fetch/$s_!LYj4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf637888-edef-4dbc-8022-51490e96d5c7_590x153.png 1272w, https://substackcdn.com/image/fetch/$s_!LYj4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf637888-edef-4dbc-8022-51490e96d5c7_590x153.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LYj4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf637888-edef-4dbc-8022-51490e96d5c7_590x153.png" width="590" height="153" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cf637888-edef-4dbc-8022-51490e96d5c7_590x153.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:153,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;My Event Loop&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="My Event Loop" title="My Event Loop" srcset="https://substackcdn.com/image/fetch/$s_!LYj4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf637888-edef-4dbc-8022-51490e96d5c7_590x153.png 424w, https://substackcdn.com/image/fetch/$s_!LYj4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf637888-edef-4dbc-8022-51490e96d5c7_590x153.png 848w, https://substackcdn.com/image/fetch/$s_!LYj4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf637888-edef-4dbc-8022-51490e96d5c7_590x153.png 1272w, https://substackcdn.com/image/fetch/$s_!LYj4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf637888-edef-4dbc-8022-51490e96d5c7_590x153.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">My Event Loop</figcaption></figure></div><h2>Balance thy sprint well</h2><p>My learnings are perfectly explained by Alan Page, Quality Director at Unity, in <a href="https://github.com/angryweasel/managerreadme#3-improvement-is-a-prioirity">his Manager Readme</a>. Specifically:</p><blockquote><p><em><strong>The ACM framework</strong></em> <em>(Ambitious, Comfortable, Mundane). The idea is that if you look at the work you do over a week / sprint, some of that work is new, challenging, or ambitious, a big chunk of work is stuff that you&#8217;re just really good at (comfortable work), and you may end up with some work that you&#8217;re overqualified for, or is boring, but that just needs to get done (mundane).</em></p></blockquote><blockquote><p><em>We should work together to make sure you have enough ambitious work that you are challenged and growing, if you&#8217;re not learning something new every week, that&#8217;s something we should work on together. We also want to minimize your mundane work. Often, your mundane work may be someone else&#8217;s ambitious work.</em></p></blockquote><p>Vikas Gulati, CTO at Squad and my mentor, often points to the following diagram, from <a href="http://firstround.com/review/track-and-facilitate-your-engineers-flow-states-in-this-simple-way/">this First Review post</a>, for explaining roughly the same thing:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zCXx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295707a2-cb25-4d76-b2de-16c5ac9d444d_590x590.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zCXx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295707a2-cb25-4d76-b2de-16c5ac9d444d_590x590.jpeg 424w, https://substackcdn.com/image/fetch/$s_!zCXx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295707a2-cb25-4d76-b2de-16c5ac9d444d_590x590.jpeg 848w, https://substackcdn.com/image/fetch/$s_!zCXx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295707a2-cb25-4d76-b2de-16c5ac9d444d_590x590.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!zCXx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295707a2-cb25-4d76-b2de-16c5ac9d444d_590x590.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zCXx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295707a2-cb25-4d76-b2de-16c5ac9d444d_590x590.jpeg" width="590" height="590" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/295707a2-cb25-4d76-b2de-16c5ac9d444d_590x590.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:590,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Skills vs Challenge - The \&quot;Flow\&quot; graph&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Skills vs Challenge - The &quot;Flow&quot; graph" title="Skills vs Challenge - The &quot;Flow&quot; graph" srcset="https://substackcdn.com/image/fetch/$s_!zCXx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295707a2-cb25-4d76-b2de-16c5ac9d444d_590x590.jpeg 424w, https://substackcdn.com/image/fetch/$s_!zCXx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295707a2-cb25-4d76-b2de-16c5ac9d444d_590x590.jpeg 848w, https://substackcdn.com/image/fetch/$s_!zCXx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295707a2-cb25-4d76-b2de-16c5ac9d444d_590x590.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!zCXx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295707a2-cb25-4d76-b2de-16c5ac9d444d_590x590.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Skills vs Challenge</figcaption></figure></div><p>It is common knowledge that any good developer won&#8217;t be too happy if they don&#8217;t see themselves growing. But an overlooked fact is that growing is a continuous process, and not a badge that you get by working hard for a month. <strong>It is easy to burn out people by assigning ambitious tasks to them one after the another (with good intentions on your part)</strong>, and it is often too late by the time one realises that some irreversible damage has been dealt. <strong>Another important aspect is gratification</strong>. Some tasks are challenging but provide gratification early, while for some it might come in much later.</p><p>Your sprint is like a dessert. Too much sugar? Thank you for the Diabetes. Too little? Is it even a dessert? And what about the nuts and cherries? Can&#8217;t have too much or too few of them as well. The perfect dessert is just the right amount of sweet, with nuts and cherries that randomly surprise you when you take a bite.</p><p><strong>A sprint, much like a dessert, should be a good mix of challenging and comfortable work, with wins spread for good measure :)</strong></p><p>These lessons might be just common sense to some (you have my respect people), but I felt that knowing these things explicitly just helped me channel the team&#8217;s strength in a better manner, and, thus, do justice to their talents.</p><p><em>(Originally <a href="https://hackernoon.com/how-i-stopped-being-awful-at-managing-leadership-lessons-from-a-dev-d1bfebcb3a21">posted on my Medium account</a>)</em></p>]]></content:encoded></item><item><title><![CDATA[Configure Postgres statement_timeout from within Django]]></title><description><![CDATA[In a bid to prepare ourselves for projected growth, we are at the moment trying to figure out what part of our system will break at what scale, and how.]]></description><link>https://www.ketanbhatt.com/p/postgres-django-timeout</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/postgres-django-timeout</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Mon, 02 Apr 2018 00:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!nNCQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1aff45e-209c-453f-a0bb-3ff1c7074f57_590x393.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nNCQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1aff45e-209c-453f-a0bb-3ff1c7074f57_590x393.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nNCQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1aff45e-209c-453f-a0bb-3ff1c7074f57_590x393.jpeg 424w, https://substackcdn.com/image/fetch/$s_!nNCQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1aff45e-209c-453f-a0bb-3ff1c7074f57_590x393.jpeg 848w, https://substackcdn.com/image/fetch/$s_!nNCQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1aff45e-209c-453f-a0bb-3ff1c7074f57_590x393.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!nNCQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1aff45e-209c-453f-a0bb-3ff1c7074f57_590x393.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nNCQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1aff45e-209c-453f-a0bb-3ff1c7074f57_590x393.jpeg" width="590" height="393" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b1aff45e-209c-453f-a0bb-3ff1c7074f57_590x393.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:393,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A close-up of white dials on a music mixer&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A close-up of white dials on a music mixer" title="A close-up of white dials on a music mixer" srcset="https://substackcdn.com/image/fetch/$s_!nNCQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1aff45e-209c-453f-a0bb-3ff1c7074f57_590x393.jpeg 424w, https://substackcdn.com/image/fetch/$s_!nNCQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1aff45e-209c-453f-a0bb-3ff1c7074f57_590x393.jpeg 848w, https://substackcdn.com/image/fetch/$s_!nNCQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1aff45e-209c-453f-a0bb-3ff1c7074f57_590x393.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!nNCQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1aff45e-209c-453f-a0bb-3ff1c7074f57_590x393.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><em>&#8220;A close-up of white dials on a music mixer&#8221; by <a href="https://unsplash.com/@kartochky?utm_source=medium&amp;utm_medium=referral">Alexey Ruban</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></em></figcaption></figure></div><p>In a bid to prepare ourselves for projected growth, we are at the moment trying to figure out what part of our system will break at what scale, and how. One step towards this was to also define strict timeouts for our database queries, and eliminate/fix bad queries in the process.</p><h2>The problem</h2><p>Our requirements were:</p><ol><li><p>Be able to define different timeout values for different types of servers (app servers, analytics etc.)</p></li><li><p>The different limits should be well represented in the code so that they&#8217;re easy to discover, even by people who join our team in the future</p></li><li><p>It should be easy and quick to modify these limits</p></li></ol><p>We identified multiple sources of our queries. Each of these might need a different query timeout. These sources are:</p><ol><li><p><strong>App servers</strong>: queries that run for our frontend facing APIs, like APIs that our Android app or clients use</p></li><li><p><strong>Celery servers</strong>: queries made by our celery tasks that run asynchronously</p></li><li><p><strong>Cron servers</strong>: queries made as part of crons</p></li><li><p><strong>Alerts</strong>: we have a system that runs SQL queries at configured time intervals, and pushes the data (results of the queries) to relevant people (over Slack)</p></li><li><p><strong>Analytics</strong>: queries that run as a part of our ETL (v0.1) system</p></li></ol><p>We planned to incrementally reduce the timeouts because at every step/iteration, there will be queries which will not be able to run properly within the planned timeout. We will have to fix all those queries before we reduce the timeout. The incremental limits we defined for each iterations were:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ow_G!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82f9d3c7-9807-42da-bae6-448a63bfc21b_590x127.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ow_G!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82f9d3c7-9807-42da-bae6-448a63bfc21b_590x127.png 424w, https://substackcdn.com/image/fetch/$s_!ow_G!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82f9d3c7-9807-42da-bae6-448a63bfc21b_590x127.png 848w, https://substackcdn.com/image/fetch/$s_!ow_G!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82f9d3c7-9807-42da-bae6-448a63bfc21b_590x127.png 1272w, https://substackcdn.com/image/fetch/$s_!ow_G!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82f9d3c7-9807-42da-bae6-448a63bfc21b_590x127.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ow_G!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82f9d3c7-9807-42da-bae6-448a63bfc21b_590x127.png" width="590" height="127" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/82f9d3c7-9807-42da-bae6-448a63bfc21b_590x127.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:127,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Timeout planned for each iteration&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Timeout planned for each iteration" title="Timeout planned for each iteration" srcset="https://substackcdn.com/image/fetch/$s_!ow_G!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82f9d3c7-9807-42da-bae6-448a63bfc21b_590x127.png 424w, https://substackcdn.com/image/fetch/$s_!ow_G!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82f9d3c7-9807-42da-bae6-448a63bfc21b_590x127.png 848w, https://substackcdn.com/image/fetch/$s_!ow_G!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82f9d3c7-9807-42da-bae6-448a63bfc21b_590x127.png 1272w, https://substackcdn.com/image/fetch/$s_!ow_G!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82f9d3c7-9807-42da-bae6-448a63bfc21b_590x127.png 1456w" sizes="100vw"></picture><div></div></div></a><figcaption class="image-caption"><em>Timeout planned for each iteration</em></figcaption></figure></div><p>This is a no-brainer <em><strong>if</strong></em> your requirements are simple. You can simply <a href="https://www.postgresql.org/docs/9.6/static/sql-createrole.html">create roles</a> in the database and <a href="https://www.postgresql.org/docs/9.6/static/sql-alterrole.html">set different timeouts for them</a>.</p><p>Our backend is built using Django, and to accomplish this we would have to</p><ol><li><p>Write a raw SQL migration to create the roles (if needed), and</p></li><li><p>Alter them to set the appropriate timeout</p></li><li><p>Set the <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#databases">database dictionary</a> differently for different server in Django settings with the correct role and passwords</p></li></ol><p><strong>Why not just directly log in to the shell and do this?</strong> Because then this change isn&#8217;t represented in the code and creates gaps in knowledge over time. But, <em>even though</em> migrations are part of the code, they are just for change management, and rarely does someone go back to migrations to look for &#8220;logic&#8221; affecting your app&#8217;s behaviour.</p><p>Since we were planning on having multiple iterations, and there would be a lot of back and forth between the timeout limits while we are experimenting, it would become a hassle to write migrations and apply them every time something had to be changed. <em>This solution was not for us.</em></p><h2>The solution</h2><p>We started thinking of a better way to accomplish what we had in mind.</p><p>We knew the &#8220;ease of configuration&#8221; would <em>only</em> come if we can set the timeouts from within Django somehow. Thinking more in this direction and connecting little tidbits we were aware about Django and Postgres, we realized that:</p><ol><li><p>One can set a timeout using <code>SET</code> inside a Postgres session which is then adhered to until the end of the current session using: <code>SET statement_timeout=&lt;x&gt;;</code>.</p></li><li><p>Django publishes a <a href="https://docs.djangoproject.com/en/1.11/ref/signals/#connection-created"><code>connection_created</code></a>&nbsp;signal every time a new database connection is created. This connection is then <a href="https://docs.djangoproject.com/en/1.11/ref/databases/#general-notes">put in the Connection Pool</a> from where it can be reused (governed by configuration parameters like <a href="https://docs.djangoproject.com/en/1.11/ref/settings/#std:setting-CONN_MAX_AGE"><code>CONN_MAX_AGE</code></a>)</p></li></ol><p>Aha! Can&#8217;t we just catch the connection as soon as it is created, and set the timeout to whatever we desire for the session? Yes we can :)</p><div class="github-gist" data-attrs="{&quot;innerHTML&quot;:&quot;<div id=\&quot;gist88600903\&quot; class=\&quot;gist\&quot;>\n    <div class=\&quot;gist-file\&quot; translate=\&quot;no\&quot; data-color-mode=\&quot;light\&quot; data-light-theme=\&quot;light\&quot;>\n      <div class=\&quot;gist-data\&quot;>\n        <div class=\&quot;js-gist-file-update-container js-task-list-container\&quot;>\n  <div id=\&quot;file-db_timeouts-py\&quot; class=\&quot;file my-2\&quot;>\n    \n    <div itemprop=\&quot;text\&quot;\n      class=\&quot;Box-body p-0 blob-wrapper data type-python  \&quot;\n      style=\&quot;overflow: auto\&quot; tabindex=\&quot;0\&quot; role=\&quot;region\&quot;\n      aria-label=\&quot;db_timeouts.py content, created by ketanbhatt on 10:55AM on March 25, 2018.\&quot;\n    >\n\n        \n<div class=\&quot;js-check-bidi js-blob-code-container blob-code-content\&quot;>\n\n  <template class=\&quot;js-file-alert-template\&quot;>\n  <div data-view-component=\&quot;true\&quot; class=\&quot;flash flash-warn flash-full d-flex flex-items-center\&quot;>\n  <svg aria-hidden=\&quot;true\&quot; height=\&quot;16\&quot; viewBox=\&quot;0 0 16 16\&quot; version=\&quot;1.1\&quot; width=\&quot;16\&quot; data-view-component=\&quot;true\&quot; class=\&quot;octicon octicon-alert\&quot;>\n    <path d=\&quot;M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\&quot;></path>\n</svg>\n    <span>\n      This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.\n      <a class=\&quot;Link--inTextBlock\&quot; href=\&quot;https://github.co/hiddenchars\&quot; target=\&quot;_blank\&quot;>Learn more about bidirectional Unicode characters</a>\n    </span>\n\n\n  <div data-view-component=\&quot;true\&quot; class=\&quot;flash-action\&quot;>        <a href=\&quot;{{ revealButtonHref }}\&quot; data-view-component=\&quot;true\&quot; class=\&quot;btn-sm btn\&quot;>    Show hidden characters\n</a>\n</div>\n</div></template>\n<template class=\&quot;js-line-alert-template\&quot;>\n  <span aria-label=\&quot;This line has hidden Unicode characters\&quot; data-view-component=\&quot;true\&quot; class=\&quot;line-alert tooltipped tooltipped-e\&quot;>\n    <svg aria-hidden=\&quot;true\&quot; height=\&quot;16\&quot; viewBox=\&quot;0 0 16 16\&quot; version=\&quot;1.1\&quot; width=\&quot;16\&quot; data-view-component=\&quot;true\&quot; class=\&quot;octicon octicon-alert\&quot;>\n    <path d=\&quot;M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\&quot;></path>\n</svg>\n</span></template>\n\n  <table data-hpc class=\&quot;highlight tab-size js-file-line-container\&quot; data-tab-size=\&quot;8\&quot; data-paste-markdown-skip data-tagsearch-path=\&quot;db_timeouts.py\&quot;>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L1\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;1\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC1\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-k>from</span> <span class=pl-s1>django</span>.<span class=pl-s1>conf</span> <span class=pl-k>import</span> <span class=pl-s1>settings</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L2\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;2\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC2\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L3\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;3\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC3\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c># NOTE</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L4\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;4\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC4\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c># The timeout values here only restrict the roles from application code. The actual timeout set in the DB could</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L5\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;5\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC5\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c># be a different value.</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L6\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;6\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC6\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c># Timeout set in DB on 27/03/2018 is 50s.</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L7\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;7\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC7\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L8\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;8\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC8\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c1>DEFAULT_DB_TIMEOUT_IN_MS</span> <span class=pl-c1>=</span> <span class=pl-c1>50000</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L9\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;9\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC9\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L10\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;10\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC10\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-s1>default_conn</span> <span class=pl-c1>=</span> <span class=pl-s1>settings</span>.<span class=pl-c1>DJANGO_DEFAULT_DB_CONNECTION_NAME</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L11\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;11\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC11\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-s1>explorer_conn</span> <span class=pl-c1>=</span> <span class=pl-s1>settings</span>.<span class=pl-c1>EXPLORER_CONNECTION_NAME</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L12\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;12\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC12\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-s1>slave_conn</span> <span class=pl-c1>=</span> <span class=pl-s1>settings</span>.<span class=pl-c1>SLAVE_NAME</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L13\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;13\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC13\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L14\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;14\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC14\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L15\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;15\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC15\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c1>DB_IDENTIFIER_AND_CONNECTION_TO_TIMEOUT_MAP</span> <span class=pl-c1>=</span> {</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L16\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;16\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC16\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-s1>settings</span>.<span class=pl-c1>PROD_APP_DB_TIMEOUT_IDENTIFIER</span>: {</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L17\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;17\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC17\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-s1>default_conn</span>: <span class=pl-c1>20000</span>,</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L18\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;18\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC18\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-s1>explorer_conn</span>: <span class=pl-c1>50000</span>,</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L19\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;19\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC19\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-s1>slave_conn</span>: <span class=pl-c1>20000</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L20\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;20\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC20\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    },</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L21\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;21\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC21\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-s1>settings</span>.<span class=pl-c1>PROD_CELERY_DB_TIMEOUT_IDENTIFIER</span>: {</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L22\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;22\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC22\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-s1>default_conn</span>: <span class=pl-c1>50000</span>,</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L23\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;23\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC23\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-s1>explorer_conn</span>: <span class=pl-c1>50000</span>,</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L24\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;24\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC24\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-s1>slave_conn</span>: <span class=pl-c1>50000</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L25\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;25\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC25\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    },</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L26\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;26\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC26\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-s1>settings</span>.<span class=pl-c1>DEFAULT_DB_TIMEOUT_IDENTIFIER</span>: {</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L27\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;27\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC27\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-s1>default_conn</span>: <span class=pl-c1>50000</span>,</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L28\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;28\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC28\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-s1>explorer_conn</span>: <span class=pl-c1>50000</span>,</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L29\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;29\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC29\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-s1>slave_conn</span>: <span class=pl-c1>50000</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L30\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;30\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC30\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    },</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-db_timeouts-py-L31\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;31\&quot;></td>\n          <td id=\&quot;file-db_timeouts-py-LC31\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>}</td>\n        </tr>\n  </table>\n</div>\n\n\n    </div>\n\n  </div>\n</div>\n\n      </div>\n      <div class=\&quot;gist-meta\&quot;>\n        <a href=\&quot;https://gist.github.com/ketanbhatt/730b86ebece1aa91ebbf2b6182163bb8/raw/ddccb5120701c06cd0415a648e3cfe4e090653d7/db_timeouts.py\&quot; style=\&quot;float:right\&quot; class=\&quot;Link--inTextBlock\&quot;>view raw</a>\n        <a href=\&quot;https://gist.github.com/ketanbhatt/730b86ebece1aa91ebbf2b6182163bb8#file-db_timeouts-py\&quot; class=\&quot;Link--inTextBlock\&quot;>\n          db_timeouts.py\n        </a>\n        hosted with &amp;#10084; by <a class=\&quot;Link--inTextBlock\&quot; href=\&quot;https://github.com\&quot;>GitHub</a>\n      </div>\n    </div>\n    <div class=\&quot;gist-file\&quot; translate=\&quot;no\&quot; data-color-mode=\&quot;light\&quot; data-light-theme=\&quot;light\&quot;>\n      <div class=\&quot;gist-data\&quot;>\n        <div class=\&quot;js-gist-file-update-container js-task-list-container\&quot;>\n  <div id=\&quot;file-models-py\&quot; class=\&quot;file my-2\&quot;>\n    \n    <div itemprop=\&quot;text\&quot;\n      class=\&quot;Box-body p-0 blob-wrapper data type-python  \&quot;\n      style=\&quot;overflow: auto\&quot; tabindex=\&quot;0\&quot; role=\&quot;region\&quot;\n      aria-label=\&quot;models.py content, created by ketanbhatt on 10:55AM on March 25, 2018.\&quot;\n    >\n\n        \n<div class=\&quot;js-check-bidi js-blob-code-container blob-code-content\&quot;>\n\n  <template class=\&quot;js-file-alert-template\&quot;>\n  <div data-view-component=\&quot;true\&quot; class=\&quot;flash flash-warn flash-full d-flex flex-items-center\&quot;>\n  <svg aria-hidden=\&quot;true\&quot; height=\&quot;16\&quot; viewBox=\&quot;0 0 16 16\&quot; version=\&quot;1.1\&quot; width=\&quot;16\&quot; data-view-component=\&quot;true\&quot; class=\&quot;octicon octicon-alert\&quot;>\n    <path d=\&quot;M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\&quot;></path>\n</svg>\n    <span>\n      This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.\n      <a class=\&quot;Link--inTextBlock\&quot; href=\&quot;https://github.co/hiddenchars\&quot; target=\&quot;_blank\&quot;>Learn more about bidirectional Unicode characters</a>\n    </span>\n\n\n  <div data-view-component=\&quot;true\&quot; class=\&quot;flash-action\&quot;>        <a href=\&quot;{{ revealButtonHref }}\&quot; data-view-component=\&quot;true\&quot; class=\&quot;btn-sm btn\&quot;>    Show hidden characters\n</a>\n</div>\n</div></template>\n<template class=\&quot;js-line-alert-template\&quot;>\n  <span aria-label=\&quot;This line has hidden Unicode characters\&quot; data-view-component=\&quot;true\&quot; class=\&quot;line-alert tooltipped tooltipped-e\&quot;>\n    <svg aria-hidden=\&quot;true\&quot; height=\&quot;16\&quot; viewBox=\&quot;0 0 16 16\&quot; version=\&quot;1.1\&quot; width=\&quot;16\&quot; data-view-component=\&quot;true\&quot; class=\&quot;octicon octicon-alert\&quot;>\n    <path d=\&quot;M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\&quot;></path>\n</svg>\n</span></template>\n\n  <table data-hpc class=\&quot;highlight tab-size js-file-line-container\&quot; data-tab-size=\&quot;8\&quot; data-paste-markdown-skip data-tagsearch-path=\&quot;models.py\&quot;>\n        <tr>\n          <td id=\&quot;file-models-py-L1\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;1\&quot;></td>\n          <td id=\&quot;file-models-py-LC1\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-k>from</span> <span class=pl-s1>django</span>.<span class=pl-s1>db</span>.<span class=pl-s1>backends</span>.<span class=pl-s1>signals</span> <span class=pl-k>import</span> <span class=pl-s1>connection_created</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L2\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;2\&quot;></td>\n          <td id=\&quot;file-models-py-LC2\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L3\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;3\&quot;></td>\n          <td id=\&quot;file-models-py-LC3\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c># ...</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L4\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;4\&quot;></td>\n          <td id=\&quot;file-models-py-LC4\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L5\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;5\&quot;></td>\n          <td id=\&quot;file-models-py-LC5\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-k>def</span> <span class=pl-en>set_timeout_on_new_conn</span>(<span class=pl-s1>sender</span>, <span class=pl-s1>connection</span>, <span class=pl-c1>**</span><span class=pl-s1>kwargs</span>):</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L6\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;6\&quot;></td>\n          <td id=\&quot;file-models-py-LC6\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-s>&amp;quot;&amp;quot;&amp;quot;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L7\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;7\&quot;></td>\n          <td id=\&quot;file-models-py-LC7\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-s>        Rig django to set statement timeout for each new connection based on the config</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L8\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;8\&quot;></td>\n          <td id=\&quot;file-models-py-LC8\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-s>    &amp;quot;&amp;quot;&amp;quot;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L9\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;9\&quot;></td>\n          <td id=\&quot;file-models-py-LC9\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>try</span>:</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L10\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;10\&quot;></td>\n          <td id=\&quot;file-models-py-LC10\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-s1>timeout_to_set</span> <span class=pl-c1>=</span> <span class=pl-c1>DB_IDENTIFIER_AND_CONNECTION_TO_TIMEOUT_MAP</span>[<span class=pl-s1>settings</span>.<span class=pl-c1>DB_TIMEOUT_IDENTIFIER</span>][<span class=pl-s1>connection</span>.<span class=pl-c1>alias</span>]</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L11\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;11\&quot;></td>\n          <td id=\&quot;file-models-py-LC11\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>except</span> <span class=pl-v>KeyError</span> <span class=pl-k>as</span> <span class=pl-s1>e</span>:</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L12\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;12\&quot;></td>\n          <td id=\&quot;file-models-py-LC12\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-s1>logger</span>.<span class=pl-c1>error</span>(<span class=pl-s>&amp;quot;KeyError while setting DB Timeout: {0}&amp;quot;</span>.<span class=pl-c1>format</span>(<span class=pl-s1>e</span>))</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L13\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;13\&quot;></td>\n          <td id=\&quot;file-models-py-LC13\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-s1>timeout_to_set</span> <span class=pl-c1>=</span> <span class=pl-c1>DEFAULT_DB_TIMEOUT_IN_MS</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L14\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;14\&quot;></td>\n          <td id=\&quot;file-models-py-LC14\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L15\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;15\&quot;></td>\n          <td id=\&quot;file-models-py-LC15\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>with</span> <span class=pl-s1>connection</span>.<span class=pl-c1>cursor</span>() <span class=pl-k>as</span> <span class=pl-s1>cursor</span>:</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L16\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;16\&quot;></td>\n          <td id=\&quot;file-models-py-LC16\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-s1>cursor</span>.<span class=pl-c1>execute</span>(<span class=pl-s>&amp;quot;set statement_timeout={0}&amp;quot;</span>.<span class=pl-c1>format</span>(<span class=pl-s1>timeout_to_set</span>))</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L17\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;17\&quot;></td>\n          <td id=\&quot;file-models-py-LC17\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L18\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;18\&quot;></td>\n          <td id=\&quot;file-models-py-LC18\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-models-py-L19\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;19\&quot;></td>\n          <td id=\&quot;file-models-py-LC19\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-s1>connection_created</span>.<span class=pl-c1>connect</span>(<span class=pl-s1>set_timeout_on_new_conn</span>)</td>\n        </tr>\n  </table>\n</div>\n\n\n    </div>\n\n  </div>\n</div>\n\n      </div>\n      <div class=\&quot;gist-meta\&quot;>\n        <a href=\&quot;https://gist.github.com/ketanbhatt/730b86ebece1aa91ebbf2b6182163bb8/raw/ddccb5120701c06cd0415a648e3cfe4e090653d7/models.py\&quot; style=\&quot;float:right\&quot; class=\&quot;Link--inTextBlock\&quot;>view raw</a>\n        <a href=\&quot;https://gist.github.com/ketanbhatt/730b86ebece1aa91ebbf2b6182163bb8#file-models-py\&quot; class=\&quot;Link--inTextBlock\&quot;>\n          models.py\n        </a>\n        hosted with &amp;#10084; by <a class=\&quot;Link--inTextBlock\&quot; href=\&quot;https://github.com\&quot;>GitHub</a>\n      </div>\n    </div>\n    <div class=\&quot;gist-file\&quot; translate=\&quot;no\&quot; data-color-mode=\&quot;light\&quot; data-light-theme=\&quot;light\&quot;>\n      <div class=\&quot;gist-data\&quot;>\n        <div class=\&quot;js-gist-file-update-container js-task-list-container\&quot;>\n  <div id=\&quot;file-settings-py\&quot; class=\&quot;file my-2\&quot;>\n    \n    <div itemprop=\&quot;text\&quot;\n      class=\&quot;Box-body p-0 blob-wrapper data type-python  \&quot;\n      style=\&quot;overflow: auto\&quot; tabindex=\&quot;0\&quot; role=\&quot;region\&quot;\n      aria-label=\&quot;settings.py content, created by ketanbhatt on 10:55AM on March 25, 2018.\&quot;\n    >\n\n        \n<div class=\&quot;js-check-bidi js-blob-code-container blob-code-content\&quot;>\n\n  <template class=\&quot;js-file-alert-template\&quot;>\n  <div data-view-component=\&quot;true\&quot; class=\&quot;flash flash-warn flash-full d-flex flex-items-center\&quot;>\n  <svg aria-hidden=\&quot;true\&quot; height=\&quot;16\&quot; viewBox=\&quot;0 0 16 16\&quot; version=\&quot;1.1\&quot; width=\&quot;16\&quot; data-view-component=\&quot;true\&quot; class=\&quot;octicon octicon-alert\&quot;>\n    <path d=\&quot;M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\&quot;></path>\n</svg>\n    <span>\n      This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.\n      <a class=\&quot;Link--inTextBlock\&quot; href=\&quot;https://github.co/hiddenchars\&quot; target=\&quot;_blank\&quot;>Learn more about bidirectional Unicode characters</a>\n    </span>\n\n\n  <div data-view-component=\&quot;true\&quot; class=\&quot;flash-action\&quot;>        <a href=\&quot;{{ revealButtonHref }}\&quot; data-view-component=\&quot;true\&quot; class=\&quot;btn-sm btn\&quot;>    Show hidden characters\n</a>\n</div>\n</div></template>\n<template class=\&quot;js-line-alert-template\&quot;>\n  <span aria-label=\&quot;This line has hidden Unicode characters\&quot; data-view-component=\&quot;true\&quot; class=\&quot;line-alert tooltipped tooltipped-e\&quot;>\n    <svg aria-hidden=\&quot;true\&quot; height=\&quot;16\&quot; viewBox=\&quot;0 0 16 16\&quot; version=\&quot;1.1\&quot; width=\&quot;16\&quot; data-view-component=\&quot;true\&quot; class=\&quot;octicon octicon-alert\&quot;>\n    <path d=\&quot;M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\&quot;></path>\n</svg>\n</span></template>\n\n  <table data-hpc class=\&quot;highlight tab-size js-file-line-container\&quot; data-tab-size=\&quot;8\&quot; data-paste-markdown-skip data-tagsearch-path=\&quot;settings.py\&quot;>\n        <tr>\n          <td id=\&quot;file-settings-py-L1\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;1\&quot;></td>\n          <td id=\&quot;file-settings-py-LC1\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c># ...</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-settings-py-L2\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;2\&quot;></td>\n          <td id=\&quot;file-settings-py-LC2\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-settings-py-L3\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;3\&quot;></td>\n          <td id=\&quot;file-settings-py-LC3\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c1>PROD_APP_DB_TIMEOUT_IDENTIFIER</span> <span class=pl-c1>=</span> <span class=pl-s>&amp;#39;db_timeout:production:app&amp;#39;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-settings-py-L4\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;4\&quot;></td>\n          <td id=\&quot;file-settings-py-LC4\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c1>PROD_CELERY_DB_TIMEOUT_IDENTIFIER</span> <span class=pl-c1>=</span> <span class=pl-s>&amp;#39;db_timeout:production:celery&amp;#39;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-settings-py-L5\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;5\&quot;></td>\n          <td id=\&quot;file-settings-py-LC5\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c1>PROD_CRON_DB_TIMEOUT_IDENTIFIER</span> <span class=pl-c1>=</span> <span class=pl-s>&amp;#39;db_timeout:production:cron&amp;#39;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-settings-py-L6\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;6\&quot;></td>\n          <td id=\&quot;file-settings-py-LC6\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c1>PROD_BIZ_API_DB_TIMEOUT_IDENTIFIER</span> <span class=pl-c1>=</span> <span class=pl-s>&amp;#39;db_timeout:production:biz&amp;#39;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-settings-py-L7\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;7\&quot;></td>\n          <td id=\&quot;file-settings-py-LC7\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c1>PROD_ADMIN_DB_TIMEOUT_IDENTIFIER</span> <span class=pl-c1>=</span> <span class=pl-s>&amp;#39;db_timeout:production:admin&amp;#39;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-settings-py-L8\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;8\&quot;></td>\n          <td id=\&quot;file-settings-py-LC8\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c1>PROD_EXPLORER_DB_TIMEOUT_IDENTIFIER</span> <span class=pl-c1>=</span> <span class=pl-s>&amp;#39;db_timeout:production:explorer&amp;#39;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-settings-py-L9\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;9\&quot;></td>\n          <td id=\&quot;file-settings-py-LC9\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c1>STAGING_DB_TIMEOUT_IDENTIFIER</span> <span class=pl-c1>=</span> <span class=pl-s>&amp;#39;db_timeout:staging:*&amp;#39;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-settings-py-L10\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;10\&quot;></td>\n          <td id=\&quot;file-settings-py-LC10\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c1>DEFAULT_DB_TIMEOUT_IDENTIFIER</span> <span class=pl-c1>=</span> <span class=pl-s>&amp;#39;db_timeout:*:*&amp;#39;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-settings-py-L11\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;11\&quot;></td>\n          <td id=\&quot;file-settings-py-LC11\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-settings-py-L12\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;12\&quot;></td>\n          <td id=\&quot;file-settings-py-LC12\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c># ...</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-settings-py-L13\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;13\&quot;></td>\n          <td id=\&quot;file-settings-py-LC13\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-settings-py-L14\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;14\&quot;></td>\n          <td id=\&quot;file-settings-py-LC14\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c1>DB_TIMEOUT_IDENTIFIER</span> <span class=pl-c1>=</span> <span class=pl-c1>PROD_CELERY_DB_TIMEOUT_IDENTFIER</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-settings-py-L15\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;15\&quot;></td>\n          <td id=\&quot;file-settings-py-LC15\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-settings-py-L16\&quot; class=\&quot;blob-num js-line-number js-blob-rnum\&quot; data-line-number=\&quot;16\&quot;></td>\n          <td id=\&quot;file-settings-py-LC16\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c># ...</span></td>\n        </tr>\n  </table>\n</div>\n\n\n    </div>\n\n  </div>\n</div>\n\n      </div>\n      <div class=\&quot;gist-meta\&quot;>\n        <a href=\&quot;https://gist.github.com/ketanbhatt/730b86ebece1aa91ebbf2b6182163bb8/raw/ddccb5120701c06cd0415a648e3cfe4e090653d7/settings.py\&quot; style=\&quot;float:right\&quot; class=\&quot;Link--inTextBlock\&quot;>view raw</a>\n        <a href=\&quot;https://gist.github.com/ketanbhatt/730b86ebece1aa91ebbf2b6182163bb8#file-settings-py\&quot; class=\&quot;Link--inTextBlock\&quot;>\n          settings.py\n        </a>\n        hosted with &amp;#10084; by <a class=\&quot;Link--inTextBlock\&quot; href=\&quot;https://github.com\&quot;>GitHub</a>\n      </div>\n    </div>\n</div>\n&quot;,&quot;stylesheet&quot;:&quot;https://github.githubassets.com/assets/gist-embed-04c27bb90e5b.css&quot;}" data-component-name="GitgistToDOM"><link rel="stylesheet" href="https://github.githubassets.com/assets/gist-embed-04c27bb90e5b.css"><div id="gist88600903" class="gist">
    <div class="gist-file" data-color-mode="light" data-light-theme="light">
      <div class="gist-data">
        <div class="js-gist-file-update-container js-task-list-container">
  <div id="file-db_timeouts-py" class="file my-2">
    
    <div itemprop="text" class="Box-body p-0 blob-wrapper data type-python  " style="overflow:auto">

        
<div class="js-check-bidi js-blob-code-container blob-code-content">

  
  <div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
  
    

    <span>
      This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      <a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
    </span>


  <div data-view-component="true" class="flash-action">        <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn">    Show hidden characters
</a>
</div>
</div>

  <span data-view-component="true" class="line-alert tooltipped tooltipped-e">
    
    

</span>

  <table data-hpc="" class="highlight tab-size js-file-line-container" data-tab-size="8" data-paste-markdown-skip="" data-tagsearch-path="db_timeouts.py">
        <tbody><tr>
          <td id="file-db_timeouts-py-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
          <td id="file-db_timeouts-py-LC1" class="blob-code blob-code-inner js-file-line"><span class="pl-k">from</span> <span class="pl-s1">django</span>.<span class="pl-s1">conf</span> <span class="pl-k">import</span> <span class="pl-s1">settings</span></td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
          <td id="file-db_timeouts-py-LC2" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
          <td id="file-db_timeouts-py-LC3" class="blob-code blob-code-inner js-file-line"><span class="pl-c"># NOTE</span></td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
          <td id="file-db_timeouts-py-LC4" class="blob-code blob-code-inner js-file-line"><span class="pl-c"># The timeout values here only restrict the roles from application code. The actual timeout set in the DB could</span></td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
          <td id="file-db_timeouts-py-LC5" class="blob-code blob-code-inner js-file-line"><span class="pl-c"># be a different value.</span></td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
          <td id="file-db_timeouts-py-LC6" class="blob-code blob-code-inner js-file-line"><span class="pl-c"># Timeout set in DB on 27/03/2018 is 50s.</span></td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
          <td id="file-db_timeouts-py-LC7" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
          <td id="file-db_timeouts-py-LC8" class="blob-code blob-code-inner js-file-line"><span class="pl-c1">DEFAULT_DB_TIMEOUT_IN_MS</span> <span class="pl-c1">=</span> <span class="pl-c1">50000</span></td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L9" class="blob-num js-line-number js-blob-rnum" data-line-number="9"></td>
          <td id="file-db_timeouts-py-LC9" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L10" class="blob-num js-line-number js-blob-rnum" data-line-number="10"></td>
          <td id="file-db_timeouts-py-LC10" class="blob-code blob-code-inner js-file-line"><span class="pl-s1">default_conn</span> <span class="pl-c1">=</span> <span class="pl-s1">settings</span>.<span class="pl-c1">DJANGO_DEFAULT_DB_CONNECTION_NAME</span></td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L11" class="blob-num js-line-number js-blob-rnum" data-line-number="11"></td>
          <td id="file-db_timeouts-py-LC11" class="blob-code blob-code-inner js-file-line"><span class="pl-s1">explorer_conn</span> <span class="pl-c1">=</span> <span class="pl-s1">settings</span>.<span class="pl-c1">EXPLORER_CONNECTION_NAME</span></td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L12" class="blob-num js-line-number js-blob-rnum" data-line-number="12"></td>
          <td id="file-db_timeouts-py-LC12" class="blob-code blob-code-inner js-file-line"><span class="pl-s1">slave_conn</span> <span class="pl-c1">=</span> <span class="pl-s1">settings</span>.<span class="pl-c1">SLAVE_NAME</span></td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L13" class="blob-num js-line-number js-blob-rnum" data-line-number="13"></td>
          <td id="file-db_timeouts-py-LC13" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L14" class="blob-num js-line-number js-blob-rnum" data-line-number="14"></td>
          <td id="file-db_timeouts-py-LC14" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L15" class="blob-num js-line-number js-blob-rnum" data-line-number="15"></td>
          <td id="file-db_timeouts-py-LC15" class="blob-code blob-code-inner js-file-line"><span class="pl-c1">DB_IDENTIFIER_AND_CONNECTION_TO_TIMEOUT_MAP</span> <span class="pl-c1">=</span> {</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L16" class="blob-num js-line-number js-blob-rnum" data-line-number="16"></td>
          <td id="file-db_timeouts-py-LC16" class="blob-code blob-code-inner js-file-line">    <span class="pl-s1">settings</span>.<span class="pl-c1">PROD_APP_DB_TIMEOUT_IDENTIFIER</span>: {</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L17" class="blob-num js-line-number js-blob-rnum" data-line-number="17"></td>
          <td id="file-db_timeouts-py-LC17" class="blob-code blob-code-inner js-file-line">        <span class="pl-s1">default_conn</span>: <span class="pl-c1">20000</span>,</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L18" class="blob-num js-line-number js-blob-rnum" data-line-number="18"></td>
          <td id="file-db_timeouts-py-LC18" class="blob-code blob-code-inner js-file-line">        <span class="pl-s1">explorer_conn</span>: <span class="pl-c1">50000</span>,</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L19" class="blob-num js-line-number js-blob-rnum" data-line-number="19"></td>
          <td id="file-db_timeouts-py-LC19" class="blob-code blob-code-inner js-file-line">        <span class="pl-s1">slave_conn</span>: <span class="pl-c1">20000</span></td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L20" class="blob-num js-line-number js-blob-rnum" data-line-number="20"></td>
          <td id="file-db_timeouts-py-LC20" class="blob-code blob-code-inner js-file-line">    },</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L21" class="blob-num js-line-number js-blob-rnum" data-line-number="21"></td>
          <td id="file-db_timeouts-py-LC21" class="blob-code blob-code-inner js-file-line">    <span class="pl-s1">settings</span>.<span class="pl-c1">PROD_CELERY_DB_TIMEOUT_IDENTIFIER</span>: {</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L22" class="blob-num js-line-number js-blob-rnum" data-line-number="22"></td>
          <td id="file-db_timeouts-py-LC22" class="blob-code blob-code-inner js-file-line">        <span class="pl-s1">default_conn</span>: <span class="pl-c1">50000</span>,</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L23" class="blob-num js-line-number js-blob-rnum" data-line-number="23"></td>
          <td id="file-db_timeouts-py-LC23" class="blob-code blob-code-inner js-file-line">        <span class="pl-s1">explorer_conn</span>: <span class="pl-c1">50000</span>,</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L24" class="blob-num js-line-number js-blob-rnum" data-line-number="24"></td>
          <td id="file-db_timeouts-py-LC24" class="blob-code blob-code-inner js-file-line">        <span class="pl-s1">slave_conn</span>: <span class="pl-c1">50000</span></td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L25" class="blob-num js-line-number js-blob-rnum" data-line-number="25"></td>
          <td id="file-db_timeouts-py-LC25" class="blob-code blob-code-inner js-file-line">    },</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L26" class="blob-num js-line-number js-blob-rnum" data-line-number="26"></td>
          <td id="file-db_timeouts-py-LC26" class="blob-code blob-code-inner js-file-line">    <span class="pl-s1">settings</span>.<span class="pl-c1">DEFAULT_DB_TIMEOUT_IDENTIFIER</span>: {</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L27" class="blob-num js-line-number js-blob-rnum" data-line-number="27"></td>
          <td id="file-db_timeouts-py-LC27" class="blob-code blob-code-inner js-file-line">        <span class="pl-s1">default_conn</span>: <span class="pl-c1">50000</span>,</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L28" class="blob-num js-line-number js-blob-rnum" data-line-number="28"></td>
          <td id="file-db_timeouts-py-LC28" class="blob-code blob-code-inner js-file-line">        <span class="pl-s1">explorer_conn</span>: <span class="pl-c1">50000</span>,</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L29" class="blob-num js-line-number js-blob-rnum" data-line-number="29"></td>
          <td id="file-db_timeouts-py-LC29" class="blob-code blob-code-inner js-file-line">        <span class="pl-s1">slave_conn</span>: <span class="pl-c1">50000</span></td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L30" class="blob-num js-line-number js-blob-rnum" data-line-number="30"></td>
          <td id="file-db_timeouts-py-LC30" class="blob-code blob-code-inner js-file-line">    },</td>
        </tr>
        <tr>
          <td id="file-db_timeouts-py-L31" class="blob-num js-line-number js-blob-rnum" data-line-number="31"></td>
          <td id="file-db_timeouts-py-LC31" class="blob-code blob-code-inner js-file-line">}</td>
        </tr>
  </tbody></table>
</div>


    </div>

  </div>
</div>

      </div>
      <div class="gist-meta">
        <a href="https://gist.github.com/ketanbhatt/730b86ebece1aa91ebbf2b6182163bb8/raw/ddccb5120701c06cd0415a648e3cfe4e090653d7/db_timeouts.py" style="float:right" class="Link--inTextBlock">view raw</a>
        <a href="https://gist.github.com/ketanbhatt/730b86ebece1aa91ebbf2b6182163bb8#file-db_timeouts-py" class="Link--inTextBlock">
          db_timeouts.py
        </a>
        hosted with &#10084; by <a class="Link--inTextBlock" href="https://github.com">GitHub</a>
      </div>
    </div>
    <div class="gist-file" data-color-mode="light" data-light-theme="light">
      <div class="gist-data">
        <div class="js-gist-file-update-container js-task-list-container">
  <div id="file-models-py" class="file my-2">
    
    <div itemprop="text" class="Box-body p-0 blob-wrapper data type-python  " style="overflow:auto">

        
<div class="js-check-bidi js-blob-code-container blob-code-content">

  
  <div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
  
    

    <span>
      This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      <a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
    </span>


  <div data-view-component="true" class="flash-action">        <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn">    Show hidden characters
</a>
</div>
</div>

  <span data-view-component="true" class="line-alert tooltipped tooltipped-e">
    
    

</span>

  <table data-hpc="" class="highlight tab-size js-file-line-container" data-tab-size="8" data-paste-markdown-skip="" data-tagsearch-path="models.py">
        <tbody><tr>
          <td id="file-models-py-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
          <td id="file-models-py-LC1" class="blob-code blob-code-inner js-file-line"><span class="pl-k">from</span> <span class="pl-s1">django</span>.<span class="pl-s1">db</span>.<span class="pl-s1">backends</span>.<span class="pl-s1">signals</span> <span class="pl-k">import</span> <span class="pl-s1">connection_created</span></td>
        </tr>
        <tr>
          <td id="file-models-py-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
          <td id="file-models-py-LC2" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-models-py-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
          <td id="file-models-py-LC3" class="blob-code blob-code-inner js-file-line"><span class="pl-c"># ...</span></td>
        </tr>
        <tr>
          <td id="file-models-py-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
          <td id="file-models-py-LC4" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-models-py-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
          <td id="file-models-py-LC5" class="blob-code blob-code-inner js-file-line"><span class="pl-k">def</span> <span class="pl-en">set_timeout_on_new_conn</span>(<span class="pl-s1">sender</span>, <span class="pl-s1">connection</span>, <span class="pl-c1">**</span><span class="pl-s1">kwargs</span>):</td>
        </tr>
        <tr>
          <td id="file-models-py-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
          <td id="file-models-py-LC6" class="blob-code blob-code-inner js-file-line">    <span class="pl-s">"""</span></td>
        </tr>
        <tr>
          <td id="file-models-py-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
          <td id="file-models-py-LC7" class="blob-code blob-code-inner js-file-line"><span class="pl-s">        Rig django to set statement timeout for each new connection based on the config</span></td>
        </tr>
        <tr>
          <td id="file-models-py-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
          <td id="file-models-py-LC8" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    """</span></td>
        </tr>
        <tr>
          <td id="file-models-py-L9" class="blob-num js-line-number js-blob-rnum" data-line-number="9"></td>
          <td id="file-models-py-LC9" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">try</span>:</td>
        </tr>
        <tr>
          <td id="file-models-py-L10" class="blob-num js-line-number js-blob-rnum" data-line-number="10"></td>
          <td id="file-models-py-LC10" class="blob-code blob-code-inner js-file-line">        <span class="pl-s1">timeout_to_set</span> <span class="pl-c1">=</span> <span class="pl-c1">DB_IDENTIFIER_AND_CONNECTION_TO_TIMEOUT_MAP</span>[<span class="pl-s1">settings</span>.<span class="pl-c1">DB_TIMEOUT_IDENTIFIER</span>][<span class="pl-s1">connection</span>.<span class="pl-c1">alias</span>]</td>
        </tr>
        <tr>
          <td id="file-models-py-L11" class="blob-num js-line-number js-blob-rnum" data-line-number="11"></td>
          <td id="file-models-py-LC11" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">except</span> <span class="pl-v">KeyError</span> <span class="pl-k">as</span> <span class="pl-s1">e</span>:</td>
        </tr>
        <tr>
          <td id="file-models-py-L12" class="blob-num js-line-number js-blob-rnum" data-line-number="12"></td>
          <td id="file-models-py-LC12" class="blob-code blob-code-inner js-file-line">        <span class="pl-s1">logger</span>.<span class="pl-c1">error</span>(<span class="pl-s">"KeyError while setting DB Timeout: {0}"</span>.<span class="pl-c1">format</span>(<span class="pl-s1">e</span>))</td>
        </tr>
        <tr>
          <td id="file-models-py-L13" class="blob-num js-line-number js-blob-rnum" data-line-number="13"></td>
          <td id="file-models-py-LC13" class="blob-code blob-code-inner js-file-line">        <span class="pl-s1">timeout_to_set</span> <span class="pl-c1">=</span> <span class="pl-c1">DEFAULT_DB_TIMEOUT_IN_MS</span></td>
        </tr>
        <tr>
          <td id="file-models-py-L14" class="blob-num js-line-number js-blob-rnum" data-line-number="14"></td>
          <td id="file-models-py-LC14" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-models-py-L15" class="blob-num js-line-number js-blob-rnum" data-line-number="15"></td>
          <td id="file-models-py-LC15" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">with</span> <span class="pl-s1">connection</span>.<span class="pl-c1">cursor</span>() <span class="pl-k">as</span> <span class="pl-s1">cursor</span>:</td>
        </tr>
        <tr>
          <td id="file-models-py-L16" class="blob-num js-line-number js-blob-rnum" data-line-number="16"></td>
          <td id="file-models-py-LC16" class="blob-code blob-code-inner js-file-line">        <span class="pl-s1">cursor</span>.<span class="pl-c1">execute</span>(<span class="pl-s">"set statement_timeout={0}"</span>.<span class="pl-c1">format</span>(<span class="pl-s1">timeout_to_set</span>))</td>
        </tr>
        <tr>
          <td id="file-models-py-L17" class="blob-num js-line-number js-blob-rnum" data-line-number="17"></td>
          <td id="file-models-py-LC17" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-models-py-L18" class="blob-num js-line-number js-blob-rnum" data-line-number="18"></td>
          <td id="file-models-py-LC18" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-models-py-L19" class="blob-num js-line-number js-blob-rnum" data-line-number="19"></td>
          <td id="file-models-py-LC19" class="blob-code blob-code-inner js-file-line"><span class="pl-s1">connection_created</span>.<span class="pl-c1">connect</span>(<span class="pl-s1">set_timeout_on_new_conn</span>)</td>
        </tr>
  </tbody></table>
</div>


    </div>

  </div>
</div>

      </div>
      <div class="gist-meta">
        <a href="https://gist.github.com/ketanbhatt/730b86ebece1aa91ebbf2b6182163bb8/raw/ddccb5120701c06cd0415a648e3cfe4e090653d7/models.py" style="float:right" class="Link--inTextBlock">view raw</a>
        <a href="https://gist.github.com/ketanbhatt/730b86ebece1aa91ebbf2b6182163bb8#file-models-py" class="Link--inTextBlock">
          models.py
        </a>
        hosted with &#10084; by <a class="Link--inTextBlock" href="https://github.com">GitHub</a>
      </div>
    </div>
    <div class="gist-file" data-color-mode="light" data-light-theme="light">
      <div class="gist-data">
        <div class="js-gist-file-update-container js-task-list-container">
  <div id="file-settings-py" class="file my-2">
    
    <div itemprop="text" class="Box-body p-0 blob-wrapper data type-python  " style="overflow:auto">

        
<div class="js-check-bidi js-blob-code-container blob-code-content">

  
  <div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
  
    

    <span>
      This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      <a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
    </span>


  <div data-view-component="true" class="flash-action">        <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn">    Show hidden characters
</a>
</div>
</div>

  <span data-view-component="true" class="line-alert tooltipped tooltipped-e">
    
    

</span>

  <table data-hpc="" class="highlight tab-size js-file-line-container" data-tab-size="8" data-paste-markdown-skip="" data-tagsearch-path="settings.py">
        <tbody><tr>
          <td id="file-settings-py-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
          <td id="file-settings-py-LC1" class="blob-code blob-code-inner js-file-line"><span class="pl-c"># ...</span></td>
        </tr>
        <tr>
          <td id="file-settings-py-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
          <td id="file-settings-py-LC2" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-settings-py-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
          <td id="file-settings-py-LC3" class="blob-code blob-code-inner js-file-line"><span class="pl-c1">PROD_APP_DB_TIMEOUT_IDENTIFIER</span> <span class="pl-c1">=</span> <span class="pl-s">'db_timeout:production:app'</span></td>
        </tr>
        <tr>
          <td id="file-settings-py-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
          <td id="file-settings-py-LC4" class="blob-code blob-code-inner js-file-line"><span class="pl-c1">PROD_CELERY_DB_TIMEOUT_IDENTIFIER</span> <span class="pl-c1">=</span> <span class="pl-s">'db_timeout:production:celery'</span></td>
        </tr>
        <tr>
          <td id="file-settings-py-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
          <td id="file-settings-py-LC5" class="blob-code blob-code-inner js-file-line"><span class="pl-c1">PROD_CRON_DB_TIMEOUT_IDENTIFIER</span> <span class="pl-c1">=</span> <span class="pl-s">'db_timeout:production:cron'</span></td>
        </tr>
        <tr>
          <td id="file-settings-py-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
          <td id="file-settings-py-LC6" class="blob-code blob-code-inner js-file-line"><span class="pl-c1">PROD_BIZ_API_DB_TIMEOUT_IDENTIFIER</span> <span class="pl-c1">=</span> <span class="pl-s">'db_timeout:production:biz'</span></td>
        </tr>
        <tr>
          <td id="file-settings-py-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
          <td id="file-settings-py-LC7" class="blob-code blob-code-inner js-file-line"><span class="pl-c1">PROD_ADMIN_DB_TIMEOUT_IDENTIFIER</span> <span class="pl-c1">=</span> <span class="pl-s">'db_timeout:production:admin'</span></td>
        </tr>
        <tr>
          <td id="file-settings-py-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
          <td id="file-settings-py-LC8" class="blob-code blob-code-inner js-file-line"><span class="pl-c1">PROD_EXPLORER_DB_TIMEOUT_IDENTIFIER</span> <span class="pl-c1">=</span> <span class="pl-s">'db_timeout:production:explorer'</span></td>
        </tr>
        <tr>
          <td id="file-settings-py-L9" class="blob-num js-line-number js-blob-rnum" data-line-number="9"></td>
          <td id="file-settings-py-LC9" class="blob-code blob-code-inner js-file-line"><span class="pl-c1">STAGING_DB_TIMEOUT_IDENTIFIER</span> <span class="pl-c1">=</span> <span class="pl-s">'db_timeout:staging:*'</span></td>
        </tr>
        <tr>
          <td id="file-settings-py-L10" class="blob-num js-line-number js-blob-rnum" data-line-number="10"></td>
          <td id="file-settings-py-LC10" class="blob-code blob-code-inner js-file-line"><span class="pl-c1">DEFAULT_DB_TIMEOUT_IDENTIFIER</span> <span class="pl-c1">=</span> <span class="pl-s">'db_timeout:*:*'</span></td>
        </tr>
        <tr>
          <td id="file-settings-py-L11" class="blob-num js-line-number js-blob-rnum" data-line-number="11"></td>
          <td id="file-settings-py-LC11" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-settings-py-L12" class="blob-num js-line-number js-blob-rnum" data-line-number="12"></td>
          <td id="file-settings-py-LC12" class="blob-code blob-code-inner js-file-line"><span class="pl-c"># ...</span></td>
        </tr>
        <tr>
          <td id="file-settings-py-L13" class="blob-num js-line-number js-blob-rnum" data-line-number="13"></td>
          <td id="file-settings-py-LC13" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-settings-py-L14" class="blob-num js-line-number js-blob-rnum" data-line-number="14"></td>
          <td id="file-settings-py-LC14" class="blob-code blob-code-inner js-file-line"><span class="pl-c1">DB_TIMEOUT_IDENTIFIER</span> <span class="pl-c1">=</span> <span class="pl-c1">PROD_CELERY_DB_TIMEOUT_IDENTFIER</span></td>
        </tr>
        <tr>
          <td id="file-settings-py-L15" class="blob-num js-line-number js-blob-rnum" data-line-number="15"></td>
          <td id="file-settings-py-LC15" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-settings-py-L16" class="blob-num js-line-number js-blob-rnum" data-line-number="16"></td>
          <td id="file-settings-py-LC16" class="blob-code blob-code-inner js-file-line"><span class="pl-c"># ...</span></td>
        </tr>
  </tbody></table>
</div>


    </div>

  </div>
</div>

      </div>
      <div class="gist-meta">
        <a href="https://gist.github.com/ketanbhatt/730b86ebece1aa91ebbf2b6182163bb8/raw/ddccb5120701c06cd0415a648e3cfe4e090653d7/settings.py" style="float:right" class="Link--inTextBlock">view raw</a>
        <a href="https://gist.github.com/ketanbhatt/730b86ebece1aa91ebbf2b6182163bb8#file-settings-py" class="Link--inTextBlock">
          settings.py
        </a>
        hosted with &#10084; by <a class="Link--inTextBlock" href="https://github.com">GitHub</a>
      </div>
    </div>
</div>
</div><p>We also went ahead and set the timeout separately for each <a href="https://docs.djangoproject.com/en/2.0/topics/db/multi-db/#defining-your-databases"><code>connection.alias</code></a>. That gives us <strong>even more flexibility</strong>, we can now set multiple separate timeout values for the connections from the same server as well (for example: set a timeout of 5s for queries made for our Android app facing APIs, except for login API, for which we set the timeout to 1s. And then use <code>&lt;queryset&gt;.using('&lt;alias&gt;')</code> to use the timeout you want).</p><p>The benefit of this approach is:</p><ol><li><p>Everything is in the code, you can just read and figure out what is happening</p></li><li><p>Easy to modify the timeouts</p></li><li><p>Since the logic is now in the application code, we can do more stuff with this, like setting a certain timeout only for some percentage of the connections.</p></li><li><p>Further, we can set different timeouts for different queries made from the same server</p></li></ol><h2>Caveats</h2><ol><li><p>Since we are running an additional query every time a connection is made, it has some implications. Even though <a href="https://docs.djangoproject.com/en/2.0/ref/databases/#optimizing-postgresql-s-configuration">Django&#8217;s documentation says</a> that the effect is minor, it is worth checking out if it&#8217;s okay for your case</p></li><li><p>Since the timeout is set using Django&#8217;s signals, it means that wherever Django does not publish a signal, this will not work. One such case is when you are directly logging in to the Postgres shell (or by doing <code>python manage.py dbshell</code>).</p></li></ol><p>The changes mentioned here gives us more control over our queries and we can selectively restrict our systems in case there is a &#128293; that needs strict actions to be taken to keep the more important parts of the app alive (<em>not the best solution, but sometimes they don&#8217;t have to be</em> &#128515;) .</p><p>(Originally <a href="https://medium.com/squad-engineering/configure-postgres-statement-timeouts-from-within-django-6ce4cd33678a">posted on my Medium account</a>)</p><h2></h2><h5>UPDATE: Jun 2020</h5><p><a href="https://hakibenita.com/pages/about">Haki Benita</a> was kind enough to provide feedback on this article. Here is a link to what he suggested: <a href="https://medium.com/p/a3755acdf195/responses/show">Haki Benita&#8217;s comment on Medium</a>. If you are learnt from or liked this article, you should definitely read Haki&#8217;s feedback as well.</p>]]></content:encoded></item><item><title><![CDATA[Two years with Celery in Production: Bug Fix Edition]]></title><description><![CDATA[Update 6th Mar, 2022: My friend and colleague Ayush Shanker recently published a follow-up post for most of the problems mentioned in this article.]]></description><link>https://www.ketanbhatt.com/p/django-celery</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/django-celery</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Sun, 17 Dec 2017 00:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!9PZy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8b6b68c-100a-42bd-ac10-91d71cac92b9_590x332.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9PZy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8b6b68c-100a-42bd-ac10-91d71cac92b9_590x332.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9PZy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8b6b68c-100a-42bd-ac10-91d71cac92b9_590x332.png 424w, https://substackcdn.com/image/fetch/$s_!9PZy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8b6b68c-100a-42bd-ac10-91d71cac92b9_590x332.png 848w, https://substackcdn.com/image/fetch/$s_!9PZy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8b6b68c-100a-42bd-ac10-91d71cac92b9_590x332.png 1272w, https://substackcdn.com/image/fetch/$s_!9PZy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8b6b68c-100a-42bd-ac10-91d71cac92b9_590x332.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9PZy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8b6b68c-100a-42bd-ac10-91d71cac92b9_590x332.png" width="590" height="332" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f8b6b68c-100a-42bd-ac10-91d71cac92b9_590x332.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:332,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Ladybug in grass&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Ladybug in grass" title="Ladybug in grass" srcset="https://substackcdn.com/image/fetch/$s_!9PZy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8b6b68c-100a-42bd-ac10-91d71cac92b9_590x332.png 424w, https://substackcdn.com/image/fetch/$s_!9PZy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8b6b68c-100a-42bd-ac10-91d71cac92b9_590x332.png 848w, https://substackcdn.com/image/fetch/$s_!9PZy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8b6b68c-100a-42bd-ac10-91d71cac92b9_590x332.png 1272w, https://substackcdn.com/image/fetch/$s_!9PZy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8b6b68c-100a-42bd-ac10-91d71cac92b9_590x332.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><em>Photo by <a href="https://unsplash.com/photos/Qi93Pl5vDRw?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Martin Oslic</a> on <a href="https://unsplash.com">Unsplash</a></em></figcaption></figure></div><p><strong>Update 6th Mar, 2022:</strong> My friend and colleague Ayush Shanker recently published a follow-up post for most of the problems mentioned in this article. I recommend going through that post as well (or maybe just that post): &#8221;<strong><a href="https://ayushshanker.com/celery-long-post/">Celery in production: Three more years of fixing bugs</a></strong>&#8220;.</p><p>As mentioned in an <a href="https://medium.com/squad-engineering/leveraging-aws-lambda-for-image-compression-at-scale-a01afd756a12">earlier post</a>, we rely on Celery for publishing and consuming tasks to/from our <a href="https://www.rabbitmq.com/">RabbitMQ (RMQ)</a> broker. We are very happy with the whole setup and it works reliably for us. But this wasn&#8217;t exactly the case up until four weeks back. We were plagued with a plethora of issues which we hadn&#8217;t got down to fix, mostly because first, the number of issues was small, and the occurrences less frequent.</p><p>But as the frequency and the types of issues increased, we started spending a lot of time in maintenance. I personally would have to keep an eye on the RMQ admin to see which queues weren&#8217;t being consumed properly, see if workers (we will use &#8220;workers&#8221; for celery workers or consumers) were still up or not stuck in an infinite restart loop. And every time I noticed something abnormal, I would have to restart the rogue worker/s manually.</p><h2>The Issues</h2><p>Here is a list of issues that we identified, and fixed:</p><ol><li><p>Worker servers always had an unexplainably high RAM usage</p></li><li><p>Worker servers always had an unexplainably high CPU usage</p></li><li><p>Workers stayed idle, not consuming any tasks</p></li><li><p>Workers kept on restarting</p></li><li><p>Publishers did not adhere to <a href="http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-task_publish_retry_policy"><code>task_publish_retry_policy</code></a></p></li></ol><p>Let&#8217;s go over them one by one</p><h2>Worker servers always had an unexplainably high RAM usage</h2><blockquote><p>Tl;dr: <em>We had orphaned child processes still running. Use <code>stopasgroup</code> if you are using <code>supervisord</code>.</em></p></blockquote><blockquote><p><strong>UPDATE:</strong> After a month of writing, this is still happening, albeit rarely and less severely. A final solution is to regularly (crons, maybe) kill the processes. This is also hinted at in <a href="http://docs.celeryproject.org/en/latest/userguide/workers.html#stopping-the-worker">Celery&#8217;s documentation</a>.</p></blockquote><p>We noticed our worker servers always having 100% RAM usage, and noticed that processes for old workers were still alive. This became apparent because we had very recently changed the configuration and we could see processes for workers with the old configuration (they were listening to inexistent queues). This was a red flag.</p><p>We use <code>supervisor</code> to control the celery workers and have to do a <code>reread</code> every time we change the worker config. On searching, we found out that when we <code>reread</code> and <code>update</code> or when we <code>restart</code>, <code>supervisor</code> doesn&#8217;t kill the old processes. This was also <a href="https://github.com/Supervisor/supervisor/issues/600">raised in an issue on supervisor</a>. As suggested, we started doing <code>stop</code> and <code>start</code> instead of a <code>restart</code> (in hindsight this looks dumb now since all <code>restart</code> does is <code>stop</code> and <code>start</code>) and before <code>reread</code>s as well. <em>Of course</em>, this didn&#8217;t help things.</p><p>A <code>ps auxf</code> output (after removing unneeded info) from one of our servers clearly showed how there were old celery processes still hanging out.</p><pre><code>USER        START   TIME COMMAND
ubuntu      03:38   0:57  \_ celery worker A
ubuntu      03:38   0:30  |   \_ celery worker A
ubuntu      03:38   0:32  |   \_ celery worker A
ubuntu      03:38   0:32  |   \_ celery worker A
ubuntu      03:38   0:31  |   \_ celery worker A
ubuntu      03:38   0:58  \_ celery worker B
ubuntu      03:38   0:31      \_ celery worker B
ubuntu      03:38   0:31      \_ celery worker B
ubuntu      03:38   0:31      \_ celery worker B
ubuntu      03:38   0:30      \_ celery worker B
ubuntu      Sep09   1:59  celery worker C
ubuntu      Sep10   3:04  celery worker D</code></pre><p>It was clear because:</p><ol><li><p>We had restarted all the celery workers quiet recently, as is visible by the <code>START</code> column, while the other two older processes are at least a day old.</p></li><li><p>How celery, roughly, works is that we start a parent process that starts more child processes (depending on the <code>concurrency</code>) and maintains a pool of these workers. This is consistent with how the newer workers are depicted, while the two older ones have no parent/child processes.</p></li></ol><p>On further research, I came across <a href="https://github.com/celery/celery/issues/102">an issue in celery</a> that mentioned the same problem.</p><p><em><strong>Turns out, celery parent processes don&#8217;t propagate the <code>STOP</code> signal to its child processes, leaving them orphaned (these are the old workers we saw in our <code>ps</code> output above). This got fixed by using <code>stopasgroup</code> in the <code>supervisord</code> config. <a href="http://supervisord.org/configuration.html">As documented</a>, with <code>stopasgroup</code> set to <code>true</code>, supervisor sends the <code>STOP</code> signal to the whole process group.</strong></em></p><h2>Worker servers always had an unexplainably high CPU usage</h2><blockquote><p>Tl;dr: <em>Our <code>--max-memory-per-child</code> was set too low, we moved back to using <code>--max-tasks-per-child</code>.</em></p></blockquote><p>We would be okay with this if the tasks were CPU intensive or the rate or volume of task consumption is high. They were not. It was just simple get and set on the database and the rate was about 17 tasks per second when we had ~12 workers spread over 4 servers.</p><p>To figure out what is going on, I <code>ssh</code>ed into a server with ~100% CPU usage, and did:</p><pre><code>&gt; ps aux -sort -%cpu</code></pre><p>Found a worker process that was taking ~48% of the CPU. To check what was happening with that worker, I checked the logs generated by that worker. The logs showed that the worker was being killed after every 3&#8211;4 tasks because of reaching the memory limit (we were starting our workers with a <code>--max-memory-per-child</code> of some 200&#8211;300mb). Although it did look like a sane value to us, maybe we misunderstood how this limit works.</p><p>The logs were similar on other servers. This would explain the high CPU usage. Killing and creating processes would take CPU. And this would also explain the slow rate of execution. So, on a hunch, I removed this limit from the workers and moved back to using <code>--max-tasks-per-child</code>.</p><p><em><strong>Instantly the rate increased to ~250 tasks per second (from 17) and the CPU usage also settled down. Huge win. Memory leaks are still covered because of the limit on the number of tasks.</strong></em></p><h2>Workers stayed idle, not consuming any tasks</h2><blockquote><p>Tl;dr: <em>There was a deadlock because of a play between <code>psycopg2</code> and <code>ssl</code>. Updating <code>psycopg2</code> to <code>2.6.1</code> fixed the issue.</em></p></blockquote><p>The queue had available tasks, and healthy consumers, but the tasks weren&#8217;t being consumed. This would happen every 2&#8211;3 hours for some particular workers. We had to manually restart the hanging workers and quickly became a headache.</p><p>One hypothesis was that a particular task was making the workers hang, since the RMQ admin showed picked but <code>unacked</code> tasks whenever the workers used to hang. Checking the logs for the hung workers confirmed that the workers had started a particular task but not completed it. Double checked this by looking at the active tasks for hung workers by doing:</p><pre><code>&gt; celery -A squadrun inspect active</code></pre><p>Solution for this, as mentioned on the internet, was to periodically restart the workers. But this looked like a hack and not <em>the</em> solution to me. But we weren&#8217;t getting any further headway into this. On a hunch, again, we decided to use <code>strace</code> on a hung worker to <em>actually</em> see what a worker was stuck on. This turned out to be a good idea:</p><pre><code>&gt; sudo strace -p 27067
Process 27067 attached
futex(0x33372e8, FUTEX_WAIT_PRIVATE, 2, NULL ^C
Process 27067 detached</code></pre><p>On searching about what <code>futex</code> and <code>FUTEX_WAIT_PRIVATE</code> means, and checking similar issues with celery, we stumbled upon a <a href="https://github.com/celery/celery/issues/2429">similar issue as ours</a>.</p><p>From <a href="https://github.com/celery/celery/issues/2429#issuecomment-126402926">one of the comments</a> on the issue:</p><blockquote><p>Are you using Postgres in any way? Because that&#8217;s exactly what was happening with us. Postgres registers a locking callback with ssl, which all ssl requests use. Unfortunately Postgres unloads the callback after closing its connections, which is fine for Postgres, but any other ssl consumer (requests included) would be unable to release the lock, allowing a thread to deadlock itself if it tries to reacquire its own lock.</p></blockquote><p><em><strong>As mentioned, updating to <code>psycopg2==2.6.1</code> (we were at <code>2.5.2</code>) fixed the issue.</strong></em></p><h2>Workers kept on restarting</h2><blockquote><p>Tl;dr: <em><code>celery==4.0</code> had a bug, which got fixed in <code>v4.1.0</code>. But we went ahead with our own fork and applied the patch to it.</em></p></blockquote><p>Sometimes, we would have our workers continually restarting. This was a problem on our <code>staging</code> environment though, and, when nothing worked, emptying the RMQ queue once, and then publishing the tasks fixed the restarting worker.</p><p>To figure out what was going wrong, we again went back to the logs, and saw the following exception and traceback:</p><pre><code>2017-09-09 16:48:44,188 [CRITICAL] celery.worker: Unrecoverable error: TypeError("'NoneType' object is not callable",)
Traceback (most recent call last):
  File "celery/worker/worker.py", line 203, in start
    self.blueprint.start(self)

... ...

  File "billiard/pool.py", line 1487, in apply_async
    self._quick_put((TASK, (result._job, None, func, args, kwds)))
TypeError: 'NoneType' object is not callable</code></pre><p>This one was the easiest problem to figure out. Looks like when there are tasks already in the queue, and a worker is consuming from multiple queues, this bug makes an appearance. <a href="https://github.com/celery/celery/issues/3620">They start consuming from the queue even before the queue is ready</a>. This bug was fixed in <code>celery==4.1.0</code>. We were at <code>v4.0</code>. Before making the update though, we made sure there were no regressions or new bugs introduced in <code>v4.1.0</code>, we came across <a href="https://github.com/celery/celery/issues/4221">one major one</a>. This bug made us less confident of making the update, because <code>v4.1.0</code> was out fairly recently, and testing it thoroughly to make sure nothing else was broken would have taken a long time.</p><p><em><strong>So, we forked celery and <a href="https://github.com/ketanbhatt/celery/commit/c81c9f276cf48e68a654d98c5da7c4792ca4e441">applied the patch that fixes the bug</a> we were facing . with <code>v4.0</code>, and we were good to go!</strong></em></p><h2>Publishers did not adhere to &#8220;task publish retry policy&#8221;</h2><blockquote><p>Tl;dr: <em><code>task_publish_retry_policy</code> is broken in <code>kombu==4.1.0</code>, we downgraded to <code>4.0.2</code>.</em></p></blockquote><p>Just when we thought we were done with bugs and issues caused by celery and other related libraries, another made appearance. Somewhere amongst all this, we had also updated <code>kombu</code> to <code>v4.1.0</code>.</p><p>Now, for one of our APIs, we wanted the celery publisher to raise an exception as soon as the connection couldn&#8217;t be established while publishing. But it wouldn&#8217;t just happen. The worker will go to <code>sleep(1)</code> (as found out by the traceback when we killed the process manually) if a connection wasn&#8217;t established, and do this infinitely. We tried setting a stricter retry policy explicitly while publishing the task using <code>apply_async</code> but it didn&#8217;t work out.</p><p><em><strong>Finally, we found out that <code>task_publish_retry_policy</code> was broken in <code>kombu==4.1.0</code>. We downgraded to <code>4.0.2</code> and things started working as expected.</strong></em></p><h2>Takeaways:</h2><ol><li><p>Logs made it easy to debug issues. Long live logs.</p></li><li><p>Test library updates before applying them to production, because great developers make mistakes too :D</p></li></ol><p>A big thank you to <a href="https://www.linkedin.com/in/tarungarg546/">Tarun Garg</a>, my friend and colleague, for helping with the fixes :)</p><p>(Originally <a href="https://medium.com/squad-engineering/two-years-with-celery-in-production-bug-fix-edition-22238669601d">posted on my Medium account</a>)</p>]]></content:encoded></item><item><title><![CDATA[Leveraging AWS Lambda for Image Compression at scale]]></title><description><![CDATA[A lot of problems we solve at Squad deal with images.]]></description><link>https://www.ketanbhatt.com/p/aws-lambda-image-compression</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/aws-lambda-image-compression</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Sun, 17 Sep 2017 00:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25fe62de-4610-4644-91ac-54269404572d_590x332.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_GoH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25fe62de-4610-4644-91ac-54269404572d_590x332.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_GoH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25fe62de-4610-4644-91ac-54269404572d_590x332.png 424w, https://substackcdn.com/image/fetch/$s_!_GoH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25fe62de-4610-4644-91ac-54269404572d_590x332.png 848w, https://substackcdn.com/image/fetch/$s_!_GoH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25fe62de-4610-4644-91ac-54269404572d_590x332.png 1272w, https://substackcdn.com/image/fetch/$s_!_GoH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25fe62de-4610-4644-91ac-54269404572d_590x332.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_GoH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25fe62de-4610-4644-91ac-54269404572d_590x332.png" width="590" height="332" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/25fe62de-4610-4644-91ac-54269404572d_590x332.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:332,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Cover Image&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Cover Image" title="Cover Image" srcset="https://substackcdn.com/image/fetch/$s_!_GoH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25fe62de-4610-4644-91ac-54269404572d_590x332.png 424w, https://substackcdn.com/image/fetch/$s_!_GoH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25fe62de-4610-4644-91ac-54269404572d_590x332.png 848w, https://substackcdn.com/image/fetch/$s_!_GoH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25fe62de-4610-4644-91ac-54269404572d_590x332.png 1272w, https://substackcdn.com/image/fetch/$s_!_GoH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25fe62de-4610-4644-91ac-54269404572d_590x332.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A lot of problems we solve at Squad deal with images. Images to be tagged, labeled, moderated, transcribed etc. And there is this thing about images, they are generally heavier than text, digitally. And if our contractors (users of our app) spend more money on internet packs/plans than what they earn, or if they have to wait longer for the image to download because they have a slow/bad connection, their ROI from using Squad is no more justified.</p><h2>Once, there lived a simple program&#8230;</h2><p>No points for guessing, we compress the images on our end before we send it out to our contractors.</p><p>The client sends us, what we call, a &#8220;Data Unit&#8221; (basically a JSON containing all the data, like text, images, etc. pertaining to one particular item). Each data unit can contain multiple images. And all of these need to be compressed. Our current architecture involves sending a task (using <a href="http://www.celeryproject.org/">Celery</a>) to our queue (<a href="https://www.rabbitmq.com/">RabbitMQ</a>). These tasks get consumed by celery workers who are listening to the queue. Each worker picks up a task, which holds the ID of one data unit, loops over each URL, downloads it, compresses it, and then uploads it to a bucket in S3.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NI2A!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23199fe7-8d25-4570-abf0-c02fb871064e_590x449.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NI2A!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23199fe7-8d25-4570-abf0-c02fb871064e_590x449.png 424w, https://substackcdn.com/image/fetch/$s_!NI2A!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23199fe7-8d25-4570-abf0-c02fb871064e_590x449.png 848w, https://substackcdn.com/image/fetch/$s_!NI2A!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23199fe7-8d25-4570-abf0-c02fb871064e_590x449.png 1272w, https://substackcdn.com/image/fetch/$s_!NI2A!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23199fe7-8d25-4570-abf0-c02fb871064e_590x449.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NI2A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23199fe7-8d25-4570-abf0-c02fb871064e_590x449.png" width="590" height="449" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/23199fe7-8d25-4570-abf0-c02fb871064e_590x449.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:449,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Old Architecture&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Old Architecture" title="Old Architecture" srcset="https://substackcdn.com/image/fetch/$s_!NI2A!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23199fe7-8d25-4570-abf0-c02fb871064e_590x449.png 424w, https://substackcdn.com/image/fetch/$s_!NI2A!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23199fe7-8d25-4570-abf0-c02fb871064e_590x449.png 848w, https://substackcdn.com/image/fetch/$s_!NI2A!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23199fe7-8d25-4570-abf0-c02fb871064e_590x449.png 1272w, https://substackcdn.com/image/fetch/$s_!NI2A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23199fe7-8d25-4570-abf0-c02fb871064e_590x449.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Sorted.</p><p>This simple solution served us well. <strong>We had 4 workers that were able to process 3-4 Data Units per second on average</strong>. Of course, if a Data Unit had more images, it would take more time for that particular Data Unit. <strong>This was really slow.</strong> And as we are looking at getting more data every second and every hour, this would just not scale. At the current rate, we would just be able to process 10k data units per hour. <strong>We set out to change this to 100k per hour, with the provision of increasing this rate to 1 million per hour with simple and minimal horizontal scaling.</strong></p><h2>The Upgrade</h2><p>We figured the only way to do this was if:</p><ol><li><p>the downloading, compressing and uploading to S3 could all happen somewhere outside of our servers,</p></li><li><p>asynchronously.</p></li><li><p>And something that could scale infinitely, without us having to manage the infrastructure.</p></li></ol><p>The answer for us was <a href="http://docs.aws.amazon.com/lambda/latest/dg/welcome.html">AWS Lambda</a> which has all these features, plus is <a href="https://aws.amazon.com/lambda/pricing/">dirt cheap</a>.</p><p>We made a Lambda function that will take an URL and put the compressed version of it in S3 at a predefined location. But since we got multiple images per Data Unit, and we didn&#8217;t want to make HTTP calls in a loop (because they are costly as well), we created one more orchestrator Lambda function that took a list of URLs and for each URL hit the original Lambda function. And now each Data Unit for us is just one triggering of a lambda function. <strong>We hit rates of 30&#8211;40 Data Units per second, which is straight away a 10x growth.</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VSdX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37776db0-ac6a-44ea-8e9a-64480f0a13da_590x465.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VSdX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37776db0-ac6a-44ea-8e9a-64480f0a13da_590x465.png 424w, https://substackcdn.com/image/fetch/$s_!VSdX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37776db0-ac6a-44ea-8e9a-64480f0a13da_590x465.png 848w, https://substackcdn.com/image/fetch/$s_!VSdX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37776db0-ac6a-44ea-8e9a-64480f0a13da_590x465.png 1272w, https://substackcdn.com/image/fetch/$s_!VSdX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37776db0-ac6a-44ea-8e9a-64480f0a13da_590x465.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VSdX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37776db0-ac6a-44ea-8e9a-64480f0a13da_590x465.png" width="590" height="465" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/37776db0-ac6a-44ea-8e9a-64480f0a13da_590x465.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:465,&quot;width&quot;:590,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;New Architecture&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="New Architecture" title="New Architecture" srcset="https://substackcdn.com/image/fetch/$s_!VSdX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37776db0-ac6a-44ea-8e9a-64480f0a13da_590x465.png 424w, https://substackcdn.com/image/fetch/$s_!VSdX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37776db0-ac6a-44ea-8e9a-64480f0a13da_590x465.png 848w, https://substackcdn.com/image/fetch/$s_!VSdX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37776db0-ac6a-44ea-8e9a-64480f0a13da_590x465.png 1272w, https://substackcdn.com/image/fetch/$s_!VSdX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37776db0-ac6a-44ea-8e9a-64480f0a13da_590x465.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And this is essentially with half the number of workers because we are using the other half workers to check if the compression happened and if the URL was not broken. We straight away hit our goal. And since we get more throughput from individual workers, adding even a single one will increase the rate by a lot. <em>But there is another way this can scale.</em> Right now each worker just sends images of one Data Unit at one time. Since all we are doing is trigger the lambda function with a list of URLs, we can club URLs from multiple Data Units and send them all at once. That directly means performance gain in the order of <code>n</code>, <em>where n is the number of Data Units you club</em>. This lets us scale it further to an insane rate!</p><h2>Giveaways</h2><p>It was straightforward to set up PIL for AWS Lambda, you just have to follow the <a href="http://docs.aws.amazon.com/lambda/latest/dg/with-s3-example-deployment-pkg.html">provided docs</a> really well. Fire up an EC2, with Amazon&#8217;s Linux image, and install Pillow, and then package this in a zip and download it. See?</p><p>No worries, we took the pain so you don&#8217;t have to. <strong>You can find the site packages zip along with the image compression and the orchestrator Lambda functions <a href="https://github.com/squadrun/lambda-image-compression">here in our repository</a>.</strong> Break a leg!</p><p>(Originally <a href="https://medium.com/squad-engineering/leveraging-aws-lambda-for-image-compression-at-scale-a01afd756a12">posted on my Medium account</a>)</p>]]></content:encoded></item><item><title><![CDATA[Why Squad: Second Innings]]></title><description><![CDATA[No, I don&#8217;t think the world at large is interested in what I am doing with my life.]]></description><link>https://www.ketanbhatt.com/p/squad-second-innings</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/squad-second-innings</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Wed, 09 Aug 2017 00:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!adSA!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e37e6d-f289-4a21-95bd-008a89277566_426x426.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>No, I don&#8217;t think the world at large is interested in what I am doing with my life. But I needed to write this because this question will get asked often internally and because <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a>.</p><p>For the lazy, here is a <strong>tl;dr</strong>:</p><ol><li><p>I missed the team, and the hustle.</p></li><li><p>Platform team was facing acute bandwidth crunch.</p></li><li><p>It is the team that makes or breaks a company. And I am responsible for whatever happens to Squadrun.</p></li><li><p><strong>&lt;ambition&gt;</strong><em>Bring me a problem, I will solve it better than anyone else</em><strong>&lt;ambition\&gt;</strong>. Squad has only ever helped me move towards this, can&#8217;t wait to learn more by doing.</p></li></ol><p>For others, you are too free, but here it goes:</p><p><em>I left SquadRun on 2nd July 2017. On 10th August 2017, I am going to be back.</em></p><p>In reality though, I never actually left. I was in constant touch with the team here, talking to at least 1-2 different people daily (no exaggeration). I got constant updates on the good and the bad of whatever was happening in the team. I was contributing a little by writing a few articles here and there talking about our <a href="https://medium.com/squad-engineering">engineering team and culture</a>.</p><p>It is because of these conversations I was having with the team that I started realising how much was I missing the hustle. On top of that, we already had a shortage of engineers in the team, and me leaving only made it worse. The team was, thus, facing an acute bandwidth crunch. Sometimes VG had to get down to fix things himself, which I believe shouldn&#8217;t be needed now. <strong>And I felt responsible</strong>. I started toying with the idea of getting back for a small period just till the time we hire more people and the bandwidth crunch gets sorted.</p><p>Taking a step back, during my notice period, I read <a href="https://www.goodreads.com/book/show/27220736-shoe-dog">&#8220;The Shoe Dog&#8221;</a>. It made me realise that it is the team that makes a company great. This would not hold true a lot for an MNC or a startup that has grown to a large people scale (Zomato, Urbanclap etc), where you will just be a cog in the wheel (the cog size might differ based on a number of parameters). But at the stage at which we are right now, each individual contributor has a high impact. And we, as early employees, can not just ignore the responsibility of carrying SquadRun to great heights. I was already feeling that maybe my decision to leave wasn&#8217;t right, but wheels were already in motion and I wanted to take this plunge anyway. This also came back to me while I was toying with the idea of temporarily joining.</p><p>Also, I have come to realise that we overthink growth. We are thinking about it every week (at least I did), did I learn something new this week, how much did I learn, is my growth curve as steep as it was before? Sometimes we just need to also not take the growth thing so seriously, and lie back and enjoy the work we are doing. Don&#8217;t stress about it, you are getting better with every passing day.</p><ol><li><p>growth isn&#8217;t exactly quantifiable, it is not a score you can just compare every week. Compare with where you were 6 months back, maybe?</p></li><li><p>you grow as the company grows, the challenges that are thrown at you become different, and with each problem solved, you learn something.</p></li><li><p>just learning a new framework or a language is not growth, it is also being able to solve problems more effectively, or being able to manage stress better, or multitask with more efficiency, or being able to be a better mentor. Growth comes in many different forms.</p></li></ol><p>As Vikas told me when I was leaving, Squad has a lot of challenges for me still. But challenges or not, I am not letting anything happen to Squad. I have built a huge part of it, and I want to see, and not just see, but take, it to where it goes. Slowly, the idea of joining temporarily changed to an intense desire to be here long term. And here I am.</p><p>Now when I join back, I join back with the same vigour and passion I joined as an intern 2 years ago.</p><p>Let&#8217;s kick asses together, <strong>Game On!</strong></p><h2>FAQs</h2><p><strong>Q. Did Squad ask you to be back, or did you approach them?</strong> I did, completely my decision. VG supported the decision though.</p><p><strong>Q. Kahi aur job nahi mila? (Didn&#8217;t find a job or what?)</strong> :P While I am fine with these jokes, I thought I will just clarify once. No, I didn&#8217;t apply to any place. I was still figuring out stuff and enjoying my break.</p><p><strong>Q. Temporarily joined or here for good?</strong> Here for good. Until VG kicks me out.</p>]]></content:encoded></item><item><title><![CDATA[Vikas Gulati - The Platform behind Squad's Platform]]></title><description><![CDATA[As I just get off Whatsapp after talking to VG, I couldn&#8217;t stop mentioning it to a friend that this guy is Gold.]]></description><link>https://www.ketanbhatt.com/p/vikas-gulati</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/vikas-gulati</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Thu, 27 Jul 2017 00:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!adSA!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e37e6d-f289-4a21-95bd-008a89277566_426x426.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As I just get off Whatsapp after talking to VG, I couldn&#8217;t stop mentioning it to a friend that this guy is Gold. Platinum. Titanium (now, I am not that good with metals, all I mean is, he is the best). Before I lose this intense passion, I thought I will just document it. <strong>Let this be one of the first testimonials on him that the internet has because when the world starts knowing Vikas, they should know how far his awesomeness dates back to.</strong></p><p>When I joined SquadRun back in 2015, I was no more different than I am right now. And VG couldn&#8217;t be any more different than he is right now. And both of us couldn&#8217;t be any more different than each other than two people can ever be. This might not make sense, but it doesn&#8217;t have to. <em><strong>Feel it</strong></em>. Despite having these starkly different personality types, we clicked. He was the CTO of SquadRun, ever so serious. And I would pick him up (literally) and dance around with him (something that not a lot of people can boast of doing). But this is not about how well we clicked. This is about how Vikas goes on about his life solving hard problems every day, like a boss (please notice pun).</p><p><em>(L-R) Vikas, I and Shubham (another guy I love)</em></p><p>Vikas is empathetic and in all his dealings with people, he will always be fair. I have had my fair share of sad moments in life. And when they happened, VG would simply ask me to take a break if I was unable to focus. Once I made a sudden plan for a trip (before you judge me, Coldplay was in the country and I got free passes), VG happened to take one day to tell me it was fine for me to go away for 3-4 days. He paid the amount by which the flight price increased from the previous day, even though he really didn&#8217;t have to.</p><p>He is the type of guy who will let you take the love for something he helped you build that worked well, and _share_ the heat for things that didn&#8217;t work well (I know, I know that&#8217;s what great leaders do. But that&#8217;s the whole point! He is great!). Those times you did a mistake, sometimes major ones, you could see that he is not happy. And you would feel bad about it and despair that how are we going to fix it. And then the moment passes, and VG smiles at you and tells you it is okay, and that he thinks you can fix it. Your despair is gone, you scramble away to fix things, promising yourself you will never repeat the same mistake again. The understanding that mistakes happen, what is more important is that you learn from it and strive to not do it again, made sure we never had a terrible day at work.</p><p>As Squad grew, he magically started to handle all the new problems that come with people scale and in product development, in addition to what he was doing already. In true CTO style, he can break open-ended, large problems into smaller ones. He was involved in building hiring processes, managing product development, learning how to run a startup, learning how to make sure your engineers are happy and growing. He always knew the right thing to do. All this, and you will never see him go out and about beating his chest for something he did that solved a huge pain (I can&#8217;t say that about myself!).</p><blockquote><p>If VG was in cricket, Rahul Dravid would have just been called the fence or something. Because VG is the wall. ~ <em>Ketan Bhatt, giver of bad analogies</em></p></blockquote><p>He doesn&#8217;t break. He is been at it for ~3 years. Constantly taking on new challenges, preparing/learning for them, anticipating some of them before they even come, constantly growing and constantly inspiring. <strong>24*7</strong>. I haven&#8217;t met anyone who is so open to being a better version of himself every day. He is constantly experimenting. With routines, processes, books, food. What not!</p><p>And now, after I have left SquadRun, leaving the team with a bandwidth crunch, VG has again risen up to the occasion and is handling all the extra pressure with a smile. Even then, when I asked him to take a look at one of the companies I was interviewing with, he took out time to read up about the company and share what he felt. And that&#8217;s when I realised again, he is Gold.</p><p>I will just stop here because I am feeling better now. You praise us a lot for a small bug we could fix quickly, or a design we could think in and out of. But you need to be praised properly too. <strong>You have changed a lot of lives. Keep at it.</strong></p><p>I know this reads like a fan-mail. Aha, good observation skills lovely reader. It is! I am a fan Vikas &#10084;&#65039; and I know at least 10 more people who feel the same.</p>]]></content:encoded></item><item><title><![CDATA[How we built an engineering culture of doing more with less]]></title><description><![CDATA[Designed by Freepik]]></description><link>https://www.ketanbhatt.com/p/squad-engineering-culture</link><guid isPermaLink="false">https://www.ketanbhatt.com/p/squad-engineering-culture</guid><dc:creator><![CDATA[Ketan Bhatt]]></dc:creator><pubDate>Sat, 15 Jul 2017 00:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!adSA!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e37e6d-f289-4a21-95bd-008a89277566_426x426.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em><a href="https://www.freepik.com/free-vector/two-developers-planning-their-work_1311469.htm">Designed by Freepik</a></em></p><p>Sometimes when I tell my friends what we have built in a short span of time, I see there eyes going wide in disbelief. One candidate I was talking to about SquadRun thought we had 10&#8211;12 developers in the team, judging by what all we had built.</p><p><em>But we are not a team of 10&#8211;12.</em> <strong>At the moment, we are just 5, really committed, developers.</strong></p><p>And we get asked this often, how do we do it? Let me try and answer that. Two axes in the development plane have to be:</p><h2>Quality</h2><p><em>How and how well is the problem being solved.</em></p><p>We generally keep these things in mind while designing solutions for problems:</p><ol><li><p>The problem should get solved, of course, taking the right tradeoffs</p></li><li><p>All practical failure cases are handled</p></li><li><p>Most importantly, the design &#8220;FEELS&#8221; right</p></li></ol><p>Once the design is done and reviewed, we get down to implementing it. <strong>This means less time taken for the actual code to be written since the process is carefully thought of, and less time spent on fixing bugs.</strong></p><h2>Speed</h2><p><em>Always be shipping.</em></p><p>You will never find a Squad member whiling away his time. The kind of people we hire, they get frustrated with themselves if they are not achieving the best that they can.</p><p>Some of the things that we have done to maintain speed are:</p><ol><li><p>Our deployment method, although still far from being perfect, enables devs to deploy fast! 2 minutes average deployment time to all servers, with the longer ones taking around 6 minutes.</p></li><li><p>Encouraged to ship as soon as a story gets done and not wait for the deployments to get larger.</p></li><li><p>Pull requests are cleared at higher priority so code can be shipped ASAP.</p></li><li><p>Do the right thing. If a particular feature doesn&#8217;t need optimisations, don&#8217;t spend time on it. Premature optimisations are a no-no.</p></li></ol><p>Speed and Quality are like position and momentum in Heisenberg&#8217;s Uncertainty Principle. You can&#8217;t achieve best levels of both at the same time (I know that is not <em>exactly</em> what the principle says, scientists). But SquadRun has managed to strike a healthy balance between the two.</p><p>A lot of this has been possible by giving more emphasis on <strong>why we need to</strong> build something, <strong>what to build and how to build</strong> (in that order), that is:</p><ol><li><p>Why do we even need to solve this problem? <a href="https://www.youtube.com/watch?v=qp0HIF3SfI4">Questioning with &#8216;why&#8217;</a> always helps ensure we are aligned with higher level goals. <a href="https://blog.codinghorror.com/the-best-code-is-no-code-at-all/">Best code is no code</a>.</p></li><li><p>Out of N problems, what needs solving before others. (What has the most &#8216;<a href="https://www.goodreads.com/book/show/25238425-the-effective-engineer">leverage</a>&#8217;).</p></li><li><p>What is the best way to solve the problem?</p></li></ol><p><strong>It would be wrong to say that we don&#8217;t make mistakes. But we do our best to not make the same one twice.</strong> We learn from them and are constantly experimenting with our development process, and division of work among the team members. We share feedback openly, call out what didn&#8217;t work (<em>no matter whose suggestion it was</em>), and do more of what worked.</p><p>But at the end, more than the process, a team of engineers who are passionate about growing and solving problems are the key to doing a lot with little.</p><p>We still have a long way to go and much to learn, if you&#8217;d like to join us in building a product which we believe is going to change the world, <a href="http://bit.ly/squad-careers">reach out to us</a>!</p><p>(Originally <a href="https://medium.com/squad-engineering/how-we-built-an-engineering-culture-of-doing-more-with-less-a0a053be8e30">posted on my Medium account</a>)</p>]]></content:encoded></item></channel></rss>