Introduction
System design involves planning the architecture of highly available, scale-tolerant, and performant systems. As databases expand past single-node capabilities, developers partition data tables across multiple master nodes.
Designing distributed architectures requires making trade-offs between data consistency, query latency, availability, and hardware cost. System design frameworks help engineers systematically evaluate these trade-offs to choose the optimal architecture.
System Diagram
This architecture layout illustrates the CAP Theorem constraints, showing that a distributed system can guarantee at most two of the three properties during a network partition (P):
/\ / \ / \ Consistency /------\ Availability / P \ /__________\ Partition Tolerance
Architecture & Mechanics
Distributed systems decisions are governed by fundamental theorems:
- **CAP Theorem**: States that in the presence of a network partition (P), a distributed system must choose between Consistency (C - all nodes see the same data at the same time) or Availability (A - every request receives a non-error response).
- **PACELC Theorem**: Extends CAP. If there is a partition (PA), how does the system choose between Consistency (C) and Availability (A)? Else (EL), when the system runs normally, how does it balance Latency (L) against Consistency (C)?
- **Consistent Hashing**: A key-mapping algorithm that allows adding or removing storage nodes with minimal key redistribution, using a virtual ring structure.
Concrete Examples
Below is a JavaScript class demonstrating a consistent hashing ring. It handles node registration and hashes keys to target node slots.
const crypto = require('crypto');
class ConsistentHashRing {
constructor(replicas = 3) {
this.replicas = replicas;
this.ring = {};
this.sortedKeys = [];
}
hash(str) {
return crypto.createHash('md5').update(str).digest().readUInt32BE(0);
}
addNode(node) {
for (let i = 0; i < this.replicas; i++) {
const val = this.hash(`${node}-replica-${i}`);
this.ring[val] = node;
this.sortedKeys.push(val);
}
this.sortedKeys.sort((a, b) => a - b);
}
getNode(key) {
if (this.sortedKeys.length === 0) return null;
const hashVal = this.hash(key);
for (let i = 0; i < this.sortedKeys.length; i++) {
if (hashVal <= this.sortedKeys[i]) {
return this.ring[this.sortedKeys[i]];
}
}
return this.ring[this.sortedKeys[0]]; # Ring wrap-around
}
}Production Best Practices
- **Choose Sharding Keys Wisely**: Select high-cardinality shard keys (like user_id) to distribute database writes evenly, avoiding 'hot shards' that overload specific nodes.
- **Establish Database Replicas**: Configure read replicas to offload search traffic from the master write databases, optimizing system throughput.
- **Implement Circuit Breakers**: Wrap service integrations in fallback mechanisms to prevent a slow service from locking server worker threads and exhausting system connections.
References
- Designing Data-Intensive Applications by Martin Kleppmann
- CAP Theorem Revisited: https://www.infoq.com/articles/cap-twelve-years-later-how-the-rules-have-changed/
Conclusion
Building scalable systems requires understanding distributed trade-offs. By deploying consistent hashing, horizontal sharding, and robust replica routing, applications can support millions of concurrent users while maintaining high availability.
Written by Ajit KumarCloud & Security Specialist
BCA cloud computing and security student, studying kernel namespaces, networking protocols, security pipelines, and competitive programming solutions.