الأساسيات · فصل ٣

التخزين والاسترجاع: الداتابيز من جوه

LSM · B-tree · OLTP vs OLAP · column storage
🎯 الهدف: تفهم ليه الكتابة السريعة بتتحط في log، وامتى تختار LSM وامتى B-tree.

أبسط داتابيز

ملف: set بيعمل append لسطر في الآخر، و get بيدور على آخر سطر للمفتاح. الكتابة سريعة جدًا. القراءة كارثة لأنك بتلف على الملف كله.

الـ index بنية إضافية مشتقة بتوصّلك بسرعة. كل index بيسرّع القراءة وبيبطّأ الكتابة، لأن لازم يتحدث مع كل write. أنت اللي بتختارهم حسب الكويريز.

Hash index و LSM

Hash map في الرام: كل key → مكانه في الملف. ممتاز لو المفاتيح تدخل الرام والقيم بتتحدث كتير (عداد مشاهدات). الملف بيكبر، فنقسّمه segments، ونعمل compaction (نسيب آخر قيمة) و merge.

الكتابة append-only أسرع من التعديل في المكان، والـ crash recovery أبسط. بس كل المفاتيح لازم تدخل الرام، والـ range queries مستحيلة عمليًا.

لو رتبت المفاتيح جوه كل segment يبقى SSTable:

  1. الـ merge سهل زي merge sort، ولو المفتاح اتكرر ناخد الأحدث.
  2. Sparse index: مش محتاج كل المفاتيح في الرام، لأن الترتيب بيقولك المفتاح بين مين ومين.
  3. تقدر تضغط كل block.

الكتابات العشوائية بتتحط الأول في memtable (شجرة في الرام). لما تكبر تتكتب SSTable. ولو السيرفر وقع، في log منفصل عشان الـ recovery. ده LSM-tree: LevelDB، RocksDB، Cassandra، HBase، وفكرة قريبة في Lucene.

B-tree

أشهر index من السبعينات. الديسك صفحات ثابتة (غالبًا ٤KB)، والتعديل في مكان الصفحة. فيه root، وكل صفحة فيها مفاتيح و pointers لأولادها. الشجرة دايمًا balanced: ٤ مستويات بـ branching عالي تشيل تيرابايتات.

لو الصفحة اتملت تتقسم نصين. الكتابة فوق كذا صفحة خطر لو السيرفر وقع في النص، فأي تعديل يتكتب الأول في WAL (redo log) قبل ما يتنفذ على الشجرة.

LSM-treeB-tree
الكتابةأسرع غالبًا: sequential و write amplification أقلكل write = WAL + الصفحة، وأحيانًا الصفحة كلها عشان بايتات
القراءةأبطأ شوية (memtable + كذا segment)أسرع ومتوقعة
المساحةضغط أحسنصفحات فيها فراغ
العيبالـ compaction ممكن يعمل tail latency، ولو الكتابة أسرع من الدمج الديسك يتملىwrite amplification أعلى، ومساحة فاضية

مفيش قاعدة ثابتة. جرّب على الـ workload. الـ B-trees راسخة ومتوقعة ومريحة للـ transactions. الـ LSM بيزيد في الأنظمة الـ write-heavy.

Indexes تانية

النوعالفكرة
Secondaryعلى عمود مش primary. كل قيمة تشاور على ليستة صفوف. مهمة للـ joins
Clusteredالصف نفسه جوه الـ index (زي InnoDB). الـ secondary بيشاور على الـ primary key
Coveringالـ index فيه الأعمدة اللي الكويري محتاجاها، فمتروحش للصف
Multi-columnزي دليل التليفون: اسم العيلة ثم الاسم. مفيد بالترتيب ده بس
Multi-dimensionallat/long أو لون. B-tree عادي مش يكفي. R-trees
Full-textمرادفات وأخطاء إملائية. Lucene و Levenshtein

OLTP و OLAP

OLTPOLAP
القراءةصفوف قليلة بالمفتاحتجميع على ملايين الصفوف
الكتابةrandom ومن المستخدمbulk import أو event stream
مينالعميل من الأبلكيشنالمحلل
الداتاأحدث حالةتاريخ الأحداث
العائقdisk seekdisk bandwidth

الـ data warehouse داتابيز منفصلة للتحليلات، read-only، فيها نسخة من أنظمة الـ OLTP. ليه منفصلة؟ عشان الكويري التقيلة متضربش العملاء. الطريق ETL: Extract ثم Transform ثم Load.

الكويري بتحتاج ٤ أعمدة من ١٠٠. التخزين صف-صف هيقرا الصف كله. Column storage: كل عمود في ملف، والصف رقم k هو العنصر k في كل ملف. الأعمدة فيها تكرار فبتتضغط (bitmap و run-length)، والـ CPU يعالجها vectorized بـ SIMD.

الكتابة في الـ column store صعبة لأن فيه ضغط وترتيب، فبيرجعوا LSM: الكتابة في الميموري وبعدين تتدمج. ولو نفس الـ aggregation بيتكرر، خزّن النتيجة (materialized view). الـ OLAP cube أسرع وأقل مرونة.

Checklist التخزين

🧠 اختبر نفسك
١) ليه الـ SSTable يسمح بـ sparse index؟
لأن المفاتيح مرتبة، فتعرف المفتاح محصور بين أنهي مفتاحين وتمسح block صغير.
٢) وظيفة الـ Bloom filter في LSM؟
يقولك بسرعة إن المفتاح أكيد مش موجود، فمتلفش على كل الـ segments.
٣) ليه الـ WAL ضروري في B-tree؟
عشان لو حصل crash في نص تعديل كذا صفحة (split) تقدر ترجّع الشجرة لحالة سليمة.
٤) كويري بتقرا ٣ أعمدة من ١٠٠. تختار تخزين إيه؟
Column-oriented: تقرأ الملفات التلاتة بس، والضغط والـ vectorization يخلوا المسح سريع.