- अच्छा सिस्टम डिज़ाइन वह है जो जटिल न लगे और लंबे समय तक बिना किसी खास समस्या के चलता रहे
- सिस्टम डिज़ाइन में state को संभालना सबसे कठिन हिस्सा है, और जहाँ तक संभव हो state स्टोर करने वाले components की संख्या कम रखना महत्वपूर्ण है
- डेटाबेस आम तौर पर वह जगह है जहाँ state संग्रहीत होता है, इसलिए schema design और indexing, तथा bottleneck हटाने पर केंद्रित दृष्टिकोण की आवश्यकता होती है
- caching, event processing, background jobs आदि को performance और maintenance के लिए सावधानी से लागू करना चाहिए, और इनके अति-उपयोग से बचना बेहतर है
- जटिल डिज़ाइन की बजाय पर्याप्त रूप से सत्यापित सरल components और methodologies का उचित उपयोग टिकाऊ और स्थिर सिस्टम निर्माण की कुंजी है
सिस्टम डिज़ाइन की परिभाषा और समग्र दृष्टिकोण
- यदि software design कोड को जोड़ने की प्रक्रिया है, तो system design विभिन्न services को संयोजित करने की प्रक्रिया है
- सिस्टम डिज़ाइन के मुख्य components हैं app server, database, cache, queue, event bus, proxy आदि
- अच्छा डिज़ाइन ऐसी प्रतिक्रियाएँ पैदा करता है जैसे "कोई खास समस्या नहीं है", "सोच से आसान निकला", "इस हिस्से पर अलग से ध्यान देने की ज़रूरत नहीं है"
- इसके उलट, जटिल और बहुत ध्यान खींचने वाला डिज़ाइन मूल समस्या को छिपा सकता है, या overengineering का संकेत हो सकता है
- जटिल सिस्टम को शुरुआत से ही लागू करने के बजाय, न्यूनतम काम करने वाली सरल संरचना से धीरे-धीरे विकसित करना अधिक लाभदायक है
state और stateless के बीच अंतर
- software design में सबसे पेचीदा हिस्सा है state management
- जो services जानकारी स्टोर किए बिना तुरंत परिणाम लौटा देती हैं (जैसे GitHub का PDF rendering), वे stateless होती हैं
- दूसरी ओर, जो service डेटाबेस में write करती है, वह state को manage करती है
- सिस्टम में state store करने वाले components को यथासंभव कम रखना अच्छा है। इससे सिस्टम की complexity और failure की संभावना कम होती है
- ऐसी संरचना की सिफारिश की जाती है जहाँ state management केवल एक service संभाले, और बाकी services API call या event trigger जैसी stateless भूमिकाओं पर केंद्रित रहें
डेटाबेस डिज़ाइन और bottleneck बिंदु
schema और index डिज़ाइन
- डेटा स्टोर करने के लिए मानव-पठनीय schema design की आवश्यकता होती है
- अत्यधिक flexible schema (उदाहरण: सब कुछ JSON column में स्टोर करना) application code और performance दोनों पर बोझ डाल सकता है
- जिन columns पर query बार-बार चलेगी, उनके आधार पर उपयुक्त index सेट करना चाहिए। हर चीज़ पर index लगाने से उल्टा अनावश्यक overhead बढ़ता है
bottleneck हल करने के तरीके
- डेटाबेस access अक्सर एक भारी bottleneck बन जाता है
- जहाँ तक संभव हो, जटिल डेटा को application में नहीं बल्कि डेटाबेस के भीतर JOIN जैसे तरीकों से process करना performance के लिहाज़ से बेहतर है
- ORM इस्तेमाल करते समय loop के अंदर query चलाने वाली गलती से सावधान रहना चाहिए
- ज़रूरत पड़ने पर query को विभाजित करके डेटाबेस के load या query complexity को नियंत्रित करना भी एक तरीका है
- read queries को read-replica में distribute करके मुख्य (Write) node का load कम करने की रणनीति प्रभावी है
- जब बड़ी संख्या में queries एक साथ आती हैं, तो transaction और write operations डेटाबेस को आसानी से overload कर सकते हैं, इसलिए query throttling पर विचार करना चाहिए
धीमे और तेज़ कामों का विभाजन
- जिन कार्यों से user interact करता है, उनमें कुछ सौ milliseconds के भीतर response आवश्यक है
- अधिक समय लेने वाले कार्य (जैसे बड़े PDF conversion) में सिर्फ न्यूनतम काम तुरंत front-end पर उपलब्ध कराना और बाकी को background में भेजना एक प्रभावी pattern है
- background jobs आम तौर पर queue (जैसे Redis) और job runner के साथ मिलकर काम करती हैं
- बहुत दूर भविष्य के लिए scheduled jobs को Redis की बजाय अलग DB table में manage करना और scheduler के माध्यम से चलाना अधिक व्यावहारिक है
caching
- caching एक जैसे या महंगे operations को बार-बार दोहराने की स्थिति में cost कम करने और performance बढ़ाने में मदद करती है
- आम तौर पर cache के बारे में अभी-अभी सीखने वाला junior engineer हर चीज़ cache करना चाहता है, जबकि अनुभवी engineer cache लागू करने में अधिक सावधानी बरतते हैं
- cache नया state जोड़ता है, इसलिए synchronization issues, errors, stale data जैसे जोखिम मौजूद रहते हैं
- पहले query indexing जैसी performance optimization आज़माने के बाद ही caching लागू करना बेहतर है
- बड़े पैमाने के cache के लिए Redis/Memcached की बजाय S3/Azure Blob Storage जैसे document storage में समय-समय पर स्टोर करने का तरीका भी उपयोगी हो सकता है
event processing
- अधिकांश कंपनियों के पास event hub (जैसे Kafka) होता है, और विभिन्न services events के आधार पर distributed processing करती हैं
- events का अति-उपयोग करने की बजाय, सरल request–response API design logging और troubleshooting के लिहाज़ से अधिक उपयोगी होता है
- event-driven processing तब उपयुक्त है जब sender को receiver के behavior की चिंता न करनी पड़े, या high-volume, latency-tolerant scenarios हों
डेटा पहुँचाने के तरीके: push और pull
- डेटा ट्रांसफर के लिए दो तरीके हैं: Pull (request के बाद response) और Push (बदलाव होते ही स्वतः डिलीवरी)
- Pull तरीका सरल है, लेकिन इसमें repeated requests और overload की समस्या हो सकती है
- Push तरीके में सर्वर डेटा बदलते ही client को तुरंत भेज देता है, इसलिए यह अधिक कुशल है और latest data बनाए रखने में लाभदायक है
- बड़ी संख्या में clients को संभालने के लिए, दोनों तरीकों के अनुसार infrastructure (event queue, कई cache servers आदि) का विस्तार आवश्यक होता है
hot paths पर ध्यान
- hot paths से आशय सिस्टम के भीतर उन रास्तों से है जहाँ सबसे महत्वपूर्ण और सबसे अधिक डेटा प्रवाहित होता है
- hot paths में विकल्प कम होते हैं, और डिज़ाइन विफल होने पर पूरी service में गंभीर समस्या पैदा हो सकती है, इसलिए सावधानीपूर्वक डिज़ाइन अनिवार्य है
- कई विकल्पों वाले छोटे features की तुलना में, hot paths पर ध्यान देकर design और testing में संसाधन लगाना अधिक प्रभावी है
logging, metrics, tracing
- failure होने पर कारण की पहचान के लिए, abnormal paths (unhappy path) के लिए विस्तृत logs सक्रिय रूप से रिकॉर्ड करने चाहिए
- सिस्टम resources (CPU/मेमोरी), queue size, request/job time जैसे बुनियादी observability metrics का संग्रह आवश्यक है
- केवल औसत मान देखने के बजाय p95, p99 latency जैसे distribution metrics भी ज़रूर देखने चाहिए। ऊपर के कुछ धीमे requests ही मुख्य users की समस्या हो सकते हैं
kill switch, retry, failure recovery
- kill switch (सिस्टम को अस्थायी रूप से रोकना) और retry का रणनीतिक उपयोग महत्वपूर्ण है
- बिना सोचे-समझे retry करना सिर्फ दूसरी services पर बोझ डालता है; प्रभावी होने के लिए पहले से circuit breaker जैसे तंत्रों द्वारा requests को नियंत्रित करना चाहिए
- Idempotency Key लागू करके एक ही request के दोबारा process होने पर duplicate work को रोका जा सकता है
- कुछ failure स्थितियों में fail open या fail closed में से चुनाव करना पड़ता है। उदाहरण के लिए, Rate Limiting के लिए fail open (अनुमति देना) user impact के लिहाज़ से कम नुकसानदेह हो सकता है। वहीं authentication के लिए fail closed अनिवार्य है
समापन
- service separation, containers, VM adoption, tracing जैसे कुछ विषय छोड़े गए हैं, लेकिन अच्छी तरह सत्यापित components का सही जगह उपयोग ही लंबे समय में सबसे स्थिर सिस्टम निर्माण की ओर ले जाता है
- तकनीकी रूप से बहुत विशेष डिज़ाइन वास्तव में बहुत दुर्लभ होते हैं, और उबाऊ हद तक सरल डिज़ाइन ही व्यवहारिक काम में सबसे अधिक इस्तेमाल होते हैं
- मूल रूप से अच्छा सिस्टम डिज़ाइन वह प्रक्रिया है जिसमें बिना दिखावे के, पर्याप्त रूप से सिद्ध methodologies को सुरक्षित ढंग से संयोजित किया जाता है
अभी कोई टिप्पणी नहीं है.