<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="https://www.w3.org/2005/Atom" xml:lang="en">



<title type="text">eLitmus Blog</title>
<generator uri="https://github.com/mojombo/jekyll">Jekyll</generator>
<link rel="self" type="application/atom+xml" href="/feed.xml" />
<link rel="alternate" type="text/html" href="/" />
<updated>2025-09-12T10:47:23+05:30</updated>
<id>/</id>
<author>
  <name>eLitmus.com</name>
  <uri>/</uri>
  <email>site-admin@elitmus.com</email>
</author>


<entry>
  <title type="html"><![CDATA[What's the deal with secret_key_base in Rails?]]></title>
  <link rel="alternate" type="text/html" href="/technology/whats-the-deal-with-secret-key-base-in-rails/"/>
  <id>/technology/whats-the-deal-with-secret-key-base-in-rails</id>
  <updated>2025-09-11 18:52:53 +0530T00:00:00-00:00</updated>
  <published>2025-09-11T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#rails" term="rails" /><category scheme="/tags/#secrets" term="secrets" /><category scheme="/tags/#credentials" term="credentials" />
  <content type="html">
  
    &lt;p&gt;&lt;br /&gt;
If you’ve spent any time with a Rails app, you’ve probably stumbled across this mysterious setting called &lt;code&gt;secret_key_base&lt;/code&gt;. Maybe you saw it in &lt;code&gt;config/secrets.yml&lt;/code&gt;, or like me, you were upgrading Rails, forgetting that credentials are the new norm and suddenly you saw something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;
Missing `secret_key_base` for &apos;production&apos; environment, set this string with `bin/rails credentials:edit`

&lt;/code&gt;&lt;/pre&gt;

&lt;div&gt;
  &lt;img src=&quot;/blog/images/secret_key_base/secret_key_base.png&quot; width=&quot;425&quot; /&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;the-short-version-of-what-it-is&quot;&gt;The short version of what it is:&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;secret_key_base&lt;/code&gt; is Rails’ way of keeping things safe. It’s a big, random string of characters that Rails uses under the hood to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Sign and verify cookies (so nobody can tamper with them).&lt;/li&gt;
  &lt;li&gt;Encrypt and decrypt sensitive data.&lt;/li&gt;
  &lt;li&gt;Make sure session data is legit and not forged.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as the master lock on your app. If someone gets hold of it, they could impersonate users or mess with your sessions. That’s why Rails treats it like a deal-breaker if the key isn’t set.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;where-does-it-come-from&quot;&gt;Where does it come from?&lt;/h3&gt;
&lt;p&gt;In older Rails apps, you’ll see it inside &lt;code&gt;config/secrets.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;
production:
  secret_key_base: &amp;lt;%= ENV[&quot;SECRET_KEY_BASE&quot;] %&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In newer Rails versions, it’s usually stored in &lt;code&gt;config/credentials.yml.enc&lt;/code&gt;. Either way, it usually gets pulled in from an environment variable. You don’t want this string hard-coded in your code. But guess which genius hard-coded it? ;)&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;how-do-you-get-one&quot;&gt;How do you get one?&lt;/h3&gt;

&lt;p&gt;Rails makes this easy. Just run: rails secret&lt;/p&gt;

&lt;p&gt;You’ll get a nice long random string (128 characters). Copy that into your environment variable, and you’re good to go.&lt;/p&gt;

&lt;p&gt;In developmentand test environments, even if you don’t run this command, a secret will be generated for you when you either start the server or the console.&lt;/p&gt;

&lt;p&gt;You can find this generated file in &lt;code&gt;tmp/local_secret.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In a way this file is proof that Rails is looking for &lt;code&gt;config/credentials.yml.enc&lt;/code&gt; and not &lt;code&gt;config/secrets.yml&lt;/code&gt;. To override this, and to make Rails fetch this value from &lt;code&gt;secrets.yml&lt;/code&gt;, just add the following in your application.rb:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;
config.secret_key_base = Rails.application.config_for(:secrets)[:secret_key_base]

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;to-sum-it-up&quot;&gt;To sum it up:&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;secret_key_base&lt;/code&gt; isn’t something you’ll deal with every day, but it’s quietly protecting your Rails app all the time. The good news is: once you set it up properly, you mostly forget about it, unless you’re upgrading to Rails 7 or 8 and you still want to continue using secrets.yml.&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;/technology/whats-the-deal-with-secret-key-base-in-rails/&quot;&gt;What's the deal with secret_key_base in Rails?&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on September 11, 2025.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Kamal App Deployment Tool]]></title>
  <link rel="alternate" type="text/html" href="/technology/kamal-app-deployment-tool-internals/"/>
  <id>/technology/kamal-app-deployment-tool-internals</id>
  <updated>2024-11-14 10:48:20 +0530T00:00:00-00:00</updated>
  <published>2024-11-14T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#kamal" term="kamal" /><category scheme="/tags/#deployment" term="deployment" /><category scheme="/tags/#docker" term="docker" /><category scheme="/tags/#rails" term="rails" /><category scheme="/tags/#ruby" term="ruby" />
  <content type="html">
  
    &lt;p&gt;Kamal is a simple, dedicated orchestration tool built specifically for deploying containerized applications (mainly Rails). In this blog post, I will take a deep dive into the internal workings of Kamal, exploring its high-level architecture and key deployment phases.&lt;/p&gt;

&lt;h4 id=&quot;high-level-architecture-of-kamal&quot;&gt;&lt;strong&gt;High Level Architecture of Kamal&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;We can divide Kamal deployment in 3 parts&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Build&lt;/li&gt;
  &lt;li&gt;Push Image to container registry&lt;/li&gt;
  &lt;li&gt;Deploy&lt;/li&gt;
&lt;/ol&gt;

&lt;div style=&quot; text-align: center; margin: 20px;&quot;&gt;
  &lt;img src=&quot;/blog/images/kamal-deploy/High-level-arch.png&quot; /&gt;
&lt;/div&gt;

&lt;h5 id=&quot;build-phase&quot;&gt;&lt;strong&gt;Build Phase&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;The build process can occur either locally or on a remote server. Its primary purpose is to create images compatible with both amd64 and arm64 architectures. Kamal employs a straightforward Docker-in-Docker strategy for cross-platform builds, making it seamless to create images that work across different architectures.&lt;/p&gt;

&lt;h5 id=&quot;container-registry&quot;&gt;&lt;strong&gt;Container Registry&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;When using &lt;code&gt;kamal build&lt;/code&gt; command, the tool automatically pushes the newly built image to your container registry. If you’re handling the build process separately, you’ll need to manage the registry push manually. Kamal supports most major container registries out of the box (e.g. Docker hub / AWS ECR).&lt;/p&gt;

&lt;h5 id=&quot;deploy-phase-the-core-magic&quot;&gt;&lt;strong&gt;Deploy Phase: The Core Magic&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;This is the core of Kamal’s deployment process, which goes beyond just pulling the image and starting a container. Here’s a step by step breakdown of what Kamal does under the hood during deployment:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;SSH into servers &amp;amp; start by removing any outdated image with the same tag on each server, ensuring the environment is clean.&lt;/li&gt;
  &lt;li&gt;Then, it pulls the latest version of the Docker image from the registry&lt;/li&gt;
  &lt;li&gt;Kamal integrates with Kamal-proxy (or similar proxies) to manage network routing. It checks if the kamal-proxy is active, which is essential for rerouting traffic to the new container when it’s ready.&lt;/li&gt;
  &lt;li&gt;Kamal Now, first replaces the old container name to &lt;code&gt;_replaced&lt;/code&gt; than start this new primary container &amp;amp; will wait for healthcheck to be passed to consider it as healthy.&lt;/li&gt;
  &lt;li&gt;Once the above step completed Kamal starts the secondary containers.&lt;/li&gt;
  &lt;li&gt;After confirming that the new containers are stable, Kamal prunes old containers and images, freeing up disk space and reducing clutter on servers. This cleanup ensures efficient resource use over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;div style=&quot; text-align: center; margin: 20px;&quot;&gt;
  &lt;img src=&quot;/blog/images/kamal-deploy/deploy.png&quot; /&gt;
&lt;/div&gt;

&lt;h4 id=&quot;practical-uses&quot;&gt;&lt;strong&gt;Practical Uses&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Kamal can be used in multiple ways -&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;As a cross platform image builder.&lt;/li&gt;
  &lt;li&gt;For deploying pre-built containerized applications (Build step can be taken care by someone else).
    &lt;ul&gt;
      &lt;li&gt;Deploy step can be done for any containerized application.&lt;/li&gt;
      &lt;li&gt;Platform specific deployment to multiple nodes in parallel.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;As an end-to-end solution handling both build and deploy phases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;prerequisites-to-use-kamal&quot;&gt;&lt;strong&gt;Prerequisites to use Kamal&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;Ruby Environment.&lt;/li&gt;
  &lt;li&gt;A containerized application.&lt;/li&gt;
  &lt;li&gt;Access to container registry.&lt;/li&gt;
  &lt;li&gt;Server Infrastructure (Bare Metal / EC2 / Google Cloud )
    &lt;ul&gt;
      &lt;li&gt;SSH Keys Setup so that kamal can access the servers during deploy.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;steps&quot;&gt;&lt;strong&gt;Steps&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;Install latest ruby&lt;/li&gt;
  &lt;li&gt;Initialize kamal
    &lt;ul&gt;
      &lt;li&gt;Install kamal gem. &lt;code&gt;gem install kamal&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;In app directory run &lt;code&gt;kamal init&lt;/code&gt; (This can be run outside of project directory if build is not part of the deployment)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Configure deployment settings in &lt;code&gt;config/deploy.yml&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;For multiple environments, create specific config files(e.g. staging / UAT / production)
config/deploy.staging.yml&lt;code&gt;
config/deploy.production.yml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;build-configuration&quot;&gt;&lt;strong&gt;Build Configuration&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;Kamal offers flexible options for managing the build process.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Within same project folder using &lt;code&gt;config/deploy.yml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;builder:
  arch: amd64&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Specify external project locations, if &lt;code&gt;config/deploy.yml&lt;/code&gt; is in different folder:&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;builder:
    arch: amd64
    context: &amp;#39;path_to_project_base&amp;#39; 
    dockerfile: &amp;#39;path_to_dockerfile&amp;#39;
    args:
      COMMIT_SHA: 1.0.0 # default it picks up the latest commit hash.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h6 id=&quot;image-tagging&quot;&gt;&lt;strong&gt;Image Tagging&lt;/strong&gt;&lt;/h6&gt;
&lt;p&gt;By default, Kamal uses Git commit hashes for image tagging. This provides automatic versioning based on your Git history. However, you can customize this behavior if you want to create your own tags:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;builder:
  # Other configurations...
  args:
    # Override default git hash-based tag
    COMMIT_SHA: 1.0.0&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Once configuration are set use &lt;code&gt;kamal build&lt;/code&gt; to finish up the build process.&lt;/p&gt;

&lt;p&gt;To deploy a specific version we need to pass &lt;code&gt;VERSION&lt;/code&gt; during deploy&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;VERSION=1.0.0 kamal deploy -P&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h5 id=&quot;deploy-configurations&quot;&gt;&lt;strong&gt;Deploy configurations&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;Kamal gives us following commands to deploy application -&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;kamal setup    # First-time setup and deployment
kamal deploy   # Standard deployment
kamal redeploy # Just Deploy without bootstrapping servers / proxy containers&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Pro Tip&lt;/strong&gt;: Add &lt;code&gt;-P&lt;/code&gt; or &lt;code&gt;--skip-push&lt;/code&gt; to any command to skip the build and push phases. This is particularly useful when you’re using a separate CI/CD pipeline for building images:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;kamal deploy -P  # Deploy without image build &amp;amp; push step&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h5 id=&quot;managing-database-migrations&quot;&gt;&lt;strong&gt;Managing Database Migrations&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;We can use following approaches to run the migrations so that migrations runs only in primary server.&lt;/p&gt;

&lt;h6 id=&quot;using-hooks&quot;&gt;&lt;strong&gt;Using Hooks&lt;/strong&gt;&lt;/h6&gt;
&lt;p&gt;Hooks are Kamal’s way of executing commands at specific points in the deployment process. Here’s the complete hooks list.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;docker-setup
pre-connect
pre-build
pre-deploy
post-deploy
pre-proxy-reboot
post-proxy-reboot&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;For migrations, the &lt;code&gt;pre_deploy&lt;/code&gt; hook can be used, insert the below code in &lt;code&gt;hooks/pre-delploy&lt;/code&gt; file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;VERSION=$KAMAL_VERSION kamal app exec -p &amp;quot;./bin/rails db:prepare&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h6 id=&quot;server-tags-for-migration-control&quot;&gt;&lt;strong&gt;Server Tags for Migration Control&lt;/strong&gt;&lt;/h6&gt;
&lt;p&gt;To run migrations using server tags we can do following steps&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Add a tag to the server&lt;/li&gt;
  &lt;li&gt;Create Env variable for that tag&lt;/li&gt;
  &lt;li&gt;Use that ENV variable in docker-entrypoint&lt;/li&gt;
&lt;/ol&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;config.yml
servers:
  - 172.0.0.1: db
  - 172.0.0.2
  - 172.0.0.3
  
env:
  clear:
    MYSQL_USER: app
  secret:
    - MYSQL_PASSWORD
  tags:
    db:
      MIGRATION: 1&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;if [ &amp;quot;$RUN_MIGRATIONS&amp;quot; = &amp;quot;1&amp;quot; ]; then
  echo &amp;quot;Running database migrations...&amp;quot;
  bundle exec rails db:migrate
fi&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;For Detailed discussion on Database migration visit &lt;a href=&quot;https://github.com/basecamp/kamal/discussions/526&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Github Discussion&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;references&quot;&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/basecamp/kamal&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Kamal github&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://kamal-deploy.org&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Kamal Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

  
  &lt;p&gt;&lt;a href=&quot;/technology/kamal-app-deployment-tool-internals/&quot;&gt;Kamal App Deployment Tool&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on November 14, 2024.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Improving Drag in React JS: A Smoother Approach to Draggable Elements]]></title>
  <link rel="alternate" type="text/html" href="/technology/improving-drag-in-reactjs-a-smoother-approach-to-draggable-elements/"/>
  <id>/technology/improving-drag-in-reactjs-a-smoother-approach-to-draggable-elements</id>
  <updated>2024-10-15 19:39:07 +0530T00:00:00-00:00</updated>
  <published>2024-10-15T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#technology" term="technology" /><category scheme="/tags/#javascript" term="javascript" /><category scheme="/tags/#ReactJS" term="ReactJS" />
  <content type="html">
  
    &lt;p&gt;When building interactive web components, such as a &lt;code&gt;draggable&lt;/code&gt; element, you might run into the problem where the native draggable attribute leaves the original element in place while dragging a semi-transparent copy. This behavior can feel clunky and disrupts the user experience. In this blog, I’ll show you how to build a smooth draggable component where the element itself follows the mouse pointer, rather than a ghost image.&lt;/p&gt;

&lt;div style=&quot; text-align: center; margin: 20px;&quot;&gt;
  &lt;img src=&quot;/blog/images/draggable-element/draggable.gif&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;By default, when you set an HTML element to &lt;code&gt;draggable=&quot;true&quot;&lt;/code&gt;, the browser shows a semi-transparent copy of the element that moves with your mouse. While this behavior is native and functional, it often doesn’t look great. You want to make sure that the original element moves smoothly with the cursor without showing the browser’s default ghost image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The solution is to create an overlay element that follows the mouse pointer while hiding the original element during dragging. This approach improves the user experience and creates a smoother drag-and-drop interaction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step-by-Step Guide&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Setup Initial State&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;We are going to need some state to handle the Drag &amp;amp; Drop.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;const [isDragging, setIsDragging] = useState(false);
    const [offset, setOffset] = useState(null);
    const [position, setPosition] = useState(null);
    const draggableRef = useRef(null);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;code&gt;isDragging&lt;/code&gt;: This will store the current drag state of the element.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;offset&lt;/code&gt;: When the user starts dragging, they can click anywhere within the draggable element. This stores the coordinate distance between the element’s origin point and the clicked position.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;position&lt;/code&gt;: This will store the current cursor position.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;draggableRef&lt;/code&gt;: We’ll use this to retrieve the origin position of the draggable element.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Create the Draggable Element&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;First, let’s set up a simple HTML element to drag:&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;return (
      &amp;lt;div className=&amp;quot;App&amp;quot;&amp;gt;
        {
          isDragging &amp;amp;&amp;amp; position &amp;amp;&amp;amp; (
            &amp;lt;div
              className=&amp;quot;draggable-item draggable-overlay&amp;quot;
              style={ `top: ${position.y}px; left: ${position.x}px`}
            &amp;gt;
              Drag Me!
            &amp;lt;/div&amp;gt;
          )
        }
        &amp;lt;div
          ref={draggableRef}
          onMouseDown={handleMouseDown}
          className=&amp;quot;draggable-item&amp;quot;
          style={`opacity: ${isDragging ? 0 : 1};`}
        &amp;gt;
          Drag Me!
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    );&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Handle Drag Start: Hide the Original and Create an Overlay&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;We are not going to use native &lt;code&gt;drag&lt;/code&gt; events. Instead we will be using &lt;code&gt;onMouseDown&lt;/code&gt; event as it will give us more flexibility.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;const getInitialPosition = () =&amp;gt; {
      const draggableElement = draggableRef?.current?.getBoundingClientRect();
      return {
        x: (draggableElement?.x || 0) + window.scrollX, // It will handle edge case when there is scroll in the page.
        y: (draggableElement?.y || 0) + window.scrollY,
      };
    };

    const handleMouseDown = (event) =&amp;gt; {
      setIsDragging(true);
      const initialPosition = getInitialPosition();
      setOffset({
        x: event.clientX - initialPosition.x,
        y: event.clientY - initialPosition.y,
      });
      setPosition(initialPosition);
      document.addEventListener(&amp;#39;mouseup&amp;#39;, handleMouseUp);
    };

    useEffect(() =&amp;gt; {
    if (offset) {
      document.addEventListener(&amp;#39;mousemove&amp;#39;, handleMouseMove);
    } else {
      document.removeEventListener(&amp;#39;mousemove&amp;#39;, handleMouseMove);
    }

    return () =&amp;gt; {
      document.removeEventListener(&amp;#39;mousemove&amp;#39;, handleMouseMove);
    };
  }, [offset]);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Move the Overlay with the Mouse&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;In last step we had added an &lt;code&gt;eventListener&lt;/code&gt; on &lt;code&gt;mousemove&lt;/code&gt; event. Now, let’s make sure that on mouse move the overlay follows it.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;const handleMouseMove = (event) =&amp;gt; {
      if (!isDragging) return;

      if (event.clientX &amp;gt; 0 &amp;amp;&amp;amp; event.clientY &amp;gt; 0) {
        setPosition({
          x: event.clientX - (offset?.x || 0),
          y: event.clientY - (offset?.y || 0),
        });
      }
    };&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Clean Up on Drag End&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;Once the dragging is finished, we need to remove the overlay and make the original element visible again. Also we need to remove the &lt;code&gt;mousemove&lt;/code&gt; event listener.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;const handleMouseUp = () =&amp;gt; {
      setIsDragging(false);
      setPosition(null);
      setOffset(null);

      document.removeEventListener(&amp;#39;mouseup&amp;#39;, handleMouseUp);
    };&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Styling the Draggable &amp;amp; Overlay&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;Finally, you can style the overlay so that it looks like the original element. This CSS will ensure that the overlay matches the original element’s appearance.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;.draggable-item {
      border: 2px solid #707070;
      padding: 7px 14px;
      border-radius: 8px;
      width: fit-content;
      cursor: grab;
      user-select: none;
      background-color: coral;
    }

    .draggable-overlay {
      position: absolute;
      cursor: grabbing;
      box-shadow: 0px 20px 25px -5px #0000001a;
    }&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By using an overlay element to follow the mouse and hiding the original element, you can avoid the default ghost image that the browser shows when dragging. This method provides a smoother and more visually pleasing drag-and-drop experience.&lt;/p&gt;

&lt;p&gt;Feel free to experiment with this solution in your projects, and let me know if you find any other creative ways to enhance the draggable experience!&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;/technology/improving-drag-in-reactjs-a-smoother-approach-to-draggable-elements/&quot;&gt;Improving Drag in React JS: A Smoother Approach to Draggable Elements&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on October 15, 2024.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Book review: Never let me go - Kazuo Ishiguro]]></title>
  <link rel="alternate" type="text/html" href="/book-review/book-review-never-let-me-go-kazuo-ishiguro/"/>
  <id>/book-review/book-review-never-let-me-go-kazuo-ishiguro</id>
  <updated>2024-09-27 16:03:24 +0530T00:00:00-00:00</updated>
  <published>2024-09-27T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#Kazuo%20Ishiguro" term="Kazuo Ishiguro" /><category scheme="/tags/#Haruki%20Murakami" term="Haruki Murakami" /><category scheme="/tags/#Book%20Review" term="Book Review" />
  <content type="html">
  
    &lt;p&gt;I was recently given 2 books as a gift by a close friend, who is a great fan of Murakami’s. The books in question are “Norwegian woods” by Murakami and “Never Let Me Go” by Kazuo Ishiguro.&lt;/p&gt;

&lt;p&gt;The task was to read both of these books, and to validate the said friend’s belief that Murakami,as an author, is far superior to Ishiguro. Being a die-hard Murakami fan, she told me beforehand that “Norwegian Woods” is Murakami’s only story without any magical realism, and probably one of his lesser works, while on the other hand, “Never Let Me Go” is Ishiguro’s finest.&lt;/p&gt;

&lt;div style=&quot; text-align:center;&quot;&gt;
  &lt;img src=&quot;/blog/images/book_review/murakami-vs-ishiguro.png&quot; style=&quot;width: 80%; max-width: 400px;margin: 5%&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;So you see, the balance was already distorted. Comparing Ishiguro’s finest to Murakami’s worst? Only to prove that Murakami was still the better author! Being a fan of neither, but a true friend, I believed what I was told, and picked up “Norwegian Woods” first.&lt;/p&gt;

&lt;p&gt;While much of what I felt while I read the book is already lost, for it has been around a month since, I remember the writing style quite vividly. “Norwegian Woods” follows a very level-headed and mature protagonist, with the tone of the book being serious mostly.&lt;/p&gt;

&lt;p&gt;It is a story of the past, present and future. Without going into much detail (since this blog is mostly about the second book), the protagonist learns to remember and cherish, while also letting go of the past. He fights his demons, and finally accepts and embraces his present, while on the very last page it is hinted that he is finally looking towards his future.&lt;/p&gt;

&lt;p&gt;I felt like death was looming around the corner, throughout the book. And towards the end, it became quite predictable what was about to happen. Nevertheless, I was still distraught when what was supposed to happen, happened.&lt;/p&gt;

&lt;p&gt;The sadness that I felt throughout this book made me purchase a whole set of Murakami’s best works. If this is one of his worst books, I expect nothing short of heaven from his best.&lt;/p&gt;

&lt;p&gt;I next picked up “Never Let Me Go” by Kazuo Ishiguro, the “wannabe japanese” author, as i was told by my friend. I could instantly see the difference in the writing styles. While Murakami’s was a more somber and mature tone, Ishiguro’s was more childlike.&lt;/p&gt;

&lt;p&gt;With this bias already in my mind, I put the book down only after a few pages. But as luck would have it, I had to sit in a hospital for hours the next couple of days and I found this book in my bag. So, I gave the book another try, expecting to be let down. Needless to say, I couldn’t put down the book before finishing it. It was that good.&lt;/p&gt;

&lt;p&gt;Being as cautious as I can be about not giving spoilers, I’ll go through the essence of what I went through while I read this gem.&lt;/p&gt;

&lt;p&gt;“Never Let Me Go” makes you remember your childhood. It makes you remember what being innocent (that’s right, being and not pretending to be) felt like. And it also highlights how you come to lose your innocence. How you finally come to see this world for what it is, instead of what you want it to be.&lt;/p&gt;

&lt;p&gt;In this regard, there is this specific moment in the book where the students prank “Madame” - only to realize that while they could predict how she would react, they weren’t ready for the way it made them, and especially our protagonist, feel.&lt;/p&gt;

&lt;p&gt;Like I said above, I found the tone to be child-like. But only after I was engrossed in the book, did I realize that that was how it was supposed to be. Because the tone was child-like, I could feel the innocence of our protagonist. I could really believe that it was a child who was narrating the book.&lt;/p&gt;

&lt;p&gt;The book makes you go through a variety of emotions - love, jealousy, hatred, apathy, sympathy, and a plethora of others. There were times when I had tears in my eyes reading the book, only to feel hopeful a couple of pages later. And a few more pages down the line, I would go from being hopeful to feeling completely hopeless.&lt;/p&gt;

&lt;div style=&quot; text-align:center;&quot; width=&quot;30px&quot;&gt;
  &lt;img src=&quot;/blog/images/book_review/kazuo-ishiguro-never-let-me-go.png&quot; style=&quot;width: 60%; max-width: 400px;margin: 5%&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;In particular, I loved the character of “Ruth”. A fighter, a leader, a go-getter. Someone who wanted to belong. Someone who wanted to be validated or accepted by others.&lt;/p&gt;

&lt;p&gt;I went from respecting the child she was, to hating the teenager she grew up to be, to slightly understanding, but also feeling apathy towards the adult she became, to finally coming to terms with the good-hearted person she really was, especially towards the end.&lt;/p&gt;

&lt;p&gt;I feel there is a “Ruth” inside all of us. All we want is to be accepted and validated. We would like to belong. Many times, I find myself doing things that I normally wouldn’t. Things I do in order to be validated by others.&lt;/p&gt;

&lt;p&gt;From laughing too hard to a joke that I didn’t really find funny, just to be a part of the group, to being extremely courteous towards elders, because that’s how I am supposed to behave. Not that there’s anything wrong with this, I just realize while doing these things, that I am not being myself.&lt;/p&gt;

&lt;p&gt;Towards the end, the book made me feel grateful for the freedom that I enjoy. It made me understand 
How the most important things in our lives, we take for granted! 
How stupid and shallow and narrow-minded we are.
How evil we really are.&lt;/p&gt;

&lt;p&gt;In summary, “Never Let Me Go” is a masterpiece. While fiction, I found it to be a representation of our current dystopian society. You read that right - 
Not a “dystopian representation of our current society” but a “representation of our current dystopian society”.&lt;/p&gt;

&lt;p&gt;Once I was done reading, I immediately called my friend to let her know that Ishiguro was in no way inferior to Murakami.&lt;/p&gt;

&lt;p&gt;To Mr. Murakami, I look forward to reading your best. I have been suggested to read “Kafka on the shore”. I apologize for ever doubting you, Mr. Ishiguro. In my defense, I hadn’t read “Never Let Me Go.”&lt;/p&gt;


  
  &lt;p&gt;&lt;a href=&quot;/book-review/book-review-never-let-me-go-kazuo-ishiguro/&quot;&gt;Book review: Never let me go - Kazuo Ishiguro&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on September 27, 2024.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Mastering Multi Tenant setup with rails - background jobs]]></title>
  <link rel="alternate" type="text/html" href="/technology/mastering-multi-tenant-setup-with-rails-background-jobs/"/>
  <id>/technology/mastering-multi-tenant-setup-with-rails-background-jobs</id>
  <updated>2024-05-05 12:39:14 +0530T00:00:00-00:00</updated>
  <published>2024-05-05T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#rails" term="rails" /><category scheme="/tags/#multi-tenant" term="multi-tenant" /><category scheme="/tags/#sidekiq" term="sidekiq" /><category scheme="/tags/#activejob" term="activejob" />
  <content type="html">
  
    &lt;p&gt;Welcome back to the Rails multi-tenant architecture series! If you’re just joining in, be sure to check out Part 1, where you’ll find an introduction to multi-tenancy and a detailed walkthrough on setting up a multi-tenant Rails application.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.elitmus.com/blog/technology/mastering-multi-tenant-setup-with-rails-part-1/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Part 1&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;quick-recap&quot;&gt;&lt;strong&gt;Quick Recap&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;In the previous blog post, the focus was on delving into the concept of multi-tenancy in software design, with a specific emphasis on managing separate databases for each tenant. After exploring three types of multi-tenant application architectures, a step-by-step guide was provided for setting up a multi-tenant Rails blog application. This included configuring databases for each tenant, implementing automatic connection switching in Rails 6/7, and using Nginx to run multiple databases simultaneously on different ports.&lt;/p&gt;

&lt;h4 id=&quot;introduction&quot;&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;In this blog post, the focus is on background job processing within a multi-tenant Rails environment. Specifically, it addresses the challenges of running background jobs across multiple databases and proposes solutions to ensure seamless execution of jobs.&lt;/p&gt;

&lt;h4 id=&quot;sidekiq&quot;&gt;&lt;strong&gt;Sidekiq&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;First we will setup Sidekiq, A popular background job processing library for Ruby. Here’s a quick guide on how to set it up:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Add sidekiq( use &amp;gt; 6 version) in Gemfile. Follow &lt;a href=&quot;https://github.com/sidekiq/sidekiq/wiki/Getting-Started&quot; target=&quot;_blank&quot; style=&quot;color:blue;&quot;&gt;This Guide&lt;/a&gt; for setup.&lt;/li&gt;
  &lt;li&gt;Create a sidekiq job &lt;code&gt;rails generate sidekiq:job multi_db_testing&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;# app/sidekiq/multi_db_testing_job.rb
class MultiDbTestingJob &amp;lt; ApplicationJob

  def perform
    p &amp;quot;Number of articles is #{Article.count}&amp;quot;
  end
end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h5 id=&quot;running-up-application-along-with-sidekiq&quot;&gt;&lt;strong&gt;Running up application along with sidekiq&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;To start both the Rails server and Sidekiq, follow these steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Install foreman gem to start both rails server and sidekiq.&lt;/li&gt;
  &lt;li&gt;In Gemfile add &lt;code&gt;foreman&lt;/code&gt; gem &amp;amp; run bundle install.&lt;/li&gt;
  &lt;li&gt;Create a Procfile to define the processes:&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;# procfile
web: bin/rails server --binding=0.0.0.0 --port=3000 --environment=development
sidekiq: bundle exec sidekiq&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h5 id=&quot;triggering-background-jobs&quot;&gt;&lt;strong&gt;Triggering Background jobs&lt;/strong&gt;&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;Create a route and controller action to trigger the Sidekiq job:&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;# config/routes.rb
    resources :articles do
      collection do
        get :run_background_job
      end
    end

    # app/controllers/articles_controller.rb  def run_background_job
      MultiDbTestingJob.perform_later

      redirect_to root_path
    end

    # app/views/articles/index.html.erb
    &amp;lt;%= link_to &amp;quot;Run sidekiq job&amp;quot;, run_background_job_articles_path %&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Start the server using &lt;code&gt;foreman start&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Navigate to http://localhost:3000, and trigger the job.&lt;/li&gt;
  &lt;li&gt;You’ll notice that the job is executed, but it retrieves data only from the default database. why? Continue reading to find out the reason.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;problem&quot;&gt;&lt;strong&gt;Problem?&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;When a Sidekiq server initializes, it establishes a connection pool to manage database queries. During job execution, it retrieves a connection from this pool. If a specific database is not specified for the job, it defaults to the primary database (default - db 1).&lt;/p&gt;

&lt;h5 id=&quot;addressing-the-database-connection-issue&quot;&gt;&lt;strong&gt;Addressing the Database Connection Issue&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;To ensure that background jobs access the correct database, we need to pass the database name as a parameter to each job and modify the job accordingly:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;# /app/controllers/articles_controller.rb
def run_background_job
  MultiDbTestingJob.perform_later(shard_name)

  redirect_to root_path
end

# /app/sidekiq/multi_db_testing.rb
class MultiDbTestingJob &amp;lt; ApplicationJob
  def perform(shard)
    ActiveRecord::Base.connected_to(shard: shard) do
      p &amp;quot;Number of articles in DB is #{Article.count}&amp;quot;
    end
  end
end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, you’ll get the desired result for both databases.&lt;/p&gt;

&lt;p&gt;However, this approach has its drawbacks:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;For each background job, we need to pass an additional parameter.&lt;/li&gt;
  &lt;li&gt;We need to write additional code to connect to the correct database for each background job.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To address these issues, we can create a Sidekiq adapter that will decide which database to connect to based on the database that initiated the background job. But before creating the adapter, we need a global attribute to remember which database we are connected to. To achieve this, Rails &lt;code&gt;CurrentAttributes&lt;/code&gt; and Sidekiq &lt;code&gt;Middleware&lt;/code&gt; will be utilized.&lt;/p&gt;

&lt;h5 id=&quot;current-attributes&quot;&gt;&lt;strong&gt;Current Attributes&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;From the definition of &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html&quot;&gt;Current Attributes&lt;/a&gt;, &lt;em&gt;Abstract super class that provides a thread-isolated attributes singleton, which resets automatically before and after each request. This allows you to keep all the per-request attributes easily available to the whole system.&lt;/em&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;# app/models/current.rb
class Current &amp;lt; ActiveSupport::CurrentAttributes
  attribute :tenant
end

# app/controllers/application_controller.rb
before_action :setup_tenant

def setup_tenant
  tenants = Rails.application.config_for(:settings)[:tenants]
  current_tenant = tenants.keys.find { |key| tenants[key][:hosts].include?(request.env[&amp;#39;HTTP_HOST&amp;#39;]) } || :app1_shard
  Current.tenant = current_tenant.to_sym
end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; - Sidekiq also introduced the &lt;code&gt;cattr&lt;/code&gt; feature, this will help in persisting the value of current attributes when sidekiq job runs.
&lt;a href=&quot;https://www.mikeperham.com/2022/07/29/sidekiq-and-request-specific-context/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Read More&lt;/a&gt;&lt;/p&gt;

&lt;h5 id=&quot;sidekiq-middleware&quot;&gt;&lt;strong&gt;Sidekiq Middleware&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;It is a set of customizable modules that intercept and augment the behavior of Sidekiq job processing in Ruby on Rails applications. &lt;a herf=&quot;https://github.com/sidekiq/sidekiq/wiki/Middleware&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Sidekiq Middleware&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Create file &lt;code&gt;config/initializers/sidekiq.rb&lt;/code&gt; and paste following code.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;# config/initializers/sidekiq.rb
    require &amp;#39;sidekiq&amp;#39;
    require &amp;#39;sidekiq/web&amp;#39;
    require &amp;#39;sidekiq/middleware/current_attributes&amp;#39;
    require_relative &amp;#39;../../app/middleware/sidekiq_adapter&amp;#39;

    Sidekiq::CurrentAttributes.persist(&amp;#39;Current&amp;#39;)

    Sidekiq.configure_server do |config|
      config.server_middleware do |chain|
        chain.add Middleware::SidekiqAdapter
      end
    end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Create file &lt;code&gt;app/middleware/sidekiq_adapter.rb&lt;/code&gt; and paste following code.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;module Middleware
      class SidekiqAdapter
        include Sidekiq::ServerMiddleware

        def call(job_instance, job_payload, queue)
          shard = current_shard(job_payload)
          ApplicationRecord.connected_to(shard: shard, role: :writing) do
            yield
          end
        rescue StandardError =&amp;gt; e
          p &amp;quot;Error occured #{e}&amp;quot;
        end

        def current_shard(job_payload)
          job_payload.try(:[], &amp;#39;cattr&amp;#39;).try(:[], &amp;#39;tenant&amp;#39;)&amp;amp;.to_sym
        end
      end
    end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;With the middleware in place, we can simplify our Sidekiq job and remove the shard logic from it. The middleware will handle connecting to the correct shard.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;# multi_db_testing_job.rb
  class MultiDbTestingJob &amp;lt; ApplicationJob
    def perform
      p &amp;quot;Number of articles in DB is #{Article.count}&amp;quot;
    end
  end


  # /app/controllers/articles_controller.rb
  def run_background_job
    MultiDbTestingJob.perform_later(shard_name)

    redirect_to root_path
  end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Run the project again and subsqeuently run the sidekiq job to test it out.&lt;/li&gt;
&lt;/ul&gt;

&lt;div style=&quot; text-align:center;&quot;&gt;
  &lt;img src=&quot;/blog/images/multi-tenant/sidekiqbg.jpg&quot; /&gt;
&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;You will notice that with the middleware in place, when executing a background job, it connects to the correct database.
    &lt;h5 id=&quot;code---github-link&quot;&gt;&lt;strong&gt;Code&lt;/strong&gt; - &lt;a href=&quot;https://github.com/nikhilbhatt/rails-multi-db-tutorial/releases/tag/0.1.0&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Github Link&lt;/a&gt;&lt;/h5&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;summary&quot;&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;In this blog post, we solved database issue with background job processing in a multi-tenant Rails application. We introduced a custom Sidekiq middleware adapter, that fixes the issue of running background jobs across multiple databases. This approach provides a robust &amp;amp; scalable framework for managing background job execution in complex multi-tenant environments.&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;/technology/mastering-multi-tenant-setup-with-rails-background-jobs/&quot;&gt;Mastering Multi Tenant setup with rails - background jobs&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on May 05, 2024.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Mastering Multi Tenant setup with rails part 1]]></title>
  <link rel="alternate" type="text/html" href="/technology/mastering-multi-tenant-setup-with-rails-part-1/"/>
  <id>/technology/mastering-multi-tenant-setup-with-rails-part-1</id>
  <updated>2023-12-17 14:17:45 +0530T00:00:00-00:00</updated>
  <published>2023-12-17T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#rails" term="rails" /><category scheme="/tags/#multi-tenant" term="multi-tenant" /><category scheme="/tags/#multi-db" term="multi-db" />
  <content type="html">
  
    &lt;p&gt;Multi-tenancy is a software design where a single instance of a software application serves multiple customers or tenants (individual users or organizations). In a multi-tenant architecture, each tenant’s data and configuration are logically isolated from one another, providing a sense of individuality and privacy while sharing the same underlying infrastructure, codebase, and application instance.&lt;/p&gt;

&lt;h4 id=&quot;single-tenant-application&quot;&gt;&lt;strong&gt;Single Tenant application&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;In a single-tenant application, each hosted instance has its dedicated database. Upon addition of a new organization that requires segregated data, a new application is hosted with a different database.&lt;/p&gt;

&lt;div style=&quot; text-align:center;&quot;&gt;
  &lt;img src=&quot;/blog/images/multi-tenant/single-tenant.png&quot; style=&quot;height:600px&quot; /&gt;
&lt;/div&gt;

&lt;h4 id=&quot;multi-tenant-application-types&quot;&gt;&lt;strong&gt;Multi Tenant Application types&lt;/strong&gt;&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Single Database shared rows&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Each table in database will contain an additional row known as tenant_id.&lt;/li&gt;
      &lt;li&gt;Whenever data is stored and retrieved from table this coloumn will be used to get/store the data.&lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;Only the data that belongs to a specific customer/tenant will be fetched.&lt;/p&gt;

        &lt;div style=&quot; text-align:center;&quot;&gt;
  &lt;img src=&quot;/blog/images/multi-tenant/single-db-shared-rows.png&quot; style=&quot;height:600px;&quot; /&gt;
&lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Single Database shared schema&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;For each tenant a different table will be maintained in same database.&lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;Data will be segregated table wise.&lt;/p&gt;

        &lt;div style=&quot; text-align:center;&quot;&gt;
  &lt;img src=&quot;/blog/images/multi-tenant/single-db-separate-tables.png&quot; style=&quot;height:600px;&quot; /&gt;
&lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Dedicated Database for Each Tenant&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;For each tenant a new database schema will be maintained, it can be termed as shard.&lt;/p&gt;

        &lt;div style=&quot; text-align:center;&quot;&gt;
  &lt;img src=&quot;/blog/images/multi-tenant/multi-tenant.png&quot; style=&quot;height:600px;&quot; /&gt;
&lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this blog post, we’ll take an in-depth look at the third approach, where we opt to manage separate databases for each tenant. To demonstrate this, we’ll walk through the process of creating a basic Rails blog application from the ground up.&lt;/p&gt;

&lt;h4 id=&quot;goal&quot;&gt;&lt;strong&gt;Goal&lt;/strong&gt;&lt;/h4&gt;
&lt;ol&gt;
  &lt;li&gt;Setting up a multi-tenant application in development mode.&lt;/li&gt;
  &lt;li&gt;dynamically switching databases according to the requesting host name.&lt;/li&gt;
&lt;/ol&gt;

&lt;h5 id=&quot;what-features-rails-6-brings-in&quot;&gt;&lt;strong&gt;What features rails 6 brings in&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;Rails 6 introduced the multiple database setup with following features -&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Multiple writer databases and a replica for each.&lt;/li&gt;
  &lt;li&gt;Automatic connection switching for the model you’re working with.&lt;/li&gt;
  &lt;li&gt;Automatic swapping between the writer and replica depending on the HTTP verb and recent writes.&lt;/li&gt;
  &lt;li&gt;Rails tasks for creating, dropping, migrating, and interacting with the multiple databases.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;setup&quot;&gt;&lt;strong&gt;Setup&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Create new rails app&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;rails new multi_db_blog&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;update gemfile to use mysql2 instead of sqlite3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Setup databases&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;In &lt;code&gt;database.yml&lt;/code&gt; file update the database with name.&lt;/li&gt;
&lt;/ol&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;development:
  app1:
    adapter: mysql2
    encoding: utf8
    reconnect: false
    database: app1_development
    pool: 5
    username:
    password:
    socket: /tmp/mysql.sock
    host: 127.0.0.1
  app2:
    adapter: mysql2
    encoding: utf8
    reconnect: false
    database: app2_development
    pool: 5
    username:
    password:
    socket: /tmp/mysql.sock
    host: 127.0.0.1&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ol start=&quot;2&quot;&gt;
  &lt;li&gt;&lt;code&gt;bin/rake db:create&lt;/code&gt; &lt;em&gt;create databases for both the tenants.&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;You have the option to execute specific rake commands for each database. For instance, you can create the &lt;code&gt;app1&lt;/code&gt; database using the command: &lt;code&gt;bin/rake db:create:app1&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Generate Models and Controller&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Model&lt;/p&gt;

    &lt;p&gt;&lt;code&gt;bin/rails generate model Article title:string body:text&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Run migrations&lt;/p&gt;

    &lt;p&gt;&lt;code&gt;bin/rake db:migrate&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Controller&lt;/p&gt;

    &lt;p&gt;&lt;code&gt;bin/rails generate controller Articles index --skip-routes&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;update &lt;code&gt;routes.rb&lt;/code&gt; file.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;root &amp;quot;articles#index&amp;quot;
resources :articles&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Complete the &lt;code&gt;Articles&lt;/code&gt; Controller, Model and respective views by following &lt;a href=&quot;https://guides.rubyonrails.org/getting_started.html&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;This Guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start App&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Run &lt;code&gt;bin/rails s&lt;/code&gt; to start the server.&lt;/li&gt;
  &lt;li&gt;By default rails will connect to db1 now.&lt;/li&gt;
  &lt;li&gt;This will act as a default database for the current application.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Running up both databases simaltaneously&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Install nginx &amp;amp; paste the following code in nginx.conf file.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;http {
  server {
   listen 3000;
   server_name localhost;

   location / {
        proxy_pass http://127.0.0.1:3000; # Rails app running on port 3000
        proxy_set_header Host $host:$server_port;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
  }

  server {
    listen 4000;
    server_name localhost; # Change this to your actual domain if needed

    location / {
        proxy_pass http://127.0.0.1:3000; # Rails app running on port 3000
        proxy_set_header Host $host:$server_port;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
  }
}
events { }&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Above nginx configurations listens to port 3000 and 4000 and redirect to rails application running in port 3000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additional Rails changes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since We are using Rails 7 we can use automatic shard swap feature provided by rails. if using rails 6.1 or 6, a middleware can be introduced to automatic switch the tenants depending on request. Visit next section for the details.&lt;/p&gt;

&lt;p&gt;Mention list of tenants in a &lt;code&gt;.yml&lt;/code&gt; file. You can maintain these records in a separate database as well, for now I will create a &lt;code&gt;settings.yml&lt;/code&gt; file.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;development:
  tenants:
    app1:
      hosts:
        - localhost:3000
    app2:
      hosts:
        - localhost:4000&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;update &lt;code&gt;application.rb&lt;/code&gt; with following configurations.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;Rails.application.configure do
  config.active_record.shard_selector = { lock: true }

  tenants = Rails.application.config_for(:settings)[:tenants]  # maintaining list of tenants with host
  config.active_record.shard_resolver = -&amp;gt;(request) {
    tenants.keys.find { |key| tenants[key][:hosts].include?(request.env[&amp;#39;HTTP_HOST&amp;#39;]) } || :app1
  }
end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;update &lt;code&gt;application_record.rb&lt;/code&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;# connects_to shards: {
   #  app1: { writing: :app1 },
   #  app2: { writing: :app2 }
   # }
   # OR

  TENANTS = Rails.application.config_for(:settings)[:tenants]
  connects_to TENANTS.keys.map { |shard| [shard, { writing: shard }] }.to_h&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h5 id=&quot;creating-middleware-for-automatic-shard-switchingignore-if-using-rails-7-or-above&quot;&gt;&lt;strong&gt;Creating Middleware for automatic shard switching(ignore if using rails 7 or above)&lt;/strong&gt;&lt;/h5&gt;
&lt;ol&gt;
  &lt;li&gt;Create a middleware named &lt;code&gt;middleware/tenant_selector.rb&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Add following code&lt;/li&gt;
&lt;/ol&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;module Middleware
    class TenantSelector
      def initialize(app, tenants)
        @app = app
        @tenants = tenants
      end

      attr_reader :tenants

      def call(env)
        request = ActionDispatch::Request.new(env)
        tenant = selected_tenant(request)

        set_tenant(tenant) do
          @app.call(env)
        end
      end

      private
      def selected_tenant(request)
        tenants.keys.find { |key| tenants[key][:hosts].include?(request.env[&amp;#39;HTTP_HOST&amp;#39;]) } || :app1
      end

      def set_tenant(tenant, &amp;amp;block)
        ActiveRecord::Base.connected_to(shard: tenant.to_sym, role: :writing) do
          yield
        end
      end
    end
  end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ol start=&quot;3&quot;&gt;
  &lt;li&gt;Update &lt;code&gt;application.rb&lt;/code&gt; file with following changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;tenants = Rails.application.config_for(:settings)[:tenants]
    config.app_middleware.use Middleware::TenantSelector, tenants&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Final Steps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Follow these final steps to confirm your multi-tenant Rails application is up and running smoothly:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Run &lt;code&gt;bin/rails s&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Access localhost:3000 to connect to db1&lt;/li&gt;
  &lt;li&gt;Access localhost:4000 to connect to db2&lt;/li&gt;
  &lt;li&gt;If you wish to add more databases, simply update the &lt;code&gt;database.yml&lt;/code&gt; and &lt;code&gt;settings.yml&lt;/code&gt; files&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What Next?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the upcoming series of blog posts, we will delve into the following topics:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Maintaining Background Jobs.&lt;/li&gt;
  &lt;li&gt;Running Rake Tasks with Cron Jobs for Multiple Databases.&lt;/li&gt;
  &lt;li&gt;ActiveStorage Data Management with Different Storage Types for Each Tenant.&lt;/li&gt;
  &lt;li&gt;Caching.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;summary&quot;&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;In this blog post we covered creating a multi tenant application from scratch and setting it up in development environment. We were able to automatically switch databases according to type of database.&lt;/p&gt;

&lt;h4 id=&quot;references&quot;&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/h4&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/nikhilbhatt/rails-multi-db-tutorial/releases/tag/0.0.0&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Github Code&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://guides.rubyonrails.org/active_record_multiple_databases.html&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Rails Multi Db introduction&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

  
  &lt;p&gt;&lt;a href=&quot;/technology/mastering-multi-tenant-setup-with-rails-part-1/&quot;&gt;Mastering Multi Tenant setup with rails part 1&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on December 17, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[An in-depth look at Database Indexing]]></title>
  <link rel="alternate" type="text/html" href="/technology/an-in-depth-look-at-database-indexing/"/>
  <id>/technology/an-in-depth-look-at-database-indexing</id>
  <updated>2023-12-10 15:49:39 +0530T00:00:00-00:00</updated>
  <published>2023-12-10T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#database" term="database" /><category scheme="/tags/#indexing" term="indexing" /><category scheme="/tags/#postgres" term="postgres" /><category scheme="/tags/#mysql" term="mysql" /><category scheme="/tags/#relational%20database" term="relational database" /><category scheme="/tags/#clustered%20index" term="clustered index" /><category scheme="/tags/#primary%20key%20index" term="primary key index" /><category scheme="/tags/#explain" term="explain" /><category scheme="/tags/#analyze" term="analyze" /><category scheme="/tags/#postgres%20indexing" term="postgres indexing" />
  <content type="html">
  
    &lt;p&gt;In this article, we will explore Database Indexing. We will begin by installing the Docker &amp;amp; running a Postgres container on it. Subsequently, to execute queries and comprehend how the database uses various indexing strategies, we will insert millions of rows into a Postgres table.&lt;/p&gt;

&lt;p&gt;Following that, we will explore different tools to gaining insights into the SQL query planner and optimizers. After that, we will delve into understanding database indexing, examining how various types of indexing works with examples, and do a comparison between different types of database scan strategies.&lt;/p&gt;

&lt;p&gt;Finally, we will then demystify how database indexes operates for the WHERE clause with the AND and OR operators.&lt;/p&gt;

&lt;h4 id=&quot;prerequisite&quot;&gt;&lt;strong&gt;Prerequisite&lt;/strong&gt;&lt;/h4&gt;

&lt;ol style=&quot;margin-left: 2rem&quot;&gt;
	&lt;li&gt;
		&lt;b&gt;Installing Docker &amp;amp; Running a Postgres Container:&lt;/b&gt;
		&lt;ol style=&quot;margin-left: 2rem&quot;&gt;
			&lt;li&gt;
				Install Docker by following the instructions provided in the &lt;a href=&quot;https://www.docker.com/get-started/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;getting started&lt;/a&gt; guide on the official Docker website.
			&lt;/li&gt;
			&lt;li&gt;
				Verify that Docker is installed by running the command &lt;code&gt;docker --version&lt;/code&gt;
			&lt;/li&gt;
		&lt;/ol&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;b&gt;
			Running a PostgresSQL Container:
		&lt;/b&gt;
			&lt;ol style=&quot;margin-left: 2rem&quot;&gt;
			&lt;li&gt;
				Spin up the Docker container by using the official Postgres &lt;a href=&quot;https://hub.docker.com/_/postgres&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;image&lt;/a&gt;.

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;docker run -e POSTGRES_PASSWORD=secret --name pg postgres&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

			&lt;/li&gt;
			&lt;li&gt;
				Start the Postgres command shell:

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;docker exec -it pg psql -U postgres&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

			&lt;/li&gt;
		&lt;/ol&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;b&gt;
			Inserting a Million Rows into a Postgres Table:
		&lt;/b&gt;
		&lt;ol style=&quot;margin-left: 2rem&quot;&gt;
			&lt;li&gt;
				Create a table named &lt;code&gt;employees&lt;/code&gt;:

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;create table employees(id serial primary key, employeeid integer, name TEXT);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

			&lt;/li&gt;
			&lt;li&gt;
				Insert into the &lt;code&gt;employees&lt;/code&gt; table using the &lt;a href=&quot;https://www.postgresql.org/docs/current/functions-srf.html&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;generate_series&lt;/a&gt; function:

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;create or replace function gen_random_string(length integer)
RETURNS VARCHAR as
$$
DECLARE
result VARCHAR := &amp;#39;&amp;#39;;
BEGIN
FOR i IN 1..length LOOP
	result := result || chr((floor(random() * 26) + 65)::integer);
END LOOP;
RETURN result;
END;
$$ language plpgsql;

INSERT INTO EMPLOYEES(employeeid, name)
SELECT *, gen_random_string((random() * 10 + 5)::integer)
FROM generate_series(0, 1000000);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

			&lt;/li&gt;
			&lt;li&gt;
				Confirm the result by executing the &lt;code&gt;count&lt;/code&gt; query:

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;select count(*) from employees;
count
--------
	1000001
(1 row)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

			&lt;/li&gt;
		&lt;/ol&gt;
		This sequence of steps creates a table named &lt;code&gt;employees&lt;/code&gt; and inserts one million rows into it, generating random values for the &lt;code&gt;employeeid&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt; columns. The final count query verifies the successful insertion of the specified number of rows.
	&lt;/li&gt;


	&lt;li&gt;
		&lt;b&gt;The SQL Query Planner and Optimizer:&lt;/b&gt;
		&lt;ul style=&quot;margin-left: 2rem&quot;&gt;
			&lt;li&gt;
				&lt;b&gt;Explanation:&lt;/b&gt;
					The &lt;code&gt;explain&lt;/code&gt; command displays the execution plan generated by the PostgresSQL planner for the provided statement. This plan illustrates how the table(s) referenced in the statement will be scanned, whether through plain sequential scans, index scans, etc.
			&lt;/li&gt;
			&lt;li&gt;
				&lt;b&gt;Examples:&lt;/b&gt;
				&lt;ol style=&quot;margin-left: 2rem&quot;&gt;
					&lt;li&gt;
						&lt;b&gt;Select All Query:&lt;/b&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# explain select * from employees;
QUERY PLAN
-------------------------------------------------------------
Seq Scan on employees (cost=0.00..16139.01 rows=1000001 width=19)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

						&lt;ul style=&quot;margin-left: 2rem&quot;&gt;
							&lt;li&gt;
								&lt;code&gt;Seq Scan&lt;/code&gt;: Directly goes to the heap and fetches everything, similar to a Full Table Scan in other databases. In Postgres, with multiple threads, it&apos;s called &lt;code&gt;Parallel Seq Scan&lt;/code&gt;.
							&lt;/li&gt;

							&lt;li&gt;
								&lt;code&gt;Cost=0.00..16139.01&lt;/code&gt;: The first number represents work before fetching (e.g., aggregating, ordering), and the second number is the total estimated execution time.
							&lt;/li&gt;
							&lt;li&gt;
								&lt;code&gt;rows=1000001&lt;/code&gt;: An approximate number of rows to be fetched.
							&lt;/li&gt;
							&lt;li&gt;
								&lt;code&gt;width=19&lt;/code&gt;: The sum of bytes for all columns.
							&lt;/li&gt;						
						&lt;/ul&gt;
					&lt;/li&gt;
					&lt;br /&gt;
					&lt;li&gt;
      			&lt;b&gt;Select All Query with Order By (Indexed Column):&lt;/b&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# create index employees_employeeid_idx ON employees(employeeid);
CREATE INDEX
postgres=# explain select * from employees order by employeeid;
QUERY PLAN
-----------------------------------------------------------------
Index Scan using employees_employeeid_idx on employees (cost=0.42..32122.44 rows=1000001 width=19)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

						&lt;ul style=&quot;margin-left: 2rem&quot;&gt;
							&lt;li&gt;
         				&lt;code&gt;cost=0.42&lt;/code&gt;: Postgres performs work, ordering by &lt;code&gt;employeeid&lt;/code&gt;. An index on &lt;code&gt;employeeid&lt;/code&gt; leads to an &lt;code&gt;Index Scan&lt;/code&gt;.
							&lt;/li&gt;
						&lt;/ul&gt;
					&lt;/li&gt;
					&lt;br /&gt;
					&lt;li&gt;
						&lt;b&gt;Select All Query with Order By (Non-Indexed Column):&lt;/b&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# explain select * from employees order by name;
QUERY PLAN
--------------------------------------------------------------
Sort (cost=136306.96..138806.96 rows=1000001 width=19)
	Sort Key: name
	-&amp;gt; Seq Scan on employees (cost=0.00..16139.01 rows=1000001 width=19)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

						&lt;ul style=&quot;margin-left: 2rem&quot;&gt;
							&lt;li&gt;
								&lt;code&gt;Seq Scan &amp;amp; Sort&lt;/code&gt;:  Seq Scan on the table, followed by sorting. Sorting cost is critical.
							&lt;/li&gt;
						&lt;/ul&gt;
					&lt;/li&gt;
					&lt;br /&gt;
					&lt;li&gt;
						&lt;b&gt;Select Only ID:&lt;/b&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# explain select id from employees;
QUERY PLAN
---------------------------------------------------
Seq Scan on employees (cost=0.00..16139.01 rows=1000001 width=4)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

						&lt;ul style=&quot;margin-left: 2rem&quot;&gt;
							&lt;li&gt;
								&lt;code&gt;width=4&lt;/code&gt;: Fetching only &lt;code&gt;id&lt;/code&gt;, resulting in a smaller &lt;code&gt;width&lt;/code&gt; of 4 bytes (integer).
							&lt;/li&gt;
						&lt;/ul&gt;
					&lt;/li&gt;
					&lt;br /&gt;
					&lt;li&gt;
						&lt;b&gt;Select All Query for a Particular ID:&lt;/b&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# explain select * from employees where id = 10;
QUERY PLAN
-------------------------------------------------------------------
Index Scan using employees_pkey on employees (cost=0.42..8.44 rows=1 width=19)
	Index Cond: (id = 10)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

						&lt;ul style=&quot;margin-left: 2rem&quot;&gt;
							&lt;li&gt;
							&lt;code&gt;rows=1&lt;/code&gt;: Fetching only 1 record using the primary key index.
							&lt;/li&gt;
						&lt;/ul&gt;
					&lt;/li&gt;
				&lt;/ol&gt;
			&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;what-is-database-indexing&quot;&gt;&lt;strong&gt;What is Database indexing?&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;An index is a data structure that speeds up data retrieval without needing to scan every row present in the table. Index improves lookup performance but decreases write performance because every time a new row is created, indexes need to be updated.&lt;/p&gt;

&lt;p&gt;Indexes are typically stored on the disk. An index is typically a small table with two columns: a primary/candidate key and address. Keys are made from one or more columns.&lt;/p&gt;

&lt;p&gt;The data structure used for storing the index is B+ Trees. In the simplest form, an index is a stored table of key-value pairs that allows searches to be conducted in &lt;code&gt;O(logn)&lt;/code&gt; time using binary search on sorted data.&lt;/p&gt;

&lt;p&gt;Types of Indexes:&lt;/p&gt;

&lt;ul style=&quot;margin-left: 2rem&quot;&gt;
	&lt;li&gt;
		Clustered Index
		&lt;ul style=&quot;margin-left: 2rem&quot;&gt;
			&lt;li&gt;Index and data reside together and are ordered by the key. A Clustered Index is basically a tree-organized table. Instead of storing the records in an unsorted Heap table space, the clustered index is actually B+Tree index having the Leaf Nodes, which are ordered by the clusters key column value, store the actual table records, as illustrated by the following diagram.&lt;/li&gt;
		&lt;/ul&gt;
		&lt;img src=&quot;/blog/images/database-indexing/clustered-index.png&quot; width=&quot;425&quot; /&gt;
	&lt;/li&gt;
		&lt;li&gt;
		Nonclustered Index
		&lt;ul style=&quot;margin-left: 2rem&quot;&gt;
			&lt;li&gt;A nonclustered index contains the key values and each key value entry has a pointer to the data row that contains the key value. Since the Clustered Index is usually built using the Primary Key column values, if you want to speed up queries that use some other column, then you&apos;ll have to add a Secondary Non-Clustered Index.

			The Secondary Index is going to store the Primary Key value in its Leaf Nodes, as illustrated by the following diagram
			&lt;/li&gt;
		&lt;/ul&gt;
		&lt;img src=&quot;/blog/images/database-indexing/nonclustered-index.png&quot; width=&quot;425&quot; /&gt;
	&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;how-database-indexes-works-under-the-hood&quot;&gt;&lt;strong&gt;How database indexes works under the hood?&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;We have already created a database index on the &lt;code&gt;employeeid&lt;/code&gt; column in our employees table using the &lt;code&gt;CREATE INDEX&lt;/code&gt; statement. Behind the scenes, Postgres creates a new pseudo-table in the database with two columns: a value for &lt;code&gt;employeeid&lt;/code&gt; and a pointer to the corresponding record in the &lt;code&gt;employees&lt;/code&gt; table. This pseudo-table is organized and stored as a binary tree with ordered values for the &lt;code&gt;employeeid&lt;/code&gt; column. Consequently, the query operates with O(logn) efficiency and typically executes in a second or less.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/database-indexing/indexing-structure.png&quot; width=&quot;425&quot; /&gt;
Let’s delve into two scenarios:&lt;/p&gt;

&lt;ol style=&quot;margin-left: 2rem&quot;&gt;
	&lt;li&gt;
		&lt;b&gt;&lt;code&gt;SELECT * FROM employees WHERE employeeid = 4;&lt;/code&gt;&lt;/b&gt;
		&lt;br /&gt;
		Here, with an index on the &lt;code&gt;employeeid&lt;/code&gt; column, the query initiates an &lt;code&gt;Index Scan&lt;/code&gt;. The process begins by accessing the Index table, retrieving the reference for the Page number, and obtaining the row number for the specific record on that page. Subsequently, it navigates to the corresponding page in the heap and fetches the entire row. This method, known as an &lt;code&gt;Index Scan&lt;/code&gt;.
	&lt;/li&gt;
	&lt;li&gt;
		&lt;b&gt;&lt;code&gt;SELECT employeeid FROM employees WHERE employeeid = 4;&lt;/code&gt;&lt;/b&gt;&lt;br /&gt;
		In this instance, there is no need to access the heap to retrieve the complete record. Since the required value for &lt;code&gt;employeeid&lt;/code&gt; is already present in the index table, the operation is streamlined, and it directly performs an &lt;code&gt;Index Only Scan&lt;/code&gt;. This approach allows the system to retrieve the specific &lt;code&gt;employeeid&lt;/code&gt; directly from the index table without the additional step of fetching the complete row from the heap. This can lead to improved performance, particularly when the index includes all the columns needed for the query, minimizing the amount of data that needs to be processed.
	&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;what-are-different-type-of-database-scan-strategies&quot;&gt;&lt;strong&gt;What are different type of database scan strategies?&lt;/strong&gt;&lt;/h4&gt;

&lt;ol style=&quot;margin-left: 2rem&quot;&gt;
	&lt;li&gt;
		&lt;b&gt;Index Only Scan&lt;/b&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# EXPLAIN ANALYZE select id from employees where id = 100;
							QUERY PLAN
-----------------------------------------------------------------------------
 Index Only Scan using employees_pkey on employees  (cost=0.42..4.44 rows=1 width=4) (actual time=2.529..2.542 rows=1 loops=1)
   Index Cond: (id = 100)
   Heap Fetches: 0
 Planning Time: 0.510 ms
 Execution Time: 2.708 ms&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

		If we examine the given query, we retrieve the ID using a filter on the ID column, which serves as the primary key and has an index on it.

		&lt;img src=&quot;/blog/images/database-indexing/index-only-scan.png&quot; width=&quot;425&quot; /&gt;

		Let&apos;s break down the query output:
		&lt;ol style=&quot;margin-left: 2rem&quot;&gt;
			&lt;li&gt;
				&lt;b&gt;&lt;code&gt;Index Only Scan:&lt;/code&gt;&lt;/b&gt; In the case of an &lt;code&gt;Index Only Scan&lt;/code&gt;, Postgres scans the index table, resulting in faster performance as the Index table is significantly smaller than the actual table. With &lt;code&gt;Index Only Scan&lt;/code&gt;, results are directly fetched from the Index table when querying columns for which indexes have been created.
			&lt;/li&gt;
			&lt;li&gt;
				&lt;b&gt;&lt;code&gt;Heap Fetches: 0:&lt;/code&gt;&lt;/b&gt; This indicates that the queried ID value did not necessitate accessing the heap table to retrieve information. The information was obtained inline, and this is referred to as an Inline query.
			&lt;/li&gt;
			&lt;li&gt;
				&lt;b&gt;&lt;code&gt;Planning Time: 0.510 ms:&lt;/code&gt;&lt;/b&gt; This represents the time taken by Postgres to determine whether to use the index or perform a full table scan.
			&lt;/li&gt;
			&lt;li&gt;
				&lt;b&gt;&lt;code&gt;Execution Time: 2.708 ms:&lt;/code&gt;&lt;/b&gt; This is the time taken by Postgres to actually fetch the records from the table.
			&lt;/li&gt;
		&lt;/ol&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;b&gt;Index Scan&lt;/b&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# EXPLAIN ANALYZE select name from employees where id = 1000;
								QUERY PLAN
----------------------------------------------------------------------------
 Index Scan using employees_pkey on employees  (cost=0.42..8.44 rows=1 width=11) (actual time=1.250..1.260 rows=1 loops=1)
   Index Cond: (id = 1000)
 Planning Time: 0.703 ms
 Execution Time: 1.655 ms&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


		If we examine the given query, we are retrieving the &lt;code&gt;name&lt;/code&gt; using a filter on the ID column, which serves as the primary key and has an index on it.

		In this case, the process begins with an index scan on the Index table to retrieve information about the Page number and row number on the Heap. Since the &lt;code&gt;name&lt;/code&gt; is not available in the Index table, we must go to the heap to fetch the &lt;code&gt;name&lt;/code&gt;. This type of scan is referred to as an &lt;code&gt;Index Scan&lt;/code&gt;.
		&lt;img src=&quot;/blog/images/database-indexing/index-scan.png&quot; width=&quot;425&quot; /&gt;	


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# EXPLAIN ANALYZE select name from employees where id &amp;lt; 1000;
                      QUERY PLAN
----------------------------------------------------------------------------------------------------------
 Index Scan using employees_pkey on employees  (cost=0.42..40.75 rows=1047 width=32) (actual time=0.062..1.139 rows=999 loops=1)
   Index Cond: (id &amp;lt; 1000)
 Planning Time: 4.948 ms
 Execution Time: 1.215 ms
(4 rows)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


		In this case, we are filtering the record using the filter on the id with &apos;&amp;lt;&apos; operator and filtering out record which have id less than 1000. So the process begins with an Index scanning on the Index table then fetching the rows from the heap. Same as in case of fetching single id.
		&lt;img src=&quot;/blog/images/database-indexing/index-scan.png&quot; width=&quot;425&quot; /&gt;	


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# EXPLAIN ANALYZE select name from employees where id &amp;gt; 1000;
                    QUERY PLAN
---------------------------------------------------------------------------
 Seq Scan on employees  (cost=0.00..18639.01 rows=998953 width=32) (actual time=0.104..168.884 rows=999001 loops=1)
   Filter: (id &amp;gt; 1000)
   Rows Removed by Filter: 1000
 Planning Time: 0.158 ms
 Execution Time: 198.259 ms
(5 rows)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


		In this case, we are filtering the record using the filter on the id with &apos;&amp;gt;&apos; operator and filtering out records which have id greater than 1000. So, in this case, as Postgres knows it has to fetch 99% of the data anyway, it prefers to use the Seq Scan on the heap table. Rather than going to the Index table to filter out the records and then again going to the heap to filter those Index-scanned rows.

		&lt;img src=&quot;/blog/images/database-indexing/seq-scan.png&quot; width=&quot;425&quot; /&gt;	
	&lt;/li&gt;
	&lt;li&gt;
		&lt;b&gt;Parallel Seq Scan&lt;/b&gt;


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# EXPLAIN ANALYZE select id from employees where name = &amp;#39;WABOY&amp;#39;;
								QUERY PLAN
----------------------------------------------------------------------------
 Gather  (cost=1000.00..12347.44 rows=1 width=4) (actual time=3.970..120.383 rows=1 loops=1)
   Workers Planned: 2
   Workers Launched: 2
   -&amp;gt;  Parallel Seq Scan on employees  (cost=0.00..11347.34 rows=1 width=4) (actual time=64.894..102.448 rows=0 loops=3)
		 Filter: ((name)::text = &amp;#39;WABOY&amp;#39;::text)
		 Rows Removed by Filter: 333333
 Planning Time: 0.898 ms
 Execution Time: 120.850 ms
(8 rows)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


		If we examine the given query, we are retrieving the &lt;code&gt;id&lt;/code&gt; using a filter on the name column, which doesn&apos;t have an index on it.

		As we don&apos;t have an index on the name column, that means we have to actually search for the name &lt;code&gt;WABOY&lt;/code&gt; one by one and perform a sequential scan on the employees table. Postgres efficiently addresses this by executing multiple worker threads and conducting a parallel sequential scan.
		&lt;img src=&quot;/blog/images/database-indexing/parallel-seq-scan.png&quot; width=&quot;425&quot; /&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;b&gt;Bitmap Scan&lt;/b&gt;
		&lt;br /&gt;
		Let&apos;s create a Bitmap Index on the name column to get started.


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# CREATE INDEX employees_name_idx ON employees(name);
CREATE INDEX&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

		Let&apos;s explore how Bitmap Scan works in PostgreSQL.
		&lt;br /&gt;&lt;br /&gt;
		Heap pages are stored on disk, and loading a page into memory can be expensive. When using an Index Scan, if the query yields a large number of rows, the query&apos;s performance may suffer because each row&apos;s retrieval involves loading a page into memory.
		&lt;br /&gt;
		In contrast, with a Bitmap Scan, instead of loading rows into memory, we set a bit to 1 in an array of bits corresponding to heap page numbers. The operation then works on top of this bitmap.
		&lt;br /&gt;
		&lt;img src=&quot;/blog/images/database-indexing/bitmap-or-example.png&quot; width=&quot;425&quot; style=&quot;margin-top: 2rem; margin-bottom: 2rem&quot; /&gt;

		Here&apos;s a simplified breakdown of above image:
		&lt;ul style=&quot;margin-left: 2rem&quot;&gt;
			&lt;li&gt;
				In a bitmap index scan, rows are not loaded into memory. PostgreSQL sets the bit to 1 for page number 1 when the name is &apos;CD&apos; and 0 for other pages.
			&lt;/li&gt;
			&lt;li&gt;
				When the name is &apos;BC&apos;, page number 2 is set to 1, and others are set to 0.
			&lt;/li&gt;
			&lt;li&gt;
				Subsequently, a new bitmap is created by performing an OR operation on both bitmaps.
			&lt;/li&gt;
			&lt;li&gt;
				Finally, PostgreSQL executes a Bitmap Heap Scan where it fully scans each heap page and rechecks the conditions.
			&lt;/li&gt;
		&lt;/ul&gt;
		&lt;br /&gt;
		This approach minimizes the need to load entire pages into memory for individual rows, improving the efficiency of the query. If the query results in a lot of rows located in only a limited number of heap pages then this strategy will be very efficient.
		&lt;br /&gt;
		&lt;br /&gt;
		Now let&apos;s filter out the id, name by the name


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# EXPLAIN ANALYZE select id, name from employees where name = &amp;#39;WABOY&amp;#39;;
                  QUERY PLAN
--------------------------------------------------------------------------------
 Bitmap Heap Scan on employees  (cost=111.17..6277.29 rows=5000 width=36) (actual time=0.348..0.369 rows=1 loops=1)
   Recheck Cond: (name = &amp;#39;WABOY&amp;#39;::text)
   Heap Blocks: exact=1
   -&amp;gt;  Bitmap Index Scan on employees_name_idx  (cost=0.00..109.92 rows=5000 width=0) (actual time=0.274..0.274 rows=1 loops=1)
         Index Cond: (name = &amp;#39;WABOY&amp;#39;::text)
 Planning Time: 0.905 ms
 Execution Time: 0.734 ms&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

		Upon analyzing the provided query, we extract the &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt; by applying a filter on the &lt;code&gt;name&lt;/code&gt; column, which has an index.
		&lt;img src=&quot;/blog/images/database-indexing/bitmap-scan.png&quot; width=&quot;425&quot; /&gt;
		Let&apos;s clarify the process:
		&lt;ol style=&quot;margin-left: 2rem&quot;&gt;
			&lt;li&gt;
				&lt;code&gt;Bitmap Index Scan&lt;/code&gt;: This step involves scanning the index table for the &lt;code&gt;name&lt;/code&gt; column since an index exists on it. It retrieves the page number and row number to obtain references to the corresponding records in the heap.
			&lt;/li&gt;
			&lt;li&gt;
			&lt;code&gt;Bitmap Heap Scan&lt;/code&gt;: Since we are filtering based on both &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt;, this step is necessary to visit the heap and retrieve the values for both attributes for a specific record. The reference to the record is obtained from the preceding &lt;code&gt;Bitmap Index Scan&lt;/code&gt;.
			&lt;/li&gt;
		&lt;/ol&gt;	
	&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;combining-database-indexes&quot;&gt;&lt;strong&gt;Combining Database Indexes&lt;/strong&gt;&lt;/h4&gt;

&lt;ul style=&quot;margin-left: 2rem&quot;&gt;
	&lt;li&gt;
		Prerequisite: Let&apos;s create a table to learn how to combine indexes.


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;CREATE TABLE NUMBERS(id serial primary key, a integer, b integer, c integer);
INSERT INTO NUMBERS(a, b, c) select (random() * 100)::integer, (random() * 1000)::integer, (random() * 2000)::integer from generate_series(0, 10000000);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

	&lt;/li&gt;
	&lt;li&gt;
		Now let&apos;s create index on the columns &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;B&lt;/code&gt;.


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;CREATE INDEX numbers_a_idx on numbers(a);
CREATE INDEX numbers_b_idx on numbers(b);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

	&lt;/li&gt;
&lt;/ul&gt;

&lt;ol style=&quot;margin-left: 2rem&quot;&gt;
	&lt;li&gt;
	&lt;b&gt;Select column &lt;code&gt;c&lt;/code&gt; for a particular value of column &lt;code&gt;a&lt;/code&gt;&lt;/b&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# EXPLAIN ANALYZE SELECT c FROM numbers WHERE a = 88;
                  QUERY PLAN

---------------------------------------------------------------------------------
 Bitmap Heap Scan on numbers  (cost=1101.09..57496.05 rows=98665 width=4) (actual time=41.110..683.631 rows=99888 loops=1)
   Recheck Cond: (a = 88)
   Heap Blocks: exact=45619
   -&amp;gt;  Bitmap Index Scan on numbers_a_idx  (cost=0.00..1076.42 rows=98665 width=0) (actual time=29.403..29.403 rows=99888 loops=1)
         Index Cond: (a = 88)
 Planning Time: 1.569 ms
 Execution Time: 687.152 ms&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


		Here, we can analyze that since we have an index only on column &lt;code&gt;a&lt;/code&gt;, a bitmap index scan is performed on column &lt;code&gt;a&lt;/code&gt;. To retrieve column &lt;code&gt;c&lt;/code&gt;, it jumps to the heap and performs a bitmap heap scan.
	&lt;/li&gt;
	&lt;br /&gt;
	&lt;li&gt;
		&lt;b&gt;Select column c but we are going to query on both &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; with &lt;code&gt;AND&lt;/code&gt; operation&lt;/b&gt;


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# EXPLAIN ANALYZE SELECT c FROM numbers WHERE a = 90 AND b = 500;
                  QUERY PLAN
-----------------------------------------------------------------------------
 Bitmap Heap Scan on numbers  (cost=1320.12..1746.88 rows=110 width=4) (actual time=32.300..38.262 rows=107 loops=1)
   Recheck Cond: ((b = 500) AND (a = 90))
   Heap Blocks: exact=107
   -&amp;gt;  BitmapAnd  (cost=1320.12..1320.12 rows=110 width=0) (actual time=32.079..32.081 rows=0 loops=1)
         -&amp;gt;  Bitmap Index Scan on numbers_b_idx  (cost=0.00..110.88 rows=9926 width=0) (actual time=4.494..4.494 rows=9974 loops=1
)
               Index Cond: (b = 500)
         -&amp;gt;  Bitmap Index Scan on numbers_a_idx  (cost=0.00..1208.93 rows=111000 width=0) (actual time=26.799..26.800 rows=99868 l
oops=1)
               Index Cond: (a = 90)
 Planning Time: 3.362 ms
 Execution Time: 38.604 ms&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


		Here, we can analyze the following:
		&lt;ol style=&quot;margin-left: 2rem&quot;&gt;
			&lt;li&gt;PostgreSQL executed a bitmap index scan on column &apos;A&apos;.&lt;/li&gt;
			&lt;li&gt;Concurrently, a bitmap index scan was performed on column &apos;B&apos;.&lt;/li&gt;
			&lt;li&gt;Subsequently, PostgreSQL executed a bitmap AND operation to combine the results of the scans on &apos;A&apos; and &apos;B&apos;.&lt;/li&gt;
			&lt;li&gt;After obtaining the references for the rows to be retrieved, PostgreSQL proceeds to perform a bitmap heap scan.&lt;/li&gt;
		&lt;/ol&gt;
	&lt;/li&gt;
	&lt;br /&gt;
	&lt;li&gt;
		&lt;b&gt;Select column c but we are going to query on both a and b with &lt;code&gt;OR&lt;/code&gt; operation.&lt;/b&gt;


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# EXPLAIN ANALYZE SELECT c FROM numbers WHERE A = 50 OR B = 500;
                QUERY PLAN

-------------------------------------------------------------------------------
 Bitmap Heap Scan on numbers  (cost=1164.23..57490.68 rows=101835 width=4) (actual time=37.957..600.439 rows=109466 loops=1)
   Recheck Cond: ((a = 50) OR (b = 500))
   Heap Blocks: exact=46998
   -&amp;gt;  BitmapOr  (cost=1164.23..1164.23 rows=101926 width=0) (actual time=25.625..25.626 rows=0 loops=1)
         -&amp;gt;  Bitmap Index Scan on numbers_a_idx  (cost=0.00..1002.43 rows=92000 width=0) (actual time=24.309..24.309 rows=99602 lo
ops=1)
               Index Cond: (a = 50)
         -&amp;gt;  Bitmap Index Scan on numbers_b_idx  (cost=0.00..110.88 rows=9926 width=0) (actual time=1.313..1.314 rows=9974 loops=1
)
               Index Cond: (b = 500)
 Planning Time: 1.135 ms
 Execution Time: 604.165 ms&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


		Here, we can analyze the following:
		&lt;ol style=&quot;margin-left: 2rem&quot;&gt;
			&lt;li&gt;PostgreSQL executed a bitmap index scan on column &lt;code&gt;a&lt;/code&gt;.&lt;/li&gt;
			&lt;li&gt;Concurrently, a bitmap index scan was performed on column &lt;code&gt;b&lt;/code&gt;.&lt;/li&gt;
			&lt;li&gt;Subsequently, PostgreSQL executed a &lt;code&gt;BitmapOr&lt;/code&gt; operation to combine the results of the scans on columns &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;.&lt;/li&gt;
			&lt;li&gt;After obtaining the references for the rows to be retrieved, PostgreSQL proceeds to perform a bitmap heap scan.&lt;/li&gt;
		&lt;/ol&gt;
	&lt;/li&gt;
&lt;/ol&gt;

&lt;ul style=&quot;margin-left: 2rem&quot;&gt;
	&lt;li&gt;&lt;b&gt;Composite Index&lt;/b&gt;&lt;/li&gt;
	&lt;li style=&quot;margin-left: 2rem&quot;&gt;
		First, we need to drop the indexes on both columns &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;, and then create a composite index on columns &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;.


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# CREATE INDEX numbers_a_b_idx on numbers(a, b);
CREATE INDEX&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

	&lt;/li&gt;
	&lt;ol style=&quot;margin-left: 2rem&quot;&gt;
		&lt;li&gt;
			&lt;b&gt;Select column &lt;code&gt;c&lt;/code&gt; for a particular value of column &lt;code&gt;a&lt;/code&gt;&lt;/b&gt;


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# EXPLAIN ANALYZE SELECT c FROM numbers WHERE a = 70;
							QUERY PLAN

-----------------------------------------------------------------------
Bitmap Heap Scan on numbers  (cost=1189.93..56830.03 rows=106000 width=4) (actual time=38.779..610.173 rows=99789 loops=1)
	Recheck Cond: (a = 70)
	Heap Blocks: exact=45549
	-&amp;gt;  Bitmap Index Scan on numbers_a_b_idx  (cost=0.00..1163.43 rows=106000 width=0) (actual time=27.796..27.797 rows=99789 loops
=1)
		Index Cond: (a = 70)
Planning Time: 5.188 ms
Execution Time: 613.305 ms
(7 rows)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

		
			Here, we can analyze the following:
			&lt;ol style=&quot;margin-left: 2rem&quot;&gt;
				&lt;li&gt;This time, PostgreSQL decided to use the composite index &lt;code&gt;numbers_ab_idx&lt;/code&gt; on both columns &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;.&lt;/li&gt;
				&lt;li&gt;Subsequently, it performs a Bitmap Heap Scan on the selected rows based on the composite index.&lt;/li&gt;
			&lt;/ol&gt;
		&lt;/li&gt;
		&lt;br /&gt;
		&lt;li&gt;
			&lt;b&gt;Select column &lt;code&gt;c&lt;/code&gt; for a particular value of column &lt;code&gt;b&lt;/code&gt;&lt;/b&gt;
			

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# EXPLAIN ANALYZE SELECT c FROM numbers WHERE b = 900;
							QUERY PLAN
-----------------------------------------------------------------------
Gather  (cost=1000.00..108130.94 rows=9926 width=4) (actual time=24.402..395.326 rows=10027 loops=1)
	Workers Planned: 2
	Workers Launched: 2
	-&amp;gt;  Parallel Seq Scan on numbers  (cost=0.00..106138.34 rows=4136 width=4) (actual time=9.913..317.809 rows=3342 loops=3)
		Filter: (b = 900)
		Rows Removed by Filter: 3329991
Planning Time: 0.574 ms
JIT:
	Functions: 12
	Options: Inlining false, Optimization false, Expressions true, Deforming true
	Timing: Generation 4.899 ms, Inlining 0.000 ms, Optimization 3.039 ms, Emission 25.030 ms, Total 32.968 ms
Execution Time: 398.820 ms
(12 rows)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


			Here, we can analyze the following:
			&lt;ol style=&quot;margin-left: 2rem&quot;&gt;
				&lt;li&gt;
				This time, Postgres did not use the index &lt;code&gt;numbers_a_b_idx&lt;/code&gt;. Even though we have a composite index on both columns &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;. Why? Because we cannot use this composite index when scanning a filter. The filter condition is on column &lt;code&gt;a&lt;/code&gt;, and the composite index can be used for conditions involving both columns &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; or just column &lt;code&gt;a&lt;/code&gt;. However, it cannot be used for conditions involving only column &lt;code&gt;b&lt;/code&gt;. Therefore, if we have a composite index on columns &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;, querying on column &lt;code&gt;b&lt;/code&gt; alone will not utilize the index.
				&lt;/li&gt;
			&lt;/ol&gt;	
		&lt;/li&gt;
		&lt;br /&gt;
		&lt;li&gt;
			&lt;b&gt;Select column c but we are going to query on both &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;B&lt;/code&gt; with &lt;code&gt;AND&lt;/code&gt; operation&lt;/b&gt;


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# EXPLAIN ANALYZE SELECT C FROM numbers WHERE A = 60 AND B = 600;
							QUERY PLAN
-------------------------------------------------------------------------
Bitmap Heap Scan on numbers  (cost=5.44..386.39 rows=98 width=4) (actual time=0.732..6.281 rows=102 loops=1)
	Recheck Cond: ((a = 60) AND (b = 600))
	Heap Blocks: exact=101
	-&amp;gt;  Bitmap Index Scan on numbers_a_b_idx  (cost=0.00..5.42 rows=98 width=0) (actual time=0.513..0.513 rows=102 loops=1)
		Index Cond: ((a = 60) AND (b = 600))
Planning Time: 0.756 ms
Execution Time: 6.659 ms&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


			Here, the situation remains the same as earlier when we had an index on both columns A and B.
		&lt;/li&gt;
		&lt;br /&gt;
		&lt;li&gt;
			&lt;b&gt;Select column c but we are going to query on both &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;B&lt;/code&gt; with &lt;code&gt;OR&lt;/code&gt; operation&lt;/b&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;postgres=# EXPLAIN ANALYZE SELECT C FROM numbers WHERE A = 60  or B = 80;
							QUERY PLAN
------------------------------------------------------------------------
Gather  (cost=1000.00..128404.51 rows=108495 width=4) (actual time=20.721..388.512 rows=109443 loops=1)
	Workers Planned: 2
	Workers Launched: 2
	-&amp;gt;  Parallel Seq Scan on numbers  (cost=0.00..116555.01 rows=45206 width=4) (actual time=8.325..304.804 rows=36481 loops=3)
		Filter: ((a = 60) OR (b = 80))
		Rows Removed by Filter: 3296853
Planning Time: 1.009 ms
JIT:
	Functions: 12
	Options: Inlining false, Optimization false, Expressions true, Deforming true
	Timing: Generation 5.795 ms, Inlining 0.000 ms, Optimization 2.561 ms, Emission 21.798 ms, Total 30.154 ms
Execution Time: 397.675 ms&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


			Here, we can analyze the situation as follows:
			&lt;ul style=&quot;margin-left: 2rem&quot;&gt;
				&lt;li&gt;
				As observed earlier, it&apos;s not feasible to use a composite index on column &lt;code&gt;B&lt;/code&gt; individually. The option is either to use it on column &lt;code&gt;A&lt;/code&gt; alone or on both columns &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;B&lt;/code&gt;. Consequently, Postgres opts for a Parallel Sequential Scan in this scenario.
				&lt;/li&gt;
			&lt;/ul&gt;
		&lt;/li&gt;
	&lt;/ol&gt;
&lt;/ul&gt;


  
  &lt;p&gt;&lt;a href=&quot;/technology/an-in-depth-look-at-database-indexing/&quot;&gt;An in-depth look at Database Indexing&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on December 10, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[How to setup AWS Cloudwatch alarm for your SES reputation metrics]]></title>
  <link rel="alternate" type="text/html" href="/technology/how-to-setup-aws-cloudwatch-alarm-for-your-ses-reputation-metrics/"/>
  <id>/technology/how-to-setup-aws-cloudwatch-alarm-for-your-ses-reputation-metrics</id>
  <updated>2023-11-20 18:58:55 +0530T00:00:00-00:00</updated>
  <published>2023-11-20T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#AWS" term="AWS" /><category scheme="/tags/#AWS%20SES" term="AWS SES" /><category scheme="/tags/#AWS%20SNS" term="AWS SNS" /><category scheme="/tags/#AWS%20Cloudwatch" term="AWS Cloudwatch" />
  <content type="html">
  
    &lt;p&gt;Amazon Simple Email Service (SES) is an email platform that offers a straightforward and cost-effective way for you to send and receive emails using your own email addresses and domains.&lt;/p&gt;

&lt;p&gt;AWS SES has associated &lt;a href=&quot;https://docs.aws.amazon.com/pinpoint/latest/userguide/channels-email-deliverability-dashboard-bounce-complaint.html&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;reputation metrics (Bounce &amp;amp; Complaint rate)&lt;/a&gt;, and if these metrics exceed the threshold limit, AWS may disable your email service, potentially causing a significant impact on your business.&lt;/p&gt;

&lt;p&gt;Why not create an alarm that monitors these reputation metrics and notifies you when they approach the threshold value? This way, you can prevent email service downtime.&lt;/p&gt;

&lt;p&gt;Fortunately, AWS provides a few services that, when combined, can help you easily set up the SES reputation metrics alarm.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/aws-cloudwatch-alarm-for-ses-monitoring/aws-cloudwatch-alarm-architecture.png&quot; alt=&quot;Alarm Architecture&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Amazon Simple Email Service&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I recommend following &lt;a href=&quot;https://www.sitepoint.com/deliver-the-mail-with-amazon-ses-and-rails/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;deliver the mail with Amazon SES and Rails&lt;/a&gt; 
  article to set up AWS SES for your Ruby on Rails application, as I’ll be using Ruby on Rails as my backend language.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Amazon Simple Notification Service&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Amazon Simple Notification Service (SNS) is a web service that coordinates and manages message delivery from publishers to subscribers. You can learn more about it &lt;a href=&quot;https://www.sitepoint.com/deliver-the-mail-with-amazon-ses-and-rails/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We will configure SNS to send notifications to both an email address and an API endpoint in your backend server.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Steps to create SNS topic&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Go to &lt;a href=&quot;https://us-east-1.console.aws.amazon.com/sns/v3/home?region=us-east-1#/dashboard&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt; AWS SNS dashboard&lt;/a&gt; and click on the &lt;strong&gt;&lt;em&gt;Create Topic&lt;/em&gt;&lt;/strong&gt; button.&lt;/li&gt;
      &lt;li&gt;Select &lt;strong&gt;&lt;em&gt;Standard&lt;/em&gt;&lt;/strong&gt; as the type of topic.&lt;/li&gt;
      &lt;li&gt;Type a name for the topic. For example, &lt;strong&gt;&lt;em&gt;ses-reputation-notifier&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;Click on &lt;strong&gt;&lt;em&gt;Create topic&lt;/em&gt;&lt;/strong&gt; button.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Steps to create subscription for SNS topic&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Go to &lt;a href=&quot;https://us-east-1.console.aws.amazon.com/sns/v3/home?region=us-east-1#/dashboard&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt; AWS SNS dashboard&lt;/a&gt; and click on the &lt;strong&gt;&lt;em&gt;Create Subscription&lt;/em&gt;&lt;/strong&gt; button.&lt;/li&gt;
      &lt;li&gt;Select the SNS topic you created from Topic ARN&lt;/li&gt;
      &lt;li&gt;Choose the protocol from the list of protocols.
        &lt;ul&gt;
          &lt;li&gt;&lt;strong&gt;Email&lt;/strong&gt;
            &lt;ul&gt;
              &lt;li&gt;Select the &lt;strong&gt;&lt;em&gt;Email&lt;/em&gt;&lt;/strong&gt; protocol.&lt;/li&gt;
              &lt;li&gt;Enter your email in the endpoint.&lt;/li&gt;
              &lt;li&gt;You’ll receive a subscription URL on your email. Visit this URL to subscribe to the SNS topic.&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;HTTP/HTTPS&lt;/strong&gt;
            &lt;ul&gt;
              &lt;li&gt;We will configure the HTTP/HTTPS protocol after creating the public API endpoint in later part of the blog.&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Amazon Cloudwatch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Amazon CloudWatch monitors your Amazon Web Services (AWS) resources and the applications you run on AWS in real time. You can use CloudWatch to collect and track metrics, which are variables you can measure for your resources and applications. You can learn more about it &lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/WhatIsCloudWatch.html&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For our SES reputation monitoring alarm, we’ll require four alarms. Two will be for monitoring bounce rate, and two will be for monitoring complaint rate. We create two alarms for each reputation metric because the first alarm triggers when the reputation metric exceeds the threshold, and the second alarm triggers when the reputation metric returns to normal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps to create alarm&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;1. Bounce rate (OK -&amp;gt; ALARM)&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;This alarm will activate when the Bounce rate surpasses the set limit. This transition will change the alarm state from &lt;strong&gt;&lt;em&gt;OK&lt;/em&gt;&lt;/strong&gt; to &lt;strong&gt;&lt;em&gt;ALARM.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;Go to &lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/WhatIsCloudWatch.html&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;AWS Cloudwatch&lt;/a&gt; and click on &lt;strong&gt;&lt;em&gt;Create alarm&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;Click on &lt;strong&gt;&lt;em&gt;Select metrics&lt;/em&gt;&lt;/strong&gt; and select &lt;strong&gt;&lt;em&gt;SES &amp;gt; Account Metrics &amp;gt; Reputation.BounceRate&lt;/em&gt;&lt;/strong&gt; and click on &lt;strong&gt;&lt;em&gt;Select metrics&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
      &lt;li&gt;Select &lt;strong&gt;&lt;em&gt;1 hour&lt;/em&gt;&lt;/strong&gt; from period dropdown.&lt;/li&gt;
      &lt;li&gt;Fill &lt;strong&gt;&lt;em&gt;Define the threshold value&lt;/em&gt;&lt;/strong&gt; with 0.05 (suggested by AWS).&lt;/li&gt;
      &lt;li&gt;Click on &lt;strong&gt;&lt;em&gt;Additional configuration&lt;/em&gt;&lt;/strong&gt; and from dropdown select &lt;strong&gt;&lt;em&gt;Treat Missing data as Good&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;From &lt;strong&gt;&lt;em&gt;Alarm state trigger&lt;/em&gt;&lt;/strong&gt; select &lt;strong&gt;&lt;em&gt;In alarm&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;From &lt;strong&gt;&lt;em&gt;Send a notification to…&lt;/em&gt;&lt;/strong&gt; select the SNS topic you created.&lt;/li&gt;
      &lt;li&gt;Type a name for the alarm. For example, &lt;strong&gt;&lt;em&gt;bounce-rate-threshold-exceeded&lt;/em&gt;&lt;/strong&gt; and click on &lt;strong&gt;&lt;em&gt;Create alarm&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;2. Bounce rate (ALARM -&amp;gt; OK)&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;This alarm will activate when the Bounce rate returns within the specified limit. This transition will change the alarm state from &lt;strong&gt;&lt;em&gt;ALARM&lt;/em&gt;&lt;/strong&gt; to &lt;strong&gt;&lt;em&gt;OK&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;The procedure will be same for as for &lt;strong&gt;&lt;em&gt;Alarm #1&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;Just &lt;strong&gt;&lt;em&gt;OK&lt;/em&gt;&lt;/strong&gt; will be selected for &lt;strong&gt;&lt;em&gt;Alarm state trigger&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;Type a name for the alarm. for example, &lt;strong&gt;&lt;em&gt;bounce-rate-threshold-inlimit&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;3. Complaint rate (OK -&amp;gt; ALARM)&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;This alarm will activate when the Complaint rate surpasses the set limit. This transition will change the alarm state from &lt;strong&gt;&lt;em&gt;OK&lt;/em&gt;&lt;/strong&gt; to &lt;strong&gt;&lt;em&gt;ALARM.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;Go to &lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/WhatIsCloudWatch.html&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;AWS Cloudwatch&lt;/a&gt; and click on &lt;strong&gt;&lt;em&gt;Create alarm&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;Click on &lt;strong&gt;&lt;em&gt;Select metrics&lt;/em&gt;&lt;/strong&gt; and select &lt;strong&gt;&lt;em&gt;SES &amp;gt; Account Metrics &amp;gt; Reputation.ComplaintRate&lt;/em&gt;&lt;/strong&gt; and click on &lt;strong&gt;&lt;em&gt;Select metrics&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
      &lt;li&gt;Select &lt;strong&gt;&lt;em&gt;1 hour&lt;/em&gt;&lt;/strong&gt; from period dropdown.&lt;/li&gt;
      &lt;li&gt;Fill &lt;strong&gt;&lt;em&gt;Define the threshold value&lt;/em&gt;&lt;/strong&gt; with 0.001 (suggested by AWS).&lt;/li&gt;
      &lt;li&gt;Click on &lt;strong&gt;&lt;em&gt;Additional configuration&lt;/em&gt;&lt;/strong&gt; and from dropdown select &lt;strong&gt;&lt;em&gt;Treat Missing data as Good&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;From &lt;strong&gt;&lt;em&gt;Alarm state trigger&lt;/em&gt;&lt;/strong&gt; select &lt;strong&gt;&lt;em&gt;In alarm&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;From &lt;strong&gt;&lt;em&gt;Send a notification to…&lt;/em&gt;&lt;/strong&gt; select the SNS topic you created.&lt;/li&gt;
      &lt;li&gt;Type a name for the alarm. For example, &lt;strong&gt;&lt;em&gt;complaint-rate-threshold-exceeded&lt;/em&gt;&lt;/strong&gt; and click on &lt;strong&gt;&lt;em&gt;Create alarm&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;4. Complaint rate (ALARM -&amp;gt; OK)&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;This alarm will activate when the Complaint rate returns within the specified limit. This transition will change the alarm state from &lt;strong&gt;&lt;em&gt;ALARM&lt;/em&gt;&lt;/strong&gt; to &lt;strong&gt;&lt;em&gt;OK.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;The procedure will be same for as for &lt;strong&gt;&lt;em&gt;Alarm #3&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;Just &lt;strong&gt;&lt;em&gt;OK&lt;/em&gt;&lt;/strong&gt; will be selected for &lt;strong&gt;&lt;em&gt;Alarm state trigger&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;Type a name for the alarm. for example, &lt;strong&gt;&lt;em&gt;complaint-rate-threshold-inlimit&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;API endpoint to receive POST request&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You’ll need an API endpoint to receive POST requests from AWS SNS.&lt;/p&gt;

&lt;p&gt;Create a file in &lt;code&gt;app &amp;gt; controllers &amp;gt; sns_notification_controller.rb&lt;/code&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;class SnsNotificationController &amp;lt; ApplicationController
      skip_before_action :verify_authenticity_token
      before_action :authenticate_request

      def ses_reputation_notifier
        case message_body[&amp;#39;Type&amp;#39;]
        when &amp;#39;SubscriptionConfirmation&amp;#39;
          Rails.logger.error(message_body[&amp;#39;SubscribeURL&amp;#39;])
        when &amp;#39;Notification&amp;#39;
          message = JSON.parse(message_body[&amp;#39;Message&amp;#39;])

          alarm_active = message[&amp;#39;NewStateValue&amp;#39;] == &amp;#39;ALARM&amp;#39;
          // Your logic based on alarm status
        end

        head :ok
      end

      private

        def authenticate_request
          head :unauthorized if raw_post.blank? || !message_verifier.authentic?(raw_post)
        end

        def raw_post
          @raw_post ||= request.raw_post
        end

        def message_body
          @message_body ||= JSON.parse(raw_post)
        end

        def message_verifier
          @message_verifier ||= Aws::SNS::MessageVerifier.new
        end
    end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The code above uses the official &lt;a href=&quot;https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/SNS/Client.html&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;AWS SNS SDK&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Add a route for the &lt;strong&gt;&lt;em&gt;ses_reputation_notifier&lt;/em&gt;&lt;/strong&gt; action in the &lt;strong&gt;&lt;em&gt;sns_notification&lt;/em&gt;&lt;/strong&gt; within the &lt;code&gt;config/routes.rb&lt;/code&gt; file.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;post &amp;#39;/ses_reputation_notifier&amp;#39;, to: &amp;#39;sns_notification#ses_reputation_notifier&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The API endpoint needs to be a public endpoint so that SNS can send notifications without requiring any token. Since it’s a public endpoint, we need to verify the authenticity of the request to ensure it comes from SNS.&lt;/p&gt;

&lt;p&gt;There are two types of notifications sent by SNS:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Subscription Confirmation&lt;/li&gt;
  &lt;li&gt;Notifications&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before being able to receive notifications, we must confirm the subscription by visiting the subscribeUrl sent in the request body. That’s why we log the subscribeURL. Once you visit that URL, you’ll be subscribed to the SNS topic. After subscription, you’ll start receiving notifications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps to create HTTP/HTTPS subscription for SNS topic&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Select &lt;strong&gt;&lt;em&gt;HTTP&lt;/em&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;em&gt;HTTPS&lt;/em&gt;&lt;/strong&gt; protocol.&lt;/li&gt;
  &lt;li&gt;Enter the public API endpoint URL in the endpoint field. For example: &lt;strong&gt;&lt;em&gt;https://your-domain/ses_reputation_notifier&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;A POST request will be sent to the API endpoint, and the subscription URL will be logged (as specified in the code above). Visit this URL to confirm the subscription.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, your SNS topic is configured to publish messages to the specified email and API endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now that we’ve set up a CloudWatch alarm to monitor SES reputation metrics, it will notify both via email and API endpoint using SNS. With the notifications received by the server, you can ensure that any potential issues won’t significantly impact the current flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;AWS SES - &lt;a href=&quot;https://www.sitepoint.com/deliver-the-mail-with-amazon-ses-and-rails/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Here&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;AWS SNS - &lt;a href=&quot;https://aws.amazon.com/sns/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Here&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;AWS Cloudwatch - &lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/WhatIsCloudWatch.html&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Here&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;AWS SNS SDK (Ruby) - &lt;a href=&quot;https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/SNS/Client.html&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Here&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Reputation monitoring alarms using CloudWatch - &lt;a href=&quot;https://docs.aws.amazon.com/ses/latest/dg/reputationdashboard-cloudwatch-alarm.html&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Here&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;


  
  &lt;p&gt;&lt;a href=&quot;/technology/how-to-setup-aws-cloudwatch-alarm-for-your-ses-reputation-metrics/&quot;&gt;How to setup AWS Cloudwatch alarm for your SES reputation metrics&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on November 20, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Building a Collaborative code-editor &amp; Whiteboard: For tech interviews.]]></title>
  <link rel="alternate" type="text/html" href="/technology/building-a-collaborative-code-editor-and-whiteboard-for-tech-interviews/"/>
  <id>/technology/building-a-collaborative-code-editor-and-whiteboard-for-tech-interviews</id>
  <updated>2023-11-18 14:53:31 +0530T00:00:00-00:00</updated>
  <published>2023-11-18T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#Concurrency%20Management" term="Concurrency Management" /><category scheme="/tags/#Websockets" term="Websockets" /><category scheme="/tags/#Multifile%20Navigation" term="Multifile Navigation" /><category scheme="/tags/#YJS" term="YJS" /><category scheme="/tags/#tldraw" term="tldraw" /><category scheme="/tags/#OT%20Vs%20CRDT" term="OT Vs CRDT" /><category scheme="/tags/#CodeMirror%206" term="CodeMirror 6" /><category scheme="/tags/#Docker" term="Docker" /><category scheme="/tags/#AWS" term="AWS" />
  <content type="html">
  
    &lt;p&gt;A Collaborative code-editor and Whiteboard aims to diversify the current interviewing scenario to Live - Coding, Sketching, Crafting, and Conceptualisation of ideas between the Interviewer and Interviewee for better communication which was until now only limited to Video and audio chats.&lt;/p&gt;

&lt;h1 id=&quot;the-blog-blueprint&quot;&gt;The Blog Blueprint:&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;Need for collaboration: Unveiling the Why&lt;/li&gt;
  &lt;li&gt;Cracking the Code: Research and Concluded Solutions
    &lt;ul&gt;
      &lt;li&gt;    Collaborative conflict management.&lt;/li&gt;
      &lt;li&gt;    Code-editor research&lt;/li&gt;
      &lt;li&gt;    Existing code-editors&lt;/li&gt;
      &lt;li&gt;    Existing whiteboards&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Feature Showcase&lt;/li&gt;
  &lt;li&gt;Architecture&lt;/li&gt;
  &lt;li&gt;Actors: Use case diagram&lt;/li&gt;
  &lt;li&gt;Dockerization, Deployment and scale&lt;/li&gt;
  &lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;need-for-collaboration-unveiling-the-why&quot;&gt;Need for Collaboration: Unveiling the Why&lt;/h1&gt;

&lt;p&gt;In the post-pandemic landscape, the majority of tech interviews and initial screening rounds have transitioned to virtual formats through platforms like Google Meet, Zoom, or Microsoft Teams. While these platforms excel in facilitating video and audio interactions, they fall short when it comes to assessing candidates’ coding problem-solving skills in a live, interview-pressure setting. Typically, recruiters resort to screen sharing candidates’ local code editors, but this approach presents limitations.&lt;/p&gt;

&lt;p&gt;Recruiters lack the ability to actively edit the code or navigate through different code files seamlessly. Simple tasks such as pointing out errors, saving code for later review, brainstorming designs, and optimizing code quality become cumbersome, relying on manual instructions for candidates to scroll up or down.&lt;/p&gt;

&lt;p&gt;As a result, the demand for a collaborative code editor and whiteboard becomes not just beneficial but critical in addressing these challenges efficiently.&lt;/p&gt;

&lt;h1 id=&quot;cracking-the-code-research-and-concluded-solutions&quot;&gt;Cracking the Code: Research and Concluded Solutions&lt;/h1&gt;

&lt;h2 id=&quot;what-if-two-users-press-two-different-letters-on-the-same-line-at-the-exact-same-time&quot;&gt;What if two users press two different letters on the same line at the exact same time?&lt;/h2&gt;

&lt;p&gt;This conflict is a major problem that all collaborative and distributed softwares have to address. Therefore, when the user makes a change to the document that change needs to be synchronised with other users as well and if the user has to wait for this synchronisation for every letter they type this will make the application very slow. So in order to get a near real-time collaborative editing experience every client maintains a local replica of the document and the main issue now is just to maintain consistency with other clients local replica.&lt;/p&gt;

&lt;p&gt;Consider the following example -
In an empty line, Alice inserts letter ‘A’ at index 0 and Bob inserts letter ‘B’ at index 0 at the exact same time. Now how should we combine these changes together? Should we prefer Alice’s changes over Bob’s or vice-versa?&lt;/p&gt;

&lt;p&gt;Well there is no logical answer and quite frankly it doesn’t matter, what actually matters is that at the end of combining these operations it should “Converge to an identical state” i.e both Alice and Bob should get the same text as soon as possible and if they are not satisfied they will re-edit just like in git you would have merge conflicts and you would have to merge manually but here we can automate this merging using various concurrency control and consistency models.&lt;/p&gt;

&lt;p&gt;There are two conceptual protocols when dealing with such problems -&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Consensus based protocol&lt;/strong&gt; - Pick one &amp;amp; reject the rest. In this case it will select any one between Alice and Bob, thereby losing one client’s data completely. This is mainly used in decentralised blockchain applications.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Collaboration based protocol&lt;/strong&gt; - Merge &amp;amp; Keep them all.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But that’s not the end of the story as we can take advantage of commutative operations like consider a double insertion situation - the text initially is “APP”, Alice inserts “H” at the beginning and Bob inserts “Y” at the end at the exact same time as Alice. In such a situation there is a line conflict but since insertions at different positions are commutative we can merge these operations automatically to get the final text “HAPPY”.
Thereby not discarding Alice’s changes or Bob’s changes, providing a real-time conflict-free editing experience and there is no need for someone to manually merge these changes for us.&lt;/p&gt;

&lt;p&gt;The concurrency control algorithms are widely studied for collaborative tools and a lot of research is currently going into such algorithms to attain distributed concurrency. The two most widely used ones are OT and CRDT.&lt;/p&gt;

&lt;h3 id=&quot;operational-transformation-ot&quot;&gt;Operational Transformation (OT)&lt;/h3&gt;

&lt;p&gt;Also known as &lt;strong&gt;Event passing&lt;/strong&gt;. Any key event happening on the client-side will be sent to the server. Events can be inserting/deleting a character. The operation received by the server from the client is transformed against its operation which results in a new operation to be performed on the client-side. OT implementations are used in Google docs and many more such collaborative applications pre-2018 ( before CRDT optimisations started to outperform their OT counterparts ).&lt;/p&gt;

&lt;p&gt;This can be better understood with the help of an example -&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/collaborative_editor_and_whiteboard/ot.png&quot; alt=&quot;ot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There are many models based on Operational Transformation that are used in prod addressing different issues and there is a plethora of dense academic research whose references can be found in the Documentation. As this blog is written mainly from a developer point of view but academic references are also mentioned wherever needed.&lt;/p&gt;

&lt;h3 id=&quot;conflict-free-replicated-data-type-crdt&quot;&gt;Conflict-free Replicated Data Type (CRDT)&lt;/h3&gt;

&lt;p&gt;CRDT works on two core principles -&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Commutative&lt;/strong&gt; - Re-ordering different operations will not change the final result.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Idempotency&lt;/strong&gt; - No matter how many times an operation is performed the final result remains the same.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here, every character in the document is assigned a unique ID, and when a new character is
inserted, the new character would get an ID based on the average of its neighbours (ideally could even take non-integer IDs), which helps to make the algorithm less complex in conflict resolution.&lt;/p&gt;

&lt;p&gt;Just like OT had its own various models, CRDTs too have different implementations developed &amp;amp; optimised over time. To get a conceptual understanding consider the example below - using Lagoot’s CRDT Algorithm&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/collaborative_editor_and_whiteboard/crdt.png&quot; alt=&quot;crdt&quot; /&gt;&lt;/p&gt;

&lt;p&gt;CRDTs have a lenient approach to the order of operations. In theory, this reduces the design complexity of distribution mechanisms since there is no need for strict serialisation protocols. By tolerating out-of-order updates, the distribution mechanism has to meet simpler integrity guarantees.&lt;/p&gt;

&lt;h3 id=&quot;ot-vs-crdt-final-solution&quot;&gt;OT Vs CRDT: Final Solution&lt;/h3&gt;

&lt;p&gt;CRDTs serve our requirements much better because of these two main reasons -&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Speed&lt;/strong&gt;: CRDTs are much much faster than many OT implementations applying operations should be possible with just a &lt;em&gt;O(log(n))&lt;/em&gt; lookup. But in the past the main problem was the large memory overhead. Which is now resolved after significant optimisations in YJS CRDT implementation. ( Exact benchmarks are provided in the references section of Documentation. )&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Flexibility and Usability&lt;/strong&gt;: CRDTs support a wider range of data types. Compared to the OT library, which requires a thorough modelling of business logic as different types of operation data structures, when using the CRDT library, you only need to perform the same operation on common data structures, such as Map and Array.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;code-editor-research&quot;&gt;Code-editor Research&lt;/h2&gt;

&lt;p&gt;The main code-editing component libraries explored were -&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://codemirror.net/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;CodeMirror 6&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://microsoft.github.io/monaco-editor/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Monaco Editor&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://ace.c9.io/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Ace Editor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Out of which CodeMirror 6 was selected due to the following reasons -&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Highly modular and lightweight&lt;/strong&gt;: seamless integration of only the necessary components, allowing for a tailored and resource-efficient implementation.&lt;/li&gt;
  &lt;li&gt;Has a modern, extensible API with &lt;strong&gt;excellent documentation.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Is easy to customise, style, and reconfigure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;existing-browser-based-code-editors&quot;&gt;Existing Browser based code-editors&lt;/h2&gt;

&lt;p&gt;Here are some more projects and companies whose products were researched thoroughly, but to prevent this blog from becoming super lengthy only the names with references are mentioned and all the other details are mentioned in the Proof Of Concept given alongside the Documentation:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://gitlab.com/gitlab-org/gitlab-web-ide&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Gitlab Web IDE&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/coder/code-server&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Code-server&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/replit&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Replit&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://codeanywhere.com/solutions/collaborate &quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Codeanywhere&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://codepen.io/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Codepen&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.codetogether.com/ &quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Codetogether&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.dev/github/dev&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Github.dev&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/features/codespaces&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Github Codespaces&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://vscode.dev/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Vscode.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;existing-whiteboards&quot;&gt;Existing Whiteboards&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;tldraw&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.tldraw.dev/introduction&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Docs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/tldraw/tldraw&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Github&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;22k+ stars&lt;/li&gt;
  &lt;li&gt;130 contributors actively maintaining.&lt;/li&gt;
  &lt;li&gt;Well funded &lt;a href=&quot;https://tldraw.substack.com/p/tiny-little-seed-round&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;$2.7M seed&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Supports YJS ( Shared-editing framework )&lt;/li&gt;
  &lt;li&gt;Easy to integrate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other options researched:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/excalidraw/excalidraw&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Excalidraw&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://fabricjs.com/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;fabric.js&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/embiem/react-canvas-draw&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;react-canvas-draw&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;feature-showcase&quot;&gt;Feature Showcase&lt;/h1&gt;

&lt;h3 id=&quot;client-workspace&quot;&gt;Client Workspace&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/collaborative_editor_and_whiteboard/client_workspace.png&quot; style=&quot;width: 810px;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;multifile-navigation--room-info&quot;&gt;Multifile Navigation &amp;amp; Room info&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/collaborative_editor_and_whiteboard/combined.png&quot; style=&quot;width: 810px; height: 380px&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;whiteboard&quot;&gt;Whiteboard&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/collaborative_editor_and_whiteboard/whiteboard.png&quot; style=&quot;width: 810px;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;All features are satisfied as specified in FRS:&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;Collaborative Code Editor&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Real-time code editing and sharing capabilities using websockets.&lt;/li&gt;
  &lt;li&gt;Syntax highlighting for multiple programming languages using codemirror 6 language packages.&lt;/li&gt;
  &lt;li&gt;Support for multiple users editing code simultaneously using YJS shared-editing framework.&lt;/li&gt;
  &lt;li&gt;Files navigator bar for multi-file editing using Depth First Search Algorithm in react.&lt;/li&gt;
  &lt;li&gt;Create a new meet in just one click!&lt;/li&gt;
  &lt;li&gt;Fast and low-latency using websockets + sending only new changes made to the document instead of sending the whole document each time.&lt;/li&gt;
  &lt;li&gt;Modular code as specified in Architecture below.&lt;/li&gt;
  &lt;li&gt;Secure groups/ rooms using YJS update serialisation.&lt;/li&gt;
  &lt;li&gt;Additional features provided -
    &lt;ul&gt;
      &lt;li&gt;    Zip downloads the entire codebase.&lt;/li&gt;
      &lt;li&gt;    Pdf download all whiteboard designs.&lt;/li&gt;
      &lt;li&gt;    Role management for different actors ( or users ) specified below in the use-case diagram.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;&lt;strong&gt;Whiteboard&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Interactive whiteboard for visual collaboration and problem-solving using tldraw.&lt;/li&gt;
  &lt;li&gt;Drawing tools, shapes, and text annotations.&lt;/li&gt;
  &lt;li&gt;Real-time updates for all participants using YJS.&lt;/li&gt;
  &lt;li&gt;Ability to save whiteboard content by downloading a local pdf copy: Simply by pressing &lt;strong&gt;Ctrl + P&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Additional features provided -
    &lt;ul&gt;
      &lt;li&gt;    Upload any new shape or image for UML diagrams, flowcharts or any other design pattern.&lt;/li&gt;
      &lt;li&gt;    Dark mode and multi page editing.&lt;/li&gt;
      &lt;li&gt;    Set custom opacity, texture patterns and border levels.&lt;/li&gt;
      &lt;li&gt;    Use laser pointers for better presentations.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;architecture&quot;&gt;Architecture&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/collaborative_editor_and_whiteboard/architecture.png&quot; style=&quot;width: 780px;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;frontend&quot;&gt;Frontend&lt;/h2&gt;

&lt;p&gt;The Frontend architecture is composed of CodeMirror 6, a powerful code-editing tool. To enable multi-file support, a file explorer is seamlessly integrated, allowing users to create and delete files or folders within a nested structure. The status bar dynamically displays all open files until they are either closed or removed.&lt;/p&gt;

&lt;p&gt;Additionally, the whiteboard functionality is implemented using Tldraw as a separate component. Users can effortlessly switch between the Code Editor and Whiteboard using the left sidebar.&lt;/p&gt;

&lt;p&gt;The system adopts a &lt;strong&gt;modular approach&lt;/strong&gt;, breaking down the Code Editor into smaller, manageable components such as Editor-screen, FileTree, and LeftPanel. This modular design enhances code maintainability and fosters a more scalable and extensible architecture.&lt;/p&gt;

&lt;h2 id=&quot;backend&quot;&gt;Backend&lt;/h2&gt;

&lt;p&gt;Utilising a Node.js server with the Express framework, the application listens for incoming HTTP requests through RESTful API routes. The server efficiently handles data persistence by saving user input code to the database. Additionally, it provides administrative functionality to retrieve all saved data sets from the database.&lt;/p&gt;

&lt;p&gt;The system is Augmented with a WebSocket server for low-latency communication and real-time updates, this system further enhances its capabilities. The WebSocket server efficiently receives updates from diverse clients, manages document state updates, and broadcasts these changes to other clients within the same room. This functionality ensures synchronised, &lt;strong&gt;real-time communication&lt;/strong&gt; across connected clients for a seamless and interactive user experience.&lt;/p&gt;

&lt;h2 id=&quot;testing&quot;&gt;Testing&lt;/h2&gt;

&lt;p&gt;I have used Jest, in conjunction with React Testing Library, for a comprehensive testing suite. This included rigorous testing scenarios for:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Buttons&lt;/li&gt;
  &lt;li&gt;Connection management.&lt;/li&gt;
  &lt;li&gt;Form submissions&lt;/li&gt;
  &lt;li&gt;Page redirections&lt;/li&gt;
  &lt;li&gt;Error message handling within the project&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;actors-use-case-diagram&quot;&gt;Actors: Use case diagram&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/collaborative_editor_and_whiteboard/use_case.png&quot; style=&quot;width: 770px&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;dockerization-deployment-and-scale&quot;&gt;Dockerization, Deployment and scale&lt;/h1&gt;

&lt;p&gt;I’ve partitioned the project into distinct frontend and backend components, each independently containerized using Docker. This &lt;strong&gt;modularization&lt;/strong&gt; facilitates the potential for separate hosting on different servers, promoting a &lt;strong&gt;decoupled architecture&lt;/strong&gt;. Although, in the current deployment setup, both containers reside on the same EC2 instance, leveraging Docker Compose for streamlined single-host deployment.&lt;/p&gt;

&lt;p&gt;This configuration ensures swift and straightforward deployment with efficient container orchestration. Please note that since frontend &amp;amp; backend are decoupled you need to enter the correct ip address of the backend container so that frontend can connect with it properly. For in-depth instructions and troubleshooting guidance during deployment, refer to the comprehensive documentation provided in the code repository.&lt;/p&gt;

&lt;h3 id=&quot;docker-composeyml&quot;&gt;docker-compose.yml&lt;/h3&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;version: &amp;quot;3.8&amp;quot;

services:
frontend:
build: ./Frontend
container_name: frontend_c
ports: - &amp;quot;3000:3000&amp;quot;
stdin_open: true
tty: true
depends_on: - backend
networks: - mern-network

backend:
build: ./Backend
container_name: backend_c
restart: always
ports: - &amp;quot;5000:5000&amp;quot;
networks: - mern-network

networks:
mern-network:
driver: bridge&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;docker-run-command&quot;&gt;Docker run command&lt;/h3&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;sudo docker compose up --build -d&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;scaling&quot;&gt;Scaling&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Maximum number of users in one room&lt;/strong&gt; &lt;br /&gt;
In this system, WebSockets are adopted over WebRTC for a client-server architecture. The rationale behind this decision lies in the inherent limitations of WebRTC, particularly in scenarios with a large number of users in a room.&lt;/p&gt;

&lt;p&gt;WebRTC relies on a mesh topology, where each user establishes connections with every other user in the room. However, as the user count surpasses 15 to 30, the P2P connections become inefficient, leading to increased latency. In contrast, the client-server model with WebSockets exhibits superior scalability, comfortably supporting over 100 users in a room without encountering performance issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reducing user bandwidth&lt;/strong&gt; &lt;br /&gt;
Optimising user bandwidth is achieved through a granular approach of selectively transmitting updates or modifications made to the document, as opposed to repetitively sending the entire document.&lt;/p&gt;

&lt;p&gt;In the collaborative environment, the CRDT algorithm is leveraging this approach, conflict resolution is executed solely based on the transmitted changes, eliminating the need to transmit the entire document each time and ensuring a more efficient and bandwidth-conscious collaborative experience.&lt;/p&gt;

&lt;p&gt;More detailed scaling information about YJS are specified in the references documentation.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;In this exploration, we dived into the intricacies of a Collaborative Code Editor and Whiteboard, aiming to transform the interview process. This enhances communication beyond the traditional bounds of video and audio chats.&lt;/p&gt;

&lt;p&gt;The challenges faced during development served as valuable lessons, fortifying my grasp of web development intricacies and inspiring innovative solutions. This journey was not without its hurdles, researching various repositories, digging one article after another, and experimenting with unknown codebases but with perseverance and effective problem-solving, I navigated these challenges, gaining valuable insights and skills in web development.&lt;/p&gt;

&lt;p&gt;Feel free to reach out if you’d like to learn more about this project—I’m here to help and answer any questions you might have.&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;/technology/building-a-collaborative-code-editor-and-whiteboard-for-tech-interviews/&quot;&gt;Building a Collaborative code-editor &amp; Whiteboard: For tech interviews.&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on November 18, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Puma: From Daemonization to Process Control with Systemctl and Monit]]></title>
  <link rel="alternate" type="text/html" href="/technology/puma-from-daemonization-to-process-control-with-systemctl-and-monit/"/>
  <id>/technology/puma-from-daemonization-to-process-control-with-systemctl-and-monit</id>
  <updated>2023-10-21 21:06:27 +0530T00:00:00-00:00</updated>
  <published>2023-10-21T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#puma" term="puma" /><category scheme="/tags/#daemon" term="daemon" /><category scheme="/tags/#systemctl" term="systemctl" /><category scheme="/tags/#monit" term="monit" /><category scheme="/tags/#rails" term="rails" />
  <content type="html">
  
    &lt;p&gt;Puma is a popular Ruby web server that is known for its speed and scalability. It has undergone significant changes in recent versions(starting 5.0.0). One of the most notable alterations is the removal of the daemonization feature. But what does it mean?&lt;/p&gt;

&lt;p&gt;Daemonization, in the context of web servers, is a process that allows a program to run in the background as a system service. In older versions, Puma made it simple for users to daemonize their processes with a straightforward configuration snippet:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;#config/puma.rb
daemonize&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;However, in recent versions, attempting to use the &lt;code&gt;daemonize&lt;/code&gt; code will result in an error, as this functionality has been removed from the  codebase.&lt;/p&gt;

&lt;h4 id=&quot;why-daemonization-should-not-be-part-of-gem&quot;&gt;&lt;strong&gt;Why daemonization should not be part of gem?&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Incorporating daemonization directly within a gem can lead to undesirable consequences: as explained by Mike Perham in a &lt;a href=&quot;https://www.mikeperham.com/2014/09/22/dont-daemonize-your-daemons/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Blog Post&lt;/a&gt;. Here are some key points that should be considered -&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Complexity&lt;/strong&gt;: Adding daemonization features to a gem can make its code more complex and challenging.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Maintenance&lt;/strong&gt;: The responsibility of maintaining daemonization, automatic restart, and similar core features becomes an additional burden.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Efficiency&lt;/strong&gt;: System processes are better equipped to manage tasks like daemonization. Delegating this function to the system ensures more efficient and reliable execution, rather than embedding it within the gem.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a result of these considerations, Puma decided to remove the daemonization feature from the gem.&lt;/p&gt;

&lt;p&gt;This decision led us to make some changes in our setup to ensure the smooth running of our applications.&lt;/p&gt;

&lt;h4 id=&quot;using-systemd&quot;&gt;&lt;strong&gt;Using Systemd&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;We had previously implemented daemonization for &lt;a href=&quot;https://www.elitmus.com/blog/technology/sidekiq-process-in-production-with-systemd-and-monit&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Sidekiq&lt;/a&gt;, which was a process similar to Puma’s needs. Although there were some minor adjustments required for Puma. Here are steps to achieve daemnization through systemctl:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Remove &lt;code&gt;daemonization&lt;/code&gt; from config/puma.rb file&lt;/li&gt;
  &lt;li&gt;
    Create a file in &lt;code&gt;/lib/systemd/system/puma.service&lt;/code&gt;. Below is sample systemd service configuration example, modify it according to your needs.

    
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;[Unit]
      Description=Puma HTTP Server
      After=network.target

      [Service]
      Type=notify
      User=username

      WorkingDirectory=/dir/path
      ExecStart=/bin/pumactl start -F /path/puma_config --environment env
      ExecStartPost=/bin/sh -c &amp;#39;/bin/echo $MAINPID &amp;gt; /usr/myapp/shared/pids/puma.pid&amp;#39;
      ExecStop=/bin/kill -TSTP $MAINPID

      RestartSec=10
      Restart=on-failure

      [Install]
      WantedBy=multi-user.target&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/li&gt;
  &lt;li&gt;
    Two prominent Puma restart strategies are Phased and Hot restarts. &lt;strong&gt;Phased restarts are slower but ensure that all workers finish their existing requests before restarting the server, while Hot restarts are faster but come with increased latency during the restart.&lt;/strong&gt; &lt;br /&gt;
    To initiate Puma with a phased restart, you can pass the &lt;code&gt;phased-restart&lt;/code&gt; option. This choice offers flexibility to adapt Puma&apos;s behavior according to specific needs. More about puma restarts &lt;a href=&quot;https://github.com/puma/puma/blob/master/docs/restart.md&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Here&lt;/a&gt;.
  &lt;/li&gt;
  &lt;li&gt;
    &lt;strong&gt;Monit configurations&lt;/strong&gt;&lt;br /&gt;

    Monit is a utility for managing and monitoring processes, programs, files, directories and filesystems on a Unix system &lt;a href=&quot;https://mmonit.com/monit/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Monit Docs&lt;/a&gt;. &lt;br /&gt;

    Updated &lt;code&gt;monitrc&lt;/code&gt; file
    
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;check process puma with pidfile &amp;quot;/usr/myapp/shared/pids/puma.pid&amp;quot;
      start program = &amp;quot;/bin/bash -l -c &amp;#39;sudo systemctl start puma&amp;#39;&amp;quot; with timeout 20 seconds
      stop program = &amp;quot;/bin/bash -l -c &amp;#39;sudo systemctl stop puma&amp;#39;&amp;quot; with timeout 20 seconds
      if totalmem is greater than 800 MB for 3 cycles then restart
      if cpu is greater than 65% for 2 cycles then exec &amp;quot;/etc/monit/slack_notifier.sh&amp;quot; else if succeeded then exec &amp;quot;/etc/monit/slack_notifier.sh&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/li&gt;
  &lt;li&gt;
    To check if puma is running correctly follow the commands.

    
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;ps aux | grep puma
    sudo monit summary&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;exploring-other-alternatives&quot;&gt;&lt;strong&gt;Exploring Other Alternatives&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;As alternative to this we considered using &lt;a href=&quot;https://github.com/kigster/puma-daemon/)  gem. it copied the removed code and maintained a separate gem&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;puma-daemon&lt;/a&gt; gem, which essentially replicated the removed code and maintained it in a separate gem. However, after careful consideration, we chose not to adopt this alternative for the following reasons:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Violation of system standards.&lt;/li&gt;
  &lt;li&gt;Additional gem and maintainence burden.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;summary&quot;&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;While the removal of daemonization from Puma may require some adjustments, it aligns with the best practices of modern web server management Managing processes at the system level, using tools like systemd and Monit, is considered a more efficient and maintainable approach. Daemonizing processes within application code is discouraged, as it’s a task that falls under the system level. Ultimately, the shift towards system-level process management ensures the stability and efficiency of web applications.&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;/technology/puma-from-daemonization-to-process-control-with-systemctl-and-monit/&quot;&gt;Puma: From Daemonization to Process Control with Systemctl and Monit&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on October 21, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Demystifying Rails 7 System Tests: Configuring CI Pipeline]]></title>
  <link rel="alternate" type="text/html" href="/technology/demystifying-rails-7-system-tests-configuring-ci-pipeline/"/>
  <id>/technology/demystifying-rails-7-system-tests-configuring-ci-pipeline</id>
  <updated>2023-08-28 17:28:05 +0530T00:00:00-00:00</updated>
  <published>2023-08-28T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#Rails%207" term="Rails 7" /><category scheme="/tags/#System%20Tests" term="System Tests" /><category scheme="/tags/#Integration%20Tests" term="Integration Tests" /><category scheme="/tags/#CI-CD" term="CI-CD" /><category scheme="/tags/#Minitest" term="Minitest" /><category scheme="/tags/#Capybara" term="Capybara" /><category scheme="/tags/#Selenium" term="Selenium" /><category scheme="/tags/#Selenium-webdriver" term="Selenium-webdriver" /><category scheme="/tags/#webdriver" term="webdriver" /><category scheme="/tags/#webdrivers%20gem" term="webdrivers gem" /><category scheme="/tags/#chromedriver" term="chromedriver" /><category scheme="/tags/#arm64" term="arm64" /><category scheme="/tags/#amd64" term="amd64" /><category scheme="/tags/#linux" term="linux" /><category scheme="/tags/#docker" term="docker" /><category scheme="/tags/#gitlab-runner" term="gitlab-runner" />
  <content type="html">
  
    &lt;p&gt;In Rails 5.1 and later versions, system tests were introduced as a new type of test to simulate a user interacting with a web application. These tests use a headless browser, typically powered by Capybara and a WebDriver, to mimic a user’s actions like clicking buttons, filling forms, and navigating through the application.&lt;/p&gt;

&lt;h3 id=&quot;why-do-we-need-system-tests&quot;&gt;&lt;strong&gt;Why do we need System Tests?&lt;/strong&gt;&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://guides.rubyonrails.org/testing.html#system-testing&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;System tests&lt;/a&gt; let you test applications in the browser. Because system tests use a real browser experience, you can test all of your JavaScript easily from your test suite.&lt;/li&gt;
  &lt;li&gt;Typically used for:
    &lt;ul style=&quot;margin-left: 2rem&quot;&gt;
&lt;li&gt;&lt;strong&gt;Acceptance testing:&lt;/strong&gt; verify that the app has implemented a specific feature&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Smoke testing:&lt;/strong&gt; verify that the app is functional on a fundamental level and doesn&apos;t have code issues.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Characterization testing:&lt;/strong&gt; is a type of software testing that involves examining and documenting the behavior of an existing system or application without making any modifications to its code&lt;/li&gt;
&lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;margin-top: 2rem&quot;&gt;&lt;/div&gt;
&lt;h3 id=&quot;how-we-can-run-system-test&quot;&gt;&lt;strong&gt;How we can run System Test?&lt;/strong&gt;&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;System Test interacts with your app via an actual browser to run them.&lt;/li&gt;
  &lt;li&gt;From a technical perspective, system tests aren’t necessarily required to interact with a real browser; they can be set up to utilize the &lt;a href=&quot;https://github.com/rack/rack-test&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;rack test&lt;/a&gt; backend, which emulates HTTP requests and processes the HTML responses. While system tests based on rack_test run faster and more dependable than front-end tests involving an actual browser, they have notable limitations in mimicking a genuine user experience as they are incapable of executing JavaScript.&lt;/li&gt;
&lt;/ul&gt;

&lt;div style=&quot;margin-top: 2rem&quot;&gt;&lt;/div&gt;
&lt;h3 id=&quot;the-anatomy-of-a-system-test&quot;&gt;&lt;strong&gt;The Anatomy of a System Test?&lt;/strong&gt;&lt;/h3&gt;
&lt;div style=&quot;margin-bottom: 2rem&quot;&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/system-test/flow-chart.png&quot; width=&quot;425&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;margin-bottom: 2rem&quot;&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Minitest&lt;/strong&gt;
    &lt;ul style=&quot;margin-left: 2rem&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/seattlerb/minitest&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Minitest&lt;/a&gt; is a small and incredibly fast unit testing framework.&lt;/li&gt;
&lt;li&gt;It provides the base classes for test cases.
  For Rails System Tests, Rails provides an ApplicationSystemTestCase base class which is in turn based on  &lt;i&gt;ActionDispatch::SystemTestCase:&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;require &amp;quot;test_helper&amp;quot;

  class ApplicationSystemTestCase &amp;lt; ActionDispatch::SystemTestCase
    driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
  end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul style=&quot;margin-left: 2rem&quot;&gt;
  &lt;li&gt;In &lt;code&gt;ActionDispatch::SystemTestCase&lt;/code&gt; we require the &lt;code&gt;capybara/minitest&lt;/code&gt; library.&lt;/li&gt;
  &lt;li&gt;It provides basics assertions like &lt;strong&gt;assert_equal, assert_nil, assert_same, assert_raises, assert_includes&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;A runner to run the tests and report on their success and failure.&lt;/li&gt;
  &lt;/ul&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Capybara&lt;/strong&gt;&lt;/p&gt;

    &lt;ul style=&quot;margin-left: 2rem&quot;&gt;

  &lt;li&gt;&lt;a href=&quot;https://github.com/teamcapybara/capybara&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Capybara&lt;/a&gt; starts your app in a separate process before running the tests. This ensures that the tests are run against the correct version of your app.&lt;/li&gt;
  &lt;li&gt;Capybara provides a high-level API that makes it easy to write tests in a natural way. For example, you can write a test that says &lt;code&gt;&quot;click the button&quot;&lt;/code&gt; instead of having to write code to find the button and click it.&lt;/li&gt;
  &lt;li&gt;Here is an example of a test written with Capybara&apos;s DSL (Domain Specific Language):&lt;/li&gt;
&lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;visit(&amp;#39;/login&amp;#39;)
  fill_in(&amp;#39;email&amp;#39;, with: &amp;#39;user@example.com&amp;#39;)
  fill_in(&amp;#39;password&amp;#39;, with: &amp;#39;password&amp;#39;)
  click_button(&amp;#39;Login&amp;#39;)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Selenium-Webdriver&lt;/strong&gt;&lt;/p&gt;

    &lt;ul style=&quot;margin-left: 2rem&quot;&gt;
&lt;li&gt;Capybara uses the &lt;a href=&quot;https://rubygems.org/gems/selenium-webdriver/versions/4.11.0&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Selenium Webdriver&lt;/a&gt; library to interact with real browsers. Selenium WebDriver is a cross-platform library that provides a way to control web browsers from code. Capybara uses Selenium WebDriver to translate its high-level DSL (Domain Specific Language) into low-level commands that the browser can understand.&lt;/li&gt;
&lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;require &amp;quot;selenium-webdriver&amp;quot;

  driver = Selenium::WebDriver.for :firefox
  driver.navigate.to &amp;quot;http://google.com&amp;quot;

  element = driver.find_element(name: &amp;#39;q&amp;#39;)
  element.send_keys &amp;quot;Hello WebDriver!&amp;quot;
  element.submit

  puts driver.title

  Driver.quit&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul style=&quot;margin-left: 2rem&quot;&gt;
  &lt;li&gt;You can see how it’s a bit lower-level than the Capybara example further up. The selenium-webdriver library translates these calls into WebDriver Protocol, which it speaks to a webdriver executable.&lt;/li&gt;
  &lt;/ul&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Webdriver Protocol&lt;/strong&gt;&lt;/p&gt;

    &lt;ul style=&quot;margin-left: 2rem&quot;&gt;

&lt;li&gt;The Selenium WebDriver library translates its calls into the &lt;a href=&quot;https://www.w3.org/TR/webdriver2/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;WebDriver Protocol&lt;/a&gt;. The WebDriver Protocol is a HTTP-based wire protocol that is used to communicate between the Selenium WebDriver library and the web browser.&lt;/li&gt;
&lt;li&gt;In order to start a chrome browser window and navigate to google.com. We need to startup geckodriver.&lt;/li&gt;
&lt;li&gt;We send it a &lt;strong&gt;“new session”&lt;/strong&gt; command with a HTTP post request&lt;/li&gt;
&lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;curl -X POST &amp;#39;http://127.0.0.1:9515/session&amp;#39; -d &amp;#39;{&amp;quot;capabilities&amp;quot;:{&amp;quot;firstMatch&amp;quot;:[{&amp;quot;browserName&amp;quot;:&amp;quot;firefox&amp;quot;}]}}&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul style=&quot;margin-left: 2rem&quot;&gt;

  &lt;li&gt;This return a session id along with data&lt;/li&gt;
  &lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;{ ... &amp;quot;sessionId&amp;quot;:&amp;quot;f1776ba558e28309299dc5f62864e977&amp;quot; ... }&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul style=&quot;margin-left: 2rem&quot;&gt;

  &lt;li&gt;Then we make another post request with a session id. And url in data parameters&lt;/li&gt;
  &lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;curl -X POST &amp;#39;http://127.0.0.1:9515/session/f1776ba558e28309299dc5f62864e977/url&amp;#39; -d &amp;#39;{&amp;quot;url&amp;quot;: &amp;quot;https://google.com&amp;quot;}&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Webdriver&lt;/strong&gt;
    &lt;ul style=&quot;margin-left: 2rem&quot;&gt;

&lt;li&gt;Webdriver is a tool that speaks &lt;strong&gt;“Webdriver protocol”&lt;/strong&gt; and controls the browser.&lt;/li&gt;
&lt;li&gt;Every major browser there is an associated webdriver tool. Chrome has &lt;a href=&quot;https://sites.google.com/a/chromium.org/chromedriver/home&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;chromedriver&lt;/a&gt;. Firefox has a &lt;a href=&quot;https://github.com/mozilla/geckodriver&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;geckodriver&lt;/a&gt;. MS Edge has &lt;a href=&quot;https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;edgedriver&lt;/a&gt;. Safari has &lt;a href=&quot;https://developer.apple.com/documentation/webkit/testing_with_webdriver_in_safari&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;safaridriver&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;WebDriver tools act as servers: when you execute them, they start a persistent process that listens for HTTP requests until it is terminated.&lt;/li&gt;
&lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;margin-bottom: 1rem&quot;&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Webdrivers gem&lt;/strong&gt;
    &lt;ul style=&quot;margin-left: 2rem&quot;&gt;

&lt;li&gt;Before selenium-webdriver 4.11, &lt;a href=&quot;https://github.com/titusfortner/webdrivers&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;webdrivers&lt;/a&gt; gem automatically determines which WebDriver executable needs to be downloaded for your platform and selected browser, downloads it, and arranges for that executable to be used by selenium-webdriver.&lt;/li&gt;
&lt;li&gt;From version 4.11, they have incorporated the functionality in selenium-webdriver gem using &lt;a href=&quot;https://www.selenium.dev/blog/2023/whats-new-in-selenium-manager-with-selenium-4.11.0/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;selenium-manager&lt;/a&gt;.&lt;/li&gt;

&lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/blog/images/system-test/webdriver.png&quot; width=&quot;425&quot; /&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 2rem&quot;&gt;&lt;/div&gt;

&lt;h3 id=&quot;running-rails-7-system-tests-with-docker-and-gitlab-runner-on-arm64-and-amd64-linux-machines&quot;&gt;&lt;strong&gt;Running Rails 7 System Tests with Docker and Gitlab Runner on Arm64 and Amd64 linux machines&lt;/strong&gt;&lt;/h3&gt;
&lt;div style=&quot;margin-top: 2rem&quot;&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Prepare the Rails 7 application for testing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Run the command below to generate a very basic Ruby on Rails 7 app:&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;rails new minitest-rails-app&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Go ahead and open up the project in your favourite editor and proceed to the Gemfile, specifically to the test block:&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;group :test do
    # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
    gem &amp;quot;capybara&amp;quot;
    gem &amp;quot;selenium-webdriver&amp;quot;
    gem &amp;quot;webdrivers&amp;quot;
  end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Next, let’s do a quick scaffold generation to have something to work with:&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;rails generate scaffold Blog title:string body:text&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Usually, generating a scaffold will automatically generate the &lt;code&gt;application_system_test_case.rb&lt;/code&gt; and everything you need for the system tests&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;application_system_test_case.rb (default) 
  
  require &amp;quot;test_helper&amp;quot;
  
  class ApplicationSystemTestCase &amp;lt; ActionDispatch::SystemTestCase
    driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
  end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Run the database commands&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;rails db:setup
  rails db:migrate&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Running a Basic System For the First Time&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;rails test:system&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Exclude the gem webdrivers from the list of dependencies&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Before selenium-webdriver 4.11, webdrivers gem automatically download webdriver executable.&lt;/li&gt;
  &lt;li&gt;From version 4.11, they have incorporated the functionality in selenium-webdriver gem using selenium-manager.&lt;/li&gt;
  &lt;li&gt;We can comment out the webdrivers line from Gemfile.&lt;/li&gt;
  &lt;li&gt;After change, &lt;code&gt;Gemfile&lt;/code&gt; looks like this&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;group :test do
  # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
  gem &amp;quot;capybara&amp;quot;
  gem &amp;quot;selenium-webdriver&amp;quot;, &amp;quot;~&amp;gt; 4.11&amp;quot;
  #gem &amp;quot;webdrivers&amp;quot;
  end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Point the Selenium-webdriver to use the firefox browser&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;As chrome has not released binary compatible with &lt;code&gt;linux/arm64&lt;/code&gt; machine. So the test failed on the arm64 linux machine. I tried multiple approaches to make it work with headless_chrome, but didn’t work and commend the issue in details in this  &lt;a href=&quot;https://github.com/titusfortner/webdrivers/issues/213#issuecomment-1686094017&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;issue tracker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;We need to change the browser to the firefox.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;#application_system_test_case.rb (change driver to Firefox)
 
  require &amp;quot;test_helper&amp;quot;
  
  class ApplicationSystemTestCase &amp;lt; ActionDispatch::SystemTestCase
    driven_by :selenium, using: :firefox, screen_size: [1400, 1400]
  end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Prepare the docker image&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Create &lt;code&gt;Dockerfile&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;FROM ruby:3.1.2-slim-buster

  RUN apt-get update
  RUN apt-get -y install gnupg curl wget xvfb unzip

  ENV NODE_VERSION 19

  RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -  &amp;amp;&amp;amp; \
  apt-get install --yes nodejs &amp;amp;&amp;amp; \
  apt-get install --yes libxss1 libappindicator1 libindicator7 python2

  RUN apt-get update &amp;amp;&amp;amp; \
  apt-get install --yes software-properties-common build-essential libssl-dev sqlite3 libsqlite3-dev pkg-config ca-certificates firefox-esr

  RUN apt-get install -y git-all
  RUN npm install yarn -g
  ADD . /data&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;This Dockerfile sets up an image with Ruby 3.1.2 and Node.js 19 installed. It installs system dependencies like Git, Yarn, various libraries for sqlite and Firefox.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Build Docker image&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;docker buildx build -t dockermanishelitmus/systemtest-rails-app:latest1.0 . --platform linux/amd64,linux/arm64 --push&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Command is building a Docker image using the buildx extension, targeting two different platforms (Intel/AMD 64-bit and ARM 64-bit), tagging the image as latest1.0, and pushing the resulting image to a container registry.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Prepare the gitlab-runner&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;In the project root directory create a file &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; with content&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;image: &amp;quot;dockermanishelitmus/systemtest-rails-app:latest1.0&amp;quot;
services:
 - redis:latest
variables:
 RAILS_ENV: &amp;quot;test&amp;quot;

cache:
 paths:
   - vendor/ruby
   - node_modules/

before_script:
 - gem install bundler  --no-document
 - bundle config set force_ruby_platform true
 - bundle install
 - bin/rake db:drop
 - bin/rake db:setup
 - bin/rake db:migrate

stages:
 - tests

SystemTests:
 stage: tests
 script:
   - yarn install
   - bin/rake assets:precompile
   - bin/rails test:system
 artifacts:
   when: on_failure
   name: &amp;quot;$CI_JOB_NAME-$CI_COMMIT_REF_NAME&amp;quot;
   paths:
     - coverage/
   expire_in: 1 day&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Finally run your test suite&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;gitlab-runner exec docker SystemTests&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Output&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;$ bin/rails test:system
  Running 4 tests in a single process (parallelization threshold is 50)
  Run options: --seed 13031

  # Running:

  Capybara starting Puma...
  * Version 5.6.7 , codename: Birdie&amp;#39;s Version
  * Min threads: 0, max threads: 4
  * Listening on http://127.0.0.1:33385
  ....

  Finished in 7.865541s, 0.5085 runs/s, 0.5085 assertions/s.
  4 runs, 4 assertions, 0 failures, 0 errors, 0 skips
  Saving cache for successful job
  Creating cache SystemTests/main...
  WARNING: vendor/ruby: no matching files. Ensure that the artifact path is relative to the working directory
  node_modules/: found 2 matching files and directories
  No URL provided, cache will not be uploaded to shared cache server. Cache will be stored only locally.
  Created cache
  Job succeeded&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;Now we have a setup that enables us to run system tests in both arm64 and amd64 linux machines with minimal customizations we may want to add. A few tips and tricks should help to get your first system tests up and running in CI pipeline.&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;/technology/demystifying-rails-7-system-tests-configuring-ci-pipeline/&quot;&gt;Demystifying Rails 7 System Tests: Configuring CI Pipeline&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on August 28, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Interview-Platform:Insights and Learnings]]></title>
  <link rel="alternate" type="text/html" href="/technology/interview-platform-insights-and-learnings/"/>
  <id>/technology/interview-platform-insights-and-learnings</id>
  <updated>2023-07-27 13:58:13 +0530T00:00:00-00:00</updated>
  <published>2023-07-27T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#video-conferencing" term="video-conferencing" /><category scheme="/tags/#socket-io" term="socket-io" /><category scheme="/tags/#webrtc" term="webrtc" /><category scheme="/tags/#mediasoup" term="mediasoup" />
  <content type="html">
  
    &lt;p&gt;An interview platform is a platform that offers a digital answer to the interview procedure. The conventional hiring procedure is frequently ineffective, time-consuming, and difficult. The platform was developed to address the issue by streamlining the hiring process and offering an effective means to carry it out.&lt;/p&gt;

&lt;h2 id=&quot;requirements&quot;&gt;Requirements&lt;/h2&gt;

&lt;p&gt;It was chosen to incorporate the following features in the application taking into account current interview circumstances, which are as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Multi-User Conferencing&lt;/li&gt;
  &lt;li&gt;The ability to communicate via video and voice,&lt;/li&gt;
  &lt;li&gt;Chat feature&lt;/li&gt;
  &lt;li&gt;Screen-sharing Capabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;exploring-the-applications-fundamentals&quot;&gt;Exploring the Application’s Fundamentals&lt;/h2&gt;

&lt;p&gt;To build a solution incorporating the above features it is necessary to have a clear understanding of the following concepts.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;WebRTC (Web Real-Time Communication): It is a free and open-source project that provides web browsers and mobile applications with real-time communication (RTC) via application programming interfaces (APIs). It allows audio and video communication to work inside web pages by allowing direct peer-to-peer communication.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Socket IO: It is a JavaScript library that enables real-time, bidirectional communication between the server and clients. By leveraging WebSockets, it establishes persistent connections, allowing instantaneous data exchange.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Websocket: It is a protocol that provides a full-duplex communications channel over a network connection. WebRTC is standardised on WebSocket as the way to send information from a web browser to the signalling server and vice versa.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Adapter JS: It takes care of the differences between browsers, so developers don’t have to worry about compatibility issues and can focus on building their applications.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Signaling: The signalling process involves exchanging messages between two peers using an intermediary, the signalling server. WebRTC does not define a signalling protocol.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;RTCPeerConnection: RTCPeerConnection is a web API in the JavaScript language used for enabling real-time communication (RTC) between web browsers. It is a fundamental part of the Web Real-Time Communication (WebRTC) technology&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Media Servers: A media server is a device or software that stores digital media such as video, audio, or images and makes it available over a network.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;SDP (Session Description Protocol): A protocol for describing media communication sessions is used. It is used for peer-to-peer negotiation of different audio and video codecs, network topologies, and other device characteristics but does not deliver the media data.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let us understand briefly what signalling taking an example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;User A creates an offer that contains its local SDP.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;User A attaches that offer to something known as an RTCPeerConnection object.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;User A sends its offer to the signalling server using WebSocket.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;User B receives User A’s offer using WebSocket.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;User B creates an answer containing her local SDP.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;User B attaches its answer, along with User A’s offer, to its RTCPeerConnection object.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;User B returns its answer to the signalling server using WebSocket.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;User A receives User B’s offer using WebSocket.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may have a basic understanding of the technology used to create the solution up to this point, so let’s take a quick look at the approach used to develop the solution.&lt;/p&gt;

&lt;h2 id=&quot;approach-to-build-the-solution&quot;&gt;Approach to Build the Solution&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;The first milestone was accomplished achieved after finishing room functionality, which essentially means that a unique meeting can be made and participants can enter the room with a unique id.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;To make the application more purpose-specific, admin features and a chat component were included. With this feature, the interviewer has more control over the meeting, and participants can only participate after being accepted by the admin.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;It was important to understand how to use the AWS platform and how to incorporate networking concepts to host the application.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;architecture-to-be-used&quot;&gt;Architecture to be used:&lt;/h2&gt;

&lt;p&gt;Different Architecture that can be used to enable Multiple User Video Calling Apps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Mesh Topology&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;SFU Topology&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;MCU Topology&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;/blog/images/interview-platform-insights-and-learnings/topology.png&quot; alt=&quot;Image&quot; width=&quot;600&quot; /&gt;
&lt;/p&gt;

&lt;h2 id=&quot;comparing-the-mesh--sfu-topology-to-be-used&quot;&gt;Comparing the Mesh &amp;amp; SFU Topology to be used:&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;In the Mesh architecture a user when joining the room, needs to establish a connection with every other present in the room.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;And in turn the guests present needs to establish a connection with the new user.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Mesh architecture can be suitably implemented in a group of 3-4 people.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The advantage is that it is less expensive and less complex.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The disadvantage lies in the extent of scalability.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;SFU topology has an edge over the Mesh taking into Scalability as a factor.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The bandwidth decreases taking the number of participants to be constant in both cases.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The one disadvantage is that it is more complex and relatively difficult to implement.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;topology-to-be-followed&quot;&gt;Topology to be followed:&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Selective forwarding unit&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;In this various clients connect to the media server.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Here P2P is taken into use.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Each device is connected to the server.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The server combines them in the stream and puts them in a single stream.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;comparision-between-mediasoup-and-kurento&quot;&gt;Comparision between Mediasoup and Kurento&lt;/h2&gt;

&lt;p&gt;Until now we have a fair idea of which topology we need to implement in the application. We need a media server to get implemented for Selective Forwarding Unit. There are many options available in media servers that we can proceed but taking into multiple factors we are left with two options and we need to proceed with one.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Media connections are established 80% quicker than Kurento&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Provides rich scalability and performance with its robust selective forwarding unit&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Can be used as a NodeJS package or a Rust library&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;MediaSoup is designed to work in a distributed environment, making it suitable for large-scale deployments. It can handle multiple rooms and numerous concurrent calls, which aligns with your scalability requirement&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;MediaSoup has good community support and regular updates over time&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;MediaSoup allows you to write automated test cases for all the features, helping ensure the stability and functionality of your application.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;insights-of-the-application&quot;&gt;Insights of the application&lt;/h2&gt;

&lt;p&gt;We currently have a good understanding of the architecture that will be used in the application. According to the demands of the application, SFUs (single forwarding units) will be deployed. And we conclude that we will keep using Mediasoup as the application’s media server.&lt;/p&gt;

&lt;p&gt;Talking about the features to be implemented, which include audio and video chat and screen sharing, we need to have a better understanding.&lt;/p&gt;

&lt;p&gt;The application requests authorization to use the available camera and microphone to establish communication and transfer media, such as video and audio.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;navigator.mediaDevices 
	.getDisplayMedia({ video: true, }) 
	.then(streamSuccess) 
	.catch((err) =&amp;gt; { console.log(err); });&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The ability to share the screen must be implemented in addition to audio and video. To do this, we can use the code snippet below.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;navigator.mediaDevices 
	.getDisplayMedia({ video: true, }) 
	.then(streamSuccess) 
	.catch((err) =&amp;gt; { console.log(err); });&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As fetching the streams we can proceed with the following steps of implementing the mediasoup architecture. Here is a brief overview of the steps implemented.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Create a media device (mediasoup-client) for capturing media.&lt;/li&gt;
  &lt;li&gt;Create a transport for sending media to the server.&lt;/li&gt;
  &lt;li&gt;Connect the send transport and produce audio/video tracks.&lt;/li&gt;
  &lt;li&gt;Signal a new consumer transport for a remote producer.&lt;/li&gt;
  &lt;li&gt;Get the list of available producers from the server.&lt;/li&gt;
  &lt;li&gt;Connect the receiving transport and consume remote media.&lt;/li&gt;
  &lt;li&gt;Render the local video, screen sharing video, and other controls.&lt;/li&gt;
  &lt;li&gt;Implement event handlers for muting, camera, and screen sharing toggles.&lt;/li&gt;
  &lt;li&gt;Implement event handlers for leaving the meeting and accepting new users.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;building-the-solution&quot;&gt;Building the Solution&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://react.dev/&quot;&gt;React Js&lt;/a&gt; is used to develop the application’s front end. &lt;a href=&quot;https://redux.js.org/&quot;&gt;Redux&lt;/a&gt; is utilised for state management, and after establishing the page routes, work on UI design began.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;After reading the documentation, we began implementing Mediasoup in the backend and preferred &lt;a href=&quot;https://nodejs.org/en&quot;&gt;Node Js&lt;/a&gt; as the backend framework.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is an overview of the Mediasoup WebRTC server application. The application is built using Node.js, &lt;a href=&quot;https://expressjs.com/&quot;&gt;Express&lt;/a&gt;, &lt;a href=&quot;https://www.mongodb.com/&quot;&gt;MongoDB&lt;/a&gt;, and &lt;a href=&quot;https://socket.io/&quot;&gt;Socket.IO&lt;/a&gt; to facilitate real-time communication and media streaming.&lt;/p&gt;

&lt;h3 id=&quot;dependencies&quot;&gt;Dependencies&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;dotenv&lt;/code&gt;: Loads environment variables from a &lt;code&gt;.env&lt;/code&gt; file.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;express&lt;/code&gt;: Web framework for building the server.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;httpolyglot&lt;/code&gt;: Provides HTTPS server functionality for secure communication.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;socket.io&lt;/code&gt;: Enables WebSocket communication for real-time events.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;mediasoup&lt;/code&gt;: A WebRTC media server library for media processing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;server-setup&quot;&gt;Server Setup&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Import required modules and set up the Express server.&lt;/li&gt;
  &lt;li&gt;Create an HTTPS server using &lt;code&gt;httpolyglot&lt;/code&gt; for secure communication with SSL certificates.&lt;/li&gt;
  &lt;li&gt;Connect to MongoDB using Mongoose to store user information and other data.&lt;/li&gt;
  &lt;li&gt;Define the MongoDB schema for the “users” collection.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;mediasoup-integration&quot;&gt;Mediasoup Integration&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Create a Mediasoup worker to manage media processing.&lt;/li&gt;
  &lt;li&gt;Define media codecs for audio and video.&lt;/li&gt;
  &lt;li&gt;Set up Socket IO to handle WebSocket communication for real-time media streams.&lt;/li&gt;
  &lt;li&gt;Create maps and arrays to manage Mediasoup peers, transports, producers, and consumers.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;socket-io-event-handlers&quot;&gt;Socket IO Event Handlers&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Implement event handlers for various actions:
    &lt;ul&gt;
      &lt;li&gt;Joining a room and creating WebRTC transports.&lt;/li&gt;
      &lt;li&gt;Joining a room and creating WebRTC transports.&lt;/li&gt;
      &lt;li&gt;Producing and consuming media.&lt;/li&gt;
      &lt;li&gt;Sending and receiving messages between peers.&lt;/li&gt;
      &lt;li&gt;Confirming admin status and accepting user requests.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;mediasoup-transports&quot;&gt;Mediasoup Transports&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Implement functions to create Mediasoup WebRTC transports with specific options.&lt;/li&gt;
  &lt;li&gt;Handle transport events like DTLS state changes and transport closure.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;final-setup&quot;&gt;Final Setup&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Create the Express app and set it to listen on the specified port (default: 3002).&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;deployment&quot;&gt;Deployment&lt;/h3&gt;
&lt;ol&gt;
  &lt;li&gt;Dockerize the application&lt;/li&gt;
&lt;/ol&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-docker&quot; data-lang=&quot;docker&quot;&gt;FROM node:20 
WORKDIR /app 
COPY package*.json ./ 
RUN npm cache clean --force 
COPY . . 
EXPOSE 8000 
CMD [ &amp;quot;npm&amp;quot;, &amp;quot;start&amp;quot; ]&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ol&gt;
  &lt;li&gt;Initialize an EC2 Instance on AWS&lt;/li&gt;
  &lt;li&gt;Run the docker container with Ngnix WebServer&lt;/li&gt;
  &lt;li&gt;Expose TCP UDP Ports for EC2 Instance for media transmission.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;challenges-faced&quot;&gt;Challenges Faced&lt;/h2&gt;

&lt;p&gt;During building the product the major problem I faced was to establish a connection between the clients using the application. Every Client who was joining was producing media but the media was not transporting to other clients. After debugging and revamping the application’s state, the conclusion was drawn that the React state was not behaving as decided and solved the issue after fixing it. During development, the socket instance needs to be properly handled so that it gets mapped to the proper room and doesn’t get broadcasted to every other instance.&lt;/p&gt;

&lt;p&gt;During deployment, the major issue was assigning the proper IP address to be used in the application as the application has the requirement of broadcasting the IP address to every applicant with a public IP. After using the Amazon EC2 instance the issue was solved and then I implemented Docker to containerize the application and run it with the nginx server.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;/blog/images/interview-platform-insights-and-learnings/app_ss.png&quot; alt=&quot;Image&quot; width=&quot;600&quot; /&gt;
&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;The first version of the application is built with the multi-user functionality joining with their desired choice of media.&lt;/li&gt;
  &lt;li&gt;The admin can accept the desired user and on accepting the user can enter the room.&lt;/li&gt;
  &lt;li&gt;The participants can communicate over audio, see each other’s video and chat in the room. The entire process of building the application is a great chance of learning and rewarding experience and future improvements can be done to make the application more consistent and reliable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://openvidu.medium.com/a-new-era-for-openvidu-better-perfomance-and-media-quality-with-mediasoup-24d46a9eb10d&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Comparision of Kurento and Mediasoup Mediaserver&lt;/a&gt;&lt;/p&gt;


  
  &lt;p&gt;&lt;a href=&quot;/technology/interview-platform-insights-and-learnings/&quot;&gt;Interview-Platform:Insights and Learnings&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on July 27, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Building a Frontend Scoring Engine: Automating Frontend Evaluation]]></title>
  <link rel="alternate" type="text/html" href="/technology/building-a-frontend-scoring-engine-automating-frontend-evaluation/"/>
  <id>/technology/building-a-frontend-scoring-engine-automating-frontend-evaluation</id>
  <updated>2023-10-05 20:17:46 +0530T00:00:00-00:00</updated>
  <published>2023-07-21T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#frontend%20development" term="frontend development" /><category scheme="/tags/#evaluation" term="evaluation" /><category scheme="/tags/#automation" term="automation" /><category scheme="/tags/#testing" term="testing" /><category scheme="/tags/#coding" term="coding" />
  <content type="html">
  
    &lt;p&gt;The frontend scoring engine is a powerful tool designed to assess the frontend skills of candidates based on code quality, responsiveness, and functionality. It aims to streamline the evaluation process for frontend development by automating the assessment of code quality, best practices, and functionality.&lt;/p&gt;

&lt;h2 id=&quot;what-youll-learn-from-this-blog&quot;&gt;&lt;strong&gt;What you’ll learn from this blog&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;In this blog, we will dive into the technical aspects of building a frontend scoring engine.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The need for frontend scoring engine in today’s technology landscape.&lt;/li&gt;
  &lt;li&gt;The technical requirements gathering and Research phase involved.&lt;/li&gt;
  &lt;li&gt;Generation of Test script for Test automation using Puppeteer.&lt;/li&gt;
  &lt;li&gt;Dockerizing the Application.&lt;/li&gt;
  &lt;li&gt;Features and Process of building the application.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;need-for-the-frontend-scoring-engine&quot;&gt;&lt;strong&gt;Need for the Frontend Scoring Engine&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;In today’s technology-driven world, the demand for skilled frontend developers is at an all-time high. With the rapid evolution of web applications and user interfaces, companies are constantly seeking talented individuals who can create visually appealing, intuitive, and responsive frontend experiences. However, evaluating frontend development skills can be a complex and time-consuming task. This is where a frontend scoring engine comes into play Automating the Evaluation Process, Measurement of Code Quality and Ensuring Mobile Responsiveness. By allowing users to input HTML, CSS and JavaScript code, and generating scores based on predefined test cases, the scoring engine provides a comprehensive evaluation of candidates’ frontend skills.&lt;/p&gt;

&lt;h2 id=&quot;research-work&quot;&gt;&lt;strong&gt;Research Work&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;Before starting the implementation of the frontend scoring engine project, extensive research was conducted to understand the need for such a system, evaluate existing systems, explore testing tools, and plan the evaluation process. This research phase played a crucial role in shaping the project and ensuring its successful execution. Let’s take a brief look on highlight and the key areas of research conducted during the project’s inception.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Evaluating Existing Systems&lt;/strong&gt; :
To gain insights into the existing solutions available in the market, a comprehensive evaluation of similar systems was conducted. Various frontend scoring engines, online code editors were explored to understand their features, functionalities, strengths, and weaknesses. This evaluation provided valuable insights that influenced the design decisions and feature set of the new scoring engine. &lt;br /&gt;
Some similar existing systems:
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://codier.io/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Codier.io&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://www.frontendmentor.io/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Frontend Mentor&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://cssbattle.dev&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;CSS Battle&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://www.algoexpert.io/frontend/product&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Algoexpert.io Frontend&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Testing Tools and Technologies&lt;/strong&gt; :
During our research, we explored various testing tools and technologies to find the perfect fit for executing test cases, assessing code quality, and evaluating frontend functionalities. The evaluation revolved around factors like capabilities, ease of use, and compatibility with our project requirements. Tools such as Selenium, Cypress, Jest, csslint, eslint were taken into consideration.&lt;br /&gt;
Read more about the tools:
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://www.selenium.dev/documentation/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Selenium&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://docs.cypress.io/guides/overview/why-cypress&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Cypress&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://jestjs.io/docs/getting-started&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Jest&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Puppeteer&lt;/strong&gt; :
Puppeteer was chosen over Selenium primarily due to its compatibility with Docker and its ability to control headless Chrome or Chromium instances. Docker provides an efficient and scalable environment for running tests, and Puppeteer seamlessly integrates with Docker containers. Additionally, Puppeteer offers a more modern and concise API, making it easier to write test scripts and perform browser automation tasks.
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://oxylabs.io/blog/puppeteer-vs-selenium&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Puppeteer vs Selenium&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://pptr.dev/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Puppeteer Docs&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Docker Integration&lt;/strong&gt; :
We explored the benefits of Docker, a widely-used containerization platform, and discovered how it could greatly enhance our project. Docker allows us to create lightweight, portable, and isolated containers, which provide a consistent and reproducible environment. Leveraging Docker, we encapsulated and ran our scoring engine, testing tools, and other dependencies, ensuring seamless integration and efficient execution. &lt;br /&gt;
We pulled various Docker images from Docker Hub, enabling us to set up the required tools effortlessly.
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://hub.docker.com/r/eeacms/csslint&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;csslint&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://hub.docker.com/r/cytopia/eslint&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;eslint&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://hub.docker.com/r/cfreak/jest&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;jest&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Real-Time Code Editor&lt;/strong&gt; :
To provide a user-friendly and real-time code editing experience, we started searching for frontend code editors and existing projects available on GitHub. Various code editor projects were evaluated, and their source code were studied to understand the implementation details. This research helped in selecting the most suitable code editor framework and implementing it within our frontend scoring engine. &lt;br /&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://codepen.io/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Codepen&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://www.fronteditor.dev/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Fronteditor&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://github.com/Prince-Codemon/Code-G-The-Coding-Playground-&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;CodeG&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Problem Statement and Test Case Creation&lt;/strong&gt; :
The goal was to design problem statements that accurately reflect real-world frontend development challenges and create test cases that thoroughly evaluate candidates’ code. Puppeteer test scripts were written to simulate user interactions, perform assertions, and capture screenshots for image comparison using the PixelMatch JavaScript library.&lt;br /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Cloud Deployment and Infrastructure&lt;/strong&gt; :
For our final Deployment and integration Amazon Web Services (AWS) was choosen. The research covered various AWS services, including EC2 instances for hosting the scoring engine, S3 for storage, and other relevant services for infrastructure setup. The deployment process, security considerations, and scaling options were thoroughly explored to ensure a robust and scalable deployment architecture.&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;test-script-generation&quot;&gt;&lt;strong&gt;Test Script Generation&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;In the frontend scoring engine, we ensure evaluation of user-submitted HTML, CSS, and JavaScript code by subjecting it to comprehensive testing against predefined test cases. These tests are designed to assess the code quality, functionality, and adherence to best practices, providing a total assessment of candidates’ frontend development skills. By conducting these thorough evaluations, we can accurately determine the proficiency of developers in creating efficient and reliable frontend solutions. Throughout this section, you’ll get an overview of the various types of tests performed, explaining their significance in evaluating code quality and functionality.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Heading/Element Testing&lt;/strong&gt;
This test focuses on ensuring the presence and correctness of specific HTML elements within the user’s code. Test cases are designed to check if required headings, such as h1, h2, p or specific elements identified by ID or class, are present. The purpose of this test is to assess the structure and semantic correctness of the user’s HTML code.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;CSS Properties Testing&lt;/strong&gt;
This test aims to verify the correct usage of CSS properties in the user’s code. It includes checking for the presence of essential CSS properties, such as margin, padding, font-size, or specific properties required for a particular problem statement. This test ensures that the user’s code adheres to the defined CSS requirements and best practices.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Form Validation Testing&lt;/strong&gt;
Form validation testing focuses on assessing the user’s code for proper form validation techniques. Test cases can include checking for required fields, validating email formats, enforcing password complexity, or implementing custom validation logic. This test ensures that the user’s code handles form validation correctly and provides appropriate error messages.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Function Testing&lt;/strong&gt;
This test evaluates the functionality and correctness of JavaScript functions implemented by the user. Test cases are designed to cover different scenarios and edge cases to ensure that the functions perform as expected. This test assesses the user’s ability to write functional and efficient JavaScript code.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;API Testing&lt;/strong&gt;
API testing involves verifying the integration of API calls in the user’s code. Test cases may include checking if an API request is made, handling the API response correctly, and displaying the data from the API on the page. This test ensures that the user’s code effectively interacts with external APIs.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Button Testing&lt;/strong&gt;
Button testing focuses on evaluating the behavior and interactivity of buttons implemented by the user. Test cases may include checking if a button triggers a specific action, updates the UI, or performs a navigation action. This test ensures the proper functionality of user-defined buttons.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Redirection Testing&lt;/strong&gt;
This test aims to assess the behavior of navigation and redirection implemented by the user’s code. Test cases may include checking if clicking a link or a button redirects the user to the correct page or if the page refreshes as intended. This test ensures that the user’s code correctly handles navigation and redirection scenarios.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;dockerizing-the-puppeteer-with-chrome-browser-support&quot;&gt;&lt;strong&gt;Dockerizing the Puppeteer with Chrome Browser Support&lt;/strong&gt;&lt;/h2&gt;

&lt;h4 id=&quot;dockerfile&quot;&gt;Dockerfile:&lt;/h4&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;# Use the node:slim base image

FROM node:slim

# Set an environment variable to skip Puppeteer Chromium download during installation

ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
RUN apt-get update &amp;amp;&amp;amp; apt-get install gnupg wget -y &amp;amp;&amp;amp; \
 wget --quiet --output-document=- https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor &amp;gt; /etc/apt/trusted.gpg.d/google-archive.gpg &amp;amp;&amp;amp; \
 sh -c &amp;#39;echo &amp;quot;deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main&amp;quot; &amp;gt;&amp;gt; /etc/apt/sources.list.d/google.list&amp;#39; &amp;amp;&amp;amp; \
 apt-get update &amp;amp;&amp;amp; \
 apt-get install google-chrome-stable -y --no-install-recommends &amp;amp;&amp;amp; \
 rm -rf /var/lib/apt/lists/\*

# Set the working directory inside the container

WORKDIR /usr/src/app

# Copy the package.json file to the working directory

COPY package.json ./

# Install project dependencies using npm

RUN npm install

# Expose port 3000 to allow access to the app outside the container

EXPOSE 3000

# Run the app using the &amp;quot;npm test&amp;quot; command when the container starts

CMD [&amp;quot;npm&amp;quot;, &amp;quot;test&amp;quot;]&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;build-command&quot;&gt;Build command:&lt;/h4&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;docker build -t bhushan21z/puppchrome .&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;publish-it-to-docker-hub&quot;&gt;Publish it to Docker Hub:&lt;/h4&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;docker push bhushan21z/puppchrome:tagname&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;pull-commnd&quot;&gt;Pull commnd:&lt;/h4&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;docker pull bhushan21z/puppchrome&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;run-command&quot;&gt;Run command:&lt;/h4&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;docker run -it --rm -v $(pwd)/files:usr/src/app/files puppeteerchrome&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;features-and-architecture&quot;&gt;&lt;strong&gt;Features and Architecture&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/frontend-scoring-engine/frontend_scoring_engine_architecture.png&quot; alt=&quot;Application Architecture&quot; /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;h4 id=&quot;scoring-engine&quot;&gt;&lt;strong&gt;Scoring Engine:&lt;/strong&gt;&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;Inputs: The scoring engine takes HTML, CSS, and JavaScript files created by users on the client side, as well as the test cases file generated on the backend.&lt;/li&gt;
  &lt;li&gt;Code Quality Assessment: The engine assesses code quality using ESLint CSSlint and similar tools.&lt;/li&gt;
  &lt;li&gt;Scoring: The engine generates a score based on code quality, along with the results of the test cases executed on the client-side code.&lt;/li&gt;
  &lt;li&gt;Modular Architecture: The scoring engine is a separate entity, independent of the frontend and backend code.&lt;/li&gt;
  &lt;li&gt;Technology Stack: Python Flask framework is used to implement the scoring engine.&lt;/li&gt;
  &lt;li&gt;Working: Flask runs various Docker run commands to execute test script.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;backend&quot;&gt;&lt;strong&gt;Backend:&lt;/strong&gt;&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;MySql Database: Schema Created with various tables such as users, questions, testcases and submissions.&lt;/li&gt;
  &lt;li&gt;Node JS: Express framework is used to implement Rest APIs.&lt;/li&gt;
  &lt;li&gt;User auth: Contains user register and login APIs.&lt;/li&gt;
  &lt;li&gt;Questions: Questions create/get APIs.&lt;/li&gt;
  &lt;li&gt;Test Cases: Testcases create/get APIs and joining it with Questions table with question id as foreign key.&lt;/li&gt;
  &lt;li&gt;Scoring Engine: POST request to get user data and sending it to scoring engine and returning scoring engine response to frontend.&lt;/li&gt;
  &lt;li&gt;Submissions: User Submissions create/get APIs and joing it with users table and questions table.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;frontend-admin-side&quot;&gt;&lt;strong&gt;Frontend (Admin Side):&lt;/strong&gt;&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;Problem Creation: Admins can create problem statements, describing the problem to be solved.&lt;/li&gt;
  &lt;li&gt;Problem Settings: Problems can include various settings such as score weightage, best practices to check, and mobile responsiveness evaluation.&lt;/li&gt;
  &lt;li&gt;Test Cases: Admins can add multiple test cases related to each problem statement.&lt;/li&gt;
  &lt;li&gt;Test Case Visibility: Some test case outputs will be visible to users, while others will be hidden, showing only whether the score passed or failed.&lt;/li&gt;
  &lt;li&gt;User-Friendly Test Case Creation: Adding test cases are straightforward, even for users with limited programming knowledge.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;frontend-client-side&quot;&gt;&lt;strong&gt;Frontend (Client Side):&lt;/strong&gt;&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;Problem List: Users can view a list of problems on their screen.&lt;/li&gt;
  &lt;li&gt;Code Editor: Users can write HTML, CSS, and JavaScript code for each problem, similar to the CodePen editor.&lt;/li&gt;
  &lt;li&gt;Code Compilation: Users can compile their code and generate the output.&lt;/li&gt;
  &lt;li&gt;Score Display: Users can view the scores generated by the scoring engine based on the performed test cases.&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;tools--technologies&quot;&gt;&lt;strong&gt;Tools &amp;amp; Technologies&lt;/strong&gt;&lt;/h2&gt;

&lt;h4 id=&quot;frontend&quot;&gt;&lt;strong&gt;Frontend:&lt;/strong&gt;&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;ReactJS is used develop the frontend of the scoring engine.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;backend-1&quot;&gt;&lt;strong&gt;Backend:&lt;/strong&gt;&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Node.js is employed for building the backend of the scoring engine.&lt;/li&gt;
  &lt;li&gt;MySQL is used as the database management system.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;scoring-engine-1&quot;&gt;&lt;strong&gt;Scoring Engine&lt;/strong&gt;&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Puppeteer is used for implementing testcases and browser testing.&lt;/li&gt;
  &lt;li&gt;Docker containers are utilized for testing code quality and running test cases.&lt;/li&gt;
  &lt;li&gt;Flask is used to make scoring engine server which takes data and interacts with docker.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;By implementing a frontend scoring engine, we can automate frontend development evaluation, resulting in a streamlined and efficient assessment process. This blog has explored the goals, research, features, technical requirements, and tools and technologies involved in developing a frontend scoring engine. The automation of code assessment, real-time editing, and integration of testing tools have resulted in an efficient and comprehensive evaluation platform. The challenges we faced during development have strengthened our understanding of frontend development and inspired innovative solutions. As we move forward, we remain committed to enhancing the scoring engine to meet the evolving needs of the tech industry. &lt;br /&gt;
If you have any questions, doubts or suggestions feel free to reach out to me on &lt;a href=&quot;https://www.linkedin.com/in/bhushan-wanjari-952042213/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;/technology/building-a-frontend-scoring-engine-automating-frontend-evaluation/&quot;&gt;Building a Frontend Scoring Engine: Automating Frontend Evaluation&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on July 21, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Revamping eLitmus.com | Stand-Alone Front-end Module]]></title>
  <link rel="alternate" type="text/html" href="/technology/revamping-elitmus-dot-com-stand-alone-front-end-module/"/>
  <id>/technology/revamping-elitmus-dot-com-stand-alone-front-end-module</id>
  <updated>2023-07-20 04:10:15 +0530T00:00:00-00:00</updated>
  <published>2023-07-20T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#elitmus" term="elitmus" /><category scheme="/tags/#revamp" term="revamp" /><category scheme="/tags/#upgrade" term="upgrade" /><category scheme="/tags/#distributed%20system" term="distributed system" />
  <content type="html">
  
    &lt;p&gt;The current elitmus.com is a web application built with Ruby on Rails Framework, and the views are sent directly from the backend server whenever requested. This was quite good before, but in present scenario of internet and web technologies, these seem to lack some very basic requirements. And Hence, an upgradation is required.&lt;/p&gt;

&lt;p&gt;Formally, current &lt;a href=&quot;http://elitmus.com&quot;&gt;elitmus.com&lt;/a&gt; has a monolithic structure i.e. the front-end and the back-end are tightly coupled together. As a result of this, it is not possible to divide the project’s logic and team for front-end and back-end. Only Full Stack Developers having knowledge of both the domains are required in order to work in this project. This somehow limits the people who are more expertised in one of the domains.&lt;/p&gt;

&lt;p&gt;Also, the present &lt;a href=&quot;http://elitmus.com&quot;&gt;elitmus.com&lt;/a&gt; is not using the latest web technologies available. This greatly impacts the user experience.&lt;/p&gt;

&lt;p&gt;So, What’s the solution for this ?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/revamping-elitmus-dot-com-stand-alone-front-end-module/monolithic-distributed.png&quot; alt=&quot;Monolithic and Distributed Systems&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Well, we can separate the front-end and back-end. This will solve all the problems faced by the developers who work or tends to work in this project. This solves some of the major issues faced today by developers.&lt;/p&gt;

&lt;p&gt;Now, we can have a distributed system, with the views ( front-end ) in one place and the Models and Controllers in the other. The Front-end we plan to build can be built using the latest and efficient web technologies currently available. This helps to improve the User Experience as well.&lt;/p&gt;

&lt;h2 id=&quot;what-benefits-&quot;&gt;What Benefits ?&lt;/h2&gt;
&lt;hr /&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Developer Experience&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Team Separation → We can have Dedicated teams for front-end and back-end, each expertised in their own domains&lt;/li&gt;
      &lt;li&gt;Logic Separation → We can separate the Logic of course for the frontend and backend&lt;/li&gt;
      &lt;li&gt;Easy to Manage&lt;/li&gt;
      &lt;li&gt;Easy to Scale&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;User Experience&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Latest Web Tech like React can be used to Build Views&lt;/li&gt;
      &lt;li&gt;Improved Speed&lt;/li&gt;
      &lt;li&gt;Improved Performance&lt;/li&gt;
      &lt;li&gt;Consistency in design&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;how-do-we-do-it&quot;&gt;How do we do it?&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;Well, now that we know what we have to do. We are halfway there already ( Just Kidding ). Let’s discuss some of the things we can use to make the front-end efficient and reliable.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;React JS&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Its component architecture , helps us building a consistent design across the site.&lt;/li&gt;
      &lt;li&gt;It’s fast and performant.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Tailwind CSS&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;This is a light-weight CSS framework which is highly reliable and easy to use.&lt;/li&gt;
      &lt;li&gt;This has a good community, which can help to borrow UI components rather than making it from scratch.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Redux Toolkit&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Redux Toolkit is a light version of Redux, which extracts away a lot of boilerplate codes and provides us easy to use APIs to manage state.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Jest&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Jest is the most popular library for writing tests in a react application. Infact, Create-React-App provides support for this out of the box when we initiate a new react project.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, that’s all the core technologies we can use to build an efficient and reliable front-end. But, here is the catch: we can even improve more by following certain practices, which will be fruitful in the long run.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;what-else-can-we-improve-&quot;&gt;What else can we Improve ?&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;Following are some of the best practices that we can use to further improve the frontend application.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;ES Lint&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Enforcing a code style guide is important to maintain the source code of the application. It helps to maintain consistency across the application.&lt;/li&gt;
      &lt;li&gt;More Particularly, we can use the AirBNB Style Guide. This is the most popular style guide for React Application.&lt;/li&gt;
      &lt;li&gt;We can add rules as per our need and requirements in the .eslintrc.js&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Nested Routes&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;This is one of the features of react. We can nest the routes under other routes to maintain a route intuition.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&amp;lt;Route path=&amp;quot;/jobs&amp;quot; element={&amp;lt;JobsAndInterviews /&amp;gt;}&amp;gt;
    &amp;lt;Route index element={&amp;lt;AllJobs /&amp;gt;} /&amp;gt;
    &amp;lt;Route path=&amp;quot;my_jobs&amp;quot; element={&amp;lt;MyJobs /&amp;gt;}&amp;gt;
      &amp;lt;Route index element={&amp;lt;ActiveJobs /&amp;gt;} /&amp;gt;
      &amp;lt;Route path=&amp;quot;active&amp;quot; element={&amp;lt;ActiveJobs /&amp;gt;} /&amp;gt;
      &amp;lt;Route path=&amp;quot;inactive&amp;quot; element={&amp;lt;InActiveJobs /&amp;gt;} /&amp;gt;
      &amp;lt;Route path=&amp;quot;interviews&amp;quot; element={&amp;lt;Interviews /&amp;gt;} /&amp;gt;
    &amp;lt;/Route&amp;gt;
    &amp;lt;Route path=&amp;quot;all_jobs&amp;quot; element={&amp;lt;AllJobs /&amp;gt;} /&amp;gt;
  &amp;lt;/Route&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Like in this example snippet, we have a parent route for job and under that my_jobs and inside that we have active, inactive, interviews.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;/jobs/my_jobs/active → this route path is really gives a lot of information of the pages.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Dynamic Routes&lt;/strong&gt;&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;This is another feature of React Itself. This allows us to only load the pages that are requested by the user and not all.&lt;/li&gt;
      &lt;li&gt;Just imagine, our site has hundreds of pages. When the user wants to visit the homepage, we are trying to send him all the hundred pages. This doesn’t make any sense right ?&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;// Jobs Page Routes
  export const JobsAndInterviews = lazy(() =&amp;gt; import(&amp;#39;../pages/Jobs&amp;#39;));
  export const AllJobs = lazy(() =&amp;gt; import(&amp;#39;../pages/Jobs/AllJobs&amp;#39;));
  export const ApplyJob = lazy(() =&amp;gt; import(&amp;#39;../pages/Jobs/ApplyJob&amp;#39;));
  export const JobDetails = lazy(() =&amp;gt; import(&amp;#39;../pages/Jobs/JobDetails&amp;#39;));
  export const MyJobs = lazy(() =&amp;gt; import(&amp;#39;../pages/Jobs/MyJobs&amp;#39;));
  export const ActiveJobs = lazy(() =&amp;gt; import(&amp;#39;../pages/Jobs/MyJobs/Active&amp;#39;));
  export const InActiveJobs = lazy(() =&amp;gt; import(&amp;#39;../pages/Jobs/MyJobs/Inactive&amp;#39;));
  export const Interviews = lazy(() =&amp;gt; import(&amp;#39;../pages/Jobs/MyJobs/Interviews&amp;#39;));&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This above snippet shows how to import the components dynamically. But for this thing to work, we need to wrap the Routes in a Suspense Component which takes fallback.
  The component given inside the fallback is rendered in between the dynamic loads. So, we can put our page loader here. Below is the snippet showing how to do it.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;import jobsRoutes from &amp;#39;./routes/jobsRoutes&amp;#39;;
  const App = () =&amp;gt; (
  &amp;lt;Router&amp;gt;
    &amp;lt;Provider store={store}&amp;gt;
      &amp;lt;Layout&amp;gt;
        &amp;lt;Suspense fallback={() =&amp;gt; &amp;lt;Loader /&amp;gt;}&amp;gt;
          &amp;lt;Routes&amp;gt;
            {jobsRoutes}
          &amp;lt;/Routes&amp;gt;
        &amp;lt;/Suspense&amp;gt;
      &amp;lt;/Layout&amp;gt;
    &amp;lt;/Provider&amp;gt;
  &amp;lt;/Router&amp;gt;
  );&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, this makes the website immensely faster than before.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Intuitive File and Folder Organization&lt;/strong&gt; -&amp;gt; Organizing the files and folders properly is a very important task because it significantly helps the new developers. It lowers the learning curve for the new fellas.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;/src
    /__tests__
      /categoryA
        /page1.test.js
        /page2.test.js
      /categoryB
        /page1.test.js
        /page2.test.js
    /assets
    /components
      /customElements
      /Layout
    /features
      /redux_slices.js
    /pages
      /categoryA
        /page1.jsx
        /page2.jsx
      /categoryB
        /page1.jsx
        /page2.jsx
    /routes
    /store
      /redux_store.js
    /styles&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That’s how we can improve our codebase even more.&lt;/p&gt;

&lt;p&gt;Then, we have to make sure if our application runs the same on every device, OS, and system specs. For that we can dockerize the react app.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;dockerization-and-deployment&quot;&gt;Dockerization and Deployment&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;Dockerizing the react app gives us the following benefits:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Consistency:&lt;/strong&gt; Docker ensures the app runs consistently across different environments.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Dependency Management:&lt;/strong&gt; Docker encapsulates app dependencies, preventing conflicts.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Easy Deployment:&lt;/strong&gt; Docker simplifies deployment to various environments.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Scalability:&lt;/strong&gt; Docker facilitates easy scaling to handle increased traffic.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Versioning and Rollbacks:&lt;/strong&gt; Docker images can be versioned, enabling controlled updates and rollbacks.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Development and Testing:&lt;/strong&gt; Docker streamlines development and testing in a consistent environment.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Infrastructure Agnostic:&lt;/strong&gt; Docker allows running the app on various infrastructures.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Resource Efficiency:&lt;/strong&gt; Docker containers are lightweight and efficient in resource utilization.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Easy Collaboration:&lt;/strong&gt; Docker promotes seamless collaboration among developers and teams.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Security:&lt;/strong&gt; Docker provides isolation, adding an extra layer of security to the app.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We can dockerize the react app by adding docker files i.e.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Dockerfile&lt;/strong&gt; → contains environment and installation instructions for the app.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;FROM node:18 as builder
  WORKDIR /app
  COPY package.json .
  RUN npm install
  COPY . .
  RUN npm run build
  FROM nginx
  EXPOSE 80
  COPY --from=builder /app/build /usr/share/nginx/html&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;docker-compose.yml&lt;/strong&gt; → contain commands to run our docker container.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;version: &amp;#39;3&amp;#39;
  services:
    web:
      build:
        context: .
        dockerfile: Dockerfile
      ports:
        - &amp;#39;80:80&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, we have successfully containerized our react application. Finally, we need to deploy it to some cloud services such as AWS.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We can first push our docker image to docker hub&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;docker push iamsmruti/elitmus-frontend&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Then we can login to EC2 instance and then pull the docker image&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;docker pull iamsmruti/elitmus-frontend&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Finally, we can run the docker image&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;docker run -d -p 5000:5000 iamsmruti/elitmus-frontend&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That wraps up our frontend application which can now be live. It is fully capable of consuming the APIs from the backend. Now, the business logic is in the backend and doesn’t put much load on the frontend and hence it is performant and reliable.&lt;/p&gt;

&lt;p&gt;If you have any questions, doubts, you can ping me at &lt;code&gt;smrutiranjanbadatya2@gmail.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I would definitely get back to you.&lt;/p&gt;

&lt;p&gt;I Hope this was a helpful and insightful guide for making a better frontend application with all the necessary good practices to maintain sustainability of the project.&lt;/p&gt;

&lt;p&gt;See Ya 👋🏻 … Peace ✌🏻&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;React Docs - &lt;a href=&quot;https://react.dev/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Here&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Tailwind Docs - &lt;a href=&quot;https://tailwindcss.com/docs/installation&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Here&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Redux Toolkit Docs - &lt;a href=&quot;https://redux-toolkit.js.org/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Here&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Jest Docs - &lt;a href=&quot;https://jestjs.io/docs/getting-started&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Here&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;ES Lint Docs - &lt;a href=&quot;https://eslint.org/docs/latest/&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Here&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Docker Docs - &lt;a href=&quot;https://docs.docker.com&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Here&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

  
  &lt;p&gt;&lt;a href=&quot;/technology/revamping-elitmus-dot-com-stand-alone-front-end-module/&quot;&gt;Revamping eLitmus.com | Stand-Alone Front-end Module&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on July 20, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[My Experience as a Summer Intern at eLitmus: Building a Telegram Bot]]></title>
  <link rel="alternate" type="text/html" href="/technology/my-experience-as-a-summer-intern-at-elitmus-building-a-telegram-bot/"/>
  <id>/technology/my-experience-as-a-summer-intern-at-elitmus-building-a-telegram-bot</id>
  <updated>2023-07-14 15:18:53 +0530T00:00:00-00:00</updated>
  <published>2023-07-19T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#Rails" term="Rails" /><category scheme="/tags/#Telegram%20bot" term="Telegram bot" />
  <content type="html">
  
    &lt;p&gt;As a summer intern at eLitmus, I had the opportunity to work on an exciting project that involved building a Telegram Bot. In today’s digital era, effective communication channels play a crucial role in connecting businesses with their stakeholders. eLitmus, a talent-tech platform, identified the need for a two-way communication channel between the platform and candidates. To achieve this, Telegram bots were chosen as the ideal starting point. This blog post will delve into the Telegram Bot Integration project.&lt;/p&gt;

&lt;h2 id=&quot;how-it-began&quot;&gt;&lt;strong&gt;How it Began&lt;/strong&gt;:&lt;/h2&gt;

&lt;p&gt;The project started with the idea of leveraging the Telegram platform as a communication channel between eLitmus and its candidates. The goal was to create a two-way communication channel, enabling candidates to access information, receive updates, and engage in various activities through Telegram bots. This opened up possibilities for automating communication, collecting data, running quizzes, and providing valuable services to candidates.&lt;/p&gt;

&lt;h2 id=&quot;design&quot;&gt;&lt;strong&gt;Design&lt;/strong&gt;:&lt;/h2&gt;

&lt;p&gt;Before diving into development phase, thorough planning and design are crucial. I begin by defining the core functionalities of the Telegram bots. I discovered that creating a bot through Bot Father (Telegram’s official bot) was the standard approach. As I was tasked with implementing the project using Ruby on Rails, I focused on two key aspects: developing the Telegram bot and designing the Admin panel.
Designing such an application involves three key aspects: architecture design, database design, and UI/UX design. Let’s dive into each of these parts in more detail:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Architecture&lt;/strong&gt; : The Telegram bots interact with users through messages and commands. Users can access FAQs, participate in quizzes, and receive responses based on their interactions with the bots. The bots handle user inputs, validate quiz answers, and provide feedback and results accordingly. An intuitive admin panel is developed using Ruby on Rails to facilitate easy management of the bot’s functionalities. The admin panel allows administrators to add, update, and delete FAQs, quizzes, and other content. It also provides insights and analytics related to user engagement and bot usage.
  &lt;img src=&quot;/blog/images/telegram-bot/architecture.png&quot; width=&quot;425&quot; /&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Database&lt;/strong&gt;: The project utilizes a MySQL database to store and manage data related to users, FAQs, quizzes, quiz attempts, analytics, and other relevant information. The database schema is designed to efficiently store and retrieve data, ensuring optimal performance.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;UI/UX&lt;/strong&gt;:  To ensure a visually appealing and user-friendly Telegram bot interface, I delved into various UI options and explored the best ways to present information and interact with users. This research helped me identify the most effective strategies for creating an engaging and intuitive bot interface. And for the Admin panel, I took the initiative to design the entire interface using Figma. By visualizing the layout, components, and functionalities, I was able to ensure a cohesive and user-friendly experience for administrators managing the bot’s functionalities. Figma provided a powerful toolset for creating wireframes, mock-ups, and interactive prototypes, allowing me to iterate and refine the design before implementation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;development&quot;&gt;&lt;strong&gt;Development&lt;/strong&gt;:&lt;/h2&gt;

&lt;p&gt;Before starting this project, I had experience developing mobile applications, and most of them followed the Model-View-Template (MVT) pattern for backend, such as Django. However, for this project, I needed to learn and work with Ruby on Rails, which follows the Model-View-Controller (MVC) architectural pattern. Fortunately, my previous experience with backend development made it easier for me to understand Rails, and within the first two weeks, I was able to develop the basic functionalities of both the FAQ and Quiz bots.&lt;/p&gt;

&lt;p&gt;Integrating the telegram bot consists of 3 steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Creating a bot using Bot Father ( Official bot of telegram for creating telegram bot) and get the token that was generated by the bot father.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/blog/images/telegram-bot/botfather.png&quot; width=&quot;425&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Initalizing the bot in the ruby file and declare a listening function that listens every messsage from the bot.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Writing the message specified functions that is called only when a specified message if recieved from the bot.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have used Ruby on Rails for both front-end and back-end to develope admin panel. For database I have used mysql and for hosting purpose I have used AWS, EC2 to host admin panel using docker and telegram and RDS for database.&lt;/p&gt;

&lt;p&gt;Using docker to host the bot and admin panel was another part of the development that gave me an idea of how to does docker used by most of the companies, it was my personal goal in the year to learn docker so it got done by this project. And to say using docker wasn’t the difficult part. I had to learn how to write a docker file and docker compose file.&lt;/p&gt;

&lt;h2 id=&quot;features-developed&quot;&gt;&lt;strong&gt;Features Developed&lt;/strong&gt;&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;User Flow&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I focused on refining the functionalities and user flow of the bots, particularly in the context of the Telegram channels. The FAQ bot is connected to the Telegram channel, and when a user posts a question in the channel’s comment section, it gets stored in the database. The admin can then view and answer the question, which is sent back to the user personally through Telegram. Additionally, users can access the FAQ bot to view existing FAQs and request the addition of new ones.&lt;/p&gt;

&lt;h6&gt;
    &lt;strong&gt;
      FAQ bot flow
    &lt;/strong&gt;
  &lt;/h6&gt;
&lt;table&gt;
    &lt;tr&gt;
      &lt;td&gt;
      &lt;img src=&quot;/blog/images/telegram-bot/faqbot2.png&quot; width=&quot;425&quot; /&gt;
      &lt;/td&gt;
      &lt;td&gt;
        &lt;img src=&quot;/blog/images/telegram-bot/faqbot3.png&quot; width=&quot;425&quot; /&gt; 
      &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;

&lt;h6&gt;
    &lt;strong&gt;
      Quiz bot flow
    &lt;/strong&gt;
  &lt;/h6&gt;
&lt;table&gt;
    &lt;tr&gt;
      &lt;td&gt;
        &lt;img src=&quot;/blog/images/telegram-bot/quizbot1.png&quot; width=&quot;425&quot; /&gt; 
      &lt;/td&gt;
      &lt;td&gt;
        &lt;img src=&quot;/blog/images/telegram-bot/quizbot2.png&quot; width=&quot;425&quot; /&gt;
      &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Admin Panel&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;On the other hand, the admin panel allows the admin to create quizzes and questions. These quizzes are then posted in the Telegram channel, with a button redirecting users to the Quiz bot. Users can access multiple quizzes and attempt them through the bot.&lt;/p&gt;

    &lt;p&gt;By developing these functionalities, I was able to establish a seamless flow for users, ensuring they can interact with the bots and access relevant information easily. The admin panel provides the necessary tools for managing FAQs, quizzes, and user interactions, allowing for efficient administration and engagement with the users.&lt;/p&gt;

    &lt;p&gt;In the Admin panel, I implemented the design that I had previously created using Figma. The Admin panel offers various functionalities to enhance the administration and management of the Telegram bots. Here are some key features of the Admin panel:&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;&lt;strong&gt;User Management&lt;/strong&gt;: The Admin panel allows the admin to view active users and access individual user data. This includes information about the user’s activities, quiz attempts, and questions asked through the bot.&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;&lt;strong&gt;FAQ Management&lt;/strong&gt;: The Admin can view and manage the FAQs. They have the ability to add, edit, or remove FAQs as needed. Additionally, the Admin can track the number of reads by users, providing insights into the popularity and relevance of different FAQs.&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;&lt;strong&gt;Quiz Management&lt;/strong&gt;: The Admin can create quizzes and manage them within the Admin panel. They can add questions, set multiple options, and define correct answers. The Admin also has access to the responses of the quizzes, allowing them to analyze individual question analytics and gain insights into user performance. This can also be used to host surveys on telegram channels.&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;&lt;strong&gt;Analytics&lt;/strong&gt;: The Admin panel provides analytics on user activities related to both the FAQ and Quiz bots. The Admin can view data such as the number of attempts per day, week, month, or year, as well as the number of FAQ reads per day, week, month, or year. These analytics help the Admin understand user engagement and make data-driven decisions.&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;&lt;strong&gt;Post Management&lt;/strong&gt;: The Admin can utilize the post section in the Admin panel to create and publish posts in the Telegram channel directly from Telegram and to make it effective I have created two phases of create and publishing the post so that post get reviewed before publishing the post. This feature streamlines the process of sharing content and updates with users in the channel.&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;&lt;strong&gt;Admin Panel&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/blog/images/telegram-bot/admin-panel1.png&quot; /&gt;&lt;/p&gt;

    &lt;p&gt;By incorporating these functionalities into the Admin panel, I ensured that the administrative tasks associated with managing the Telegram bots were streamlined and efficient. The panel provides comprehensive control and insights, empowering the admin to effectively manage user interactions, content, and analytics.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;challenges-faced&quot;&gt;&lt;strong&gt;Challenges Faced&lt;/strong&gt;:&lt;/h2&gt;

&lt;p&gt;Working with 3rd party API’s is one of the most challenging task and that is the challenging task of the project using telegram bot API. I could able to use telegram api to minimal amount of data of user, for example I couldn’t able to get users contact details, and I have crossed this challenge by finidng a feature of telegram that is by using permissions to access user details and request user to send the mobile number and location, but I couldn’t able to get location from the web or laptop. The biggest challenge I have faced was setting up and displaying analytics using charts and graphs. Initially, I tried using gems like Chartkick and FusionCharts, but faced issues with rendering the graphs correctly. Despite spending considerable time troubleshooting, the graphs weren’t displaying as expected. Eventually, I opted for Chart.js, which proved to be a more suitable solution for my needs. With Chart.js, I could create visually appealing and interactive charts to showcase the data collected through admin panel. The transition to Chart.js was smooth, and it enabled me to present data insights effectively, providing a valuable user experience.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;:&lt;/h2&gt;

&lt;p&gt;In summary, working on this project presented its fair share of challenges. However, with perseverance and problem-solving skills, I was able to overcome these obstacles and achieve success. I was able to develop the Telegram bots and the Admin panel effectively. I am thrilled to share that my hard work did not go unnoticed, and my project was selected for use by the company. This recognition is truly gratifying, as it demonstrates the value my work brings to the organization and the impact it can have on the company operations. Overall, this project was a rewarding journey that expanded my knowledge and skills in web development.&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;/technology/my-experience-as-a-summer-intern-at-elitmus-building-a-telegram-bot/&quot;&gt;My Experience as a Summer Intern at eLitmus: Building a Telegram Bot&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on July 19, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Resume Parsing: Insights and Steps to Create Your Own Parser]]></title>
  <link rel="alternate" type="text/html" href="/technology/resume-parsing-insights-and-steps-to-create-your-own-parser/"/>
  <id>/technology/resume-parsing-insights-and-steps-to-create-your-own-parser</id>
  <updated>2023-06-20 13:40:00 +0530T00:00:00-00:00</updated>
  <published>2023-06-20T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#Resume-parser" term="Resume-parser" /><category scheme="/tags/#Python" term="Python" /><category scheme="/tags/#Flask" term="Flask" /><category scheme="/tags/#NLP" term="NLP" /><category scheme="/tags/#LLM" term="LLM" />
  <content type="html">
  
    &lt;p&gt;Resume parsing is the automated process of extracting relevant information from resumes or CVs. 
It analyzes the unstructured text of a resume and extracts specific details like contact information, work experience, education, skills, and achievements. 
The extracted data is then converted into a structured format, allowing for easy analysis and integration into recruitment systems.&lt;/p&gt;

&lt;h2 id=&quot;benefits-of-resume-parsing&quot;&gt;Benefits of Resume Parsing&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;It is a time-saving automation&lt;/li&gt;
  &lt;li&gt;It increases efficiency in candidate screening&lt;/li&gt;
  &lt;li&gt;Improves accuracy in data extraction&lt;/li&gt;
  &lt;li&gt;It standardizes the data extraction and formatting&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-youll-learn-from-this-blog&quot;&gt;What you’ll learn from this blog:&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;Resume parsing techniques for different file formats.&lt;/li&gt;
  &lt;li&gt;Extracting specific details from resumes.&lt;/li&gt;
  &lt;li&gt;Leveraging NLP techniques for parsing.&lt;/li&gt;
  &lt;li&gt;Handling multicolumn resumes.&lt;/li&gt;
  &lt;li&gt;Dockerizing the Application: Simplifying Deployment and Scalability&lt;/li&gt;
  &lt;li&gt;Hosting it on AWS EC2.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Let’s get Started 🎉&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ll utilize Python and its Flask framework to create a resume parsing server.&lt;/p&gt;

&lt;h2 id=&quot;application-flow-chart&quot;&gt;Application Flow Chart:&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/resume-parsing-insights-and-steps-to-create-your-own-parser/file-flow.jpg&quot; alt=&quot;Application Flow Chart Image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We will be primarily working on 3 categories of file formats:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;PDF&lt;/li&gt;
  &lt;li&gt;DOCX&lt;/li&gt;
  &lt;li&gt;Images (.png, .jpg, etc.)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;data-that-we-will-be-extracting&quot;&gt;Data that we will be extracting&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Embedded links in PDF&lt;/li&gt;
  &lt;li&gt;Personal data: &lt;br /&gt;
 2.1. Name: First name and last name &lt;br /&gt;
 2.2. Email &lt;br /&gt;
 2.3. Phone Number &lt;br /&gt;
 2.4. Address: City, Country, and Zip code &lt;br /&gt;
 2.5. Links: Social and Coding Platform links &lt;br /&gt;&lt;/li&gt;
  &lt;li&gt;Education &lt;br /&gt;
 3.1. Institute name &lt;br /&gt;
 3.2. Duration: Start date and End date &lt;br /&gt;
 3.3. Grade/CGPA &lt;br /&gt;
 3.4. Degree &lt;br /&gt;&lt;/li&gt;
  &lt;li&gt;Experience&lt;br /&gt;
 4.1. Company name&lt;br /&gt;
 4.2. Role&lt;br /&gt;
 4.3. Durations: Start date and End date&lt;br /&gt;
 4.4. Skills&lt;br /&gt;&lt;/li&gt;
  &lt;li&gt;Certification: &lt;br /&gt;
 5.1. Description &lt;br /&gt;
 5.2. Duration &lt;br /&gt;
 5.3. Skill &lt;br /&gt;&lt;/li&gt;
  &lt;li&gt;Project: &lt;br /&gt;
 6.1. Project name &lt;br /&gt;
 6.2. Skills &lt;br /&gt;
 6.3. Description &lt;br /&gt;&lt;/li&gt;
  &lt;li&gt;Skills&lt;/li&gt;
  &lt;li&gt;Achievements&lt;/li&gt;
  &lt;li&gt;Exam scores&lt;br /&gt;
 9.1. Exam name&lt;br /&gt;
 9.2 Score&lt;br /&gt;&lt;/li&gt;
  &lt;li&gt;All other sections present in resume&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;dateduration-extraction&quot;&gt;Date/Duration Extraction&lt;/h2&gt;

&lt;p&gt;To extract dates from text, we will use &lt;code&gt;datefinder&lt;/code&gt; module, and regexp to extract years.
Then we will combine these two and sort dates to get start and end date for our duration.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import re
from datetime import date
import datefinder


def get_date(input_string):
    &apos;&apos;&apos;Get date from text&apos;&apos;&apos;
    matches = list(datefinder.find_dates(input_string))

    res = []
    for i in matches:
        date_str = str(i).split(&apos; &apos;)
        extracted_date = date_str[0]

        res.append(extracted_date)
    return res


def get_years(txt):
    &apos;&apos;&apos;Get years from text&apos;&apos;&apos;
    pattern = r&apos;[0-9]{4}&apos;
    lst = re.findall(pattern, txt)

    current_date = date.today()
    current_year = current_date.year
    res = []
    for i in lst:
        year = int(i)
        if 1900 &amp;lt;= year &amp;lt;= (current_year + 10):
            res.append(i + &quot;-01-01&quot;)
    return res


def get_duration(input_text):
    &apos;&apos;&apos;Get duration from text&apos;&apos;&apos;

    dates = get_date(input_text)
    years = get_years(input_text)

    for i in years:
        dates.append(i)
    dates.sort()

    duration = {
        &quot;start_date&quot;: &quot;&quot;,
        &quot;end_date&quot;: &quot;&quot;
    }
    if len(dates) &amp;gt; 1:
        duration[&quot;start_date&quot;] = dates[0]
        duration[&quot;end_date&quot;] = dates[len(dates) - 1]
    return duration

&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;extracting-links-from-pdf&quot;&gt;Extracting links from PDF:&lt;/h2&gt;

&lt;p&gt;To extract links from the PDF, we will use the python module &lt;code&gt;PDFx&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pdfx

def get_urls_from_pdf(file_path):
    &apos;&apos;&apos;extract urls from pdf file&apos;&apos;&apos;
    url_list = []

    # for invalid file path
    if os.path.exists(file_path) is False:
        return url_list

    pdf = pdfx.PDFx(file_path)

    # get urls
    pdf_url_dict = pdf.get_references_as_dict()

    if &quot;url&quot; not in pdf_url_dict.keys():
        return url_list

    url_list = pdf_url_dict[&quot;url&quot;]

    return url_list
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;pdf-to-text&quot;&gt;PDF to Text&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pdfx
def get_text_from_pdf(file_path):
    &apos;&apos;&apos;extract complete text from pdf&apos;&apos;&apos;

    # for invalid file path
    if os.path.exists(file_path) is False:
        return &quot;&quot;

    pdf = pdfx.PDFx(file_path)

    pdf_text = pdf.get_text()

    return pdf_text

&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;extracting-personal-details&quot;&gt;Extracting Personal Details:&lt;/h2&gt;

&lt;p&gt;We will extract text from the PDF and move ahead with further extractions.&lt;/p&gt;

&lt;h3 id=&quot;name&quot;&gt;Name&lt;/h3&gt;

&lt;p&gt;Extracting the name from the text is one of the challenging tasks.&lt;/p&gt;

&lt;p&gt;For this, we will be using &lt;code&gt;NLP: Named Entity Recognition&lt;/code&gt; to extract name from the text.&lt;/p&gt;

&lt;h4 id=&quot;nlp-function&quot;&gt;NLP function:&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def get_name_via_nltk(input_text):
    &apos;&apos;&apos;extract name from text via nltk functions&apos;&apos;&apos;
    names = []
    for sent in nltk.sent_tokenize(input_text):
        for chunk in nltk.ne_chunk(nltk.pos_tag(nltk.word_tokenize(sent))):
            if hasattr(chunk, &apos;label&apos;):
                name = &apos; &apos;.join(c[0] for c in chunk.leaves())
                names.append(name)
    return names
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
  &lt;li&gt;The text is tokenized into sentences using nltk.sent_tokenize().&lt;/li&gt;
  &lt;li&gt;Each sentence is further tokenized into words using nltk.word_tokenize().&lt;/li&gt;
  &lt;li&gt;The part-of-speech tags are assigned to each word using nltk.pos_tag().&lt;/li&gt;
  &lt;li&gt;The named entities are identified by applying the named entity recognition (NER) using nltk.ne_chunk().&lt;/li&gt;
  &lt;li&gt;For each identified named entity chunk, if it has a ‘label’, indicating it is a named entity, the individual words are concatenated to form a name.&lt;/li&gt;
  &lt;li&gt;The extracted names are appended to the names list.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;phone-number&quot;&gt;Phone Number&lt;/h3&gt;

&lt;p&gt;To extract the Phone number, we use the following module &lt;code&gt;phonenumbers&lt;/code&gt;, we extract users country from text and using that we will extract relevant phone numbers.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import geotext
from phonenumbers import PhoneNumberMatcher

def get_phone(input_text):
    &apos;&apos;&apos;extract phone number from text&apos;&apos;&apos;

    phone_numbers = []

    countries_dict = geotext.GeoText(input_text).country_mentions
    
    country_code = &quot;IN&quot;
    for i in countries_dict.items():
        country_code = i[0]
        break

    search_result = PhoneNumberMatcher(input_text, country_code)

    phone_number_list = []
    for i in search_result:
        i = str(i).split(&apos; &apos;)
        match = i[2:]

        phone_number = &apos;&apos;.join(match)
        phone_number_list.append(phone_number)

    for i in phone_number_list:
        if i not in phone_numbers:
            phone_numbers.append(i)

    return phone_numbers
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;email&quot;&gt;Email&lt;/h3&gt;

&lt;p&gt;To extract the Email, we use the following regexp: &lt;code&gt;[^\s]+@[^\s]+[.][^\s]+&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def get_email(input_text):
    &apos;&apos;&apos;extract email from text&apos;&apos;&apos;
    email_pattern = &apos;[^\s]+@[^\s]+[.][^\s]+&apos;

    emails = []
    emails = re.findall(email_pattern, input_text)

    # pick only unique emails
    emails = set(emails)
    emails = list(emails)

    return emails

&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;address&quot;&gt;Address&lt;/h3&gt;

&lt;p&gt;To Extract address, we use the &lt;code&gt;geotext&lt;/code&gt; module; we get City, Country, and Zipcode.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import geotext
def get_address(input_arr):
    &apos;&apos;&apos;get address information from input array&apos;&apos;&apos;

    input_text = &quot; \n &quot;.join(input_arr)

    res = {}
    # getting all countries
    countries_dict = geotext.GeoText(input_text).country_mentions

    res[&quot;country&quot;] = []
    for i in countries_dict:
        res[&quot;country&quot;].append(i)

    # getting all cities
    res[&quot;city&quot;] = geotext.GeoText(input_text).cities

    # zip code
    pattern = &quot;\b([1-9]{1}[0-9]{5}|[1-9]{1}[0-9]{2}\\s[0-9]{3})\b&quot;
    res[&quot;zipcode&quot;] = re.findall(pattern, input_text)

    return res

&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;links&quot;&gt;Links&lt;/h3&gt;

&lt;p&gt;As we already have a URL list from 1st operation, we will match links from a list of our own, this can be saved in any database or hard-coded, and categorize them into &lt;code&gt;social&lt;/code&gt; or &lt;code&gt;coding&lt;/code&gt; sections.&lt;/p&gt;

&lt;h2 id=&quot;other-sections&quot;&gt;Other Sections&lt;/h2&gt;

&lt;p&gt;There can be many sections in a resume, that we cannot always account for.
To extract them, we will create a list of possible section heading and match them against each line from the resume that we have extracted.&lt;/p&gt;

&lt;p&gt;The code will be as following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
from utils import dynamo_db

RESUME_SECTIONS = dynamo_db.get_item_db(&quot;RESUME_SECTIONS&quot;)


def extract_resume_sections(text):
    &apos;&apos;&apos;Extract section based on resume heading keywords&apos;&apos;&apos;
    text_split = [i.strip() for i in text.split(&apos;\n&apos;)]

    entities = {}
    entities[&quot;extra&quot;] = []
    key = False
    for phrase in text_split:
        if len(phrase.split(&apos; &apos;)) &amp;gt; 10:
            if key is not False:
                entities[key].append(phrase)
            else:
                entities[&quot;extra&quot;].append(phrase)
            continue

        if len(phrase) == 1:
            p_key = phrase
        else:
            p_key = set(phrase.lower().split()) &amp;amp; set(RESUME_SECTIONS)

        try:
            p_key = list(p_key)[0]
        except IndexError:
            pass

        if p_key in RESUME_SECTIONS and (p_key not in entities.keys()):
            entities[p_key] = []
            key = p_key
        elif key and phrase.strip():
            entities[key].append(phrase)
        else:
            if len(phrase.strip()) &amp;lt; 1:
                continue
            entities[&quot;extra&quot;].append(phrase)

    return entities

&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;education&quot;&gt;Education&lt;/h2&gt;

&lt;p&gt;To extract education, we need to identify a line from our education section that represent the school/institute name, and a line that represents the degree. After which we can search for CGPA or Percentage using regexp.
For name recognition, we will make use of a list of keywords that can be present in the name.&lt;/p&gt;

&lt;p&gt;Code to get school name, similarly we can implement to get degree as well.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import re
from utils import helper, dynamo_db

SCHOOL_KEYWORDS = dynamo_db.get_item_db(&quot;SCHOOL_KEYWORDS&quot;)


def get_school_name(input_text):
    &apos;&apos;&apos;Extract list of school names from text&apos;&apos;&apos;
    text_split = [i.strip() for i in input_text.split(&apos;\n&apos;)]

    school_names = []

    for phrase in text_split:
        p_key = set(phrase.lower().split(&apos; &apos;)) &amp;amp; set(SCHOOL_KEYWORDS)

        if (len(p_key) == 0):
            continue

        school_names.append(phrase)
    return school_names

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Code to extract CGPA/GPA or Percentage grade&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def get_percentage(txt):
    &apos;&apos;&apos;Extract percentage from text&apos;&apos;&apos;
    pattern = r&apos;((\d+\.)?\d+%)&apos;
    lst = re.findall(pattern, txt)
    lst = [i[0] for i in lst]
    return lst


def get_gpa(txt):
    &apos;&apos;&apos;Extract cgpa or gpa from text in format x.x/x&apos;&apos;&apos;
    pattern = r&apos;((\d+\.)?\d+\/\d+)&apos;
    lst = re.findall(pattern, txt)
    lst = [i[0] for i in lst]
    return lst


def get_grades(input_text):
    &apos;&apos;&apos;Extract grades from text&apos;&apos;&apos;
    input_text = input_text.lower()
    # gpa
    gpa = get_gpa(input_text)

    if (len(gpa) != 0):
        return gpa

    # percentage
    percentage = get_percentage(input_text)

    if (len(percentage) != 0):
        return percentage

    return []
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;skills&quot;&gt;Skills&lt;/h2&gt;

&lt;p&gt;In order to extract skills from the text, a master list of commonly used skills can be created and stored in a database, such as AWS DynamoDB. Each skill from the list can be matched against the text to identify relevant skills. By doing so, a comprehensive master skill list can be generated, which can be utilized for more specific skill extraction in subsequent sections.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
from utils import dynamo_db

skills = dynamo_db.get_item_db(&quot;ALL_SKILLS&quot;)


def get_skill_tags(input_text):
    &apos;&apos;&apos;Extract skill tags from text&apos;&apos;&apos;
    user_skills = []
    for skill in skills:
        if skill in input_text.lower():
            user_skills.append(skill.upper())

    return user_skills

&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;experience&quot;&gt;Experience&lt;/h2&gt;

&lt;p&gt;To extract company names and roles, a similar strategy can be employed as we used for finding school names and degrees. By applying appropriate techniques, such as named entity recognition or pattern matching, we can identify company names and associated job roles from the text. Additionally, for skill extraction, we can match the text against our previously calculated list of skills to identify and extract relevant skills mentioned in the text&lt;/p&gt;

&lt;h2 id=&quot;achievements-and-certifications&quot;&gt;Achievements and Certifications&lt;/h2&gt;

&lt;p&gt;We can use the section text that we extracted previously and for each line of it, we can search for duration and skills in it.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
from utils import helper, skill_tags


def get_certifications(input_array):
    &apos;&apos;&apos;Function to extract certificate information&apos;&apos;&apos;

    res = {
        &quot;description&quot;: input_array,
        &quot;details&quot;: []
    }

    try:

        for cert in input_array:
            elem_dict = {
                &quot;institute_name&quot;: str(cert),
                &quot;skills&quot;: skill_tags.get_skill_tags(cert),
                &quot;duration&quot;: helper.get_duration(cert)
            }
            res[&quot;details&quot;].append(elem_dict)

    except Exception as function_exception:
        helper.logger.error(function_exception)

    return res

&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;projects&quot;&gt;Projects&lt;/h2&gt;

&lt;p&gt;When it comes to extracting project titles, it can be challenging due to the variations in how individuals choose to title their projects. However, we can make an assumption that project titles are often written in a larger font size compared to the rest of the text. Leveraging this assumption, we can analyze the font sizes of each line in the text and sort them in descending order. By selecting the lines with the largest font sizes from the top, we can identify potential project titles. This approach allows us to further segment the project section and extract additional details such as skills utilized and project durations.&lt;/p&gt;

&lt;p&gt;Link: &lt;a href=&quot;https://stackoverflow.com/questions/68097779/how-to-find-the-font-size-of-every-paragraph-of-pdf-file-using-python-code&quot;&gt;How to find the Font Size of every paragraph of PDF file using python code?&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import fitz

def scrape(keyword, filePath):
    results = [] # list of tuples that store the information as (text, font size, font name) 
    pdf = fitz.open(filePath) # filePath is a string that contains the path to the pdf
    for page in pdf:
        dict = page.get_text(&quot;dict&quot;)
        blocks = dict[&quot;blocks&quot;]
        for block in blocks:
            if &quot;lines&quot; in block.keys():
                spans = block[&apos;lines&apos;]
                for span in spans:
                    data = span[&apos;spans&apos;]
                    for lines in data:
                            results.append((lines[&apos;text&apos;], lines[&apos;size&apos;], lines[&apos;font&apos;]))

    pdf.close()
    return results
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Using this we find our project titles:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from utils import helper, skill_tags
from difflib import SequenceMatcher

def similar(string_a, string_b):
    &apos;&apos;&apos;Find similarity between two string&apos;&apos;&apos;
    return SequenceMatcher(None, string_a, string_b).ratio()

def extract_project_titles(input_array, text_font_size):
    ls = []
    for line_tuple in text_font_size:
        line = line_tuple[0]
        for s in input_array:
            if similar(line,s) &amp;gt; 0.85:
                ls.append([line_tuple[1], s])
    ls.sort(reverse=True)

    title_font_size = ls[0][0] if(len(ls) &amp;gt; 0) else 0
    project_title = []
    for i in ls:
        if i[0] == title_font_size:
          project_title.append(i[1])
    return project_title

def get_projects(input_array, text_font_size):
    &apos;&apos;&apos;extract project details from text&apos;&apos;&apos;
    res = {
        &quot;description&quot;: input_array,
        &quot;details&quot;: []
    }
    txt = &apos; \n &apos;.join(input_array)

    project_titles = helper.extract_titles_via_font_size(
        input_array, text_font_size)

    project_sections = helper.extract_sections(txt, project_titles)

    try:
        for i in project_sections.items():
            key = i[0]
            txt = &apos;\n&apos;.join(project_sections[key])

            elem_dict = {
                &quot;project_name&quot;: key,
                &quot;skills&quot;: skill_tags.get_skill_tags(txt),
                &quot;duration&quot;: helper.get_duration(txt)
            }

            res[&quot;details&quot;].append(elem_dict)
    except Exception as function_exception:
        helper.logger.error(function_exception)

    return res

&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;handling-multicolumn-resumes&quot;&gt;Handling multicolumn resumes&lt;/h2&gt;

&lt;p&gt;Up until now, we have explored techniques to handle single-column resumes successfully. 
However, when it comes to two-column or multicolumn resumes, a direct extraction of text may not be sufficient. If we attempt to extract text from a multicolumn PDF using the same method as before, we will encounter challenges such as, the text from different columns will merge together, as our previous approach scans the text from left to right and top to bottom, rather than column-wise.&lt;/p&gt;

&lt;p&gt;To overcome this issue, let’s delve into how we can solve this problem and effectively handle multicolumn resumes.&lt;/p&gt;

&lt;h3 id=&quot;drawing-textboxes&quot;&gt;Drawing textboxes&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Optical Character Recognition (OCR)&lt;/code&gt; comes to the rescue by identifying textboxes and providing their coordinates within the document. By utilizing OCR, we can pinpoint the location of these textboxes, which serve as a starting point for further analysis.&lt;/p&gt;

&lt;p&gt;To tackle the challenge of multicolumn resumes, a line sweep algorithm is implemented. This algorithm systematically scans along the X-axis and determines how many textboxes intersect each point. By analyzing this distribution, potential column divide lines can be inferred. These lines act as reference markers, indicating the boundaries between columns.&lt;/p&gt;

&lt;p&gt;Once the column lines are established, the text can be extracted from the identified textboxes in a column-wise manner. Following the order of the column lines, the text can be retrieved and processed accordingly.&lt;/p&gt;

&lt;p&gt;By leveraging OCR, the line sweep algorithm, and the concept of column lines, we can effectively handle multicolumn resumes and extract the necessary information in an organized and structured manner.&lt;/p&gt;

&lt;p&gt;Code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import cv2
import fitz
from fitz import Document, Page, Rect
import pytesseract
import functools

def textbox_recognition(file_path):
    &apos;&apos;&apos;Extract text_boxes from image&apos;&apos;&apos;

    img = cv2.imread(file_path, cv2.IMREAD_GRAYSCALE)

    ret, thresh1 = cv2.threshold(
        img, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY_INV)

    # kernel
    kernel_size = 10
    rect_kernel = cv2.getStructuringElement(
        cv2.MORPH_RECT, (kernel_size, kernel_size))

    # Applying dilation on the threshold image
    dilation = cv2.dilate(thresh1, rect_kernel, iterations=1)

    # Finding contours
    contours, hierarchy = cv2.findContours(
        dilation, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

    segments = []
    text_boxes = []
    # Looping through the identified contours
    for cnt in contours:
        x, y, w, h = cv2.boundingRect(cnt)
        cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
        segments.append([x, x+w])
        text_boxes.append((x, y, w, h))

    return (segments, text_boxes)


def detect_column_lines(segments):
    &apos;&apos;&apos;Detect column lines from segments&apos;&apos;&apos;

    mx = max(i[1] for i in segments)

    line_sweep_arr = [0 for _ in range(mx+10)]
    for i in segments:
        line_sweep_arr[i[0] + 1] += 1
        line_sweep_arr[i[1]] -= 1

    for i in range(1, mx+10):
        line_sweep_arr[i] += line_sweep_arr[i-1]

    line_mean = sum(line_sweep_arr)/len(line_sweep_arr)

    potential_points = []
    for i in range(1, mx+10):
        if line_sweep_arr[i] &amp;lt; int(line_mean/2.5):
            potential_points.append(i)

    line_points = []
    for i in potential_points:
        if len(line_points) == 0:
            line_points.append(i)
            continue
        prev = line_points[len(line_points) - 1]

        if i == prev + 1:
            line_points[len(line_points) - 1] = i
        else:
            line_points.append(i)

    return line_points


def get_text(img, box_data):
    &apos;&apos;&apos;Extract text from given box data&apos;&apos;&apos;
    (x, y, w, h) = box_data
    cropped_image = img[y:y+h, x:x+w]

    # to show image
    txt = pytesseract.image_to_string(cropped_image)
    return txt


def box_coverage_percentage(x, w, line):
    &apos;&apos;&apos;Extract coverage area in percentage for box&apos;&apos;&apos;

    covered_width = line - x
    cover_percentage = covered_width / w
    return cover_percentage


def clean_text(txt):
    &apos;&apos;&apos;Clean text&apos;&apos;&apos;
    txt = txt.strip()
    txt = txt.replace(&quot;•&quot;, &apos;&apos;)
    return txt


Y_LIMIT = 10


def custom_sort(a, b):
    &apos;&apos;&apos;custom sort logic&apos;&apos;&apos;
    if a[1] - Y_LIMIT &amp;lt;= b[1] &amp;gt;= a[1] + Y_LIMIT:
        return -1 if (a[0] &amp;lt;= b[0]) else 1
    return -1 if (a[1] &amp;lt;= b[1]) else 1


def get_boxes_for_line(text_boxes, line, ordered_text_box, prev_line):
    &apos;&apos;&apos;get boxes with line constraints&apos;&apos;&apos;
    temp_boxes = [i for i in text_boxes]
    temp_boxes.sort(key=functools.cmp_to_key(custom_sort))

    res = []

    # check if 90% of box is before line
    for box in temp_boxes:
        if box in ordered_text_box:
            continue

        (x, y, w, h) = box

        if (x &amp;gt;= prev_line - Y_LIMIT and x &amp;lt; line and box_coverage_percentage(x, w, line) &amp;gt;= 0.9):
            res.append(box)
    res.sort(key=lambda x: x[1])
    return res


def map_size(x, org, new):
    &apos;&apos;&apos;map box co-ordinates from image to pdf&apos;&apos;&apos;
    return (x*new)/org


def get_text_from_pdf(box, img_shape, pdf_shape, page):
    &apos;&apos;&apos;extract text from pdf box&apos;&apos;&apos;
    (x, y, w, h) = box
    (height, width) = img_shape
    (W, H) = pdf_shape
    x = map_size(x, width, W)
    w = map_size(w, width, W)
    y = map_size(y, height, H)
    h = map_size(h, height, W)
    rect = Rect(x, y, x+w, y+h)
    text = page.get_textbox(rect)
    return text


def image_to_text(file_path, pdf_file_path=&quot;&quot;):
    &apos;&apos;&apos;extract text from image&apos;&apos;&apos;
    segments, text_boxes = textbox_recognition(file_path)
    column_lines = detect_column_lines(segments)

    # if single column
    if len(column_lines) &amp;lt; 3:
        return &quot;&quot;

    # align text boxes by column
    # text boxes within columns
    ordered_text_box = []
    for i in range(len(column_lines)):
        prev_line = column_lines[i-1] if ((i-1) &amp;gt;= 0) else 0
        boxes = get_boxes_for_line(
            text_boxes, column_lines[i], ordered_text_box, prev_line)
        for b in boxes:
            ordered_text_box.append(b)

    # boxes that are not in any column
    # text boxes not in any column
    non_selected_boxes = []
    for i in text_boxes:
        if i not in ordered_text_box:
            non_selected_boxes.append(i)

    for i in non_selected_boxes:
        y = i[1]
        if y &amp;lt;= ordered_text_box[0][1]:
            ordered_text_box.insert(0, i)
        else:
            ordered_text_box.append(i)

    img = cv2.imread(file_path, cv2.IMREAD_GRAYSCALE)
    ret, thresh = cv2.threshold(img, 225, 255, 0)
    img_shape = img.shape

    pdf_shape = (0, 0)
    page = None
    if pdf_file_path != &quot;&quot;:
        doc = fitz.open(pdf_file_path)
        page = doc[0]
        pdf_shape = (page.rect.width, page.rect.height)

    resume_text = &quot;&quot;
    for i in ordered_text_box:
        if pdf_file_path != &quot;&quot;:
            txt = clean_text(get_text_from_pdf(i, img_shape, pdf_shape, page))
        else:
            txt = clean_text(get_text(thresh, i))
        resume_text += txt + &quot;\n&quot;

    # clean text
    txt = resume_text.split(&quot;\n&quot;)

    res = []
    for line in txt:
        if len(line) == 0:
            continue
        res.append(line)

    resume_text = &apos; \n &apos;.join(res)
    return resume_text

&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;dockerizing-the-application&quot;&gt;Dockerizing the Application&lt;/h2&gt;

&lt;p&gt;To make deploying the application easy we will be &lt;code&gt;Dockerizing the Application&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Dockerfile&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# syntax=docker/dockerfile:1

FROM python:3.9-buster

WORKDIR /resume-parser-docker

RUN mkdir input_files
RUN pip3 install --upgrade pip

COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt

# download nltk required
RUN python -m nltk.downloader punkt
RUN python -m nltk.downloader averaged_perceptron_tagger
RUN python -m nltk.downloader maxent_ne_chunker
RUN python -m nltk.downloader words

RUN apt-get update \
  &amp;amp;&amp;amp; apt-get -y install tesseract-ocr

RUN apt-get update &amp;amp;&amp;amp; apt-get install ffmpeg libsm6 libxext6  -y

COPY . .

EXPOSE 5000/tcp

CMD [ &quot;python3&quot;, &quot;-u&quot; , &quot;main.py&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then run following commands to create image and run it.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Build Image
    &lt;pre&gt;&lt;code&gt;docker build --tag jhamadhav/resume-parser-docker .
&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;Run Image at port 5000
    &lt;pre&gt;&lt;code&gt;docker run -d -p 5000:5000 jhamadhav/resume-parser-docker
&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;Check images
    &lt;pre&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;Stop once done
    &lt;pre&gt;&lt;code&gt;docker stop jhamadhav/resume-parser-docker
&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;hosting-on-aws&quot;&gt;Hosting on AWS&lt;/h2&gt;

&lt;p&gt;Now that we have a docker image of our application.&lt;/p&gt;

&lt;p&gt;We can publish it to dockerHub:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker push jhamadhav/resume-parser-docker
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then login to your EC2 instance and pull the image:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker pull jhamadhav/resume-parser-docker
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Run the image:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -d -p 5000:5000 jhamadhav/resume-parser-docker
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
  &lt;p&gt;🎉🎉🎉 We have a fully functional Resume parser ready.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;future-work&quot;&gt;Future Work&lt;/h2&gt;

&lt;p&gt;We can make use of &lt;code&gt;Large Language Models (LLM)&lt;/code&gt;, train on datasets and fine tune LLM model to make extraction of below fields more accurate:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;School/Institute name&lt;/li&gt;
  &lt;li&gt;Degree&lt;/li&gt;
  &lt;li&gt;Company name&lt;/li&gt;
  &lt;li&gt;Role in a job&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;In conclusion, resume parsing using NLP techniques offers a streamlined approach to extract crucial information from resumes, enhancing the efficiency and accuracy of candidate screening.&lt;/li&gt;
  &lt;li&gt;By leveraging OCR, named entity recognition, and line sweep algorithms, we can handle various resume formats, including multicolumn layouts.&lt;/li&gt;
  &lt;li&gt;The power of NLP automates the parsing process, empowering recruiters to efficiently process resumes and make informed hiring decisions.&lt;/li&gt;
  &lt;li&gt;Embracing resume parsing techniques ensures fair and objective evaluation of applicants, leading to successful recruitment outcomes.&lt;/li&gt;
  &lt;li&gt;With this skillset, you can revolutionize resume processing and contribute to more efficient hiring practices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have any questions, doubts, or just want to say hi, feel free to reach out to me at &lt;code&gt;contact@jhamadhav.com&lt;/code&gt; ! I’m always ready to chat about this cool project and help you out. Don’t be shy, drop me a line and let’s geek out together!&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;/technology/resume-parsing-insights-and-steps-to-create-your-own-parser/&quot;&gt;Resume Parsing: Insights and Steps to Create Your Own Parser&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on June 20, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Debugging &amp; Fixing mysql deadlock issue]]></title>
  <link rel="alternate" type="text/html" href="/technology/debugging-and-fixing-mysql-deadlock-issue/"/>
  <id>/technology/debugging-and-fixing-mysql-deadlock-issue</id>
  <updated>2023-06-12 18:31:00 +0530T00:00:00-00:00</updated>
  <published>2023-06-12T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#Mysql" term="Mysql" /><category scheme="/tags/#deadlock" term="deadlock" /><category scheme="/tags/#Index%20locks" term="Index locks" /><category scheme="/tags/#Next%20Key%20Lock" term="Next Key Lock" /><category scheme="/tags/#ruby" term="ruby" /><category scheme="/tags/#Ruby%20on%20Rails" term="Ruby on Rails" /><category scheme="/tags/#aasm" term="aasm" />
  <content type="html">
  
    &lt;p&gt;Recently, during one of our tests, we encountered a deadlock issue that was reported by Sentry. The deadlock occurred while attempting to insert scores into a table after completing a candidate’s test. We were initially unsure about the cause of this deadlock. Upon investigation, we discovered that it was due to the interplay of various locks in our MySQL database. In this blog post, we will deep dive into the nature of these locks, understand their impact on transactions, and present the solutions we implemented to mitigate deadlock occurrences.&lt;/p&gt;

&lt;h4 id=&quot;understanding-deadlocks&quot;&gt;&lt;strong&gt;Understanding deadlocks&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;To understand the deadlock situation, let’s familiarize ourselves with the different types of locks involved, as defined by the official MySQL documentation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GAP Lock:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A gap lock is a lock on a gap between index records, or a lock on the gap before the first or after the last index record. A gap might span a single index value, multiple index values, or even be empty.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If id is not indexed or has a nonunique index, the statement does lock the preceding gap.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next Key Lock:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A next-key lock is a combination of a record lock on the index record and a gap lock on the gap before the index record. in simple words If one session has a shared or exclusive lock on record R in an index, another session cannot insert a new index record in the gap immediately before R in the index order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Insert Intention Lock:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An insert intention lock is a type of gap lock set by INSERT operations prior to row insertion. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap.&lt;/p&gt;

&lt;h4 id=&quot;problem-scenario&quot;&gt;&lt;strong&gt;Problem Scenario&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;In our case, we have two tables, table1 and table2, with a has_many relationship. All operations are performed on table2, which has an index on table1 as a foreign key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transaction A&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;BEGIN;
DELETE FROM table2 WHERE table2.table1_id=127;
Query OK, 1 row affected (0.00 sec)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Resulting data locks&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;mysql&amp;gt; SELECT INDEX_NAME, LOCK_TYPE,LOCK_DATA,LOCK_MODE,LOCK_STATUS, EVENT_ID FROM performance_schema.data_locks;
+-----------------------------------------+-----------+-----------+---------------+-------------+----------+
| INDEX_NAME                | LOCK_TYPE | LOCK_DATA | LOCK_MODE     | LOCK_STATUS | EVENT_ID |
+-----------------------------------------+-----------+-----------+---------------+-------------+----------+
| NULL                      | TABLE     | NULL      | IX            | GRANTED     |      408 |
| index_table2_on_table1_id | RECORD    | 127, 92   | X             | GRANTED     |      408 |
| PRIMARY                   | RECORD    | 92        | X,REC_NOT_GAP | GRANTED     |      408 |
| index_table2_on_table1_id | RECORD    | 128, 93   | X,GAP         | GRANTED     |      408 |
+-----------------------------------------+-----------+-----------+---------------+-------------+----------+
4 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This query acquires a gap lock on table2 and an insert intention lock on table1_id values 126 and 127.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transaction B&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;BEGIN;
INSERT INTO table2(table1_id) VALUES(126);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Resulting data locks&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;mysql&amp;gt; SELECT INDEX_NAME,LOCK_TYPE,LOCK_DATA,LOCK_MODE,LOCK_STATUS, EVENT_ID FROM performance_schema.data_locks;
+-----------------------------------------+-----------+-----------+------------------------+-------------+----------+
| INDEX_NAME                  | LOCK_TYPE | LOCK_DATA | LOCK_MODE              | LOCK_STATUS | EVENT_ID |
+-----------------------------------------+-----------+-----------+------------------------+-------------+----------+
| NULL                        | TABLE     | NULL      | IX                     | GRANTED     |      351 |
| index_table2_on_table1_id   | RECORD    | 127, 92   | X,GAP,INSERT_INTENTION | WAITING     |      351 |
| NULL                        | TABLE     | NULL      | IX                     | GRANTED     |      408 |
| index_table2_on_table1_id   | RECORD    | 127, 92   | X                      | GRANTED     |      408 |
| PRIMARY                     | RECORD    | 92        | X,REC_NOT_GAP          | GRANTED     |      408 |
| index_table2_on_table1_id   | RECORD    | 128, 93   | X,GAP                  | GRANTED     |      408 |
+-----------------------------------------+-----------+-----------+------------------------+-------------+----------+
6 rows in set (0.01 sec)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As Transaction A holds the lock on table1_id 126 due to the gap lock, Transaction B waits for the lock. However, it eventually times out, resulting in a lock wait timeout error.&lt;/p&gt;

&lt;p&gt;To create a deadlock, one must perform a delete query in Transaction B. Then, when attempting to insert a record in Transaction A, a deadlock error is thrown, with Transaction B becoming the victim. &lt;strong&gt;This deadlock situation arises due to the conflicts in the next-key lock, preventing Transaction B from inserting the record.&lt;/strong&gt;&lt;/p&gt;

&lt;h4 id=&quot;in-a-nutshell&quot;&gt;&lt;strong&gt;In a nutshell&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Lets understood the above queries in nutshell to create a deadlock.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Transaction A -&amp;gt; BEGIN;&lt;/li&gt;
  &lt;li&gt;Transaction A -&amp;gt; DELETE records on table2 with table1_id=x.&lt;/li&gt;
  &lt;li&gt;Transaction B -&amp;gt; BEGIN;&lt;/li&gt;
  &lt;li&gt;Transaction B -&amp;gt; DELETE record on table2 with table1_id=y;&lt;/li&gt;
  &lt;li&gt;Transaction B -&amp;gt; INSERT a record on table2 and table1_id is x-1.&lt;/li&gt;
  &lt;li&gt;Transaction A -&amp;gt; INSERT a record on table2 and table1_id is y-1.&lt;/li&gt;
  &lt;li&gt;A deadlock occurs, with Transaction A being the victim.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;practical-example-of-gap-lock--next-key-lock&quot;&gt;&lt;strong&gt;Practical example of GAP lock &amp;amp; Next Key Lock.&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Gap lock is basically on range of values &amp;amp; will be aquired on a range if we try to delete a record which does not exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;table1&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;+----+
| id |
+----+
| 73 |
| 74 |
| 81 |
| 82 |
+----+&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;table2&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;+-----+-----------+
| id  | table1_id |
+-----+-----------+
| 1   | 73        | 
| 2   | 82        |
+-----+-----------+&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Transaction A&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;BEGIN;
DELETE from table2 where table1_id=75;
Query OK, 0 rows affected (0.00 sec)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This transaction will aquire a gap lock on range from 74-80.
this means if we try to insert new values in table2(in another session) with table1_id ranging from 74-80 it will wait until delete transaction commits.&lt;/p&gt;

&lt;h4 id=&quot;other-issues&quot;&gt;&lt;strong&gt;Other issues&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;In addition to addressing the deadlock issues caused by gap locks, we also encountered problems related to AASM records. We were using the AASM gem, a library that manages state transitions. In our case, this library was responsible for changing the state of the test to “completed” and executing several callback functions. These operations were performed as part of a single transaction, which sometimes resulted in prolonged transaction durations and increased the likelihood of deadlocks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model dummy code&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;aasm do
  state :active, initial: true
  state :complete
  event :complete, after: [:method1, :method2, :method3] do
      transitions from: :active, to: :complete
  end
end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;When the test is marked as complete and the state changes, all the MySQL-related queries are executed as part of a single transaction.&lt;/p&gt;

&lt;p&gt;Due to the execution of all these methods within a single transaction, there were instances where the transaction took a considerable amount of time to complete. These prolonged transactions duration increased the risk of deadlocks occurrence and also resulted in issues related to lock wait time.&lt;/p&gt;

&lt;h4 id=&quot;fix&quot;&gt;&lt;strong&gt;FIX&lt;/strong&gt;&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;To fix this we moved the insertion of records as a separate transaction out of the aasm state change.&lt;/li&gt;
  &lt;li&gt;Optimized transaction size: We optimized the other badly written queries in the transaction.&lt;/li&gt;
  &lt;li&gt;Reduced transaction duration: Only limited number of queries were part of the state change transaction (to keep the transaction short).&lt;/li&gt;
  &lt;li&gt;We further optimized the GAP lock by avoiding unnecessary delete queries when the records were not present in the table with the corresponding ID.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;references&quot;&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/h4&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html#innodb-gap-locks&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Innodb Gap Lock&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html#innodb-next-key-locks&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Innodb Next Key Lock&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html#innodb-insert-intention-locks&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Innodb Insert Intention Lock&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://medium.com/@tanishiking/avoid-deadlock-caused-by-a-conflict-of-transactions-that-accidentally-acquire-gap-lock-in-innodb-a114e975fd72&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Gap lock with example medium article&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.percona.com/blog/innodbs-gap-lock&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Gap lock article by percona&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

  
  &lt;p&gt;&lt;a href=&quot;/technology/debugging-and-fixing-mysql-deadlock-issue/&quot;&gt;Debugging &amp; Fixing mysql deadlock issue&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on June 12, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Website Monitor Using Google App Script]]></title>
  <link rel="alternate" type="text/html" href="/technology/website-monitor-using-google-app-script/"/>
  <id>/technology/website-monitor-using-google-app-script</id>
  <updated>2022-12-30 02:04:27 +0530T00:00:00-00:00</updated>
  <published>2022-12-30T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#google-app-script" term="google-app-script" /><category scheme="/tags/#uptime-monitor" term="uptime-monitor" /><category scheme="/tags/#website-status" term="website-status" /><category scheme="/tags/#upptime" term="upptime" />
  <content type="html">
  
    &lt;p&gt;Recently, I was looking for a solution to notify me when a website is down and when it is back up. I found a few solutions, but they all had a learning curve. So I thought of an alternative solution using Google App Script, which I had recently learned about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Can run every 5 minutes.&lt;/li&gt;
  &lt;li&gt;Can send emails when the website is down.&lt;/li&gt;
  &lt;li&gt;Trustworthy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wasn’t sure if the first requirement was possible with Google App Script, but the other two were. After reading the documentation, I found that it was possible to create a time-based trigger for a script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps to follow:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Create a new Google App Script project.&lt;/li&gt;
  &lt;li&gt;Create a function to track the website. Here is an example:&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;function myFunction() {
   const initialUrls = [
     { uri: &amp;#39;https://mock.codes/200&amp;#39;, status: &amp;#39;&amp;#39;},
     { uri: &amp;#39;https://mock.codes/500&amp;#39;, status: &amp;#39;&amp;#39;}
   ];
 
 const properties = PropertiesService.getScriptProperties();
 let urls =  JSON.parse(properties.getProperty(&amp;#39;URL_LIST&amp;#39;)) || initialUrls;
 const errorResponseCodes = [500, 502, 503, 504];
 const alertEmail = &amp;#39;alertmail@gmail.com&amp;#39;;
 
 const options = { muteHttpExceptions: true };
 
 urls.forEach((url) =&amp;gt; {
   let responseCode = UrlFetchApp.fetch(url.uri, options).getResponseCode();
 
   const isErrorResponse = errorResponseCodes.includes(responseCode);
   const wasPreviouslyDown = url.status === &amp;#39;down&amp;#39;;

   if (isErrorResponse &amp;amp;&amp;amp; !wasPreviouslyDown) {
     // Site is now down for the first time
     const subject = `Alert: Your site ${url.uri} is currently down`;
     const body = `${url.uri} has encountered an error with status code ${responseCode}`;
     MailApp.sendEmail(alertEmail, subject, body);
     url.status = &amp;#39;down&amp;#39;;
   } else if (!isErrorResponse &amp;amp;&amp;amp; wasPreviouslyDown) {
     // Site was previously down, but is now back up
     const subject = `Your site ${url.uri} is now back up`;
     const body = `${url.uri} has recovered and is now back up`;
     MailApp.sendEmail(alertEmail, subject, body);
     url.status = &amp;#39;&amp;#39;;
   }
 });

 properties.setProperty(&amp;#39;URL_LIST&amp;#39;, JSON.stringify(urls));
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Go to the “Triggers” menu in the left sidebar of the Google App Script project.&lt;/li&gt;
  &lt;li&gt;Click the “Add Trigger” button and select the function to run.&lt;/li&gt;
  &lt;li&gt;Choose the options to run the trigger every 5 minutes and click “Save”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This above code uses the UrlFetchApp service to make HTTP requests to the websites and check their status. it stores the value of each trigger in a variable so that whenver site goes live again it can send email of website backed up.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;You can also check the logs for each trigger execution in the “Execution” menu on the left side of the project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/website-monitor/email.jpg&quot; alt=&quot;Email Sample&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In conclusion, Google App Script is a useful tool for creating a customized website tracker that can notify the user when a website is down. The process of setting up the tracker is straightforward and the logs can be easily accessed to track the execution of the function. this basic functionality can be enhanced more to record the status in a csv file. also interesting graphs and charts can be made using that data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additional investigations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/upptime/upptime&quot; target=&quot;_blank&quot; style=&quot;color: blue;&quot;&gt;Upptime&lt;/a&gt; is one of the good open-source alternative which can be used to monitor a website. it uses github actions to make sure the website is up and creates a issue if website is down for some reason. it also logs the information about the website speed.&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;/technology/website-monitor-using-google-app-script/&quot;&gt;Website Monitor Using Google App Script&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on December 30, 2022.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[The revamp of a Video Proctoring Solution: A Behind-the-Scenes Look]]></title>
  <link rel="alternate" type="text/html" href="/technology/the-revamp-of-a-video-proctoring-solution-a-behind-the-scenes-look/"/>
  <id>/technology/the-revamp-of-a-video-proctoring-solution-a-behind-the-scenes-look</id>
  <updated>2022-12-27 12:33:45 +0530T00:00:00-00:00</updated>
  <published>2022-12-27T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#proctoring" term="proctoring" /><category scheme="/tags/#live%20proctoring" term="live proctoring" /><category scheme="/tags/#remote%20proctoring" term="remote proctoring" /><category scheme="/tags/#eLitmus%20proctoring" term="eLitmus proctoring" /><category scheme="/tags/#100ms" term="100ms" /><category scheme="/tags/#webRTC" term="webRTC" />
  <content type="html">
  
    &lt;p&gt;&lt;strong&gt;&lt;em&gt;The story of how we took a good platform and made it even better&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/revamp-of-proctoring-solution/proctoring-dashboard.png&quot; alt=&quot;Protcoring Dashboard&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Background&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For the past few months, the number of test takers and clients at eLitmus has increased significantly. Conducting all of these tests remotely poses a significant challenge in terms of preventing cheating. To address this issue, eLitmus has developed an in-house solution using the open-source &lt;a href=&quot;https://github.com/Kurento/kurento-media-server&quot;&gt;Kurento media server&lt;/a&gt;. While this solution has been effective in terms of recording videos, it is not horizontally scalable.&lt;/p&gt;

&lt;p&gt;In search of a more effective solution, eLitmus turned to &lt;a href=&quot;https://aws.amazon.com/kinesis/&quot;&gt;Amazon Kinesis&lt;/a&gt; and worked with the AWS team to conduct a proof-of-concept. While this approach allowed for live proctoring, it was not possible to record the exams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How did it get begin?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As I was learning about &lt;a href=&quot;https://webrtc.org/&quot;&gt;WebRTC&lt;/a&gt; and Amazon Kinesis during this time, I had the opportunity to attend a session by a company called &lt;a href=&quot;https://www.100ms.live/&quot;&gt;100ms&lt;/a&gt;. This company is focused on solving problems related to live conferencing, and I was eager to learn more about their approach.&lt;/p&gt;

&lt;p&gt;After connecting with the co-founder of &lt;a href=&quot;https://www.100ms.live/&quot;&gt;100ms&lt;/a&gt;, I received a message from their salesperson to schedule a demo call. During the call, we determined that 100ms could be a potential solution for eLitmus’ scalability problem. However, we needed to weigh the costs of maintaining engineering time and effort to maintain the solution against the opportunity cost of using that time to build a new product, as well as overall server and bandwidth costs.&lt;/p&gt;

&lt;p&gt;Based on this analysis, we decided to proceed with a proof-of-concept for live remote proctoring. I spent the next week working on the proof-of-concept and was able to complete it successfully. From there, we saw potential synergies between 100ms and eLitmus and decided to make the product(&lt;a href=&quot;https://github.com/elitmus/knights-watch&quot;&gt;Knights Watch&lt;/a&gt;) an open-source platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Designing &amp;amp; Developing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I created a document outlining the requirements for the video proctoring solution, including features such as a proctoring dashboard, candidate tests screen, cheating analysis and verification dashboard, admin dashboard, and auto proctoring. For the first version (v0.1), we planned to roll out the proctoring dashboard with multiple streams visible to the proctor, storage of the video stream on an s3 server, retrieval of the video stream in the cheating analysis and verification dashboard, and admin configuration.&lt;/p&gt;

&lt;p&gt;After outlining the requirements for the video proctoring solution, I designed the &lt;a href=&quot;https://docs.google.com/presentation/d/1_CebvXEStUtx8m4Hw9DLQPK6AD8gxBKU/edit?usp=sharing&amp;amp;ouid=100590295233713204603&amp;amp;rtpof=true&amp;amp;sd=true&quot;&gt;architecture&lt;/a&gt; for the solution, diagrammatically representing how all of the components would be connected. The main components of the app were the 100ms server API, the eLitmus server, and the candidate or proctor’s browser.&lt;/p&gt;

&lt;p&gt;Next, I created a &lt;a href=&quot;https://github.com/elitmus/knights-watch/milestone/1&quot;&gt;milestone&lt;/a&gt; on Github and listed out the issues that needed to be addressed, including the integration of the proctor dashboard, candidate test screen, algorithm for assigning candidates and proctors to rooms, and storage of videos on the eLitmus prescribed directory structure on an s3 server.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/revamp-of-proctoring-solution/milestone.png&quot; alt=&quot;Milestone&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I began working on these issues and was able to roll out the v0.1 of the proctoring solution within a few weeks. During this time, our team encountered various challenges and suggested various features to 100ms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenges Faced&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As we worked on storing videos on an AWS s3 server in our prescribed directory structure, we encountered a challenge with the 100ms API. The webhook provided by 100ms was only for the composite recording of the room, not for individual recordings. However, we needed webhooks to notify us of the success of each individual recording. In addition, 100ms had the functionality for only a single webhook per account, but we needed to support multiple environments with multiple applications within a single account. We requested this feature from 100ms.&lt;/p&gt;

&lt;p&gt;While working on an algorithm to assign candidates and proctors to rooms, I faced the challenge of storing authentication tokens in the user’s browser and in Redis storage in production. I wrote an algorithm to handle the expiration of tokens from both ends and to handle multiple events.&lt;/p&gt;

&lt;p&gt;As we configured 100ms for various environments including staging, production, and edge, we encountered several issues and suggested various features to 100ms. These included the ability to delete apps and templates from the 100ms dashboard from the front-end, team management options in the dashboard, and handling of access keys and secrets for multiple environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing live remote proctoring solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After completing the first version (v0.1) of the video proctoring solution, we were ready to test it in production. eLitmus was conducting an internal hiring event at the time, and we used the live video proctoring feature for this event with around 400 candidates. The event went smoothly, with minor issues. The proctor was able to hear the voices of the candidates and all of the videos were recorded throughout the session.&lt;/p&gt;

&lt;p&gt;This success gave us confidence in the solution, and we made some minor tweaks. However, our main concern from the start had been scalability, and we wanted to test the solution at a larger scale. We had an in-person test at IITK with over 600 candidates, and decided to conduct the event with live proctoring. The event went smoothly, but the next day we conducted data analysis and discovered that 14 out of 600+ videos had some data loss or were not recorded.&lt;/p&gt;

&lt;p&gt;We had a meeting with 100ms to discuss this issue, and after working with their engineering team, we determined that the issue was caused by network connectivity problems. We fixed the issue and the proctoring solution became more stable, with 97% of the videos being recorded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open-sourcing video proctoring solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After this event, we had discussions with 100ms about pricing and suggested various features, including pricing on the 100ms dashboard itself and the option to opt-in or opt-out of composite recording and browser-based recording.&lt;/p&gt;

&lt;p&gt;After making the video proctoring solution an open-source project, I focused on documenting the project so that it could be used by others in the community and more developers could contribute to it. I wrote several documents, including a readme file, information on the architecture and prerequisites, installation guidelines, development guidelines, deployment guidelines, a code of conduct, and guidelines for contributing and welcoming new contributors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In conclusion, the development of the video proctoring solution at eLitmus was a challenging but rewarding process. By identifying a need to solve the problem of vertical scalability, we were able to explore various solutions and ultimately choose 100ms as a partner to help us build a scalable and effective video proctoring platform. Through the development process, we encountered various challenges and were able to work closely with the 100ms team to find solutions and improve the stability of the platform. We are proud to have made the video proctoring solution an open-source project and to have contributed to the community by documenting the project and welcoming new contributors. We hope that others will find this project useful and will be able to build upon it to create even better solutions in the future.&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;/technology/the-revamp-of-a-video-proctoring-solution-a-behind-the-scenes-look/&quot;&gt;The revamp of a Video Proctoring Solution: A Behind-the-Scenes Look&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on December 27, 2022.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Fixing Capybara Flaky Tests]]></title>
  <link rel="alternate" type="text/html" href="/technology/fixing-capybara-flaky-tests/"/>
  <id>/technology/fixing-capybara-flaky-tests</id>
  <updated>2022-12-20 00:21:23 +0530T00:00:00-00:00</updated>
  <published>2022-12-20T00:00:00+05:30</published>
  
  <author>
    <name>eLitmus.com</name>
    <uri></uri>
    <email>site-admin@elitmus.com</email>
  </author>
  <category scheme="/tags/#capybara" term="capybara" /><category scheme="/tags/#rails" term="rails" /><category scheme="/tags/#system-tests" term="system-tests" /><category scheme="/tags/#flaky" term="flaky" />
  <content type="html">
  
    &lt;p&gt;When writing system tests for a user interface, it is common to encounter test cases that fail randomly. One of the common failure can occur when the JavaScript on a page takes time to render, causing issues with the test case.&lt;/p&gt;

&lt;p&gt;For example, imagine a test case that clicks a button on a page and then checks for the presence of certain content after the click.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo Code&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;visit submit_page
click_on &amp;#39;Submit&amp;#39;
assert page.has_content &amp;#39;Some content after clicking on submit&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In most cases, this test will run without any issues. However, occasionally the test may fail on the third line with the error “Expected false to be truthy”. This error can occur when the page is visited and the JavaScript on the page takes a few seconds to load. During this time, the submit button may be clicked, but because there is no JavaScript associated with the button yet, the button click does not do anything. As a result, the test is still on the submit page when it tries to assert that the expected content is present, causing the test to fail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One solution to this problem is to increase the &lt;code&gt;wait_time&lt;/code&gt; setting in capybara. However, this approach has several limitations. First, the wait_time setting is global and applies to all test cases, so if it is set to a high value, it will increase the overall execution time of the test suite. Additionally, the wait_time setting only waits for a fixed amount of time before moving on with the test, without checking whether the page has finished loading. This means that if the page takes longer to load than the wait_time&lt;/p&gt;

&lt;p&gt;The other solution is to use the &lt;code&gt;execute_script&lt;/code&gt; method provided by Capybara to click the button instead of the &lt;code&gt;click_on&lt;/code&gt; method. The execute_script method allows you to execute JavaScript code within the context of the current page. By using this method to click the button, the click action is added to the end of the browser’s call stack. This means that the click action will be executed after any existing JavaScript code on the page has finished running, ensuring that the button is fully initialized and ready to be interacted with before the test tries to click it.&lt;/p&gt;

&lt;p&gt;To use the execute_script method to click the button, you can use the following code:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;page.find_button(&amp;#39;Submit&amp;#39;).execute_script(&amp;#39;this.click()&amp;#39;)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This way we can ensure that click method will run only after the page javascript is fully loaded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser Call Stack&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;|               |
          |               |
          |   JavaScript  |  &amp;lt;-- existing code on the page(1)
          |_______________|
          |               |
          |   JavaScript  |  &amp;lt;-- existing code on the page(2)
          |_______________|
          |               |
          |  click action |  &amp;lt;-- added by execute_script method(3)
          |_______________|&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


  
  &lt;p&gt;&lt;a href=&quot;/technology/fixing-capybara-flaky-tests/&quot;&gt;Fixing Capybara Flaky Tests&lt;/a&gt; was originally published by eLitmus.com at &lt;a href=&quot;&quot;&gt;eLitmus Blog&lt;/a&gt; on December 20, 2022.&lt;/p&gt;</content>
</entry>

</feed>
