<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Prithvi Rajan</title>
    <description>The latest articles on DEV Community by Prithvi Rajan (@prithvi_rajan_222).</description>
    <link>https://dev.to/prithvi_rajan_222</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3785609%2F47db4c68-0c5a-4001-8f70-2ca9dc8c9116.png</url>
      <title>DEV Community: Prithvi Rajan</title>
      <link>https://dev.to/prithvi_rajan_222</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/prithvi_rajan_222"/>
    <language>en</language>
    <item>
      <title>My RAG Pipeline Took an Hour. Here's How I Got It Down to 30 Seconds.</title>
      <dc:creator>Prithvi Rajan</dc:creator>
      <pubDate>Sun, 01 Mar 2026 19:57:16 +0000</pubDate>
      <link>https://dev.to/prithvi_rajan_222/my-rag-pipeline-took-an-hour-heres-how-i-got-it-down-to-30-seconds-3786</link>
      <guid>https://dev.to/prithvi_rajan_222/my-rag-pipeline-took-an-hour-heres-how-i-got-it-down-to-30-seconds-3786</guid>
      <description>&lt;p&gt;A content ingestion job used to take over an hour. Now it finishes in 30 seconds. No change in hardware, just better utilization of what is already there, a smarter queue system, and hours debugging how CUDA and multiprocessing works. Here’s how I got there.&lt;/p&gt;

&lt;p&gt;I was creating a RAG application with Django, and Milvus as my vector database. I initially created a very simple way to ingest documents. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gnpg9hqrraz1ts7ia6y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gnpg9hqrraz1ts7ia6y.png" alt="Old Pipeline" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a celery task → Fetch the page → Chunk the page → Create vector embeddings → Upload them to Milvus.&lt;/p&gt;

&lt;p&gt;This worked great. Nothing wrong with it other than the fact that it was slow. Ingesting the entire Django docs took over an hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can we do better?
&lt;/h2&gt;

&lt;p&gt;So I run everything on my person computer. I have a CUDA GPU (Nvidia 4070 Super), so I wanted to see if that can speed up the process. I changed the embedding model to use the GPU, tweaked some of the docker images and got the GPU to start creating embeddings on my test code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_embedding_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;force_cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;embedding_model&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;embedding_model&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;
        &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;FlagEmbedding&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BGEM3FlagModel&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_available&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;force_cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cuda:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Loading BGE-M3 on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Correct, fast loading
&lt;/span&gt;        &lt;span class="n"&gt;embedding_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BGEM3FlagModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/models/bge-m3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# local path from Docker image
&lt;/span&gt;            &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Model was loaded with &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;embedding_model&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Looks simple right?&lt;/p&gt;

&lt;p&gt;This change ended breaking my celery setup, and forced me to actually optimize my code.&lt;/p&gt;

&lt;p&gt;CUDA does not support &lt;code&gt;fork()&lt;/code&gt; , so having multiple celery workers was not possible. Each fork tries to re-initialize CUDA context, which throws this error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="nl"&gt;RuntimeError:&lt;/span&gt; &lt;span class="n"&gt;Cannot&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;initialize&lt;/span&gt; &lt;span class="n"&gt;CUDA&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;forked&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;To&lt;/span&gt; &lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="n"&gt;CUDA&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;multiprocessing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt; &lt;span class="n"&gt;must&lt;/span&gt; &lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;spawn&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I changed how celery creates new processes to use the “spawn” function, but that ended up crashing my system. My GPU ran out of VRAM quickly, and shut everything down. &lt;/p&gt;

&lt;p&gt;My PC crashing kinda scared me and made me take a step back. What are the different bottlenecks I am facing? What is the solution to the bottlenecks?&lt;/p&gt;

&lt;p&gt;There were two distinct bottlenecks. The first was fetching pages. With the current pipeline, a worker, after fetching a page has to wait for chunking, embedding, and upload to milvus. The second bottleneck was loading the embedding model onto the GPU takes up a lot of VRAM. &lt;/p&gt;

&lt;p&gt;Luckily, the solution to both bottlenecks were exactly the same thing. Decoupling the IO work from the GPU work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimized architecture
&lt;/h2&gt;

&lt;p&gt;The solution was two Celery queues with very different personalities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F95d6y5bmiq0igpk6t14g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F95d6y5bmiq0igpk6t14g.png" alt="New Pipeline" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first is a general-purpose CPU queue. Multiple workers run freely in parallel, each one fetching a page from the API, chunking the content, and passing it downstream. Concurrency here is a feature; the more workers, the faster the pages come in.&lt;/p&gt;

&lt;p&gt;The second is a GPU queue, locked to a single worker by design. That one worker does nothing but take chunks from the CPU queue, run them through the embedding model, and push the results to a Redis queue. One process, one model loaded in memory, running continuously without interruption.&lt;/p&gt;

&lt;p&gt;The final piece is a Celery Beat job that drains the Redis queue every minute, batching up the accumulated embeddings and writing them to Milvus in bulk rather than one document at a time.&lt;/p&gt;

&lt;p&gt;What makes this architecture satisfying is how cleanly each component maps to a resource. CPU workers are cheap to spin up, so you run many. GPU memory is precious, so you protect it with a single process. Milvus writes are expensive per-call, so you batch them. Every design decision follows directly from the constraint it's solving.&lt;/p&gt;

&lt;p&gt;And because each layer is independent, scaling is straightforward. If fetching becomes the bottleneck, add CPU workers. If embedding becomes the bottleneck, add a GPU container. Neither change requires touching the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final results
&lt;/h2&gt;

&lt;p&gt;An embedding job, which took over an hour before, was done in 30 seconds. It could have been even faster, but I had to rate limit the API calls to GitHub to follow their policy. This is an 120x improvement in performance over the naive strategy.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>rag</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How to make Django asynchronous?</title>
      <dc:creator>Prithvi Rajan</dc:creator>
      <pubDate>Thu, 26 Feb 2026 06:54:10 +0000</pubDate>
      <link>https://dev.to/prithvi_rajan_222/how-to-make-django-asynchronous-7j9</link>
      <guid>https://dev.to/prithvi_rajan_222/how-to-make-django-asynchronous-7j9</guid>
      <description>&lt;p&gt;Django is synchronous by nature. It was not built for an asynchronous system. However, the team is making an effort to bring true asynchronous support to Django. In the meantime, how do we make Django work async?&lt;/p&gt;

&lt;p&gt;Note how I mentioned “true” asynchronous support. Currently, Django does support some form of asynchronous workflows, but the core part of the ORM (connecting to the DB) is still synchronous. &lt;/p&gt;

&lt;h3&gt;
  
  
  Why do we need async workflows in the first place?
&lt;/h3&gt;

&lt;p&gt;If you are creating a RAG chatbot, calling an LLM api in a synchronous workflow will block the worker for an extremely long time. Another option would be to use celery for this, but that just adds unnecessary complications. &lt;/p&gt;

&lt;h2&gt;
  
  
  So how do we make it asynchronous?
&lt;/h2&gt;

&lt;p&gt;A basic checklist would comprise of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Switch to an ASGI server&lt;/li&gt;
&lt;li&gt;Start using the async ORM from Django&lt;/li&gt;
&lt;li&gt;Stop using any sync middleware&lt;/li&gt;
&lt;li&gt;Stop using DRF&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sounds easy right? This will work, but the moment you get any load, it will start showing its cracks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The big problem
&lt;/h2&gt;

&lt;p&gt;In Django, connections are normally reused, but only per thread. So If you have multiple workers running, multiple celery workers and long running LLM calls, then you end up with a ton of open connections to your DB.&lt;/p&gt;

&lt;p&gt;The thing is, DB connections are expensive. In terms of memory. In a postgres server, 1 connection can use 10 Mb of RAM. Which is extremely resource intensive. Normally, a Postgres server limits the number of connections to 20, maybe 50. So under load, you are extremely likely to hit those limits, and your APIs will start to fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do we fix this?
&lt;/h2&gt;

&lt;p&gt;The answer is a connection pooler like PgBouncer. PgBouncer acts as an interface to the DB. Django can make 1000s of connections to PgBouncer, but PgBouncer routes it through the 20 odd connections you have to Postgres.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffksi69ydh46uljvwddf4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffksi69ydh46uljvwddf4.png" alt="Django to PgBouncer to Postgres" width="800" height="365"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;This works because, a connection to PgBouncer is cheap. Each connection is only 2Kb of overhead. This means you can easily have 1000s of connections to PgBouncer.&lt;/p&gt;

&lt;p&gt;With this change your endpoint can now handle a much larger concurrent load.&lt;/p&gt;

&lt;p&gt;While Django might add truly async support, people might still face the exact same issue, as the problem mainly arises when we use run multiple workers, or add celery.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Introducing Slide-CN</title>
      <dc:creator>Prithvi Rajan</dc:creator>
      <pubDate>Mon, 23 Feb 2026 00:14:27 +0000</pubDate>
      <link>https://dev.to/prithvi_rajan_222/introducing-slide-cn-1038</link>
      <guid>https://dev.to/prithvi_rajan_222/introducing-slide-cn-1038</guid>
      <description>&lt;p&gt;Have you ever created a presentation, and tried to change the font size of the header? You need to change it manually for every slide. If you want to change the font color, you need to select all the text in every slide, and change it manually. Then, after making these changes, you realize it looked better before, but going back to the previous version is a nightmare.&lt;/p&gt;

&lt;p&gt;The problem is not with fonts, color, or anything else. I am a developer, and these things are solved problems. I am frustrated with the fact that I am still having to struggle with basic things like version control, and reusability. With code, I have git. I can create one component and re use it everywhere, so why can’t I do the same with slides?&lt;/p&gt;

&lt;p&gt;Slides are just UI. It is not rocket science.&lt;/p&gt;

&lt;p&gt;So why not create slides with code?&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://slide-cn.com" rel="noopener noreferrer"&gt;Slide-CN&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What this unlocks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Version control for presentations
&lt;/h3&gt;

&lt;p&gt;Because I am now using code to create slides, I can also use things like, git and github. You can review your collaborators changes. Your workflow as a whole gets better.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reusability without templates
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://slide-cn.com" rel="noopener noreferrer"&gt;Slide-CN&lt;/a&gt; is component based. You can create small custom components that you can reuse anywhere you want. You can set color schemes. You can standardize how presentations from your company is supposed to look like, without having to create complex templates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real data, not screenshots
&lt;/h3&gt;

&lt;p&gt;Your slide deck is basically a website now. That means, you can call APIs, and dynamically render content. You can pull data from your dashboard in real time. Your presentations become alive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interactive storytelling
&lt;/h3&gt;

&lt;p&gt;Your slides are now made out of web components. Web components that can be interactive. React to clicks, mouse movements, and more. Maximum freedom, zero restriction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Link, not a file
&lt;/h3&gt;

&lt;p&gt;Host your presentation, and use a url to share it with people. That way, you can update your slides, and everyone sees the new version. This means, you dont have to deal with flies like “demo_version_2_final_final” anymore. You can also track how people progress through you slides, and see where people are the most engaged, where people loose interest and so on. Unlimited freedom to do what you want.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open-source
&lt;/h3&gt;

&lt;p&gt;There are a ton of open source component libraries that you can drag and drop into a slide-cn project. A few examples of these are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reactbits&lt;/li&gt;
&lt;li&gt;Shadcn&lt;/li&gt;
&lt;li&gt;MagicUI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing comes close to the kind of ecosystem code has.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not Gamma or Canva?
&lt;/h2&gt;

&lt;p&gt;I will probably write a whole article about this, but the short version is that these tools like Canva and PPT are an abstraction over code. They were built in a time where the thought of coding a website seemed alien to most people.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That is not the case now&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Anyone can pickup cursor and prompt away. People are not shying away from code now. The tools available to vibecode are getting better every month. Even LLMs are natively better at “coding” than at “canva.” &lt;/p&gt;

&lt;p&gt;Gamma represents the opposite end of this spectrum, where an agent generates your entire presentation. You give up all control. You can not change minor details, you are constrained by their system. &lt;a href="https://slide-cn.com" rel="noopener noreferrer"&gt;Slide-CN&lt;/a&gt; gives you complete freedom, along with the speed that comes from vibe-coding.&lt;/p&gt;

</description>
      <category>react</category>
      <category>opensource</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
