<?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[Engineering at oohalo]]></title><description><![CDATA[Love * Users = Product]]></description><link>https://engineering.oohalo.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1696752280979/pqriXqa5u.png</url><title>Engineering at oohalo</title><link>https://engineering.oohalo.com</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 05 Sep 2026 15:08:09 GMT</lastBuildDate><atom:link href="https://engineering.oohalo.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[FastApi sync def vs async def  ?]]></title><description><![CDATA[In case you are using Fastapi, when does one use def and async def and how does using one make a difference over the other?
Assuming that you scanned this and know a bit of the async stuff.
async
@route(“/users/info”)
async def userInfo():
    user =...]]></description><link>https://engineering.oohalo.com/fastapi-sync-def-vs-async-def</link><guid isPermaLink="true">https://engineering.oohalo.com/fastapi-sync-def-vs-async-def</guid><category><![CDATA[Python]]></category><category><![CDATA[FastAPI]]></category><category><![CDATA[synchronous]]></category><category><![CDATA[asynchronous]]></category><dc:creator><![CDATA[K]]></dc:creator><pubDate>Tue, 30 May 2023 20:11:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/BwXsi8tcXlk/upload/f6587cf02ad83c1386fddad64f35073c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In case you are using Fastapi, when does one use def and async def and how does using one make a difference over the other?</p>
<p>Assuming that you scanned this and know a bit of the async stuff.</p>
<p><strong>async</strong></p>
<pre><code class="lang-python"><span class="hljs-meta">@route(“/users/info”)</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">userInfo</span>():</span>
    user = <span class="hljs-keyword">await</span> fechUser() <span class="hljs-comment">#(some http request to get users info)</span>
    <span class="hljs-keyword">return</span> process(user)
</code></pre>
<p>As we used async, fastapi will run the function for each request on the same single event thread.</p>
<p>As there is an await for a response, fastapi will use this time for serving the other requests. And those requests also wait for response from the external service and hence all of them are served concurrently.</p>
<p>So in this case as most of the time, a request to the route waits for a response from some external service and no single request is taking a long time doing something on the single thread they are running, its fine to use async as fastapi can serve other requests which come at the same time while some of them are waiting.</p>
<p>When we use await how fastapi puts the request in a backlog and serves another request while the response for the first request comes back from the external service depends on the framework implementation.</p>
<p><strong>async with blocking code</strong></p>
<pre><code class="lang-python"><span class="hljs-meta">@route(“/heavyTask”)</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">userInfo</span>():</span>
    time.sleep(<span class="hljs-number">10</span>)
</code></pre>
<p>Similar to the above, all the requests use the same event thread for the function execution.But in this case, the code is sleeping for 100 seconds(or consider doing some heavy maths calculation).</p>
<p>Now until the first request completes (10 seconds), the other requests will be waiting and they will be served only when the first request is done.Not only for this route but any request to any other route also gets blocked.Why ? Because the event loop ie. the single thread which executes async functions and also schedules execution of all the requests is blocked on sleep.</p>
<p>Even though you used async, as your code has a blocking operation, all the requests that come after the current served request wait in queue and get blocked.Oops!</p>
<p>So just be sure that any operation down the line is not blocking.</p>
<p><strong>sync</strong></p>
<pre><code class="lang-python"><span class="hljs-meta">@route(“/heavyTask”)</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">userInfo</span>():</span>
    time.sleep(<span class="hljs-number">10</span>)
</code></pre>
<p>Now similar to above, here we have just changed from async to sync and hence the function userInfo for each request is executed in a separate thread pool and hence one of them doesn’t block the other.</p>
<p>Let's consider the below code</p>
<pre><code class="lang-python">
<span class="hljs-keyword">import</span> time
<span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> FastAPI

app = FastAPI()

<span class="hljs-meta">@app.get("/async/sleep")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">async_sleep</span>():</span>
    time.sleep(<span class="hljs-number">100</span>)
    <span class="hljs-keyword">return</span> {<span class="hljs-string">"message"</span>: <span class="hljs-string">"slept for 100 secs"</span>}

<span class="hljs-meta">@app.get("/sync/sleep")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sync_sleep</span>():</span>
    time.sleep(<span class="hljs-number">100</span>)
    <span class="hljs-keyword">return</span> {<span class="hljs-string">"message"</span>: <span class="hljs-string">"slept for 100 secs"</span>}

<span class="hljs-meta">@app.get("/async/hello")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">async_hello</span>():</span>
    <span class="hljs-keyword">return</span> {<span class="hljs-string">"message"</span>: <span class="hljs-string">"hello"</span>}

<span class="hljs-meta">@app.get("/sync/hello")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sync_hello</span>():</span>
    <span class="hljs-keyword">return</span> {<span class="hljs-string">"message"</span>: <span class="hljs-string">"hello"</span>}

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    uvicorn.run(app, host=<span class="hljs-string">"0.0.0.0"</span>, port=<span class="hljs-number">8000</span>)
</code></pre>
<p><strong>Test 1:</strong></p>
<p><em>Requests:</em></p>
<p>1. <a target="_blank" href="http://localhost:8000/sync/sleep">http://localhost:8000/sync/sleep</a></p>
<p>2. <a target="_blank" href="http://localhost:8000/async/hello">http://localhost:8000/async/hello</a></p>
<p>3. <a target="_blank" href="http://localhost:8000/sync/hello">http://localhost:8000/sync/hello</a></p>
<p><em>Access Log:</em></p>
<p>INFO: 172.17.0.1:45992 — “GET /async/hello HTTP/1.1” 200 OK<br />INFO: 172.17.0.1:45992 — “GET /sync/hello HTTP/1.1” 200 OK<br />INFO: 172.17.0.1:45968 — “GET /sync/sleep HTTP/1.1” 200 OK</p>
<p><em>Summary:</em></p>
<p>As you see, we have launched 1, 2, and 3 requests one after the other. The 1st request is for a route which sleeps for 10 seconds.</p>
<p>From the logs, you can see that we get responses for 2 and 3 instantly. We get a response for 1 after 10 seconds. (the logs don’t capture the timestamps and hope you get what I meant when you test it)</p>
<p>As we use def for the sync/sleep route, the request is run on a separate thread pool and other routes are not impacted.</p>
<p><strong>Test 2:</strong></p>
<p><em>Requests:</em></p>
<p>1. <a target="_blank" href="http://localhost:8000/async/sleep">http://localhost:8000/async/sleep</a></p>
<p>2. <a target="_blank" href="http://localhost:8000/async/hello">http://localhost:8000/async/hello</a></p>
<p>3. <a target="_blank" href="http://localhost:8000/sync/hello">http://localhost:8000/sync/hello</a></p>
<p><em>Access Log:</em></p>
<p>INFO: 172.17.0.1:46064 — “GET /async/sleep HTTP/1.1” 200 OK<br />INFO: 172.17.0.1:46066 — “GET /async/hello HTTP/1.1” 200 OK<br />INFO: 172.17.0.1:46116 — “GET /sync/hello HTTP/1.1” 200 OK</p>
<p><em>Summary:</em></p>
<p>The 1st request is for a route which sleeps for 10 seconds but we have used async for it.</p>
<p>From the logs, you can see that only once the 1st request is completed, the 2nd and 3rd are served and processed.</p>
<p>As we used async def for the async/sleep route, the request is run on the same event loop and hence blocks all the other async operations.</p>
<p>Even the 3rd request i.e. <strong>/sync/hello</strong> is also blocked because the event loop which schedules the execution of requests on the thread pool is blocked by the async sleep function!</p>
<p>So haha, there is no one to schedule the 3rd request on the thread pool though it does not run on the event loop thread.</p>
<p>Now, there are still like which I did not yet come to terms with.</p>
<ol>
<li><p>How does sync + async work</p>
</li>
<li><p>What about def mutating the global state, does it create race conditions</p>
</li>
<li><p>What does this mean from <a target="_blank" href="https://fastapi.tiangolo.com/async/#other-utility-functions">here</a>. The initial trigger point is a request itself and from which the route operation gets invoked and which might go down the route with one function calling another <a target="_blank" href="http://etc.Like">etc. Like</a> we don’t explicitly invoke a utility function right?</p>
</li>
</ol>
<blockquote>
<p><em>Any other utility function that you call directly can be created with normal</em> <code>def</code> or <code>async def</code> and FastAPI won't affect the way you call it.</p>
<p><em>This is in contrast to the functions that FastAPI calls for you: path operation functions and dependencies.</em></p>
<p><em>If your utility function is a normal function with</em> <code>def</code>, it will be called directly (as you write it in your code), not in a threadpool, if the function is created with <code>async def</code> then you should <code>await</code> for that function when you call it in your code.</p>
</blockquote>
<p><a target="_blank" href="https://github.com/tiangolo/fastapi/issues/2619#issuecomment-762495981%EF%BF%BChttps://github.com/tiangolo/fastapi/issues/603%EF%BF%BChttps://news.ycombinator.com/item?id=25992078%EF%BF%BChttps://gist.github.com/crackerplace/c853fe41d66045652a4421654098f0f8https://gist.github.com/lukin0110/0074ec5325224674010193bb95f8b835">https://github.com/tiangolo/fastapi/issues/2619#issuecomment-762495981<br />https://github.com/tiangolo/fastapi/issues/603<br />https://news.ycombinator.com/item?id=25992078<br />https://gist.github.com/crackerplace/c853fe41d66045652a4421654098f0f8https://gist.github.com/lukin0110/0074ec5325224674010193bb95f8b835</a></p>
]]></content:encoded></item><item><title><![CDATA[Few notes on load testing]]></title><description><![CDATA[Sometimes it happens that we wanted to load test an application and then in the whole process we forget the basic premise of why we started it. At least I sometimes miss the point.
My Use Case:
I have an instance with a certain configuration(x cores ...]]></description><link>https://engineering.oohalo.com/few-notes-on-load-testing</link><guid isPermaLink="true">https://engineering.oohalo.com/few-notes-on-load-testing</guid><category><![CDATA[Load Testing]]></category><category><![CDATA[cpu]]></category><category><![CDATA[stress test]]></category><dc:creator><![CDATA[K]]></dc:creator><pubDate>Sat, 15 Apr 2023 13:56:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1691243723795/5a4fb3fe-db5d-46ff-80d0-e123283deeb7.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Sometimes it happens that we wanted to load test an application and then in the whole process we forget the basic premise of why we started it. At least I sometimes miss the point.</p>
<p><strong>My Use Case:</strong></p>
<p>I have an instance with a certain configuration(x cores and y gb ram) and I want to test how much load an application deployed on this instance can take.</p>
<p>Application A: A standalone application with no external API <a target="_blank" href="http://interactions.So">interactions. So</a> given a request it should respond. It's an ml app to be specific which does generate certain recommendations.</p>
<p><strong>Expectation:</strong></p>
<p>How much load the application can take?</p>
<p>or</p>
<p>How much load does an application take and serve within a reasonable time?</p>
<p>Here I would refer to load as the request per second(RPS) that the app can handle.</p>
<p>Assume you have a 1 core machine and the app is a CPU-intensive app such as some number crunching.</p>
<p>So if the app takes like say 1 sec to serve the request, the RPS is just 1req/s.</p>
<p>What if you have 2 cores, as the app was in Python and as Python is single-threaded, even if you have 2 cores, it doesn't <a target="_blank" href="http://help.So">help. So</a> you need to spawn 1 more process to utilize the other <a target="_blank" href="http://core.So">core. So</a> now multi-process thing is dependent on which framework you are using.</p>
<p>So now, we are serving 2 req/s.</p>
<p>Now let's say your app takes 2 seconds to serve a request and we have 2 cores.</p>
<p>Now your request rate is 1 req/s as 2 requests take 2 secs to complete in overall.</p>
<p>But we are missing a point here.</p>
<p>What happens if we increase the number of requests assume we are send 8 requests now at once.</p>
<p>So the overall time taken for all the requests to complete is 8 seconds..Some of the requests take more time waiting for CPU time as we only have 2 cores at a certain moment for <a target="_blank" href="http://computation.So">computation. So</a> in the worst case it takes 8 secs for the last two of the 8 requests.</p>
<p>So just increasing the load on the application for testing, will not help much as the system is already saturated and hence even if you increase the load there will not be much improvement in the request rate. And worse the latency starts to increase as requests have to wait for more time to get a chance of CPU time.</p>
<p>That brings up <a target="_blank" href="http://saturation.So">saturation. So</a> load testing is also about trying to understand the saturation limits of the system beyond which it will not scale and deteriorates.</p>
<p>Req/sec and latency are 2 important metrics to keep in mind.Req/sec as an independent metric is not much <a target="_blank" href="http://useful.So">useful. So</a> ok your app can serve x req/s within y time? That is a bit more meaningful.</p>
]]></content:encoded></item><item><title><![CDATA[Postgres replica conflicts: Part 1]]></title><description><![CDATA[Error
We were occasionally seeing the below errors in our app logs when some queries run on the Postgres replica.
“Canceling statement due to conflict with recovery”
Background
There are many reasons why conflicts can occur on a hot standby(replica) ...]]></description><link>https://engineering.oohalo.com/postgres-replica-conflicts-part-1</link><guid isPermaLink="true">https://engineering.oohalo.com/postgres-replica-conflicts-part-1</guid><category><![CDATA[PostgreSQL]]></category><category><![CDATA[replication]]></category><category><![CDATA[conflicts]]></category><dc:creator><![CDATA[K]]></dc:creator><pubDate>Mon, 20 Mar 2023 07:06:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1691219453014/f32e95ac-9e39-4ab1-a094-4b4e1394c704.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Error</strong></p>
<p>We were occasionally seeing the below errors in our app logs when some queries run on the Postgres replica.</p>
<p>“Canceling statement due to conflict with recovery”</p>
<p><strong>Background</strong></p>
<p>There are many reasons why conflicts can occur on a hot standby(replica) as described <a target="_blank" href="https://www.postgresql.org/docs/current/hot-standby.html#HOT-STANDBY-CONFLICT">here</a>.</p>
<p>Also from across the web.</p>
<blockquote>
<p><a target="_blank" href="https://www.cybertec-postgresql.com/en/streaming-replication-conflicts-in-postgresql"><em>A</em></a> <em>replication conflict occurs whenever the recovery process cannot apply WAL information from the primary server to the standby, because the change would disrupt query processing there. These conflicts cannot happen with queries on the primary server, but they happen on the streaming replication standby server because the primary server has limited knowledge about what is going on on the standby.</em></p>
</blockquote>
<p><strong>Which Conflict ?</strong></p>
<p>Now in our case why is the conflict happening ? You check the stats by running the below query on your replica</p>
<pre><code class="lang-plaintext">select *from pg_stat_database_conflicts;

datid | datname | confl_tablespace | confl_lock | confl_snapshot | confl_bufferpin | confl_deadlock
— — — -+ — — — — — -+ — — — — — — — — — + — — — — — — + — — — — — — — — + — — — — — — — — -+ — — — — — — — —
16404 | myservice | 0 | 0 | 3 | 0 | 
016403 | rdsadmin | 0 | 0 | 0 | 0 | 
016401 | postgres | 0 | 0 | 0 | 0 | 
014372 | template0 | 0 | 0 | 0 | 0 | 
016402 | template1 | 0 | 0 | 0 | 0 | 0
</code></pre>
<p>So count 3 indicates the number of occurrences of conflicts of type <strong>confl_snapshot</strong>.<br />As per the <a target="_blank" href="https://www.postgresql.org/docs/9.5/monitoring-stats.html">docs</a>, this means</p>
<blockquote>
<p><em>Number of queries in this database that have been canceled due to old snapshots</em></p>
</blockquote>
<p><strong>Snapshot Conflicts</strong></p>
<p>From <a target="_blank" href="https://www.postgresql.org/docs/current/hot-standby.html#HOT-STANDBY-CONFLICT">docs</a>, the above error occurs when</p>
<blockquote>
<p><em>Application of a vacuum cleanup record from WAL conflicts with standby transactions whose snapshots can still “see” any of the rows to be removed.</em></p>
</blockquote>
<p>If the replica is applying a wal received from the primary and that wal’s application on the replica includes removing dead rows, and if at the same time, a query(or queries) running on the replica has visibility of the same dead rows, then a snapshot conflict occurs.</p>
<p><strong>Analogy</strong></p>
<p>To over-simplify (yeah overly hence might not be accurate), in Postgres when a transaction starts it takes a snapshot (assume s1) and which is nothing but the state of the database when the transaction <a target="_blank" href="http://began.So">began. So</a> assume at s1, the DB had 100 rows. This is on the replica.</p>
<p>So while t1 was still in progress, a transaction t2 on master deleted 5 rows and these changes are streamed in the wal file to the <a target="_blank" href="http://replica.So">replica. So</a> now as t1 is running the replica tries to apply the wal changes but as t1’s snapshot s1 was aware of those 5 rows which are being deleted now, a conflict arises as the replica assumes t1 might be needing those deleted rows.</p>
<p>Note, the transaction t1 in this case doesn’t even need those deleted rows, but that does not matter. Irrespective of the data being needed by the query, the query on the replica is cancelled due to the conflict. It's more about the snapshot time than the actual data being accessed by the query.</p>
<p>Continued in part 2 …</p>
]]></content:encoded></item><item><title><![CDATA[Build the simplest thing that works, Now improve it.]]></title><description><![CDATA[Build the simplest thing that works, Now improve it.]]></description><link>https://engineering.oohalo.com/build-the-simplest-thing-that-works-now-improve-it</link><guid isPermaLink="true">https://engineering.oohalo.com/build-the-simplest-thing-that-works-now-improve-it</guid><category><![CDATA[Startups]]></category><category><![CDATA[Founder]]></category><category><![CDATA[technology]]></category><dc:creator><![CDATA[K]]></dc:creator><pubDate>Tue, 14 Feb 2023 21:43:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1691219921534/8a64aaf4-b4b2-4a7a-aaf7-b10608424d27.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Build the simplest thing that works, Now improve it.</p>
]]></content:encoded></item><item><title><![CDATA[What is the right technology stack to implement a solution ?]]></title><description><![CDATA[One of the biggest challenges in software development is decision quagmire. For example what to choose when we have multiple options.
For a project whose scope is well defined and given the functional and non-functional requirements, we could first c...]]></description><link>https://engineering.oohalo.com/what-is-the-right-technology-stack-to-implement-a-solution</link><guid isPermaLink="true">https://engineering.oohalo.com/what-is-the-right-technology-stack-to-implement-a-solution</guid><category><![CDATA[Founder]]></category><category><![CDATA[Startups]]></category><category><![CDATA[technology]]></category><category><![CDATA[tips]]></category><dc:creator><![CDATA[K]]></dc:creator><pubDate>Fri, 10 Feb 2023 21:36:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1691098467636/20b7528c-b6c4-4e99-89f8-f6a43bebfa9b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of the biggest challenges in software development is decision quagmire. For example what to choose when we have multiple options.</p>
<p>For a project whose scope is well defined and given the functional and non-functional requirements, we could first create the system design and then find many of the answers.</p>
<p>But still, sometimes there could be some aspects which still hamper our productivity.</p>
<ul>
<li><p>variable naming/database table naming</p>
</li>
<li><p>ORM vs SQL</p>
</li>
<li><p>SQL vs NoSQL</p>
</li>
<li><p>java vs nodejs</p>
</li>
<li><p>react vs angular</p>
</li>
</ul>
<p>Below are some ways you could make progress and rapidly</p>
<ul>
<li><p><strong>Comfortable</strong>  </p>
<p>  Choose whatever you or your team is comfortable. For example, choose sql if your team is not comfortable with ORM’s and you are short on enough time.</p>
</li>
<li><p><strong>Previous Experience</strong>  </p>
<p>  You could have had some experience with some aspect of a software component which could push you to use a different one.</p>
</li>
<li><p><strong>Less Number of Moving Parts</strong>  </p>
<p>  When you are starting, the more components you introduce, the greater the complexity.<br />  Try to push your existing systems and reap the maximum benefit.<br />  Of course, postgres is not design to function as a queue, but that doesn’t mean it cannot be used. It could very well work for your scale and problem.  </p>
<p>  You need to build a recommendation system for suggesting products to your <a target="_blank" href="http://customers.You">customers. You</a> could use sophisticated machine learning algorithms using a complex pipeline. But what if you could pull off an initial baseline using some SQL. This helps in having a baseline for evaluating an ML-based recommendation system should you choose to go down that line in the future.</p>
</li>
<li><p><strong>Conventions</strong>  </p>
<p>  Choose a convention good or bad (choose the best practices) and stick to it. After some experience, if you feel a need for improvement, change the conventions. The less number of decision points, the faster you progress  </p>
<ul>
<li><p>folder structure</p>
</li>
<li><p>file naming (underscore/camelcase)</p>
</li>
<li><p>Generic Request validation, Services for Business logic etc.</p>
</li>
</ul>
</li>
<li><p><strong>Experiment</strong>  </p>
<p>  Sometimes you could have a decent time, try using something that you have been wanting to try. Like, use golang for building a cli tool used internally.<br />  This way, you will learn what its like to be on other side of the fence which you would have been wanting to.</p>
</li>
</ul>
<p>Whatever you do, even if sometimes you move backwards, just remember that its improving your workflow and productivity.</p>
<p>Start building !</p>
]]></content:encoded></item><item><title><![CDATA[Can we have a scalable fastapi service with common cache ?]]></title><description><![CDATA[So as it goes, we were using FastAPI for one of the apps. Our app uses a lot of memory(for ml models).
Premise: I wanted to launch multiple workers of the app as Python is single-threaded and also be able to have a common cache across.
We can use Uvi...]]></description><link>https://engineering.oohalo.com/can-we-have-a-scalable-fastapi-service-with-common-cache</link><guid isPermaLink="true">https://engineering.oohalo.com/can-we-have-a-scalable-fastapi-service-with-common-cache</guid><category><![CDATA[FastAPI]]></category><category><![CDATA[cache]]></category><category><![CDATA[workers]]></category><dc:creator><![CDATA[K]]></dc:creator><pubDate>Sun, 11 Dec 2022 20:19:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1691439110204/ff7e07df-3605-407a-8182-270be450c8a4.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So as it goes, we were using <strong><em>FastAPI</em></strong> for one of the apps. Our app uses a lot of memory(for ml models).</p>
<p><strong>Premise:</strong> I wanted to launch multiple workers of the app as Python is single-threaded and also be able to have a common cache across.</p>
<p>We can use Uvicorn for launching multiple workers of <strong><em>FastAPI</em></strong>. But Uvicorn doesn’t support preload option that is we wanted to load the main app only once and still have multiple workers.<br />So I had to look at gunicorn and as gunicorn is a wsgi server, we had to use worker type as uvicorn and launch <strong><em>FastAPI</em></strong>.</p>
<p>We can use preload option of gunicorn so that the app loads only once with multiple workers for handling the load. Check <a target="_blank" href="https://stackoverflow.com/questions/27240278/sharing-memory-in-gunicorn">this</a>.</p>
<p>Okk ! kool.</p>
<p>But I wanted to have a common data structure (cache) across all the workers, so I instead went with the multiple threads option with just one worker.</p>
<p>Oops, but if we use any worker class other than gthread, gunicorn ignores it as in this case, I had to use uvicorn worker for asgi interface between guvicorn and fastapi.</p>
<p>From <a target="_blank" href="https://github.com/benoitc/gunicorn/issues/1045#issuecomment-137539283">here</a></p>
<blockquote>
<p><em>Threads is only meaningful with the threaded worker. Every other worker type ignores that setting and runs one thread per process.</em></p>
</blockquote>
<p>So, I cannot use threads.</p>
<p>Also if you are using an async framework such as <strong><em>FastAPI</em></strong>, using threads is a bit orthogonal.</p>
<p>Ok, can I use multiple workers with the preload option and have a common data structure which is loaded in the app as a module-level variable.</p>
<p>Oops, but as per <a target="_blank" href="https://github.com/benoitc/gunicorn/issues/1045#issuecomment-351817488">this</a> a mutable cache is not possible between workers.</p>
<blockquote>
<p><em>With or without the preload option you will end up with one background thread in each worker because when a process forks it forks all its threads. Whether the threads are created before the fork or after does not matter. In both cases the processes are independent once forked and do not share data structures. If you populate the data at module load time, that initial data will be visible to every worker.</em> <strong><em>Future modifications will not be because they happen in separate processes.</em></strong> <em>To share memory between processes (workers) you need to use a construct for explicitly sharing memory (/dev/shm, filesystem, network cache, db, etc).</em></p>
</blockquote>
<p>You might be able to do the below, but you cannot change the data in itself as a common data structure.</p>
<p>So it's not possible to have a common mutable cache across workers at least in a straightforward way unless you employ other techniques.</p>
<p>Just to end this, let's see how the preload option itself works.I came across the below blog which explains it well.</p>
<p><a target="_blank" href="https://www.joelsleppy.com/blog/gunicorn-application-preloading/">https://www.joelsleppy.com/blog/gunicorn-application-preloading/</a></p>
<p>P.S: References</p>
<p><a target="_blank" href="https://github.com/tiangolo/fastapi/issues/2425￼https://levelup.gitconnected.com/supercharging-pythons-scalability-1eec2f501dd5￼https://stackoverflow.com/questions/38425620/gunicorn-workers-and-threads">https://github.com/tiangolo/fastapi/issues/2425<br />https://levelup.gitconnected.com/supercharging-pythons-scalability-1eec2f501dd5<br />https://stackoverflow.com/questions/38425620/gunicorn-workers-and-threads</a></p>
<p><a target="_blank" href="https://medium.com/tag/gunicorn?source=post_page-----26b8197ceb81---------------gunicorn-----------------">  
</a></p>
]]></content:encoded></item></channel></rss>