<?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: Gideon Adeti</title>
    <description>The latest articles on DEV Community by Gideon Adeti (@gideonadeti).</description>
    <link>https://dev.to/gideonadeti</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1952953%2F76415eab-9fb7-44fb-a8b2-518d901cac1e.png</url>
      <title>DEV Community: Gideon Adeti</title>
      <link>https://dev.to/gideonadeti</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gideonadeti"/>
    <language>en</language>
    <item>
      <title>I Almost Gave Up on QuizRank Because I Thought Render Was Too Small</title>
      <dc:creator>Gideon Adeti</dc:creator>
      <pubDate>Sun, 09 Aug 2026 14:56:39 +0000</pubDate>
      <link>https://dev.to/gideonadeti/i-almost-gave-up-on-quizrank-because-i-thought-render-was-too-small-1jkd</link>
      <guid>https://dev.to/gideonadeti/i-almost-gave-up-on-quizrank-because-i-thought-render-was-too-small-1jkd</guid>
      <description>&lt;p&gt;&lt;a href="https://www.playquizrank.com" rel="noopener noreferrer"&gt;QuizRank&lt;/a&gt; has been one of those projects where I've learned almost as much from things going wrong as I have from actually building features.&lt;/p&gt;

&lt;p&gt;Recently, I went through a whole deployment journey that started with me thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Maybe Render's free tier just isn't enough for this app."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Turns out, I was partly the problem. 😂&lt;/p&gt;

&lt;h2&gt;
  
  
  It started with Render
&lt;/h2&gt;

&lt;p&gt;At some point, I couldn't get the &lt;a href="https://www.playquizrank.com" rel="noopener noreferrer"&gt;QuizRank&lt;/a&gt; backend deployed on Render.&lt;/p&gt;

&lt;p&gt;The build itself worked, but the application would run out of memory while starting. Render's instance has a 512 MB memory limit, and I was seeing OOM errors.&lt;/p&gt;

&lt;p&gt;So naturally, I assumed the backend had simply become too large for Render's free tier.&lt;/p&gt;

&lt;p&gt;I also had another problem: I wasn't even starting the production application correctly. I was using &lt;code&gt;nest start&lt;/code&gt; instead of running the compiled production build.&lt;/p&gt;

&lt;p&gt;There was also a &lt;code&gt;NODE_OPTIONS&lt;/code&gt; configuration that wasn't suitable for both building and running the application. Eventually, I separated the memory requirements: more heap for the build and a much smaller limit for the running application.&lt;/p&gt;

&lt;p&gt;Once we actually investigated what was happening instead of assuming Render was the problem, it became clear:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;QuizRank could run on Render. I was just doing it wrong.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The detour
&lt;/h2&gt;

&lt;p&gt;Before that realization, I had already moved the backend around quite a bit.&lt;/p&gt;

&lt;p&gt;I self-hosted it with Coolify and got it running successfully for more than two months.&lt;/p&gt;

&lt;p&gt;The problem was that my free trial eventually ran out, and I couldn't afford the subscription, so the QuizRank backend was suspended.&lt;/p&gt;

&lt;p&gt;After that, I self-hosted the backend on my own machine for about two weeks.&lt;/p&gt;

&lt;p&gt;Eventually, I gave up on that too.&lt;/p&gt;

&lt;p&gt;Not because it didn't work, but because hardly anyone was using QuizRank at the time, and keeping a backend running on my own machine was consuming resources for very little benefit.&lt;/p&gt;

&lt;p&gt;That's when I started thinking about simplifying the project and stripping out some features so I could get it back onto Render.&lt;/p&gt;

&lt;p&gt;And that's when we discovered the actual problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The leaderboard was the bigger problem
&lt;/h2&gt;

&lt;p&gt;Getting the backend onto Render solved the deployment problem, but then I looked more closely at how the leaderboard worked.&lt;/p&gt;

&lt;p&gt;Every time someone submitted a quiz, the backend was rebuilding the &lt;strong&gt;entire leaderboard&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That meant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading the entire Redis sorted set&lt;/li&gt;
&lt;li&gt;Querying the database for all those users&lt;/li&gt;
&lt;li&gt;Rebuilding the leaderboard array&lt;/li&gt;
&lt;li&gt;Caching the entire thing&lt;/li&gt;
&lt;li&gt;Broadcasting the entire leaderboard through Socket.IO&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this happened for every submission.&lt;/p&gt;

&lt;p&gt;That might be perfectly fine when there are 20 users.&lt;/p&gt;

&lt;p&gt;It becomes a very different story when there are thousands.&lt;/p&gt;

&lt;p&gt;So instead of stripping features from QuizRank, I decided to fix the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the leaderboard scalable
&lt;/h2&gt;

&lt;p&gt;The first change was simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop reading the entire leaderboard.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The UI only displays a limited number of rows at a time, so there's no reason for the backend to constantly materialize thousands of users.&lt;/p&gt;

&lt;p&gt;The leaderboard is now bounded to the top 100.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ZREVRANGE 0 -1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we effectively do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ZREVRANGE 0 99
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database queries are bounded too.&lt;/p&gt;

&lt;p&gt;But there was another problem.&lt;/p&gt;

&lt;p&gt;Even if rebuilding the top 100 is cheap, rebuilding it after &lt;strong&gt;every single submission&lt;/strong&gt; still isn't ideal.&lt;/p&gt;

&lt;p&gt;So I added a small debounce mechanism using Redis and BullMQ.&lt;/p&gt;

&lt;p&gt;Multiple submissions arriving within a short window can now share a single leaderboard rebuild.&lt;/p&gt;

&lt;p&gt;The result is roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100 submissions
      ↓
100 score updates
      ↓
~1 leaderboard rebuild
      ↓
top 100 broadcast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100 submissions
      ↓
100 leaderboard rebuilds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The leaderboard can now be a few seconds behind during bursts, which is a trade-off I'm completely comfortable with.&lt;/p&gt;

&lt;p&gt;It's a live leaderboard, not a stock exchange. 😅&lt;/p&gt;

&lt;h2&gt;
  
  
  But what about users outside the top 100?
&lt;/h2&gt;

&lt;p&gt;That introduced another interesting problem.&lt;/p&gt;

&lt;p&gt;If the API only returns the top 100, what happens to someone ranked 847th?&lt;/p&gt;

&lt;p&gt;They still need to know their own rank.&lt;/p&gt;

&lt;p&gt;So the API now returns the top 100 &lt;strong&gt;plus the authenticated user's own leaderboard position&lt;/strong&gt; when they're outside the top 100.&lt;/p&gt;

&lt;p&gt;That means the shared leaderboard stays bounded while users can still see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Their score&lt;/li&gt;
&lt;li&gt;Their rank&lt;/li&gt;
&lt;li&gt;Their relevant statistics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without forcing the backend to build the entire leaderboard for everyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping Render awake
&lt;/h2&gt;

&lt;p&gt;There was one more issue with using Render's free tier: the service can sleep after inactivity.&lt;/p&gt;

&lt;p&gt;For QuizRank, I don't really want someone visiting the site after a period of inactivity and waiting for the backend to wake up.&lt;/p&gt;

&lt;p&gt;So I added an external uptime monitor using UptimeRobot.&lt;/p&gt;

&lt;p&gt;Instead of having QuizRank constantly ping itself, the monitor periodically hits the backend's lightweight liveness endpoint.&lt;/p&gt;

&lt;p&gt;Simple, cheap, and good enough for this stage of the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  And then Vercel decided to join the story
&lt;/h2&gt;

&lt;p&gt;After finally getting the backend deployment sorted out, I ran into another deployment problem.&lt;/p&gt;

&lt;p&gt;This time it was the frontend.&lt;/p&gt;

&lt;p&gt;Vercel blocked the deployment because the GitHub commit author wasn't recognized as a member of the Vercel team.&lt;/p&gt;

&lt;p&gt;For a while I was confused because I was already logged into the correct Vercel account.&lt;/p&gt;

&lt;p&gt;Eventually I found the actual problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My GitHub account was connected to the wrong Vercel account.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I disconnected the GitHub integration, connected it to the correct account, and everything worked.&lt;/p&gt;

&lt;p&gt;So after all that:&lt;/p&gt;

&lt;p&gt;Render wasn't actually the problem.&lt;/p&gt;

&lt;p&gt;Vercel wasn't actually the problem either.&lt;/p&gt;

&lt;p&gt;Apparently, I was. 😂&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;The biggest lesson from all of this isn't really about Render, Coolify, Redis, BullMQ, or Vercel.&lt;/p&gt;

&lt;p&gt;It's this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't assume your infrastructure is the bottleneck before investigating your application.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When Render failed, I initially thought:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The free tier isn't powerful enough."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But the real issues included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running the wrong production command&lt;/li&gt;
&lt;li&gt;Incorrect memory configuration&lt;/li&gt;
&lt;li&gt;Rebuilding an entire leaderboard on every submission&lt;/li&gt;
&lt;li&gt;Sending unnecessarily large Socket.IO payloads&lt;/li&gt;
&lt;li&gt;Doing unbounded Redis and database work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those required me to remove features.&lt;/p&gt;

&lt;p&gt;They required me to understand what the application was actually doing.&lt;/p&gt;

&lt;p&gt;And that's probably the biggest thing I'm taking away from this phase of building &lt;a href="https://www.playquizrank.com" rel="noopener noreferrer"&gt;QuizRank&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You don't always need more infrastructure.&lt;/p&gt;

&lt;p&gt;Sometimes you need less work.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>backend</category>
      <category>nestjs</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
