<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Abhibhaw Asthana]]></title><description><![CDATA[Hobbyist Developer | Full time PM | Confused Entrepreneur]]></description><link>https://blog.abhibhaw.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1701623716591/8dALyOA9L.jpeg</url><title>Abhibhaw Asthana</title><link>https://blog.abhibhaw.com</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 16 Oct 2026 05:17:49 GMT</lastBuildDate><atom:link href="https://blog.abhibhaw.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Automating Redash permission with Redash API]]></title><description><![CDATA[Redash is such an amazing tool that literally helps you make sense of your data. If this is the first time you have encountered this word:

Redash is an opensource tool that helps you to connect and query your data sources, build some cool dashboards...]]></description><link>https://blog.abhibhaw.com/automating-redash-permission-with-redash-api</link><guid isPermaLink="true">https://blog.abhibhaw.com/automating-redash-permission-with-redash-api</guid><category><![CDATA[redash]]></category><category><![CDATA[Python]]></category><category><![CDATA[automation]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Abhibhaw Asthana]]></dc:creator><pubDate>Mon, 26 Feb 2024 03:59:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1708919820781/f58d5f3b-ac27-4b69-9215-341e85589708.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Redash is such an amazing tool that literally helps you make sense of your data. If this is the first time you have encountered this word:</p>
<blockquote>
<p><a target="_blank" href="https://redash.io/">Redash</a> is an opensource tool that helps you to connect and query your data sources, build some cool dashboards &amp; graphs to visualize data and share them with your team.</p>
</blockquote>
<p>We at our company are using the same to run tons of queries across multiple datasources and teams. But as the team size kept growing, it becames difficult to manage and audit the permissions of the users.</p>
<p>Luckily we have our in-house permission management dashboard built over Django, where a user raises a request to get a particular permission and the manager <strong>aka approver</strong> verifies and approves the request. So we decided to add this Redash permission thing to the same flow. In our case anyone with our company’s email have default access to redash (via Google SSO), so we will be only updating the permissions for user in this article, you can use the same approach for creating new user or any other api requirements as well.</p>
<h2 id="heading-context">Context</h2>
<p>As stated above our dashboard is built over Django, the code examples below are in python, but you can use any language of your choice.</p>
<p>After going through the <a target="_blank" href="https://redash.io/help/user-guide/integrations-and-api/api/">Redash API docs</a>, our redash client network tab and few googling, I figured out that the redash API is pretty simple and straight forward. You just need to append <code>/api/</code> between your base url and route to get the API endpoint.</p>
<blockquote>
<h3 id="heading-the-solution">The Solution</h3>
<p>Let’s say your redash client is accessible at: <code>https://redash.example.com/</code> and you are able to get the list of all the groups at: <code>https://redash.example.com/groups</code>, then the API endpoint would be: <code>https://redash.example.com/api/groups</code></p>
</blockquote>
<h2 id="heading-coding-begins">Coding Begins</h2>
<p>Now that we have the API endpoint, we can start the interesting part of writing the code. We will be using the <a target="_blank" href="https://docs.python-requests.org/en/master/">requests</a> library to make the API calls. Let’s begin by building up some utility functions for the same.</p>
<h3 id="heading-common-utility-functions">Common Utility Functions</h3>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> requests

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Redash</span>():</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, endpoint, api_key</span>):</span>
        self.redash_url = endpoint
        self.session = requests.Session()
        self.session.headers.update({<span class="hljs-string">"Authorization"</span>: <span class="hljs-string">"Key {}"</span>.format(api_key)})

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_get</span>(<span class="hljs-params">self, path, **kwargs</span>):</span>
        <span class="hljs-keyword">return</span> self._request(<span class="hljs-string">"GET"</span>, path, **kwargs)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_post</span>(<span class="hljs-params">self, path, **kwargs</span>):</span>
        <span class="hljs-keyword">return</span> self._request(<span class="hljs-string">"POST"</span>, path, **kwargs)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_put</span>(<span class="hljs-params">self, path, **kwargs</span>):</span>
        <span class="hljs-keyword">return</span> self._request(<span class="hljs-string">"PUT"</span>, path, **kwargs)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_patch</span>(<span class="hljs-params">self, path, **kwargs</span>):</span>
        <span class="hljs-keyword">return</span> self._request(<span class="hljs-string">"PATCH"</span>, path, **kwargs)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_delete</span>(<span class="hljs-params">self, path, **kwargs</span>):</span>
        <span class="hljs-keyword">return</span> self._request(<span class="hljs-string">"DELETE"</span>, path, **kwargs)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_request</span>(<span class="hljs-params">self, method, path, **kwargs</span>):</span>
        url = <span class="hljs-string">"{}/api/{}"</span>.format(self.redash_url, path)
        response = self.session.request(method, url, **kwargs)
        response.raise_for_status()
        <span class="hljs-keyword">return</span> response

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_credentials</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">try</span>:
            response = self._get(<span class="hljs-string">"session"</span>)
            <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>
        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
            <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
</code></pre>
<p>Here we have defined a class <code>Redash</code> which takes in the <code>endpoint</code> and <code>api_key</code> as the arguments. We have defined some utility functions like <code>_get</code>, <code>_post</code>, <code>_put</code>, <code>_patch</code> and <code>_delete</code> which will be used to make the API calls. And they are using the <code>_request</code> function which will concatinate <code>endpoint</code>, <code>api</code>, <code>path</code> and finally makes the request. We have also defined a function <code>test_credentials</code> which will be used to test our Redash credential (kind of health check).</p>
<h3 id="heading-main-utility-functions">Main Utility Functions</h3>
<p>From here the main utility functions are specific to my use case but you can use them as a reference to build your own. My idea is to ask emailID of the user’s redash account and list them all the groups they aren’t part of. And then ask the user to select the groups they want to be part of.</p>
<p>So we need the following main functions:</p>
<ol>
<li><p><code>get_available_groups_for_user</code> to get all the groups the user isn’t part of. As I couldn’t find a readymade <code>api endpoint</code> for this we will require the following functions:</p>
<ul>
<li><p><code>get_all_groups</code> to get all the groups in the redash instance</p>
</li>
<li><p><code>get_user_groups</code> to get all the groups the user is part of</p>
</li>
</ul>
</li>
</ol>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_all_groups</span>(<span class="hljs-params">self</span>):</span>
    response = self._get(<span class="hljs-string">"groups"</span>)
    <span class="hljs-keyword">return</span> response.json() <span class="hljs-comment"># You can put try cache here as well for better error handeling.</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_user_groups</span>(<span class="hljs-params">self, email</span>):</span>
    <span class="hljs-keyword">try</span>:
        path = <span class="hljs-string">f"users?page=1&amp;page_size=1&amp;q=<span class="hljs-subst">{email}</span>"</span>
        response = self._get(path)
        json_response = response.json()
        <span class="hljs-keyword">if</span>(json_response[<span class="hljs-string">"count"</span>] &gt; <span class="hljs-number">0</span>):
            <span class="hljs-keyword">return</span> json_response[<span class="hljs-string">"results"</span>][<span class="hljs-number">0</span>][<span class="hljs-string">"groups"</span>]
        <span class="hljs-keyword">else</span>:
            <span class="hljs-keyword">return</span> []
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        <span class="hljs-keyword">return</span> []
</code></pre>
<p>Using the above 2 functions we can get the list of all the groups the user isn’t part of.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_available_groups_for_user</span>(<span class="hljs-params">self, email</span>):</span>
    all_groups = self.get_all_groups()
    user_groups = self.get_user_groups(email)
    available_groups = []
    <span class="hljs-keyword">for</span> group <span class="hljs-keyword">in</span> all_groups:
        current_group = {<span class="hljs-string">'name'</span>: group[<span class="hljs-string">'name'</span>], <span class="hljs-string">'id'</span>: group[<span class="hljs-string">'id'</span>]} <span class="hljs-comment"># we will be appending group id for the final function &amp; name to display in dropdown</span>
        <span class="hljs-keyword">if</span> current_group <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> user_groups:
            available_groups.append(current_group)
    <span class="hljs-keyword">return</span> available_groups
</code></pre>
<ol>
<li><p>And then, <code>post_user_in_that_group</code> to add the user to the selected group. As this POST request requires user_id not emailID, we need to get the user_id from the emailID. So we need this function as well:</p>
<ul>
<li><code>get_user_id</code> to get the user id of the user. It will also act as a check if the user got removed/deleted from redash after raising the request.</li>
</ul>
</li>
</ol>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_user_id</span>(<span class="hljs-params">self, email</span>):</span>
    <span class="hljs-keyword">try</span>:
        path = <span class="hljs-string">f"users?page=1&amp;page_size=1&amp;q=<span class="hljs-subst">{email}</span>"</span>
        response = self._get(path)
        json_response = response.json()
        <span class="hljs-keyword">if</span>(json_response[<span class="hljs-string">"count"</span>] &gt; <span class="hljs-number">0</span>):
            <span class="hljs-keyword">return</span> json_response[<span class="hljs-string">"results"</span>][<span class="hljs-number">0</span>][<span class="hljs-string">"id"</span>]
        <span class="hljs-keyword">else</span>:
            <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>
</code></pre>
<p>Now as we have everything in place. We can finally <code>update</code> the requested group permission for the current user.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">post_user_in_that_group</span>(<span class="hljs-params">self, email, group_id</span>):</span>
        <span class="hljs-keyword">try</span>:
            user_id = self.get_user_id(email) <span class="hljs-comment"># You can put a check here to go ahead only if user_id is not None</span>
            path = <span class="hljs-string">f"groups/<span class="hljs-subst">{group_id}</span>/members"</span>
            response = self._post(path, json={<span class="hljs-string">"user_id"</span>: user_id})
            <span class="hljs-keyword">return</span> response.json()
        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
            <span class="hljs-keyword">raise</span> e
</code></pre>
<h2 id="heading-putting-it-all-together">Putting it all together</h2>
<p>The above functions can be called in frontend (<code>views.py</code> in my case) to carry out the permission updating tasks.</p>
<p>Suggestions and feedbacks are welcome.</p>
<p>Until next time, TaDa 👋</p>
]]></content:encoded></item><item><title><![CDATA[Automating schema migration at scale with gh-ost]]></title><description><![CDATA[Schema migration can be a real headache, especially in large-scale applications where database structures evolve over time. Traditional methods of schema migration often involve manual intervention, which can be time-consuming, error-prone, and disru...]]></description><link>https://blog.abhibhaw.com/automating-schema-migration-at-scale-with-gh-ost</link><guid isPermaLink="true">https://blog.abhibhaw.com/automating-schema-migration-at-scale-with-gh-ost</guid><category><![CDATA[Devops]]></category><category><![CDATA[MySQL]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[automation]]></category><category><![CDATA[gh-ost]]></category><dc:creator><![CDATA[Abhibhaw Asthana]]></dc:creator><pubDate>Sun, 28 May 2023 12:21:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1701623810106/13a22410-e4ce-4f60-9533-da2f719a9d9a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Schema migration can be a real headache, especially in large-scale applications where database structures evolve over time. Traditional methods of schema migration often involve manual intervention, which can be time-consuming, error-prone, and disruptive to ongoing operations. In this article, we will explore how we have eliminated this problem by completely automating the schema migration at our organization with the help of this excellent tool developed by <a target="_blank" href="https://github.com/">GitHub</a>, <a target="_blank" href="https://github.com/github/gh-ost/">gh-ost</a>, which is a triggerless online schema migration solution for MySQL. GH-OST has some <a target="_blank" href="https://github.com/github/gh-ost/blob/master/doc/requirements-and-limitations.md">requirements and limitations</a>, which we handled separately via our custom rule engine.</p>
<h1 id="heading-gh-ost-who"><strong>GH-OST ??? Who?</strong></h1>
<p>GH-OST operates on a concept similar to other online schema migration tools. It begins by creating a ghost table that closely resembles the structure of the original table. The migration process starts with an empty ghost table, and data is gradually and incrementally copied from the original table to the ghost table. Simultaneously, any ongoing changes, such as INSERT, DELETE, or UPDATE operations applied to the original table, are also propagated to the ghost table. This ensures that the ghost table remains synchronized with the original table throughout the migration process. Finally, when the appropriate moment arrives, GH-OST performs the crucial step (Cut-Over) of replacing the original table with the ghost table, completing the migration seamlessly. This approach allows for minimal downtime and ensures a smooth transition while preserving the integrity of the data. For more detailed information, you can refer to the <a target="_blank" href="https://github.com/github/gh-ost#readme">project description on GH-OST’s GitHub page</a> within the GitHub organization.</p>
<h1 id="heading-the-flow"><strong>The Flow!</strong></h1>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*7T5vEPRqWPhiyKAiNWGDfQ.png" alt class="image--center mx-auto" /></p>
<p>In our implementation, we have established a streamlined high-level flow to handle user Alter Requests effectively. When a user initiates an Alter Request, our robust rule engine comes into play, thoroughly examining the submitted details against a predefined set of rules. This ensures that the request aligns with our system’s requirements and standards. Once the request passes the rule checks, it proceeds to the verification stage, where it undergoes scrutiny and approval from project owners, who hold the responsibility of ensuring the request’s validity and feasibility. Following this, the request moves on to the next level of scrutiny, where a database administrator (DBA) thoroughly reviews and approves it, considering the impact on the database environment. Once the request has obtained the necessary approvals, it undergoes execution first in the development or testing environment (DB stage) to ensure smooth implementation and identify any potential issues. Finally, the altered functionality or feature is executed in the production environment, ensuring the changes are reflected for end-users. Throughout this process, all stakeholders involved in the Alter Request are kept informed and notified at each crucial step, maintaining transparency and collaboration. Additionally, to maintain a clean and organized system, a cleanup job is triggered automatically once the process is completed, ensuring any temporary resources or artifacts are appropriately handled. This well-structured flow not only ensures efficient handling of Alter Requests but also emphasizes accountability, reliability, and effective communication among the team members involved.</p>
<p>This was a rough overview of the implemented process; now let’s dive deep into each individual step.</p>
<h1 id="heading-rule-engine"><strong>Rule Engine</strong></h1>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*jB1CQHn7ee9o3bydBPYQsg.png" alt class="image--center mx-auto" /></p>
<p>To facilitate the seamless execution of Alter Requests, we have developed our own rule engine as a Python class, incorporating essential functions to conduct various predefined checks. This rule engine plays a pivotal role in ensuring the integrity and compatibility of the requested changes. Within this engine, a range of checks are performed on both the query and the database environment. Some key checks include verifying the presence of <strong>foreign keys in the table</strong>, assessing the <strong>size of indexes</strong>, evaluating the <strong>overall database size</strong>, examining the <strong>size of individual tables</strong>, the <strong>right time</strong> to carry out this migration process, analyzing the <strong>maximum thread capacity</strong> and current <strong>database load</strong>, and assessing the <strong>status of the database replication</strong>.</p>
<p>These checks can be tailored to align with specific organizational requirements, allowing for a customized rule engine that meets the unique needs of our system. By implementing this rule engine, we enhance the accuracy and reliability of our Alter Request process, empowering us to effectively address potential issues and maintain the stability of our database system</p>
<h1 id="heading-role-of-our-ghostly-wizard-gh-ost"><strong>Role of our ghostly wizard, GH-OST?</strong></h1>
<p><img src="https://miro.medium.com/v2/resize:fit:320/0*6FvbaqAKtZjatb1x.png" alt class="image--center mx-auto" /></p>
<p>Once the Alter Request has successfully passed the rule engine and undergone the two-step validation process, it enters the realm of our ghostly wizard, GH-OST. Our Python script takes charge at this stage and utilizes GH-OST commands to execute the alteration process seamlessly. The specific GH-OST parameters used in our implementation, which are determined by the rule engine, are outlined below.</p>
<blockquote>
<p><em>Note: {{xyz}} represents variables</em></p>
</blockquote>
<h2 id="heading-1-test-on-replica"><strong>1. Test on replica</strong></h2>
<p>To ensure a smooth transition, GH-OST performs a test on a replica database before proceeding with the actual alteration.</p>
<pre><code class="lang-bash">gh-ost --max-load=Threads_connected={{max_thread_connected}},Threads_running={{max_thread_running}} --cut-over-lock-timeout-seconds=180 --max-lag-millis=50000 --chunk-size=2000 --test-on-replica --throttle-control-replicas={{slave_db_host}}:3306 --port=3306 --user={{username}} --password={{password}} --host={{master_db_host}} --database={{database_name}} --table={{table_name}} --verbose --alter=<span class="hljs-string">"{{alter_query}}"</span> --switch-to-rbr --cut-over=default --default-retries=120 --nice-ratio=0 --throttle-flag-file={{request_id}}_gh-ost.throttle --postpone-cut-over-flag-file={{request_id}}_ghost.postpone.flag --panic-flag-file={{request_id}}_ghost.panic.flag --execute
</code></pre>
<h2 id="heading-2-execute-on-master"><strong>2. Execute on master</strong></h2>
<p>Once the test on the replica is successfully completed, GH-OST proceeds with the actual execution on the master database.</p>
<pre><code class="lang-bash">gh-ost --max-load=Threads_connected={{max_thread_connected}},Threads_running={{max_thread_running}} --cut-over-lock-timeout-seconds=180 --max-lag-millis=50000 --chunk-size=2000 --allow-on-master --throttle-control-replicas={{slave_db_host}}:3306 --port=3306 --user={{username}} --password={{password}} --host={{master_db_host}} --database={{database_name}} --table={{table_name}} --verbose --alter=<span class="hljs-string">"{{alter_query}}"</span> --switch-to-rbr --cut-over=default --default-retries=120 --nice-ratio=0 --throttle-flag-file={{request_id}}_gh-ost.throttle --postpone-cut-over-flag-file={{request_id}}_ghost.postpone.flag --panic-flag-file={{request_id}}_ghost.panic.flag --execute
</code></pre>
<p>The parameters for this phase mirror those used in the replica test, with the addition of the allow-on-master parameter to enable alteration execution on the master database</p>
<h2 id="heading-3-sanity-check"><strong>3. Sanity Check</strong></h2>
<p>Before proceeding with the cut-over and finalizing the migration, a sanity check is performed. This check compares the entries in the migrated table and the original table for the past seven days, ensuring a perfect synchronization between the two.</p>
<h2 id="heading-4-remove-postpone-file-to-take-cutover-and-finish-migration"><strong>4. Remove postpone file to take cutover and finish migration</strong></h2>
<p>Once the migration is ready for the cut-over, the postpone flag file is removed, triggering the swapping of tables and the completion of the migration process.</p>
<pre><code class="lang-python">remove_file = <span class="hljs-string">f"{{request_id}}_ghost.postpone.flag"</span>
<span class="hljs-keyword">if</span> os.path.exists(remove_file):
            os.remove(remove_file)
            logger.info(<span class="hljs-string">"Cutover flag file removed"</span>)
</code></pre>
<p>The script checks for the existence of the flag file (postpone-cut-over-flag-file) and removes it if present, allowing the cut-over call to be made and finalizing the migration.</p>
<h1 id="heading-post-completion"><strong>Post Completion</strong></h1>
<p><img src="https://miro.medium.com/v2/resize:fit:1024/0*fmWY63LyFQuqEzZi.png" alt class="image--center mx-auto" /></p>
<p>Upon the successful completion of the migration process, the stakeholders involved are promptly notified to acknowledge the accomplishment. However, in the rare event that issues arise during the process, appropriate measures are in place to address them effectively. In such cases, notifications are triggered, employing various communication channels such as Slack, email, or even phone calls to alert the Database Administrators (DBAs) of any errors based on their severity level. This proactive approach ensures that immediate attention is given to high severity errors, enabling swift resolution and minimizing any potential impact on the system. By incorporating robust error handling and notification mechanisms, we reinforce the reliability and resilience of our Alter Request process, fostering a culture of accountability, and ensuring the smooth operation of our database environment.</p>
<p>By implementing this approach, we have significantly alleviated the workload of our DBAs, allowing them to focus their valuable time and expertise on driving innovation and further advancements. To put it into perspective, our system has successfully processed over 10,000 DB requests in the past six months alone. This impressive accomplishment stands as a motivation to our team to consistently deliver timely and reliable innovative solutions while promoting a culture of continuous improvement.</p>
]]></content:encoded></item></channel></rss>