<?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: Anshu Kumar</title>
    <description>The latest articles on DEV Community by Anshu Kumar (@101xanshu).</description>
    <link>https://dev.to/101xanshu</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%2F3816810%2Fbd13dcc5-7862-4ac2-9a14-61f28f11e2af.jpg</url>
      <title>DEV Community: Anshu Kumar</title>
      <link>https://dev.to/101xanshu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/101xanshu"/>
    <language>en</language>
    <item>
      <title>System Design 01 : Before You Draw a Single Box in System Design</title>
      <dc:creator>Anshu Kumar</dc:creator>
      <pubDate>Wed, 28 Oct 2026 12:57:39 +0000</pubDate>
      <link>https://dev.to/101xanshu/system-design-01-before-you-draw-a-single-box-in-system-design-4gb0</link>
      <guid>https://dev.to/101xanshu/system-design-01-before-you-draw-a-single-box-in-system-design-4gb0</guid>
      <description>&lt;p&gt;Imagine you're in a system design interview, and a senior engineer asks you to design a notification system that can deliver millions of notifications.&lt;/p&gt;

&lt;p&gt;You open Excalidraw and start drawing. An API server, Kafka, Redis, a bunch of workers, and a database. You've seen similar architectures before, so you know roughly where everything goes.&lt;/p&gt;

&lt;p&gt;Then the senior engineer asks:&lt;/p&gt;

&lt;p&gt;"How many of these notifications need to be delivered immediately, and how many can wait?"&lt;/p&gt;

&lt;p&gt;You pause.&lt;/p&gt;

&lt;p&gt;Because the answer could change how you design the entire system.&lt;/p&gt;

&lt;p&gt;And that's just one of the questions you haven't asked yet.&lt;/p&gt;

&lt;p&gt;The hardest part of system design isn't drawing the architecture. It's figuring out what architecture the problem actually needs.&lt;/p&gt;

&lt;p&gt;So, before we draw another box, let's start with the questions that should come first.&lt;/p&gt;

&lt;h2&gt;
  
  
  One notification system, several different problems
&lt;/h2&gt;

&lt;p&gt;Let's start with something familiar.&lt;/p&gt;

&lt;p&gt;Imagine you're building a social application. Someone likes a post, another user sends a message, and someone else logs in from a new device.&lt;/p&gt;

&lt;p&gt;The application wants to notify the relevant users about these events.&lt;/p&gt;

&lt;p&gt;At first, this sounds like a straightforward feature: receive an event, find the recipient, and send a notification.&lt;/p&gt;

&lt;p&gt;But consider what happens if we treat all three events the same way.&lt;/p&gt;

&lt;p&gt;A message notification might need to arrive within seconds. A like notification can probably tolerate a short delay. A security alert about an unfamiliar login might need to be delivered as quickly as possible.&lt;/p&gt;

&lt;p&gt;And a weekly email digest? That can be processed in batches whenever the system has capacity.&lt;/p&gt;

&lt;p&gt;We have one product feature, but several different expectations.&lt;/p&gt;

&lt;p&gt;This is where the design starts.&lt;/p&gt;

&lt;p&gt;Not with a database choice. Not with Kafka. Not with a diagram copied from another system.&lt;/p&gt;

&lt;p&gt;With the behavior the system is expected to provide.&lt;/p&gt;

&lt;p&gt;Let's say we ask the product team about these use cases and establish the following initial requirements:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwiq3h7kh6t130jwyz4af.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwiq3h7kh6t130jwyz4af.png" alt=" " width="679" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are illustrative assumptions, not universal requirements. A real system might have different latency targets, channels, or priorities.&lt;/p&gt;

&lt;p&gt;But writing them down gives us something useful: a description of the problem that we can challenge and refine.&lt;/p&gt;

&lt;p&gt;For example, what does "near real-time" actually mean? Two seconds? Five seconds? And does the target refer to accepting a notification, beginning delivery, or the recipient actually receiving it?&lt;/p&gt;

&lt;p&gt;Those distinctions will matter later.&lt;/p&gt;

&lt;p&gt;For now, we know that our system must support different notification types, multiple channels, and different latency expectations.&lt;/p&gt;

&lt;p&gt;That's already more useful than simply saying "millions of notifications."&lt;/p&gt;

&lt;h2&gt;
  
  
  What does "millions" actually tell us?
&lt;/h2&gt;

&lt;p&gt;Let's say the requirement is ten million notifications per day.&lt;/p&gt;

&lt;p&gt;Is that enough to decide how many servers we need?&lt;/p&gt;

&lt;p&gt;Not yet. But we can start with a rough estimate.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7bxnugsqee9w38bmcjil.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7bxnugsqee9w38bmcjil.png" alt=" " width="680" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our average arrival rate is approximately 116 notifications per second.&lt;/p&gt;

&lt;p&gt;That calculation is useful, but it doesn't tell us how the traffic is distributed.&lt;/p&gt;

&lt;p&gt;Suppose most of those notifications arrive during a two-hour window. Or a scheduled campaign suddenly creates a large burst of work. Or an external provider goes down, and the system needs to process a backlog after it recovers.&lt;/p&gt;

&lt;p&gt;In each case, the system might need to handle much more work at particular moments than the daily average suggests.&lt;/p&gt;

&lt;p&gt;Let's record what we know and what we don't:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fos99t38b7id7lugpmn6x.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fos99t38b7id7lugpmn6x.png" alt=" " width="660" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that we haven't invented a peak number just to make the design look complete.&lt;/p&gt;

&lt;p&gt;We have an estimate, an assumption about delivery behavior, and an unanswered question about peak traffic.&lt;/p&gt;

&lt;p&gt;We can ask for more information if it's available. Otherwise, we can state an assumption, proceed with the design, and revisit it if the assumption turns out to matter.&lt;/p&gt;

&lt;p&gt;This is an important habit: distinguish facts from assumptions instead of silently treating both as requirements.&lt;/p&gt;

&lt;p&gt;Now let's see what happens when we try to build the simplest possible system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version 1: Just send the notification
&lt;/h2&gt;

&lt;p&gt;Our first design could be very simple.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkme1ociemapiuvssl56l.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkme1ociemapiuvssl56l.png" alt=" " width="679" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The application calls our Notification API. The API validates the request and calls the appropriate external provider.&lt;/p&gt;

&lt;p&gt;An external provider is simply another service that performs the actual delivery. For email, that might be an email delivery service; for push notifications, it might be a platform's push notification service.&lt;/p&gt;

&lt;p&gt;Our application doesn't need to implement the entire delivery infrastructure itself.&lt;/p&gt;

&lt;p&gt;For a small application with modest traffic, this design might be perfectly reasonable.&lt;/p&gt;

&lt;p&gt;But let's introduce a constraint.&lt;/p&gt;

&lt;p&gt;Suppose the email provider takes several seconds to respond. Our Notification API is waiting for that response, which means the application calling our API may also have to wait.&lt;/p&gt;

&lt;p&gt;Now imagine the provider becomes temporarily unavailable.&lt;/p&gt;

&lt;p&gt;Requests can pile up, connections remain occupied, and a problem with an external dependency can affect the application itself.&lt;/p&gt;

&lt;p&gt;The issue isn't that direct delivery is always wrong. It's that direct delivery couples the application's request to the provider's response time and availability.&lt;/p&gt;

&lt;p&gt;Can we separate those two things?&lt;/p&gt;

&lt;h2&gt;
  
  
  Version 2: Accept the work now, deliver it later
&lt;/h2&gt;

&lt;p&gt;Imagine going to a restaurant during a busy evening.&lt;/p&gt;

&lt;p&gt;The staff takes your order, records it, and gives it to the kitchen. You don't need to stand in the kitchen and wait for the food to be prepared before the restaurant acknowledges your order.&lt;/p&gt;

&lt;p&gt;The order has been accepted. Preparation happens separately.&lt;/p&gt;

&lt;p&gt;A notification system can use a similar idea.&lt;/p&gt;

&lt;p&gt;Instead of sending the notification directly from the API, we can place the work in a queue.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;queue&lt;/strong&gt; is a structure that holds work until a consumer is ready to process it. A worker can take a queued notification and attempt delivery independently of the original API request.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;durable queue&lt;/strong&gt; goes a step further: it retains accepted messages in persistent storage so that a process restart or machine failure doesn't simply erase pending work. The exact guarantees depend on the queue technology and its configuration.&lt;/p&gt;

&lt;p&gt;Our architecture now looks like this:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxtb9tqmx90xy9aow3g6o.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxtb9tqmx90xy9aow3g6o.png" alt=" " width="680" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Notification API validates the request and places a message in the queue. A delivery worker reads the message and contacts the provider.&lt;/p&gt;

&lt;p&gt;The API no longer needs to wait for the provider to finish sending the notification.&lt;/p&gt;

&lt;p&gt;We've separated two operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Accepting responsibility for processing the notification.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Attempting to deliver it to the recipient.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is asynchronous processing. The system accepts work now and completes it separately, rather than making the caller wait for the entire operation.&lt;/p&gt;

&lt;p&gt;But this introduces an important question: what exactly does the API promise when it returns success?&lt;/p&gt;

&lt;p&gt;If the API returns success after the notification has been durably accepted, it can tell the caller that the system has accepted the work.&lt;/p&gt;

&lt;p&gt;It cannot honestly claim that the recipient has received the notification.&lt;/p&gt;

&lt;p&gt;Those are different guarantees.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx6b5dkrwcnk8jq4gt4sa.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx6b5dkrwcnk8jq4gt4sa.png" alt=" " width="679" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even this simplified flow forces us to define what each state means.&lt;/p&gt;

&lt;p&gt;For example, a provider accepting an email doesn't necessarily mean the email reached the recipient's inbox. If the product needs that distinction, our state model must represent it.&lt;/p&gt;

&lt;p&gt;And notice how we arrived at the queue. We didn't add it because a large system is supposed to have one. We added it because we wanted to isolate API request handling from slow or unavailable delivery providers.&lt;/p&gt;

&lt;p&gt;That's a design decision we can explain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version 3: A queue doesn't magically solve every problem
&lt;/h2&gt;

&lt;p&gt;Our queue protects the API from waiting for the provider. It can also hold pending work when delivery slows down.&lt;/p&gt;

&lt;p&gt;But what happens when the system receives notifications faster than its workers can process them?&lt;/p&gt;

&lt;p&gt;The queue grows.&lt;/p&gt;

&lt;p&gt;A queue is useful for absorbing bursts, but it doesn't create unlimited processing capacity. If incoming work continuously exceeds processing capacity, the backlog will keep increasing.&lt;/p&gt;

&lt;p&gt;We need to understand both sides of the system.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd06wm1yp6auedm8avsyr.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd06wm1yp6auedm8avsyr.png" alt=" " width="680" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the arrival rate temporarily exceeds the processing rate, the queue can absorb the difference.&lt;/p&gt;

&lt;p&gt;If the arrival rate stays higher for long enough, the system eventually needs more capacity, less incoming work, or a different processing strategy.&lt;/p&gt;

&lt;p&gt;We should monitor the queue's size and age, not just whether the API is returning successful responses.&lt;/p&gt;

&lt;p&gt;Why the age of queued messages?&lt;/p&gt;

&lt;p&gt;Because a queue containing a million messages isn't necessarily unhealthy if they are allowed to wait for hours. A much smaller queue might be a serious problem if security alerts are sitting in it for thirty seconds.&lt;/p&gt;

&lt;p&gt;The important measurement depends on the requirement.&lt;/p&gt;

&lt;p&gt;For our system, useful measurements might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;How many notifications are waiting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How long the oldest notification has been waiting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How long delivery attempts take.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How many attempts fail.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Whether high-priority notifications meet their latency targets.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where requirements become operational measurements.&lt;/p&gt;

&lt;p&gt;"Security alerts should arrive quickly" isn't enough on its own. We need a measurable target to determine whether the system is meeting that expectation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version 4: What if an important notification is stuck behind millions of emails?
&lt;/h2&gt;

&lt;p&gt;Let's return to the notification types we identified earlier.&lt;/p&gt;

&lt;p&gt;Imagine a marketing campaign schedules ten million emails. At the same time, a user triggers a security alert.&lt;/p&gt;

&lt;p&gt;If both kinds of work share the same queue and processing capacity, the security alert could wait behind bulk traffic.&lt;/p&gt;

&lt;p&gt;The queue is working exactly as designed. The problem is that the design doesn't necessarily protect our high-priority workload.&lt;/p&gt;

&lt;p&gt;We now have a few options.&lt;/p&gt;

&lt;p&gt;We could let the workers prioritize messages within a shared queue. We could create separate queues for different classes of work. Or we could reserve some worker capacity for high-priority notifications.&lt;/p&gt;

&lt;p&gt;Each option has trade-offs.&lt;/p&gt;

&lt;p&gt;A shared queue can be simpler to operate, but priority scheduling depends on the capabilities of the queue and consumer design.&lt;/p&gt;

&lt;p&gt;Separate queues offer more isolation, but require additional configuration and operational attention. They also don't automatically guarantee fast delivery: if the high-priority workers are overloaded or the provider is slow, latency can still increase.&lt;/p&gt;

&lt;p&gt;Suppose we decide that the isolation is worth the added complexity.&lt;/p&gt;

&lt;p&gt;Our design could evolve into this:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ihlnxd0wksbstd7hps0.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ihlnxd0wksbstd7hps0.png" alt=" " width="680" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is one possible design, not a rule that every notification system needs two queues.&lt;/p&gt;

&lt;p&gt;The decision depends on the latency requirements, workload, queue technology, and cost of operating separate processing paths.&lt;/p&gt;

&lt;p&gt;More importantly, we've now seen the design change for a reason: our original requirements told us that not all notifications could wait equally long.&lt;/p&gt;

&lt;p&gt;That is how architecture should evolve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now let's talk about failure
&lt;/h2&gt;

&lt;p&gt;Suppose a worker sends a notification to an external provider.&lt;/p&gt;

&lt;p&gt;The provider receives the request, but its response never reaches the worker. The worker times out.&lt;/p&gt;

&lt;p&gt;Did the provider send the notification?&lt;/p&gt;

&lt;p&gt;We don't know.&lt;/p&gt;

&lt;p&gt;The worker has an ambiguous outcome: the request may have failed before delivery, or the provider may have accepted it even though the response was lost.&lt;/p&gt;

&lt;p&gt;If the worker retries, the recipient might receive the same notification twice. If the worker doesn't retry, a notification that was never accepted might never be sent.&lt;/p&gt;

&lt;p&gt;This is why "just retry on failure" isn't a complete reliability strategy.&lt;/p&gt;

&lt;p&gt;At a high level, we need to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Which failures are temporary and worth retrying.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How many attempts are allowed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How long to wait between attempts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What happens when all attempts fail.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How we track delivery attempts and outcomes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Whether the provider supports idempotency mechanisms that can reduce duplicate effects.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A retry policy defines when and how failed work is attempted again. Some systems also move repeatedly failing messages into a dead-letter queue, a separate place for messages that need further investigation or special handling.&lt;/p&gt;

&lt;p&gt;We don't need to implement all of that in this first design exercise. The point is to recognize that a new requirement, recovering from temporary failures introduces new responsibilities.&lt;/p&gt;

&lt;p&gt;And even a durable queue doesn't solve all of them. It can preserve pending work, but it cannot guarantee that an external provider delivers a notification exactly once.&lt;/p&gt;

&lt;p&gt;We'll explore retries, idempotency, and reliable state changes in more depth later in the series.&lt;/p&gt;

&lt;p&gt;For now, let's capture the design decisions we've made and the reasons behind them.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi9ayll1hcg7nbba19gyb.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi9ayll1hcg7nbba19gyb.png" alt=" " width="680" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is much more useful than a list of technologies.&lt;/p&gt;

&lt;p&gt;It tells us what we chose, why we chose it, and what information could still change the design.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, do we need Kafka and Redis?
&lt;/h2&gt;

&lt;p&gt;We haven't actually established that yet.&lt;/p&gt;

&lt;p&gt;We've established that we need a way to accept notification requests, process them asynchronously, and preserve pending work according to our reliability requirements.&lt;/p&gt;

&lt;p&gt;A durable queue is one possible component that provides those capabilities.&lt;/p&gt;

&lt;p&gt;Kafka is a distributed event-streaming platform that can retain records and allow consumers to process them independently. It may be a good fit when we need high throughput, replay, or multiple independent consumers, but it also brings operational and design considerations.&lt;/p&gt;

&lt;p&gt;A managed queue service might satisfy our requirements with less operational effort.&lt;/p&gt;

&lt;p&gt;Redis can be useful for caching, rate limiting, and certain queue-like workloads, depending on the data structures and persistence guarantees involved. But we shouldn't automatically assume that a Redis-backed queue provides the same durability and delivery guarantees as every dedicated queue or event-streaming system.&lt;/p&gt;

&lt;p&gt;We would compare the actual guarantees, workload, operational complexity, and cost before choosing.&lt;/p&gt;

&lt;p&gt;The same applies to our database. We might need to persist notification state, user preferences, and delivery attempts. But the appropriate data model depends on which queries we need to support, how much data we retain, and what consistency guarantees the product requires.&lt;/p&gt;

&lt;p&gt;The technology should follow from those decisions.&lt;/p&gt;

&lt;p&gt;If we choose Kafka simply because the prompt says "millions," we've skipped the most important part of the exercise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture is still a hypothesis
&lt;/h2&gt;

&lt;p&gt;Let's step back and look at what we have:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2hfcbifottcwo032didk.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2hfcbifottcwo032didk.png" alt=" " width="679" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This isn't a complete production architecture. It doesn't yet specify the schema, exact API contract, worker concurrency, retention policy, provider failover strategy, or every failure-handling detail.&lt;/p&gt;

&lt;p&gt;And that's okay.&lt;/p&gt;

&lt;p&gt;We now have a baseline design that follows from the requirements we've discussed. We can identify the remaining questions and investigate the ones that could materially change our choices.&lt;/p&gt;

&lt;p&gt;For example, if users must query the status of every notification, we'll need to define how delivery state is stored and retrieved.&lt;/p&gt;

&lt;p&gt;If the system must meet strict latency targets, we'll need to calculate peak capacity and examine how queueing affects end-to-end latency.&lt;/p&gt;

&lt;p&gt;If notifications must respect changing user preferences, we'll need to decide when those preferences are checked and what happens to work already waiting in a queue.&lt;/p&gt;

&lt;p&gt;Each new constraint can lead to another revision.&lt;/p&gt;

&lt;p&gt;The goal isn't to draw the most complicated architecture possible. It's to reach a design that satisfies the known requirements, then understand where it might fail as those requirements change.&lt;/p&gt;

&lt;h2&gt;
  
  
  The habit worth building
&lt;/h2&gt;

&lt;p&gt;When you face an unfamiliar system, don't start by asking which database, queue, or cache you should use.&lt;/p&gt;

&lt;p&gt;Start by understanding what the system must do.&lt;/p&gt;

&lt;p&gt;Find out who uses it, what operations it supports, what the important latency and reliability expectations are, and how much work it needs to handle.&lt;/p&gt;

&lt;p&gt;Write down the requirements and assumptions so you can distinguish what is known from what is merely plausible.&lt;/p&gt;

&lt;p&gt;Then build the simplest design that satisfies them.&lt;/p&gt;

&lt;p&gt;When a constraint exposes a limitation, evolve the design. Explain why the change is necessary, what it improves, and what new complexity it introduces.&lt;/p&gt;

&lt;p&gt;You don't need to answer every possible question before drawing anything. You do need to know which unanswered questions could invalidate your current design.&lt;/p&gt;

&lt;p&gt;And don't treat a technology name as an explanation. Saying "we'll use Kafka" doesn't explain how the system handles a provider outage, how long work is retained, or what the API promises when it returns success.&lt;/p&gt;

&lt;p&gt;Those are the decisions that give an architecture its meaning.&lt;/p&gt;

&lt;p&gt;A good system design is not a collection of familiar boxes. It's a set of decisions that you can trace back to requirements, constraints, and explicit trade-offs.&lt;/p&gt;

&lt;p&gt;Once you learn to think that way, the diagram becomes a way to communicate your reasoning not a substitute for it.&lt;/p&gt;

&lt;p&gt;In the next article, we'll explore what happens after a system accepts a request: how to design APIs, model data, and represent state transitions so that the system's behavior remains predictable as it grows.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>career</category>
      <category>interview</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>I built a company brain in 3 days. Notes on memory, evidence, and what broke.</title>
      <dc:creator>Anshu Kumar</dc:creator>
      <pubDate>Sun, 18 Oct 2026 17:05:23 +0000</pubDate>
      <link>https://dev.to/101xanshu/i-built-a-company-brain-in-3-days-notes-on-memory-evidence-and-what-broke-6p</link>
      <guid>https://dev.to/101xanshu/i-built-a-company-brain-in-3-days-notes-on-memory-evidence-and-what-broke-6p</guid>
      <description>&lt;h2&gt;
  
  
  Why am I even thinking about company memory?
&lt;/h2&gt;

&lt;p&gt;Whenever we talk about AI brain for companies, the first thing that usually comes to mind is a chatbot over some documents. You upload your docs, you ask questions, you get answers.&lt;/p&gt;

&lt;p&gt;But I think the actual problem is more interesting than that.&lt;/p&gt;

&lt;p&gt;Imagine I am an engineer at a company and I ask:&lt;br&gt;
"Why did INC-231 happen?" (INC-231 is just an incident ID, don't worry about it)&lt;/p&gt;

&lt;p&gt;What exactly should happen?&lt;/p&gt;

&lt;p&gt;The system needs to understand that I am not asking for a document. I am asking for a causal chain (causal as in cause-and-effect, not a typo for casual). It needs to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the incident in the company's history.&lt;/li&gt;
&lt;li&gt;Trace what caused it: which PR, which decision, which commit.&lt;/li&gt;
&lt;li&gt;Show me the path, not just the answer.&lt;/li&gt;
&lt;li&gt;Cite every source along the way.&lt;/li&gt;
&lt;li&gt;Tell me who was involved and who knows the most about it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And now take this one step further.&lt;/p&gt;

&lt;p&gt;What if I then ask:&lt;br&gt;
"What breaks if we migrate the ledger database?"&lt;/p&gt;

&lt;p&gt;The system needs to follow service dependencies outward, rank the impact, and then, before doing anything about it, stop and ask a human for permission.&lt;/p&gt;

&lt;p&gt;That's where the idea of Recall comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, let's understand the problem
&lt;/h2&gt;

&lt;p&gt;Company knowledge doesn't live in one place. It lives in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Incidents (what broke)&lt;/li&gt;
&lt;li&gt;PRs and commits (what changed)&lt;/li&gt;
&lt;li&gt;ADRs (why decisions were made)&lt;/li&gt;
&lt;li&gt;Chat messages (what the team actually said)&lt;/li&gt;
&lt;li&gt;People (who knows what)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A normal RAG system chunks all of this into vectors and hopes semantic similarity finds the right pieces. But the questions I care about aren't similarity questions. They're connection questions:&lt;/p&gt;

&lt;p&gt;"Why did this happen?" is a path question.&lt;br&gt;
"What breaks if I touch this?" is a traversal question.&lt;br&gt;
"Who knows this system?" is a ranking question.&lt;/p&gt;

&lt;p&gt;These are graph questions. So I built a graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  The graph model
&lt;/h2&gt;

&lt;p&gt;The core idea is simple. Everything a company knows becomes nodes and edges:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8lbyc3er33xq21a732vs.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8lbyc3er33xq21a732vs.png" alt="Graph schema: incidents, PRs, ADRs, services, people, and teams linked by labeled relationships." width="800" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;People, teams, services, incidents, PRs, commits, decisions, chat messages. All connected. All queryable with Cypher.&lt;/p&gt;

&lt;p&gt;But here's the thing I want to be clear about. The demo company, Meridian Pay, is fictional. Every person, incident, and message is seeded test data. What's real is the FalkorDB instance, the queries, and the agent behavior. I say this upfront because the whole point of the project is honesty about evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "shows its work" actually means
&lt;/h2&gt;

&lt;p&gt;When you ask "why did INC-231 happen?", the agent doesn't just generate an answer. It runs a causal traversal:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvigjaqvdtrqdtrgz7it3.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvigjaqvdtrqdtrgz7it3.png" alt="Causal query flow: question to scope resolution to path traversal to cited answer." width="800" height="687"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every claim in the answer links back to a node. If the model can't cite it, it doesn't say it. That's the rule.&lt;/p&gt;

&lt;p&gt;I think this matters more than it sounds. Most AI answers are confident prose with no provenance. You can't check them. You can't trace them. You just have to trust the model. Recall inverts that: the answer is only as good as its citations, and the citations are right there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory has scope
&lt;/h2&gt;

&lt;p&gt;This is something I thought hard about. In a company, memory isn't global.&lt;/p&gt;

&lt;p&gt;If Priya tells the system "the deploy window is 2-4 PM," that's Priya's memory. Anshu shouldn't see it as his own. So each person gets their own isolated memory graph, running as a separate graph in the same FalkorDB instance.&lt;/p&gt;

&lt;p&gt;And memories have a lifecycle. When Priya later says "actually, make it 2-4 AM," the old fact isn't deleted. It's superseded:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fslrp4s7qsax2g9dcheiy.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fslrp4s7qsax2g9dcheiy.png" alt="Memory lifecycle: old fact marked superseded, replaced by the new active fact." width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The past is versioned, never rewritten. And if someone wants a fact gone entirely, there's a forget operation that issues an erasure receipt. You can prove what was forgotten and when.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent acts, but only with permission
&lt;/h2&gt;

&lt;p&gt;Here's the flow:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frltnt0gul6f8piij4z9x.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frltnt0gul6f8piij4z9x.png" alt="Approval flow: retrieve, draft, pause for human approval, execute, audit trail into the graph." width="800" height="731"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pause is the feature. The agent proposes a database migration, shows you the blast radius, and then stops. Nothing happens until you click approve. And the approval itself becomes a node in the graph, so the audit trail survives even if the backend restarts mid-flow.&lt;/p&gt;

&lt;p&gt;I verified that last part by killing the backend mid-approval. The executor reads the approved proposal from FalkorDB, not from process memory. It picked up where it left off.&lt;/p&gt;

&lt;h2&gt;
  
  
  The kill-switch
&lt;/h2&gt;

&lt;p&gt;This is the part I'm most serious about. The hackathon rules say FalkorDB must be central, not decorative. "If the product still works once FalkorDB is taken out, the graph is decoration."&lt;/p&gt;

&lt;p&gt;So I built the test into the demo. Disconnect FalkorDB and the entire product falls back to honest "I don't know" answers. No graph, no answers. No silent degradation, no pretending.&lt;/p&gt;

&lt;p&gt;Most demos tell you the database matters. Mine shows you what happens without it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers, including the misses
&lt;/h2&gt;

&lt;p&gt;I ran 30 questions across five categories. Graph vs flat RAG. Same corpus, same model, same scoring.&lt;/p&gt;

&lt;p&gt;Graph: 24.667. Flat RAG: 19.251.&lt;/p&gt;

&lt;p&gt;But here's what I want to be honest about. The graph lost 5.333 points. Two hard zeros where the scorer demanded exact phrasing and the model paraphrased. Plus partial-credit losses scattered across categories. The README documents every one of them.&lt;/p&gt;

&lt;p&gt;I could have tweaked the questions or dropped the zeros. I didn't. If I'm building a system whose entire pitch is "show your work," the benchmark has to show its work too.&lt;/p&gt;

&lt;p&gt;I re-ran the eval to check reproducibility. Graph won again, 25.0 to 19.251. Same margin. The numbers hold.&lt;/p&gt;

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

&lt;p&gt;The hardest bug wasn't in the graph. It was in the test suite.&lt;/p&gt;

&lt;p&gt;An e2e check asserted the literal string "Ledger freeze ends Friday" in the model's answer. It broke four different ways across four runs: lowercase, markdown bold, an invisible unicode narrow no-break space, and a paraphrase ("ends on Friday"). The data was correct every time. The assertion was wrong.&lt;/p&gt;

&lt;p&gt;I rewrote it to check the structured evidence block instead of the prose. The lesson is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never assert on LLM prose. Assert on structured data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The second lesson: scope your memory before you search it. Don't dump the entire memory store into the model and hope it figures out what's relevant. Resolve the user, resolve the scope, retrieve only what applies. Then rank.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture in one sentence
&lt;/h2&gt;

&lt;p&gt;If I had to explain Recall in one sentence:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Recall keeps a company's causal history in a graph and reasons over it, so every answer traces its path, every memory respects its owner, and every action waits for a human.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final principles
&lt;/h2&gt;

&lt;p&gt;If I had to reduce the whole project to a few principles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every claim needs a citation. If the model can't cite it, it doesn't say it.&lt;/li&gt;
&lt;li&gt;Memory is scoped. A fact belongs to a person, not to the system.&lt;/li&gt;
&lt;li&gt;The past is versioned, never rewritten. Supersede, don't delete. And when you do delete, issue a receipt.&lt;/li&gt;
&lt;li&gt;The agent proposes. The human disposes. The pause before execution is the most important part of the loop.&lt;/li&gt;
&lt;li&gt;The graph is the foundation, not a feature. Disconnect it and the product should honestly stop, not silently degrade.&lt;/li&gt;
&lt;li&gt;Show your misses. A benchmark that hides its zeros is marketing. A benchmark that documents them is evidence.&lt;/li&gt;
&lt;li&gt;Never assert on LLM prose. Assert on structured data.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The main idea
&lt;/h2&gt;

&lt;p&gt;The way I currently think about it is:&lt;/p&gt;

&lt;p&gt;Company knowledge is a graph. Answers are paths through it. Memory is scoped and versioned. Actions require permission. And everything, including the failures, should be visible.&lt;/p&gt;

&lt;p&gt;That visibility is what makes it trustworthy.&lt;/p&gt;

&lt;p&gt;By Anshu&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built for the WeMakeDevs x FalkorDB Graph Hacks hackathon. I used OpenCode as a coding assistant with Muse guiding the work. The architecture, the graph schema, and every verification step are my own. Meridian Pay is fictional; the graph, the queries, and the kill-switch are real.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/101xAnshu/Recall" rel="noopener noreferrer"&gt;https://github.com/101xAnshu/Recall&lt;/a&gt;&lt;br&gt;
Video: &lt;a href="https://youtu.be/F9H5nxtCiow" rel="noopener noreferrer"&gt;https://youtu.be/F9H5nxtCiow&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>softwaredevelopment</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
