Skip to content

Instantly share code, notes, and snippets.

@VenkataSakethDakuri
Last active December 23, 2025 15:26
Show Gist options
  • Select an option

  • Save VenkataSakethDakuri/a1bae5ed7ab08ff5bdc5840fd01954fa to your computer and use it in GitHub Desktop.

Select an option

Save VenkataSakethDakuri/a1bae5ed7ab08ff5bdc5840fd01954fa to your computer and use it in GitHub Desktop.
System Design
HORIZONTAL Scaling vs VERTICAL Scaling
1. LOAD BALANCING REQUIRED, 1. N/A.
2. RESILIENT, 2. Single point of failure.
3. Network calls (RPC), 3. Inter process Communication.
4. DATA INCONSISTENCY., 4. Consistent
5. SCALES WELL. AS USERS INCREASE, 5. Hardware limit.
Tradional vs Consistent hashing:
The problem with tradional hashing is that whenever we change the no. of servers ( up or down ), lot of data has to be reshuffled across the available servers. This is inefficient, for ex: we may have some cache stored in a particular server and that may become obsolete.
In consistent hashing we have a abstract ring where both servers and requests are mapped ( the hash function for both can be same or different ). For assigning requests to servers we use clockwise rule. Now when we increase or decrease servers only the 2 points in the ring where the server is mapped and the clockwise next point is affected and the traffic gets redistributed, rest all servers are unaffected.
We can further improve the system by using virtual nodes, to avoid cases of some servers handling a large portion of requests due to non uniform layout. Each V-Node acts as a replica of the physical server, allowing the keys to be spread much more evenly across the entire ring and providing better load balance and resilience.
Message queue is a asynchronous way to process requests between producers and consumers.
1. Producer (Sender)
The producer is the application component that creates and publishes the message to the queue.
The producer does not need to know where, when, or how the message will be processed. It simply drops the message into the queue and immediately moves on to its next task.
2. Queue (Broker)
The queue ensures the message is persisted until a consumer successfully processes it.
3. Consumer (Receiver)
The consumer is the application component that connects to the queue and retrieves messages.
When a message is retrieved, the consumer processes it (e.g., updates a database, sends an email).
The consumer typically sends an acknowledgment (ACK) back to the queue to confirm successful processing, after which the queue deletes the message.
The queue can also have a heartbeat and load balancing mechanism.
Monolithic architecture is simple and straightforward for smaller applications or teams. The entire system is packaged as a single code base, making deployment and testing easy at first. Developers need to understand the whole codebase when making changes, which can be time-consuming and challenging for newcomers. Every change—even to a small feature—requires redeploying the full application. If a bug or crash occurs, the entire system may go down because all logic is tightly coupled. Also, scaling is less flexible: you must scale the whole application even if only one part faces heavy traffic.
Microservice architecture breaks down the system into separate, independent services that communicate via APIs. This structure enables teams to work on services separately using different technologies. Developers can onboard more quickly because they only need to understand and work on isolated modules. Services can be deployed individually, allowing downtime or changes in one module without affecting the rest. You can scale just the services that need it, without scaling the whole system. However, the architecture and infrastructure are more complex. Managing many small services adds overhead for monitoring, testing, and deployment, and you need reliable API contracts between modules. If done poorly, it can lead to too many tiny services and unnecessary complexity.
Consistency trumps availability in most cases in databases.
Joins Across Shards: Expensive & slow.
Flexibility: Hard to increase/decrease number of shards with simple partitioning.
Consistent Hashing helps balance data when you add/remove servers.
Hierarchical Sharding: If a shard is too large, split it further and add a manager to route requests.
Indexing is usually done within each shard to boost performance.
Cache:
Eventual consistency: Cache may not always be updated instantly, leading to stale reads (especially problematic for financial transactions).
Cache placement options:
In-memory with server apps (fastest access)
Directly in the database (small built-in caches)
Global, distributed cache servers (scalable, reusable by multiple services)
In real production systems, usually all 3 options are used together.
To avoid SPOF we usually use multiple server instances, backups of data, multiple load balancers, geographical distribution etc.
A CDN is a globally distributed network of servers (cache servers) that delivers static content (e.g., images, HTML, videos) to users from locations geographically close to them. It Reduces latency by serving content from local servers instead of a central server.
Publisher Subscriber model (Pub - Sub)
A service publishes events to a message broker (e.g., Kafka, RabbitMQ) instead of calling other services directly.
Subscribers register with the broker and receive relevant events asynchronously, allowing the publisher to “fire and forget” after persisting the message.
Advantages
Decoupling: Publishers do not need to know which services consume their events; they only interact with the broker, simplifying interfaces and dependencies.
Reliability and scalability: The broker persists messages and can replay them when a consumer comes back up, giving at-least-once delivery and making it easy to add new subscribers like S6 without changing the publisher.
Failure handling: Multiple potential points of failure in a request–response chain are centralized into the broker, which becomes a single, well-managed point to harden.
Main drawbacks
Weaker consistency for cross-service transactions: In financial examples, splitting a logically atomic operation (fees + fund transfer) across services via events can cause inconsistent balances if one service lags or fails.
Idempotency issues: With at-least-once delivery, a consumer may process the same message multiple times (e.g., debiting ₹50 twice) unless the application adds idempotency using request IDs and state checks.
Extra latency and complexity: The broker adds another network hop and operational overhead (learning, running, and maintaining message queues).
Event Driven system:
Core idea
Services publish events (state changes) to a central event bus rather than calling each other directly.
Interested services subscribe, persist events locally, and may emit new events, forming a chain of reactions.
Key advantages
Loose coupling & extensibility: Publishers do not know who consumes events, so new services can be added by just subscribing to existing events.
High availability & self‑healing: Each service stores its own event log, so it can rebuild state, debug by replaying events, and even replace another service by consuming its historical events.
Better rollback and audit: Because events are immutable, you can time‑travel to past states for debugging, audits, or reprocessing with new logic.
Data & transactions
Each service keeps a local database of events relevant to it, often duplicating data from other services to avoid synchronous dependencies.
Delivery semantics are modeled explicitly as “at most once” (e.g., welcome email) or “at least once” (e.g., invoice email) with retry logic in the event bus.
Drawbacks
Hard to reason about flow: From one service’s code you cannot easily see who consumes its events or the full end‑to‑end path, which complicates debugging and onboarding.
Consistency issues: Local copies may diverge; the system favors availability and eventual consistency over strong consistency.
Painful with external side effects: Replaying events that trigger time‑dependent external actions (like sending emails or calling third‑party APIs) can produce different outcomes and is tricky to handle.
Nosql:
NoSQL databases store denormalized, flexible documents (or key-value blobs) instead of normalized relational rows, which makes them great for high-write, large-scale systems but weaker for complex reads and strict transactions.
Core NoSQL ideas
Data is stored as a single blob per key (often JSON-like), so all of a user’s fields live together instead of being split across many related tables.
Schema is flexible: new attributes (like salary) can be added only for new records without expensive schema migrations.
Main advantages
Write-optimized: Inserts and typical “get user by id” reads hit one contiguous blob, so writes and common reads are fast and simple.
Easy horizontal scaling: Sharding and replication are built-in; keys are hashed and distributed across nodes, helping load balancing and availability.
Good for aggregations and analytics-style metrics (e.g., average age, total salary) over large volumes of data. Essentially because of denormalization it becomes easy for "some" type of queries to be executed fastly.
Main disadvantages
Weaker consistency: Classic ACID-style transactions and strong consistency guarantees are not inherent; data replicas can briefly disagree.
Not read-optimized for column-wise queries: to get “all ages,” the system must scan full blobs rather than just one column, making some reads slower than in SQL.
Relations and joins are manual and costly, since foreign keys and relational constraints are not first-class.
Problems:
Partitioning can be done wrt location, age, gender.
Can use XMPP with TCP (TCP for persistent connection) for chats instead of HTTP polling.
Prefer file storage + distributed file system (with CDN on top) over storing images as BLOBs: mutability, transactions, and DB indexes add little value for large static images, while files are cheaper, simpler, and faster to serve.
BLOB (Binary Large Object) vs File for images:
files: cheaper, mutable, stores url/path ref in the db, can add cdn
BLOB: costly, mutable but expensive, stores the binary data in the db, ACID transactions, CDN cant be added as public URLs or HTTP endpoints are needed to access web objects.
In instagram we can keep the no. of likes in a seperate table instead of post or comment table because we can scale the likes table independently of the other tables. We can also make the likes table in a way that we can find all the post the user liked. for example: If we had likes in comment table, whenever we pull out a row from this table, we get likes as well whoch may not be useful.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment