دربارهٔ این فصل
یادداشتِ همراه نوارتا حالا به این فکر کردهاید که وقتی دادهای را به پایگاه داده میدهید، دقیقاً چه اتفاقی در لایههای زیرین ذخیرهسازی میافتد و چطور این دادهها دوباره با سرعت بالا پیدا میشوند؟ خیلی وقتها برنامهنویسان موتورهای ذخیرهسازی را از صفر نمینویسند، اما انتخاب درست میان گزینهها و تنظیم دقیق آنها برای رسیدن به کارایی مطلوب نیازمند درک آنچه در پشت صحنه میگذرد است. در کتاب «طراحی برنامههای دادهگرا» نوشتهٔ «مارتین کلپمن» در فصل سوم این موضوع بررسی میشود که پایگاههای داده چگونه دادهها را ذخیره و بازیابی میکنند. در ادامه، چند ایدهٔ کلیدی این بخش را با هم مرور میکنیم.
سادهترین شکل پایگاه داده میتواند تنها یک فایل متنی باشد که در آن هر دستور نوشتن، اطلاعات را به انتهای فایل الحاق میکند. مشکل اینجاست که برای خواندن یا پیدا کردن یک مقدار، باید کل فایل از ابتدا تا انتها اسکن شود که با افزایش حجم دادهها، زمان پاسخدهی بهشدت بالا میرود. برای حل این مشکل، پایگاه داده از index (ایندکس) استفاده میکند؛ یک ساختار کمکی برای پیدا کردن سریع داده که از دادههای اصلی مشتق میشود. ایندکسها سرعت خواندن را بالا میبرند اما هر ایندکس جدیدی نوشتن را کند میکند، زیرا با هر تغییر داده، خود ایندکس هم باید بهروزرسانی شود و این یک دادوستد مهم در سیستمهای ذخیرهسازی است.
یکی از شیوههای ایندکسگذاری برای دادههای کلید-مقدار، نگهداری یک ساختار هش (hash) در حافظه رم است که هر کلید را به آدرس بایت آن در فایل روی دیسک وصل میکند. موتوری مثل Bitcask از همین روش استفاده میکند که برای خواندن و نوشتنهای سریع بسیار عالی است، به شرطی که تمام کلیدها در حافظه جا شوند. برای اینکه فایلهای لاگ تا ابد بزرگ نشوند، پایگاه داده آنها را به قطعات مشخصی تقسیم کرده و عملیات فشردهسازی و تجمیع را روی آنها انجام میدهد تا کلیدهای تکراری حذف شوند و فقط آخرین مقدار باقی بماند.
روش پیشرفتهتر دیگر، استفاده از Sorted String Table SSTable است که در آن جفتهای کلید-مقدار بر اساس کلید مرتب شدهاند. وقتی دادهها مرتب باشند، ادغام فایلها بسیار کارآمد میشود و دیگر نیازی نیست ایندکس تمام کلیدها کامل در حافظه بماند زیرا ایندکسهای پراکنده هم میتوانند مسیر را نشان دهند. برای پیادهسازی این رویکرد روی دیسک، نوشتنها ابتدا وارد یک ساختار متوازن در حافظه رم به نام memtable میشوند و وقتی حجم آنها زیاد شد، به شکل یک فایل مرتب روی دیسک ریخته میشوند. کتابخانههایی مثل LevelDB و RocksDB از ساختاری به نام LSM-tree (درخت LSM) استفاده میکنند که ترکیبی از این جدولهای مرتب و لاگهای حافظهای است تا توان عملیاتی نوشتن را به شکل چشمگیری افزایش دهند.
در سوی مقابل، ساختار پرکاربرد دیگر B-tree (درخت B) است که پایگاه داده را به بلوکها یا صفحات با اندازهٔ ثابت تقسیم میکند و آنها را در جا بازنویسی میکند. در این ساختار، هر صفحه شامل تعدادی کلید و ارجاع به صفحات فرزند است که درخت را متوازن نگه میدارد و جستجوی کلیدها را با تعداد کمتری از پرشها ممکن میسازد. البته برای جلوگیری از خرابی دادهها هنگام قطع برق، تغییرات ابتدا در یک write-ahead log (لاگ نوشتن پیشرو) ثبت میشود که یعنی لاگ تغییرات پیش از اعمال آنها نوشته میشود تا در صورت بروز مشکل، وضعیت سیستم بازیابی شود.
در نهایت، تفاوت اساسی میان سامانههای پردازش تراکنش و سامانههای تحلیلی وجود دارد که اولی برای خواندن و نوشتنهای تعاملی سریع با تعداد رکورد کم طراحی شدهاند و دومی برای اسکن میلیونها رکورد و محاسبهٔ آماری استفاده میشوند. در انبارهای داده data warehouse، رویکرد ذخیرهسازی ستونی به جای سطری به کار میرود تا پرسوجوهای تحلیلی فقط ستونهای مورد نیاز را بخوانند و با فشردهسازی پیشرفته، کارایی پردازش دادهها به حداکثر برسد.
پیشنهاد من این است که پیش از انتخاب نهایی موتور ذخیرهسازی برای پروژهٔ خود، الگوهای خواندن و نوشتن برنامهتان را بهدقت بررسی کنید؛ اگر سیستم شما با حجم عظیمی از نوشتنهای سریع روبهرو است، ساختارهای مبتنی بر لاگ و پیادهسازیهای مرتبشده میتوانند انتخاب بهتری باشند، در حالی که برای بارهای کاری سنتیتر، ساختارهای مبتنی بر درخت همچنان گزینهای قابل اتکا هستند.
شناخت این لایههای زیرین به شما کمک میکند تا به جای حدس و گمان، با آگاهی کامل از ساختارهای دادهای پشت صحنه، معماری نرمافزار خود را بهینه کنید.