کتابهای نویسنده: مارتین کلپمن · همهٔ کتابها
کتابخانهٔ شنیدنی شبگفتار
کتابهای مارتین کلپمن
خلاصههای صوتی فارسی کتابهای مرتبط با مارتین کلپمن؛ کتاب و فصل دلخواهتان را برای شنیدن انتخاب کنید.
این مجموعه خلاصهٔ کتابهاست؛ نسخهٔ کامل کتاب اصلی نیست.
1 عنوان · 13 قسمت منتشرشده
راه را پیدا کنید
مسیرهای مطالعاتی
قفسهٔ کتابها
از این کتابها شروع کنید
فصل دوازدهم: آینده سامانههای دادهای
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که چرا وقتی حجم دادهها زیاد میشود، هیچ پایگاه دادهٔ واحدی نمیتواند تمام نیازهای ما را به تنهایی برآورده کند و مجبور میشویم چندین ابزار مختلف را به زور به هم وصل کنیم؟ آیا راه بهتری برای فرار از این کلاف سردرگم ابزارهای ناسازگار وجود دارد؟
این مبحث از کتاب «طراحی برنامههای دادهگرا» نوشتهٔ «مارتین کلپمن» به بررسی آیندهٔ سیستمهای دادهگرا و الگوهای نوآورانه در معماری داده میپردازد.
اولین ایدهٔ کلیدی دربارهٔ چالش یکپارچهسازی دادهها است. در برنامههای پیچیده، دادهها به شکلهای گوناگونی استفاده میشوند و تلاش برای انجام دادن همهٔ کارها در یک تکه نرمافزار به شکست میانجامد. راهکار مرسوم، چسباندن چندین ابزار مختلف مثل پایگاه داده و موتور جستجو به همدیگر است، اما اگر مشتریان مستقیماً روی هر دو سیستم بنویسند، بهدلیل تفاوت ترتیب پردازش، ناسازگار میشوند. نویسنده پیشنهاد میکند که تمام ورودیها از یک مسیر واحد عبور کنند تا بقیهٔ نماها بر اساس همان ترتیب ساخته شوند.
دومین ایدهٔ کلیدی مقایسهٔ رویکرد دادهٔ مشتقشده (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) بهروزرسانی کنیم؛ یعنی گرهها را یکییکی آپدیت کنیم بدون اینکه سرویس قطعی داشته باشد.
پیشنهاد من به عنوان میزبان این است که در پروژههای واقعی، هرگز به سریالسازی پیشفرض زبانها اعتماد نکنید و برای دادههای داخلی سازمان حتماً از فرمتهای باینری اسکیمامحور بهره ببرید تا در زمان تغییرات ساختاری دچار بحران نشوید.
امیدوارم این نگاه دقیقتر به رمزگذاری دادهها به شما کمک کند تا برنامههای انعطافپذیرتری بسازید. تا برنامهای دیگر و بررسی بخشی دیگر از دادههای مقیاسپذیر، روزگارتان خوش.
فصل سوم: ذخیرهسازی و بازیابی اطلاعات
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که وقتی دادهای را به پایگاه داده میدهید، دقیقاً چه اتفاقی در لایههای زیرین ذخیرهسازی میافتد و چطور این دادهها دوباره با سرعت بالا پیدا میشوند؟ خیلی وقتها برنامهنویسان موتورهای ذخیرهسازی را از صفر نمینویسند، اما انتخاب درست میان گزینهها و تنظیم دقیق آنها برای رسیدن به کارایی مطلوب نیازمند درک آنچه در پشت صحنه میگذرد است. در کتاب «طراحی برنامههای دادهگرا» نوشتهٔ «مارتین کلپمن» در فصل سوم این موضوع بررسی میشود که پایگاههای داده چگونه دادهها را ذخیره و بازیابی میکنند. در ادامه، چند ایدهٔ کلیدی این بخش را با هم مرور میکنیم.
سادهترین شکل پایگاه داده میتواند تنها یک فایل متنی باشد که در آن هر دستور نوشتن، اطلاعات را به انتهای فایل الحاق میکند. مشکل اینجاست که برای خواندن یا پیدا کردن یک مقدار، باید کل فایل از ابتدا تا انتها اسکن شود که با افزایش حجم دادهها، زمان پاسخدهی بهشدت بالا میرود. برای حل این مشکل، پایگاه داده از index (ایندکس) استفاده میکند؛ یک ساختار کمکی برای پیدا کردن سریع داده که از دادههای اصلی مشتق میشود. ایندکسها سرعت خواندن را بالا میبرند اما هر ایندکس جدیدی نوشتن را کند میکند، زیرا با هر تغییر داده، خود ایندکس هم باید بهروزرسانی شود و این یک دادوستد مهم در سیستمهای ذخیرهسازی است.
یکی از شیوههای ایندکسگذاری برای دادههای کلید-مقدار، نگهداری یک ساختار هش (hash) در حافظه رم است که هر کلید را به آدرس بایت آن در فایل روی دیسک وصل میکند. موتوری مثل Bitcask از همین روش استفاده میکند که برای خواندن و نوشتنهای سریع بسیار عالی است، به شرطی که تمام کلیدها در حافظه جا شوند. برای اینکه فایلهای لاگ تا ابد بزرگ نشوند، پایگاه داده آنها را به قطعات مشخصی تقسیم کرده و عملیات فشردهسازی و تجمیع را روی آنها انجام میدهد تا کلیدهای تکراری حذف شوند و فقط آخرین مقدار باقی بماند.
روش پیشرفتهتر دیگر، استفاده از Sorted String Table SSTable است که در آن جفتهای کلید-مقدار بر اساس کلید مرتب شدهاند. وقتی دادهها مرتب باشند، ادغام فایلها بسیار کارآمد میشود و دیگر نیازی نیست ایندکس تمام کلیدها کامل در حافظه بماند زیرا ایندکسهای پراکنده هم میتوانند مسیر را نشان دهند. برای پیادهسازی این رویکرد روی دیسک، نوشتنها ابتدا وارد یک ساختار متوازن در حافظه رم به نام memtable میشوند و وقتی حجم آنها زیاد شد، به شکل یک فایل مرتب روی دیسک ریخته میشوند. کتابخانههایی مثل LevelDB و RocksDB از ساختاری به نام LSM-tree (درخت LSM) استفاده میکنند که ترکیبی از این جدولهای مرتب و لاگهای حافظهای است تا توان عملیاتی نوشتن را به شکل چشمگیری افزایش دهند.
در سوی مقابل، ساختار پرکاربرد دیگر B-tree (درخت B) است که پایگاه داده را به بلوکها یا صفحات با اندازهٔ ثابت تقسیم میکند و آنها را در جا بازنویسی میکند. در این ساختار، هر صفحه شامل تعدادی کلید و ارجاع به صفحات فرزند است که درخت را متوازن نگه میدارد و جستجوی کلیدها را با تعداد کمتری از پرشها ممکن میسازد. البته برای جلوگیری از خرابی دادهها هنگام قطع برق، تغییرات ابتدا در یک write-ahead log (لاگ نوشتن پیشرو) ثبت میشود که یعنی لاگ تغییرات پیش از اعمال آنها نوشته میشود تا در صورت بروز مشکل، وضعیت سیستم بازیابی شود.
در نهایت، تفاوت اساسی میان سامانههای پردازش تراکنش و سامانههای تحلیلی وجود دارد که اولی برای خواندن و نوشتنهای تعاملی سریع با تعداد رکورد کم طراحی شدهاند و دومی برای اسکن میلیونها رکورد و محاسبهٔ آماری استفاده میشوند. در انبارهای داده data warehouse، رویکرد ذخیرهسازی ستونی به جای سطری به کار میرود تا پرسوجوهای تحلیلی فقط ستونهای مورد نیاز را بخوانند و با فشردهسازی پیشرفته، کارایی پردازش دادهها به حداکثر برسد.
پیشنهاد من این است که پیش از انتخاب نهایی موتور ذخیرهسازی برای پروژهٔ خود، الگوهای خواندن و نوشتن برنامهتان را بهدقت بررسی کنید؛ اگر سیستم شما با حجم عظیمی از نوشتنهای سریع روبهرو است، ساختارهای مبتنی بر لاگ و پیادهسازیهای مرتبشده میتوانند انتخاب بهتری باشند، در حالی که برای بارهای کاری سنتیتر، ساختارهای مبتنی بر درخت همچنان گزینهای قابل اتکا هستند.
شناخت این لایههای زیرین به شما کمک میکند تا به جای حدس و گمان، با آگاهی کامل از ساختارهای دادهای پشت صحنه، معماری نرمافزار خود را بهینه کنید.
فصل دوم: مدلهای داده و زبانهای پرسوجو
رایگانتوضیح و منابع این قسمت
تا حالا به این فکر کردهاید که چرا وقتی میخواهید ساختار پیچیدهای مثل مشخصات یک کاربر را در پایگاه داده ذخیره کنید، گاهی کدهای برنامهنویسی شما پر از ترجمههای دستوپاگیر میشود؟ چطور انتخاب یک مدل داده میتواند تعیین کند که نوشتن کدام ویژگی نرمافزار آسان یا عذابآور باشد؟ چه فرقی بین روشهای مختلف سازماندهی اطلاعات در زیر پوست برنامهها وجود دارد؟
در کتاب «طراحی برنامههای دادهگرا» نوشتهٔ «مارتین کلپمن»، فصل دوم به بررسی مدلهای داده و زبانهای پرسوجو میپردازد تا ببینیم زیربنای انتخابهای ما در ذخیرهسازی اطلاعات چیست.
اولین ایدهٔ کلیدی، مدل رابطهای (Relational Model) در برابر مدل سندی (Document Model) است. مشکل اینجاست که برنامههای امروزی با شیءها کار میکنند، در حالی که پایگاههای دادهٔ سنتی اطلاعات را در جدولها نگه میدارند و این فاصله به ناهمگونی شیء-رابطهای (Impedance Mismatch) منجر میشود. گزینههای مختلفی مانند جدولهای نرمالشده، دادههای ساختیافته درون ستونها یا ذخیرهٔ جیسان وجود دارد که هرکدام تعادلی بین پیچیدگی کد و انعطاف ایجاد میکنند؛ مثلاً ذخیرهٔ مشخصات یک رزومه به شکل یک سند واحد جیسان، دسترسی به تمام اطلاعات را با یک پرسوجو ممکن میکند.
ایدهٔ دوم، روابط چند به چند و محدودیت مدلهای سندی است. مشکل اینجاست که وقتی دادهها بهشدت به هم مرتبط میشوند، مدلهایی که پشتیبانی مناسبی از اتصالها ندارند کار را سخت میکنند. نویسنده توضیح میدهد که مدلهای سندپایه برای ساختارهای درختی یک به چند عالی هستند، اما اگر روابط پیچیده شوند، ممکن است به تکرار دادهها یا پیادهسازی دستی ارتباطها در کد برنامهنویسی نیاز پیدا کنید. برای نمونه، اضافه کردن امکان ارجاع به شرکتها یا توصیههای کاربران دیگر در یک شبکهٔ اجتماعی، مدلهای سندی را دچار چالش میکند.
ایدهٔ سوم، مسئلهٔ طرحواره اسکیما (Schema) و انعطاف آن است. مشکل اینجاست که تغییر ساختار دادهها در پایگاههای داده میتواند وقتگیر باشد و به بازنویسی اطلاعات نیاز پیدا کند. گزینههای پیش رو بین مدل اسکیما-روی-نوشتن (Schema-on-Write) که ساختار را در زمان درج داده کنترل میکند و اسکیما-روی-خواند (Schema-on-read) که ساختار را در زمان خواندن تفسیر میکند متغیر است. برای مثال، پایگاههای دادهٔ سندی به شما اجازه میدهند بدون تغییر قبلی ساختار جدول، فیلد جدیدی را به برخی اسناد اضافه کنید و ناسازگاریها را هنگام خواندن مدیریت کنید.
ایدهٔ چهارم، تفاوت زبانهای پرسوجو است. مشکل اینجاست که دستورات دستوری به کامپیوتر میگویند چه عملیاتی را به چه ترتیبی انجام دهد، که نوشتن برنامههای موازی را دشوار میکند. گزینههای بهتری مثل زبانهای پرسوجوی اعلامی (Declarative) وجود دارند که در آنها فقط الگوی دادهٔ مورد نظر را توصیف میکنید. مثلاً در زبان اسکیوال (SQL) شما مشخص میکنید چه اطلاعاتی میخواهید و موتور پایگاه داده خودش تصمیم میگیرد از چه روشی برای سریعتر شدن کار استفاده کند.
ایدهٔ پنجم، مدلهای مبتنی بر گراف برای دادههای بهشدت مرتبط است. مشکل اینجاست که وقتی همهچیز میتواند به همهچیز وصل باشد، مدلهای جدولمحور یا درختی ناکارآمد میشوند. گزینههایی مانند گرافهای ویژگی (Property Graphs) از رأسها (Vertices) و یالها (Edges) تشکیل شدهاند تا ارتباطهای پیچیده را به شکلی طبیعی ذخیره و پرسوجو کنند. برای مثال، پیدا کردن مسیر مهاجرت افراد از یک کشور به قارهای دیگر در پایگاههای دادهٔ گرافی با زبانهایی مثل سایفر (Cypher) بسیار کوتاهتر و خواناتر از اسکیوال پیادهسازی میشود.
پیشنهاد من این است که هنگام طراحی سیستم جدید، به جای پیروی کورکورانه از ترندهای روز، به نوع روابط واقعی بین دادههایتان نگاه کنید؛ اگر دادههایتان درختهای مستقل هستند مدل سندپایه را انتخاب کنید و اگر با شبکهای پیچیده از ارتباطات روبرو هستید، پایگاه دادههای گرافی را به کار بگیرید.
انتخاب درست مدل داده، مرز میان یک معماری نرمافزار تمیز و توسعهپذیر با کدهایی پر از وصله و پینه است که در نهایت سرنوشت محصول شما را رقم میزند.
فصل اول: برنامههای قابلاعتماد، مقیاسپذیر و قابلنگهداری
رایگانتوضیح و منابع این قسمت
وقتی به اینترنت و سرویسهای بزرگ نگاه میکنیم، به نظر میرسد این فناوریها همیشه بینقص کار کردهاند. اما پشت این سادگی ظاهری، چالشهای بسیار پیچیدهای در مدیریت حجم انبوه دادهها وجود دارد که مهندسان نرمافزار هر روز با آنها دستوپنجه نرم میکنند.
این مباحث برگرفته از کتاب «طراحی برنامههای دادهگرا» نوشتهٔ «مارتین کلپمن» است و به ما نشان میدهد چگونه باید سیستمهایی بسازیم که در برابر مشکلات مقاوم باشند و با رشد حجم اطلاعات، عملکرد خود را حفظ کنند.
اولین اصل بنیادین در طراحی چنین سیستمهایی، قابلیت اعتماد (Reliability) است؛ یعنی توانایی سیستم در ادامهٔ کار درست حتی وقتی خطا رخ میدهد. در دنیای واقعی، سختافزارها خراب میشوند، نرمافزارها باگ دارند و انسانها اشتباه میکنند. برای نمونه، در یک کلاستر بزرگ با هزاران هارد دیسک، خرابی روزانهٔ چند قطعه کاملاً عادی است. نویسنده توضیح میدهد که به جای تلاش برای حذف کامل خطاها، باید با افزودن قابلیت تحمل خطا و استفاده از تکنیکهایی مانند تستهای مخرب خودکار، مطمئن شویم سیستم با خرابیها از پا نمیافتد.
دومین چالش مهم، مقیاسپذیری (Scalability) است که توانایی سیستم در مدیریت رشد بار و حجم داده را توصیف میکند. وقتی تعداد کاربران یا حجم درخواستها دهها برابر میشود، نمیتوان انتظار داشت همان معماری قبلی پاسخگو باشد. برای مثال، در طراحی شبکهای مانند توییتر، چالش اصلی تعداد توییتها نیست، بلکه توزیع نامتوازن دنبالکنندگان است که باعث میشود ارسال یک توییت ساده به میلیونها عملیات نوشتن منجر شود و مهندسان ناچار شوند بین انجام کار هنگام نوشتن یا هنگام خواندن دادهها تعادل برقرار کنند.
برای سنجش این مقیاسپذیری، ما به معیارهای دقیق عملکرد نیاز داریم. میانگین زمان پاسخگویی معمولاً شاخص مناسبی نیست چون اثر کاربران کند را پنهان میکند. به همین دلیل استفاده از صدکها percentiles اهمیت پیدا میکند؛ یعنی بررسی تاخیر در صدکهای نود و پنجم یا نود و نهم تا مطمئن شویم حتی کاربرانی که دادههای بیشتر یا درخواستهای سنگینتری دارند، تجربهٔ کندی نداشتهاند و صفهای انتظار سرور باعث تاخیر آبشاری نمیشود.
سومین ستون اصلی، قابلیت نگهداری (Maintainability) است که تلاش میکند سهولت تغییر، تعمیر و ادارهٔ سیستم در طول زمان را فراهم کند. بخش بزرگی از هزینهٔ نرمافزار مربوط به روزهای پس از توسعه است و پیچیدگیهای تصادفی که در کد یا معماری انباشته میشوند، کار تیمهای مهندسی و عملیات را دشوار میکنند. استفاده از انتزاعهای مناسب، مانند زبانهای سطح بالا یا پایگاههای دادهای که جزئیات پیچیدهٔ ذخیرهسازی روی دیسک را پنهان میکنند، ابزارهایی هستند که به ما کمک میکنند پیچیدگیها را مهار کنیم.
پیشنهاد عملی من به شما این است که وقتی سیستم جدیدی را طراحی میکنید، از همین امروز مانیتورing و سنجش صدکهای واقعی تاخیر را در سمت کلاینت جدی بگیرید و پیش از اینکه مقیاس سیستم شما را غافلگیر کند، فرضهای خود دربارهٔ بار کاری را مستند کنید.
امیدوارم این مرور کوتاه به شما کمک کرده باشد نگاه عمیقتری به زیرساخت برنامههای دادهگرا داشته باشید. تا اپیزود بعدی، پیروز و پایدار باشید.
پیشگفتار
رایگانتوضیح و منابع این قسمت
وقتی درگیر ساخت برنامههای سمت سرور هستید، مدام با کلمات دهانپُرکنی مثل پایگاههای دادهٔ غیررابطهای، مقیاسپذیری وب و معماری ابری بمباران میشوید. اما واقعاً پشت این همه اصطلاح رنگارنگ چه اصول پایداری نهفته است که با تغییر کردن ابزارها همچنان سر جای خودشان باقی میمانند؟
این پرسش محوری کتاب «طراحی برنامههای دادهگرا» نوشتهٔ «مارتین کلپمن» است؛ راهنمایی عمیق که کمک میکند از سطح این اصطلاحات عبور کنیم و به درک درستی از مهندسی داده برسیم.
نخستین چالش در این مسیر، ساختن سیستمی است که در برابر خطاهای مختلف مقاومت کند. در مهندسی نرمافزار، قابلیت اعتماد (Reliability) یعنی توانایی سیستم در ادامهٔ کار درست حتی وقتی خطا رخ میدهد. فرض کنید در یک سامانهٔ بانکی، بروز یک نقص سختافزاری یا خطای انسانی نباید کل داراییها را به خطر بیندازد. انتخابهای معماری ما باید طوری باشد که سیستم در مواجهه با این مشکلات پیشبینینشده، پابرجا بماند.
چالش دوم به رشد مداوم حجم اطلاعات مربوط میشود. وقتی یک اپلیکیشن با میلیونها کاربر روبهرو میشود، مقیاسپذیری (Scalability) یعنی توانایی سیستم در مدیریت رشد بار و حجم داده اهمیت حیاتی پیدا میکند. اگر بدون نیاز واقعی سراغ معماریهای پیچیده و توزیعشده برویم، دچار بهینهسازی زودهنگام میشویم و هزینههای گزافی میپردازیم؛ اما اگر سیستم را درست طراحی نکنیم، در روزهای اوج ترافیک زمین گیر خواهیم شد.
مسئلهٔ سوم به طول عمر کدها و زیرساختها برمیگردد. قابلیت نگهداری (Maintainability) یعنی سهولت تغییر، تعمیر و ادارهٔ سیستم در طول زمان که کمک میکند تیمهای نرمافزاری بدون هدر دادن انرژی، ویژگیهای جدید را به محصول اضافه کنند. کتاب توضیح میدهد که چطور میتوان ساختارها را طوری چید که تغییر نیازمندیهای کسبوکار، کل پروژه را از بین نبرد و توسعهدهندگان در سالهای آینده هم از کار کردن روی کدها لذت ببرند.
در نهایت، وقتی از یک ماشین فراتر میرویم و به سراغ سامانههای توزیعشده میرویم، هماهنگی بین بخشهای مختلف سخت میشود. سازگاری نهایی (Eventual Consistency) یعنی نسخهها در نهایت همخوان میشوند، نه لزوماً در همان لحظه؛ روشی که در بسیاری از پایگاههای دادهٔ مقیاسپذیر برای سرعت بخشیدن به پاسخدهی به کار میرود و البته پیچیدگیهای منطقی خاص خودش را دارد.
پیشنهاد من این است که وقتی ابزار جدیدی وارد بازار میشود، بهجای هجوم آوردن برای یادگیری تمام جزئیات سطحی آن، ابتدا اصول پایهای ذخیرهسازی، پردازش و توزیع داده را مرور کنید تا ببینید آیا واقعاً حلکنندهٔ مسئلهٔ واقعی شما هستید یا خیر.
شناخت این اصول زیربنای ساخت هر نرمافزار مقاومتپذیری است که باید هر روز در دنیای واقعی دوئت کند. امیدوارم این مرور کوتاه نگاه تازهای به معماری دادهها به شما داده باشد.