System Design Codex retweeted
15 articles to help you get better at System Design:
[1] 4 Challenges of Distributed Systems - And Possible Solutions
newsletter.systemdesigncodex…
[2] Your Code is Fast, But Your Database is Slow—Now What?
newsletter.systemdesigncodex…
[3] 15 Must-Know Elements of System Design
newsletter.systemdesigncodex…
[4] How To Do Well in System Design Interviews?
newsletter.systemdesigncodex…
[5] 8 Must Know Distributed System Design Patterns
newsletter.systemdesigncodex…
[6] 6 Strategies to Build Secure APIs
newsletter.systemdesigncodex…
[7] How HTTP 2 Improves Upon HTTP 1
newsletter.systemdesigncodex…
[8] How Netflix Uses Caching to Hold Attention?
newsletter.systemdesigncodex…
[9] 8 Must-Know Strategies to Build Scalable Systems
newsletter.systemdesigncodex…
[10] 3 Interview Questions on Event-Driven Patterns
newsletter.systemdesigncodex…
[11] Don’t Use Sync Communication for Every Case
newsletter.systemdesigncodex…
[12] How Consistent Hashing Works?
newsletter.systemdesigncodex…
[13] The Journey of a SQL Query Through a Database
newsletter.systemdesigncodex…
[14] TCP 3-Way Handshake
newsletter.systemdesigncodex…
[15] Database Sharding
newsletter.systemdesigncodex…
System Design Codex retweeted
What’s the most important part of Event-Driven Architecture?
Events, of course.
In an EDA-based application, different components communicate by sending and receiving events.
But what exactly is an event?
It is an occurrence that has happened in the application.
Some examples of events:
- A customer signing up for your application
- A payment is received for an order
- A failed authentication attempt
Whatever their nature may be, there are mainly 3 different patterns for handling events:
- Event Notifications
- Event-Based State Transfer
- Event Sourcing
✅ Event Notifications
In this approach, events are used to notify something has occurred within your application.
Such an event carries the absolute minimum state. Most times, it’s just the identifier (ID) of an entity.
When receiving such events, some components may record the information for auditing purposes.
Other components might call the source component to fetch additional information about the event.
Such events are general-purpose in nature and suitable for broadcasting requirements. Less data is sent over the network in this case.
✅ Event-Based State Transfer
This is more like the asynchronous relative of REST.
Instead of REST’s on-demand pull model, event-based state transfer follows a push approach.
Data (id + information) is sent out to be consumed by any components.
Some components may create their own local cached copies of the data.
Others may take some other action based on the data received.
The advantage of this approach is that the consumer need not call back the source component for additional information. The entire information is right there in the event payload.
✅ Event Sourcing
This is not about event transmission but event storage.
Changes to an entity are not captured as irreversible modifications but are stored as events in an event store.
These changes can be read and processed to recreate the final state of an entity whenever required.
👉 So - which patterns for EDAs have you used?
System Design Codex retweeted
What’s the deal with Stateless Architecture?
The term “stateless architecture” can be a little confusing.
It seems to imply that the system is without any state.
This is not exactly true.
Stateless architecture just means that the state information (if needed) is stored outside the application server.
Let’s understand it via a real-life example:
✅ You are visiting a stateless restaurant and each waiter has a perfectly forgetful memory.
✅ They just note the order on a separate system using a handheld device and forget about it.
✅ You make an order but then realize, you have an allergy to a particular item and want to change the order.
✅ The waiter will simply check their device to get the details of the order and make changes to it as required.
👉 The same is the case with a system built using Stateless Architecture.
In such a system HTTP requests from a client can be sent to any of the servers.
The state is typically stored on a separate database or cache that is accessible by all the servers.
This way the load balancer can distribute the incoming requests more or less evenly across the instances.
👉 So - do you build your applications in a stateless manner?
System Design Codex retweeted
Layer 4 vs Layer 7 Load Balancers Simplified
✅ Layer 4 Load Balancer
As the name suggests, Layer 4 load balancers operate in the transport layer of the OSI model.
Yes, the famous OSI model!
What does it mean?
The routing decisions are made solely based on the info available at that layer.
Information such as IP address or port.
In other words, a layer 4 load balancer cannot see the data in the network packet.
It has some advantages:
- Simpler to run and maintain
- Better performance since no data lookup
- More secure since there’s no need to decrypt the TLS data
- Only one TCP connection
But there are disadvantages as well.
- No smart load balancing
- No routing to different service types
- No caching since the LB can’t even see the data.
✅ Layer 7 Load Balancer
Layer 7 load balancers operate at the layer 7 of the OSI model.
They deal with protocols such as HTTP(S), WebSocket, FTP, and SMTP.
What this means is that Layer 7 LBs can see the actual data within the packet and make routing decisions based on that data.
Here’s how they work:
[1] Let’s say you have two microservices - one dedicated to the blog posts (/blog) and the other dedicated to the comments (/comments).
[2] The client makes a request to the load balancer by establishing a TCP connection and says that it wants to access the route /blog.
[3] The load balancer decrypts the data and checks the configured rules
[4] Based on the request destination, the load balancer establishes a new TCP connection to the server instances hosting the blog microservice.
[5] Once the response comes back, it sends over the response to the client.
Advantages:
- Smart load balancing (big benefit)
- LB can also cache data
- Can play the role of a reverse proxy
Disadvantages:
- Expensive to run and maintain
- Needs to decrypt the data
- Maintains 2 TCP connections - one from the client to LB and another from LB to the service
👉 So - have you used Layer 4 or Layer 7 load balancer? Or have you used both together?
System Design Codex retweeted
How do you scale a component?
There are three points that I consider:
✅ Vertical Scaling vs Horizontal Scaling
The easiest way to scale a component is vertical scaling.
Think bigger RAM. A larger disk or CPU.
It's cost-effective in the short term. Plus, you avoid dealing with distributed stuff.
But it becomes expensive in the long run and doesn't help you get rid of single points of failure.
This brings us to Horizontal scaling i.e. scaling a component by adding more machines.
✅ Horizontally Scaling Stateless Apps
A stateless component doesn't save the client data from one session for use in the next session.
This makes it easier to scale horizontally.
You can use a load balancer to distribute requests between multiple stateless components.
Of course, it becomes important to build the services in a stateless manner.
✅ Horizontally Scaling Stateful Components
A stateful component saves data from one session to the next one.
For example, a web application that stores the session info. Or a database/key-value store.
For scaling out stateful components, we've to adopt techniques like replication, sharding, and so on.
👉 TL;DR
- Vertical scaling is less complex and you should stay with it for as long as possible. It can also be sufficient for a majority of the apps.
- Think horizontal scaling when vertical is no longer cost-effective.
- Try to build your application layer in a stateless manner so that it's easy to scale out.
- For stateful components like databases, choose modern options designed for horizontal scalability. It will make your life easier.
👉 So - how do you approach the task of scaling a component?
System Design Codex retweeted
15 articles to help you get better at System Design:
[1] 6 Strategies to Build Secure APIs
newsletter.systemdesigncodex…
[2] How HTTP 2 Improves Upon HTTP 1
newsletter.systemdesigncodex…
[3] How Netflix Uses Caching to Hold Attention?
newsletter.systemdesigncodex…
[4] 8 Must-Know Strategies to Build Scalable Systems
newsletter.systemdesigncodex…
[5] 8 Must-Know Strategies to Build Scalable Systems
newsletter.systemdesigncodex…
[6] Don’t Use Sync Communication for Every Case
newsletter.systemdesigncodex…
[7] How Consistent Hashing Works?
newsletter.systemdesigncodex…
[8] The Journey of a SQL Query Through a Database
newsletter.systemdesigncodex…
[9] TCP 3-Way Handshake
newsletter.systemdesigncodex…
[10] Database Sharding
newsletter.systemdesigncodex…
[11] Eventual Consistency is Tricky
newsletter.systemdesigncodex…
[12] Intro to GraphQL
newsletter.systemdesigncodex…
[13] 3 Kafka Messaging Strategies
newsletter.systemdesigncodex…
[14] Intro to Kafka
newsletter.systemdesigncodex…
[15] 3 Interview Questions on Event-Driven Patterns
newsletter.systemdesigncodex…
System Design Codex retweeted
4 must-know Microservice Design Patterns:
✅ Database per service
- In this pattern, each microservice manages its own data.
- This means that no other service can access that data directly.
- Communication or exchange of data can only happen through the owner service.
- The success of this pattern depends on how well you’ve defined the bounded context of your application.
✅ Shared Database
- Avoid this pattern as much as possible
- But sometimes, it’s the only viable option to incrementally move to a microservice architecture.
- This approach is lenient in the sense that multiple services use a shared database.
- However, it creates a bigger impact surface and chances of run-time issues.
✅ API Composition
- This pattern tries to solve the problem of implementing complex queries in a microservices architecture.
- An API Composer invokes other services in the required order.
- After fetching the results, it performs an in-memory join of the data before returning it to the caller.
- This is an inefficient approach due to in-memory joins on potentially large datasets.
✅ CQRS + Event Sourcing
- Next, we have CQRS
- It can help get around the issues with the API Composition Pattern.
- An application listens to domain events from other services and updates a separate query database. This makes it easy to serve complex aggregation queries.
- CQRS can also be combined with Event Sourcing where you store the state of the entity (aggregate) as a sequence of events.
- Of course, this pattern can sometimes be unfamiliar to the developers.
At the end of the day, no pattern is perfect.
Learn about the trade-offs with each pattern and choose what works for you.
👉 So - which patterns have you used?
System Design Codex retweeted
If you are working with databases, you should know about ACID.
ACID in databases stands for Atomicity, Consistency, Isolation, and Durability.
Here’s what each term means:
✅ Atomicity (A)
Atomic means something that cannot be broken down.
Atomicity describes what happens to a Transaction that’s executing multiple write operations and something goes wrong halfway.
Think of a Transaction as a bridge between two states of data named A and B.
No intermediate states are allowed.
It’s all or nothing.
So, if you can’t complete the entire transaction successfully, you don’t complete any part of it. and the transaction is completely aborted.
Without atomicity, a mid-way error during multiple updates, the entire database state can turn into a mess.
Retrying the transaction would be risky because it can result in duplicate data.
Atomicity saves you from all this trouble.
✅Consistency (C)
Consistency in ACID is all about the context of your application.
In any application or system, some statements must be true. For example, credits and debits in a bank must always be balanced.
A consistent transaction is one where statements are true at the beginning as well as the end of the transaction.
But most of it depends on your application.
A database cannot save you if you write bad data that violates the invariants.
In a nutshell, Consistency is the property of your application.
✅ Isolation (I)
Isolation is the trickiest guarantee.
It’s a given that your database will be accessed by multiple clients at the same time.
Databases can handle this quite well if they are reading and writing different parts of the database.
But the plot thickens when different clients try to access the same database records.
This ends up creating concurrency issues.
An example of this issue is two clients simultaneously trying to increment a counter that is stored in a database.
Despite two increment requests, the counter was only incremented by 1 because of race conditions.
The goal of Isolation is that concurrently executing transactions are isolated from each other. But it’s more of a degree than an absolute value.
There are multiple isolation levels ranging from weak to strong. Some common ones are:
- Read Uncommitted
- Read Committed
- Repeatable Read
- Serializable
✅ Durability (D)
Lastly, the purpose of a database system is to let you store data without any fear of losing it.
Durability is the guarantee that promises this safety.
The guarantee holds even in case of a hardware fault or database crash.
There are two ways databases achieve this:
- Disk storage and write-ahead logs for single-node databases
- Replication for multiple nodes.
Of course, there is no perfect durability.
It’s all about risk-reduction techniques.
👉 So - what would you add to make the concept of ACID even more clear?
System Design Codex retweeted
Cookies vs Session Simplified
A cookie is basically a key-value pair that’s stored on the browser.
How does it work?
[1] The user logs in to your frontend application.
[2] The frontend sends the request to the backend server
[3] The backend server generates a cookie
[4] It sets the cookie on the browser via the Set-Cookie response header.
[5] The user makes a new request to view a different page.
[6] The front end sends the request to the backend and includes the Cookie as part of the header.
[7] The server checks the cookie for the user and responds with the required data.
Sounds good, doesn’t it?
But there’s one issue with using cookies.
Cookies are accessible via the browser. You can modify the cookie information.
That’s why it’s not a good idea to use cookies for storing sensitive data about the users.
👉 This is where sessions come into the picture.
The session contains a unique set of characters to identify the user.
It works as follows (the animation also explains the steps):
[1] The user makes a login request
[2] The frontend sends the request to the backend server
[3] The backend creates a session using a secret key and stores it in some sort of session storage (database or cache)
[4] Next, the server sends a cookie back to the client
[5] However, the cookie contains the unique identifier for the session
[6] The user makes a new request to view another page.
[7] The browser sends the session ID as part of the cookie.
This time only the server can validate whether the session is valid.
A few important points to note over here:
✅ Cookies can have a “Secure” flag indicating that it should only be sent over HTTPS. This is good for security reasons.
✅ Also, “HttpOnly” cookies restrict the cookie’s access to JavaScript reducing the risk of XSS attacks.
✅ Cookies (especially 3rd party cookies) raise a bunch of privacy concerns because they can be used to track user behavior.
✅ While cookies can be made secure, server-side sessions provide additional layers of security against CSRF attacks and handling sensitive information
✅ Also, server-side sessions can be centrally managed. This means you can invalidate sessions, expire, or revoke them if needed.
👉 So - what do you typically use for authentication?
System Design Codex retweeted
Database Caching 101 (5 strategies you must know)
👉 Cache-Aside Strategy
In this strategy, the cache sits next to the database.
Here’s how it works:
- When there is a request for data, the application first checks the cache
- If there’s a cache hit, the application returns from the cache
- In case of a cache miss, the application queries the DB and returns the data
- The application also stores the missing data in the cache for future requests
Pros: Great for read-heavy workloads. Also, better resiliency since a cache failure cannot cripple the system.
Cons: Potential inconsistency between the cache and the database.
👉 Read-Through Strategy
The cache sits between the application and the database.
Here’s what happens in read-through:
The application goes to the cache for any read request.
If there’s a cache hit, data is returned from the cache and that’s the end of the flow.
In case of a cache miss, the cache gets the missing data from the database and returns it to the application.
Pros: The application doesn’t have to worry about fetching from the database or the cache. The cache takes care of it.
Cons: Potential inconsistency between the cache and the DB and the need to go to the database for every brand-new read request.
👉 Write-Around Strategy
Same as Cache-Aside with the added context about the write operations.
In this strategy, all writes go to the database and the data that is read goes to the cache.
For a cache miss, the application reads from the DB and updates the cache for the next time.
Great for cases where data is only written once and rarely updated (like a blog post or a static website).
👉 Write-Through Strategy
Write-through tries to solve the problems with read-through.
Instead of writing to the DB, the application first writes to the cache.
And the cache immediately writes to the DB.
The word “immediately” is the key over here.
Pros: Cache will always have any written data. New read requests won’t experience a delay while the cache requests the data from the main DB.
Cons: Extra write latency because the data must go to the cache and then to the DB.
👉 Write-Back Strategy
It’s a variation of the write-through strategy.
With one key difference…
In the write-back, the application writes directly to the cache.
However, the cache doesn’t immediately write to the database but after a delay.
Pros: The strain on the cache is reduced in case you have a write-heavy workload. Requests to the DB are batched and the overall write performance is improved.
Cons: In case of a cache failure, there are chances of possible data loss.
👉 So - which strategy have you used?
System Design Codex retweeted
Don't go for caching without asking these 7 questions:
✅ Am I using the cache to hide the real issues?
Inefficient data models and improper indexes might be the real issues
✅ Is the data I'm trying to cache frequently accessed?
It's all about considering the ROI of caching
✅ Is the data I'm trying to cache dynamic?
There is not much advantage in caching an item that's too dynamic.
✅ Which caching strategy should I choose?
Cache-aside is popular. But there are also other strategies that might be worth exploring.
✅ How will I maintain cache consistency?
No one likes caches with stale data.
✅ Am I ok with a cold cache or do I need a warm cache?
Warm cache requires much more investment in engineering and design
✅ How will I measure the cache effectiveness?
Without measurements, you won't know whether it's even useful.
👉 So - what other question will you ask?
System Design Codex retweeted
What you must know about Kafka?
👉 What is Apache Kafka?
Apache Kafka is a distributed event store and streaming platform.
It began as an internal project at LinkedIn.
Over time, it grew rapidly and today, some of the largest data pipelines in the world use Kafka.
Organizations like Netflix and Uber rely on it for their workflows.
👉 Kafka Messages, Topics, and Partitions
The basic unit of data in Kafka is a Message
Think of a message like a record in a database table. It is transmitted as an array of bytes.
Every message goes to a particular Topic.
You can compare Kafka Topics to a database table or a folder on your computer.
Topics are also made up of multiple partitions.
Partitions improve the redundancy and make the topics horizontally scalable.
👉 Kafka Producers and Kafka Consumer
Producers in Kafka create new messages, batch them, and send them over to a Kafka topic.
A producer also balances messages across the different partitions of a topic.
You can provide a custom partitioning strategy to control the distribution of messages.
Kafka Consumers read messages from a broker.
One or more consumers work as a consumer group to consume messages from a topic.
A consumer instance is tied to a particular partition.
In other words, a partition is owned by a consumer instance.
👉 Kafka Broker and Cluster
A single Kafka server is known as a Broker.
A broker can handle thousands of partitions and millions of messages/second.
Think of the broker as a bridge between the producer and consumer.
It receives messages from producers and handles fetch requests from the consumer.
But the broker is part of a Kafka Cluster
A Kafka Cluster consists of several brokers and provides features like replication.
Every partition is replicated across multiple brokers ensuring high availability and redundancy.
👉 So - have you used Kafka?
System Design Codex retweeted
I asked 11 developers and found that 8 were struggling with data consistency issues.
The number one culprit was Dual Writes.
Dual Writes happen when you have to update two different systems.
For example,
[1] Updating a database
[2] Publishing an event to a different system (say Kafka).
The 2nd step could also be anything else like sending an email.
Because these two systems aren’t linked, we can’t update both in a transactional manner.
If the DB update is successful but a failure occurs after that, the event will never be published.
This means inconsistent data.
What’s the solution?
👉 The Transactional Outbox Pattern.
In this pattern, the transactional logic is pushed into the database.
Whenever there is an update in the database, we also update an outbox table in the same transaction.
Think of the outbox table as a mailbox. As the database updates happen, it is filled with letters that have to be delivered to a post office.
From an application point of view,
- Letters = Events
- Post Office = Kafka
All we need now is the postman, who can carry those letters from the mailbox (outbox) to the post office (Kafka).
This could be an async process and you’ve multiple options on how to implement it:
- A separate thread with the original microservice
- A separate application
- Kafka Connector or Change Data Capture process that monitors the outbox table.
👉 But is the Outbox pattern perfect?
Nothing is perfect, as such.
With this pattern, you can still have duplicate messages in case there are failures.
This is to ensure that we have an At-Least-Once delivery guarantee.
But this requires the downstream systems to de-duplicate the messages.
👉 So - have you used the Outbox pattern?
System Design Codex retweeted
5 Must-Know Techniques to Boost API Performance
✅ Caching
The idea of caching is simple.
Store frequently accessed data in a cache so that you can access it faster when needed.
If there’s a cache miss, fetch the data from the database.
It’s quite effective actually but cache invalidation and deciding on the caching strategy can be challenging.
✅ Scale-out with Load Balancing
If one server instance isn’t enough, you can think of scaling your API to multiple instances.
So - where’s the catch?
You need to find a way to distribute requests between these multiple instances.
Enter Load Balancer.
It not only helps with performance but also makes your application more reliable.
However, load balancers work best when your application is stateless and easy to scale horizontally.
✅ Async Processing
Sometimes, you can’t solve multiple problems together.
The best way is to park them for later.
With async processing, you can let the clients know that their requests are registered and under process.
Then, you process the requests one by one and communicate the results to the client later on.
This allows your application server to take a breather and give its best performance.
But of course, async processing may not be possible for every requirement.
✅ Pagination
If your API returns a large number of records, you need to explore Pagination.
Basically, you limit the number of records per request.
This improves the response time of your API for the consumer.
✅ Connection Pooling
An API often needs to connect to the database to fetch some data.
Creating a new connection for each request can degrade performance.
It’s a good idea to use connection pooling to set up a pool of database connections that can be reused across requests.
This is a subtle aspect but in highly concurrent systems, connection pooling can have a dramatic impact on performance.
👉 So - which other API Performance technique have you seen?
System Design Codex retweeted
15 articles to help you get better at System Design:
[1] 8 Must-Know Strategies to Build Scalable Systems
Why read it: To learn about the must-know scalability techniques
newsletter.systemdesigncodex…
[2] Don’t Use Sync Communication for Every Case
Why read it: To learn how synchronous communication may not be the answer to every situation. Also, an intro to other approaches that might be useful.
newsletter.systemdesigncodex…
[3] How Consistent Hashing Works?
Why read it: To understand how consistent hashing works in a simple manner.
newsletter.systemdesigncodex…
[4] The Journey of a SQL Query Through a Database
Why read it: To understand what happens when you make an SQL query to the database.
newsletter.systemdesigncodex…
[5] TCP 3-Way Handshake
Why read it: Learn about the handshake that made the Internet possible.
newsletter.systemdesigncodex…
[6] Intro to OAuth 2
Why read it: Learn how you can use it for authentication
newsletter.systemdesigncodex…
[7] Eventual Consistency is Tricky
Why read it: Learn about the best patterns to handle eventual consistency
newsletter.systemdesigncodex…
[8] Intro to GraphQL
Why read it: Getting started with GraphQL
newsletter.systemdesigncodex…
[9] 3 Kafka Messaging Strategies
Why read it: To learn about the different Kafka messaging approaches
newsletter.systemdesigncodex…
[10] Intro to BFF Pattern
Why read it: To understand the pros and cons of the BFF pattern while designing services.
newsletter.systemdesigncodex…
[11] 3 Interview Questions on Event-Driven Patterns
Why read it: To handle interview questions on event-driven patterns with conviction
newsletter.systemdesigncodex…
[12] Message Queues and Message Brokers
Why read it: To understand how message queues and message brokers support async processing
newsletter.systemdesigncodex…
[13] Database Sharding
Why read it: Understand the basics of database sharding
newsletter.systemdesigncodex…
[14] Intro to Kafka
Why read it: Get a detailed view on the basics of Kafka
newsletter.systemdesigncodex…
[15] Polling vs Webhooks
Why read it: How to choose between polling and webhooks.
newsletter.systemdesigncodex…
System Design Codex retweeted
If you’re working with message queues and events, don’t ignore this interview question:
👉 “How to handle request-response communication with message queues?”
First of all, we need to understand why this is needed.
With a message queue, you can let two services talk to each other (request-response) asynchronously.
This approach is quite important for long-running tasks. Also, no synchronous calls means less chance of a single service bringing down the application.
But there is a problem.
👉 What if there are multiple requester and responder instances?
This doesn’t matter much in a synchronous REST API call because one requester instance always connects to just one responder instance. There is a temporal coupling.
Not the case with async request-response.
The requester instance that made the request may not be the one that ultimately receives the response. It may be down or unavailable by the time the response comes back.
👉 So - how do you relate a request and response across multiple ephemeral instances?
One common way is to use a correlation ID. Here’s how it works:
✅ Suppose there is an Order Service and Payment Service that interact in an async request-reply model.
✅ A customer places an order through one instance of the Order Service. It generates a unique correlation ID for this order, storing the corresponding data in a database, distributed cache, or even a local instance-level HashMap.
✅ Next, it sends a payment request message to the Payment Service along with the correlation ID.
✅The Payment Service (a particular instance) processes the payment and sends the response to the reply queue. The response message contains the same correlation ID.
✅ The Order Service (same or another instance) picks up the response message. It uses the correlation ID in the message to match the response to the original order request and takes the necessary action.
At this point, you may ask question the need for correlation ID. The same can be accomplished with the order ID.
True, but there are some benefits:
- Multiple payment requests can be sent for the same order (retries, partial payments, etc). A unique correlation ID/request helps match them properly.
- The correlation ID separates the routing logic from the business context (like Order ID).
- Correlation IDs make it easy to trace the flow of the request across multiple services.
👉 So - have you used the async request reply approach?
System Design Codex retweeted
There’s a 91.67% chance you’ll be working on a read-heavy system.
Read-heavy systems are the ones where the majority of requests involve fetching data from a database.
For example,
- Customers view dozens of products on Amazon before buying one.
- Users read hundreds of social media posts before posting one.
Here are 5 strategies that can help you build such systems:
✅ Perform Database Indexing
Indexing your database tables is the absolute must-do activity for optimizing reads.
It allows the database to quickly locate the rows that match a given query.
✅ Database Replication (Read Replicas)
Create read replicas of your database.
These replicas are copies of the primary database that can handle read requests.
With more replicas, you can distribute read requests and improve performance.
✅ Caching
Caching is great for read-heavy systems where the data doesn’t change much after creation.
A good caching strategy can reduce the load on your database.
Look at caching solutions like Redis or Memcached for fast in-memory storage.
✅ Content Delivery Networks
Take caching to the next level by investing in a CDN.
With a CDN, you can cache and serve static content closer to the end users and reduce read latency.
✅ Load Balancing
You can spin up multiple replicas, cache instances, or servers to handle the reads.
But you also need to ensure fair distribution of traffic between these instances.
To do so, implement load balancing.
👉 So - which other strategies have you used for building read-heavy applications?
System Design Codex retweeted
📚 AI Reference Library
Yes, AI can generate technical architecture diagrams. You already knew that.
But will it follow your company's reference architecture and security guidelines? With Eraser AI, it will now! Upload your architecture building blocks to the reference library and Eraser AI will utilize it and even offer citations.
🎥 Watch our video to see how we averted a Black Friday crisis!
System Design Codex retweeted
If you are working with an event-driven system, don’t let this interview question surprise you:
“How do you retry failed transactions using message queues?”
This is a common pattern to handle transient errors. Let’s understand with the help of payment processing as an example.
The general approach to implementing a retry mechanism using message queues has 3 main parts:
✅ Main Queue: This is where new payment transactions are queued.
✅ Dead Letter Queue: A separate queue for messages that failed processing multiple times.
✅ Retry Queue: This is where retries are scheduled with delays. This queue is optional as you can also use the main queue for it.
Here’s how the process works:
[1] The consumer or payment processor picks up a message from the main queue. It attempts to process the payment transaction.
[2] If processing fails, it checks the retry count that’s often stored in the message metadata.
[3] If retry count > max retries, increment count and re-queue the message.
[4] If retry count ≥ max retries, move the message to the DLQ.
[5] For retries, you can either re-queue directly to the main queue with a delay or use a separate retry queue with a time-based trigger.
[6] Lastly, monitor the DLQ for messages that have exhausted retry attempts. Implement a process for dealing with them.
Some best practices to keep in mind while following this pattern:
👉 Exponential Backoff: Increase the delay between retries exponentially to avoid overwhelming the system.
👉 Idempotency: Ensure that the payment processor can safely retry payment without crashing the economy
👉 Message TTL: Set an overall TTL for messages to stop very old transactions from being processed.
👉 Retry Limits: Set a value for max number of retries
👉 Error Types: Distinguish between transient errors (can be retried) and permanent errors (direct to DLQ)
So - what will you add to this approach to make it better?
System Design Codex retweeted
5 articles that can help you get better at Caching Concepts for System Design Interviews (and also help you in projects):
✅ Database Caching Strategies
newsletter.systemdesigncodex…
✅ Don't Cache Without Asking Questions
newsletter.systemdesigncodex…
✅ Introduction to Pre-caching
newsletter.systemdesigncodex…
✅ How LinkedIn Uses Caching to Serve 5M Reads/Second
newsletter.systemdesigncodex…
✅ Caching at Multiple Levels
newsletter.systemdesigncodex…















