کتابهای کلیدواژه: مقیاسپذیری · همهٔ کتابها
کتابخانهٔ شنیدنی شبگفتار
کتابهای مقیاسپذیری
خلاصههای صوتی فارسی کتابهای مرتبط با مقیاسپذیری؛ کتاب و فصل دلخواهتان را برای شنیدن انتخاب کنید.
این مجموعه خلاصهٔ کتابهاست؛ نسخهٔ کامل کتاب اصلی نیست.
3 عنوان · 24 قسمت منتشرشده
راه را پیدا کنید
مسیرهای مطالعاتی
قفسهٔ کتابها
از این کتابها شروع کنید
بخش اول: الگوهای معماری در سیستمهای توزیعشده
نمونهٔ رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که وقتی تعداد کاربران یک سرویس آنلاین به طور ناگهانی چند برابر میشود، زیرساخت آن چطور زیر بار دوام میآورد؟ چطور ممکن است درخواستهای میلیونها نفر در سراسر دنیا بدون از کار افتادن سیستم پردازش شوند؟
این موضوع در فصلهای ابتدایی کتاب «الگوهای سیستمهای توزیعشده» نوشتهٔ اونمش جوشی بررسی میشود که به ما نشان میدهد چطور میتوان چالشهای مقیاسپذیری و پایداری را با استفاده از الگوهای معماری مدیریت کرد.
اولین ایده برای مدیریت دادههای حجیم، بخشبندی کردن اطلاعات روی چندین ماشین است. مسئله این است که وقتی دادهها تقسیم میشوند، احتمال خرابی سختافزار افزایش مییابد و گزینههای پیش رو شامل تکثیر دادهها برای پوشش این خرابیهاست؛ تریدآف این روش در پیچیدگی هماهنگی میان گرههاست. برای نمونه، در یک سامانهٔ خردهفروشی بزرگ، اطلاعات سبد خرید مشتریان روی چندین گره مختلف کپی میشود تا با از دست رفتن یکی از آنها، سیستم همچنان پاسخگو بماند.
برای حفظ ثبات و پایداری دادهها روی یک گره، استفاده از Write-Ahead Log (لاگ نوشتن پیشرو) به کار گرفته میشود. مشکل اینجاست که اگر سرور هنگام بهروزرسانی فایلهای متعدد دچار نقص شود، دادهها ناسازگار میشوند. راهحل این است که سیستم پیش از اعمال تغییرات، جزئیات را در یک فایل ترتیبی ثبت کند تا در صورت راهاندازی مجدد، وضعیت قبلی قابل بازیابی باشد. پایگاههای داده از این الگو برای ثبت و مدیریت تراکنشها استفاده میکنند.
مشکل بعدی بهروزرسانیهای رقابتی است؛ وقتی دو کاربر همزمان درخواستهای متفاوتی میفرستند، سیستم به مدیریت هماهنگ نیاز دارد. الگوی Leader and Followers (رهبر و پیروان) این مسئله را حل میکند به این شکل که یک گره به عنوان رهبر تمام تغییرات را میپذیرد و به بقیه منتقل میکند. تریدآف آن تمرکز بار روی یک گره است و برای حل خطاهای ناشی از قطع ارتباط آن، Quorum (حد نصاب) به کار میرود که یعنی یک تصمیم تنها زمانی معتبر است که اکثریت گرهها آن را تأیید کرده باشند.
گاهی رهبر از کار میافتد و گرههای دیگر باید رهبر جدیدی انتخاب کنند که این امر با خطراتی در ناسازگاری لاگها همراه است. علامت سطح بالا یا High-Water Mark روشی است که نشان میدهد آخرین بهروزرسانی تا کدام نقطه توسط اکثریت گرهها تأیید شده است. همچنین برای اینکه ارسال دوبارهٔ درخواست توسط مشتریان باعث اجرای تکراری عملیات نشود، سامانه از Idempotent Receiver (گیرندهٔ خودتوان) استفاده میکند تا با بررسی شناسهٔ یکتای درخواست، پاسخهای قبلی را بدون اجرای مجدد برگرداند.
پیشنهاد من این است که هنگام طراحی سامانههای توزیعشده، از همان ابتدا به چالشهای همگامسازی زمان و خطاهای جزئی شبکه فکر کنید و به جای تلاش برای پیادهسازی دستی الگوریتمهای پیچیده، از الگوهای اثباتشده و ابزارهای استاندارد بهره بگیرید.
امیدوارم این بررسی کوتاه از الگوهای سیستمهای توزیعشده برایتان مفید بوده باشد و در قسمتهای بعدی همراه ما بمانید.
بخش چهارم: همروندی، پایش از راه دور، احراز هویت و مفهوم برنامههای بومی ابری
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که وقتی نرمافزاری را روی بسترهای ابری مستقر میکنید، چطور میتوانید از سلامت و عملکرد آن مطمئن باشید در حالی که دسترسی فیزیکی به سرورهای آن ندارید؟ چه چالشهایی در انتظار تیمهای توسعهای است که میخواهند از روش سنتی فاصله بگیرند و برنامههای خود را مقیاسپذیر کنند؟
این مباحث در بخش چهارم کتاب «فراتر از اپلیکیشن دوازدهعاملی» نوشتهٔ «کوین هافمن» بررسی شدهاند که به ما کمک میکند نگاه عمیقتری به معماریهای مدرن داشته باشیم.
یکی از موضوعات کلیدی، همروندی (Concurrency) است که به ما میگوید برنامههای بومی ابر (Cloud Native) باید به جای بزرگ کردن یک پردازش واحد، مقیاس افقی داشته باشند و بار کاری را بین چند پردازش توزیع کنند. مشکل این است که افزودن منابع به یک پردازش منفرد که مقیاسپودی عمودی خوانده میشود، پاسخگوی نیازهای مدرن نیست و انتخاب مناسب این است که چندین نمونه برنامه ایجاد کنیم و بار کاری را بین آنها تقسیم نماییم؛ مانند استفاده از قوانین مقیاسدهی خودکار که تعداد نمونههای برنامه را بر اساس بار کاری تنظیم میکنند.
مسئلهٔ مهم دیگر، پایش از راه دور (Telemetry) است که به ما امکان میدهد رفتار سیستم را زیر نظر بگیریم. در محیطهای ابری، شما نمیتوانید مثل زمان توسعه محلی به درون برنامه سرک بکشید و نمونههای برنامه ممکن است مدام جابهجا شوند، بنابراین نویسنده پیشنهاد میکند برنامهها را مانند کاوشگرهای فضایی ببینیم که به ابزارهای دقیق سنجش نیاز دارند. در این میان، دستههای مختلفی از دادهها وجود دارند که پایش عملکرد اپلیکیشن (Application Performance Monitoring) یکی از آنهاست و جریانی از رویدادها را برای بررسی عملکرد فراهم میکند؛ در حالی که پایشهای مختص دامنه تجاری به گزارش رویدادهای مرتبط با کسبوکار مانند تعداد اقلام فروختهشده میپردازند.
امنیت، احراز هویت و کنترل دسترسی از دیگر ستونهای حیاتی هستند که نباید به عنوان یک کار پس از اتمام پروژه دیده شوند. مشکل اینجاست که تمرکز روی قابلیتهای اولیه باعث غفلت از امنیت میشود، در حالی که کدهای ما در شبکهها و کانتینر (Container) در حال گردش هستند و توسط کاربران مختلفی درخواست میشوند. بهترین گزینه این است که کنترل دسترسی مبتنی بر نقش (Role-Based Access Control) در تمام نقاط پایانی برنامه پیادهسازی شود تا هر درخواستی اعتبارسنجی گردد و این کار با ابزارهایی مانند استانداردهای مدرن از روز اول توسعه انجام گیرد.
در نهایت، مفهوم بومی ابر به برنامههایی اشاره دارد که برای اجرا روی سکو به عنوان خدمت (Platform as a Service) طراحی شدهاند تا تیمها به جای درگیر شدن با زیرساخت، روی هسته اصلی کسبوکار خود که نویسنده آن را به ایده تمرکز بر یک کار بزرگ تشبیه میکند، تمرکز کنند. چالش همیشگی در این میان، توازن میان اصول آرمانگرایانه و نیازهای واقعگرایانه و بودجه تیمها است که نشان میدهد سازش با واقعیتهای توسعه چقدر اهمیت دارد.
پیشنهاد من به شما این است که هنگام طراحی یا بازنگری پروژهها، ساختار برنامههای خود را با این اصول بسنجید تا بدون وابستگی به سختافزار مشخص، آماده مواجهه با بارهای سنگین ترافیکی باشند.
امیدوارم این مرور کوتاه توانسته باشد دید روشنتری نسبت به معماری مدرن نرمافزار به شما بدهد و در مسیر توسعه برنامههای مقیاسپذیر یاریتان کند.
بخش دوم: پیکربندی، لاگها، قابلیت دور انداختن و سرویسهای پشتیبان
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که چرا گاهی برنامهای که روی کامپیوتر شخصی شما بدون هیچ مشکلی کار میکند، وقتی روی سرور اصلی قرار میگیرد دچار اختلال میشود؟ چه چیزی میان کد برنامهنویسی و محیط اجرای آن پنهان است که میتواند همهچیز را به هم بریزد؟
کتاب «فراتر از اپلیکیشن دوازدهعاملی» نوشتهٔ کوین هافمن به ما نشان میدهد که برای ساخت نرمافزارهای مدرن در دنیای ابر، رعایت اصول سنتی دیگر کافی نیست و باید نگاه تازهای به ساختار برنامههای خود داشته باشیم.
نخستین چالش جدی، مدیریت تنظیمات برنامه است که نویسنده پیشنهاد میکند تنظیمات و اعتبارنامهها را به عنوان مواد فراری در نظر بگیریم که با ترکیب شدن منفجر میشوند. مشکل اینجاست که قرار دادن اطلاعاتی مانند آدرس پایگاه داده یا رمزهای عبور در داخل کدهای برنامه، انعطافپذیری را از بین میبرد و امنیت را به خطر میاندازد. در روش سنتی، توسعهدهندگان از فایلهای تنظیمات محلی استفاده میکنند، اما این کار مانع از داشتن build (ساخت) یکتا میشود. راهحل این است که از Configuration (پیکربندی) خارج از کد استفاده کنیم و مقادیر را به وسیله متغیرهای محیطی یا ابزارهای تخصصی به برنامه برسانیم، به طوری که اگر روزی سورسکد روی گیتهاب قرار گرفت، هیچ اطلاعات حساسی افشا نشود.
موضوع مهم بعدی نحوهٔ مدیریت اطلاعات مربوط به Credentials (اعتبارنامه) است که شامل نامهای کاربری، رمزهای عبور و کلیدهای دسترسی به سرویسهای شخص ثالث میشود. اگر این موارد حساس در فایلهای منابع همراه برنامه بستهبندی شوند، همچنان بخشی از سورسکد محسوب میشوند و قانون جداسازی را نقض میکنند. در روش اصولی، این اطلاعات هرگز نباید در مخزن کد وجود داشته باشند و باید در زمان اجرا از محیط ابری دریافت شوند تا برنامهها کاملاً مستقل بمانند.
در بخش ثبت رویدادها، چالش اصلی کنترل مقصد و نحوهٔ ذخیرهسازی Logs (لاگها) است. برنامههای سنتی عادت دارند فایلهای لاگ را روی دیسک ذخیره کنند و خودشان مسئولیت چرخش و مدیریت حجم آنها را به عهده بگیرند، اما در زیرساختهای ابری، سیستمعامل موقتی است. راهحل بومی ابر این است که برنامه تمام خروجیهای خود را صرفاً روی stdout و stderr بنویسد و جمعآوری، پردازش و ذخیرهسازی آنها را به ابزارهای جانبی واگذار کند. این کار باعث سادهتر شدن کد و تمرکز بیشتر روی ارزش تجاری اصلی میشود.
چالش چهارم مربوط به Disposability (قابلیت دور انداختن) است که به سرعتِ آغاز به کار و توقف نرمافزار مربوط میشود. اگر برنامهای برای بالا آمدن به چند دقیقه زمان نیاز داشته باشد، در زمان اوج ترافیک یا مقیاسپذیری خودکار دچار مشکل میشود و درخواستهای کاربران را از دست میدهد. ترفند کار در این است که فعالیتهای طولانیمدت مانند بارگذاری کش را از مرحلهٔ راهاندازی اولیه خارج کنیم تا نمونههای جدید برنامه در کسری از ثانیه آماده سرویسدهی یا متوقف شدن باشند.
در نهایت، برنامهها برای انجام کارهای خود به Backing Service (سرویس پشتیبان) نیاز دارند که شامل پایگاههای داده، سیستمهای پیامرسان و فضاهای ذخیرهسازی میشود. مشکل اینجاست که اتصال مستقیم و سختکدشده به این سرویسها، برنامه را در برابر خرابی آنها آسیبپذیر میکند. راهحل درست این است که سرویسهای پشتیبان به عنوان منابع متصل در نظر گرفته شوند تا اگر پایگاه دادهای از کار افتاد، مدیر سیستم بتواند بدون نیاز به تغییر کد برنامه، اتصال آن را به یک نمونه سالم دیگر تغییر دهد.
پیشنهاد شخصی من این است که هنگام توسعه برنامهها، همیشه فرض کنید سرور شما هر لحظه ممکن است خاموش شود و از بین برود؛ این فرض ساده کمک میکند تا وابستگیهای پنهان را حذف کنید و نرمافزاری انعطافپذیر بسازید.
امیدوارم از این بررسی کوتاه لذت برده باشید و این نکات در طراحی سیستمهای بعدی به کار شما بیاید.
بخش اول: مبانی: پایگاه کد، API-محور، وابستگیها، طراحی/ساخت/انتشار/اجرا
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که چرا بعضی از نرمافزارها به محض قرار گرفتن روی ابر به مشکل برمیخورند، در حالی که بعضی دیگر به راحتی مقیاسپذیر میشوند و رشد میکنند؟ تفاوت اصلی در نحوه پیریزی و رعایت استانداردهایی است که به سیستم اجازه میدهد در دنیای مدرن ابری دوام بیاورد و عملکرد روانی داشته باشد.
در کتاب «فراتر از اپلیکیشن دوازدهعاملی» نوشته «کوین هافمن»، اصول پایهای و ساختاری بررسی میشود که برای ساخت اپلیکیشن دوازدهعاملی (Twelve-Factor App) به عنوان مجموعهای از اصول برای ساخت اپلیکیشنهای ابری قابلحمل و مقیاسپذیر، به شدت به آنها نیاز داریم.
اولین چالش در این زمینه، بینظمی در ساختار کد و داشتن مخازن متعدد برای یک اپلیکیشن واحد است. برخی تیمها کدهای یک محصول را در چندین جای مختلف پخش میکنند که این کار خودکارسازی ساخت و انتشار را تقریباً غیرممکن میسازد. راه حل این است که کل سیستم را به عنوان یک مخزن کد واحد در کنترل نسخه ثبت کنیم و اگر چند بخش مجزا داریم، آنها را به عنوان میکروسرویسهای مستقل از هم جدا کنیم. به عنوان نمونه، داشتن یک اپلیکیشن یکپارچه بزرگ با اسکریپتهای اجرایی متعدد نشاندهنده به هم ریختگی تیمهاست که مستقیماً در معماری نرمافزار هم منعکس میشود.
چالش بعدی در محیطهای ابری پیچیده، نامشخص بودن قراردادهای ارتباطی بین تیمهای مختلف است که باعث خطاهای یکپارچهسازی میشود. انتخاب بهینه این است که رویکرد API First در پیش بگیریم تا هر بخش از سیستم قبل از پیادهسازی کامل، قرارداد مشخصی برای تعامل داشته باشد. این رویکرد همکاری و تست را ساده میکند، اما نیازمند انضباط بالایی در مستندسازی است. برای مثال، استفاده از فرمتهایی برای تعریف این قراردادها به تیمها اجازه میدهد بدون وابستگی به کدهای داخلی، همزمان روی بخشهای مختلف کار کنند.
مدیریت وابستگیها نیز یکی از نقاط عطف در توسعه نرمافزار است که نباید به سرورهای سنتی و وابستگیهای پنهان سیستم تکیه کند. در روش سنتی، برنامهها متکی به پکیجهای نصبشده روی سرور میزبان هستند که خطاهای عجیبی را به دنبال دارد، اما راهکار مدرن این است که وابستگیها صریحاً اعلام و ایزوله شوند. کتاب اشاره میکند که ابزارهای مدیریت وابستگی، تمام کتابخانههای مورد نیاز را مستقیماً درون پکیج اجرایی اپلیکیشن بستهبندی میکنند تا اجرا در هر محیطی کاملاً قابل تکرار باشد.
چهارمین ایده کلیدی، جداسازی دقیق مرحلههای طراحی، ساخت، انتشار و اجرا در چرخه حیات نرمافزار است. خیلی از تیمها با مشکل مواجه میشوند چون تفاوت محیط توسعه و تولید را نادیده میگیرند و تستها را روی نسخههای متغیر انجام میدهند. راهکار اصولی این است که یک کد واحد از طریق خط لوله (Pipeline) که زنجیرهٔ خودکار ساخت، آزمون و انتشار است، به یک بسته اجرایی تغییرناپذیر تبدیل شود. برای نمونه، فایلی که در محیط آزمون تأیید میشود، دقیقاً همان فایلی است که بدون هیچگونه دستکاری دستی به محیط تولید ارسال میگردد.
پیشنهاد عملی من این است که پیش از افزودن هرگونه پیچیدگی به معماری سیستم، ابتدا مخزن کد و وابستگیهای آن را کاملاً پاکسازی کنید و مطمئن شوید که فرآیند ساخت و انتشار شما به صورت خودکار انجام میشود.
امیدوارم این مرور کوتاه به شما کمک کرده باشد نگاه دقیقتری به ساختار نرمافزارهای ابری داشته باشید.
بخش سوم: همسانی محیطها، فرایندهای مدیریتی، اتصال پورت و فرایندهای بیحالت
رایگانتوضیح و منابع این قسمت
آیا تا به حال شده که برنامهای در محیط توسعه یا تست شما بینقص کار کند، اما به محض رسیدن به محیط عملیاتی، با مشکلات غیرمنتظرهای روبرو شوید؟ یا شاید در سازمانی کار کردهاید که انتشار یک تغییر کوچک، هفتهها یا ماهها طول میکشد؟ این تجربهها چقدر برایتان آشناست؟
امروز در ادامه بحث از کتاب «فراتر از اپلیکیشن دوازدهعاملی» نوشته «کوین هافمن»، به سراغ چالشهای «همسانی محیطها» و چند مفهوم کلیدی دیگر در طراحی برنامههای بومی ابر میرویم که به ما کمک میکنند تا به عملکرد اپلیکیشنهایمان در هر محیطی اعتماد بیشتری داشته باشیم.
یکی از مهمترین عواملی که شکاف بین محیطها را ایجاد میکند، «زمان» است. در بسیاری از سازمانها، از لحظهای که یک توسعهدهنده کدی را ثبت میکند تا زمانی که آن کد به تولید میرسد، ممکن است هفتهها یا ماهها طول بکشد. این فاصله زمانی طولانی باعث میشود که افراد تغییرات را فراموش کنند و حتی توسعهدهندگان هم از یاد ببرند که کدشان در زمان ثبت چگونه بوده است. راهکار مدرن این است که این فاصله زمانی را از هفتهها و ماهها به دقیقه یا ساعت کاهش دهیم. این کار با استفاده از یک «خط لوله (Pipeline)» استقرار مداوم مناسب انجام میشود که در آن، تغییرات پس از اجرای تستهای خودکار در محیطهای مختلف، به صورت خودکار به تولید منتقل میشوند. شاید این ایده در ابتدا ترسناک به نظر برسد، اما وقتی توسعهدهندگان عادت کنند که کدشان همان روز به تولید میرسد، نظم و کیفیت کد به شدت افزایش مییابد. عامل انسانی هم مهم است: وقتی استقرار به دست افراد انجام میشود، هر فرد ممکن است مراحل را متفاوت اجرا کند؛ خودکارسازی این تفاوتها را کم میکند و هر کامیت را به یک کاندید انتشار تبدیل میکند.
عامل دیگری که به همسانی محیطها آسیب میزند، «منابع» هستند. وقتی برای راهاندازی سریع چیزی عجله داریم، اغلب سازشهایی میکنیم. مثلاً، ممکن است اپلیکیشن ما در تولید به یک پایگاه داده قوی مثل اوراکل نیاز داشته باشد، اما برای توسعه محلی، از یک پایگاه داده در حافظه استفاده کنیم. هر بار که چنین سازشی میکنیم، شکاف بین محیطهای توسعه و تولید را افزایش میدهیم و هر چه این شکاف بیشتر باشد، پیشبینیپذیری رفتار اپلیکیشن کمتر میشود و قابلیت اطمینان آن کاهش مییابد. امروزه ابزارهای زیادی در دسترس توسعهدهندگان است. میتوان از همان نوع «سرویس پشتیبان (Backing service)» در همه محیطها استفاده کرد، یا ابزارهایی مانند داکر (Docker) به ایجاد محیطهای شبیه تولید روی ایستگاههای کاری توسعهدهندگان کمک میکنند. هر تصمیمی که شکاف عملکردی بین محیطهای استقرار شما را افزایش میدهد، باید مورد سؤال قرار گیرد و باید در برابر وسوسه تفاوت قائل شدن بین محیطها مقاومت کرد.
حالا به سراغ «فرایندهای مدیریتی (Admin process)» میرویم. این فرایندها کارهای یکباره یا نگهداری هستند که جدا از فرایند اصلی اپلیکیشن اجرا میشوند. توصیه اصلی اپلیکیشن دوازدهعاملی این بود که این کارها به عنوان فرایندهای یکباره اجرا شوند، اما کوین هافمن معتقد است که این توصیه میتواند گمراهکننده باشد. مشکل اینجاست که استفاده از زمانبندهای خارجی مثل کرون (Cron) یا حتی اجرای اسکریپتهای تعاملی، در محیط ابری میتواند منجر به هرج و مرج و خطاهای دادهای شود، به خصوص وقتی چندین نمونه از اپلیکیشن در حال اجرا هستند. مثلاً، اگر یک کار دستهای شبانه را با یک زمانبند داخلی در هر نمونه از اپلیکیشن اجرا کنید، ممکن است این کار چندین بار اجرا شود و دادهها را خراب یا تکراری کند.
برای حل این مشکل، راهحلهای بهتری وجود دارد. یکی از جذابترین آنها، این است که قابلیتهای موقت و مدیریتی را از طریق یک «پایانه REST (REST endpoint)» در اپلیکیشن خود ارائه دهید. این کار به شما اجازه میدهد که این قابلیتها را به صورت کنترلشده و امن فراخوانی کنید. راهکار دیگر این است که کد مربوط به کارهای دستهای را از اپلیکیشن اصلی جدا کرده و به یک «میکروسرویس (Microservice)» جداگانه تبدیل کنید. این میکروسرویس میتواند به صورت مستقل مقیاسپذیر باشد و از مزایای دیگر ابر نیز بهرهمند شود. حتی میتوانید از توابع بومی ابر مانند AWS Lambda استفاده کنید که فقط در صورت نیاز فراخوانی میشوند و نیازی به سرورهای همیشه روشن ندارند.
در نهایت، به «اتصال پورت (Port binding)» میرسیم. اپلیکیشنهای بومی ابر باید سرویسهای خود را از طریق اتصال پورت ارائه دهند. در محیطهای غیرابری، به خصوص در سازمانهای بزرگ، معمولاً چندین اپلیکیشن در یک «کانتینر (Container)» سرور وب میزبانی میشوند و با شماره پورتهای مختلف از هم جدا میشوند. این کار مدیریت پورتها را پیچیده میکند. اما در محیط ابری، ارائهدهنده خدمات ابری مسئولیت مدیریت پورتها، مسیریابی، مقیاسگذاری و دسترسی بالا را بر عهده دارد.
و در نهایت، فصل دوازدهم به «فرایندهای بیحالت (Stateless Processes)» میپردازد. یک فرایند بیحالت هیچ فرضی دربارهٔ محتوای حافظه پیش یا پس از پردازش درخواست ندارد؛ میتواند در میانهٔ کار وضعیت موقت بسازد، اما پس از پاسخ دادن، آن وضعیت باید از بین برود. هر وضعیت ماندگار باید به یک سرویس پشتیبان مثل پایگاه داده منتقل شود. به همین دلیل، یک میکروسرویس مدیریت کاربران بیحالت است و فهرست کاربران در پایگاه داده نگهداری میشود. هافمن الگوی «هیچچیز مشترک (Share-Nothing)» را هم توصیه میکند: هر دادهٔ مشترک بین فرایندها یک نقطهٔ شکست است، و چون فرایندها در ابر هر لحظه ممکن است حذف یا جابهجا شوند، اشتراک وضعیت میتواند باعث شکست زنجیرهای شود. حتی فایلسیستم هم سرویس پشتیبان محسوب نمیشود.
فصل دوازدهم: آینده سامانههای دادهای
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که چرا وقتی حجم دادهها زیاد میشود، هیچ پایگاه دادهٔ واحدی نمیتواند تمام نیازهای ما را به تنهایی برآورده کند و مجبور میشویم چندین ابزار مختلف را به زور به هم وصل کنیم؟ آیا راه بهتری برای فرار از این کلاف سردرگم ابزارهای ناسازگار وجود دارد؟
این مبحث از کتاب «طراحی برنامههای دادهگرا» نوشتهٔ «مارتین کلپمن» به بررسی آیندهٔ سیستمهای دادهگرا و الگوهای نوآورانه در معماری داده میپردازد.
اولین ایدهٔ کلیدی دربارهٔ چالش یکپارچهسازی دادهها است. در برنامههای پیچیده، دادهها به شکلهای گوناگونی استفاده میشوند و تلاش برای انجام دادن همهٔ کارها در یک تکه نرمافزار به شکست میانجامد. راهکار مرسوم، چسباندن چندین ابزار مختلف مثل پایگاه داده و موتور جستجو به همدیگر است، اما اگر مشتریان مستقیماً روی هر دو سیستم بنویسند، بهدلیل تفاوت ترتیب پردازش، ناسازگار میشوند. نویسنده پیشنهاد میکند که تمام ورودیها از یک مسیر واحد عبور کنند تا بقیهٔ نماها بر اساس همان ترتیب ساخته شوند.
دومین ایدهٔ کلیدی مقایسهٔ رویکرد دادهٔ مشتقشده (Derived Data) با تراکنشهای توزیعشده است. دادهٔ مشتقشده یعنی مجموعهای که از دادههای دیگر طی یک فرایند قابل تکرار به دست میآید تا خواندن را سریعتر کند. تراکنشهای توزیعشده با استفاده از قفلها تلاش میکنند سازگاری ایجاد کنند، اما ویژگیهای ضعیفی در تحمل خطا دارند. در مقابل، سیستمهای مبتنی بر لاگ با استفاده از تغییر دادهٔ ضبطشده (Change Data Capture) که تکنیک ثبت تغییرات پایگاه داده برای اعمال به سایر سیستمهاست، دادهها را به شکل غیرهمزمان بهروزرسانی میکنند که این روش جداسازی مؤلفهها را بهبود میبخشد.
سومین ایدهٔ کلیدی به محدودیت ترتیب کلی و ضرورت حفظ علیت مربوط میشود. ساختن یک لاگ کاملاً مرتبشده نیازمند عبور تمام رویدادها از یک گره رهبر است که با مقیاسبندی سیستم در چند پارتیشن (Partition) که به معنی بخشبندی دادهها روی گرههای مختلف است، دچار مشکل میشود. در غیاب ترتیب کلی، وابستگیهای علی در سیستمهای توزیعشده ممکن است گم شوند؛ مثلاً وقتی یک کاربر رابطهٔ دوستی را قطع میکند و بعد پیامی میفرستد، اگر رویدادها به هم ریخته پردازش شوند، پیام ممکن است قبل از لغو دوستی به دست طرف مقابل برسد. برای حل این مشکل، استفاده از شناسههای یکتا راهگشا است.
چهارمین ایدهٔ کلیدی اتحاد پردازش دستهای و جریانی است. پردازش دستهای (Batch Processing) روی دادههای محدود و پایانپذیر کار میکند و توابع خالص و قطعی تولید میکند، در حالی که پردازش جریانی (Stream Processing) روی دادههای بینهایت و پیوسته عمل میکند. در گذشته معماری لامبدا تلاش میکرد هر دو سیستم را موازی نگه دارد، اما رویکردهای مدرن به دنبال یکی کردن آنها در یک موتور واحد هستند که بتواند رویدادهای تاریخی را هم بازپخش کند و اجرا را به صورت دقیقاً یکبار (Exactly-Once) تضمین نماید؛ یعنی طوری که اثر نهایی عملیات حتی با وجود خطا دقیقاً یک بار اعمال شود.
پنجمین ایدهٔ کلیدی مفهوم تکهتکه کردن پایگاههای داده (Unbundled Databases) است. درست همانطور که سیستمعاملهای یونیکس از ابزارهای کوچک و تخصصی تشکیل شدهاند که با لولهها (Pipes) به هم وصل میشوند، معماری دادهها نیز به سمت ترکیب ابزارهای تخصصی حرکت میکند. در این معماری، خواندن از طریق پایگاههای دادهٔ فدرال (Federated Databases) که رابط پرسوجوی واحدی برای موتورهای ذخیرهسازی مختلف فراهم میکنند، یکپارچه میشود و نوشتن با استفاده از لاگ رویدادها همزمان میگردد تا انعطاف سیستم افزایش یابد.
پیشنهاد عملی من به شما این است که وقتی برنامههای مقیاسپذیر میسازید، بلافاصله به سراغ تراکنشهای توزیعشدهٔ سنگین نروید، بلکه طراحی مبتنی بر رویداد و دادههای مشتقشده را با شناسههای یکتا برای پیگیری درخواستها امتحان کنید تا سیستم شما در برابر خطاها انعطافپذیرتر بماند.
این فصل به ما نشان میدهد که آیندهٔ سیستمهای دادهگرا در گرو گذر از انبارههای صلب و حرکت به سوی جریانهای انعطافپذیر داده است.
فصل یازدهم: پردازش جریانها
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردید که چطور اپلیکیشنهای امروزی تراکنشها، پیامها و رفتارهای ما را در همان لحظه و بدون معطلی تحلیل میکنند، در حالی که حجم دادهها هر لحظه بیشتر میشود؟ این مسئله به یکی از بنیادیترین چالشهای مهندسی نرمافزار تبدیل شده است که سیستمهای توزیعشده با آن دستوپنجه نرم میکنند. در کتاب «طراحی برنامههای دادهگرا» نوشتهٔ «مارتین کلپمن»، فصل یازدهم به همین موضوع اختصاص دارد و نشان میدهد چطور میتوانیم دادهها را به شکل جریانهای بینهایت پردازش کنیم.
برای درک بهتر پردازش جریانی (Stream processing)، یعنی پردازش پیوستهٔ رویدادها همزمان با تولید آنها، باید بدانیم برخلاف پردازش دستهای (Batch processing) که با فایلهای محدود و مشخص سر و کار دارد، دنیای واقعی پر از دادههای پایانناپذیر است. وقتی کاربران مدام در حال تولید داده هستند، تقسیم کردن مصنوعی دادهها به بازههای روزانه یا ساعتی باعث تاخیر زیادی میشود. در اینجا سیستمهای پیامرسان با چالش مدیریت مصرفکنندههای کند روبهرو میشوند؛ اگر تولیدکننده سریعتر از مصرفکننده پیام بفرستد، سیستم باید بین حذف پیامها، صفبندی در حافظه، یا اعمال کنترل جریان یکی را انتخاب کند.
یکی از روشهای کلاسیک برای تبادل پیام، استفاده از کارگزار پیام (Message broker) است که مانند یک پایگاه دادهٔ بهینهشده برای جریان پیامها عمل میکند. کارگزارها معمولاً پس از تحویل موفق پیام به مصرفکننده، آن را پاک میکنند که این رویکرد برای ذخیرهسازی بلندمدت مناسب نیست. در عوض، رویکرد مبتنی بر لاگ (Log-based message broker) مانند آپاچی کافکا، رویدادها را به صورت دنبالهای الحاقی روی دیسک ذخیره میکند تا چندین مصرفکننده بتوانند مستقل از هم، دادهها را بخوانند بدون اینکه نگران پاک شدن پیامها باشند.
ارتباط عمیقی بین پایگاههای داده و جریانها وجود دارد که نمونهٔ بارز آن تغییر دادههاست. مکانیزم تغییر دادهها یا سیدیسی (Change Data Capture) راهی است که تمام تغییرات نوشتهشده در یک پایگاه داده را به شکل یک جریان استخراج میکند تا سیستمهای دیگر مانند ایندکسهای جستجو یا کشها همیشه بهروز بمانند. این الگو کمک میکند تا از خطاهای رایج ناشی از نوشتن همزمان در چند سیستم جلوگیری شود و یک پایگاه داده به عنوان منبع حقیقت عمل کند.
در بحث پردازش جریانها، زمان یکی از پیچیدهترین ابعاد است که باید بین زمان رویداد و زمان پردازش تفاوت قائل شویم. وقتی رویدادها به دلیل تاخیر شبکه یا قطعی اینترنت دیرتر میرسند، سیستم باید با این رویدادهای جا مانده کنار بیاید و پنجرههای زمانی را مدیریت کند. انواع پنجرهها مانند پنجرهٔ غلتان برای تقسیم زمان به بازههای ثابت بدون همپوشانی، به ما کمک میکنند تا تحلیلهای آماری دقیقی روی دادههای در حال حرکت انجام دهیم.
پیشنهاد عملی من این است که وقتی معماری سیستمهای خود را طراحی میکنید، به جای تکیه صرف بر وضعیتهای تغییرپذیر پایگاه داده، به رویدادها به چشم حقایق تغییرناپذیر نگاه کنید. ثبت تغییرات به صورت لاگهای الحاقی به شما اجازه میدهد در صورت بروز خطا، دادهها را دوباره پخش کنید و نماهای خواندنی جدید بسازید بدون اینکه نگران آسیب دیدن دادههای اصلی باشید.
امیدوارم این روایت از کتاب طراحی برنامههای دادهگرا برای شما مفید بوده باشد و دید بهتری نسبت به مدیریت جریان دادهها در سیستمهای مقیاسپذیر پیدا کرده باشید.
فصل دهم: پردازش دستهای
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که شرکتهای بزرگ چطور حجم عظیمی از دادههای انباشتهشده را پردازش میکنند، وقتی هیچ کاربری پشت خط ننشسته و منتظر پاسخ آنی نیست؟ چطور میتوان میلیاردها رکورد گزارش سرور را بدون اخلال در سرویسهای آنلاین تحلیل کرد و از دل آنها گزارشهای دقیق بیرون کشید؟
این مبحث کلیدی از کتاب «طراحی برنامههای دادهگرا» نوشتهٔ «مارتین کلپمن» است که به ما نشان میدهد چگونه پردازش دادهها فراتر از درخواست و پاسخهای آنلاین معنا پیدا میکند.
اولین ایدهٔ بنیادین، استفاده از رویکرد پردازش دستهای (batch processing) پردازش آفلاین است. در این سیستمها، ورودی حجم ثابتی از داده است که پردازش آن ممکن است دقایق یا روزها طول بکشد و هیچ کاربری منتظر نتیجهٔ فوری نیست. گزینهٔ جایگزین، سیستمهای آنلاین هستند که برای پاسخ سریع به کاربر طراحی شدهاند؛ اما تفاوت بزرگ پردازش دستهای در این است که به جای بهینهسازی زمان پاسخ، روی توان عملیاتی متمرکز میشود تا بیشترین حجم داده را در کمترین زمان ممکن پردازش کند. برای نمونه، تحلیل لاگهای دسترسی وبسایت برای پیدا کردن پربازدیدترین صفحات در پایان روز، نمونهای کلاسیک از این کار است.
ایدهٔ دوم، الهام گرفتن از فلسفهٔ ابزارهای یونیکس در مقیاس توزیعشده است. در سیستمهای سنتی، برنامهها ممکن است در حافظه کار کنند، اما ابزارهایی مانند دستورات مرتبسازی و خطوط لوله به کمک ما میآیند تا دادههای بزرگتر از حافظه اصلی را با کمک دیسک و الگوهای مرتبسازی و ادغام مدیریت کنند. مسئله این است که وقتی حجم دادهها از رم سیستم بیشتر میشود، روشهای متکی بر جدول هش داخلی شکست میخورند. راهحل این است که دادهها را قطعهقطعه مرتب کرده و روی دیسک بنویسیم و سپس با ادغامسازی مرتبسازیشده آنها را ترکیب کنیم، دقیقا مشابه همان کاری که ابزار مرتبسازی لینوکس برای مدیریت دادههای عظیم انجام میدهد.
ایدهٔ سوم، معماری نگهداری دادهها روی یک سامانهٔ فایل توزیعشده است. برای پردازش در مقیاس هزاران ماشین، الگوریتمهایی مانند مپردیوز (MapReduce) معرفی شدند که دادهها را روی فایلسیستم توزیعشدهٔ هدوپ HDFS ذخیره میکنند. مسئله اصلی این است که چطور محاسبات را به جایی که داده قرار دارد ببریم تا از بار شبکه کاسته شود. گزینهها شامل استفاده از ذخیرهسازی متمرکز یا معماری بدون اشتراک است. معامله یا تریدآف در اینجا این است که ساختار بدون اشتراک نیازی به سختسافزارهای گرانقیمت شبکه ندارد و محاسبات مستقیماً روی گرههای حاوی داده اجرا میشود. نمونهٔ عینی آن اجرای وظایف مپ و ردیوز روی همان ماشینهایی است که بلوکهای فایل ورودی را ذخیره کردهاند.
ایدهٔ چهارم به چگونگی اجرای پیوستگیها عمل جوین در پردازشهای دستهای مربوط میشود. وقتی بخواهیم لاگ فعالیت کاربران را با پایگاه دادهٔ پروفایل آنها ترکیب کنیم، پرسوجوی تکتک رکوردها از پایگاه دادهٔ از راه دور عملکرد را به شدت پایین میآورد. گزینهٔ بهتر، کپی کردن پایگاه داده در همان فایلسیستم توزیعشده و استفاده از روش جوین کاهشیافته و مرتبسازی sort-merge join است. در این روش، کلیدها توسط مپردیوز مرتب و کنار هم قرار میگیرند تا ردیوز بتواند دادههای مرتبط را بدون درخواستهای شبکه و با کارایی بالا ترکیب کند.
پیشنهاد من این است که وقتی با حجم دادههای تحلیلی کلان سر و کار دارید، به جای وابستگی به تراکنشهای آنلاین و پرسوجوهای منفرد، از الگوهای پردازش دستهای با دادههای تغییرناپذیر استفاده کنید تا قابلیت اطمینان و خطایابی سیستم شما بهشدت افزایش یابد.
در نهایت، این معماریهای توزیعشده به ما یاد میدهند که جداسازی منطق برنامهنویسی از زیرساخت سیمکشی دادهها، کلید ساخت سیستمهای دادهگرا و مقیاسپذیر در دنیاست.
فصل نهم: سازگاری و اجماع
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که اگر در یک پایگاه داده توزیعشده، دو نفر همزمان به اطلاعات متفاوتی نگاه کنند، سیستم چطور باید تصمیم بگیرد کدام نسخه درست است؟ وقتی شبکه با تاخیر مواجه میشود یا گرهها از کار میافتند، هماهنگ نگه داشتن تمام بخشهای یک سیستم بزرگ به یکی از پیچیدهترین چالشهای مهندسی نرمافزار تبدیل میشود.
در کتاب «طراحی برنامههای دادهگرا» نوشتهٔ «مارتین کلپمن»، فصل نهم به بررسی دقیق مفهوم سازگاری (consistency) و اجماع (consensus) میپردازد تا نشان دهد چطور میتوانیم سیستمهای توزیعشدهای بسازیم که در برابر خطاها مقاومت کنند. درک این مفاهیم به ما کمک میکند تا حد و مرز تضمینهایی را که پایگاههای داده میتوانند به ما بدهند به خوبی بشناسیم.
یکی از قویترین مدلهای سازگاری، خطیسازیپذیری (linearizability) است؛ ویژگیای که باعث میشود سیستم طوری رفتار کند که انگار فقط یک نسخه از داده وجود دارد و تمام عملیات بهصورت اتمی روی آن انجام میشوند. مشکل اینجاست که تضمین تازگی دادهها در خطیسازیپذیری هزینه دارد و سیستم را در برابر مشکلات شبکه آسیبپذیر میکند. مثلاً اگر وبسایتی نتواند این تضمین را بدهد، ممکن است کاربری امتیاز مسابقه فوتبال را زودتر از کاربر دیگر روی صفحه ببیند که این موضوع تجربه گیجکنندهای ایجاد میکند.
موضوع مهم دیگر، ترتیب رویدادها و علیت (causality) در سیستمهای توزیعشده است. علت همیشه قبل از معلول اتفاق میافتد و حفظ این رابطه برای جلوگیری از تناقضهایی مثل دیدن پاسخ یک سوال قبل از خود سوال حیاتی است. ترتیبسنجی کامل با استفاده از روشهایی مانند تمبرهای لامپورت (Lamport timestamps) که به هر رویداد یک جفت شمارنده و شناسهٔ گره اختصاص میدهند، کمک میکند تا رویدادها بهگونهای مرتب شوند که با روابط علّی سازگار بمانند.
وقتی صحبت از نوشتن دادهها در میان چند گره میشود، سیستمهای پایگاه داده گاهی به توافق جمعی نیاز دارند که به آن مسئلهٔ تعهد اتمی (atomic commit) میگویند. پروتکل تعهد دو مرحلهای (two-phase commit) تلاشی برای حل این مشکل است که در آن هماهنگکننده ابتدا از همه میپرسد که آیا آمادهٔ ذخیره هستند یا نه، و بعد از گرفتن پاسخ مثبت، فرمان نهایی را صادر میکند. با این حال، اگر هماهنگکننده در میان راه سقوط کند، گرهها در حالت نامطمئن معلق میمانند و قفلها آزاد نمیشوند.
برای حل اساسی این چالشها، سیستمهای اجماع (consensus) وارد میدان میشوند تا به گرهها کمک کنند روی یک تصمیم واحد به توافق برسند. ابزارهایی مثل زوکیپر (Apache ZooKeeper) از الگوریتمهای اجماع برای پیادهسازی پخش ترتیب کامل (total order broadcast) استفاده میکنند؛ به این معنا که همهٔ پیامها دقیقاً با یک ترتیب مشخص به تمام گرهها میرسند تا تکرار دادهها بدون تناقض انجام شود.
پیشنهاد من این است که پیش از پیادهسازی معماریهای پیچیدهٔ توزیعشده، بهدقت بررسی کنید که آیا برنامهتان واقعاً به خطیسازیپذیری سختگیرانه نیاز دارد یا سازگاریهای ضعیفتر و سریعتر برای نیازهای محصول شما کفایت میکنند. انتخاب درست مدل سازگاری میتواند معماری نرمافزار شما را از پیچیدگیهای غیرضروری نجات دهد.
امیدوارم این مرور کوتاه به روشنتر شدن ابعاد سازگاری و اجماع در سیستمهای توزیعشده کمک کرده باشد؛ منتظر نظرات شما در بخش گفتگو هستیم.
فصل هشتم: مشکلات سیستمهای توزیعشده
رایگانتوضیح و منابع این قسمت
وقتی کامپیوتر شخصی شما کاملاً سالم کار میکند یا ناگهان صفحهٔ آبی مرگ را نشان میدهد، تکلیف روشن است؛ اما آیا میدانید چرا ساختن نرمافزار روی چند کامپیوتر متصل به شبکه دنیایی کاملاً متفاوت و پر از ابهام است؟
این موضوع در فصل هشتم کتاب «طراحی برنامههای دادهگرا» اثر «مارتین کلپمن» بررسی شده و نشان میدهد چرا فرض اینکه همهچیز ممکن است خراب شود در سیستمهای توزیعشده یک ضرورت است.
اولین چالش جدی، غیرقابلاعتماد بودن شبکههاست. در سیستمهای توزیعشده، ماشینها از طریق شبکه با هم ارتباط دارند و شبکه تضمینی نمیدهد که پیامی برسد یا چه زمانی برسد. وقتی پیامی میفرستید و پاسخی نمیگیرید، نمیتوانید تشخیص دهید که آیا درخواست شما گم شده، گره مقابل از کار افتاده یا پاسخ در راه برگشت تأخیر داشته است. راه حل معمول استفاده از زمانسنج timeout است؛ اما تعیین یک مقدار ثابت برای آن دشوار است زیرا تأخیرهای شبکه به دلیل ازدحام صفها متغیر هستند.
دومین مشکل به ساعتهای سیستم مربوط میشود. ماشینهای مختلف ساعتهای سختافزاری مستقل دارند که سرعتشان با هم فرق میکند و به مرور زمان دچار انحراف میشود. حتی با پروتکل همگامسازی زمان Network Time Protocol (NTP)، ساعتها ممکن است ناگهان بپرند یا عقب بمانند. استفاده از ساعتهای دیوارنگاره time-of-day clock برای مرتبسازی تراکنشها خطرناک است، زیرا اگر ساعت یک گره جلوتر باشد، ممکن است نوشتههای جدیدتر را به اشتباه حذف کند.
سومین مسئله، وقفههای پردازش process pauses است. یک گره ممکن است وسط اجرای کار به خاطر مکث زبالهروب حافظه garbage collection متوقف شود، بدون اینکه خودش بفهمد. در این فاصله، سایر گرهها فکر میکنند او مرده و گره دیگری را به جایش انتخاب میکنند؛ اما وقتی گره متوقفشده بیدار میشود، همچنان فکر میکند رهبر است و دست به عملیات خطرناک میزند.
چهارمین ایده به حقیقت و اجماع مربوط میشود. در یک سیستم توزیعشده، هیچ گرهی نمیتواند به تنهایی همهچیز را درست تشخیص دهد، چون شبکه و زمان قابلاعتماد نیستند. به همین دلیل تصمیمگیریها به رأیگیری اکثریت quorum نیاز دارد تا مشخص شود کدام گره واقعاً زنده یا رهبر است. برای جلوگیری از خرابکاری گرههایی که به اشتباه فکر میکنند هنوز رهبرند، میتوان از توکنهای حصارکشی fencing tokens استفاده کرد تا سیستم ذخیرهسازی درخواستهای قدیمی با توکن کهنه را رد کند.
پیشنهاد من به عنوان طراح سیستم این است که هیچگاه به شبکه پایدار، ساعتهای دقیق و اجرای بدون وقفه پردازندهها اعتماد نکنید و همیشه فرض کنید بدترین اتفاقات ممکن است رخ دهند.
امیدواریم این بررسی برای شما مفید بوده باشد. تا اپیزود بعدی، پیروز و پایدار باشید.
فصل هفتم: تراکنشها
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که وقتی چند نفر همزمان روی یک حساب بانکی یا یک موجودی کار میکنند، چطور پایگاه داده جلوی قاطی شدن دادهها را میگیرد؟ واقعیت این است که در دنیای سیستمهای داده، شبکهها قطع میشوند، سختافزارها از کار میافتند و درخواستهای همزمان میتوانند کل سیستم را به هم بریزند.
در کتاب «طراحی برنامههای دادهگرا» اثر «مارتین کلپمن»، فصل هفتم به بحث مهم تراکنشها (Transactions) اختصاص دارد و بررسی میکند که چطور این مکانیسم، برنامهنویسی را با پنهان کردن خطاهای جزئی و مسائل همزمانی سادهتر میکند.
برای درک بهتر رفتار تراکنشها، مفهوم ACID مخفف اتمی بودن، سازگاری، ایزولاسیون و دوام مطرح میشود که البته تعاریف تجاری آن گاهی مبهم است. ویژگی Atomicity (اتمی بودن) تضمین میکند که اگر خطایی وسط چند عملیات رخ دهد، کل کار لغو میشود و تغییرات ثبتشده برمیگردند تا برنامهنویس نگران خطاهای جزئی نباشد. مثال بارز آن انتقال وجه است که اگر مرحلهای شکست بخورد، کل تراکنش متوقف میشود.
مشکل بعدی، تداخل تراکنشهای همزمان است که برای حل آن از Isolation (ایزولاسیون) استفاده میشود. سطوح مختلف ایزولاسیون از خواندن متناوب Read Committed شروع میشوند که از کثیفخوانی جلوگیری میکند، اما گاهی به پدیدههایی مثل Lost Update (بهروزرسانی ازدسترفته) منجر میشود. وقتی دو کاربر همزمان روی یک مقدار کار میکنند و یکی تغییر دیگری را بازنویسی میکند، بهروزرسانی ازدسترفته رخ میدهد که پایگاههای داده با قفلگذاری یا عملگرهای اتمی با آن مقابله میکنند.
حتی در سطوح پیشرفتهتر مثل Snapshot Isolation (ایزولاسیون اسنپشات)، که با مکانیزم Multi-Version Concurrency Control (کنترل همزمانی چندنسخهای) به هر تراکنش یک نمای ثابت از دادهها را میدهد، مسائل پیچیدهتری مثل Write Skew (انحراف نوشتن) ممکن است اتفاق بیفتد. انحراف نوشتن زمانی رخ میدهد که دو تراکنش بر اساس یک پیشفرض درست تصمیم میگیرند دادهای را تغییر دهند، اما تداخل همزمان باعث نقض قوانین کسبوکار میشود؛ مثل نمونهٔ پزشکان کشیک که همزمان درخواست مرخصی میدهند.
برای رسیدن به بالاترین سطح اطمینان و جلوگیری از تمام این خطاهای همزمانی، Serializability (سریالپذیری) به کار میرود تا نتیجهٔ اجرای همزمان تراکنشها دقیقاً شبیه اجرای پشتسرهم آنها باشد. سیستمها برای پیادهسازی این سطح از الگوریتمهایی مثل Two-Phase Locking (قفلگذاری دو مرحلهای) یا روشهای خوشبینانه مثل Serializable Snapshot Isolation (ایزولاسیون اسنپشات سریالپذیر) استفاده میکنند که هر کدام هزینهها و مزایای خاص خود را دارند.
پیشنهاد من به عنوان میزبان این است که در طراحی سیستمهای خود، تا جایی که امکان دارد از تراکنشهای کوتاه و مشخص استفاده کنید و پیش از انتخاب سطح ایزولاسیون، دقیقاً بررسی کنید که برنامهٔ شما به چه سطح از سازگاری نیاز دارد.
امیدوارم این بخش از نگاهی به کتاب طراحی برنامههای دادهگرا برایتان مفید بوده باشد و در برنامهسیستمهای بعدیتان به کار بیاید.
فصل ششم: تقسیمبندی دادهها
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردید که چطور سرویسهای بزرگ اینترنتی میتوانند میلیاردها رکورد را بدون کند شدن ذخیره کنند؟ وقتی حجم دادهها یا تعداد درخواستها آنقدر زیاد میشود که روی یک سرور جا نمیشود، چارهای نداریم جز این که سیستم را خرد کنیم. در این قسمت از شبگفتار میخواهیم ببینیم چطور میتوانیم دادهها را بین چند ماشین تقسیم کنیم تا سیستم زیر بار زیاد زانو نزند.
موضوع این بخش از کتاب «طراحی برنامههای دادهگرا» نوشته مارتین کلپمن، پارتیشنبندی (Partitioning) است که به معنای تقسیم یک پایگاه دادهٔ بزرگ به قطعات کوچکتر و توزیع آنها بین چند گره مختلف در یک کلاستر است.
اولین ایده برای تقسیم دادههای کلید-مقدار، استفاده از محدودهٔ کلید (Key Range) است. در این روش، یک بازهٔ مداوم از کلیدها به هر پارتیشن اختصاص داده میشود؛ درست مثل جلدهای یک دائرةالمعارف چاپی که حروف الفبا را به ترتیب در خود جای دادهاند. مزیت این روش این است که اسکنهای بازهای بسیار کارآمد انجام میشوند و میتوانید به راحتی دادههای یک بازه را بخوانید. مشکل اینجاست که اگر کلید شما یک تاریخ یا زمان باشد، تمام نوشتنهای جدید روی آخرین پارتیشن یعنی امروز متمرکز میشوند و بقیهٔ سرورها بیکار میمانند که به این وضعیت توزیع نامتوازن (Skew) میگویند و پارتیشن شلوغ را هات پارتیشن (Hot Partition) مینامیم.
برای حل مشکل هات پارتیشن، راه دوم یعنی هش پارتیشن (Hash Partition) به میدان میآید. در این روش، یک تابع هش (Hash) روی کلید اعمال میشود تا خروجی بهصورت تصادفی در کل بازه پخش شود. این کار باعث میشود دادهها و بار درخواستها خیلی متوازنتر بین گرهها تقسیم شوند. عیب بزرگش این است که ترتیب قرار گرفتن کلیدها از بین میرود و دیگر نمیتوانید بهینه جستجوی بازهای انجام دهید، مگر اینکه از کلیدهای ترکیبی مثل کاساندرا (Cassandra) استفاده کنید که ستون اول را هش میکند و بقیه را مرتب نگه میدارد.
قضیه وقتی پیچیدهتر میشود که پای ایندکسها (Index) به میان میآید. اگر بخواهیم روی ستونهایی غیر از کلید اصلی سرچ کنیم، به ایندکس ثانویه (Secondary Index) نیاز داریم. اینجا با دو رویکرد روبهرو میشویم؛ یا ایندکسها را به صورت محلی در همان پارتیشن داده نگه میداریم که خواندن را نیازمند جستجوی همزمان در همهٔ پارتیشنها (Scatter/Gather) میکند، یا ایندکس جهانی میسازیم که نوشتن را کندتر و پیچیدهتر میکند.
با گذشت زمان حجم دادهها تغییر میکند یا سروری از کار میافتد و ما به بازتوازن (Rebalancing) نیاز پیدا میکنیم تا بار سیستم دوباره روی گرهها به توازن برسد. اگر بخواهیم تعداد پارتیشنها را با تغییر تعداد گرهها هماهنگ کنیم، روشهایی مثل ایجاد تعداد زیادی پارتیشن ثابت از ابتدا یا ایجاد پویای پارتیشنها به داد ما میرسند تا مجبور نباشیم کل دادهها را جابهجا کنیم.
وقتی کلاستر ما پارتیشنبندی شد، پرسش اینجاست که درخواست کاربر چطور به سرور درست میرسد. اینجاست که مسیریابی درخواست (Request Routing) اهمیت پیدا میکند. سیستمهایی مثل کافکا (Kafka) و اچبیس (HBase) برای پیگیری اینکه هر پارتیشن روی کدام گره است از سرویسهای هماهنگکننده مثل زوکیپر (ZooKeeper) استفاده میکنند، در حالی که برخی دیگر از پروتکلهای شایعهپراکنی (Gossip) بهره میبرند.
پیشنهاد کاربردی من این است که همیشه پیش از رشد ناگهانی ترافیک، استراتژی کلیدگذاری را طوری طراحی کنید که روی یک کلید خاص بار بیش از حد نیفتد و اگر کاربر خاصی دارید که درخواستهایش سیلآساست، حتماً پیشوند یا پسوند تصادفی به کلیدهایش اضافه کنید تا سیستم زیر بار له نشود.
امیدوارم این مرور کوتاه به شما کمک کرده باشد دید بهتری نسبت به مقیاسپذیری پایگاههای داده پیدا کنید. تا قسمت بعدی، پاینده و پیروز باشید.
فصل پنجم: تکثیر دادهها
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که وقتی در یک پیامرسان پیامی میفرستید، چطور همزمان روی گوشی دوستانتان، نسخه وب و سرورهای مختلف ظاهر میشود و اگر یکی از این سرورها از کار بیفتد، چطور اطلاعات ما گم نمیشود؟ نگه داشتن کپیهای یکسان از دادهها روی چند ماشین مختلف که از طریق شبکه به هم وصل هستند، کار سادهای نیست چون تغییر دادن مداوم این دادهها چالشهای پیچیدهای میسازد. در کتاب «طراحی برنامههای دادهگرا» نوشتهٔ «مارتین کلپمن»، فصل پنجم به موضوع مهم تکثیر (Replication) میپردازد و نشان میدهد چطور میتوان دادهها را در چند گره مختلف مدیریت کرد تا سیستم در برابر خرابیها مقاومت کند.
نخستین و رایجترین روش، معماری رهبر/پیرو (Leader/Follower) است که در آن، یک گره به عنوان رهبر تمام درخواستهای نوشتن را میپذیرد و بقیه گرهها به عنوان پیرو، تغییرات را از رهبر میگیرند تا نسخهای مشابه داشته باشند. برای مثال، در بسیاری از پایگاههای داده رابطه ای، کاربران برای ثبت اطلاعات جدید باید به رهبر مراجعه کنند و پیروان فقط خواندن دادهها را انجام میدهند. انتخاب بین تکثیر همگام (Synchronous) و ناهمگام (Asynchronous) در اینجا یک معامله حیاتی است. در حالت همگام، رهبر تا تأییدیه پیرو را نگیرد به کاربر پاسخ موفقیت نمیدهد که امنیت داده را بالا میبرد، اما اگر آن پیرو قطع شود، کل سیستم متوقف میشود. به همین دلیل بیشتر سیستمها از حالت ناهمگام استفاده میکنند تا کارایی حفظ شود، هرچند اگر رهبر پیش از ارسال تغییرات به پیروان سقوط کند، مقداری از دادهها از دست میرود.
دومین مسئله، نحوهٔ مدیریت قطعی گرهها و فرآیند جایگزینی است. اگر یک پیرو موقتاً از دست برود، پس از اتصال مجدد میتواند از روی لاگ تغییرات، عقبافتادگی خود را جبران کند اما اگر رهبر خراب شود، ماجرای failover پیش میآید که در آن یکی از پیروان باید به رهبر تبدیل شود. این فرآیند خودکار خطراتی مثل تقسیم مغز (Split Brain) دارد؛ حالتی که در آن دو گره همزمان فکر میکنند رهبر هستند و دادهها مخدوش میشوند. به عنوان نمونهای از خطرات عملی، در یک اتفاق واقعی در گیتهاب، ترویج یک پیرو که عقب مانده بود باعث شد شناسههای اصلی تکراری تولید شوند و ناسازگاری به وجود بیاید.
سومین ایده، بروز پدیدهٔ تاخیر تکثیر (Replication Lag) در خواندن از پیروان ناهمگام است که تناقضهای موقتی ایجاد میکند. وقتی دادهها هنوز به پیرو نرسیده باشند، کاربری که اطلاعات خود را بلافاصله پس از ثبت میخواند ممکن است فکر کند دادههایش گم شدهاند که به آن خواندن نوشتنهای خود (Read-Your-Writes) میگویند. برای حل این مشکل، برنامهنویسان گاهی مجبورند خواندن پروفایل خود کاربر را مستقیماً از رهبر انجام دهند و بقیه خواندنها را به پیروان بسپارند تا کاربر احساس نکند زمان به عقب برگشته است.
چهارمین رویکرد، استفاده از چند رهبر (Multi-Leader) است که به درد سناریوهایی مانند چند دیتاسنتر مختلف میخورد تا هر دیتاسنتر بتواند به صورت محلی نوشتنها را بپذیرد و بعداً با بقیه همخوان شود. بزرگترین چالش در این روش، بروز تضاد نوشتن همزمان در دو جای مختلف است؛ مثلاً اگر دو نفر عنوان یک صفحه را در دو دیتاسنتر مختلف همزمان تغییر دهند، دیتابیس باید راهی برای همگرا کردن وضعیت پیدا کند. برخی سیستمها از آخرین نوشتن برنده است (Last Write Wins) استفاده میکنند که با مقایسه زمانسنجها یکی را نگه میدارد، اما این روش خطر جدی از دست رفتن دادهها را به همراه دارد.
پنجمین مدل، معماری بدون رهبر (Leaderless) است که در آن کلاینتها مستقیماً درخواست نوشتن یا خواندن را به چند گره به صورت موازی میفرستند تا به حد نصاب کووروم (Quorum) برسند. در این حالت اگر مجموع تعداد گرههای نوشتن و خواندن از تعداد کل گرهها بیشتر باشد، سیستم تضمین میکند که همیشه دادهٔ بهروز خوانده میشود. با این حال، پدیدههایی مانند خواندن مقادیر کهنه همچنان ممکن است رخ دهند و برای مهار تناقضها، الگوریتمهای پیشرفتهای نیاز است تا مشخص شود کدام عملیات قبل از دیگری رخ داده است.
پیشنهاد شخصی من این است که تا جای امکان از پیچیدگیهای تکثیر چند رهبری یا بدون رهبر دوری کنید، مگر اینکه مقیاس جغرافیایی یا قطع ارتباط اینترنت واقعاً شما را مجبور به این کار کند. در بیشتر پروژهها، استفاده از معماری سادهتر با یک رهبر قابل اعتماد، زندگی مهندسی شما را بسیار آرامتر خواهد کرد.
امیدوارم این بخش از بررسی کتاب به شناخت بهتری از ساختارهای توزیعشده کمک کرده باشد. تا اپیزود بعدی، پیروز و پایدار باشید.
فصل چهارم: رمزگذاری و تکامل
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که وقتی ساختار پایگاه داده یا مدل برنامه را تغییر میدهید، چه بلایی سر دادههای قدیمی روی دیسک یا در حال انتقال روی شبکه میآید؟ چطور میشود سرورها را بهروزرسانی کرد بدون اینکه کل سیستم از کار بیفتد یا دادهها خراب شوند؟
این موضوعات در کتاب «طراحی برنامههای دادهگرا» نوشتهٔ «مارتین کلپمن» در فصل چهارم با عنوان رمزگذاری و تکامل بررسی میشوند و اهمیت زیادی در پایداری سیستمهای مقیاسپذیر دارند.
اولین مسئله، استفاده از ابزارهای پیشفرض زبانهای برنامهنویسی برای ذخیره یا ارسال داده است. مشکل اینجاست که کتابخانههای داخلی زبانهایی مثل جاوا یا پایتون معمولاً به یک زبان خاص وابستهاند و مسائل امنیتی جدی هنگام بازسازی اشیا ایجاد میکنند. راهحل جایگزین، استفاده از فرمتهای مستقل از زبان است، اما این کار نیازمند تبدیل ساختار حافظه به توالی بایتهاست که به آن رمزگذاری (encoding) میگویند؛ فرآیندی که دادهها را به فایلی قابل ذخیره یا ارسال تبدیل میکند.
دومین چالش، استفاده از فرمتهای متنی مثل جیسون است. این فرمتها خوانا هستند اما در نگهداری دقیق اعداد بزرگ یا دادههای باینری ضعف دارند و حجم زیادی اشغال میکنند. برای سیستمهای داخلی سازمان، فرمتهای باینری مبتنی بر اسکیما مانند پروتکل بافر یا پروتباف (Protocol Buffers) مطرح میشوند که با حذف نام فیلدها و استفاده از تگهای عددی، حجم داده را به شدت کاهش میدهند و قابلیت نگهداری (maintainability) سیستم را بهبود میبخشند.
سومین ایده کلیدی، مدیریت تکامل اسکیماست. وقتی برنامهها رشد میکنند، فیلدهایی اضافه یا کم میشوند و نسخههای قدیمی و جدید کد همزمان اجرا میشوند. در پروتباف یا تریفت، با استفاده از تگهای عددی، کدهای قدیمی میتوانند فیلدهای جدیدی را که نمیشناسند نادیده بگیرند تا سازگاری رو به جلو (forward compatibility) حفظ شود، و کدهای جدید نیز میتوانند دادههای قدیمی را بخوانند تا سازگاری رو به عقب (backward compatibility) برقرار بماند.
چهارمین رویکرد، استفاده از فرمت آپاچی آوورو (Apache Avro) است که برخلاف پروتباف، هیچ تگ عددی در داده ذخیره نمیکند و کوچکترین حجم را میسازد. آوورو برای خواندن داده به اسکیمای نویسنده و اسکیمای خواننده متکی است و با تطبیق نام فیلدها، تغییرات اسکیما را بدون نیاز به بازنویسی کل دادهها مدیریت میکند. این ویژگی به پایگاه داده کمک میکند تا با وجود تغییرات ساختاری در طول زمان، همچنان قابلیت اعتماد (reliability) خود را حفظ کند.
پنجمین نکته به نحوهٔ جریان دادهها در سرویسها و پیامرسانها مربوط میشود. در معماری میکروسرویسها، درخواستها و پاسخها بین سرویسها رد و بدل میشوند و به کارگیری فرمتهای سازگار به ما اجازه میدهد سرورها را طی یک ارتقای غلتکی (rolling upgrade) بهروزرسانی کنیم؛ یعنی گرهها را یکییکی آپدیت کنیم بدون اینکه سرویس قطعی داشته باشد.
پیشنهاد من به عنوان میزبان این است که در پروژههای واقعی، هرگز به سریالسازی پیشفرض زبانها اعتماد نکنید و برای دادههای داخلی سازمان حتماً از فرمتهای باینری اسکیمامحور بهره ببرید تا در زمان تغییرات ساختاری دچار بحران نشوید.
امیدوارم این نگاه دقیقتر به رمزگذاری دادهها به شما کمک کند تا برنامههای انعطافپذیرتری بسازید. تا برنامهای دیگر و بررسی بخشی دیگر از دادههای مقیاسپذیر، روزگارتان خوش.