نمذجة البيانات والتخزين

بحيرات البيانات وهندسة Lakehouse

4 دقيقة للقراءة

نموذج lakehouse يجمع بين مرونة بحيرات البيانات وموثوقية مستودعات البيانات. فهم هذا التطور حاسم لمقابلات هندسة البيانات الحديثة.

تطور بحيرة البيانات

تحديات بحيرة البيانات التقليدية

التحديالتأثير
لا معاملات ACIDفساد البيانات، قراءات غير متسقة
فرض المخططقمامة داخل، قمامة خارج
لا فهرسةمسح كامل لكل استعلام
بيانات وصفية قديمةتخطيط استعلام بطيء
لا سفر عبر الزمنلا يمكن التعافي من الكتابات السيئة

حل Lakehouse

┌─────────────────────────────────────────────────────┐
│                  محركات الاستعلام                   │
│     (Spark, Trino, Dremio, Athena, Databricks SQL)  │
├─────────────────────────────────────────────────────┤
│              طبقة صيغة الجدول                       │
│         (Delta Lake, Apache Iceberg, Hudi)          │
├─────────────────────────────────────────────────────┤
│              تخزين الكائنات                          │
│           (S3, GCS, ADLS, MinIO)                    │
└─────────────────────────────────────────────────────┘

Delta Lake

صيغة lakehouse مفتوحة المصدر من Databricks.

الميزات الرئيسية

الميزةالفائدة
معاملات ACIDكتابات متزامنة موثوقة
تطور المخططإضافة/إعادة تسمية الأعمدة بدون إعادة كتابة
السفر عبر الزمناستعلام أي إصدار سابق
Z-orderingتجميع متعدد الأبعاد
تغذية تغيير البياناتبث التغييرات للمصب

العمليات الشائعة

# إنشاء جدول Delta
df.write.format("delta").save("/data/events")

# السفر عبر الزمن - قراءة إصدار سابق
spark.read.format("delta") \
    .option("versionAsOf", 5) \
    .load("/data/events")

# السفر عبر الزمن - قراءة بالطابع الزمني
spark.read.format("delta") \
    .option("timestampAsOf", "2024-01-01") \
    .load("/data/events")

# تحسين و Z-order
from delta.tables import DeltaTable
delta_table = DeltaTable.forPath(spark, "/data/events")
delta_table.optimize().executeZOrderBy("user_id", "event_date")

# تنظيف الإصدارات القديمة (احتفظ 7 أيام)
delta_table.vacuum(168)  # ساعات

سؤال المقابلة: "كيف يحقق Delta Lake معاملات ACID؟"

الجواب: "Delta Lake يستخدم سجل معاملات (_delta_log/) يسجل كل تغيير كـ JSON commits. الكتابات ذرية من خلال التزامن المتفائل—كل كتابة تنشئ ملف commit جديد. القراءات تستخدم السجل لتحديد أي ملفات تقرأ. التعارضات تُكتشف بالتحقق إذا كانت المعاملات الملتزمة تتداخل مع الكتابة الحالية."

Apache Iceberg

صيغة طورتها Netflix، الآن مشروع Apache.

الميزات الرئيسية

الميزةالفائدة
التقسيم المخفيتطور التقسيم بدون إعادة كتابة
تطور المخططدعم كامل (إضافة، حذف، إعادة تسمية، إعادة ترتيب)
السفر عبر الزمنإصدار قائم على اللقطات
تطور التقسيمتغيير التقسيم بدون هجرة
محركات متعددةSpark, Flink, Trino, Hive, Presto

الهندسة

┌─────────────────────────────────────┐
│          الكتالوج (البيانات الوصفية)│
│  (Hive Metastore, Glue, Nessie)     │
├─────────────────────────────────────┤
│        طبقة البيانات الوصفية        │
│   ┌──────────────────────────────┐  │
│   │ قائمة البيان (لقطة)          │  │
│   │   └── ملفات البيان           │  │
│   │       └── مراجع ملفات البيانات│  │
│   └──────────────────────────────┘  │
├─────────────────────────────────────┤
│          ملفات البيانات             │
│    (Parquet, ORC, Avro)             │
└─────────────────────────────────────┘

العمليات الشائعة

-- إنشاء جدول Iceberg
CREATE TABLE events (
    event_id BIGINT,
    event_time TIMESTAMP,
    user_id INT,
    event_type STRING
)
USING iceberg
PARTITIONED BY (days(event_time), bucket(16, user_id));

-- السفر عبر الزمن
SELECT * FROM events VERSION AS OF 123456;
SELECT * FROM events TIMESTAMP AS OF '2024-01-01 00:00:00';

-- تطور التقسيم (لا إعادة كتابة بيانات!)
ALTER TABLE events ADD PARTITION FIELD bucket(8, region);

-- انتهاء صلاحية اللقطات
CALL system.expire_snapshots('events', TIMESTAMP '2024-01-01 00:00:00');

سؤال المقابلة: "ما ميزة التقسيم المخفي في Iceberg؟"

الجواب: "مع التقسيم المخفي، المستخدمون يكتبون استعلامات باستخدام قيم الأعمدة الفعلية (WHERE event_time > '2024-01-01')، و Iceberg يترجم هذا تلقائياً لمسند التقسيم الصحيح. هذا يعني:

  1. تطور التقسيم لا يتطلب تغييرات الاستعلام
  2. لا حاجة لمعرفة مخطط التقسيم
  3. تحويلات التقسيم (يوم، شهر، bucket) مجردة"

Apache Hudi

صيغة طورتها Uber للمعالجة التدريجية.

الميزات الرئيسية

الميزةالفائدة
Upsertsدعم أصلي لتحديثات مستوى السجل
استعلامات تدريجيةقراءة السجلات المتغيرة فقط
فهرسة مستوى السجلبحث وتحديثات سريعة
الضغطدمج الملفات الصغيرة تلقائياً
أنواع الجداولCopy-on-Write مقابل Merge-on-Read

مقارنة أنواع الجداول

الجانبCopy-on-Write (CoW)Merge-on-Read (MoR)
تأخير الكتابةأعلى (إعادة كتابة الملفات)أقل (إلحاق السجلات)
تأخير القراءةأقل (مدمج مسبقاً)أعلى (دمج عند القراءة)
الأفضل لـأعباء العمل الثقيلة القراءةأعباء العمل الثقيلة الكتابة
التخزينأكثر (إعادة كتابة كاملة)أقل (التغييرات فقط)

مقارنة الصيغ

الميزةDelta LakeIcebergHudi
الأصلDatabricksNetflixUber
الاستخدام الأساسيlakehouse عامالتحليلاتCDC/البث
ACID
السفر عبر الزمن
تطور المخططجيدممتازجيد
تطور التقسيممحدودممتازمحدود
Upsertsجيدجيدممتاز
القراءات التدريجيةChange Data Feedتدريجيأصلي
دعم المحركأفضل Sparkمتعدد المحركاتجيد Spark

سؤال المقابلة: "كيف تختار بين Delta و Iceberg و Hudi؟"

إطار الإجابة:

إذا تحتاجاختر
نظام Databricks البيئيDelta Lake
قابلية نقل المحركIceberg
CDC/upserts ثقيلHudi
عمليات بسيطةDelta Lake
مرونة التقسيمIceberg
استيعاب في الوقت الحقيقيHudi

هندسة Medallion

نمط تصميم lakehouse شائع.

┌───────────┐    ┌───────────┐    ┌───────────┐
│  Bronze   │───▶│  Silver   │───▶│   Gold    │
│  (خام)    │    │ (منظف)   │    │  (مجمع)   │
└───────────┘    └───────────┘    └───────────┘
الطبقةالغرضالمخططالجودة
Bronzeاستيعاب خاممخطط عند القراءةكما هو من المصدر
Silverمنظف، موحدمفروضموثق، مزال التكرار
Goldتجميعات الأعمالمنمذججاهز لـ BI

مثال التنفيذ

# Bronze: استيعاب خام
raw_df = spark.read.json("/landing/events/")
raw_df.write.format("delta").mode("append") \
    .save("/bronze/events")

# Silver: تنظيف وإزالة التكرار
bronze_df = spark.read.format("delta").load("/bronze/events")
silver_df = bronze_df \
    .dropDuplicates(["event_id"]) \
    .filter(col("user_id").isNotNull()) \
    .withColumn("event_date", to_date("event_time"))
silver_df.write.format("delta").mode("overwrite") \
    .partitionBy("event_date") \
    .save("/silver/events")

# Gold: تجميعات الأعمال
silver_df = spark.read.format("delta").load("/silver/events")
gold_df = silver_df.groupBy("event_date", "event_type") \
    .agg(count("*").alias("event_count"))
gold_df.write.format("delta").mode("overwrite") \
    .save("/gold/daily_event_summary")

نظرة المقابلة: lakehouse يمثل تقارب بحيرات البيانات والمستودعات. فهم متى تستخدم lakehouse مقابل المستودع التقليدي يُظهر نضج معماري.

بعد ذلك، سنغوص في الأبعاد المتغيرة ببطء وتنفيذاتها. :::

اختبار

الوحدة 3: نمذجة البيانات والتخزين

خذ الاختبار
نشرة أسبوعية مجانية

ابقَ على مسار النيرد

بريد واحد أسبوعياً — دورات، مقالات معمّقة، أدوات، وتجارب ذكاء اصطناعي.

بدون إزعاج. إلغاء الاشتراك في أي وقت.