<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Simplicity</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://minhajuddin.com/"/>
  <updated>2025-02-18T03:25:20.582Z</updated>
  <id>http://minhajuddin.com/</id>
  
  <author>
    <name>Khaja Minhajuddin</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>That one time when office365 was marking our emails as spam</title>
    <link href="http://minhajuddin.com/2025/02/17/that-one-time-when-office365-was-marking-our-emails-as-spam/"/>
    <id>http://minhajuddin.com/2025/02/17/that-one-time-when-office365-was-marking-our-emails-as-spam/</id>
    <published>2025-02-17T21:06:00.000Z</published>
    <updated>2025-02-18T03:25:20.582Z</updated>
    
    <content type="html"><![CDATA[<p>In 2017, I was working for a company that was building an aggregation platformfor hotel bookings. For some weird reason, for all the email we sent,recipients that used Office365 always had our emails going to their spamfolder. This was a problem because our emails were kind of important with allthe booking information.</p><p>This was kind of a head scratcher 😕, because were were using an office 365SMTP server to send these emails, so it was weird that they were being markedas spam. Also, gmail and other email providers were not marking our emails asspam.</p><p>After some investigation, I didn’t find anything weird about our email contentor how we were using the library which was used to send emails. This was anElixir app, and we were using the <a href="https://github.com/fewlinesco/bamboo_smtp">https://github.com/fewlinesco/bamboo_smtp</a>library.</p><p>The first thing I tried was to send an email to my personal email address anddownload the full message to see if there was anything weird going on. I didn’tfind anything strange.</p><p>Then, I figured, I could use another library to send out emails. I knew ruby,so I used <a href="https://github.com/benprew/pony">https://github.com/benprew/pony</a> to send out a test email from thesame SMTP server. And, NOW the emails were not marked as spam. Bingo, I knewthen, that there was something wrong with our elixir library. I dumped out afew emails from both the ruby and elixir libraries and compared them side byside using a diff tool. The only difference between the emails was a multi partdelimiter. And, I also found that the elixir library had a hard-codeddelimiter. And, for some reason this was causing the emails to be marked asspam by office365. (Office 365 was probably using this as a signal to mark theemail as spam).</p><p>Once I knew the problem, the fix was easy. I just had to change the delimiterto be a random string, which was a short PR:<a href="https://github.com/fewlinesco/bamboo_smtp/pull/39/files">https://github.com/fewlinesco/bamboo_smtp/pull/39/files</a></p><p>And, that was it. Our emails were no longer marked as spam by office365. 🎉</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;In 2017, I was working for a company that was building an aggregation platform
for hotel bookings. For some weird reason, for all the ema
      
    
    </summary>
    
    
      <category term="email" scheme="http://minhajuddin.com/tags/email/"/>
    
      <category term="elixir" scheme="http://minhajuddin.com/tags/elixir/"/>
    
      <category term="office365" scheme="http://minhajuddin.com/tags/office365/"/>
    
      <category term="spam" scheme="http://minhajuddin.com/tags/spam/"/>
    
      <category term="erlang" scheme="http://minhajuddin.com/tags/erlang/"/>
    
      <category term="bug" scheme="http://minhajuddin.com/tags/bug/"/>
    
  </entry>
  
  <entry>
    <title>How to fix cookies not persisting using CORS</title>
    <link href="http://minhajuddin.com/2025/02/17/how-to-fix-cookies-not-persisting-in-cors/"/>
    <id>http://minhajuddin.com/2025/02/17/how-to-fix-cookies-not-persisting-in-cors/</id>
    <published>2025-02-17T09:38:44.000Z</published>
    <updated>2025-02-18T03:25:20.569Z</updated>
    
    <content type="html"><![CDATA[<p>I have been toying around with React to refresh my frontend skills (The lasttime I used full stack seriously was with backbone.js 😂). While building thisapp, I wanted to use cookies to store a JWT token from the backend. I did notwant the jwt token to be stored in local storage because of the security implications.</p><p>Here is my reference app with the problem: The commit with the code that has the problem: <a href="https://github.com/minhajuddin/blog-samples/pull/1/commits/adbf276d649af049e56866306983ce653c187c96#diff-f3a80d02e5834a09d590f34b0af1f6c33a4397fd7c4a2eb6847380f228983542R44-R63">https://github.com/minhajuddin/blog-samples/pull/1/commits/adbf276d649af049e56866306983ce653c187c96#diff-f3a80d02e5834a09d590f34b0af1f6c33a4397fd7c4a2eb6847380f228983542R44-R63</a></p><p>Here is the relevant code:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">rg.POST(<span class="string">&quot;/login&quot;</span>, <span class="function"><span class="keyword">func</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line">    <span class="comment">// <span class="doctag">NOTE:</span> You would want to check username/password here</span></span><br><span class="line">    c.SetCookie(<span class="string">&quot;_auth&quot;</span>, <span class="string">&quot;awesome.jwt.token&quot;</span>, <span class="number">3600</span>, <span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>, <span class="literal">false</span>, <span class="literal">true</span>)</span><br><span class="line"></span><br><span class="line">    c.JSON(http.StatusOK, gin.H&#123;<span class="string">&quot;message&quot;</span>: <span class="string">&quot;Logged in via cookie&quot;</span>&#125;)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">rg.GET(<span class="string">&quot;/me&quot;</span>, <span class="function"><span class="keyword">func</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line">    jwt, err := c.Cookie(<span class="string">&quot;_auth&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        c.JSON(http.StatusUnauthorized, gin.H&#123;<span class="string">&quot;error&quot;</span>: <span class="string">&quot;Cookie not found&quot;</span>&#125;)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> jwt != <span class="string">&quot;awesome.jwt.token&quot;</span> &#123;</span><br><span class="line">        c.JSON(http.StatusUnauthorized, gin.H&#123;<span class="string">&quot;error&quot;</span>: <span class="string">&quot;Invalid token&quot;</span>&#125;)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    c.JSON(http.StatusOK, gin.H&#123;<span class="string">&quot;user&quot;</span>: <span class="string">&quot;minhajuddin&quot;</span>, <span class="string">&quot;id&quot;</span>: <span class="number">1</span>&#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>And, here is the javascript code that makes the request <a href="https://github.com/minhajuddin/blog-samples/pull/1/commits/adbf276d649af049e56866306983ce653c187c96#diff-b7726e02edfe1248f6ca02c671344d08f10f81801e755e28c0b8448ce3f50833R27-R34">https://github.com/minhajuddin/blog-samples/pull/1/commits/adbf276d649af049e56866306983ce653c187c96#diff-b7726e02edfe1248f6ca02c671344d08f10f81801e755e28c0b8448ce3f50833R27-R34</a></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">&#x27;login&#x27;</span>).addEventListener(<span class="string">&#x27;click&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  axios.post(<span class="string">&quot;http://localhost:8001/api/v1/login&quot;</span>).then(<span class="function"><span class="params">response</span> =&gt;</span></span><br><span class="line">    dbg(response)</span><br><span class="line">  ).catch(<span class="function"><span class="params">error</span> =&gt;</span></span><br><span class="line">      dbug(error.response)</span><br><span class="line">  )</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">&#x27;me&#x27;</span>).addEventListener(<span class="string">&#x27;click&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  axios.get(<span class="string">&quot;http://localhost:8001/api/v1/me&quot;</span>).then(<span class="function"><span class="params">response</span> =&gt;</span></span><br><span class="line">    dbg(response)</span><br><span class="line">  ).catch(<span class="function"><span class="params">error</span> =&gt;</span></span><br><span class="line">    dbg(error.response)</span><br><span class="line">  )</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>And, looking at the network tab in the browser, I see that the cookie is being set, but it is not being sent with the subsequent requests.</p><img src="/2025/02/17/how-to-fix-cookies-not-persisting-in-cors/logged-in-cookie-response.png" class="" title="Sending a login request which shows Cookie in the response"><img src="/2025/02/17/how-to-fix-cookies-not-persisting-in-cors/nocookie-response.png" class="" title="No Cookie in the next request 😞"><h2 id="The-fix"><a href="#The-fix" class="headerlink" title="The fix"></a>The fix</h2><p>The problem is that we are not using the <code>withCredentials</code> option in axios, this is required for cookies to be sent with the request. Let’s fix the code:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">&#x27;me&#x27;</span>).addEventListener(<span class="string">&#x27;click&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  axios.get(<span class="string">&quot;http://localhost:8001/api/v1/me&quot;</span>, &#123;<span class="attr">withCredentials</span>: <span class="literal">true</span>&#125;).then(<span class="function"><span class="params">response</span> =&gt;</span></span><br><span class="line">    dbg(response)</span><br><span class="line">  ).catch(<span class="function"><span class="params">error</span> =&gt;</span></span><br><span class="line">    dbg(error.response)</span><br><span class="line">  )</span><br></pre></td></tr></table></figure><h2 id="The-real-fix"><a href="#The-real-fix" class="headerlink" title="The real fix"></a>The real fix</h2><p>This still didn’t fix the problem for me and I was pulling my hair when I finally figured that the withCredentials option needs to be set even for the code that does the authentication. Let’s fix that:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">&#x27;login&#x27;</span>).addEventListener(<span class="string">&#x27;click&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  axios.post(<span class="string">&quot;http://localhost:8001/api/v1/login&quot;</span>, &#123;&#125;, &#123; <span class="attr">withCredentials</span>: <span class="literal">true</span> &#125;).then(<span class="function"><span class="params">response</span> =&gt;</span></span><br><span class="line">    dbg(response)</span><br><span class="line">  ).catch(<span class="function"><span class="params">error</span> =&gt;</span></span><br><span class="line">      dbug(error.response)</span><br><span class="line">  )</span><br><span class="line"></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>Now, the cookies are being sent with the request and the backend is able to read the cookie and authenticate the user.<img src="/2025/02/17/how-to-fix-cookies-not-persisting-in-cors/fixed.png" class="" title="Yay! Cookies in the subsequent requests 😀🍪🍪"></p><p>I hope this helps you fix your CORS cookie problems.</p><p>Happy hacking! 🚀</p><p>P.S: You could also set the withCredentials option globally for axios like so:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">axios.defaults.withCredentials = <span class="literal">true</span></span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;I have been toying around with React to refresh my frontend skills (The last
time I used full stack seriously was with backbone.js 😂). W
      
    
    </summary>
    
    
      <category term="cors" scheme="http://minhajuddin.com/tags/cors/"/>
    
      <category term="cookies" scheme="http://minhajuddin.com/tags/cookies/"/>
    
      <category term="backend" scheme="http://minhajuddin.com/tags/backend/"/>
    
      <category term="SPA" scheme="http://minhajuddin.com/tags/spa/"/>
    
      <category term="frontend" scheme="http://minhajuddin.com/tags/frontend/"/>
    
      <category term="axios" scheme="http://minhajuddin.com/tags/axios/"/>
    
      <category term="fetch" scheme="http://minhajuddin.com/tags/fetch/"/>
    
  </entry>
  
  <entry>
    <title>Memory Cached Tables for building faster web applications</title>
    <link href="http://minhajuddin.com/2021/10/31/memory-cached-tables-for-building-faster-web-applications/"/>
    <id>http://minhajuddin.com/2021/10/31/memory-cached-tables-for-building-faster-web-applications/</id>
    <published>2021-10-31T21:29:22.000Z</published>
    <updated>2025-02-18T03:25:20.579Z</updated>
    
    <content type="html"><![CDATA[<p>Most web apps have a few small tables which don’t change a lot but are read alot from, tables like <code>settings</code> or <code>plans</code> or <code>products</code> (some apps have less than 1000products). You can do a quick size check of your tables by running the followingquery:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- https://stackoverflow.com/a/21738732/24105</span></span><br><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">  pg_catalog.pg_namespace.nspname <span class="keyword">AS</span> schema_name,</span><br><span class="line">  relname,</span><br><span class="line">  pg_size_pretty(pg_relation_size(pg_catalog.pg_class.oid)) <span class="keyword">AS</span> tablesize</span><br><span class="line"><span class="keyword">FROM</span> pg_catalog.pg_class</span><br><span class="line"><span class="keyword">INNER</span> <span class="keyword">JOIN</span> pg_catalog.pg_namespace <span class="keyword">ON</span> relnamespace <span class="operator">=</span> pg_catalog.pg_namespace.oid</span><br><span class="line"><span class="keyword">WHERE</span> pg_catalog.pg_namespace.nspname <span class="operator">=</span> <span class="string">&#x27;public&#x27;</span> <span class="keyword">AND</span> pg_catalog.pg_class.reltype</span><br><span class="line"><span class="operator">&lt;&gt;</span> <span class="number">0</span> <span class="keyword">AND</span> relname <span class="keyword">NOT</span> <span class="keyword">LIKE</span> <span class="string">&#x27;%_id_seq&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> <span class="number">3</span> <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure><p>At this point you need to decide how much memory in your app you can allocatefor these lookup tables. In my experience for a moderately sized app you couldgo as high as 100MB tables. Make sure you add some metrics and benchmarkthis before and after doing any optimiztions.</p><p>Say you have 4 tables which are small enough to fit in memory, and which youread a lot from, the first thought that comes to mind is to use caching, andwhen someone says caching you reach for redis or memcache or some other networkservice. I would ask you to stop and think at this point, How would you cachein a way that is faster than redis or memcache?</p><p>Once you ask that question, the answer becomes obvious, you cache things in yourapp’s memory, if you have any data in your app’s memory you can just reach forit. Read this <a href="https://gist.github.com/jboner/2841832">excellent gist to get a sense of the latency of different kinds of storage strategies</a>.</p><p>When using your app’s memory you don’t have to pay the network cost plus theserialization/deserialization tax. Everytime you cache something in redis ormemcached, your app has to make a network call to these services and push out aserialized version of the data while saving it and do the opposite while readingit. This cost adds up if you do it on every page load.</p><p>I work with an app which keeps a website maintenance flag in memcache and thisends up adding 30ms to every request that hits our servers. There is a betterway! Move your settings to your app’s memory. This can easily be done bydefining something like below(in ruby):</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># config/initializers/settings.rb</span></span><br><span class="line"><span class="variable">$settings_hash</span> = Setting.all.map&#123;<span class="params">|x|</span> [x.key, x]&#125;.to_h</span><br><span class="line"><span class="class"><span class="keyword">module</span> <span class="title">Settings</span></span></span><br><span class="line">  module_function</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">find</span><span class="params">(key)</span></span></span><br><span class="line">    <span class="variable">$settings_hash</span>[key]</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>However, as they say one of the two hard problems in computer science is cacheinvalidation. What do you do when your data changes? This is the hard part.</p><h2 id="Just-restart-it"><a href="#Just-restart-it" class="headerlink" title="Just restart it!"></a>Just restart it!</h2><p>The easiest strategy for this is to restart the server. This might be aperfectly valid strategy. We do restart our apps when config values change, sorestarting for lookup tables with low frequency changes is a fair strategy.</p><h2 id="Poll-for-changes"><a href="#Poll-for-changes" class="headerlink" title="Poll for changes"></a>Poll for changes</h2><p>If that doesn’t work for your app because your lookup data changes frequently,let us say every 5 minutes, another strategy is to poll for this data. Theidea is simple:</p><ol><li>You load your data into a global variable.</li><li>You poll for changes in a separate thread using something like<a href="https://github.com/brandonhilkert/sucker_punch#executing-jobs-in-the-future">suckerpunch</a>in ruby and<a href="https://viniciuschiele.github.io/flask-apscheduler/rst/usage.html">APScheduler</a>in python.</li></ol><h3 id="Content-hash-of-your-table"><a href="#Content-hash-of-your-table" class="headerlink" title="Content hash of your table"></a>Content hash of your table</h3><p>Fortunately there is an easy way to see if there is any change on a table inpostgres, aggregate the whole table into a single text column and then computethe md5sum of it. This should change any time there is a change to the data.</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">    <span class="comment">/* to avoid random sorting */</span></span><br><span class="line">    MD5(<span class="built_in">CAST</span>((<span class="built_in">ARRAY_AGG</span>(t.<span class="operator">*</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> t)) <span class="keyword">AS</span> text)) content_hash</span><br><span class="line"><span class="keyword">FROM</span></span><br><span class="line">settings t;</span><br></pre></td></tr></table></figure><p>Output of this query<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">┌──────────────────────────────────┐</span><br><span class="line">│           content_hash           │</span><br><span class="line">├──────────────────────────────────┤</span><br><span class="line">│ 337f91e1e09b09e96b3413d27102c761 │</span><br><span class="line">└──────────────────────────────────┘</span><br><span class="line">(1 row)</span><br></pre></td></tr></table></figure></p><p>Now, all you do is keep a tab on this content hash every 5 minutes or so andreload the tables when it changes.</p><h2 id="Use-Postgres-Subscriptions"><a href="#Use-Postgres-Subscriptions" class="headerlink" title="Use Postgres Subscriptions"></a>Use Postgres Subscriptions</h2><p>Postgres has support for subscriptions, so you could add a mechanism where eachtable has a subscription that you push to whenever you modify data usingtriggers.<a href="https://www.postgresql.org/docs/10/sql-createsubscription.html">https://www.postgresql.org/docs/10/sql-createsubscription.html</a></p><h2 id="Use-app-based-pub-sub"><a href="#Use-app-based-pub-sub" class="headerlink" title="Use app based pub/sub"></a>Use app based pub/sub</h2><p>If all your changes go through the app through some kind of admin webpage, youcould also add pub/sub to broadcast an update whenever data is modified to whichall your app servers listen to and refresh the data.</p><p>Since elixir and erlang are all about concurrency, they lend themselves nicelyto this idiom. Let us see how this can be done in Elixir.</p><h2 id="Manual-cache-invalidation"><a href="#Manual-cache-invalidation" class="headerlink" title="Manual cache invalidation"></a>Manual cache invalidation</h2><p>You could also build a button on your admin console which just pings a specificendpoint e.g. <code>/admin/:table/cache-invalidate</code> and allow for manual cacheinvalidation. The handler for this would just reload the global data.</p><p>I feel like the polling strategy is the most robust with the least number ofmoving pieces. Please try this out in your app and let me know how this impactsyour performance.</p><p>In a future blog post, I’ll explain <a href="https://github.com/minhajuddin/memory_cached_tables/blob/main/lib/memory_cached_tables/cached_settings.ex">the elixir implemenation</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Most web apps have a few small tables which don’t change a lot but are read a
lot from, tables like &lt;code&gt;settings&lt;/code&gt; or &lt;code&gt;plans&lt;
      
    
    </summary>
    
    
      <category term="Elixir" scheme="http://minhajuddin.com/tags/elixir/"/>
    
      <category term="Ruby" scheme="http://minhajuddin.com/tags/ruby/"/>
    
      <category term="Memory Cached Tables" scheme="http://minhajuddin.com/tags/memory-cached-tables/"/>
    
      <category term="Speed" scheme="http://minhajuddin.com/tags/speed/"/>
    
      <category term="Performance" scheme="http://minhajuddin.com/tags/performance/"/>
    
      <category term="Python" scheme="http://minhajuddin.com/tags/python/"/>
    
      <category term="Web apps" scheme="http://minhajuddin.com/tags/web-apps/"/>
    
      <category term="Fast" scheme="http://minhajuddin.com/tags/fast/"/>
    
      <category term="Lookup tables" scheme="http://minhajuddin.com/tags/lookup-tables/"/>
    
  </entry>
  
  <entry>
    <title>A baton server to test your Erlang/Elixir cluster</title>
    <link href="http://minhajuddin.com/2021/04/20/a-baton-server-to-test-your-erlang-elixir-cluster/"/>
    <id>http://minhajuddin.com/2021/04/20/a-baton-server-to-test-your-erlang-elixir-cluster/</id>
    <published>2021-04-20T21:33:26.000Z</published>
    <updated>2025-02-18T03:25:20.565Z</updated>
    
    <content type="html"><![CDATA[<p>I have been dipping my feet into Distributed Systems and <a href="https://minhajuddin.com/2021/04/15/how-to-show-raspberry-pi-temperatures-in-your-datadog-dashboard/">set up a cluster of Raspberry Pi Nodes recently</a>.The first thing I wanted to try was forming an Erlang cluster, And <a href="https://github.com/bitwalker/libcluster">libcluster</a>makes this very easy through the Gossip strategy. Here is the code to form theerlang cluster automatically (as long as it is on the same network).</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># application.ex</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># ...</span></span><br><span class="line">children = [</span><br><span class="line">  &#123;Cluster.Supervisor,</span><br><span class="line">   [Application.get_env(<span class="symbol">:herd</span>, <span class="symbol">:topologies</span>), [<span class="symbol">name:</span> Herd.ClusterSupervisor]]&#125;,</span><br><span class="line"><span class="comment"># ...</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># config/config.exs</span></span><br><span class="line">config <span class="symbol">:herd</span>,</span><br><span class="line">  <span class="symbol">topologies:</span> [</span><br><span class="line">    <span class="comment"># topologies can contain multiple strategies, However, we just have one</span></span><br><span class="line">    <span class="comment"># The name `:gossip` is not important</span></span><br><span class="line">    <span class="symbol">gossip:</span> [</span><br><span class="line">      <span class="comment"># The selected clustering strategy. Required.</span></span><br><span class="line">      <span class="symbol">strategy:</span> Cluster.Strategy.Gossip,</span><br><span class="line">      <span class="comment"># Configuration for the provided strategy. Optional.</span></span><br><span class="line">      <span class="symbol">config:</span> [</span><br><span class="line">        <span class="symbol">port:</span> <span class="number">45892</span>,</span><br><span class="line">        <span class="symbol">if_addr:</span> <span class="string">&quot;0.0.0.0&quot;</span>,</span><br><span class="line">        <span class="symbol">multicast_if:</span> <span class="string">&quot;192.168.1.1&quot;</span>,</span><br><span class="line">        <span class="symbol">multicast_addr:</span> <span class="string">&quot;230.1.1.251&quot;</span>,</span><br><span class="line">        <span class="symbol">multicast_ttl:</span> <span class="number">1</span>,</span><br><span class="line">        <span class="symbol">secret:</span> <span class="string">&quot;somepassword&quot;</span></span><br><span class="line">      ],</span><br><span class="line">      <span class="comment"># The function to use for connecting nodes. The node</span></span><br><span class="line">      <span class="comment"># name will be appended to the argument list. Optional</span></span><br><span class="line">      <span class="symbol">connect:</span> &#123;<span class="symbol">:net_kernel</span>, <span class="symbol">:connect_node</span>, []&#125;,</span><br><span class="line">      <span class="comment"># The function to use for disconnecting nodes. The node</span></span><br><span class="line">      <span class="comment"># name will be appended to the argument list. Optional</span></span><br><span class="line">      <span class="symbol">disconnect:</span> &#123;<span class="symbol">:erlang</span>, <span class="symbol">:disconnect_node</span>, []&#125;,</span><br><span class="line">      <span class="comment"># The function to use for listing nodes.</span></span><br><span class="line">      <span class="comment"># This function must return a list of node names. Optional</span></span><br><span class="line">      <span class="symbol">list_nodes:</span> &#123;<span class="symbol">:erlang</span>, <span class="symbol">:nodes</span>, [<span class="symbol">:connected</span>]&#125;</span><br><span class="line">    ]</span><br><span class="line">  ]</span><br></pre></td></tr></table></figure><p>Once, the clustering was set up I wanted to try sending messages through thecluster and see how it performed, the simplest test I could think of was a batonrelay. Essentially, I spin up one GenServer per node and it relays a counter tothe next node, which sends it to the next node and so on like the picture below(psa, psb, psc, and psd are the names of the nodes):</p><img src="/2021/04/20/a-baton-server-to-test-your-erlang-elixir-cluster/BatonServer.svg" class="" title="Baton Server Flow"><p>The code for this ended up being very straightforward. We create a GenServer andmake one of the nodes a <code>main_node</code> so that it can kick off the baton relay.And, whenever we get a counter with a <code>:pass</code> message we increment the counterand forward it to the next node. Here is the full code:</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">defmodule</span> <span class="title">Herd.Baton.ErlangProcess</span></span> <span class="keyword">do</span></span><br><span class="line">  <span class="keyword">use</span> GenServer</span><br><span class="line">  <span class="keyword">require</span> Logger</span><br><span class="line"></span><br><span class="line">  <span class="variable">@doc</span> <span class="keyword">false</span></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">start_link</span></span>(opts) <span class="keyword">do</span></span><br><span class="line">    <span class="comment"># Use &#123;:global, ...&#125; name&#x27;s so that they can be addressed from other nodes</span></span><br><span class="line">    name = global_proc_name(Node.self())</span><br><span class="line">    Logger.info(<span class="string">&quot;starting a baton process&quot;</span>, <span class="symbol">name:</span> inspect(name))</span><br><span class="line">    GenServer.start_link(__MODULE__, opts, <span class="symbol">name:</span> name)</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># API to capture the shape of the function that sends a message to the next node</span></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">pass</span></span>(node, rest_nodes, counter) <span class="keyword">do</span></span><br><span class="line">    GenServer.cast(global_proc_name(node), &#123;<span class="symbol">:pass</span>, rest_nodes, counter&#125;)</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="variable">@impl</span> <span class="keyword">true</span></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">init</span></span>(state) <span class="keyword">do</span></span><br><span class="line">    send(<span class="keyword">self</span>(), <span class="symbol">:init</span>)</span><br><span class="line">    &#123;<span class="symbol">:ok</span>, state&#125;</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="variable">@impl</span> <span class="keyword">true</span></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">handle_info</span></span>(<span class="symbol">:init</span>, state) <span class="keyword">do</span></span><br><span class="line">    if main_node?() <span class="keyword">do</span></span><br><span class="line">      if cluster_formed?() <span class="keyword">do</span></span><br><span class="line">        <span class="comment"># Kick off the baton relay if we are the main node</span></span><br><span class="line">        pass(Node.self(), [], <span class="number">1</span>)</span><br><span class="line">      else</span><br><span class="line">        <span class="comment"># check again after 1 second</span></span><br><span class="line">        Process.send_after(<span class="keyword">self</span>(), <span class="symbol">:init</span>, <span class="number">1000</span>)</span><br><span class="line">      <span class="keyword">end</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">    &#123;<span class="symbol">:noreply</span>, state&#125;</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># Our config has the name of the main node like so:</span></span><br><span class="line">  <span class="comment"># config :herd,</span></span><br><span class="line">  <span class="comment">#   main_node: :herd@psa</span></span><br><span class="line">  <span class="function"><span class="keyword">defp</span> <span class="title">main_node?</span></span>() <span class="keyword">do</span></span><br><span class="line">    Application.get_env(<span class="symbol">:herd</span>, <span class="symbol">:main_node</span>) == Node.self()</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">defp</span> <span class="title">cluster_formed?</span></span>() <span class="keyword">do</span></span><br><span class="line">    Node.list() != []</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="variable">@impl</span> <span class="keyword">true</span></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">handle_cast</span></span>(&#123;<span class="symbol">:pass</span>, nodes, counter&#125;, state) <span class="keyword">do</span></span><br><span class="line">    pass_the_baton(nodes, counter)</span><br><span class="line">    &#123;<span class="symbol">:noreply</span>, state&#125;</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">defp</span> <span class="title">pass_the_baton</span></span>([], counter), <span class="symbol">do:</span> pass_the_baton(cluster_nodes(), counter)</span><br><span class="line">  <span class="function"><span class="keyword">defp</span> <span class="title">pass_the_baton</span></span>([next_node | rest_nodes], counter) <span class="keyword">do</span></span><br><span class="line">    <span class="comment"># Datadog guage to show us the change in counter</span></span><br><span class="line">    Datadog.gauge(<span class="string">&quot;baton&quot;</span>, counter, <span class="symbol">tags:</span> host_tags())</span><br><span class="line">    pass(next_node, rest_nodes, counter + <span class="number">1</span>)</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">defp</span> <span class="title">host_tags</span></span> <span class="keyword">do</span></span><br><span class="line">    tags(<span class="symbol">host:</span> to_string(Node.self()))</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">tags</span></span>(kwlist) <span class="keyword">do</span></span><br><span class="line">    kwlist</span><br><span class="line">    |&gt; Enum.map(<span class="keyword">fn</span> &#123;k, v&#125; -&gt; <span class="string">&quot;<span class="subst">#&#123;k&#125;</span>:<span class="subst">#&#123;v&#125;</span>&quot;</span> <span class="keyword">end</span>)</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">defp</span> <span class="title">global_proc_name</span></span>(node) <span class="keyword">do</span></span><br><span class="line">    &#123;<span class="symbol">:global</span>, &#123;node, __MODULE__&#125;&#125;</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">defp</span> <span class="title">cluster_nodes</span></span> <span class="keyword">do</span></span><br><span class="line">    [Node.self() | Node.list()]</span><br><span class="line">    |&gt; Enum.shuffle()</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>Finally, here is the Datadog graph for the counter, The big thing to note isthat the 4 GenServers on a local lan were able to pass around 100M messages in 8hours which amounts to about 3.5K messages per second which is impressive:</p><img src="/2021/04/20/a-baton-server-to-test-your-erlang-elixir-cluster/datadog-counter.png" class="" title="Datadog Counter Graph">]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;I have been dipping my feet into Distributed Systems and &lt;a href=&quot;https://minhajuddin.com/2021/04/15/how-to-show-raspberry-pi-temperature
      
    
    </summary>
    
    
      <category term="Elixir" scheme="http://minhajuddin.com/tags/elixir/"/>
    
      <category term="Erlang" scheme="http://minhajuddin.com/tags/erlang/"/>
    
      <category term="Distributed" scheme="http://minhajuddin.com/tags/distributed/"/>
    
      <category term="Cluster" scheme="http://minhajuddin.com/tags/cluster/"/>
    
      <category term="Baton" scheme="http://minhajuddin.com/tags/baton/"/>
    
      <category term="Server" scheme="http://minhajuddin.com/tags/server/"/>
    
      <category term="libcluster" scheme="http://minhajuddin.com/tags/libcluster/"/>
    
  </entry>
  
  <entry>
    <title>How to show Raspberry Pi temperatures in your Datadog dashboard</title>
    <link href="http://minhajuddin.com/2021/04/15/how-to-show-raspberry-pi-temperatures-in-your-datadog-dashboard/"/>
    <id>http://minhajuddin.com/2021/04/15/how-to-show-raspberry-pi-temperatures-in-your-datadog-dashboard/</id>
    <published>2021-04-15T10:38:57.000Z</published>
    <updated>2025-02-18T03:25:20.572Z</updated>
    
    <content type="html"><![CDATA[<p>So, I’ve set up a cluster of 4 Raspberry Pis to learn and experiment withdistributed systems.</p><img src="/2021/04/15/how-to-show-raspberry-pi-temperatures-in-your-datadog-dashboard/pi-cluster.jpeg" class="" title="My Raspberry Pi Cluster"><p>I also wanted to track various metrics while running the cluster, so I set upDatadog APMs on all of them and since the pis usually run hot, I wanted to tracktheir temperatures for warning signs. Here is how you can send your temperatureinfo to Datadog.</p><p>Create 2 files, a <code>temp.yaml</code> and a <code>temp.py</code> (the names should match).</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /etc/datadog-agent/conf.d/temp.yaml</span></span><br><span class="line"><span class="attr">instances:</span> [&#123;&#125;]</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /etc/datadog-agent/checks.d/temp.py</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"></span><br><span class="line"><span class="comment"># the following try/except block will make the custom check compatible with any Agent version</span></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    <span class="comment"># first, try to import the base class from new versions of the Agent...</span></span><br><span class="line">    <span class="keyword">from</span> datadog_checks.base <span class="keyword">import</span> AgentCheck</span><br><span class="line"><span class="keyword">except</span> ImportError:</span><br><span class="line">    <span class="comment"># ...if the above failed, the check is running in Agent version &lt; 6.6.0</span></span><br><span class="line">    <span class="keyword">from</span> checks <span class="keyword">import</span> AgentCheck</span><br><span class="line"></span><br><span class="line"><span class="comment"># content of the special variable __version__ will be shown in the Agent status page</span></span><br><span class="line">__version__ = <span class="string">&quot;1.0.0&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TempCheck</span>(<span class="params">AgentCheck</span>):</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">check</span>(<span class="params">self, instance</span>):</span></span><br><span class="line">        self.gauge(</span><br><span class="line">            <span class="string">&quot;custom.temperature&quot;</span>,</span><br><span class="line">            (<span class="built_in">int</span>(Path(<span class="string">&quot;/sys/class/thermal/thermal_zone0/temp&quot;</span>).read_text().strip()) / <span class="number">1000</span>),</span><br><span class="line">            tags=[],</span><br><span class="line">        )</span><br></pre></td></tr></table></figure><p>The meat of this code is the following, where we send a <code>guage</code> metric named<code>custom.temperature</code> and send it the temperature by reading<code>/sys/class/thermal/thermal_zone0/temp</code> (this is how you can read thetemperature for a pi with ubuntu installed, you may have to tweak this bit forother distros)<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">self.gauge(</span><br><span class="line">    <span class="string">&quot;custom.temperature&quot;</span>,</span><br><span class="line">    (<span class="built_in">int</span>(Path(<span class="string">&quot;/sys/class/thermal/thermal_zone0/temp&quot;</span>).read_text().strip()) / <span class="number">1000</span>),</span><br><span class="line">    tags=[],</span><br><span class="line">)</span><br><span class="line"></span><br></pre></td></tr></table></figure></p><p>That’s it, you can tack other metrics in their too if you’d like to. You’ll alsoneed to restart your datadog agent for it to start sending these metrics.</p><p>You can <a href="https://docs.datadoghq.com/developers/metrics/agent_metrics_submission/?tab=count#tutorial">read more about custom metrics in Datadog  here</a><img src="/2021/04/15/how-to-show-raspberry-pi-temperatures-in-your-datadog-dashboard/dd.png" class="" title="Max of custom.temperature by host"></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;So, I’ve set up a cluster of 4 Raspberry Pis to learn and experiment with
distributed systems.&lt;/p&gt;
&lt;img src=&quot;/2021/04/15/how-to-show-rasp
      
    
    </summary>
    
    
      <category term="raspberry pi" scheme="http://minhajuddin.com/tags/raspberry-pi/"/>
    
      <category term="cluster" scheme="http://minhajuddin.com/tags/cluster/"/>
    
      <category term="datadog" scheme="http://minhajuddin.com/tags/datadog/"/>
    
      <category term="temperature" scheme="http://minhajuddin.com/tags/temperature/"/>
    
  </entry>
  
  <entry>
    <title>How to rename table using pg_dump or pg_restore</title>
    <link href="http://minhajuddin.com/2021/03/14/how-to-rename-table-using-pg-dump-or-pg-restore/"/>
    <id>http://minhajuddin.com/2021/03/14/how-to-rename-table-using-pg-dump-or-pg-restore/</id>
    <published>2021-03-14T18:22:43.000Z</published>
    <updated>2025-02-18T03:25:20.572Z</updated>
    
    <content type="html"><![CDATA[<p>I am in the process of migrating the data from one of my side projects to arewritten schema. While doing this, I wanted to keep the old table around with adifferent name and do migrations at run time, only when I see someone is usingit. So, I started looking into how I can rename my table while doing apg_restore, turns out there is no way to do it. The following is a hacky way toget it working.</p><ol><li>Do a <code>pg_dump</code> of your db: <code>pg_dump -Fc --no-acl --no-owner --table forms my_forms_prod &gt; my_forms_prod.pgdump</code></li><li>Do a <code>pg_restore</code> into a temporary <code>scratch</code> database <code>pg_restore --verbose --clean --no-acl --no-owner -d scratch my_forms_prod.dump</code></li><li>Rename your table: <code>ALTER TABLE forms RENAME TO old_forms;</code></li><li>Do another dump: <code>pg_dump -Fc --no-acl --no-owner scratch &gt; my_old_forms_prod.pgdump</code> which will have the “RENAMED” table :D</li><li>Now, you can import this using pg_restore: <code>pg_restore --verbose --clean --no-acl --no-owner -d my_new_forms_prod my_old_forms_prod.dump</code></li></ol><p>This is just a hack though. Hope you find it useful 😀</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;I am in the process of migrating the data from one of my side projects to a
rewritten schema. While doing this, I wanted to keep the old 
      
    
    </summary>
    
    
      <category term="postgresql" scheme="http://minhajuddin.com/tags/postgresql/"/>
    
      <category term="pg_dump" scheme="http://minhajuddin.com/tags/pg-dump/"/>
    
      <category term="pg_restore" scheme="http://minhajuddin.com/tags/pg-restore/"/>
    
      <category term="rename" scheme="http://minhajuddin.com/tags/rename/"/>
    
      <category term="table" scheme="http://minhajuddin.com/tags/table/"/>
    
  </entry>
  
  <entry>
    <title>Moving a Rails managed database to Phoenix</title>
    <link href="http://minhajuddin.com/2021/03/07/moving-a-rails-managed-database-to-phoenix/"/>
    <id>http://minhajuddin.com/2021/03/07/moving-a-rails-managed-database-to-phoenix/</id>
    <published>2021-03-07T14:32:11.000Z</published>
    <updated>2025-02-18T03:25:20.579Z</updated>
    
    <content type="html"><![CDATA[<p>I am moving my app from Rails to Phoenix and as part of this I have to move mydatabase from being managed by Rails migrations to Phoenix migrations. Here ishow I did it:</p><ol><li>Rename the <code>schema_migrations</code> table. Phoenix uses Ecto for managing thedatabase. Ecto and Rails use a table called <code>schema_migrations</code> to store thedatabase migration info. So, you’ll have to rename it to avoid errors whenyou run Ecto migrations.<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">psql db</span><br><span class="line">ALTER TABLE schema_migrations RENAME TO rails_schema_migrations</span><br></pre></td></tr></table></figure></li><li>After this, you’ll need to create the schema_migrations table for ecto, youcan do it by running the <code>mix ecto.create</code> command. This will set up the<code>schema_migrations</code> table in the existing database.</li></ol><p>Now, you’ve successfully migrated your database. And, you can run yourPhoenix/Ecto migrations like you would in a normal phoenix app.</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;I am moving my app from Rails to Phoenix and as part of this I have to move my
database from being managed by Rails migrations to Phoenix
      
    
    </summary>
    
    
      <category term="Elixir" scheme="http://minhajuddin.com/tags/elixir/"/>
    
      <category term="Phoenix" scheme="http://minhajuddin.com/tags/phoenix/"/>
    
      <category term="Ecto" scheme="http://minhajuddin.com/tags/ecto/"/>
    
      <category term="Migration" scheme="http://minhajuddin.com/tags/migration/"/>
    
      <category term="Rails" scheme="http://minhajuddin.com/tags/rails/"/>
    
      <category term="Database" scheme="http://minhajuddin.com/tags/database/"/>
    
  </entry>
  
  <entry>
    <title>A simple way to store secrets using Parameter Store for your ECS applications</title>
    <link href="http://minhajuddin.com/2021/03/07/a-simple-way-to-store-secrets-using-parameter-store-for-your-ecs-applications/"/>
    <id>http://minhajuddin.com/2021/03/07/a-simple-way-to-store-secrets-using-parameter-store-for-your-ecs-applications/</id>
    <published>2021-03-07T13:52:40.000Z</published>
    <updated>2025-02-18T03:25:20.566Z</updated>
    
    <content type="html"><![CDATA[<p>I have an ECS cluster for my side projects and need to pass secrets to the app.There are a few ways of doing it, and I think I found a nice balance betweensimplicity and ease of use.</p><h2 id="Wrong-ways-of-sharing-secrets"><a href="#Wrong-ways-of-sharing-secrets" class="headerlink" title="Wrong ways of sharing secrets"></a>Wrong ways of sharing secrets</h2><p>There are a few wrong ways of sharing secrets, Make sure you don’t do any ofthese 🙂</p><ol><li><em>Secrets in source code</em>: This is a big no-no, you don’t want to storesecrets in your code because anyone with access to your code will be able toread them.</li><li><em>Secrets built into the docker image</em>: This is another bad idea, becauseanyone with access to your images will have your secrets, moreover, if youwant to change a secret, you’ll have to build a new image and deploy it.</li><li><em>Secrets in the terraform ECS task definitions Environment block</em>: This isnot very bad, but anyone with access to your terraform repo will be able toread your secrets.</li></ol><h2 id="Store-Secrets-in-the-parameter-store-one-parameter-per-secret"><a href="#Store-Secrets-in-the-parameter-store-one-parameter-per-secret" class="headerlink" title="Store Secrets in the parameter store, one parameter per secret"></a>Store Secrets in the parameter store, one parameter per secret</h2><p>The parameter store is a <em>free</em> and easy tool to save your secrets. There aremore fancy options like the secret manager, but they cost money.</p><p>One way of storing secrets is to create one parameter per environment variable,e.g. if you have an app called money, you could create parameters called<code>money_database_url</code>, <code>money_secret_access_token</code> etc,. Make sure you createthem as ‘SecretString’ types. And then in your task definition. Use thefollowing code:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">&quot;name&quot;</span>: <span class="string">&quot;money-web&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;image&quot;</span>: <span class="string">&quot;...&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;cpu&quot;</span>: <span class="number">256</span>,</span><br><span class="line">    <span class="attr">&quot;memory&quot;</span>: <span class="number">512</span>,</span><br><span class="line">    <span class="attr">&quot;essential&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">&quot;portMappings&quot;</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">&quot;containerPort&quot;</span>: <span class="number">8000</span>,</span><br><span class="line">        <span class="attr">&quot;hostPort&quot;</span>: <span class="number">0</span></span><br><span class="line">      &#125;</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">&quot;secrets&quot;</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">&quot;name&quot;</span>: <span class="string">&quot;DATABASE_URL&quot;</span>,</span><br><span class="line">        <span class="attr">&quot;valueFrom&quot;</span>: <span class="string">&quot;money_database_url&quot;</span></span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">&quot;name&quot;</span>: <span class="string">&quot;SECRET_ACCESS_TOKEN&quot;</span>,</span><br><span class="line">        <span class="attr">&quot;valueFrom&quot;</span>: <span class="string">&quot;money_secret_access_token&quot;</span></span><br><span class="line">      &#125;</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">&quot;environment&quot;</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">&quot;name&quot;</span>: <span class="string">&quot;MIX_ENV&quot;</span>,</span><br><span class="line">        <span class="attr">&quot;value&quot;</span>: <span class="string">&quot;prod&quot;</span></span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>This will make your secrets available to your ECS container via environmentvariables called <code>DATABASE_URL</code> and <code>SECRET_ACCESS_TOKEN</code>. However, if you havelots of secrets, this becomes unweildy.</p><h2 id="Store-Secrets-in-the-parameter-store-one-parameter-per-app"><a href="#Store-Secrets-in-the-parameter-store-one-parameter-per-app" class="headerlink" title="Store Secrets in the parameter store, one parameter per app"></a>Store Secrets in the parameter store, one parameter per app</h2><p>I create a file called <code>secrets.json</code> with all the secrets (You can tweak thisstep, and use some other format)</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">&quot;db&quot;</span>:<span class="string">&quot;ecto://user:password@endpoint/dbname&quot;</span>,</span><br><span class="line">  <span class="attr">&quot;secret_key_base&quot;</span>:<span class="string">&quot;....&quot;</span>,</span><br><span class="line">  ....</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Once I have all the secrets listed in this file. I pass it through the followingcommand:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jq -c . &lt; &quot;secrets.json&quot; | base64 --wrap 0</span><br></pre></td></tr></table></figure><p>This strips the spaces in the json and base64 encodes it. I plug this value intoa single parameter called <code>money_config</code> and then use the same strategy asbefore to pass it as an env var:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;secrets&quot;</span>: [</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">&quot;name&quot;</span>: <span class="string">&quot;APP_CONFIG&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;valueFrom&quot;</span>: <span class="string">&quot;money_config&quot;</span></span><br><span class="line">  &#125;,</span><br></pre></td></tr></table></figure><p>Now, in the app, I just decode base64 and then decode the json to get all thevalues. Here is how I do it in my Elixir apps:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"># config/releases.exs</span><br><span class="line">import Config</span><br><span class="line"></span><br><span class="line">app_config = System.fetch_env!(&quot;APP_CONFIG&quot;) |&gt; Base.decode64!() |&gt; Jason.decode!()</span><br><span class="line"></span><br><span class="line">config :money, Money.Repo,</span><br><span class="line">  ssl: true,</span><br><span class="line">  url: Map.fetch!(app_config, &quot;db&quot;),</span><br><span class="line">  pool_size: String.to_integer(System.get_env(&quot;POOL_SIZE&quot;, &quot;10&quot;))</span><br><span class="line"></span><br><span class="line">config :money, MoneyWeb.Endpoint,</span><br><span class="line">  http: [</span><br><span class="line">    port: 8000,</span><br><span class="line">    transport_options: [socket_opts: [:inet6]]</span><br><span class="line">  ],</span><br><span class="line">  server: true,</span><br><span class="line">  secret_key_base: Map.fetch!(app_config, &quot;secret_key_base&quot;)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>This approach allows you to use around 70 secrets in one parameter because paramater values are limited to a size of 4K characters.</p><h2 id="Making-space-for-more-environment-variables"><a href="#Making-space-for-more-environment-variables" class="headerlink" title="Making space for more environment variables"></a>Making space for more environment variables</h2><p>If you have more than 70 environment variables you can add <code>gzip</code> to the pipe to get in more environment variables in a single parameter.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jq -c . &lt; &quot;secrets.json&quot; | gzip | base64 --wrap 0</span><br></pre></td></tr></table></figure><p>You’ll have to do things in the opposite order on your app to read this data. With gzip, You can get almost 140 env variables.</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;I have an ECS cluster for my side projects and need to pass secrets to the app.
There are a few ways of doing it, and I think I found a n
      
    
    </summary>
    
    
      <category term="ECS" scheme="http://minhajuddin.com/tags/ecs/"/>
    
      <category term="Secrets" scheme="http://minhajuddin.com/tags/secrets/"/>
    
      <category term="Environment Variables" scheme="http://minhajuddin.com/tags/environment-variables/"/>
    
      <category term="Parameter Store" scheme="http://minhajuddin.com/tags/parameter-store/"/>
    
      <category term="AWS" scheme="http://minhajuddin.com/tags/aws/"/>
    
      <category term="Docker" scheme="http://minhajuddin.com/tags/docker/"/>
    
      <category term="Terraform" scheme="http://minhajuddin.com/tags/terraform/"/>
    
  </entry>
  
  <entry>
    <title>How to do batch updates in postgresql for really big updates</title>
    <link href="http://minhajuddin.com/2020/10/17/how-to-do-batch-updates-in-postgresql/"/>
    <id>http://minhajuddin.com/2020/10/17/how-to-do-batch-updates-in-postgresql/</id>
    <published>2020-10-17T08:30:53.000Z</published>
    <updated>2025-02-18T03:25:20.569Z</updated>
    
    <content type="html"><![CDATA[<p>So, you have a ton of records to update in a really large table. Say, you needto update 3 million records in a table with 100 million rows. And, let’s alsoassume that you have a way of finding these records. Even, with all of thisinfo, updating 3M records in a single transaction is troublesome if your tableis being used moderately during this data fix. You have a high probability ofrunning into a deadlock or your query timing out.</p><p>There is a way you can do this by updating your data in small batches. The ideais to first find the ids of the records you want to update and then updating asmall batch of them in each transaction.</p><p>For our example, let us say we have a <code>users</code> table which has 3M records createdin the year 2019 whose authentication token needs to be reset. Simple enough!</p><h2 id="1-Doing-this-in-a-single-update"><a href="#1-Doing-this-in-a-single-update" class="headerlink" title="1. Doing this in a single update"></a>1. Doing this in a single update</h2><p>Doing this in a single update is the easiest and is possible if you don’t usethis table a lot. However, as I said, it is prone to deadlocks and statementtimeouts.</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">UPDATE users</span><br><span class="line"><span class="keyword">SET</span> authentication_token <span class="operator">=</span> encode(gen_random_bytes(<span class="number">32</span>), <span class="string">&#x27;base64&#x27;</span>)</span><br><span class="line"><span class="keyword">WHERE</span> created_at <span class="keyword">BETWEEN</span> <span class="string">&#x27;2019-01-01&#x27;</span> <span class="keyword">AND</span> <span class="string">&#x27;2019-12-31&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="2-Doing-this-in-multiple-batches-through-a-CTE"><a href="#2-Doing-this-in-multiple-batches-through-a-CTE" class="headerlink" title="2. Doing this in multiple batches through a CTE"></a>2. Doing this in multiple batches through a CTE</h3><p>Doing this through a CTE in multiple batches works, but is not the mostefficient.</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- first get all the records that you want to update by using rolling OFFSETs</span></span><br><span class="line"><span class="comment">-- and limiting to a nice batch size using LIMIT</span></span><br><span class="line"><span class="keyword">WITH</span> users_to_be_updated (</span><br><span class="line">  <span class="keyword">SELECT</span> id</span><br><span class="line">  <span class="keyword">FROM</span> users</span><br><span class="line">  <span class="keyword">WHERE</span> created_at <span class="keyword">BETWEEN</span> <span class="string">&#x27;2019-01-01&#x27;</span> <span class="keyword">AND</span> <span class="string">&#x27;2019-12-31&#x27;</span></span><br><span class="line">  LIMIT <span class="number">1000</span></span><br><span class="line">  <span class="keyword">OFFSET</span> <span class="number">0</span></span><br><span class="line">  <span class="keyword">ORDER</span> <span class="keyword">BY</span> id</span><br><span class="line">)</span><br><span class="line">UPDATE users u</span><br><span class="line"><span class="keyword">SET</span> authentication_token <span class="operator">=</span> encode(gen_random_bytes(<span class="number">32</span>), <span class="string">&#x27;base64&#x27;</span>)</span><br><span class="line"><span class="keyword">FROM</span> users_to_be_updated utbu</span><br><span class="line"><span class="keyword">WHERE</span> utbu.id <span class="operator">=</span> u.id</span><br></pre></td></tr></table></figure><p>That works. However, it is not the most efficient update. Because, for everybatch, (in this example a batch of 1000) we perform the filtering and orderingof all the data. So, we end up making the same query 3M/1K or 3000 times. Notthe most efficient use of our database resources!</p><h3 id="3-1-Doing-this-in-multiple-batches-using-a-temporary-table"><a href="#3-1-Doing-this-in-multiple-batches-using-a-temporary-table" class="headerlink" title="3.1. Doing this in multiple batches using a temporary table"></a>3.1. Doing this in multiple batches using a temporary table</h3><p>So, to remove the inefficiency from the previous step, we can create a temporary table tostore the filtered user ids while we update the records. Also, since this is atemp table, it is discarded automatically once the session finishes.</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> TEMP <span class="keyword">TABLE</span> users_to_be_updated <span class="keyword">AS</span></span><br><span class="line">  <span class="keyword">SELECT</span> <span class="built_in">ROW_NUMBER</span>() <span class="keyword">OVER</span>(<span class="keyword">ORDER</span> <span class="keyword">BY</span> id) row_id, id</span><br><span class="line">  <span class="keyword">FROM</span> users</span><br><span class="line">  <span class="keyword">WHERE</span> created_at <span class="keyword">BETWEEN</span> <span class="string">&#x27;2019-01-01&#x27;</span> <span class="keyword">AND</span> <span class="string">&#x27;2019-12-31&#x27;</span>;</span><br><span class="line">  </span><br><span class="line"><span class="keyword">CREATE</span> INDEX <span class="keyword">ON</span> users_to_be_updated(row_id);</span><br><span class="line"></span><br><span class="line">UPDATE users u</span><br><span class="line"><span class="keyword">SET</span> authentication_token <span class="operator">=</span> encode(gen_random_bytes(<span class="number">32</span>), <span class="string">&#x27;base64&#x27;</span>)</span><br><span class="line"><span class="keyword">FROM</span> users_to_be_updated utbu</span><br><span class="line"><span class="keyword">WHERE</span> utbu.id <span class="operator">=</span> u.id</span><br><span class="line"><span class="keyword">AND</span> utbu.row_id <span class="operator">&gt;</span> <span class="number">0</span> <span class="keyword">AND</span> utbu.row_id  <span class="operator">&lt;=</span> <span class="number">1000</span></span><br></pre></td></tr></table></figure><p>So, in the above SQL we are creating a temporary table containing a row_id whichis a serial number going from 1 to the total number of rows and also adding anindex on this because we’ll be using it in our batch update WHERE clause. And,finally doing our batch update by selecting the rows from 0..1000 in the firstiteration, 1000..2000 in the second iteration, and so on.</p><h3 id="3-2-Tying-this-up-via-a-ruby-script-to-do-the-full-update"><a href="#3-2-Tying-this-up-via-a-ruby-script-to-do-the-full-update" class="headerlink" title="3.2. Tying this up via a ruby script to do the full update."></a>3.2. Tying this up via a ruby script to do the full update.</h3><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># sql_generator.rb</span></span><br><span class="line">total_count = <span class="number">3_000_000</span></span><br><span class="line">batch_size = <span class="number">10_000</span></span><br><span class="line"></span><br><span class="line">iterations = <span class="number">1</span> + total_count / batch_size</span><br><span class="line"></span><br><span class="line">puts <span class="string">&lt;&lt;~SQL</span></span><br><span class="line"><span class="string">-- create our temporary table to avoid running this query for every batch update</span></span><br><span class="line"><span class="string">CREATE TEMP TABLE users_to_be_updated AS</span></span><br><span class="line"><span class="string">  SELECT ROW_NUMBER() OVER(ORDER BY id) row_id, id</span></span><br><span class="line"><span class="string">  FROM users</span></span><br><span class="line"><span class="string">  WHERE created_at BETWEEN &#x27;2019-01-01&#x27; AND &#x27;2019-12-31&#x27;;</span></span><br><span class="line"><span class="string">  </span></span><br><span class="line"><span class="string">-- create an index on row_id because we&#x27;ll filter rows by this</span></span><br><span class="line"><span class="string">CREATE INDEX ON users_to_be_updated(row_id);</span></span><br><span class="line"><span class="string">SQL</span></span><br><span class="line"></span><br><span class="line">(<span class="number">0</span>..iterations).each <span class="keyword">do</span> <span class="params">|i|</span></span><br><span class="line">  start_id = i * batch_size</span><br><span class="line">  end_id = (i + <span class="number">1</span>) * batch_size</span><br><span class="line">  puts <span class="string">&lt;&lt;~SQL</span></span><br><span class="line"><span class="string">-- the row below prints out the current iteration which shows us the progress</span></span><br><span class="line"><span class="string">-- batch: <span class="subst">#&#123;i&#125;</span>/<span class="subst">#&#123;iterations&#125;</span></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">-- start a transaction for this batch update</span></span><br><span class="line"><span class="string">BEGIN;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">-- perform the actual batch update</span></span><br><span class="line"><span class="string">UPDATE users u</span></span><br><span class="line"><span class="string">SET authentication_token = encode(gen_random_bytes(32), &#x27;base64&#x27;)</span></span><br><span class="line"><span class="string">FROM users_to_be_updated utbu</span></span><br><span class="line"><span class="string">WHERE utbu.id = u.id</span></span><br><span class="line"><span class="string">AND utbu.row_id &gt; <span class="subst">#&#123;start_id&#125;</span> AND utbu.row_id  &lt;= <span class="subst">#&#123;end_id&#125;</span>;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">-- commit this transaction so that we don&#x27;t have a single long running transaction</span></span><br><span class="line"><span class="string">COMMIT;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">-- This is optional, sleep for 1 second to stop the database from being overwhelmed.</span></span><br><span class="line"><span class="string">-- You can tweak this to your desire time based on the resources you have or</span></span><br><span class="line"><span class="string">-- remove it.</span></span><br><span class="line"><span class="string">SELECT pg_sleep(1);</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">SQL</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>This tiny script generates a sql file which can then be executed via psql to dothe whole process in one fell swoop.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># generate the sql file</span></span><br><span class="line">ruby sql_generator.rb &gt; user_batch_update.sql</span><br></pre></td></tr></table></figure><p>Once we have the sql file we run it through psql like so</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">psql --echo-all --file=user_batch_update.psql <span class="string">&quot;DATABASE_URL&quot;</span></span><br></pre></td></tr></table></figure><p>That’s all folks, now your updates should be done in batches and shouldn’t causeany deadlocks or statement timeouts.</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;So, you have a ton of records to update in a really large table. Say, you need
to update 3 million records in a table with 100 million ro
      
    
    </summary>
    
    
      <category term="psql" scheme="http://minhajuddin.com/tags/psql/"/>
    
      <category term="postgresql" scheme="http://minhajuddin.com/tags/postgresql/"/>
    
      <category term="batch" scheme="http://minhajuddin.com/tags/batch/"/>
    
      <category term="update" scheme="http://minhajuddin.com/tags/update/"/>
    
      <category term="row_number" scheme="http://minhajuddin.com/tags/row-number/"/>
    
      <category term="temp table" scheme="http://minhajuddin.com/tags/temp-table/"/>
    
  </entry>
  
  <entry>
    <title>Lazy functional ruby</title>
    <link href="http://minhajuddin.com/2020/07/29/lazy-functional-ruby/"/>
    <id>http://minhajuddin.com/2020/07/29/lazy-functional-ruby/</id>
    <published>2020-07-29T07:55:10.000Z</published>
    <updated>2025-02-18T03:25:20.577Z</updated>
    
    <content type="html"><![CDATA[<p>Today, I was working with some ruby code that had to find the first product inone of the current contexts. Here is the code:</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">find_product_in_current_contexts</span></span></span><br><span class="line">  context_ids = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"></span><br><span class="line">  context_ids.each <span class="keyword">do</span> <span class="params">|context_id|</span></span><br><span class="line">    product = Product.find_by(<span class="symbol">context_id:</span> context_id)</span><br><span class="line">    <span class="keyword">return</span> product <span class="keyword">if</span> product</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>This code tries to find the first product in the current contexts in the orderthey are defined. However, the above code has a tiny bug. Can you figure outwhat it is?</p><p>In cases where there are no products in any of the contexts this functionreturns the array <code>[1, 2, 3]</code> instead of returning <code>nil</code> because <code>Array.each</code>returns the array and in the case where we don’t find the product we don’treturn early.</p><p>We can easily fix this by adding an extra return at the end of the function.</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">find_product_in_current_contexts</span></span></span><br><span class="line">  context_ids = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"></span><br><span class="line">  context_ids.each <span class="keyword">do</span> <span class="params">|context_id|</span></span><br><span class="line">    product = Product.find_by(<span class="symbol">context_id:</span> context_id)</span><br><span class="line">    <span class="keyword">return</span> product <span class="keyword">if</span> product</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># if it reaches this point we haven&#x27;t found a product</span></span><br><span class="line">  <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>The fix is awkward, let us see if we can improve this.</p><p>We could use <code>.map</code> to find a product for every context and return the first not<code>nil</code> record like so:</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">find_product_in_current_contexts</span></span></span><br><span class="line">  context_ids = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"></span><br><span class="line">  context_ids</span><br><span class="line">    .map &#123; <span class="params">|context_id|</span> Product.find_by(<span class="symbol">context_id:</span> context_id)&#125;</span><br><span class="line">    .find&#123;<span class="params">|x|</span> x &#125;</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>This looks much cleaner! And it doesn’t have the previous bug either. However,this code is not efficient, we want to return the first product we find for allthe contexts, and the above code always looks in all contexts even if it finds aproduct for the first context. We need to be lazy!</p><h2 id="Lazy-enumerator-for-the-win"><a href="#Lazy-enumerator-for-the-win" class="headerlink" title="Lazy enumerator for the win!"></a>Lazy enumerator for the win!</h2><p>Calling <code>.lazy</code> on an enumerable gives you a lazy enumerator and the neat thingabout that is it only executes the chain of functions as many times as needed.</p><p>Here is a short example which demonstrates its use:</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">find</span><span class="params">(id)</span></span></span><br><span class="line">  puts <span class="string">&quot;&gt; finding <span class="subst">#&#123;id&#125;</span>&quot;</span></span><br><span class="line">  <span class="keyword">return</span> <span class="symbol">:product</span> <span class="keyword">if</span> id == <span class="number">2</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># without lazy</span></span><br><span class="line">(<span class="number">1</span>..<span class="number">3</span>).map&#123;<span class="params">|id|</span> find(id)&#125;.find&#123;<span class="params">|x|</span> x&#125;</span><br><span class="line"><span class="comment"># &gt; finding 1</span></span><br><span class="line"><span class="comment"># &gt; finding 2</span></span><br><span class="line"><span class="comment"># &gt; finding 3</span></span><br><span class="line"><span class="comment"># =&gt; :product</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># The above `.map` gets executed for every element in the range every time!</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># using the lazy enumerator</span></span><br><span class="line">(<span class="number">1</span>..<span class="number">3</span>).lazy.map&#123;<span class="params">|id|</span> find(id)&#125;.find&#123;<span class="params">|x|</span> x&#125;</span><br><span class="line"><span class="comment"># &gt; finding 1</span></span><br><span class="line"><span class="comment"># &gt; finding 2</span></span><br><span class="line"><span class="comment"># =&gt; :product</span></span><br></pre></td></tr></table></figure><p>As you can see from the above example, the lazy enumerator executes only as manytimes as necessary. Here is another example from the ruby docs, to drive thepoint home:</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">irb&gt; (<span class="number">1</span>..Float::INFINITY).lazy.select(&amp;<span class="symbol">:odd?</span>).drop(<span class="number">10</span>).take(<span class="number">2</span>).to_a</span><br><span class="line"><span class="comment"># =&gt; [21, 23]</span></span><br><span class="line"><span class="comment"># Without the lazy enumerator, this would crash your console!</span></span><br></pre></td></tr></table></figure><p>Now applying this to our code is pretty straightforward, we just need to add acall to <code>#.lazy</code> before we map and we are all set!</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">find_product_in_current_contexts</span></span></span><br><span class="line">  context_ids = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"></span><br><span class="line">  context_ids</span><br><span class="line">    .lazy <span class="comment"># this gives us the lazy enumerator</span></span><br><span class="line">    .map &#123; <span class="params">|context_id|</span> Product.find_by(<span class="symbol">context_id:</span> context_id)&#125;</span><br><span class="line">    .find&#123;<span class="params">|x|</span> x &#125;</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>Ah, nice functional ruby!</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Today, I was working with some ruby code that had to find the first product in
one of the current contexts. Here is the code:&lt;/p&gt;
&lt;figure
      
    
    </summary>
    
    
      <category term="ruby" scheme="http://minhajuddin.com/tags/ruby/"/>
    
      <category term="lazy" scheme="http://minhajuddin.com/tags/lazy/"/>
    
      <category term="functional" scheme="http://minhajuddin.com/tags/functional/"/>
    
  </entry>
  
  <entry>
    <title>How to know which of the Enum functions to use in Elixir</title>
    <link href="http://minhajuddin.com/2020/07/18/how-to-know-which-of-the-enum-functions-to-use-in-elixir/"/>
    <id>http://minhajuddin.com/2020/07/18/how-to-know-which-of-the-enum-functions-to-use-in-elixir/</id>
    <published>2020-07-18T09:36:39.000Z</published>
    <updated>2025-02-18T03:25:20.572Z</updated>
    
    <content type="html"><![CDATA[<p>When you are writing functional code, it is sometimes difficult to figure outwhich of the <code>Enum</code> functions you want to use. Here are a few common use cases.</p><h2 id="Use-Enum-map"><a href="#Use-Enum-map" class="headerlink" title="Use Enum.map"></a>Use <code>Enum.map</code></h2><p>You can use <code>Enum.map</code> when you want to <em>transform</em> a set of elements intoanother set of elements. Also, note that the count of elements remainsunchanged. So, if you transform a list of 5 elements using <code>Enum.map</code>, you getan output list containing exactly 5 elements, However, the shape of the elementsmight be different.</p><h3 id="Examples"><a href="#Examples" class="headerlink" title="Examples"></a>Examples</h3><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># transform names into their lengths</span></span><br><span class="line">iex&gt; Enum.map([<span class="string">&quot;jack&quot;</span>, <span class="string">&quot;mujju&quot;</span>, <span class="string">&quot;danny boy&quot;</span>], <span class="keyword">fn</span> x -&gt; String.length(x) <span class="keyword">end</span>)</span><br><span class="line">[<span class="number">4</span>, <span class="number">5</span>, <span class="number">9</span>]</span><br></pre></td></tr></table></figure><p>If you look at the count of input and output elements it remains the same,However, the shape is different, the input elements are all strings whereas theoutput elements are all numbers.</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># get ids of all users from a list of structs</span></span><br><span class="line">iex&gt; Enum.map([%&#123;<span class="symbol">id:</span> <span class="number">1</span>, <span class="symbol">name:</span> <span class="string">&quot;Danny&quot;</span>&#125;, %&#123;<span class="symbol">id:</span> <span class="number">2</span>, <span class="symbol">name:</span> <span class="string">&quot;Mujju&quot;</span>&#125;], <span class="keyword">fn</span> x -&gt; x.id <span class="keyword">end</span>)</span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>]</span><br></pre></td></tr></table></figure><p>In this example we transform a list of maps to a list of numbers.</p><h2 id="Use-Enum-filter"><a href="#Use-Enum-filter" class="headerlink" title="Use Enum.filter"></a>Use <code>Enum.filter</code></h2><p>When you want to whittle down your input list, use <code>Enum.filter</code>, Filteringdoesn’t change the shape of the data, i.e. you are not transforming elements,and the shape of the input data will be the same as the shape of the outputdata. However, the count of elements will be different, to be more precise itwill be lesser or the same as the input list count.</p><h3 id="Examples-1"><a href="#Examples-1" class="headerlink" title="Examples"></a>Examples</h3><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># filter a list to only get names which start with `m`</span></span><br><span class="line">iex&gt; Enum.filter([<span class="string">&quot;mujju&quot;</span>, <span class="string">&quot;danny&quot;</span>, <span class="string">&quot;min&quot;</span>, <span class="string">&quot;moe&quot;</span>, <span class="string">&quot;boe&quot;</span>, <span class="string">&quot;joe&quot;</span>], <span class="keyword">fn</span> x -&gt; String.starts_with?(x, <span class="string">&quot;m&quot;</span>) <span class="keyword">end</span>)</span><br><span class="line">[<span class="string">&quot;mujju&quot;</span>, <span class="string">&quot;min&quot;</span>, <span class="string">&quot;moe&quot;</span>]</span><br></pre></td></tr></table></figure><p>The shape of data here is the same, we use a list of strings as the input andget a list of strings as an output, only the count has changed, in this case, wehave fewer elements.</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># filter a list of users to only get active users</span></span><br><span class="line">iex&gt; Enum.filter([%&#123;<span class="symbol">id:</span> <span class="number">1</span>, <span class="symbol">name:</span> <span class="string">&quot;Danny&quot;</span>, <span class="symbol">active:</span> <span class="keyword">true</span>&#125;, %&#123;<span class="symbol">id:</span> <span class="number">2</span>, <span class="symbol">name:</span> <span class="string">&quot;Mujju&quot;</span>, <span class="symbol">active:</span> <span class="keyword">false</span>&#125;], <span class="keyword">fn</span> x -&gt; x.active <span class="keyword">end</span>)</span><br><span class="line">[%&#123;<span class="symbol">active:</span> <span class="keyword">true</span>, <span class="symbol">id:</span> <span class="number">1</span>, <span class="symbol">name:</span> <span class="string">&quot;Danny&quot;</span>&#125;]</span><br></pre></td></tr></table></figure><p>In this example too, the shape of the input elements is a map (user) and theshape of output elements is still a map.</p><h2 id="Use-Enum-reduce"><a href="#Use-Enum-reduce" class="headerlink" title="Use Enum.reduce"></a>Use <code>Enum.reduce</code></h2><p>The last of the commonly used <code>Enum</code> functions is <code>Enum.reduce</code> and it is alsoone of the most powerful functions. You can use <code>Enum.reduce</code> when you need tochange the shape of the input list into something else, for instance a <code>map</code> or a<code>number</code>.</p><h3 id="Examples-2"><a href="#Examples-2" class="headerlink" title="Examples"></a>Examples</h3><p>Change a list of elements into a number by computing its product or sum</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">iex&gt; Enum.reduce(</span><br><span class="line">  _input_enumberable = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>],</span><br><span class="line">  _start_value_of_acc = <span class="number">1</span>,</span><br><span class="line">  <span class="keyword">fn</span> x, acc -&gt; x * acc <span class="keyword">end</span>)</span><br><span class="line"><span class="number">24</span></span><br><span class="line"></span><br><span class="line">iex&gt; Enum.reduce(</span><br><span class="line">  _input_list = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>],</span><br><span class="line">  _start_value_of_acc = 0,</span><br><span class="line">  <span class="keyword">fn</span> x, acc -&gt; x + acc <span class="keyword">end</span>)</span><br><span class="line"><span class="number">10</span></span><br></pre></td></tr></table></figure><p><code>Enum.reduce</code> takes three arguments, the first is the input enumerable, which isusually a list or map, the second is the starting value of the accumulator andthe third is a function which is applied for each element<strong>whose result is then sent to the next function application as the accumulator</strong>.</p><p>Let’s try and understand this using an equivalent javascript example.</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// input list</span></span><br><span class="line"><span class="keyword">const</span> inputList = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">// starting value of accumulator, we want to chose this wisely, for instance</span></span><br><span class="line"><span class="comment">// when we want addition, we should use a `0` as the start value to avoid</span></span><br><span class="line"><span class="comment">// impacting the output and if you want to compute a product we use a `1`, this</span></span><br><span class="line"><span class="comment">// is usually called the identity element for the function: https://en.wikipedia.org/wiki/Identity_element</span></span><br><span class="line"><span class="comment">// It is also the value that is returned when the input list is empty</span></span><br><span class="line"><span class="keyword">let</span> acc = <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// loop over all the input elements and for each element compute the new</span></span><br><span class="line"><span class="comment">// accumulator as the sum of the current accumulator and the current element</span></span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">const</span> x <span class="keyword">of</span> inputList) &#123;</span><br><span class="line">  <span class="comment">// compute the next value of our accumulator, in our Elixir code this is</span></span><br><span class="line">  <span class="comment">// done by the third argument which is a function which gets `x` and `acc`</span></span><br><span class="line">  acc = acc + x</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// in Elixir, the final value of the accumulator is returned</span></span><br></pre></td></tr></table></figure><p>Let’s look at another example of converting an employee list into a mapcontaining an employee id and their name.</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">iex&gt; Enum.reduce(</span><br><span class="line">  _input_list = [%&#123;<span class="symbol">id:</span> <span class="number">1</span>, <span class="symbol">name:</span> <span class="string">&quot;Danny&quot;</span>&#125;, %&#123;<span class="symbol">id:</span> <span class="number">2</span>, <span class="symbol">name:</span> <span class="string">&quot;Mujju&quot;</span>&#125;],</span><br><span class="line">  _start_value_of_acc = %&#123;&#125;,</span><br><span class="line">  <span class="keyword">fn</span> x, acc -&gt; Map.put(acc, x.id, x.name) <span class="keyword">end</span>)</span><br><span class="line"></span><br><span class="line">%&#123;<span class="number">1</span> =&gt; <span class="string">&quot;Danny&quot;</span>, <span class="number">2</span> =&gt; <span class="string">&quot;Mujju&quot;</span>&#125;</span><br></pre></td></tr></table></figure><p>So, in a map you end up reducing an input list into one output value.</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;When you are writing functional code, it is sometimes difficult to figure out
which of the &lt;code&gt;Enum&lt;/code&gt; functions you want to use. H
      
    
    </summary>
    
    
      <category term="Enum" scheme="http://minhajuddin.com/tags/enum/"/>
    
      <category term="Functional Programming" scheme="http://minhajuddin.com/tags/functional-programming/"/>
    
      <category term="map" scheme="http://minhajuddin.com/tags/map/"/>
    
      <category term="reduce" scheme="http://minhajuddin.com/tags/reduce/"/>
    
      <category term="filter" scheme="http://minhajuddin.com/tags/filter/"/>
    
  </entry>
  
  <entry>
    <title>How to control the enqueuing speed of Sidekiq jobs and their concurrency</title>
    <link href="http://minhajuddin.com/2020/07/13/how-to-control-enqueuing-speed-of-sidekiq-jobs-and-their-execution-concurrency/"/>
    <id>http://minhajuddin.com/2020/07/13/how-to-control-enqueuing-speed-of-sidekiq-jobs-and-their-execution-concurrency/</id>
    <published>2020-07-13T18:55:04.000Z</published>
    <updated>2025-02-18T03:25:20.567Z</updated>
    
    <content type="html"><![CDATA[<p>At my work, we use ruby heavily and sidekiq is an essential part of our stack.Sometimes, I long for the concurrency primitives from Elixir, but that’s notwhat today’s post is about.</p><p>A few days ago, I caused a minor incident by overloading our databases. Havingbeen away from ruby for a bit, I had forgotten that sidekiq runs multiplethreads per each worker instance. So, I ended up enqueuing about 10K jobs onSidekiq, and Sidekiq started executing them immediately. We have 50 workerinstances and run Sidekiq with a concurrency of 20. So, essentially we had 400worker threads ready to start crunching these jobs. Coincidentally we have 400database connections available and my batch background job ended up consumingall the connections for 5 minutes during which the other parts ofthe application were connection starved and started throwing errors 😬.</p><p>That was a dumb mistake. Whenever you find yourself making a dumb mistake,make sure that no one else can repeat that mistake. To fix that, we could set upour database with multiple users in such a way that the web app would connectwith a user which could only open a maximum of 100 connections, the backgroundworker with a user with its own limits and, so on. This would stop these kinds ofproblems from happening again. However, we’ll get there when we get there, asthis would require infrastructure changes.</p><p>I had another batch job lined up which had to process millions of rows in asimilar fashion. And, I started looking for solutions. A few solutions that weresuggested were to run these jobs on a single worker or a small set of workers,you can do this by having a custom queue for this job and executing a separatesidekiq instance just for this one queue. However, that would require someinfrastructure work. So, I started looking at other options.</p><p>I thought that redis might have something to help us here, and it did! So, redisallows you to make blocking pops from a list using the <code>BLPOP</code> function. So, ifyou run <code>BLPOP myjob 10</code>, it will pop the first available element in the list,However, if the list is empty, it will block for 10 seconds during which if anelement is inserted, it will pop it and return its value. Using this knowledge,I thought we could control the enqueuing based on the elements in the list. Theidea is simple.</p><ol><li>Before the background job starts, I would seed this list with <code>n</code> elementswhere <code>n</code> is the desired concurrency. So, if I seed this list with <code>2</code>elements, Sidekiq would execute only 2 jobs at any point in time, regardlessof the number of worker instances/concurrency of sidekiq workers.</li><li>The way this is enforced is by the enqueue function using a <code>BLPOP</code> before itenqueues, so, as soon as the enqueuer starts, it pops the first 2 elements fromthe redis list and enqueues 2 jobs. At this point, the enqueuer is stuck till weadd more elements to the list.</li><li>That’s where the background jobs come into play, at the end of eachbackground job, we add one element back to the list using <code>LPUSH</code> and as soonas an element is added the enqueuer which is blocked at <code>BLPOP</code> pops thiselement and enqueues another job. This goes on till all your background jobsare enqueued, all the while making sure that there are never more than 2 jobsat any given time.</li></ol><p>Let’s put this into concrete ruby code.</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">module</span> <span class="title">ControlledConcurrency</span></span></span><br><span class="line">  <span class="comment"># I love module_function</span></span><br><span class="line">  module_function</span><br><span class="line"></span><br><span class="line">  <span class="comment"># The name of our list needs to be constant per worker type, you could</span></span><br><span class="line">  <span class="comment"># probably extract this into a Sidekiq middleware with a little effort</span></span><br><span class="line">  LIST_NAME = <span class="string">&quot;migrate&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">setup</span><span class="params">(<span class="symbol">concurrency:</span>)</span></span></span><br><span class="line">    <span class="comment"># if our list already has elements before we start, our concurrency will be</span></span><br><span class="line">    <span class="comment"># screwed, so, this is a safety check!</span></span><br><span class="line">    slot_count = Redis.current.llen(LIST_NAME)</span><br><span class="line">    raise <span class="string">&quot;Key &#x27;<span class="subst">#&#123;LIST_NAME&#125;</span>&#x27; is being used, it already has <span class="subst">#&#123;slot_count&#125;</span> slots&quot;</span> <span class="keyword">if</span> slot_count &gt; <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># Seed our list with as many items as the concurrency, the contents of this</span></span><br><span class="line">    <span class="comment"># list don&#x27;t matter.</span></span><br><span class="line">    Redis.current.lpush(LIST_NAME, concurrency.times.to_a)</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># A helper function to bump up concurrency if you need to</span></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">increase_concurrency</span><span class="params">(n = <span class="number">1</span>)</span></span></span><br><span class="line">    Redis.current.lpush(LIST_NAME, n.times.to_a)</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># A helper function to bump the concurrency down if you need to</span></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">decrease_concurrency</span><span class="params">(n = <span class="number">1</span>)</span></span></span><br><span class="line">    n.times <span class="keyword">do</span></span><br><span class="line">      puts <span class="string">&quot;&gt; waiting&quot;</span></span><br><span class="line">      Redis.current.blpop(LIST_NAME)</span><br><span class="line">      puts <span class="string">&quot;&gt; decrease by 1&quot;</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># This is our core enqueuer, it runs in a loop because our blpop might get a</span></span><br><span class="line">  <span class="comment"># timeout and return nil, we keep trying till it returns a value</span></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">nq</span><span class="params">(&amp;block)</span></span></span><br><span class="line">    loop <span class="keyword">do</span></span><br><span class="line">      puts <span class="string">&quot;&gt; waiting to enqueue&quot;</span></span><br><span class="line">      slot = Redis.current.blpop(LIST_NAME)</span><br><span class="line">      <span class="keyword">if</span> slot</span><br><span class="line">        puts <span class="string">&quot;&gt; found slot <span class="subst">#&#123;slot&#125;</span>&quot;</span></span><br><span class="line">        <span class="keyword">yield</span></span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">      <span class="keyword">end</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># Function which allow background workers to signal that a job has been</span></span><br><span class="line">  <span class="comment"># completed, so that the enqueuer can nq more jobs.</span></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">return_slot</span></span></span><br><span class="line">    puts <span class="string">&quot;&gt; returning slot&quot;</span></span><br><span class="line">    Redis.current.lpush(LIST_NAME, <span class="number">1</span>)</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># This is our Sidekiq worker</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">HardWorker</span></span></span><br><span class="line">  <span class="keyword">include</span> Sidekiq::Worker</span><br><span class="line"></span><br><span class="line">  <span class="comment"># Our set up doesn&#x27;t enforce concurrency across retries, if you want this,</span></span><br><span class="line">  <span class="comment"># you&#x27;ll probably have to tweak the code a little more :)</span></span><br><span class="line">  sidekiq_options <span class="symbol">retry:</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># the only custom code here is in the ensure block</span></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">perform</span><span class="params">(user_id)</span></span></span><br><span class="line">    puts <span class="string">&quot;&gt; start: <span class="subst">#&#123;user_id&#125;</span>&quot;</span></span><br><span class="line">    <span class="comment"># mock work</span></span><br><span class="line">    sleep <span class="number">1</span></span><br><span class="line">    puts <span class="string">&quot;&gt; finish: <span class="subst">#&#123;user_id&#125;</span>&quot;</span></span><br><span class="line">  <span class="keyword">ensure</span></span><br><span class="line">    <span class="comment"># make sure that we return this slot at the end of the background job, so</span></span><br><span class="line">    <span class="comment"># that the next job can be enqueued. This doesn&#x27;t handle retries because of</span></span><br><span class="line">    <span class="comment"># failures, we disabled retries for our job, but if you have them enabled,</span></span><br><span class="line">    <span class="comment"># you might end up having more jobs than the set concurrency because of</span></span><br><span class="line">    <span class="comment"># retried jobs.</span></span><br><span class="line">    ControlledConcurrency.return_slot</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># ./concurrency_setter.rb</span></span><br><span class="line">ControlledConcurrency.setup(<span class="symbol">concurrency:</span> ARGV.first.to_i)</span><br><span class="line"></span><br><span class="line"><span class="comment"># ./enqueuer.rb</span></span><br><span class="line"><span class="comment"># Before running the enqueuer, we need to set up the concurrency using the above script</span></span><br><span class="line"><span class="comment"># This our enqueuer and it makes sure that the block passed to</span></span><br><span class="line"><span class="comment"># ControlledConcurrency.nq doesn&#x27;t enqueue more jobs that our concurrency</span></span><br><span class="line"><span class="comment"># setting.</span></span><br><span class="line"><span class="number">100</span>.times <span class="keyword">do</span> <span class="params">|i|</span></span><br><span class="line">  ControlledConcurrency.nq <span class="keyword">do</span></span><br><span class="line">    puts <span class="string">&quot;&gt; enqueuing user_id: <span class="subst">#&#123;i&#125;</span>&quot;</span></span><br><span class="line">    HardWorker.perform_async(i)</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>That’s all folks! Hope you find this useful!</p><p>The full code for this can be found at: <a href="https://github.com/minhajuddin/sidekiq-controlled-concurrency">https://github.com/minhajuddin/sidekiq-controlled-concurrency</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;At my work, we use ruby heavily and sidekiq is an essential part of our stack.
Sometimes, I long for the concurrency primitives from Elix
      
    
    </summary>
    
    
      <category term="Redis" scheme="http://minhajuddin.com/tags/redis/"/>
    
      <category term="Sidekiq" scheme="http://minhajuddin.com/tags/sidekiq/"/>
    
      <category term="Concurrency" scheme="http://minhajuddin.com/tags/concurrency/"/>
    
      <category term="Ruby" scheme="http://minhajuddin.com/tags/ruby/"/>
    
      <category term="Enqueue" scheme="http://minhajuddin.com/tags/enqueue/"/>
    
      <category term="Backpressure" scheme="http://minhajuddin.com/tags/backpressure/"/>
    
  </entry>
  
  <entry>
    <title>How to create a web server using Cowboy without Plug or Phoenix - Part 01</title>
    <link href="http://minhajuddin.com/2020/06/05/how-to-create-a-web-server-using-cowboy-without-plug/"/>
    <id>http://minhajuddin.com/2020/06/05/how-to-create-a-web-server-using-cowboy-without-plug/</id>
    <published>2020-06-05T17:33:48.000Z</published>
    <updated>2025-02-18T03:25:20.568Z</updated>
    
    <content type="html"><![CDATA[<p>Cowboy is an amazing web server that is used by Plug/Phoenix out of the box, Idon’t think Phoenix supports any other web servers at the moment. However, the<a href="https://github.com/elixir-plug/plug/blob/master/lib/plug/conn/adapter.ex">plugadapter</a>is fairly abstracted, and plug implements this adapter for cowboy through the<a href="https://github.com/elixir-plug/plug_cowboy/">plug_cowboy</a> hex package. Intheory, you should be able to write a new adapter if you just implement the <a href="https://github.com/elixir-plug/plug/blob/master/lib/plug/conn/adapter.ex">Plugadapter <abbr title="That's not a typo :) it comes from the british heritage ofErlang">behaviour</abbr></a>.The plug cowboy adapter has a lot of interesting code and you’ll learn a lotfrom reading it. Anyway, this blog post isn’t about Plug or Phoenix. I wanted toshow off how you can create a simple Cowboy server without using Plug or Phoenix(I had to learn how to do this while creating my side project<a href="https://webpipe.hyperngn.com/">webpipe</a>)</p><p>We want an application which spins up a cowboy server and renders a hello worldmessage. Here is the required code for that:</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">defmodule</span> <span class="title">Hello</span></span> <span class="keyword">do</span></span><br><span class="line">  <span class="comment"># The handler module which handles all requests, its `init` function is called</span></span><br><span class="line">  <span class="comment"># by Cowboy for all matching requests.</span></span><br><span class="line">  <span class="class"><span class="keyword">defmodule</span> <span class="title">Handler</span></span> <span class="keyword">do</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">init</span></span>(req, _opts) <span class="keyword">do</span></span><br><span class="line">      resp =</span><br><span class="line">        <span class="symbol">:cowboy_req</span>.reply(</span><br><span class="line">          _status = <span class="number">200</span>,</span><br><span class="line">          _headers = %&#123;<span class="string">&quot;content-type&quot;</span> =&gt; <span class="string">&quot;text/html; charset=utf-8&quot;</span>&#125;,</span><br><span class="line">          _body = <span class="string">&quot;&lt;!doctype html&gt;&lt;h1&gt;Hello, Cowboy!&lt;/h1&gt;&quot;</span>,</span><br><span class="line">          _request = req</span><br><span class="line">        )</span><br><span class="line"></span><br><span class="line">      &#123;<span class="symbol">:ok</span>, resp, []&#125;</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">start</span></span> <span class="keyword">do</span></span><br><span class="line">    <span class="comment"># compile the routes</span></span><br><span class="line">    routes =</span><br><span class="line">      <span class="symbol">:cowboy_router</span>.compile([</span><br><span class="line">        &#123;<span class="symbol">:_</span>,</span><br><span class="line">         [</span><br><span class="line">           <span class="comment"># &#123; wildcard, handler module (needs to have an init function), options &#125;</span></span><br><span class="line">           &#123;<span class="symbol">:_</span>, Handler, []&#125;</span><br><span class="line">         ]&#125;</span><br><span class="line">      ])</span><br><span class="line"></span><br><span class="line">    <span class="keyword">require</span> Logger</span><br><span class="line">    Logger.info(<span class="string">&quot;Staring server at http://localhost:4001/&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># start an http server</span></span><br><span class="line">    <span class="symbol">:cowboy</span>.start_clear(</span><br><span class="line">      <span class="symbol">:hello_http</span>,</span><br><span class="line">      [<span class="symbol">port:</span> <span class="number">4001</span>],</span><br><span class="line">      %&#123;<span class="symbol">env:</span> %&#123;<span class="symbol">dispatch:</span> routes&#125;&#125;</span><br><span class="line">    )</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>And, here is a quick test to assert that it works!</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">defmodule</span> <span class="title">HelloTest</span></span> <span class="keyword">do</span></span><br><span class="line">  <span class="keyword">use</span> ExUnit.Case</span><br><span class="line"></span><br><span class="line">  test <span class="string">&quot;returns hello world&quot;</span> <span class="keyword">do</span></span><br><span class="line">    assert &#123;<span class="symbol">:ok</span>, &#123;&#123;<span class="string">&#x27;HTTP/1.1&#x27;</span>, <span class="number">200</span>, <span class="string">&#x27;OK&#x27;</span>&#125;, _headers, <span class="string">&#x27;&lt;!doctype html&gt;&lt;h1&gt;Hello, Cowboy!&lt;/h1&gt;&#x27;</span>&#125;&#125; =</span><br><span class="line">             <span class="symbol">:httpc</span>.request(<span class="string">&#x27;http://localhost:4001/&#x27;</span>)</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p><a href="https://github.com/hyperngn/cowboy-examples/tree/master/hello">Full code on GitHub</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Cowboy is an amazing web server that is used by Plug/Phoenix out of the box, I
don’t think Phoenix supports any other web servers at the 
      
    
    </summary>
    
    
      <category term="Elixir" scheme="http://minhajuddin.com/tags/elixir/"/>
    
      <category term="Cowboy" scheme="http://minhajuddin.com/tags/cowboy/"/>
    
      <category term="Webpipe" scheme="http://minhajuddin.com/tags/webpipe/"/>
    
      <category term="Phoenix" scheme="http://minhajuddin.com/tags/phoenix/"/>
    
      <category term="Plug" scheme="http://minhajuddin.com/tags/plug/"/>
    
  </entry>
  
  <entry>
    <title>My first SVG creation</title>
    <link href="http://minhajuddin.com/2020/05/17/my-first-svg-creation/"/>
    <id>http://minhajuddin.com/2020/05/17/my-first-svg-creation/</id>
    <published>2020-05-17T17:51:15.000Z</published>
    <updated>2025-02-18T03:25:20.579Z</updated>
    
    <content type="html"><![CDATA[<p>SVG is amazing, I want to design the logo of my next company using it!</p><svg style='border: solid 1px #0f0' viewbox='0 0 200 200' stroke="#44337a" fill='#6b46c1'>      <circle cx=100 cy=100 r=80 fill=none />      <circle cx=60 cy=60 r=10 fill=none stroke=black />      <circle cx=60 cy=60 r=6 fill=#0074D9 stroke=none />      <circle cx=140 cy=60 r=10 fill=none stroke=black />      <circle cx=140 cy=60 r=6 fill=#0074D9 stroke=none />      <path d="               M 90,140               A 8 5 0 1 0 110,140               z               " fill=none />      <circle cx=100 cy=100 r=20 fill=#FF4136 stroke=none />    </svg><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">&lt;svg style=&#x27;border: solid 1px #0f0&#x27; viewbox=&#x27;0 0 200 200&#x27; stroke=&quot;#44337a&quot; fill=&#x27;#6b46c1&#x27;&gt;</span><br><span class="line">      &lt;circle cx=100 cy=100 r=80 fill=none /&gt;</span><br><span class="line"></span><br><span class="line">      &lt;circle cx=60 cy=60 r=10 fill=none stroke=black /&gt;</span><br><span class="line">      &lt;circle cx=60 cy=60 r=6 fill=#0074D9 stroke=none /&gt;</span><br><span class="line"></span><br><span class="line">      &lt;circle cx=140 cy=60 r=10 fill=none stroke=black /&gt;</span><br><span class="line">      &lt;circle cx=140 cy=60 r=6 fill=#0074D9 stroke=none /&gt;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">      &lt;path d=&quot;</span><br><span class="line">               M 90,140</span><br><span class="line">               A 8 5 0 1 0 110,140</span><br><span class="line">               z</span><br><span class="line">               &quot; fill=none /&gt;</span><br><span class="line"></span><br><span class="line">      &lt;circle cx=100 cy=100 r=20 fill=#FF4136 stroke=none /&gt;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    &lt;/svg&gt;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;SVG is amazing, I want to design the logo of my next company using it!&lt;/p&gt;
&lt;svg style=&#39;border: solid 1px #0f0&#39; viewbox=&#39;0 0 200 200&#39; stro
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>many_to_many relationships in Ecto and Phoenix - Screencast - Part 2</title>
    <link href="http://minhajuddin.com/2020/05/17/many-to-many-relationships-in-ecto-and-phoenix-screencast-part-2/"/>
    <id>http://minhajuddin.com/2020/05/17/many-to-many-relationships-in-ecto-and-phoenix-screencast-part-2/</id>
    <published>2020-05-17T00:00:00.000Z</published>
    <updated>2025-02-18T03:25:20.579Z</updated>
    
    <content type="html"><![CDATA[<div class="video-container"><iframe src="https://www.youtube.com/embed/_ut8o0fdXRo" frameborder="0" loading="lazy" allowfullscreen></iframe></div>]]></content>
    
    <summary type="html">
    
      
      
        &lt;div class=&quot;video-container&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/_ut8o0fdXRo&quot; frameborder=&quot;0&quot; loading=&quot;lazy&quot; allowfullscreen&gt;&lt;/iframe
      
    
    </summary>
    
    
      <category term="Elixir" scheme="http://minhajuddin.com/tags/elixir/"/>
    
      <category term="Phoenix" scheme="http://minhajuddin.com/tags/phoenix/"/>
    
      <category term="Ecto" scheme="http://minhajuddin.com/tags/ecto/"/>
    
      <category term="many_to_many" scheme="http://minhajuddin.com/tags/many-to-many/"/>
    
      <category term="Tags" scheme="http://minhajuddin.com/tags/tags/"/>
    
      <category term="relationships" scheme="http://minhajuddin.com/tags/relationships/"/>
    
  </entry>
  
  <entry>
    <title>many_to_many relationships in Ecto and Phoenix - Screencast - Part 1</title>
    <link href="http://minhajuddin.com/2020/05/11/many-to-many-relationships-in-ecto-and-phoenix-screencast-part-1/"/>
    <id>http://minhajuddin.com/2020/05/11/many-to-many-relationships-in-ecto-and-phoenix-screencast-part-1/</id>
    <published>2020-05-11T11:18:05.000Z</published>
    <updated>2025-02-18T03:25:20.579Z</updated>
    
    <content type="html"><![CDATA[<div class="video-container"><iframe src="https://www.youtube.com/embed/Cl2U_8XtUBM" frameborder="0" loading="lazy" allowfullscreen></iframe></div>]]></content>
    
    <summary type="html">
    
      
      
        &lt;div class=&quot;video-container&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/Cl2U_8XtUBM&quot; frameborder=&quot;0&quot; loading=&quot;lazy&quot; allowfullscreen&gt;&lt;/iframe
      
    
    </summary>
    
    
      <category term="Elixir" scheme="http://minhajuddin.com/tags/elixir/"/>
    
      <category term="Phoenix" scheme="http://minhajuddin.com/tags/phoenix/"/>
    
      <category term="Ecto" scheme="http://minhajuddin.com/tags/ecto/"/>
    
      <category term="many_to_many" scheme="http://minhajuddin.com/tags/many-to-many/"/>
    
      <category term="Tags" scheme="http://minhajuddin.com/tags/tags/"/>
    
      <category term="relationships" scheme="http://minhajuddin.com/tags/relationships/"/>
    
  </entry>
  
  <entry>
    <title>How to use a single aurora cluster for multiple databases each with its own restricted user</title>
    <link href="http://minhajuddin.com/2020/05/09/how-to-use-a-single-aurora-cluster-for-multiple-databases-each-with-its-own-restricted-user/"/>
    <id>http://minhajuddin.com/2020/05/09/how-to-use-a-single-aurora-cluster-for-multiple-databases-each-with-its-own-restricted-user/</id>
    <published>2020-05-09T15:58:55.000Z</published>
    <updated>2025-02-18T03:25:20.576Z</updated>
    
    <content type="html"><![CDATA[<p>I have been playing around with terraform for the last few days and it is anamazing tool to manage infrastructure. For my AWS infrastructure I needed anaurora postgresql cluster which would allow hosting of multiple databases, eachfor one of my side projects, while also keeping them isolated and preventingan app user from accessing other app’s databases.</p><img src="/2020/05/09/how-to-use-a-single-aurora-cluster-for-multiple-databases-each-with-its-own-restricted-user/db.png" class=""><p><a href="https://www.terraform.io/docs/providers/postgresql/index.html">Terraform has an awesome postgresqlprovider</a> whichcan be used for managing databases, However there are a few parts which aretricky and needed trial and error to get right.</p><h2 id="Connecting-to-an-RDS-database-via-an-SSH-tunnel"><a href="#Connecting-to-an-RDS-database-via-an-SSH-tunnel" class="headerlink" title="Connecting to an RDS database via an SSH tunnel"></a>Connecting to an RDS database via an SSH tunnel</h2><p>The first roadblock was that my RDS cluster wasn’t accessible publicly (which ishow it should be for security reasons). I do have a <a href="https://minhajuddin.com/2020/05/06/how-to-create-temporary-bastion-ec2-instances-using-terraform/">way to connect to mypostgres servers via a bastionhost</a>.I thought we could use an SSH tunnel over the bastion host to get to our RDScluster from my local computer. However, terraform doesn’t supportconnecting to the postgres server over an SSH tunnel via its configuration.</p><p>So, it required a little bit of jerry-rigging. The postgresql provider was happyas long as it could reach the postgres cluster using a host, port and password.So, I set up a local tunnel outside terraform via my SSH config like so:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">Host bastion</span><br><span class="line">Hostname ec2-180-21-145-48.us-east-2.compute.amazonaws.com</span><br><span class="line">IdentityFile ~/.ssh/aws_ssh.pem</span><br><span class="line"></span><br><span class="line">Host ecs1-pg</span><br><span class="line">  LocalForward localhost:3333 hn-aurora-pg-1.hosturl.us-east-2.rds.amazonaws.com:5432</span><br><span class="line"></span><br><span class="line">Host ecs1 ecs1-pg</span><br><span class="line">  Hostname 20.10.22.214</span><br><span class="line">  User ec2-user</span><br><span class="line">  IdentityFile ~/.ssh/aws_ssh.pem</span><br><span class="line">  ForwardAgent yes</span><br><span class="line">  ProxyJump bastion</span><br></pre></td></tr></table></figure><p>The relevant line here is the <code>LocalForward</code> declaration which wires up a localport forward so that when you network traffic hits port <code>3333</code> on your<code>localhost</code> it is tunneled over the bastion and then the ecs server and isrouted to your cluster’s port <code>5432</code>. One thing to note here is that your ecscluster should be able to connect to your RDS cluster via proper security grouprules.</p><h2 id="Setting-up-the-postgres-provider"><a href="#Setting-up-the-postgres-provider" class="headerlink" title="Setting up the postgres provider"></a>Setting up the postgres provider</h2><p>Once you have the ssh tunnel set up, you can start wiring up your postgresprovider for terraform like so:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">provider &quot;postgresql&quot; &#123;</span><br><span class="line">  version = &quot;~&gt; 1.5&quot;</span><br><span class="line"></span><br><span class="line">  # LocalForwarded on the local computer via an SSH tunnel to</span><br><span class="line">  # module.hn_db.this_rds_cluster_endpoint</span><br><span class="line">  # via</span><br><span class="line">  # LocalForward localhost:3333 module.hn_db.this_rds_cluster_endpoint:5432</span><br><span class="line">  host            = &quot;localhost&quot;</span><br><span class="line">  port            = 3333</span><br><span class="line">  username        = &quot;root&quot;</span><br><span class="line">  superuser       = false</span><br><span class="line">  password        = module.hn_db.this_rds_cluster_master_password</span><br><span class="line">  sslmode         = &quot;require&quot;</span><br><span class="line">  connect_timeout = 15</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The provider config is pretty straightforward, we point it to <code>localhost:3333</code>with a <code>root</code> user (which is the master user created by the rds cluster). So,when you connect to <code>localhost:3333</code>, you are actually connecting to the RDScluster through an SSH tunnel (make sure that your ssh connection is open atthis point via <code>ssh ecs1-pg</code> in a separate terminal). We also need to set the<code>superuser</code> to <code>false</code> because RDS doesn’t give us a postgres superuser, gettingthis wrong initially caused me a lot of frustration.</p><h2 id="Setting-up-the-database-and-it’s-user"><a href="#Setting-up-the-database-and-it’s-user" class="headerlink" title="Setting up the database and it’s user"></a>Setting up the database and it’s user</h2><p>Now that our cluster connectivity is set up, we can start creating the databasesand users, each for one of our apps.</p><p>Below is a sensible configuration for a database called <code>liveform_prod</code> and it’suser called <code>liveform</code>.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">locals &#123;</span><br><span class="line">  lf_connection_limit  = 5</span><br><span class="line">  lf_statement_timeout = 60000 # 1 minute</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;postgresql_database&quot; &quot;liveform_db&quot; &#123;</span><br><span class="line">  name             = &quot;liveform_prod&quot;</span><br><span class="line">  owner            = postgresql_role.liveform_db_role.name</span><br><span class="line">  connection_limit = local.lf_connection_limit</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;postgresql_role&quot; &quot;liveform_db_role&quot; &#123;</span><br><span class="line">  name              = &quot;liveform&quot;</span><br><span class="line">  login             = true</span><br><span class="line">  password          = random_password.liveform_db_password.result</span><br><span class="line">  connection_limit  = local.lf_connection_limit</span><br><span class="line">  statement_timeout = local.lf_statement_timeout</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;random_password&quot; &quot;liveform_db_password&quot; &#123;</span><br><span class="line">  length  = 40</span><br><span class="line">  special = false</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">output &quot;liveform_db_password&quot; &#123;</span><br><span class="line">  description = &quot;Liveform db password&quot;</span><br><span class="line">  value       = random_password.liveform_db_password.result</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>A few things to note here:</p><ol><li>The database <code>liveform_prod</code> is owned by a new user called <code>liveform</code>.</li><li>It has a connection limit of <code>5</code>, You should always set a sensibleconnection limit to prevent this app from crashing the cluster.</li><li>The db user too has a connection limit of <code>5</code> and a statement timeout of 1minute which is big enough for web apps, you should set it to the leastduration which works for your app.</li><li>A random password (via the <code>random_password</code> resource) is used as thepassword of our new <code>liveform</code> role. This can be viewed by running<code>terraform show</code></li></ol><h2 id="Isolating-this-database-from-other-users"><a href="#Isolating-this-database-from-other-users" class="headerlink" title="Isolating this database from other users"></a>Isolating this database from other users</h2><p>By default postgres allows all users to connect to all databases and create/viewfrom all the tables. We want our databases to be isolated properly so that auser for one app cannot access another app’s database. This requires running ofsome SQL on the newly created database. We can easily do this using a<code>null_resource</code> and a <code>local-exec</code> provisioner like so:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">resource &quot;null_resource&quot; &quot;liveform_db_after_create&quot; &#123;</span><br><span class="line">  depends_on = [</span><br><span class="line">    postgresql_database.liveform_db,</span><br><span class="line">    postgresql_role.liveform_db_role</span><br><span class="line">  ]</span><br><span class="line"></span><br><span class="line">  provisioner &quot;local-exec&quot; &#123;</span><br><span class="line">    command = &quot;./pg_database_roles_setup.sh&quot;</span><br><span class="line">    environment = &#123;</span><br><span class="line">      PG_DB_ROLE_NAME = postgresql_role.liveform_db_role.name</span><br><span class="line">      PG_DB_NAME      = postgresql_database.liveform_db.name</span><br><span class="line">      PGPASSWORD      = random_password.liveform_db_password.result</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>./pg_database_roles_setup.sh</code> script:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"></span><br><span class="line"><span class="comment"># This needs an SSH TUNNEL to be set up</span></span><br><span class="line"><span class="comment"># password needs to be supplied via the PGPASSWORD env var</span></span><br><span class="line">psql --host <span class="string">&quot;localhost&quot;</span> \</span><br><span class="line">    --port <span class="string">&quot;3333&quot;</span> \</span><br><span class="line">    --username <span class="string">&quot;<span class="variable">$PG_DB_ROLE_NAME</span>&quot;</span> \</span><br><span class="line">    --dbname <span class="string">&quot;<span class="variable">$PG_DB_NAME</span>&quot;</span> \</span><br><span class="line">    --file - &lt;&lt;<span class="string">SQL</span></span><br><span class="line"><span class="string">  REVOKE CONNECT ON DATABASE $PG_DB_NAME FROM PUBLIC;</span></span><br><span class="line"><span class="string">  GRANT CONNECT ON DATABASE $PG_DB_NAME TO $PG_DB_ROLE_NAME;</span></span><br><span class="line"><span class="string">  GRANT CONNECT ON DATABASE $PG_DB_NAME TO root;</span></span><br><span class="line"><span class="string">SQL</span></span><br></pre></td></tr></table></figure><p>The <code>pg_database_roles_setup.sh</code> script connects to our rds cluster over the SSHtunnel to the newly created database as the newly created user and revokesconnect privileges for all users on this database, and then adds connectprivileges to the app user and the root user. You can add more queries to thisscript that you might want to run after the database is set up. Finally, the<code>local-exec</code> provisioner passes the right data via environment variables andcalls the database setup script.</p><h2 id="Gotchas"><a href="#Gotchas" class="headerlink" title="Gotchas"></a>Gotchas</h2><p>If you create a <code>posgresql_role</code> before setting the connection’s <code>superuser</code> to<code>false</code>, you’ll get stuck trying to update or delete the new role. To work aroundthis, manually log in to the rds cluster via psql and <code>DROP</code> the role, and removethis state from terraform using: <code>terraform state rmpostgresql_role.liveform_db_role</code></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;I have been playing around with terraform for the last few days and it is an
amazing tool to manage infrastructure. For my AWS infrastruc
      
    
    </summary>
    
    
      <category term="AWS" scheme="http://minhajuddin.com/tags/aws/"/>
    
      <category term="Terraform" scheme="http://minhajuddin.com/tags/terraform/"/>
    
      <category term="Aurora" scheme="http://minhajuddin.com/tags/aurora/"/>
    
      <category term="RDS" scheme="http://minhajuddin.com/tags/rds/"/>
    
      <category term="Postgres" scheme="http://minhajuddin.com/tags/postgres/"/>
    
      <category term="Multiple" scheme="http://minhajuddin.com/tags/multiple/"/>
    
      <category term="Restricted" scheme="http://minhajuddin.com/tags/restricted/"/>
    
      <category term="Isolated" scheme="http://minhajuddin.com/tags/isolated/"/>
    
      <category term="User" scheme="http://minhajuddin.com/tags/user/"/>
    
      <category term="Postgres provider" scheme="http://minhajuddin.com/tags/postgres-provider/"/>
    
  </entry>
  
  <entry>
    <title>How to create temporary bastion EC2 instances using Terraform</title>
    <link href="http://minhajuddin.com/2020/05/06/how-to-create-temporary-bastion-ec2-instances-using-terraform/"/>
    <id>http://minhajuddin.com/2020/05/06/how-to-create-temporary-bastion-ec2-instances-using-terraform/</id>
    <published>2020-05-06T12:02:47.000Z</published>
    <updated>2025-02-18T03:25:20.568Z</updated>
    
    <content type="html"><![CDATA[<p>I have recently started learning Terraform to manage my AWS resources, And it isa great tool for maintaining your infrastructure! I use a <a href="https://en.wikipedia.org/wiki/Bastion_host">Bastionhost</a> to SSH into my main serversand bring up the bastion host on demand only when I need it giving me some costsavings. Here are the required Terraform files to get this working.</p><p>Set up the <code>bastion.tf</code> file like so:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"># get a reference to aws_ami.id using a data resource by finding the right AMI</span><br><span class="line">data &quot;aws_ami&quot; &quot;ubuntu&quot; &#123;</span><br><span class="line">  # pick the most recent version of the AMI</span><br><span class="line">  most_recent = true</span><br><span class="line"></span><br><span class="line">  # Find the 20.04 image</span><br><span class="line">  filter &#123;</span><br><span class="line">    name   = &quot;name&quot;</span><br><span class="line">    values = [&quot;ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*&quot;]</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  # With the right virtualization type</span><br><span class="line">  filter &#123;</span><br><span class="line">    name   = &quot;virtualization-type&quot;</span><br><span class="line">    values = [&quot;hvm&quot;]</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  # And the image should be published by Canonical (which is a trusted source)</span><br><span class="line">  owners = [&quot;099720109477&quot;] # Canonical&#x27;s owner_id don&#x27;t change this</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"># Configuration for your bastion EC2 instance</span><br><span class="line">resource &quot;aws_instance&quot; &quot;bastion&quot; &#123;</span><br><span class="line">  # Use the AMI from the above step</span><br><span class="line">  ami = data.aws_ami.ubuntu.id</span><br><span class="line"></span><br><span class="line">  # We don&#x27;t need a heavy duty server, t2.micro should suffice</span><br><span class="line">  instance_type = &quot;t2.micro&quot;</span><br><span class="line"></span><br><span class="line">  # We use a variable which can be set to true or false in the terraform.tfvars</span><br><span class="line">  # file to control creating or destroying the bastion resource on demand.</span><br><span class="line">  count = var.bastion_enabled ? 1 : 0</span><br><span class="line"></span><br><span class="line">  # The ssh key name</span><br><span class="line">  key_name = var.ssh_key_name</span><br><span class="line"></span><br><span class="line">  # This should refer to the subnet in which you want to spin up the Bastion host</span><br><span class="line">  # You can even hardcode this ID by getting a subnet id from the AWS console</span><br><span class="line">  subnet_id = aws_subnet.subnet[0].id</span><br><span class="line"></span><br><span class="line">  # The 2 security groups here have 2 important rules</span><br><span class="line">  # 1. hn_bastion_sg: opens up Port 22 for just my IP address</span><br><span class="line">  # 2. default: sets up an open network within the security group</span><br><span class="line">  vpc_security_group_ids = [aws_security_group.hn_bastion_sg.id, aws_default_security_group.default.id]</span><br><span class="line"></span><br><span class="line">  # Since we want to access this via internet, we need a public IP</span><br><span class="line">  associate_public_ip_address = true</span><br><span class="line"></span><br><span class="line">  # Some useful tags</span><br><span class="line">  tags = &#123;</span><br><span class="line">    Name = &quot;Bastion&quot;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"># We want to output the public_dns name of the bastion host when it spins up</span><br><span class="line">output &quot;bastion-public-dns&quot; &#123;</span><br><span class="line">  value = var.bastion_enabled ? aws_instance.bastion[0].public_dns : &quot;No-bastion&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Set up the <code>terraform.tfvars</code> file like so:<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"># Set this to `true` and do a `terraform apply` to spin up a bastion host</span><br><span class="line"># and when you are done, set it to `false` and do another `terraform apply`</span><br><span class="line">bastion_enabled = false</span><br><span class="line"></span><br><span class="line"># My SSH keyname (without the .pem extension)</span><br><span class="line">ssh_key_name = &quot;hyperngn_aws_ohio&quot;</span><br><span class="line"></span><br><span class="line"># The IP of my computer. Do a `curl -sq icanhazip.com` to get it</span><br><span class="line"># Look for the **ProTip** down below to automate this!</span><br><span class="line">myip = [&quot;247.39.103.23/32&quot;]</span><br><span class="line"></span><br></pre></td></tr></table></figure></p><p>Set up the <code>vars.tf</code> file like so:<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">variable &quot;ssh_key_name&quot; &#123;</span><br><span class="line">  description = &quot;Name of AWS key pair&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">variable &quot;myip&quot; &#123;</span><br><span class="line">  type        = list(string)</span><br><span class="line">  description = &quot;My IP to allow SSH access into the bastion server&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">variable &quot;bastion_enabled&quot; &#123;</span><br><span class="line">  description = &quot;Spins up a bastion host if enabled&quot;</span><br><span class="line">  type        = bool</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>Relevant sections from my <code>vpc.tf</code>, you could just hardcode these values in the<code>bastion.tf</code> or use <code>data</code> if you’ve set these up manually and <code>resources</code> ifyou use terraform to control them</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">resource &quot;aws_subnet&quot; &quot;subnet&quot; &#123;</span><br><span class="line">  # ...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"># Allows SSH connections from our IP</span><br><span class="line">resource &quot;aws_security_group&quot; &quot;hn_bastion_sg&quot; &#123;</span><br><span class="line">  name   = &quot;hn_bastion_sg&quot;</span><br><span class="line">  vpc_id = aws_vpc.vpc.id</span><br><span class="line"></span><br><span class="line">  ingress &#123;</span><br><span class="line">    from_port   = 22</span><br><span class="line">    to_port     = 22</span><br><span class="line">    protocol    = &quot;tcp&quot;</span><br><span class="line">    cidr_blocks = var.myip</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  egress &#123;</span><br><span class="line">    from_port   = 0</span><br><span class="line">    to_port     = 0</span><br><span class="line">    protocol    = &quot;-1&quot;</span><br><span class="line">    cidr_blocks = [&quot;0.0.0.0/0&quot;]</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"># Allow inter security group connections</span><br><span class="line">resource &quot;aws_default_security_group&quot; &quot;default&quot; &#123;</span><br><span class="line">  vpc_id = aws_vpc.vpc.id</span><br><span class="line"></span><br><span class="line">  ingress &#123;</span><br><span class="line">    protocol  = -1</span><br><span class="line">    self      = true</span><br><span class="line">    from_port = 0</span><br><span class="line">    to_port   = 0</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  egress &#123;</span><br><span class="line">    from_port   = 0</span><br><span class="line">    to_port     = 0</span><br><span class="line">    protocol    = &quot;-1&quot;</span><br><span class="line">    cidr_blocks = [&quot;0.0.0.0/0&quot;]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Finally you need to set up your ~/.ssh/config to use the bastion as the jumphost like so:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"># Bastion config</span><br><span class="line">Host bastion</span><br><span class="line"># Change the hostname to whatever you get from terraform&#x27;s output</span><br><span class="line">Hostname ec2-5-55-128-160.us-east-2.compute.amazonaws.com</span><br><span class="line">IdentityFile ~/.ssh/hyperngn_aws_ohio.pem</span><br><span class="line"></span><br><span class="line"># ECS cluster machines</span><br><span class="line">Host ecs1</span><br><span class="line">Hostname 20.10.21.217</span><br><span class="line">User ec2-user</span><br><span class="line">IdentityFile ~/.ssh/hyperngn_aws_ohio.pem</span><br><span class="line">ForwardAgent yes</span><br><span class="line">ProxyJump bastion</span><br><span class="line"></span><br><span class="line"># This section is optional but allows you to reuse SSH connections</span><br><span class="line">Host *</span><br><span class="line">  User ubuntu</span><br><span class="line">   Compression yes</span><br><span class="line"># every 10 minutes send an alive ping</span><br><span class="line">   ServerAliveInterval 60</span><br><span class="line">   ControlMaster auto</span><br><span class="line">   ControlPath /tmp/ssh-%r@%h:%p</span><br></pre></td></tr></table></figure><p>Once you are done, you can just login by running the following command and itshould run seamlessly:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh ecs1</span><br></pre></td></tr></table></figure><p><strong>Pro-Tip</strong> Put the following in your terraform folder’s .envrc, so that youdon’t have to manually copy paste your IP every time you bring your bastion hostup (You also need to have <a href="https://direnv.net/">direnv</a> for this to work).<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ cat .envrc</span><br><span class="line">export TF_VAR_myip=&quot;[\&quot;$(curl -sq icanhazip.com)/32\&quot;]&quot;</span><br></pre></td></tr></table></figure></p><h2 id="Gotchas"><a href="#Gotchas" class="headerlink" title="Gotchas"></a>Gotchas</h2><ol><li>If you run into any issues use the <code>ssh -vv ecs1</code> command to get copiouslogs and read through all of them to figure out what might be wrong.</li><li>Make sure you are using the correct <code>User</code>, Ubuntu AMIs create a user called<code>ubuntu</code> whereas Amazon ECS optimized AMIs create an <code>ec2-user</code> user, If youget the user wrong <code>ssh</code> will fail.</li><li>Use private IPs for the target servers that you are jumping into and thepublic IP or public DNS for your bastion host.</li><li>Make sure your Bastion host is in the same VPC with a default security groupwhich allows inter security group communication and a security group whichopens up the SSH port for your IP. If they are not on the same VPC make surethey have the right security groups to allow communication from the bastionhost to the target host, specifically on port 22. You can use VPC flow logsto figure problems in your network.</li></ol><p>From a security point of view this is a pretty great set up, your normal serversdon’t allow any SSH access (and in my case aren’t even public and are fronted byALBs). And your bastion host is not up all the time, and even when it is up, itonly allows traffic from your single IP. It also saves cost by tearing down thebastion instance when you don’t need it.</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;I have recently started learning Terraform to manage my AWS resources, And it is
a great tool for maintaining your infrastructure! I use 
      
    
    </summary>
    
    
      <category term="AWS" scheme="http://minhajuddin.com/tags/aws/"/>
    
      <category term="Terraform" scheme="http://minhajuddin.com/tags/terraform/"/>
    
      <category term="Bastion" scheme="http://minhajuddin.com/tags/bastion/"/>
    
      <category term="EC2" scheme="http://minhajuddin.com/tags/ec2/"/>
    
      <category term="SSH" scheme="http://minhajuddin.com/tags/ssh/"/>
    
      <category term="ProxyJump" scheme="http://minhajuddin.com/tags/proxyjump/"/>
    
  </entry>
  
  <entry>
    <title>many_to_many relationships in Ecto and Phoenix for Products and Tags</title>
    <link href="http://minhajuddin.com/2020/05/03/many-to-many-relationships-in-ecto-and-phoenix-for-products-and-tags/"/>
    <id>http://minhajuddin.com/2020/05/03/many-to-many-relationships-in-ecto-and-phoenix-for-products-and-tags/</id>
    <published>2020-05-03T12:54:34.000Z</published>
    <updated>2025-02-18T03:25:20.577Z</updated>
    
    <content type="html"><![CDATA[<p>The other day I was helping a friend set up a phoenix app which required the useof tags on products, we all have used tags in our day to day to add informationabout notes, images, and other stuff. Tags are just labels/chunks-of-text whichare used to associated with an entity like a product, blog post, image, etc.This blog post has a few tags too (Ecto, Elixir, Phoenix, etc.). Tags help usorganize information by annotating records with useful fragments ofinformation. And modeling these in a database is pretty straightforward, it isusually implemented like the following design.</p><img src="/2020/05/03/many-to-many-relationships-in-ecto-and-phoenix-for-products-and-tags/erd-small.jpg" class="" title="Products and Tags ERD"><p>As you can see, we have a many-to-many relation between the products and tagstables via a products_tags table which has just 2 columns the <code>product_id</code> andthe <code>tag_id</code> and it has a composite primary key (while also having an index onthe <code>tag_id</code> to make lookups faster). The use of a join table is required,however, you usually want the join table to be invisible in your domain, as youdon’t want to deal with a ProductTag model, it doesn’t serve any purpose otherthan helping you bridge the object model with the relational model. Anyway, hereis how we ended up building the many-to-many relationship in Phoenix and Ecto.</p><h2 id="Scaffolding-the-models"><a href="#Scaffolding-the-models" class="headerlink" title="Scaffolding the models"></a>Scaffolding the models</h2><p>We use a nondescript <code>Core</code> context for our <code>Product</code> model by running thefollowing scaffold code:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mix phx.gen.html Core Product products name:string description:text</span><br></pre></td></tr></table></figure><p>This generates the following migration (I’ve omitted the boilerplate to makereading the relevant code easier):</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">create table(:products) do</span><br><span class="line">  add :name, :string</span><br><span class="line">  add :description, :text</span><br><span class="line"></span><br><span class="line">  timestamps()</span><br><span class="line">end</span><br></pre></td></tr></table></figure><p>Don’t forget to add the following to your <code>router.ex</code></p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">resources <span class="string">&quot;/products&quot;</span>, ProductController</span><br></pre></td></tr></table></figure><p>Then, we add the <code>Tag</code> in the same context by running the following scaffoldgenerator:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mix phx.gen.html Core Tag tags name:string:unique</span><br></pre></td></tr></table></figure><p>This generates the following migration, note the unique index on <code>name</code>, as wedon’t want tags with duplicate names, you might have separate tags per user inwhich case you would have a unique index on <code>[:user_id, :name]</code>.</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">create table(<span class="symbol">:tags</span>) <span class="keyword">do</span></span><br><span class="line">  add <span class="symbol">:name</span>, <span class="symbol">:string</span></span><br><span class="line"></span><br><span class="line">  timestamps()</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">create unique_index(<span class="symbol">:tags</span>, [<span class="symbol">:name</span>])</span><br></pre></td></tr></table></figure><p>Finally, we generate the migration for the join table <code>products_tags</code>(byconvention it uses the pluralized names of both entities joined by an underscoreso <code>products</code> and <code>tags</code> joined by an <code>_</code> gives us the name <code>products_tags</code>).</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mix phx.gen.schema Core.ProductTag products_tags product_id:references:products tag_id:references:tags</span><br></pre></td></tr></table></figure><p>This scaffolded migration requires a few tweaks to make it look like thefollowing:</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">create table(<span class="symbol">:products_tags</span>, <span class="symbol">primary_key:</span> <span class="keyword">false</span>) <span class="keyword">do</span></span><br><span class="line">  add <span class="symbol">:product_id</span>, references(<span class="symbol">:products</span>, <span class="symbol">on_delete:</span> <span class="symbol">:nothing</span>), <span class="symbol">primary_key:</span> <span class="keyword">true</span></span><br><span class="line">  add <span class="symbol">:tag_id</span>, references(<span class="symbol">:tags</span>, <span class="symbol">on_delete:</span> <span class="symbol">:nothing</span>), <span class="symbol">primary_key:</span> <span class="keyword">true</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">create index(<span class="symbol">:products_tags</span>, [<span class="symbol">:product_id</span>])</span><br><span class="line">create index(<span class="symbol">:products_tags</span>, [<span class="symbol">:tag_id</span>])</span><br></pre></td></tr></table></figure><p>Note the following:</p><ol><li>We added a <code>primary_key: false</code> declaration to the <code>table()</code> function callto avoid creating a wasted <code>id</code> column.</li><li>We got rid of the <code>timestamps()</code> declaration as we don’t want to track<code>inserts</code> and <code>updates</code> on the joins. You might want to track inserts ifyou want to know when a product was tagged with a specific tag which makesthings a little more complex, so, we’ll avoid it for now.</li><li>We added a <code>, primary_key: true</code> to the <code>:product_id</code> and <code>:tag_id</code> linesto make <code>[:product_id, :tag_id]</code> a composite primary key</li></ol><p>Now our database is set up nicely for our many-to-many relationship. Here is howour tables look in the database:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line">product_tags_demo_dev=# \d products</span><br><span class="line">                                       Table &quot;public.products&quot;</span><br><span class="line">┌─────────────┬────────────────────────────────┬───────────┬──────────┬─────────────────────────────┐</span><br><span class="line">│   Column    │              Type              │ Collation │ Nullable │           Default           │</span><br><span class="line">├─────────────┼────────────────────────────────┼───────────┼──────────┼─────────────────────────────┤</span><br><span class="line">│ id          │ bigint                         │           │ not null │ nextval(&#x27;products_id_seq&#x27;::…│</span><br><span class="line">│             │                                │           │          │…regclass)                   │</span><br><span class="line">│ name        │ character varying(255)         │           │          │                             │</span><br><span class="line">│ description │ text                           │           │          │                             │</span><br><span class="line">│ inserted_at │ timestamp(0) without time zone │           │ not null │                             │</span><br><span class="line">│ updated_at  │ timestamp(0) without time zone │           │ not null │                             │</span><br><span class="line">└─────────────┴────────────────────────────────┴───────────┴──────────┴─────────────────────────────┘</span><br><span class="line">Indexes:</span><br><span class="line">    &quot;products_pkey&quot; PRIMARY KEY, btree (id)</span><br><span class="line">Referenced by:</span><br><span class="line">    TABLE &quot;products_tags&quot; CONSTRAINT &quot;products_tags_product_id_fkey&quot; FOREIGN KEY (product_id) REFERENCES products(id)</span><br><span class="line"></span><br><span class="line">product_tags_demo_dev=# \d tags</span><br><span class="line">                                         Table &quot;public.tags&quot;</span><br><span class="line">┌─────────────┬────────────────────────────────┬───────────┬──────────┬─────────────────────────────┐</span><br><span class="line">│   Column    │              Type              │ Collation │ Nullable │           Default           │</span><br><span class="line">├─────────────┼────────────────────────────────┼───────────┼──────────┼─────────────────────────────┤</span><br><span class="line">│ id          │ bigint                         │           │ not null │ nextval(&#x27;tags_id_seq&#x27;::regc…│</span><br><span class="line">│             │                                │           │          │…lass)                       │</span><br><span class="line">│ name        │ character varying(255)         │           │          │                             │</span><br><span class="line">│ inserted_at │ timestamp(0) without time zone │           │ not null │                             │</span><br><span class="line">│ updated_at  │ timestamp(0) without time zone │           │ not null │                             │</span><br><span class="line">└─────────────┴────────────────────────────────┴───────────┴──────────┴─────────────────────────────┘</span><br><span class="line">Indexes:</span><br><span class="line">    &quot;tags_pkey&quot; PRIMARY KEY, btree (id)</span><br><span class="line">    &quot;tags_name_index&quot; UNIQUE, btree (name)</span><br><span class="line">Referenced by:</span><br><span class="line">    TABLE &quot;products_tags&quot; CONSTRAINT &quot;products_tags_tag_id_fkey&quot; FOREIGN KEY (tag_id) REFERENCES tags(id)</span><br><span class="line"></span><br><span class="line">product_tags_demo_dev=# \d products_tags</span><br><span class="line">              Table &quot;public.products_tags&quot;</span><br><span class="line">┌────────────┬────────┬───────────┬──────────┬─────────┐</span><br><span class="line">│   Column   │  Type  │ Collation │ Nullable │ Default │</span><br><span class="line">├────────────┼────────┼───────────┼──────────┼─────────┤</span><br><span class="line">│ product_id │ bigint │           │ not null │         │</span><br><span class="line">│ tag_id     │ bigint │           │ not null │         │</span><br><span class="line">└────────────┴────────┴───────────┴──────────┴─────────┘</span><br><span class="line">Indexes:</span><br><span class="line">    &quot;products_tags_pkey&quot; PRIMARY KEY, btree (product_id, tag_id)</span><br><span class="line">    &quot;products_tags_product_id_index&quot; btree (product_id)</span><br><span class="line">    &quot;products_tags_tag_id_index&quot; btree (tag_id)</span><br><span class="line">Foreign-key constraints:</span><br><span class="line">    &quot;products_tags_product_id_fkey&quot; FOREIGN KEY (product_id) REFERENCES products(id)</span><br><span class="line">    &quot;products_tags_tag_id_fkey&quot; FOREIGN KEY (tag_id) REFERENCES tags(id)</span><br></pre></td></tr></table></figure><h2 id="Getting-tags-to-work"><a href="#Getting-tags-to-work" class="headerlink" title="Getting tags to work!"></a>Getting tags to work!</h2><p>Now comes the fun part, modifying our controllers and contexts to get our tagsworking!</p><p>The first thing we need to do is add a many_to_many relationship on the <code>Product</code> schema like so:</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">schema <span class="string">&quot;products&quot;</span> <span class="keyword">do</span></span><br><span class="line">  field <span class="symbol">:description</span>, <span class="symbol">:string</span></span><br><span class="line">  field <span class="symbol">:name</span>, <span class="symbol">:string</span></span><br><span class="line">  many_to_many <span class="symbol">:tags</span>, ProductTagsDemo.Core.Tag, <span class="symbol">join_through:</span> <span class="string">&quot;products_tags&quot;</span></span><br><span class="line"></span><br><span class="line">  timestamps()</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>(Note, that we don’t need to add this relationship on the other side, i.e., <code>Tag</code>to get this working)</p><p>Now, we need to modify our <code>Product</code> form to show an input mechanism for tags,the easy way to do this is to ask the users to provide a comma-separated list oftags in an input textbox. A nicer way is to use a javascript library like<a href="https://select2.org/getting-started/basic-usage#multi-select-boxes-pillbox"><em>select2</em></a>.For us, a text box with comma-separated tags will suffice.</p><p>The easiest way to do this is to add a text field like so:<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&lt;%= label f, :tags %&gt;</span><br><span class="line">&lt;%= text_input f, :tags %&gt;</span><br><span class="line">&lt;%= error_tag f, :tags %&gt;</span><br></pre></td></tr></table></figure></p><p>However, as soon as you wire this up you’ll get an error on the <code>/products/new</code>page like below:<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">protocol Phoenix.HTML.Safe not implemented for #Ecto.Association.NotLoaded&lt;association :tags is not loaded&gt; of type Ecto.Association.NotLoaded (a struct).</span><br></pre></td></tr></table></figure>This is telling us that the <code>to_string</code> function can’t convert an<code>Ecto.Association.NotLoaded</code> struct into a string, When you have a relation likea <code>belongs_to</code> or <code>has_one</code> or <code>many_to_many</code> that isn’t loaded on a struct, ithas this default value. This is coming from our controller, we can remedy thisby changing our action to the following:</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">new</span></span>(conn, _params) <span class="keyword">do</span></span><br><span class="line">  changeset = Core.change_product(%Product&#123;<span class="symbol">tags:</span> []&#125;)</span><br><span class="line">  render(conn, <span class="string">&quot;new.html&quot;</span>, <span class="symbol">changeset:</span> changeset)</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>Notice the <code>tags: []</code>, we are creating a new product with an empty tagscollection so that it renders properly in the form.</p><p>Now that we have fixed our form, we can try submitting some tags through thisform, However, when you enter any tags and hit <code>Save</code> it doesn’t do anythingwhich is not surprising because we haven’t set up the handling of these tags onthe backend yet.</p><p>We know that the <code>tags</code> field has comma-separated tags, so we need to do thefollowing to be able to save a product.</p><ol><li>Split tags on a comma.</li><li>Strip them of whitespace.</li><li>Lowercase them to get them to be homogeneous (If you want your tag names tobe persisted using the input casing and still treat the uppercased versionthe same as the lowercased or capitalized versions, you can use <code>:citext</code>(short for case insensitive text) read more about how to set up <code>:citext</code>columns in my blog post about <a href="https://minhajuddin.com/2019/04/14/how-to-store-username-or-email-with-case-insensitive-search-using-ecto-part2/">storing username/email in a case insensitivefashion</a>).</li><li>Once we have all the tag <code>names</code> we can insert any new tags and then fetchthe existing tags, combine them, and use <code>put_assoc</code> to put them on theproduct.</li></ol><p>Step #4 creates a race condition in your code which can happen when 2 requeststry to create tags with the same name at the same time. An easy way to workaround this is to treat all the tags as new and do an upsert using<code>Repo.insert_all</code> with an <code>on_conflict: :nothing</code> option which adds the fragment<code>ON CONFLICT DO NOTHING</code> to your SQL making your query run successfully even ifthere are tags with the same name in the database, it just doesn’t insert newtags. Also, note that this function inserts all the tags in a single query doinga bulk insert of all the input tags. Once you <code>upsert</code> all the tags, you canthen find them and use a <code>put_assoc</code> to create an association.</p><p>This is what ended up as the final <code>Core.create_product</code> function:</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">create_product</span></span>(attrs \\ %&#123;&#125;) <span class="keyword">do</span></span><br><span class="line">  %Product&#123;&#125;</span><br><span class="line">  |&gt; Product.changeset(attrs)</span><br><span class="line">  <span class="comment"># use put_assoc to associate the input tags to the product</span></span><br><span class="line">  |&gt; Ecto.Changeset.put_assoc(<span class="symbol">:tags</span>, product_tags(attrs))</span><br><span class="line">  |&gt; Repo.insert()</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">defp</span> <span class="title">parse_tags</span></span>(<span class="keyword">nil</span>), <span class="symbol">do:</span> []</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">defp</span> <span class="title">parse_tags</span></span>(tags) <span class="keyword">do</span></span><br><span class="line">  <span class="comment"># Repo.insert_all requires the inserted_at and updated_at to be filled out</span></span><br><span class="line">  <span class="comment"># and they should have time truncated to the second that is why we need this</span></span><br><span class="line">  now = NaiveDateTime.utc_now() |&gt; NaiveDateTime.truncate(<span class="symbol">:second</span>)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> tag &lt;- String.split(tags, <span class="string">&quot;,&quot;</span>),</span><br><span class="line">      tag = tag |&gt; String.trim() |&gt; String.downcase(),</span><br><span class="line">      tag != <span class="string">&quot;&quot;</span>,</span><br><span class="line">      <span class="symbol">do:</span> %&#123;<span class="symbol">name:</span> tag, <span class="symbol">inserted_at:</span> now, <span class="symbol">updated_at:</span> now&#125;</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">defp</span> <span class="title">product_tags</span></span>(attrs) <span class="keyword">do</span></span><br><span class="line">  tags = parse_tags(attrs[<span class="string">&quot;tags&quot;</span>]) <span class="comment"># =&gt; [%&#123;name: &quot;phone&quot;, inserted_at: ..&#125;,  ...]</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># do an upsert ensuring that all the input tags are present</span></span><br><span class="line">  Repo.insert_all(Tag, tags, <span class="symbol">on_conflict:</span> <span class="symbol">:nothing</span>)</span><br><span class="line"></span><br><span class="line">  tag_names = <span class="keyword">for</span> t &lt;- tags, <span class="symbol">do:</span> t.name</span><br><span class="line">  <span class="comment"># find all the input tags</span></span><br><span class="line">  Repo.all(from t <span class="keyword">in</span> Tag, <span class="symbol">where:</span> t.name <span class="keyword">in</span> ^tag_names)</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>It does the following:</p><ol><li>Normalize our tags</li><li>Ensure that all the tags are in our database using <code>Repo.insert_all</code> with<code>on_conflict: :nothing</code> in a single SQL query.</li><li>Load all the tag structs using the names.</li><li>Use <code>put_assoc</code> to associate the tags with the newly created product.</li><li>From here <code>Ecto</code> takes over and makes sure that our product has the rightassociation records in the <code>products_tags</code> table</li></ol><p>Notice, how through all of our code we haven’t used the <code>products_tags</code> tableexcept for defining the <code>many_to_many</code> relationship in the <code>Product</code> schema.</p><p>This is all you need to insert a product with multiple tags, However, we stillwant to show the tags of a product on the product details page. We can do thisby tweaking our action and the Core module like so:</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">defmodule</span> <span class="title">Core</span></span> <span class="keyword">do</span></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">get_product_with_tags!</span></span>(id), <span class="symbol">do:</span> Product |&gt; preload(<span class="symbol">:tags</span>) |&gt; Repo.get!(id)</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="class"><span class="keyword">defmodule</span> <span class="title">ProductTagsDemoWeb.ProductController</span></span> <span class="keyword">do</span></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">show</span></span>(conn, %&#123;<span class="string">&quot;id&quot;</span> =&gt; id&#125;) <span class="keyword">do</span></span><br><span class="line">    product = Core.get_product_with_tags!(id)</span><br><span class="line">    render(conn, <span class="string">&quot;show.html&quot;</span>, <span class="symbol">product:</span> product)</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>Here we are preloading the tags with the product and we can use it in the viewlike below to show all the tags for a product:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Tags: &lt;%= (for tag &lt;- @product.tags, do: tag.name) |&gt; Enum.join(&quot;, &quot;) %&gt;</span><br></pre></td></tr></table></figure><p>This takes care of creating and showing a product with tags, However, if we tryto edit a product we are greeted with the following error:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">protocol Phoenix.HTML.Safe not implemented for #Ecto.Association.NotLoaded&lt;association :tags is not loaded&gt; of type Ecto.Association.NotLoaded (a struct).</span><br></pre></td></tr></table></figure><p>Hmmm, we have seen this before when we rendered a new Product without tags,However, in this case, our product does have tags but they haven’t beenloaded/preloaded. We can remedy that easily by tweaking our <code>edit</code> action to thefollowing:</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">edit</span></span>(conn, %&#123;<span class="string">&quot;id&quot;</span> =&gt; id&#125;) <span class="keyword">do</span></span><br><span class="line">  product = Core.get_product_with_tags!(id)</span><br><span class="line">  changeset = Core.change_product(product)</span><br><span class="line">  render(conn, <span class="string">&quot;edit.html&quot;</span>, <span class="symbol">product:</span> product, <span class="symbol">changeset:</span> changeset)</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>This gives us a new error:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lists in Phoenix.HTML and templates may only contain integers representing bytes, binaries or other lists, got invalid entry: %ProductTagsDemo.Core.Tag&#123;__meta__: #Ecto.Schema.Metadata&lt;:loaded, &quot;tags&quot;&gt;, id: 1, inserted_at: ~N[2020-05-04 05:20:45], name: &quot;phone&quot;, updated_at: ~N[2020-05-04 05:20:45]&#125;</span><br></pre></td></tr></table></figure><p>This is because we are using a <code>text_input</code> for a collection of tags and whenphoenix tries to convert the list of tags into a string it fails. This is a goodplace to add a custom input function:</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">defmodule</span> <span class="title">ProductTagsDemoWeb.ProductView</span></span> <span class="keyword">do</span></span><br><span class="line">  <span class="keyword">use</span> ProductTagsDemoWeb, <span class="symbol">:view</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">tag_input</span></span>(form, field, opts \\ []) <span class="keyword">do</span></span><br><span class="line">    <span class="comment"># get the input tags collection</span></span><br><span class="line">    tags = Phoenix.HTML.Form.input_value(form, field)</span><br><span class="line">    <span class="comment"># render text using the text_input after converting tags to text</span></span><br><span class="line">    Phoenix.HTML.Form.text_input(form, field, <span class="symbol">value:</span> tags_to_text(tags), opts)</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">defp</span> <span class="title">tags_to_text</span></span>(tags) <span class="keyword">do</span></span><br><span class="line">    tags</span><br><span class="line">    |&gt; Enum.map(<span class="keyword">fn</span> t -&gt; t.name <span class="keyword">end</span>)</span><br><span class="line">    |&gt; Enum.join(<span class="string">&quot;, &quot;</span>)</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>With this helper we can tweak our form to:<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;%= label f, :tags %&gt;</span><br><span class="line">&lt;%= tag_input f, :tags %&gt;</span><br><span class="line">&lt;%= error_tag f, :tags %&gt;</span><br><span class="line"><span class="tag">&lt;<span class="name">small</span> <span class="attr">class</span>=<span class="string">&quot;help-text&quot;</span>&gt;</span>tags separated by commas<span class="tag">&lt;/<span class="name">small</span>&gt;</span></span><br></pre></td></tr></table></figure>Note that the <code>text_input</code> has been changed to <code>tag_input</code>.</p><p>Now, when we go to edit a product, it should render the form with the tagsseparated by commas. However, updating the product by changing tags stilldoesn’t work because we haven’t updated our backend code to handle this. Tocomplete this, we need to tweak the controller and the <code>Core</code> context like so:</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">defmodule</span> <span class="title">ProductTagsDemoWeb.ProductController</span></span> <span class="keyword">do</span></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">update</span></span>(conn, %&#123;<span class="string">&quot;id&quot;</span> =&gt; id, <span class="string">&quot;product&quot;</span> =&gt; product_params&#125;) <span class="keyword">do</span></span><br><span class="line">    product = Core.get_product_with_tags!(id)</span><br><span class="line">    <span class="comment"># ... rest is the same</span></span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="class"><span class="keyword">defmodule</span> <span class="title">ProductTagsDemo.Core</span></span> <span class="keyword">do</span></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">update_product</span></span>(%Product&#123;&#125; = product, attrs) <span class="keyword">do</span></span><br><span class="line">    product</span><br><span class="line">    |&gt; Product.changeset(attrs)</span><br><span class="line">    |&gt; Ecto.Changeset.put_assoc(<span class="symbol">:tags</span>, product_tags(attrs))</span><br><span class="line">    |&gt; Repo.update()</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>Note that in the controller we are using <code>get_product_with_tags!</code> and in thecontext, we inserted a line to <code>put_assoc</code> similar to the <code>create_product</code>function which does the same things as <code>create_product</code>.</p><p>Astute readers will observe that our create and update product implementationdoesn’t rollback newly created tags, when <code>create_product</code> or <code>update_product</code>fails. Let us handle this case and wrap our post!</p><p>Ecto provides <code>Ecto.Multi</code> to allow easy database transaction handling. Thisjust needs changes to our context and our view like so:</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">defmodule</span> <span class="title">ProductTagsDemo.Core</span></span> <span class="keyword">do</span></span><br><span class="line">  <span class="keyword">alias</span> Ecto.Multi</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">create_product</span></span>(attrs \\ %&#123;&#125;) <span class="keyword">do</span></span><br><span class="line">    multi_result =</span><br><span class="line">      Multi.new()</span><br><span class="line">      <span class="comment"># use multi to insert all the tags, so the tags are rolled back when there</span></span><br><span class="line">      <span class="comment"># is an error in product creation</span></span><br><span class="line">      |&gt; ensure_tags(attrs)</span><br><span class="line">      |&gt; Multi.insert(<span class="symbol">:product</span>, <span class="keyword">fn</span> %&#123;<span class="symbol">tags:</span> tags&#125; -&gt;</span><br><span class="line">        <span class="comment"># This chunk of code remains the same, the only difference is we let</span></span><br><span class="line">        <span class="comment"># Ecto.Multi handle insertion of the product</span></span><br><span class="line">        %Product&#123;&#125;</span><br><span class="line">        |&gt; Product.changeset(attrs)</span><br><span class="line">        |&gt; Ecto.Changeset.put_assoc(<span class="symbol">:tags</span>, tags)</span><br><span class="line">      <span class="keyword">end</span>)</span><br><span class="line">      <span class="comment"># Finally, we run all of this in a single transaction</span></span><br><span class="line">      |&gt; Repo.transaction()</span><br><span class="line"></span><br><span class="line">    <span class="comment"># a multi result can be an :ok tagged tuple with the data from all steps</span></span><br><span class="line">    <span class="comment"># or an error tagged tuple with the failure step&#x27;s atom and relevant data</span></span><br><span class="line">    <span class="comment"># in this case we only expect failures in Product insertion</span></span><br><span class="line">    <span class="keyword">case</span> multi_result <span class="keyword">do</span></span><br><span class="line">      &#123;<span class="symbol">:ok</span>, %&#123;<span class="symbol">product:</span> product&#125;&#125; -&gt; &#123;<span class="symbol">:ok</span>, product&#125;</span><br><span class="line">      &#123;<span class="symbol">:error</span>, <span class="symbol">:product</span>, changeset, _&#125; -&gt; &#123;<span class="symbol">:error</span>, changeset&#125;</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># This is identical to `create_product`</span></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">update_product</span></span>(%Product&#123;&#125; = product, attrs) <span class="keyword">do</span></span><br><span class="line">    multi_result =</span><br><span class="line">      Multi.new()</span><br><span class="line">      |&gt; ensure_tags(attrs)</span><br><span class="line">      |&gt; Multi.update(<span class="symbol">:product</span>, <span class="keyword">fn</span> %&#123;<span class="symbol">tags:</span> tags&#125; -&gt;</span><br><span class="line">        product</span><br><span class="line">        |&gt; Product.changeset(attrs)</span><br><span class="line">        |&gt; Ecto.Changeset.put_assoc(<span class="symbol">:tags</span>, tags)</span><br><span class="line">      <span class="keyword">end</span>)</span><br><span class="line">      |&gt; Repo.transaction()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">case</span> multi_result <span class="keyword">do</span></span><br><span class="line">      &#123;<span class="symbol">:ok</span>, %&#123;<span class="symbol">product:</span> product&#125;&#125; -&gt; &#123;<span class="symbol">:ok</span>, product&#125;</span><br><span class="line">      &#123;<span class="symbol">:error</span>, <span class="symbol">:product</span>, changeset, _&#125; -&gt; &#123;<span class="symbol">:error</span>, changeset&#125;</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># parse_tags is unchanged</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># We have created an ensure tags to use the multi struct passed along and the</span></span><br><span class="line">  <span class="comment"># repo associated with it to allow rolling back tag inserts</span></span><br><span class="line">  <span class="function"><span class="keyword">defp</span> <span class="title">ensure_tags</span></span>(multi, attrs) <span class="keyword">do</span></span><br><span class="line">    tags = parse_tags(attrs[<span class="string">&quot;tags&quot;</span>])</span><br><span class="line"></span><br><span class="line">    multi</span><br><span class="line">    |&gt; Multi.insert_all(<span class="symbol">:insert_tags</span>, Tag, tags, <span class="symbol">on_conflict:</span> <span class="symbol">:nothing</span>)</span><br><span class="line">    |&gt; Multi.run(<span class="symbol">:tags</span>, <span class="keyword">fn</span> repo, _changes -&gt;</span><br><span class="line">      tag_names = <span class="keyword">for</span> t &lt;- tags, <span class="symbol">do:</span> t.name</span><br><span class="line">      &#123;<span class="symbol">:ok</span>, repo.all(from t <span class="keyword">in</span> Tag, <span class="symbol">where:</span> t.name <span class="keyword">in</span> ^tag_names)&#125;</span><br><span class="line">    <span class="keyword">end</span>)</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">defmodule</span> <span class="title">ProductTagsDemoWeb.ProductView</span></span> <span class="keyword">do</span></span><br><span class="line">  <span class="keyword">use</span> ProductTagsDemoWeb, <span class="symbol">:view</span></span><br><span class="line">  <span class="keyword">import</span> Phoenix.HTML.Form</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">tag_input</span></span>(form, field, opts \\ []) <span class="keyword">do</span></span><br><span class="line">    text_input(form, field, <span class="symbol">value:</span> tag_value(form.source, form, field))</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># if there is an error, pass the input params along</span></span><br><span class="line">  <span class="function"><span class="keyword">defp</span> <span class="title">tag_value</span></span>(%Ecto.Changeset&#123;<span class="symbol">valid?:</span> <span class="keyword">false</span>&#125;, form, field) <span class="keyword">do</span></span><br><span class="line">    form.params[to_string(field)]</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">defp</span> <span class="title">tag_value</span></span>(_source, form, field) <span class="keyword">do</span></span><br><span class="line">    form</span><br><span class="line">    |&gt; input_value(field)</span><br><span class="line">    |&gt; tags_to_text</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">defp</span> <span class="title">tags_to_text</span></span>(tags) <span class="keyword">do</span></span><br><span class="line">    tags</span><br><span class="line">    |&gt; Enum.map(<span class="keyword">fn</span> t -&gt; t.name <span class="keyword">end</span>)</span><br><span class="line">    |&gt; Enum.join(<span class="string">&quot;, &quot;</span>)</span><br><span class="line">  <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>Whew, that was long, but hopefully, this gives you a comprehensive understandingof how to handle <code>many_to_many</code> relationships in Ecto and Phoenix.</p><p>The source code associated with this blog post can be found at <a href="https://github.com/minhajuddin/product_tags_demo">https://github.com/minhajuddin/product_tags_demo</a></p><p>P.S. There is a lot of duplication in our final <code>create_product</code> and<code>update_product</code> functions, try removing the duplication in an elegant way! I’llshare my take on it in the next post!</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;The other day I was helping a friend set up a phoenix app which required the use
of tags on products, we all have used tags in our day to
      
    
    </summary>
    
    
      <category term="Elixir" scheme="http://minhajuddin.com/tags/elixir/"/>
    
      <category term="Phoenix" scheme="http://minhajuddin.com/tags/phoenix/"/>
    
      <category term="Ecto" scheme="http://minhajuddin.com/tags/ecto/"/>
    
      <category term="many_to_many" scheme="http://minhajuddin.com/tags/many-to-many/"/>
    
      <category term="Tags" scheme="http://minhajuddin.com/tags/tags/"/>
    
      <category term="relationships" scheme="http://minhajuddin.com/tags/relationships/"/>
    
  </entry>
  
  <entry>
    <title>How to dump a partial/sample table(1000 rows) in postgres using pg_dump</title>
    <link href="http://minhajuddin.com/2019/11/30/how-to-dump-a-partial-sample-table-1000-rows-in-postgres-using-pg-dump/"/>
    <id>http://minhajuddin.com/2019/11/30/how-to-dump-a-partial-sample-table-1000-rows-in-postgres-using-pg-dump/</id>
    <published>2019-11-30T00:36:13.000Z</published>
    <updated>2025-02-18T03:25:20.569Z</updated>
    
    <content type="html"><![CDATA[<p>The other day, I wanted to export a sample of one of my big Postgres tables fromthe production server to my local computer. This was a huge table and I didn’twant to move around a few GBs just to get a sample onto my local environment.Unfortunately <code>pg_dump</code> doesn’t support exporting of partial tables. I lookedaround and found a utility called <a href="https://github.com/mla/pg_sample">pg_sample</a>which is supposed to help you with this. However, I wasn’t comfortable withinstalling this on my production server or letting my production data throughthis script. Thinking a little more made the solution obvious. The idea wassimple:</p><ol><li>Create a table called <code>tmp_page_caches</code> where <code>page_caches</code> is the table thatyou want to copy using <code>pg_dump</code> using the following SQL in <code>psql</code>, thisgives you a lot of freedom on SELECTing just the rows you want.<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> tmp_page_caches <span class="keyword">AS</span> (<span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> page_caches LIMIT <span class="number">1000</span>);</span><br></pre></td></tr></table></figure></li><li>Export this table using <code>pg_dump</code> as below. Here we are exporting the data toa sql file and transforming our table name to the original table namemidstream.<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pg_dump app_production --table tmp_page_caches | sed <span class="string">&#x27;s/public.tmp_/public./&#x27;</span> &gt; page_caches.sql</span><br></pre></td></tr></table></figure></li><li>Copy this file to the local server using <code>scp</code> and now run it against thelocal database:<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">scp minhajuddin@server.prod:page_caches.sql .</span><br><span class="line">psql app_development &lt; page_caches.sql</span><br></pre></td></tr></table></figure></li><li>Get rid of the temporary table on the production server<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">DROP</span> <span class="keyword">TABLE</span> tmp_page_caches; <span class="comment">-- be careful not to drop the real table!</span></span><br></pre></td></tr></table></figure></li></ol><p>Voila! We have successfully copied over a sample of our production table to ourlocal environment. Hope you find it useful.</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;The other day, I wanted to export a sample of one of my big Postgres tables from
the production server to my local computer. This was a h
      
    
    </summary>
    
    
      <category term="postgresql" scheme="http://minhajuddin.com/tags/postgresql/"/>
    
      <category term="pg_dump" scheme="http://minhajuddin.com/tags/pg-dump/"/>
    
      <category term="limit" scheme="http://minhajuddin.com/tags/limit/"/>
    
      <category term="partial table" scheme="http://minhajuddin.com/tags/partial-table/"/>
    
      <category term="sample" scheme="http://minhajuddin.com/tags/sample/"/>
    
  </entry>
  
</feed>
