System Design কী?
System Design হলো একটি সফটওয়্যার Server, Database, Cache, Load Balancer, Queue, API, এগুলো একসাথে কীভাবে কাজ করবে, কোথায় ভেঙে পড়তে পারে এবং সেটা কীভাবে সামলানো হবে, তা পরিকল্পনা করার প্রক্রিয়া। System Design হলো একটি বড় ও জটিল Software System কীভাবে কাজ করবে, তার একটি পূর্ণাঙ্গ blueprint তৈরি করার প্রক্রিয়া।
একটি সহজ তুলনা:
একটি ছোট চায়ের দোকান একজন মালিক একাই চালাতে পারেন। কিন্তু প্রতিদিন হাজার হাজার মানুষকে সেবা দেওয়া একটি রেস্তোরাঁ চেইন চালাতে লাগে রান্নাঘরের ভাগাভাগি, সরবরাহ ব্যবস্থা, অর্ডার ম্যানেজমেন্ট এবং বিকল্প পরিকল্পনা। সফটওয়্যারেও একই কথা। যা ১০০ জন ইউজারের জন্য ঠিকঠাক চলে, তা ১ কোটি ইউজারে প্রায় নিশ্চিতভাবে ভেঙে পড়ে, যদি না শুরু থেকেই ভেবে ডিজাইন করা হয়।
কেন System Design গুরুত্বপূর্ণ?
Facebook, Google, Amazon বা Netflix-এর মতো কোম্পানি প্রতি সেকেন্ডে লাখ লাখ request সামলায়। কিন্তু বিষয়টা শুধু বড় কোম্পানির নয়। বাংলাদেশের প্রেক্ষাপটে ধরুন, ঈদের আগে একটি e-commerce সাইটে বা বোনাস-সপ্তাহে একটি MFS অ্যাপে হঠাৎ কয়েকগুণ ট্র্যাফিক আসে। সিস্টেম প্রস্তুত না থাকলে ঠিক সেই মুহূর্তেই সবচেয়ে বেশি ক্ষতি হয়।
একটি ভালো ডিজাইন মূলত Non-Functional Requirements (NFR) পূরণ করে:
Scalability: লোড বাড়লে সিস্টেম যেন সেই অনুপাতে বাড়তে পারে।
Reliability: কিছু কম্পোনেন্ট ফেল করলেও সিস্টেম সঠিক ফলাফল দেবে।
Availability: সিস্টেম কতক্ষণ চালু থাকে (99.9% মানে বছরে প্রায় ৮.৮ ঘণ্টা downtime, 99.99% মানে প্রায় ৫২ মিনিট)।
Performance: Latency (একটি request-এর সময়) ও Throughput (প্রতি সেকেন্ডে কত request) দুটোই গুরুত্বপূর্ণ।
Maintainability: কোড বোঝা, debug করা ও নতুন ফিচার যোগ করা সহজ থাকা।
Cost efficiency: সবচেয়ে বড় সার্ভার কেনা সমাধান নয়, দরকার সঠিক ভারসাম্য।
মূল কথা: System Design এর কোনো "সেরা" উত্তর নেই, আছে শুধু trade-off। প্রতিটি সিদ্ধান্তে আপনি কিছু পাবেন, কিছু ছাড়বেন। ভালো ইঞ্জিনিয়ার সেটা সচেতনভাবে করেন।
Scaling এর দুই পথ
Vertical Scaling (Scale Up): একটি সার্ভারে আরও CPU/RAM যোগ করা। সহজ, কিন্তু একটা সীমা আছে, খরচ দ্রুত বাড়ে এবং এটি Single Point of Failure (SPOF) থেকে যায়।
Horizontal Scaling (Scale Out): আরও অনেক সার্ভার যোগ করা। প্রায় সীমাহীন স্কেল ও ভালো fault tolerance পাওয়া যায়, তবে জটিলতা বাড়ে (load balancing, data consistency, distributed debugging)।
ডিজাইনের দুইটি স্তর লিঙ্ক এখানে বিস্তারিত আলোচনা করা হয়েছে।
গুরুত্বপূর্ণ ধারণা: Stateless Service যদি ইউজারের সেশন নিজের মেমরিতে রাখে, তাহলে horizontal scaling কঠিন। তাই session/state বাইরে (Redis বা database-এ) রাখা হয়, যাতে যেকোনো সার্ভারই যেকোনো request সামলাতে পারে।
১. Load Balancing
Load Balancer ট্র্যাফিককে একাধিক সার্ভারে ভাগ করে দেয় এবং স্বাস্থ্যকর (healthy) সার্ভার বেছে নেয়। এটি scalability ও availability দুটোই বাড়ায়।
জনপ্রিয় Algorithm:
Round Robin: পালা করে প্রতিটি সার্ভারে পাঠানো। সরল, কিন্তু সার্ভারের ক্ষমতা ভিন্ন হলে অসম।
Weighted Round Robin: শক্তিশালী সার্ভার বেশি request পায়।
Least Connections: যে সার্ভারে সবচেয়ে কম সক্রিয় কানেকশন, তাকে দেওয়া হয়। দীর্ঘস্থায়ী কানেকশনে (WebSocket) ভালো।
IP Hash / Consistent Hashing: একই ক্লায়েন্ট বা key সবসময় একই সার্ভারে যায়। Cache locality-র জন্য কার্যকর।
২. Caching
Caching এর মূল ধারণা: যে ডেটা বারবার লাগে, তা সাধারণত memory রাখা। Redis বা Memcached-এ ডেটা পড়তে মাইক্রোসেকেন্ড-মিলিসেকেন্ড লাগে।
Cache এর স্তরসমূহ: Browser → CDN → API Gateway → Application (in-memory) → Distributed cache (Redis) → Database buffer।
প্রধান Caching Strategy:
Strategy | কীভাবে কাজ করে | কখন ভালো |
|---|---|---|
Cache-Aside (Lazy Loading) | App আগে cache দেখে; না পেলে DB থেকে এনে cache এ রাখে | সবচেয়ে প্রচলিত, read-heavy কাজে |
Write-Through | লেখার সময় cache ও DB দুটোতেই লেখে | Consistency জরুরি হলে |
Write-Back (Write-Behind) | আগে cache এ লেখে, পরে asynchronously DB তে | Write-heavy কাজে, তবে ডেটা হারানোর ঝুঁকি |
Eviction Policy: LRU (সবচেয়ে কম সম্প্রতি ব্যবহৃত বাদ), LFU (সবচেয়ে কম ঘনঘন ব্যবহৃত বাদ), TTL (সময়সীমা শেষে বাদ)।
সাধারণ সমস্যা:
Cache Stampede (Thundering Herd): জনপ্রিয় একটি key একসাথে expire হলে হাজারো request সরাসরি DB তে আঘাত করে। সমাধান: TTL এ random jitter, locking, বা আগেভাগে refresh।
Cache Penetration: এমন key এর request আসতে থাকে যা DB তেও নেই। সমাধান: null caching বা Bloom Filter।
Hot Key: একটিমাত্র key তে অতিরিক্ত চাপ।
৩. Database: নির্বাচন, Replication ও Sharding
SQL বনাম NoSQL
SQL (PostgreSQL, MySQL): ACID গ্যারান্টি, শক্তিশালী relation ও join। আর্থিক লেনদেন, order management এর জন্য উপযুক্ত।
NoSQL: নমনীয় schema ও সহজ horizontal scaling। ধরন অনুযায়ী Key-Value (Redis), Document (MongoDB), Wide-Column (Cassandra), Graph (Neo4j)।
"NoSQL দ্রুত, তাই ভালো" ধারণাটি ভুল। ডেটার আকার ও access pattern দেখে নির্বাচন করতে হয়। অনেক ক্ষেত্রে PostgreSQL ই দীর্ঘদিন যথেষ্ট থাকে।
Replication
একই ডেটা একাধিক নোডে রাখা। Primary-Replica মডেলে লেখা হয় primary তে, আর read ভাগ হয় replica গুলোতে। এতে read scalability ও availability বাড়ে। তবে Replication Lag এর কারণে replica থেকে নতুন ডেটা নাও পাওয়া যেতে পারে (eventual consistency)।
Sharding (Horizontal Partitioning)
একটি বড় ডেটাসেটকে একাধিক ডেটাবেসে ভাগ করা, যেখানে প্রতিটি অংশকে বলে shard। একটি সার্ভারে ধরার মতো নয় এমন ডেটা বা write load সামলাতে এটি লাগে।
Sharding Strategy:
Range-based: যেমন user_id 1–1M এক shard, 1M–2M আরেক। সরল, কিন্তু hotspot তৈরি হতে পারে।
Hash-based:
hash(key) % N। সমান বণ্টন, কিন্তু N বদলালে অনেক ডেটা সরাতে হয়। এখানে Consistent Hashing কাজে আসে।Directory-based: একটি lookup টেবিল বলে দেয় কোন key কোন shard-এ। নমনীয়, তবে lookup নিজেই bottleneck হতে পারে।
ব্যবহারিক পরামর্শ: আগে index, query optimization, caching ও read replica চেষ্টা করুন। Sharding হলো শেষের দিকের অস্ত্র।
৪. CAP Theorem ও তার বাস্তব ব্যাখ্যা
Distributed system-এ তিনটি বৈশিষ্ট্য:
Consistency (C): সব ক্লায়েন্ট একই সময়ে একই সর্বশেষ ডেটা দেখে।
Availability (A): প্রতিটি request একটি (ত্রুটিহীন) response পায়।
Partition Tolerance (P): নেটওয়ার্ক বিচ্ছিন্ন হলেও সিস্টেম কাজ চালায়।
একটি সাধারণ ভুল ধারণা সংশোধন: অনেকে বলেন "তিনটির মধ্যে যেকোনো দুটি বেছে নিন।" বাস্তবে, distributed system এ network partition অনিবার্য, তাই P আপনার পছন্দের বিষয় নয়, এটি ধরে নিতেই হবে। আসল প্রশ্ন হলো: partition ঘটলে আপনি C বেছে নেবেন, না A?
CP: ভুল ডেটা দেওয়ার চেয়ে request প্রত্যাখ্যান করা ভালো (ব্যাংকিং, ব্যালান্স)। উদাহরণ: ZooKeeper, HBase।
AP: কিছুটা পুরোনো ডেটা দিলেও সিস্টেম চালু থাকুক (সোশ্যাল ফিড, লাইক কাউন্ট)। উদাহরণ: Cassandra, DynamoDB (ডিফল্ট কনফিগে)।
৫. Message Queue ও Asynchronous Processing
সব কাজ সঙ্গে সঙ্গে করার দরকার নেই। ইউজার অর্ডার দিলে confirmation তখনই দেখাতে হবে, কিন্তু ইমেইল/SMS পাঠানো, invoice তৈরি, analytics আপডেট পরেও হতে পারে।
Message Queue/Broker producer ও consumer কে আলাদা (decouple) করে এবং হঠাৎ ট্র্যাফিক-স্পাইক শুষে নেয়।
RabbitMQ: ঐতিহ্যবাহী message broker। জটিল routing, task queue এর জন্য ভালো।
Kafka: বিতরণকৃত event log। অত্যন্ত উচ্চ throughput, event replay, stream processing ও event sourcing এর জন্য।
গুরুত্বপূর্ণ ধারণা:
Delivery Semantics: At-most-once, At-least-once, Exactly-once (বাস্তবায়নে কঠিন)।
Idempotency: At-least-once ডেলিভারিতে একই মেসেজ দুবার এলেও ফলাফল যেন একই থাকে (যেমন পেমেন্ট দুবার না কাটা)। এটি payment সিস্টেমে অপরিহার্য।
Dead Letter Queue (DLQ): বারবার ব্যর্থ মেসেজ আলাদা করে রাখা।
Backpressure: Consumer ধীর হলে producer কে সামলানো।
৬. Microservices বনাম Monolith
Microservices একটি বড় অ্যাপ্লিকেশনকে ছোট, স্বাধীনভাবে deploy যোগ্য সার্ভিসে ভাগ করে, প্রতিটি নিজের ডেটা ও দায়িত্ব নিয়ে।
সুবিধা:
স্বাধীন deployment ও scaling (শুধু payment service স্কেল করা যায়)
ভিন্ন টিম, ভিন্ন টেকনোলজি
একটি সার্ভিসের ব্যর্থতা সহজে সীমাবদ্ধ রাখা যায়
অসুবিধা:
নেটওয়ার্ক কল মানে latency বাড়বে
Distributed transaction কঠিন (Saga pattern লাগে)
Debugging, tracing, monitoring জটিল
Operational overhead প্রচুর (CI/CD, service discovery, orchestration)
বাস্তব পরামর্শ: ছোট টিম ও নতুন প্রোডাক্টের জন্য well-structured Modular Monolith দিয়ে শুরু করা প্রায়ই বুদ্ধিমানের কাজ। যখন নির্দিষ্ট মডিউল আলাদা করে স্কেল বা আলাদা টিমের দরকার হবে, তখন সেটি বের করে আনুন। "Microservices ট্রেন্ডি" এই কারণে শুরুতেই বেছে নেওয়া একটি সাধারণ ও ব্যয়বহুল ভুল।
সংশ্লিষ্ট প্যাটার্ন: API Gateway (routing, auth, rate limiting), Service Discovery, Circuit Breaker (ব্যর্থ সার্ভিসে বারবার কল বন্ধ করা), Saga (distributed transaction), CQRS (read ও write মডেল আলাদা)।
৭. আরও কিছু গুরুত্বপূর্ণ বিষয়
CDN (Content Delivery Network): স্ট্যাটিক কন্টেন্ট (ছবি, ভিডিও, JS/CSS) ইউজারের কাছাকাছি edge সার্ভার থেকে সরবরাহ করা। Latency কমায়, মূল সার্ভারের চাপ কমায়।
Rate Limiting: অপব্যবহার বা DDoS ঠেকাতে। Algorithm: Token Bucket, Leaky Bucket, Sliding Window।
Proxy: Forward proxy (ক্লায়েন্টের পক্ষে) ও Reverse proxy (সার্ভারের পক্ষে, যেমন Nginx)।
Observability: Logging, Metrics, Distributed Tracing। "যা মাপা যায় না, তা ঠিক করাও যায় না।"
৮. Architectural Pattern
Pattern | মূল ধারণা | ব্যবহার |
|---|---|---|
Client-Server | Client request পাঠায়, server response দেয় | প্রায় সব ওয়েব/মোবাইল অ্যাপ |
Peer-to-Peer | প্রতিটি নোড একই সঙ্গে client ও server | BitTorrent, কিছু blockchain সিস্টেম |
Event-Driven | Event ঘটলে সিস্টেমের বিভিন্ন অংশ প্রতিক্রিয়া জানায় | Order processing, real-time notification |
Layered (N-tier) | Presentation, Business, Data স্তরে ভাগ | ঐতিহ্যবাহী এন্টারপ্রাইজ অ্যাপ |
Serverless | ইনফ্রা ব্যবস্থাপনা ছাড়াই ফাংশন চালানো | অনিয়মিত/স্পাইকি workload |
৯. Back-of-the-Envelope Estimation
ডিজাইনের আগে মোটামুটি হিসাব করা দরকার: কত ইউজার, প্রতি সেকেন্ডে কত request (QPS), কত স্টোরেজ, কত bandwidth।
উদাহরণ: ১ কোটি daily active user, প্রত্যেকে দিনে ২০টি request করলে মোট ২০ কোটি request/দিন। ২০ কোটি ÷ ৮৬,৪০০ সেকেন্ড ≈ ২,৩০০ QPS (গড়)। Peak সাধারণত গড়ের ২–১০ গুণ, অর্থাৎ ৫,০০০–২৩,০০০ QPS ধরে ডিজাইন করতে হবে। এই সংখ্যাই ঠিক করে দেয় আপনার একটি DB যথেষ্ট কি না, cache লাগবে কি না, sharding দরকার কি না।
১০. জনপ্রিয় Tools ও Technologies
ক্ষেত্র | টুল |
|---|---|
Load Balancing / Proxy | Nginx, HAProxy, Envoy, AWS ELB |
Caching | Redis, Memcached |
Message Queue / Streaming | Kafka, RabbitMQ, AWS SQS |
Relational DB | PostgreSQL, MySQL |
NoSQL | MongoDB, Cassandra, DynamoDB |
Search | Elasticsearch, OpenSearch |
Container / Orchestration | Docker, Kubernetes |
Monitoring & Logging | Prometheus, Grafana, ELK Stack, OpenTelemetry |
System Design মুখস্থ করার বিষয় নয়, এটি trade-off বুঝে যুক্তিসঙ্গত সিদ্ধান্ত নেওয়ার দক্ষতা। প্রতিটি টুল একটি সমস্যার সমাধান করে এবং সঙ্গে নতুন একটি সমস্যা আনে। Cache গতি দেয় কিন্তু stale data আনে। Sharding স্কেল দেয় কিন্তু জটিলতা আনে। Microservices স্বাধীনতা দেয় কিন্তু operational বোঝা আনে।
শেখার সেরা পথ হলো ছোট থেকে শুরু করা: একটি সাধারণ অ্যাপ বানান, তারপর ধাপে ধাপে লোড বাড়ান, যেখানে ভাঙে সেখানে সমাধান যোগ করুন। এভাবেই প্রতিটি কম্পোনেন্টের "কেন" বোঝা যায়।
পরামর্শ-করা পাঠ্য: Designing Data-Intensive Applications (Martin Kleppmann), System Design Interview (Alex Xu), এবং বড় কোম্পানিগুলোর engineering blog।