{"componentChunkName":"component---src-templates-blog-post-js","path":"/2026-06-14-food-review-app-system-design/","result":{"data":{"markdownRemark":{"id":"01a43ec5-3bef-5da6-8873-d2e9b44e7f68","excerpt":"Hello! I’m Firdaus Al Ghifari (Alghi), a full-stack software engineer in Japan. Recently, I came across a system design problem that got me thinking: how would…","html":"<p>Hello! I’m Firdaus Al Ghifari (Alghi), a full-stack software engineer in Japan. Recently, I came across a system design problem that got me thinking: how would you build a nationwide food review app for Indonesia’s <strong>Makan Bergizi Gratis (MBG)</strong> program, also called <strong>Reviu MBG</strong>, where students and teachers submit daily feedback on their catered meals?</p>\n<p>The feature set is small, but the user base is huge. If tens of millions of people could submit a review at roughly the same time, what would the architecture look like? In this article, I’ll walk through how I’d approach that problem using the 4-step system design framework from Alex Xu’s book, <em>System Design Interview – An Insider’s Guide</em>.</p>\n<h2>The Problem</h2>\n<p>Picture yourself in a system design interview. The interviewer asks you to design a food review app used by Indonesian students and teachers to rate their daily school catering.</p>\n<p>System design questions often start with vague requirements. That can feel overwhelming at first, but the key is to stay calm and ask the right questions before jumping into solutions.</p>\n<h2>Step 1 - Understand the Problem and Establish the Scope</h2>\n<p>Here is an example dialogue for figuring out the requirements:</p>\n<ul>\n<li><strong>Candidate:</strong> What specific features do we need to build?</li>\n<li><strong>Interviewer:</strong> Mainly the review feature. Users submit feedback on their meal.</li>\n<li><strong>Candidate:</strong> What information can a user send?</li>\n<li><strong>Interviewer:</strong> Their name, a rating on a 1–5 scale, a free-text comment, and optionally a photo.</li>\n<li><strong>Candidate:</strong> Is it a mobile app or a web app?</li>\n<li><strong>Interviewer:</strong> Either is fine. Pick whichever is simplest.</li>\n<li><strong>Candidate:</strong> Let’s go with a web app. How many users will leave reviews, and how often?</li>\n<li><strong>Interviewer:</strong> All Indonesian students and teachers will use it, and they can leave reviews daily.</li>\n<li><strong>Candidate:</strong> Is submitting a review mandatory or optional?</li>\n<li><strong>Interviewer:</strong> Completely optional.</li>\n<li><strong>Candidate:</strong> Do users need to log in to post a review?</li>\n<li><strong>Interviewer:</strong> You can decide.</li>\n<li><strong>Candidate:</strong> Let’s skip login to keep friction low. How will users receive the review link?</li>\n<li><strong>Interviewer:</strong> A QR code printed on each food package.</li>\n</ul>\n<p>At this point, the scope is clear: a simple web app for optional daily meal reviews, shared via QR codes on food packages, serving a nationwide user base.</p>\n<h2>Step 2 - Propose a High-Level Design and Get Buy-In</h2>\n<p>This step has two parts: sketch a simple system blueprint, then run back-of-the-envelope calculations to check if the design can handle the load. Because the user base is so large, I prefer doing the capacity estimation first. It helps us pick the right architecture early.</p>\n<h3>Back-of-the-Envelope Calculation</h3>\n<p>Using recent demographic data, let’s round the potential user base to <strong>60 million</strong>. Since reviews are optional, I’ll assume <strong>15% of users submit one review per day</strong>. For a worst case, I’ll also assume most of those submissions happen during the lunch hour of <strong>12:00–13:00 WIB</strong> (Western Indonesian Time).</p>\n<p><strong>Daily review volume:</strong></p>\n<ul>\n<li>60 million users × 15% = <strong>9 million reviews per day</strong></li>\n</ul>\n<p><strong>Peak write load (worst case within the lunch hour):</strong></p>\n<ul>\n<li>9 million reviews ÷ 3,600 seconds = <strong>2,500 requests per second (RPS)</strong></li>\n<li>Applying a 4× spike multiplier: 2,500 × 4 = <strong>10,000 peak RPS</strong></li>\n</ul>\n<p>Each review submission is a write operation, so the system must handle roughly <strong>10,000 writes per second (WPS)</strong> at peak.</p>\n<p>To put this in perspective, Visa processes a global average of about 2,000 transactions per second (TPS), with network capacity designed for peaks above 83,000 TPS. A nationwide lunch rush is surprisingly close to the scale of a global payment network. (Note: WPS can be several times higher than TPS for the same user activity, since a single user action may trigger multiple database writes.)</p>\n<h3>High-Level Design</h3>\n<p>The data model is structured and relational (Catering Vendor → Daily Campaign → Food Review), so a relational database is a good fit. I’d choose <strong>PostgreSQL</strong> for its reliability and solid concurrent-write performance.</p>\n<p>That said, a standalone PostgreSQL instance typically handles up to around <strong>2,000 concurrent writes per second</strong> before it starts to slow down. At 10,000 WPS, writing directly to the database won’t work.</p>\n<p>The solution is to <strong>separate the client write path from the database</strong> using an event-driven architecture:</p>\n<ol>\n<li>The API accepts incoming reviews right away.</li>\n<li>Reviews are pushed to a message broker (e.g., Kafka).</li>\n<li>Background workers pull messages and batch multiple reviews into a single SQL statement.</li>\n</ol>\n<p>This queued, batched approach handles the lunch-hour spike without needing database sharding.</p>\n<p>One more thing to consider: <strong>image uploads</strong>. With an event-driven queue in place, we should not push large binary payloads through the message broker because that would slow things down. Instead, photos should be uploaded directly to object storage (S3) before the review text is submitted.</p>\n<p>Here is the resulting high-level architecture:</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1140px; \"\n    >\n      <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 22.333333333333336%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAxklEQVQY032P6QrCQAyE+/7vJd6giFCwF62tvdztfW3bcRMQ/OXAkhA2800M/GhZFgzDgLquIYTgWpYl4jhGEATwPA/TNOGfjKIokKYZpJRs4Pu+Xn5CasMkSZHnOZus68pAEvVd1zG0aRooNSF/C7ziFIarqY7jIgxDXn5YFsIogm07OBzPIKBSik2/6emfrxNvtju4roe6qnC93bE/XWD0fQ968zwznU4ketO2aHUlI6HT07mmaTKAko7jyHNKS6J5lmX4ANb7MOJNX6LmAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"App architecture diagram\"\n        title=\"App architecture diagram\"\n        src=\"/static/0c178cee366acc7639e1edcd9dd7561d/267c0/app_diagram.png\"\n        srcset=\"/static/0c178cee366acc7639e1edcd9dd7561d/f2d49/app_diagram.png 300w,\n/static/0c178cee366acc7639e1edcd9dd7561d/ff59c/app_diagram.png 600w,\n/static/0c178cee366acc7639e1edcd9dd7561d/267c0/app_diagram.png 1140w\"\n        sizes=\"(max-width: 1140px) 100vw, 1140px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n      />\n    </span></p>\n<h2>Step 3 - Design Deep Dive</h2>\n<p>After presenting the high-level design, an interviewer may ask you to go deeper on specific parts like data models, storage estimates, or implementation details. At this stage, work with the interviewer to focus on what they care about most.</p>\n<p>For this walkthrough, I’ll focus on the <strong>Review</strong> data model and how much storage the photos need.</p>\n<h3>Reviews Table Structure</h3>\n<p>This table stores user reviews for each daily meal package.</p>\n<ul>\n<li><code class=\"language-text\">review_id</code> (UUID, Primary Key), 16 bytes: Client-generated unique ID to prevent duplicate submissions</li>\n<li><code class=\"language-text\">campaign_id</code> (UUID, Foreign Key), 16 bytes: Links the review to a specific daily catering campaign</li>\n<li><code class=\"language-text\">rating</code> (SMALLINT), 2 bytes: Rating on a 1–5 scale</li>\n<li><code class=\"language-text\">comment</code> (TEXT), ~200 bytes: Optional free-text review (average length)</li>\n<li><code class=\"language-text\">photo_url</code> (VARCHAR), ~100 bytes: Optional URL pointing to the image in S3</li>\n<li><code class=\"language-text\">created_at</code> (TIMESTAMP), 8 bytes: When the review was created</li>\n<li><code class=\"language-text\">updated_at</code> (TIMESTAMP), 8 bytes: When the review was last modified</li>\n</ul>\n<p>Each row is roughly <strong>350 bytes</strong>. Rounding up to <strong>500 bytes</strong> per row (including index overhead and page padding):</p>\n<ul>\n<li>Daily growth: 9 million × 500 bytes ≈ <strong>4.5 GB/day</strong></li>\n<li>Yearly growth (240 school days): ≈ <strong>1.1 TB/year</strong></li>\n</ul>\n<h3>S3 Object Storage (Photos)</h3>\n<p>Assuming <strong>20% of reviews include a photo</strong>, and the frontend compresses images to an average of <strong>300 KB</strong> before upload:</p>\n<ul>\n<li>Daily upload volume: 9 million × 20% = <strong>1.8 million photos/day</strong></li>\n<li>Daily S3 volume: 1.8 million × 300 KB ≈ <strong>540 GB/day</strong></li>\n<li>Yearly S3 volume: 540 GB × 240 school days ≈ <strong>130 TB/year</strong></li>\n</ul>\n<p>130 terabytes per year is a good reason to keep photos in <strong>object storage</strong>, not the database. It also shows why we need <strong>lifecycle policies</strong>, like moving photos older than 90 days to cheaper tiers such as S3 Infrequent Access or Glacier.</p>\n<h3>Bonus: Cost Estimation</h3>\n<p>Cost estimation is uncommon in live interviews, but it helps us understand the real cost of our design. Here’s a rough monthly budget for the review system.</p>\n<h4>Storage Layer</h4>\n<ul>\n<li><strong>Database storage:</strong> ~1.1 TB on AWS Aurora ≈ <strong>$110/month</strong> ($0.10/GB)</li>\n<li><strong>Photo storage:</strong> 130 TB in S3 Standard would cost nearly <strong>$3,000/month</strong>, but moving photos older than 90 days to S3 Glacier Instant Retrieval brings the average cost down to roughly <strong>$1,200/month</strong></li>\n</ul>\n<h4>Compute and Message Broker</h4>\n<ul>\n<li><strong>Core database (AWS Aurora PostgreSQL):</strong> Multi-AZ <code class=\"language-text\">db.r6g.large</code> cluster ≈ <strong>$360/month</strong></li>\n<li><strong>Message ingestion (AWS MSK):</strong> 3-broker production cluster ≈ <strong>$600/month</strong></li>\n<li><strong>App processing (AWS ECS on Fargate):</strong> 5-instance baseline, scaling to 40 during the 3-hour lunch window ≈ <strong>$500/month</strong></li>\n</ul>\n<h4>Networking and CDN</h4>\n<p>Students only download the lightweight web app and small menu metadata (~50 KB per session):</p>\n<ul>\n<li>9 million users × 50 KB ≈ <strong>450 GB/day</strong></li>\n<li>~20 school days per month → ≈ <strong>9 TB/month</strong> of data transfer</li>\n<li>AWS CloudFront at ~$0.02/GB ≈ <strong>$180/month</strong></li>\n</ul>\n<h4>Monthly Budget Summary</h4>\n<p>Combining the layers above:</p>\n<ul>\n<li><strong>Core ingestion</strong> (storage, compute, CDN): ~$2,950/month</li>\n<li><strong>Observability and logging</strong> (e.g., Datadog): ~$1,500/month</li>\n<li><strong>Security</strong> (WAF, Secrets Manager, etc.): ~$450/month</li>\n<li><strong>Staging and development environments</strong>: ~$1,000/month</li>\n<li><strong>Total (review feature only)</strong>: ~$6,000/month</li>\n</ul>\n<p>The review feature is the heaviest part of the system. Adding supporting features like admin panels, vendor dashboards, and campaign management might push the total cost roughly <strong>50% higher</strong>, landing around <strong>~$9,000/month</strong> (~Rp 162 million at ~Rp 18,000/USD).</p>\n<h3>A Quick Reality Check</h3>\n<p>We designed for a scenario where up to 60 million users could submit reviews during a nationwide lunch rush. In practice, the real-world setup may be much simpler. If students never access the app directly and only school admins or catering vendors log the feedback, traffic drops sharply, and so does the server bill.</p>\n<h2>Wrapping Up</h2>\n<p>Designing for a nationwide lunchtime spike sounds scary at first, but clear steps make it easier to handle. By estimating capacity early, choosing PostgreSQL for relational data, and using a message queue to handle traffic spikes and batch writes, we can handle ~10,000 WPS without sharding a database.</p>\n<p>The rough monthly cost for this scale comes to about <strong>$9,000 (~Rp 162 million)</strong> for a production-ready nationwide system, but the actual bill depends on who submits reviews and when.</p>\n<p>The next time a system design question feels overwhelming, don’t panic. Ask questions, estimate capacity, propose a simple architecture, and go into the details with your interviewer.</p>\n<p>Thank you for reading this article 😄. If you have any questions or want to discuss system design, feel free to reach out via <a href=\"https://www.linkedin.com/in/alghi/\">LinkedIn</a> or my other social media platforms.</p>\n<p>Happy coding!</p>","fields":{"slug":"/2026-06-14-food-review-app-system-design/"},"frontmatter":{"title":"System Design Case Study: Scaling the Nationwide Food Review App (Reviu MBG)","date":"June, 14 2026","description":"Learn how to use a practical 4-step system design framework to architect an event-driven, cost-effective food review platform capable of handling a massive 10,000 writes-per-second lunchtime rush.","category":"system-design","author":"Firdaus Al Ghifari","topimage":{"childImageSharp":{"fluid":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACqklEQVQoz02RWXMaRxRG+ZP5I/kXSR5kP8SJIhtbqkpEpFhSWVgpRcIYjDAgS2IdAWIZGGaBWREzrINPWrgqlYevbndV97mnb0fCuUM4t1nNbNYLB39ioNkKqj1Adw0US6Xv6KjjEYpr0vMsemOHtm1iBD5eGOIuQ5zFapPIBraBfgMHgUm+fkeqXCBRzJOsfCF+fUWydsvfxQJnxWsyTYmLShE9CPBW6/9gzlIAn6wWvsHsUWMdjlmvA0bCSNb7dJ5i9JEGXdqWwb0+oGFotM0RPdfBXT3ZrRiv/gf8unTxJi6qrtJoSCgDhWU453EqLgQOiinjuF1cT2GyeMReLrBEBv6UWbjCmi7pulPCr0+my29AXXSUBxq1aoVW6wF/5jOw+pQ7NSqNLJnMPqXMS+RWGkmc7XouNdPFEbVSu+fzTQnXtje2mxmGAhqGj8JsgmH2sCYjvMVE2Ey5b99xvvM97374jnTmkJbvo8+njBYz/NWSjtyh1W3j+QHj9ZrIam6hNCtU8lm6DzUsdyieXqE77JMr3tIedskko+QudxgYdUq9Hg1dQ51NaeoGqudRqEhkb0vok4BIX+nwYi/G2+0El7/GRd1n69kLviRznPwYpdquIjcSBOaNmNGIeCxO6n0KSXzOq+ev+JD4RGz7d46juxTOz4gMxS/+tH3Iu733/HGQ4PQsze7uEcWixOvnf6JNNHpqlb5ew57pnJymOTm85MEc8ublMZ+vy2xt7fM6GuPi7TERW6nyJnrA5cFfxGKnXH1IUv6UoVHN88vPvzE268j9W1ryDe5I4iotmh6dMGoXKX28QC7nSJzG2dk74vyfj0S8fpFOMYvVvsFu5lFLKQwpI3JFp5BgolWZGvciEoGI3rhmVM+i1QsYzWs0sVfFWr5LoUk5/gVzjA24Nud46QAAAABJRU5ErkJggg==","aspectRatio":1.829268292682927,"src":"/static/fdda79fcaec3515a1e63c4f629114311/73f08/cover_image.png","srcSet":"/static/fdda79fcaec3515a1e63c4f629114311/57fc3/cover_image.png 300w,\n/static/fdda79fcaec3515a1e63c4f629114311/f8eb5/cover_image.png 600w,\n/static/fdda79fcaec3515a1e63c4f629114311/73f08/cover_image.png 1200w,\n/static/fdda79fcaec3515a1e63c4f629114311/29036/cover_image.png 1408w","sizes":"(max-width: 1200px) 100vw, 1200px"}}},"topimagedesc":"Image generated by Gemini"}}},"pageContext":{"slug":"/2026-06-14-food-review-app-system-design/","previous":{"fields":{"slug":"/2025-07-31-handling-japanese-numeric-ux/"},"frontmatter":{"title":"Handling Japanese Numeric UX in React with React Hook Form & Zod","category":"frontend"}},"next":null}},"staticQueryHashes":["2742190585","642825376"]}