Ninja Learn | نینجا لرن
Статистикаیادگیری برنامه نویسی به سبک نینجا 🥷 اینجا چیزایی یاد میگیری که فقط نینجاهای وب بلدن 🤫 👥 ɢʀᴏᴜᴘ: https://t.me/+td1EcO_YfSphNTlk
- Последний пост
- 11 авг.
- Последнее чтение
- 15 авг.
- Постов за неделю
- 1
- Всего постов
- 46
- Тип
- открытый
- Язык
- персидский
- Категория
- Технологии
- В каталоге с
- 12 авг.
- 1/24сутки в ленте
- 85
- 1/48двое суток
- 97
- 1/72трое суток
- 105
Оценка по просмотрам недавних постов: пост набирает почти всё за первые сутки.
Посты
نگاهی به روش های مختلف Cache invalidation Cache یکی از رایجترین راهکارا برای افزایش Performance توی سیستمهاست. به جای اینکه برای هر Request مستقیم به Database مراجعه کنیم، دادههایی که زیاد استفاده میشن رو توی یک لایهی سریعتر نگهداری میکنیم. این کار باعث میشه تعداد Queryها کمتر بشه، Latency کاهش پیدا کنه و فشار روی Database پایین بیاد. اما یک مشکل مهم وجود داره: اگه دادهی اصلی تغییر کنه، چه اتفاقی برای نسخهای که داخل Cache داریم میفته؟ فرض کنیم اطلاعات یه کاربر داخل Cache ذخیره شده. حالا کاربر اسم خودش رو تغییر میده و این تغییر داخل Database ثبت میشه. اما اگه مقدار قدیمی هنوز داخل Cache وجود داشته باشه، درخواستهای بعدی ممکنه همون دادهی قدیمی رو دریافت کنن. اینجاست که مفهوم Cache Invalidation اهمیت پیدا میکنه. Cache Invalidation دقیقاً یعنی چی؟ 🧠 Cache Invalidation یعنی مدیریت منقضی شدن یا حذف دادههایی که دیگه معتبر نیستند. به زبان ساده، وقتی دادهی اصلی تغییر میکنه، باید یه تصمیمی بگیریم: باید Cache رو حذف کنیم؟ یا مقدار جدید رو داخل Cache قرار بدیم؟ یا اجازه بدیم بعد از مدتی خودش منقضی بشه؟ این تصمیم یکی از چالشهای مهم طراحی سیستمهاست. چون Cache و Database معمولاً دو منبع جدا از هم هستن و همیشه باید بین سرعت بالا و Consistency تعادل ایجاد کنیم. روش اول: Cache Aside (Lazy Loading) 📦 یکی از رایجترین روشها، Cache Aside هست. تو این مدل، Application خودش مسئول مدیریت Cache و Database هست. وقتی یه داده درخواست میشه، اول Cache بررسی میشه. اگه داده وجود داشته باشه، همون مقدار برگردونده میشه. اگه Cache Miss اتفاق بیفته، Application داده رو از Database میخونه، داخل Cache قرار میده و بعد جواب رو برمیگردونه. اما بخش مهم این روش، زمان نوشتن هست. وقتی داده تغییر میکنه، معمولاً Application اول تغییر رو توی Database ذخیره میکنه و بعد مقدار مربوطه رو از Cache حذف میکنه. با این کار، درخواست بعدی مجبور میشه دادهی جدید رو از Database بخونه و دوباره Cache رو پر کنه. مزیت این روش سادگی و کنترل خوب روی Cache هست. به همین دلیل یکی از رایجترین الگوها توی سیستمهای واقعی محسوب میشه. روش دوم: TTL (Time To Live) ⏳ یکی از سادهترین روشها برای مدیریت Cache، تعیین یک زمان انقضا برای دادههاست. هر داده ای فقط تا زمان مشخصی معتبره. بعد از تموم شدن این زمان، داده حذف میشه و درخواست بعدی مقدار جدید رو دریافت میکنه. مزیت TTL اینه که نیاز نداریم تمام تغییرات Database رو دنبال کنیم. اما مشکلش اینه که ممکنه داده قبل از منقضی شدن، تغییر کرده باشه. یعنی تا زمانی که TTL تمام نشده، کاربرا ممکنه مقدار قدیمی رو ببینن. به همین دلیل TTL بیشتر برای دادههایی مناسبه که تغییراتشون حساس نیست یا Stale بودن کوتاهمدت براشون قابل قبوله. روش سوم: Write Through 🔄 توی Write Through، موقع تغییر داده، Application همزمان Cache و Database رو بهروز میکنه. یعنی هر Write باید در هر دو مکان انجام بشه. مزیت این روش اینه که Cache همیشه مقدار جدید رو داره و احتمال Stale Data کمتر میشه. اما هزینهی بیشتری داره، چون هر تغییر باید چند عملیات مختلف انجام بده. همچنین اگه یکی از این عملیاتها شکست بخوره، باید مکانیزمی برای هماهنگ کردن دوبارهی دادهها وجود داشته باشه. روش چهارم: Event-Based Invalidation 📡 تو سیستمهای بزرگتر، گاهی سرویسها از Event استفاده میکنن. یعنی وقتی تغییری داخل Database اتفاق میوفته، یه Event منتشر میشه. سرویسهای دیگه این Event رو دریافت میکنن و Cache مربوط به اون داده رو حذف یا بهروز میکنن. این روش باعث میشه سرویسها وابستگی مستقیم به هم نداشته باشن. اما در عوض، معماری پیچیدهتر میشه و باید مواردی مثل Duplicate Event، ترتیب Eventها و خطاهای احتمالی مدیریت بشن. پس بهترین روش کدومه؟💡 مثل بیشتر تصمیمهای معماری، یک جواب ثابت وجود نداره. اگه دادهها زیاد تغییر نمیکنن و کمی قدیمی بودنشون مشکلی ایجاد نمیکنه، TTL میتونه کافی باشه. اگه Performance و سادگی مهم باشه، Cache Aside معمولاً انتخاب مناسبیه. اگه Consistency اهمیت زیادی داشته باشه، باید سراغ روشهایی مثل Write Through یا Event-Based Invalidation رفت. انتخاب روش مناسب به این بستگی داره که سیستم ما بیشتر به چه چیزی نیاز داره: Performance بیشتر یا دادهی همیشه بهروز؟ جمعبندی ✍️ Cache کردن دادهها بخش سادهی ماجراست. چالش واقعی زمانی شروع میشه که داده تغییر کنه و باید تصمیم بگیریم Cache چه زمانی و چطور بهروز بشه. #️⃣ #system_design #cache #backend ➖➖➖➖➖➖➖➖➖➖ 🌙 CHANNEL | GROUP
connection Pooling چطور ارتباط با Database رو بهینه میکنه؟ 🤔 وقتی یه درخواست وارد یه Backend Service میشه، معمولاً نیاز داره برای گرفتن یا تغییر دادن دادهها با Database ارتباط برقرار کنه. در نگاه اول شاید ساده به نظر برسه: درخواست میاد → Connection ساخته میشه → Query اجرا میشه → Connection بسته میشه اما توی یه سیستم واقعی که صدها یا هزاران درخواست در ثانیه دریافت میکنه، این مدل میتونه تبدیل به یه مشکل بزرگ بشه. چون ساختن یه Database Connection فقط یه عملیات ساده نیست. برای ایجاد یه اتصال، Client باید مراحلی مثل برقراری ارتباط TCP، احراز هویت و آمادهسازی Session با Database رو انجام بده. اگه برای هر درخواست یه Connection جدید ساخته بشه، بخش زیادی از منابع سیستم صرف ساختن و از بین بردن Connectionها میشه، نه پردازش واقعی Queryها. اینجاست که مفهومی به اسم Connection Pooling وارد میشه. Connection Pooling دقیقاً یعنی چی؟ 🧠 Connection Pooling یعنی به جای اینکه برای هر درخواست یه Connection جدید بسازیم، تعدادی Connection از قبل ایجاد کنیم و اونها رو بین درخواستهای مختلف به اشتراک بذاریم. این Connectionها داخل یه Pool نگهداری میشن و هر زمان یه درخواست نیاز به Database داشته باشه، یکی از Connectionهای آزاد Pool رو دریافت میکنه. بعد از تموم شدن Queryها، Connection بسته نمیشه؛ بلکه دوباره به Pool برمیگرده تا درخواست بعدی بتونه از اون استفاده کنه. در واقع Connection Pool مثل یه لایهی مدیریت بین Application و Database عمل میکنه و چرخهی عمر Connectionها رو کنترل میکنه. بدون Connection Pool چه اتفاقی میفته؟ ⚠️ فرض کنیم یه API داریم که در هر درخواست یه Connection جدید به PostgreSQL ایجاد میکنه. اگه در یه لحظه 1000 درخواست وارد سیستم بشه، Application ممکنه تلاش کنه 1000 اتصال جداگانه بسازه. اما Database هم محدودیت داره. هر Connection معمولاً منابعی مثل Memory, Thread, Process, Session State مصرف میکنه. در نتیجه تعداد زیاد Connectionها میتونه باعث بشه Database به جای اجرای Queryها، بیشتر درگیر مدیریت Connectionها بشه. به همین دلیل داشتن Connection بیشتر همیشه به معنی Performance بهتر نیست. Connection Pool چطور کار میکنه؟ ⚙️ یه Pool معمولاً با چند مقدار مهم مدیریت میشه. مثلاً: Minimum Connections: حداقل تعداد Connectionهایی که همیشه آماده نگه داشته میشن. Maximum Connections: بیشترین تعداد Connectionهایی که Pool اجازه ایجادشون رو میده. Idle Timeout: مدتی که یه Connection بدون استفاده میتونه در Pool باقی بمونه. Connection Lifetime: حداکثر عمر یه Connection قبل از اینکه دوباره ساخته بشه. وقتی تعداد درخواستها بیشتر از تعداد Connectionهای آزاد Pool بشه، بسته به تنظیمات سیستم یا درخواست منتظر آزاد شدن یه Connection میمونه یا بعد از یه زمان مشخص خطا دریافت میکنه. Connection Pool و Database چه رابطهای دارن؟ 🗄 یکی از اشتباهات رایج اینه که فکر کنیم هرچی Pool بزرگتر باشه، سیستم سریعتر میشه. اما Database هم ظرفیت محدودی داره. اگه Application چندین Worker داشته باشه و هر Worker تعداد زیادی Connection باز کنه، ممکنه خیلی سریع تعداد Connectionها از توان Database بیشتر بشه. برای مثال: یه سرویس با 10 Worker داریم. اگه هر Worker یه Pool با حداکثر 100 Connection داشته باشه، در بدترین حالت ممکنه 1000 Connection به Database ارسال بشه. در حالی که شاید Database برای چنین تعداد Connectionهایی طراحی نشده باشه. به همین دلیل اندازهی Pool باید با توجه به ظرفیت Database، نوع Queryها و حجم Traffic تنظیم بشه. Connection Pooling چه مشکلی رو حل میکنه؟ 🤔 Connection Pooling باعث میشه هزینه ساخت Connection های تکراری کم بشه، Database کمتر درگیر مدیریت Connection ها بشه، Latency درخواست ها کمتر باشن و در نهایت منابع سیستم بهتر استفاده بشن. اما در کنارش باید درست تنظیم بشه. یه Pool خیلی کوچک باعث میشه Requestها منتظر Connection بمونن و یه Pool خیلی بزرگ، میتونه Database رو تحت فشار قرار بده. جمعبندی ✍️ Connection Pooling یکی از بخشهای مهم طراحی Backendهای مقیاسپذیره. ایدهی اصلی ساده هست: به جای ساختن و نابود کردن مداوم Connectionها، اونها رو مدیریت و دوباره استفاده میکنیم. اما پیادهسازی درستش نیاز به شناخت رفتار Application و Database داره، چون تعداد Connectionهای بیشتر همیشه به معنی Performance بهتر نیست. #️⃣ #system_design #databsae #backend ➖➖➖➖➖➖➖➖➖➖ 🌙 CHANNEL | GROUP
ایران:
ایران:
Database Replication دقیقاً یعنی چی؟ 🧠 Replication یعنی ایجاد چند نسخه از دادهها روی چند Database Server مختلف. توی این معماری، معمولاً یه Node اصلی داریم که مسئول دریافت تغییرات داده هست و Nodeهای دیگه یک کپی از دادهها رو نگهداری میکنن. هدف اصلی Replication این نیست که فضای بیشتری برای ذخیرهسازی داشته باشیم. هدف اصلی اینه که بتوانیم تعدا Request بیشتری رو مدیریت کنیم و فشار روی Database اصلی رو کم کنیم. به زبان ساده، به جای اینکه همهی کاربرا به یه Database متصل بشن، کار بین چند Database تقسیم میشه. Primary-Replica Architecture 🔄 یکی از رایجترین مدلهای Replication، معماری Primary-Replica هست. توی این مدل، یک Database به عنوان Primary انتخاب میشه و تمام عملیات های نوشتن(INSERT, UPDATE, DELETE) روی اون انجام میشن. بعد تغییرات ایجاد شده به Replicaها منتقل میشن تا اونا هم نسخه جدیدی از داده هارو داشته باشن. الان میتونیم از Replicaها برای عملیات های Read استفاده کنیم و اونارو به Replicaها ارسال کنیم تا فشار روی Primary کمتر بشه. Replication چطور دادهها رو منتقل میکنه؟ ⚙️ برای انتقال تغییرات، Database معمولاً تغییرات انجام شده رو توی یه Log ثبت میکنه. تو PostgreSQL، این کار با استفاده از WAL (Write-Ahead Log) انجام میشه. قبل از اینکه تغییرات اصلی روی Data Fileها ذخیره بشن، اطلاعات داخل WAL ثبت میشه. Replicaها این Logها رو دریافت میکنن و با اجرای دوبارهی اون ها، Database خودشون رو به وضعیت Primary نزدیک میکنن. به همین دلیل Replication معمولاً یک فرآیند Continuous هست که دائماً تغییرات جدید رو دنبال میکنه. Synchronous و Asynchronous Replication 🔄 یکی از تصمیمهای مهم در طراحی Replication اینه که تغییرات چطور به Replicaها منتقل بشن. توی Primary, Synchronous Replication قبل از تأیید یه عملیات Write باید مطمئن بشه که Replica هم اون تغییر را دریافت کرده. مزیت این روش اینه که احتمال از دست رفتن داده خیلی کم میشه. اما مشکل اینجاست که سرعت Write کاهش پیدا میکنه، چون Primary باید منتظر Replicaها بمونه. توی Primary, Asynchronous Replication بعد از ذخیرهی تغییر خودش پاسخ رو برمیگردونه و انتقال تغییرات به Replica تو پسزمینه انجام میشه. این روش Performance بهتری داره، اما ممکنه برای مدت کوتاهی Replicaها دادهی جدید را نداشته باشند. به این فاصله، Replication Lag گفته میشه. مشکل Replication Lag چیه؟ ⏳ توی معماری Replica، همیشه این احتمال وجود داره که یه Replica کمی عقبتر از Primary باشه. مثلاً یه کاربر اطلاعات خودش رو تغییر میده. Write روی Primary انجام میشه، اما قبل از اینکه Replica این تغییر رو دریافت کنه، درخواست Read به Replica ارسال میشه. توی این حالت ممکنه کاربر هنوز مقدار قدیمی دادهی خودش رو ببینه. این مشکل یکی از چالشهای مهم سیستمهای توزیع شده هست، چون افزایش Performance معمولاً با هزینهی پیچیدهتر شدن Consistency همراه میشه. Replication چه مشکلاتی رو حل میکنه؟ 💡 Replication بیشتر برای حل دو مشکل استفاده میشه: اول، Scaling Readها: وقتی تعداد Readها زیاد باشه، میتونیم اون ها رو بین چند Replica تقسیم کنیم و فشار روی Primary رو کاهش بدیم. دوم، High Availability: اگر Primary از دسترس خارج بشه، میشه یه Replica رو به عنوان Primary جدید انتخاب کرد تا سرویس ادامه پیدا کنه. البته این فرآیند معمولاً نیاز به مکانیزمهایی مثل Failover و مدیریت دقیق وضعیت Nodeها داره. Replication همیشه جواب مناسبیه؟ 🤔 Replication یک راهکار قدرتمند برای افزایش مقیاسپذیری و دسترسپذیریه، اما بدون هزینه نیست. با اضافه شدن Replicaها، مدیریت سیستم پیچیدهتر میشه و مشکلاتی مثل Replication Lag, انتخاب Primary جدید و Consistency بین Nodeها به سیستم اضافه میشن و باید درست مدیریت بشن. به همین دلیل Replication بیشتر یه Trade-off هست، نه یi راهحل جادویی برای همهی مشکلات Database. جمعبندی ✍️ Database Replication یکی از تکنیکهای اصلی برای ساخت سیستمهای مقیاسپذیر و قابل اعتماده. با نگهداری چند نسخه از دادهها، میتونیم فشار Readها رو تقسیم کنیم و در برابر خرابیها مقاومتر باشیم. اما در کنار این مزایا، باید چالشهایی مثل Consistency و Replication Lag رو هم توی طراحی سیستم در نظر بگیریم. #️⃣ #system_design #databsae #backend ➖➖➖➖➖➖➖➖➖➖ 🌙 CHANNEL | GROUP
ایران:
Leaky Bucket 🪣 Leaky Bucket از نظر ایده شباهتهایی به Token Bucket داره، اما هدف اصلی اون کنترل نرخ خروجی Requestهاست. توی این الگوریتم، Requestهای ورودی وارد یک Queue میشن و با یک سرعت ثابت پردازش میشن. یعنی حتی اگه تعداد زیادی Request در یک لحظه وارد سیستم بشه، خروجی با یک Rate مشخص انجام میشه. این رفتار باعث میشه فشار ناگهانی روی بخشهای دیگر سیستم کاهش پیدا کنه و Traffic ورودی به شکل کنترلشدهتری پردازش بشه. اما محدودیت اصلی Leaky Bucket اینه که اگه سرعت ورود Requestها برای مدت طولانی بیشتر از سرعت پردازش باشه، Queue پر میشه و Requestهای جدید باید حذف یا Reject بشن. به همین دلیل این الگوریتم بیشتر برای سیستمهایی مناسبه که کنترل ثابت روی نرخ پردازش اهمیت زیادی داره. انتخاب الگوریتم مناسب 💡 هیچکدوم از این الگوریتمها همیشه بهترین انتخاب نیستن. Fixed Window زمانی مناسب هست که سادگی و Performance پیادهسازی اهمیت زیادی داشته باشه. Sliding Window محدودیت دقیقتری ایجاد میکنه، اما هزینهی بیشتری برای نگهداری اطلاعات Requestها داره. Token Bucket برای بیشتر APIهای عمومی انتخاب محبوبیه، چون هم محدودیت ایجاد میکنه و هم اجازهی Burstهای کوتاه رو میده. Leaky Bucket هم زمانی مفیده که بخوایم جریان پردازش Requestها کاملاً کنترلشده و ثابت باشه. انتخاب درست Rate Limiting Algorithm به رفتار Traffic، نیاز سیستم و میزان حساسیت روی Consistency بستگی داره. جمعبندی ✍️ Rate Limiting یکی از بخشهای مهم طراحی سیستمهای مقیاسپذیر هست. هدف اون فقط جلوگیری از Requestهای زیاد نیست، بلکه کمک میکنه منابع سیستم پایدار بمونن و یک Client نتونه روی عملکرد کل سرویس تأثیر منفی بذاره. الگوریتمهای مختلف Rate Limiting هرکدوم یک Trade-off بین سادگی، دقت و کنترل ایجاد میکنن و انتخاب درست اونها بخشی از طراحی یک سیستم قابل اعتماد محسوب میشه. #️⃣ #system_design #web #backend ➖➖➖➖➖➖➖➖➖➖ 🌙 CHANNEL | GROUP
Redis چطور با یک Thread تعداد زیادی Request رو مدیریت میکنه؟ 🤔 وقتی دربارهی Performance صحبت میکنیم، معمولاً یکی از اولین چیزهایی که به ذهنمون میاد استفاده از Threadهای بیشتره. منطق هم ساده به نظر میرسه: Thread بیشتر یعنی کار بیشتر، پس سرعت بالاتر. اما Redis سالهاست با یک مدل متفاوت کار میکنه. Redis بخش اصلی پردازش Commandهای خودش رو با یک Thread اصلی انجام میده و با همین معماری میتونه تعداد زیادی Request رو در مدت زمان کوتاه مدیریت کنه. مشکل استفاده از چند Thread چیه؟ 🧠 وقتی چند Thread همزمان روی یک داده کار میکنن، باید مراقب دسترسی همزمان اونها باشیم. مثلاً وقتی دو Thread همزمان بخوان یک مقدار رو تغییر بدن. اینجا باید از Lock استفاده کنیم تا داده خراب نشه. اما Lockها خودشون هزینه دارن. Threadها برای دسترسی به منابع مشترک باید منتظر هم بمونن و سیستمعامل هم باید بین Threadهای مختلف Context Switch انجام بده. توی سیستمهایی که تعداد زیادی عملیات کوچیک و سریع دارن، همین هماهنگی بین Threadها میتونه تبدیل به یک سربار قابل توجه بشه. Redis با یک تصمیم معماری متفاوت، بخش زیادی از این پیچیدگی رو حذف میکنه. Redis چطور با یک Thread کار میکنه؟ ⚙️ توی Command, Redisهای Clientها وارد مسیر پردازش میشن و Thread اصلی Redis اونها رو یکییکی اجرا میکنه. یعنی در یک لحظه، فقط یک Command در حال تغییر State اصلی Redis هست. نتیجهی این کار اینه که Redis برای دادههای داخل Memory خودش نیاز زیادی به Locking نداره. چون چند Thread مختلف وجود ندارن که همزمان بخوان یک داده رو تغییر بدن. این موضوع باعث میشه عملیاتهایی مثل خوندن یا تغییر یک Key با سربار خیلی کمی انجام بشن. Redis چطور همزمان چندین Connection رو مدیریت میکنه؟ 🔄 Single Thread بودن Redis به این معنی نیست که فقط یک Client میتونه به Redis وصل باشه. در واقع Redis میتونه هزاران Connection باز داشته باشه، اما نکته اینجاست که Thread اصلی Redis نباید منتظر تکتک این Connectionها بمونه. چون بیشتر Connectionها در هر لحظه کاری برای انجام دادن ندارن. اینجا Redis از یک معماری Event-Driven استفاده میکنه. به جای اینکه دائماً همهی Connectionها رو بررسی کنه، این کار رو به سیستمعامل میسپاره. توی Redis, Linuxاز مکانیزمهایی مثل epoll استفاده میکنه. Connection, Redisهای مورد نظرش رو به epoll معرفی میکنه و سیستمعامل وضعیت اونها رو دنبال میکنه. وقتی یک Eventها اتفاق بیفته، سیستمعامل اون Connection رو توی لیست Eventهای آماده قرار میده و Redis فقط همون مواردی که واقعاً نیاز به پردازش دارن رو بررسی میکنه. در نتیجه Redis مجبور نیست هزاران Connection رو یکییکی بررسی کنه و فقط زمانی CPU مصرف میکنه که واقعاً کاری برای انجام دادن وجود داشته باشه. چرا این معماری برای Redis جواب میده؟ 🚀 Redis بیشتر برای عملیاتهای سریع روی دادههای داخل Memory طراحی شده. پس به جای اینکه زمان زیادی صرف مدیریت تعداد زیادی Thread و هماهنگی بین اونها کنه، یک مدل سادهتر انتخاب کرده: یک مسیر پردازش اصلی، بدون Lockهای زیاد، همراه با مدیریت هوشمند Connectionها. همین باعث شده Redis با وجود Single Thread بودن، بتونه Performance بسیار بالایی ارائه بده. جمعبندی ✍️ با حذف پیچیدگی Lockها و استفاده از Event Loop و قابلیتهایی مثل Redis, epoll میتونه تعداد زیادی Connection همزمان رو مدیریت کنه، بدون اینکه برای هر Connection یک Thread جدا ایجاد کنه. درک این معماری کمک میکنه بهتر بفهمیم چرا Redis برای عملیاتهای سریع و In-Memory یکی از محبوبترین انتخابهاست. #️⃣ #system_design #databsae #backend ➖➖➖➖➖➖➖➖➖➖ 🌙 CHANNEL | GROUP
چرا Nginx برای هر Request یک Process جدید نمیسازه؟ 🤔 وقتی اسم Web Server میاد، معمولاً اولین چیزی که به ذهنمون میاد اینه که یک Request وارد میشه، سرور اون رو پردازش میکنه و جواب رو برمیگردونه. اما چیزی که کمتر بهش توجه میشه، اینه که خود Web Server چطور…
چرا Nginx برای هر Request یک Process جدید نمیسازه؟ 🤔 وقتی اسم Web Server میاد، معمولاً اولین چیزی که به ذهنمون میاد اینه که یک Request وارد میشه، سرور اون رو پردازش میکنه و جواب رو برمیگردونه. اما چیزی که کمتر بهش توجه میشه، اینه که خود Web Server چطور تصمیم میگیره این Requestها رو مدیریت کنه. این تصمیم تأثیر مستقیمی روی Performance، مصرف Memory و تعداد Connectionهایی داره که سرور میتونه همزمان مدیریت کنه. معماری Worker per Request یعنی چی؟ 🧠 سالها خیلی از Web Serverها از مدلی استفاده میکردن که برای هر Request یا هر Connection، یک Process یا Thread جداگانه ایجاد میشد. ایدهی این معماری خیلی ساده بود. هر Request یک Worker مخصوص خودش رو داره، اون Worker Request رو پردازش میکنه و بعد از تموم شدن کارش آزاد میشه یا از بین میره. این مدل پیادهسازی نسبتاً سادهای داره و هر Worker کاملاً مستقل از بقیه کار میکنه. اما با زیاد شدن تعداد Requestها، تعداد Workerها هم بیشتر میشه و همین موضوع کمکم به یک مشکل تبدیل میشه. مشکل این معماری چیه؟ ⚠️ ساختن یک Process یا حتی یک Thread رایگان نیست. سیستمعامل باید برای هر Worker حافظه اختصاص بده، منابع لازم رو آماده کنه و بین Workerهای مختلف Context Switch انجام بده. شاید برای چند ده Request این هزینه خیلی محسوس نباشه، اما وقتی هزاران Connection همزمان وارد سرور بشن، تعداد Workerها هم به همون نسبت زیاد میشه. در نتیجه بخش قابل توجهی از منابع سیستم صرف مدیریت خود Workerها میشه، نه پردازش Requestها. به همین دلیل این مدل برای بارهای سنگین، خیلی مقیاسپذیر نیست. Nginx چه راهی رو انتخاب کرد؟ ⚙️ Nginx از همون اول مسیر متفاوتی رو انتخاب کرد. به جای اینکه با هر Request یک Worker جدید بسازه، تعداد محدودی Worker Process موقع بالا اومدن سرویس ایجاد میکنه. این Workerها تا زمانی که Nginx در حال اجراست، زنده میمونن و دیگه برای هر Request جدید، خبری از ساختن Process یا Thread نیست. در نتیجه هزینهی ایجاد Worker فقط یک بار پرداخت میشه و همهی Requestهای بعدی توسط همون Workerهای موجود مدیریت میشن. تعداد Workerها چطور مشخص میشه؟ 📌 توی بیشتر سناریوها، Nginx به ازای هر CPU Core یک Worker Process اجرا میکنه. مثلاً روی یک سرور ۸ هستهای، معمولاً ۸ Worker خواهیم داشت. دلیلش هم اینه که هر Worker میتونه روی یک Core اجرا بشه و از توان پردازشی سیستم به بهترین شکل استفاده کنه، بدون اینکه صدها یا هزاران Process همزمان داخل سیستم ایجاد بشه. به همین خاطر هم تنظیم worker_processes auto; یکی از رایجترین تنظیمات Nginx محسوب میشه. آیا این یعنی Nginx همیشه بهتره؟ 💡 نه لزوماً. مدل Worker per Request هنوز هم توی بعضی سناریوها میتونه انتخاب مناسبی باشه و Web Serverهایی مثل Apache هم در طول سالها مدلهای مختلفی مثل Worker MPM و Event MPM رو برای کاهش محدودیتهای معماری قدیمی معرفی کردن. اما چیزی که Nginx رو از همون اول متفاوت کرد، این بود که معماری خودش رو بر پایهی تعداد کمی Worker Process طراحی کرد، نه تعداد زیادی Worker برای هر Request. همین تصمیم باعث شد با مصرف Memory کمتر و سربار پایینتر، بتونه تعداد خیلی بیشتری Connection همزمان رو مدیریت کنه. جمعبندی ✍️ یکی از مهمترین تفاوتهای Nginx با Web Serverهای دیگه، نحوهی مدیریت Requestهاست. به جای اینکه برای هر Request یک Process یا Thread جدید ایجاد کنه، از تعداد محدودی Worker Process استفاده میکنه که از زمان بالا اومدن سرویس در حال اجرا هستن. این تصمیم باعث کاهش مصرف Memory، کم شدن سربار سیستم و افزایش مقیاسپذیری Nginx شده. Part 01 ➖➖➖➖➖➖➖➖➖➖ #️⃣ #web #backend #system_design ➖➖➖➖➖➖➖➖➖➖ 🌙 CHANNEL | GROUP
Redis چطور تغییرات رو بدون متوقف کردن سرویس روی Disk ذخیره میکنه؟ 🤔 توی پست قبلی دربارهی RDB صحبت کردیم و دیدیم Redis چطور با استفاده از ()fork و Copy-On-Write از دادههای داخل Memory یه Snapshot میگیره. اما RDB یه محدودیت مهم داره: چون Snapshotها در بازههای زمانی مشخص ساخته میشن، ممکنه تغییراتی که بعد از آخرین Snapshot اتفاق افتادن، در صورت Crash شدن Redis از بین برن. اینجاست که مکانیزم دیگهی Redis برای Persistence یعنی AOF (Append Only File) وارد میشه. AOF دقیقاً چیه؟ 🧠 برخلاف RDB که وضعیت نهایی دادهها رو ذخیره میکنه، AOF خود Commandهایی که باعث تغییر داده شدن رو ذخیره میکنه. یعنی به جای اینکه وضعیت فعلی دیتابیس رو بررسی کنه و ازش Snapshot بگیره، Commandها و ترتیب اجرا شدن اونها رو داخل فایل AOF ثبت میکنه. بعد از Restart شدن Redis، با خوندن همین فایل Commandها دوباره اجرا میشن و وضعیت قبلی بازسازی میشه. مشکل اصلی AOF چیه؟ 💿 اگر Redis بخواد بعد از هر Write، مستقیم روی دیسک بنویسه، سرعتش خراب نمیشه؟ جواب کوتاه: بله. دیسک نسبت به RAM خیلی کندتره. Redis سرعت بالاش رو از این میگیره که عملیاتها رو داخل Memory انجام میده. پس اگر برای هر Command مجبور باشه منتظر نوشتن روی دیسک بمونه، تمام مزیت In-Memory بودنش زیر سؤال میره. برای همین Redis نوشتن AOF رو از مسیر اصلی اجرای Command جدا میکنه. Redis چطور Write به Disk رو Non-blocking میکنه؟ ⚙️ Redis یک Thread اصلی داره که Commandهای Clientها رو پردازش میکنه. این Thread نباید برای عملیاتهای کند مثل Disk I/O منتظر بمونه. به همین دلیل، وقتی یه Write اتفاق میفته، Redis اول Command رو داخل یه Buffer توی Memory قرار میده. بعد در زمان مناسب، این دادهها از Buffer به فایل AOF منتقل میشن. در نتیجه Redis میتونه همچنان Requestهای جدید رو پردازش کنه و منتظر Disk نباشه. حالت های مختلف AOF میتونیم یکی از سه حالت AOF رو داشته باشیم: Always توی این حالت، بعد از هرRedis, Command مجبور میشه داده رو روی دیسک Sync کنه. این حالت بیشترین میزان durability رو میده، چون احتمال از دست رفتن داده خیلی کمه. اما هزینهی Performance بالایی داره، چون تعداد زیادی عملیات دیسک در هر ثانیه انجام میشه. Every Second این حالت پیش فرض رایج Redis هست. Redis دادهها رو تقریبا هر 1 ثانیه یک بار، sync میکنه. توی این حالت، Performance خیلی بهتره و در بدترین حالت ممکنه حدود یک ثانیه از تغییرات از بین بره. برای خیلی از سیستمهای Production این Trade-off منطقیه. No توی این حالت Redis کنترل sync رو به سیستمعامل میسپاره. Performance بالاتره، اما تضمین کمتری برای حفظ داده وجود داره. AOF Rewrite چرا لازم میشه؟ 📝 با گذشت زمان، فایل AOF بزرگ و بزرگتر میشه. مثلاً ممکنه یه Key چند هزار بار تغییر کنه. AOF ممکنه تمام تغییرات قبلی رو نگه داشته باشه و یه فایل خیلی بزرگ ایجاد کرده باشه، در حالی که فقط آخرین مقدار مهمه. برای حل این مشکل Redis از AOF Rewrite استفاده میکنه. توی Rewrite، Redis یک فایل AOF جدید تولید میکنه که فقط شامل Commandهای لازم برای ساخت وضعیت فعلی دیتابیسه. مثلاً به جای: SET counter 1 SET counter 2 SET counter 3 SET counter 4 فقط آخرین وضعیت ذخیره میشه: SET counter 4 AOF بهتره یا RDB؟ 🤔 مثل خیلی از تصمیمهای معماری، جواب مطلقی وجود نداره. RDB بیشتر برای: - Backup گرفتن - Recovery سریعتر - حجم کمتر فایل مناسبه. AOF بیشتر برای: - از دست ندادن تغییرات اخیر - Durability بالاتر - ثبت دقیقتر عملیاتها - استفاده میشه. به همین دلیل خیلی از سیستمهای واقعی حتی هر دو روش رو همزمان فعال میکنن. جمعبندی ✍️ AOF یکی از روشهای Redis برای حفظ دادهها بعد از Restart یا Crash شدنه. توی این روش Redis به جای ذخیرهی Command, Snapshotهای تغییر دهندهی داده رو ثبت میکنه. برای اینکه سرعت Redis کاهش پیدا نکنه، نوشتن روی Disk از مسیر اصلی اجرای Command جدا میشه و با Buffer مدیریت میشه. اما اینجا هم مثل RDB یک Trade-off وجود داره: هرچی بخوایم داده امنتر ذخیره بشه، هزینهی بیشتری از نظر Performance پرداخت میکنیم. Part 02 | Final ➖➖➖➖➖➖➖➖➖➖ #️⃣ #system_design #databsae #backend ➖➖➖➖➖➖➖➖➖➖ 🌙 CHANNEL | GROUP
RDB چه مزایا و معایبی داره؟ 💡 یکی از بزرگترین مزیتهای RDB اینه که فایل خروجی معمولاً حجم کمی داره و ردیس بعد از Restart میتونه خیلی سریعتر دادهها رو دوباره Load کنه. همچنین چون نوشتن Snapshot به صورت دورهای انجام میشه، فشار کمتری روی دیسک ایجاد میکنه. اما مشکل اصلی اینجاست: RDB آخرین وضعیت Memory رو ذخیره نمیکنه، بلکه آخرین Snapshot گرفتهشده رو ذخیره میکنه. یعنی اگر ردیس Crash بشه، ممکنه تغییراتی که بعد از آخرین Snapshot انجام شدن از بین برن. مثلاً اگر هر 5 دقیقه Snapshot بگیریم، در بدترین حالت ممکنه 5 دقیقه آخر تغییرات از دست بره. پس چرا ردیس فقط RDB استفاده نمیکنه؟ 🧩 چون RDB بیشتر برای Performance و Recovery سریع طراحی شده. اما بعضی سیستمها نیاز دارن کوچیکترین تغییر هم از بین نره. برای این سناریو ردیس روش دیگهای به اسم AOF (Append Only File) داره که به جای Snapshot گرفتن، Commandهای Write رو ذخیره میکنه. که توی پست های بعدی درموردش مینویسم. جمعبندی 🧠 ردیس با اینکه یک دیتابیس In-Memory هست، با استفاده از Persistence میتونه دادهها رو بعد از Restart هم حفظ کنه. توی روش RDB، ردیس به کمک ()fork یک Process جدا برای ساخت Snapshot ایجاد میکنه و با استفاده از Copy-On-Write بدون متوقف کردن Thread اصلی خودش، دادهها رو روی Disk ذخیره میکنه. اما مثل هر تصمیم معماری دیگهای، اینجا هم یک Trade-off وجود داره: RDB سرعت بالا و Recovery سریعتر میده، اما ممکنه بخشی از تغییرات اخیر از بین برن. برای همین توی سیستمهای واقعی باید با توجه به نیازمون بین Performance، Durability و Complexity انتخاب کنیم. Part 01 ➖➖➖➖➖➖➖➖➖➖ #️⃣ #system_design #databsae #backend ➖➖➖➖➖➖➖➖➖➖ 🌙 CHANNEL | GROUP
این بلا تکلیفی منو آزار میدهد 🙏
شوروی واقعا توی شروع قوی عمل کرد ولی قابل مقایسه با حرکتای ناسا نبود واقعا (مهندسای المانی بیشتری داشت)
видео или голосовое, без подписи
Who deserved it?
یکی: حتما برای انتخاباتت توی زندگیت کلی فکر میکنی من:
🌚
خیلی خوشحالم Zed برای ویندوز اومده 🥹
وقتشه فیلد مارشال شم