<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://belinhacbr.xyz/feed.xml" rel="self" type="application/atom+xml" /><link href="https://belinhacbr.xyz/" rel="alternate" type="text/html" /><updated>2025-08-14T15:15:42+00:00</updated><id>https://belinhacbr.xyz/feed.xml</id><title type="html">Isabella Ribeiro</title><subtitle>Frontpage</subtitle><entry><title type="html">Long Live RSS!</title><link href="https://belinhacbr.xyz/blog/2025-06-13-long-live-rss/" rel="alternate" type="text/html" title="Long Live RSS!" /><published>2025-06-13T10:34:00+00:00</published><updated>2025-06-13T10:34:00+00:00</updated><id>https://belinhacbr.xyz/blog/long-live-rss</id><content type="html" xml:base="https://belinhacbr.xyz/blog/2025-06-13-long-live-rss/"><![CDATA[<p>I’ve returned to using more RSS feeds for a while now, and what a refreshment it has been! A warm welcome to my long lost friend, the small web<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>, full of cool people.</p>

<p>No more algorithm-driven feed. No more personalised clickbaits. No more dubious facts from sites I never visited. No more articles purposefully fed to me to please the ad-sense lords. Control and intention over my content consumption. Embracing the simplicity and efficiency of RSS, and taking back the joy of a self-curated web experience.</p>

<p>I can’t help but feel that RSS is one of the most underrated tools on the web. And there’s so much more we can do with it!</p>

<p>Starting with web syndication, as a way to distribute content from one website to another, letting people subscribe to updates without visiting the original site. It allows decentralized content sharing, consumption, and access to desired content from multiple sources in a single place. While a conerstone of the searchable web, somewhere in the recent decade we took a wrong turn and started to rely on algorithms to decide what we want to read. This is where RSS comes in to save the day, as a simple and effective way to syndicate content across the web.</p>

<p>RSS (Really Simple Syndication) is a consolidated popular (old!) format for web syndication. RSS feeds are machine readable XML files that contain a summary of the content, including titles, descriptions, and links to the full articles. It is indeed really simple! These elements are typically updated whenever new content is published.</p>

<p>RSS feeds can be found on many websites, including <a href="/feed.xml" target="_blank">this one</a>. One can subscribe to RSS feeds using feed readers or aggregators, which automatically check for updates and display new content in a user-friendly format. RSS is particularly useful for people who want to stay updated on multiple websites without having to visit or refresh each one individually or fill your inbox with newsletters (ahem). It allows easy content discovery and online reading without distractions. There are many popular feed readers out there. Lately, I use <a href="https://feeder.co" title="Feeder - RSS Reader">feeder</a> for its slim features. You can pick the one you like and are never locked in to any platform, pretty much every service and app supports exporting/importing RSS feeds.</p>

<p>RSS works well for blogs or news sites; but it also works for for any website that publishes content regularly. This includes podcasts, <a href="https://www.youtube.com/feeds/videos.xml?channel_id=UCXNQazM-CHmsyQW2iHqUbHg" title="https://www.youtube.com/feeds/videos.xml?channel_id=&lt;channel_id&gt;">YouTube channels</a>, and even social media accounts<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">2</a></sup> <sup id="fnref:6" role="doc-noteref"><a href="#fn:6" class="footnote" rel="footnote">3</a></sup>. Having your own RSS feed lets you receive updates on your favorite content without relying on algorithms or notifications from individual platforms.</p>

<p>If you haven’t already, consider subscribing to your favorite websites’ RSS feeds. Streamline your online reading experience and take control of your content consumption. The internet is an infinite ad-space forever in expansion. While we work on a consolidated federated and syndicated<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">4</a></sup> search for ourselves. Do what you can to skip the algorithm, and spend your (limited) time on the web on the things you actually care about. As a reward you get an intentional ad-free experience by default, and privacy-friendly too!</p>

<p>If you are already reading this article in an RSS reader, send me your website or RSS feed, and I will add it to my reader.
Long live RSS!</p>

<hr />

<p>Worthy readings:</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p><a href="https://ar.al/2020/08/07/what-is-the-small-web/">What is the Small Web? - Aral Balkan</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5" role="doc-endnote">
      <p><a href="https://www.rssboard.org/news/211/every-mastodon-user-has-rss-feed">Every Mastodon user has an RSS feed - RSS Board</a> <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6" role="doc-endnote">
      <p><a href="https://openrss.org/blog/bluesky-has-launched-rss-feeds">Bluesky RSS - Bluesky</a> <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p><a href="https://martinh.net/hacks/poisoned-well/">Poisoned Well - Martin Hering</a> <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>belinhacbr</name></author><category term="blog" /><category term="rss" /><category term="web syndication" /><category term="content syndication" /><category term="small web" /><category term="a domain of one&apos;s own" /><summary type="html"><![CDATA[An old way to syndicate content]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://belinhacbr.xyz/assets/images/banner.png" /><media:content medium="image" url="https://belinhacbr.xyz/assets/images/banner.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Level Up Your Collaboration: GitHub Workflow Strategies</title><link href="https://belinhacbr.xyz/blog/2025-05-16-github-workflow-collaboration/" rel="alternate" type="text/html" title="Level Up Your Collaboration: GitHub Workflow Strategies" /><published>2025-05-16T12:13:00+00:00</published><updated>2025-05-16T12:13:00+00:00</updated><id>https://belinhacbr.xyz/blog/github-workflow-collaboration</id><content type="html" xml:base="https://belinhacbr.xyz/blog/2025-05-16-github-workflow-collaboration/"><![CDATA[<p>You and your <del>team</del> party are already crushing the basics of GitHub, juggling branches, and smashing that merge button. But have you ever felt like things could be… smoother? Or as your team and projects grow in complexity, these foundational skills might feel…stretched. Or that amazing collaboration could be even <em>more</em> amazing?</p>

<p>Developing code in a team can feel like a full on campaign, as the projects goes on phases, new levels mean different ways to deal with different challenges. The party can grow and the way you collaborate changes. How to keep a smooth workflow on a evolving scenario?</p>

<p>Well, behold adventurers, because today we’re diving into some practical ways to polish your existing GitHub workflow and make your team’s development journey even more delightful!</p>

<h3 id="branching-out-in-a-good-way">Branching Out (in a Good Way!)</h3>

<p>We all love our feature branches, right? They keep our main codebase clean and full of purpose. But have you ever felt the need to take your branching game to the next level?</p>

<ul>
  <li>
    <p><strong>More than just <code class="language-plaintext highlighter-rouge">main</code>:</strong> Depending on your team’s rhythm, exploring models like <a href="https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow"><em>Gitflow</em></a> (with its <code class="language-plaintext highlighter-rouge">develop</code> and <code class="language-plaintext highlighter-rouge">release</code> branches) or the simpler <a href="https://docs.github.com/en/get-started/using-github/github-flow"><em>GitHub Flow</em></a> (deploying directly from <code class="language-plaintext highlighter-rouge">main</code> or short-lived branches) can bring more structure. Try exploring those, highlight their pros and cons for your team and project. It’s like choosing the right dance steps for your team – find the one that makes you groove!<br />
 [ <em>Ideal antidote for</em> unstructured workflows in a small but growing team. ]</p>
  </li>
  <li>
    <p><strong>Short and Sweet Always:</strong> Tiny, focused feature branches are a trusted allies. They’re easier to review, less likely to have merge conflicts, facilitate more frequent integration with the main codebase and generally just happier to work with.<br />
  [ <em>Ideal antidote for</em> long-lived forgotten branches with <em>way too many</em> changes. ]</p>
  </li>
  <li>
    <p><strong>Explore Feature Flags:</strong> This is new to me but it has the potential to be a wild magic. Imagine being able to release code to a small group of users before unleashing it on everyone. Or even being able to work on new features in their own branches and merge them into the main codebase without making the incomplete or potentially unstable feature visible to users. Feature flags are like little toggles that will allow you do just that. Combine them with your branching strategy for safer and more controlled rollouts and you get a perfectly placed on/off switch for features.<br />
  [ <em>Ideal antidote for</em> coupled deployment/release cycles. ]</p>
  </li>
</ul>

<h3 id="pull-requests-fancy-a-touch-of-radiance">Pull Requests: Fancy a touch of radiance?</h3>

<p>Pull requests are where the magic of collaboration truly happens. Let’s focus on making them more effective!</p>

<ul>
  <li>
    <p><strong>Tell a Story:</strong> A pull request is like meeting your reviewers back at the tavern and telling them what you’ve been through on your adventure. Forget those vague “Fixes bug” descriptions, you want to tell where you went and which bug you killed. Craft pull request descriptions that explain <em>why</em> you made the changes, what problem they solve, and maybe even a little “<em>how</em>.”</p>
  </li>
  <li>
    <p><strong>Templates Are Formulas for Success:</strong> Having a consistent pull request template ensures everyone knows what to expect during a review. Include checklists for testing, documentation updates, or any other crucial steps. Everyone loves a helpful checklist.</p>
  </li>
  <li>
    <p><strong>Reviewing Like a Wizard:</strong> This is a longer topic but, to cut things short, code review isn’t just about catching bugs; it’s about sharing knowledge and improving code quality together.</p>
    <ul>
      <li>
        <p><strong>Focus on the “Why”:</strong> Ask clarifying questions about the approach and methodology, not just the syntax.</p>
      </li>
      <li>
        <p><strong>Suggest Changes Directly:</strong> GitHub’s <a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/incorporating-feedback-in-your-pull-request">“suggested changes” feature</a> is a lifesaver. It makes it super easy for authors to incorporate feedback directly avoiding ambiguity.</p>
      </li>
      <li>
        <p><strong>Automate the Boring Stuff:</strong> Tools like linters and static analysis can catch many common issues automatically. Integrate them with GitHub Actions to keep your codebase consistent and following the style guide, so you can skip the “line break here” type of comments.</p>
      </li>
    </ul>
  </li>
</ul>

<h3 id="github-actions-an-automation-npc">GitHub Actions: An Automation NPC</h3>

<p>Github Actions can also be a very attentive robot assistant! Take advantage of that on your repository.</p>

<ul>
  <li>
    <p><strong>Further Than CI/CD:</strong> Sure, automated testing and deployment are amazing, but Actions can do so much more! Think about automatically generating documentation, updating dependencies, checking for security vulnerabilities, or even sending Slack notifications when a pull request is merged. Browse the <a href="https://github.com/marketplace?type=actions">marketplace</a>. Maybe you can make a game out of it? The possibilities are endless!</p>
  </li>
  <li>
    <p><strong>Small and Mighty Workflows:</strong> Elturel wasn’t built in a day! Don’t feel like you need to build huge workflows right away. Start small. Automate one repetitive task and build from there. See if it makes sense. Iterate. Every. little. bit. helps.</p>
  </li>
  <li>
    <p><strong>Share the Knowledge!</strong> If you create a particularly useful Action, consider sharing it within your team or even with the wider community. Sharing is caring!</p>
  </li>
</ul>

<h3 id="teamwork-makes-the-perfect-party">Teamwork Makes the Perfect Party!</h3>

<p>Ultimately, a great GitHub workflow is about how well your team collaborates.</p>

<ul>
  <li>
    <p><strong>Code Ownership: Who’s Responsible for this Dragon?</strong> Defining clear code ownership (using <code class="language-plaintext highlighter-rouge">CODEOWNERS</code> files) can streamline the review process by automatically assigning the right people to review changes in specific areas. It’s great to know the designated experts for different parts of your codebase.</p>
  </li>
  <li>
    <p><strong>Talk it Out:</strong> Sometimes, complex changes are easier to discuss in a quick call or a pair-programming session rather than just through comments on a pull request. Don’t be afraid to hop on a call. Find the time, hash things out and summarize the discussion in a comment.</p>
  </li>
  <li>
    <p><strong>Retrospectives: Learn From Your Encounters!</strong> Regularly reflect on your workflow as a team and have a team huddle to strategize your next move. What’s working well? What could be better? What do you dislike doing on regular basis? Use retrospectives to identify bottlenecks and experiment with new approaches. Make it periodic, consistent and structured; it keeps everyone isn the loop in the long-game.</p>
  </li>
</ul>

<h3 id="keep-on-levelling-up">Keep on levelling-up!</h3>

<p>Optimizing your GitHub workflow isn’t a one-time thing; it’s an ongoing campaign. It will change according to the team and the nature of the work. Keep experimenting, keep learning, and most importantly, keep talking to your team! Find what works best for you and have fun along the way.</p>]]></content><author><name>belinhacbr</name></author><category term="blog" /><category term="gitops" /><category term="devops" /><category term="github" /><summary type="html"><![CDATA[You and your team party are already crushing the basics of GitHub, juggling branches, and smashing that merge button. But have you ever felt like things could be… smoother? Or as your team and projects grow in complexity, these foundational skills might feel…stretched. Or that amazing collaboration could be even more amazing?]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://belinhacbr.xyz/assets/images/banner.png" /><media:content medium="image" url="https://belinhacbr.xyz/assets/images/banner.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">CI/CD Pipeline Sanity Check</title><link href="https://belinhacbr.xyz/blog/2025-03-12-cicd-sanity-check/" rel="alternate" type="text/html" title="CI/CD Pipeline Sanity Check" /><published>2025-03-12T19:13:00+00:00</published><updated>2025-03-12T19:13:00+00:00</updated><id>https://belinhacbr.xyz/blog/cicd-sanity-check</id><content type="html" xml:base="https://belinhacbr.xyz/blog/2025-03-12-cicd-sanity-check/"><![CDATA[<p>I did my fair share of staring at a red X in my CI/CD pipeline while muttering: <em>“Why does this CI/CD pipeline keep failing?”</em>, not always sure if I made the undesired sight happen, but always knowing I wasn’t alone in this.</p>

<p>I feel pipeline failures are similar to unexploded WWII bombs in your DevOps workflow: they often frustrate the team, slow you down and stem from sneaky, overlooked issues.</p>

<p>Before rage-quitting, <code class="language-plaintext highlighter-rouge">rm -rf</code> an entire setup and never seeing a sweet terminal again, I got the habit to run a quick <strong>sanity check</strong> on the usual suspects.</p>

<h3 id="1-the-obvious-culprit-our-code">1. The Obvious Culprit: Our Code</h3>

<ul>
  <li><strong>Failing tests?</strong> Check the logs! Flaky tests, race conditions, or bad assertions love to sneak in.</li>
  <li><strong>Syntax errors?</strong> Linters and IDE checks help, but CI systems don’t forgive typos. Mirror the pipeline linter on your IDE, hold it close.</li>
  <li><strong>Dependency hell?</strong> <code class="language-plaintext highlighter-rouge">npm install</code>, <code class="language-plaintext highlighter-rouge">pip</code>, or <code class="language-plaintext highlighter-rouge">go mod</code> can break overnight. Lock your versions.</li>
  <li><strong>New failure?</strong> <strong>Write a test for it.</strong> If something breaks in CI, it’ll break again. Capture the bug with a test <em>now</em> instead of future-you debugging it twice.</li>
</ul>

<p><strong>Quick sanity check (e.g.) :</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Reproduce locally, then lock it down with a test</span>
pytest tests/test_fixed_bug.py <span class="nt">-k</span> <span class="s2">"test_thing_that_failed_in_ci"</span>
</code></pre></div></div>

<h3 id="2-the-silent-killer-pipeline-configs">2. The Silent Killer: Pipeline Configs</h3>
<p>Your <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code>, <code class="language-plaintext highlighter-rouge">Jenkinsfile</code>, or GitHub Actions workflow might be:</p>

<ul>
  <li><strong>Misconfigured</strong> (indentation, wrong keys, missing steps).</li>
  <li><strong>Using outdated syntax</strong> (CI tools evolve fast).</li>
  <li><strong>Assuming wrong environments</strong> (“But it works on my machine!”).</li>
</ul>

<p><strong>Quick sanity check (e.g.) :</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Validate your configs (e.g., GitHub Actions)</span>
<span class="s">gh workflow lint .github/workflows/deploy.yml</span>
</code></pre></div></div>

<h3 id="3-the-phantom-menace-environment-variables">3. The Phantom Menace: Environment Variables</h3>

<p><strong>Secrets vs. Variables:</strong></p>

<ul>
  <li><strong>Secrets</strong> (API keys, tokens) should <em>never</em> be hardcoded. Use your CI’s secret store.</li>
  <li><strong>Configs</strong> (e.g., <code class="language-plaintext highlighter-rouge">ENV=staging</code>) can be plain variables but should still be version-controlled.</li>
</ul>

<p><strong>Common fails:</strong></p>

<ul>
  <li><strong>Missing vars:</strong> Your local <code class="language-plaintext highlighter-rouge">.env</code> isn’t magically in CI. List required vars in your <code class="language-plaintext highlighter-rouge">README</code>.</li>
  <li><strong>Typos:</strong> <code class="language-plaintext highlighter-rouge">DATABASE_URL</code> ≠ <code class="language-plaintext highlighter-rouge">DB_URL</code> (case sensitivity matters!).</li>
  <li><strong>Scope issues:</strong> Does the variable exist in <em>this</em> job/stage?</li>
</ul>

<p><strong>Quick sanity check (e.g.) :</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Debug env vars in CI (GitHub Actions example)</span>
- name: Log <span class="nb">env </span>vars
  run: <span class="nb">printenv</span> | <span class="nb">sort</span>
</code></pre></div></div>

<h3 id="4-the-empire-strikes-back-permissions">4. The Empire Strikes Back: Permissions</h3>

<ul>
  <li><strong>Missing secrets?</strong> AWS keys, SSH tokens, or database URLs must exist in CI variables.</li>
  <li><strong>Wrong permissions?</strong> Can your runner access the registry, repo, or deployment target?</li>
  <li><strong>Resource limits?</strong> OOM kills, slow runners, or Docker rate limits can fail builds.</li>
</ul>

<p><strong>Quick sanity check (e.g.) :</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Debug permissions in a CI step</span>
- name: Check AWS access
  run: aws sts get-caller-identity
</code></pre></div></div>

<h3 id="5-the-hidden-time-bomb-external-dependencies">5. The Hidden Time Bomb: External Dependencies</h3>

<ul>
  <li><strong>APIs down?</strong> Tests calling <code class="language-plaintext highlighter-rouge">https://some-unreliable-api.com</code> will fail randomly. Mock these as much as possible without compromising your tests.</li>
  <li><strong>Package registry issues?</strong> npm, PyPI, or Maven outages do break builds. Breathe. Cache if possible.</li>
  <li><strong>Race conditions?</strong> Parallel jobs might conflict (e.g., DB migrations vs. tests). These deserve their own place in hell but also another chance.
<strong>Quick sanity check (e.g.) :</strong></li>
</ul>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Retry flaky steps in GitHub Actions</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Test</span>
  <span class="na">run</span><span class="pi">:</span> <span class="s">pytest</span>
  <span class="na">retry-on-error</span><span class="pi">:</span> <span class="no">true</span>
</code></pre></div></div>

<h3 id="6-the-human-factor">6. The Human Factor</h3>

<ul>
  <li><strong>“It worked yesterday!”</strong> → Someone changed a config, test, or dependency. Check those.</li>
  <li><strong>Manual hotfixes?</strong> Untracked changes in production can desync with CI. I know there’s no time to run a pipeline, run it anyway.</li>
  <li><strong>Stale branches?</strong> Merging old code without rebasing = 💥. Run pipelines on every bit of code. Fail in a PR, always merge with a pass and live a happy life.</li>
</ul>

<p><strong>Quick sanity check (e.g.) :</strong></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Enforce "test before merge"</span>
git push origin HEAD <span class="nt">--force-with-lease</span>  <span class="c"># (Just kidding, don't.)</span>
</code></pre></div></div>

<h3 id="main-quest-make-your-pipeline-resilient">Main Quest: Make Your Pipeline Resilient</h3>

<ul>
  <li><strong>Fail fast</strong>: Put cheap checks (lint, unit tests) early. Run pipelines as soon as you can, either on branches or PRs, avoid breaking <code class="language-plaintext highlighter-rouge">main</code>.</li>
  <li><strong>Log everything</strong>: Debugging without logs is like fixing a car blindfolded while holding a hyperactive hamster. If a riddle appears in the form of an error message, add some logs to help the next tortured soul (might be future-you).</li>
  <li><strong>Automate recovery</strong>: Auto-retry flaky steps, but <strong>always</strong> with limits.</li>
</ul>

<p>Next time your pipeline fails, <strong>don’t panic</strong> – run this checklist. And if all else fails, blame Docker. (Kidding… mostly.)</p>]]></content><author><name>belinhacbr</name></author><category term="blog" /><category term="devops" /><category term="cicd" /><summary type="html"><![CDATA[Why does this CI/CD pipeline keep failing?]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://belinhacbr.xyz/assets/images/banner.png" /><media:content medium="image" url="https://belinhacbr.xyz/assets/images/banner.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Flask Sync vs. Async: Understanding the Performance Bottleneck in IO-Bound Tasks</title><link href="https://belinhacbr.xyz/blog/2024-10-30-flask-sync-vs-async/" rel="alternate" type="text/html" title="Flask Sync vs. Async: Understanding the Performance Bottleneck in IO-Bound Tasks" /><published>2024-10-30T22:10:00+00:00</published><updated>2024-10-30T22:10:00+00:00</updated><id>https://belinhacbr.xyz/blog/flask-sync-vs-async</id><content type="html" xml:base="https://belinhacbr.xyz/blog/2024-10-30-flask-sync-vs-async/"><![CDATA[<p>A while ago, I was implementing an application using the well-known Python WSGI-based framework, <a href="https://flask.palletsprojects.com/en/stable/">Flask</a>. My application was running with 4 workers, there was route sending a request to an API and waiting for the response—a typical IO-bound operation, this operation could also be a database access or a file system call.</p>

<p>During testing, I noticed that whenever my application received more than 4 requests at once, they would ‘wait’ and take longer to respond. The access logs looked something like this:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">[2024-10-29 17:04:15] [pid-worker0] GET /io
[2024-10-29 17:04:15] [pid-worker1] GET /io
[2024-10-29 17:04:15] [pid-worker2] GET /io
[2024-10-29 17:04:15] [pid-worker3] GET /io
[2024-10-29 17:04:16] [pid-worker0] GET /io
[2024-10-29 17:04:16] [pid-worker1] GET /io
[2024-10-29 17:04:16] [pid-worker2] GET /io
[2024-10-29 17:04:16] [pid-worker3] GET /io
[2024-10-29 17:04:17] [pid-worker0] GET /io
[2024-10-29 17:04:17] [pid-worker1] GET /io
[2024-10-29 17:04:17] [pid-worker2] GET /io
[2024-10-29 17:04:17] [pid-worker3] GET /io
[2024-10-29 17:04:18] [pid-worker0] GET /io
[2024-10-29 17:04:18] [pid-worker1] GET /io
[2024-10-29 17:04:18] [pid-worker2] GET /io
[2024-10-29 17:04:18] [pid-worker3] GET /io</code></pre></figure>

<p>In this example, <code class="language-plaintext highlighter-rouge">/io</code> is an endpoint with an IO-bound task that took 1 second to be completed. So, sending 16 requests at once to this route, Flask would handle a batch of 4 requests concurrently, taking a total time of 4 seconds to process all of them. As you can see from the timestamps, even though all requests were sent concurrently, the request <code class="language-plaintext highlighter-rouge">n</code> takes <code class="language-plaintext highlighter-rouge">⌊n/number_workers⌋*task_time</code> to respond.</p>

<p>That was happening because of two reasons:</p>

<ol>
  <li>Flask is by default a <em>synchronous</em> framework</li>
  <li>Flask is based on WSGI, and WSGI uses <em>one worker</em> to handle <em>one request/response</em> cycle</li>
</ol>

<p>I had this issue a while ago. Since then, <a href="https://flask.palletsprojects.com/en/stable/changes/#version-2-0-0">Flask 2.0</a> was released with support for <a href="https://github.com/pallets/flask/pull/3412/commits/6979265fa643ed982d062f38d386c37bbbef0d9b">async views</a>, adding a more support for asynchronous tasks. Even though I was no longer working with Flask when this feature was released, I wanted to try the new <code class="language-plaintext highlighter-rouge">async views</code> and experience <em>some wild gains</em>.</p>

<p>I created an <code class="language-plaintext highlighter-rouge">async</code> route for a similar, now asynchronous task, and the access logs looked like this:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">[2024-10-29 17:37:30] [pid-worker0] GET /async-io
[2024-10-29 17:37:30] [pid-worker1] GET /async-io
[2024-10-29 17:37:30] [pid-worker2] GET /async-io
[2024-10-29 17:37:30] [pid-worker3] GET /async-io
[2024-10-29 17:37:31] [pid-worker0] GET /async-io
[2024-10-29 17:37:31] [pid-worker1] GET /async-io
[2024-10-29 17:37:31] [pid-worker3] GET /async-io
[2024-10-29 17:37:31] [pid-worker2] GET /async-io
[2024-10-29 17:37:32] [pid-worker1] GET /async-io
[2024-10-29 17:37:32] [pid-worker0] GET /async-io
[2024-10-29 17:37:32] [pid-worker2] GET /async-io
[2024-10-29 17:37:32] [pid-worker3] GET /async-io
[2024-10-29 17:37:33] [pid-worker1] GET /async-io
[2024-10-29 17:37:33] [pid-worker2] GET /async-io
[2024-10-29 17:37:33] [pid-worker3] GET /async-io
[2024-10-29 17:37:33] [pid-worker0] GET /async-io</code></pre></figure>

<p>And… it was exactly the same! It still took 4 seconds for all 16 requests to be completed.</p>

<p>After the initial disappointment, I looked for answers in the Flask documentation, and this very helpful <a href="https://flask.palletsprojects.com/en/stable/async-await/#performance">performance</a> section clarified my troubles.</p>

<p>Even though my request was now asynchronous, Flask is still using WSGI, which means that one worker handling one request/response cycle is still the case. The request is assigned to an available worker, if the route is an <code class="language-plaintext highlighter-rouge">async view</code>, Flask initializes an event loop in a thread, runs the task there, and returns the result.</p>

<p>Here is <code class="language-plaintext highlighter-rouge">Worker0</code> processing a request to a synchronous route:</p>

<figure class="highlight"><pre><code class="language-ascii" data-lang="ascii">              ┌──────────────────────────┐
   Request    │        ┌───────────────┐ │
──────────────► Worker0│/io            │ │
◄─────────────┬────────┤def ...        │ │
   Response   │        └───────────────┘ │
              └──────────────────────────┘</code></pre></figure>

<p>And here is <code class="language-plaintext highlighter-rouge">Worker1</code> processing a request to an asynchronous route:</p>

<figure class="highlight"><pre><code class="language-ascii" data-lang="ascii">              ┌──────────────────────────┐
   Request    │        ┌───────────────┐ │       ┌───────────────┐
──────────────► Worker1│/async-io      │ │       │  Event Loop   │
◄─────────────┼────────┤async def ...  ~~~~~~~~~~►   └run Task0  │
   Response   │        └────────▲──────┘ │Thread └───────────────┘
              └─────────────────┼────────┘            │
                                │                     │
                                └─────────────────────┘
                                          Result</code></pre></figure>

<p>while <code class="language-plaintext highlighter-rouge">Worker2</code> is processing a request to an asynchronous route with multiple concurrent tasks:</p>

<figure class="highlight"><pre><code class="language-ascii" data-lang="ascii">              ┌──────────────────────────┐
   Request    │        ┌───────────────┐ │       ┌───────────────┐
──────────────► Worker2│/async-io-multi│ │       │  Event Loop   │
◄─────────────┬────────│async def ...  ~~~~~~~~~~►   └run Task0  │
   Response   │        └────────▲──────┘ │Thread │   └run Task1  │
              └─────────────────┼────────┘       └───────────────┘
                                │                     │
                                └─────────────────────┘
                                          Result</code></pre></figure>

<p>Since I had a single asynchronous task per request, the worker being idle instead of switching to other incoming requests, makes the asynchronous benefit invisible.</p>

<p>If my IO-bound task could be divided in multiple concurrent tasks, then I would benefit from the <code class="language-plaintext highlighter-rouge">async view</code> feature.</p>

<p>For example, a scenario where you need to fetch data from multiple external APIs for a single request. If each API call takes 1 second and if you handle them either synchronously or asynchronously, your app might take several seconds to respond. But, if you split those requests into smaller concurrent tasks using async views, those can handled in parallel and respond much quicker.</p>

<p>With that in mind, I created another route to try that out, and the access logs for this case looked like:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">[2024-10-29 18:14:15] [pid-worker0] GET /async-io-multi
[2024-10-29 18:14:15] [pid-worker1] GET /async-io-multi
[2024-10-29 18:14:15] [pid-worker2] GET /async-io-multi
[2024-10-29 18:14:15] [pid-worker3] GET /async-io-multi
[2024-10-29 18:14:15] [pid-worker0] GET /async-io-multi
[2024-10-29 18:14:15] [pid-worker1] GET /async-io-multi
[2024-10-29 18:14:15] [pid-worker2] GET /async-io-multi
[2024-10-29 18:14:15] [pid-worker3] GET /async-io-multi
[2024-10-29 18:14:16] [pid-worker0] GET /async-io-multi
[2024-10-29 18:14:16] [pid-worker1] GET /async-io-multi
[2024-10-29 18:14:16] [pid-worker2] GET /async-io-multi
[2024-10-29 18:14:16] [pid-worker3] GET /async-io-multi
[2024-10-29 18:14:16] [pid-worker0] GET /async-io-multi
[2024-10-29 18:14:16] [pid-worker1] GET /async-io-multi
[2024-10-29 18:14:16] [pid-worker2] GET /async-io-multi
[2024-10-29 18:14:16] [pid-worker3] GET /async-io-multi</code></pre></figure>

<p>As you can see, dividing the task into two concurrent tasks, each taking 0.5 seconds, and using the same 4 workers. It processed 16 requests in 2 seconds, giving a 2x speed-up. In similar fashion, the request n for multiple concurrent tasks takes <code class="language-plaintext highlighter-rouge">⌊n/number_workers⌋*slowest_task_time</code> to return. If the task workload could be divided in even more tasks, there would be a benefit as well.</p>

<p>The real limitation lies with WSGI, which ties one worker to a request, limiting throughput due to the lack of an asynchronous request stack. An alternative, implemented by other fully asynchronous interfaces like ASGI, allows workers to handle multiple concurrent requests at once. Where, when an asynchronous task is blocked waiting for an IO-Bound operation, the worker can immediately switch to another request, optimizing resource usage and reducing latency. This approach favours frameworks based on ASGI, <a href="https://fastapi.tiangolo.com/">FastAPI</a> for example.</p>

<p>To be able to visualize that, here’s a simple comparison of response times for 100 requests to each framework, both using 4 workers, with the same tasks and similar routes based on the previous examples:</p>

<p><img src="https://imgur.com/mPf7TTe.png" alt="Flask vs. FastAPI IO-Bound" /></p>

<p>As you can see, FastAPI consistently performs better due to its ASGI foundation, while Flask’s async views still face limitations due to WSGI. FastAPI, in comparison to Flask, the synchronous endpoint <code class="language-plaintext highlighter-rouge">/io</code> is already blazing fast, taking less than 10% of the time Flask takes.</p>

<p>On the asynchronous endpoint with a single task <code class="language-plaintext highlighter-rouge">/async-io</code>, FastAPI is almost twice as fast than its own synchronous-self, and for the asychronous with multiple concurrent tasks <code class="language-plaintext highlighter-rouge">/async-io-multi</code> FastAPI is again almost twice as fast than its single-task-self.</p>

<p>Indeed, <strong>async is not inherently faster than sync code</strong>, even in a strictly IO-bound context and, that’s specially true with Flask. Between all considerations, Flask does offer an interface for multiple concurrent IO-bound tasks, in which can be useful if you are able to subdivide your tasks in smaller chunks and optimize a few endpoints that are being slow. However, if you are doing mainly asynchronous tasks, it’s worth considering other ASGI based frameworks.</p>

<hr />
<p>Both frameworks were run with 4 workers, using <a href="https://gunicorn.org/">Gunicorn</a> as the WSGI server for Flask and <a href="https://www.uvicorn.org/">Uvicorn</a> as the ASGI server for FastAPI;</p>

<p>You can find the code for this post on <a href="https://github.com/belinhacbr/flask-perf">GitHub</a>.</p>]]></content><author><name>belinhacbr</name></author><category term="blog" /><category term="python" /><category term="flask" /><category term="WSGI" /><category term="ASGI" /><category term="FastAPI" /><summary type="html"><![CDATA[A while ago, I was implementing an application using the well-known Python WSGI-based framework, Flask. My application was running with 4 workers, there was route sending a request to an API and waiting for the response—a typical IO-bound operation, this operation could also be a database access or a file system call.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://belinhacbr.xyz/assets/images/banner.png" /><media:content medium="image" url="https://belinhacbr.xyz/assets/images/banner.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Visual Catalog - 001 Moontide</title><link href="https://belinhacbr.xyz/projects/2024-08-30-visual-catalog-001/" rel="alternate" type="text/html" title="Visual Catalog - 001 Moontide" /><published>2024-08-30T20:30:00+00:00</published><updated>2024-08-30T20:30:00+00:00</updated><id>https://belinhacbr.xyz/projects/visual-catalog-001</id><content type="html" xml:base="https://belinhacbr.xyz/projects/2024-08-30-visual-catalog-001/"><![CDATA[<p>I had the idea for this visualization while looking at a tide chart last spring as I was trying to make a spontaneous crossing to a tidebound island. That day, I ended up not having enough time to cross.</p>

<p>As we know, the regular rise and fall of sea levels are caused primarily by the gravitational pull of the moon and, to a lesser extent, the sun. The moon’s gravity exerts a force on Earth, pulling water towards it. This pull creates a bulge in the ocean, resulting in high tide.</p>

<p>Interestingly, the moon’s phases also impact the strength of the tides. During a full moon and new moon, the sun, moon, and Earth align, leading to what are known as “spring tides.” These tides are characterized by higher high tides and lower low tides, resulting in a more significant difference between high and low water levels.</p>

<p>However, when you look at the line in a tide chart, it can hardly make you think about the moon or its current phase. This visualization is an attempt to show the gravitational force of the moon orchestrating the rhythmic dance of the tides—an alternate way to see our very distant neighbor relating to us in our everyday lives.</p>

<p>Check it out <a href="https://belinhacbr.xyz/visual-catalog/001-moontide/">here</a>. Fullscreen recommended! 🌝</p>

<p>This was made with <a href="https://pixijs.com/">PixiJS</a>. I drew each individual moon phase with a cutout, varying the radius of each moon phase according to a fictional “tide level.” You can find the source code <a href="https://github.com/belinhacbr/visual-catalog/">here</a>.🌚</p>

<p>This small visual project is the first in a series (at least I hope it will be!) under the umbrella of experiments that I now call “Visual Catalog”. Several years after <a href="https://belinhacbr.xyz/sensePi/">SensePi</a>, I wanted to continue doing similar experiments. Not being quite sure of where this is going is part of the destination, and I expect it to lead me through several rounds of short explorations in a similar vein.</p>]]></content><author><name>belinhacbr</name></author><category term="projects" /><category term="visual-catalog" /><category term="moontide" /><category term="javascript" /><summary type="html"><![CDATA[Visual experiments series]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://imgur.com/TpNw8Ry.gif" /><media:content medium="image" url="https://imgur.com/TpNw8Ry.gif" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Fairweather - An app for moderate weather activity enthusiasts</title><link href="https://belinhacbr.xyz/projects/2024-03-09-fairweather/" rel="alternate" type="text/html" title="Fairweather - An app for moderate weather activity enthusiasts" /><published>2024-03-09T09:34:00+00:00</published><updated>2024-03-09T09:34:00+00:00</updated><id>https://belinhacbr.xyz/projects/fairweather</id><content type="html" xml:base="https://belinhacbr.xyz/projects/2024-03-09-fairweather/"><![CDATA[<p>Winter months can be tough for people living in the north. I enjoy cycling during the warmer months, but long periods of cold and darkness can make me shy away from this activity. Sometimes, I just need a bit of reassurance that I won’t be caught in a storm at the top of a hill. After a particularly long week of cold slush in February, I felt like I needed something to help me decide when it was a good window to get outside. And so, after a couple days Fairweather was a-live!</p>

<p>At the initial sketch, I wanted the app to:</p>
<ul>
  <li>display the current location;</li>
  <li>provide a quick glimpse of the weather conditions and temperature;</li>
  <li>offer a short description of how the day might feel for me;</li>
  <li>show wind and humidity levels;</li>
  <li>include a forecast for the next few hours;</li>
  <li>highlight any potential hazards, like ice and snow.</li>
</ul>

<p>I kept the layout minimal and a bit boring —just enough information to help me decide whether it was a good day to head out. I didn’t want too much data or too many details. I was after something that would be quick to read and would make the decision easier, not harder. The least straightforward logic was the helper text, I wanted a single sentence to remind me how <em>I</em> personally feel under those conditions.</p>

<p>For the design, I kept it simple: just two contrasting colors, a technical font with a bit of roughness to it, and simple, outlined icons.</p>

<p>I ended up using <a href="https://openweathermap.org/">OpenWeather</a> to pull the essential weather data, as it’s accurate enough for my use case. The app itself was built with TypeScript, and I wanted to give <a href="https://nextjs.org/docs">Next.js</a> another try. The middleware was useful for data fetching, and deploying with Vercel was straightforward. For the “next few hours” line graph, I used <a href="https://airbnb.io/visx/docs/curve">Airbnb’s visx/curve</a>, which had the exact look and shape I was going for, and was easy to use.</p>

<p><img src="https://imgur.com/dUZhy1B.png" alt="" /><em>The final(current) version and my initial lousy sketch</em></p>

<p>You can check it out or install the app <a href="https://fairweather.belinhacbr.xyz/">here</a>. The source code is available on <a href="https://github.com/belinhacbr/fairweather">github</a>.</p>

<p>This app was made in less than a week in February, and I’ve been using it ever since, it’s simple and it gets me out there. While I could add more detailed information, I’ve realized that too much data just leads to overthinking. After all, I’m not looking for <em>perfect</em> weather; I’m just looking for a window when it’s <em>good enough</em> to get outside.</p>

<p>Living in Scotland, you quickly realize that the “perfect” time to get out is a myth. The perfect time to get out is the time you <em>do</em> get out, with the right precautions in place to not get caught in a sticky situation.</p>

<p>That said, I’m considering adding a feature highlighting visibility and sunset times, especially for the months when the sun is setting quickly and earlier. This could be useful for planning when the sunsets overlaps with the “next few hours” forecast.</p>

<p>Overall, Fairweather has been exactly what I needed. It does just enough to help me get outside without overwhelming me with unnecessary data. It’s not a fancy weather app—it’s a no-frills way to get a quick snapshot of the weather and make a decision about getting out. And for now, that’s all I need.</p>]]></content><author><name>belinhacbr</name></author><category term="projects" /><category term="fairweather" /><category term="nextjs" /><category term="typescript" /><summary type="html"><![CDATA[An app for moderate weather activity enthusiasts]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://imgur.com/NiXmnXU.png" /><media:content medium="image" url="https://imgur.com/NiXmnXU.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">All the glory to the Hypnotoad</title><link href="https://belinhacbr.xyz/projects/2017-05-12-all-the-glory/" rel="alternate" type="text/html" title="All the glory to the Hypnotoad" /><published>2017-05-12T22:10:00+00:00</published><updated>2017-05-12T22:10:00+00:00</updated><id>https://belinhacbr.xyz/projects/all-the-glory</id><content type="html" xml:base="https://belinhacbr.xyz/projects/2017-05-12-all-the-glory/"><![CDATA[<p>Since ALL SHALL OBEY THE HYPNOTOAD, this is an ascii respresentation of – ALL THE GLORY TO THE HYPNOTOAD!!!</p>

<p>Of course you can try yourself by opening your terminal and carefully typing:</p>

<p><code class="language-plaintext highlighter-rouge">$ curl hypntd.belinhacbr.xyz</code></p>

<p>You should be able to see something like this:</p>

<p><img src="http://i.imgur.com/MyiDcij.gif" alt="Screenshot" /></p>

<p>– ALL THE GLORY TO THE HYPNOTOAD!!! 🐸</p>

<hr />

<p>Check it out <a href="https://github.com/belinhacbr/ascii-hypnotoad">here</a>.
If you need some help, just <a href="https://github.com/belinhacbr/ascii-hypnotoad/issues">tell me</a>.</p>]]></content><author><name>belinhacbr</name></author><category term="projects" /><category term="hypnotoad" /><category term="python" /><summary type="html"><![CDATA[TO THE HYPNOTOAD]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://media.giphy.com/media/rou0CTAp6Z8VW/giphy.gif" /><media:content medium="image" url="https://media.giphy.com/media/rou0CTAp6Z8VW/giphy.gif" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>