المستوى المطلوب: متوسط — مفترض إن عندك إلمام بـ SQL أساسي و JOIN و INDEX، وعملت deploy لـ PostgreSQL مرة واحدة على الأقل.
لو dashboard المبيعات عندك بياخد 4 دقائق يفتح، المشكلة مش في حجم البيانات. المشكلة إن نفس الـ JOIN على 18 مليون صف بيتحسب من الصفر كل ما حد يضغط refresh. Materialized View بسطر SQL واحد بيخزّن نتيجة الاستعلام على القرص، فالاستعلام التاني بيرد في 30ms بدل 240,000ms. ركز على الفرق ده — ده 8000x تحسين من غير ما تلمس الكود.
Materialized Views: الفرق بين الـ View العادي وحفظ النتيجة على القرص
المشكلة باختصار
عندك جدول orders فيه 18 مليون صف، وجدول customers فيه 240 ألف، وجدول products فيه 12 ألف. الـ dashboard بيعرض إجمالي المبيعات لكل تصنيف منتج لكل دولة آخر 90 يوم. الاستعلام بيتعمل JOIN ثلاثي + GROUP BY + window function. الزمن: 240 ثانية في الذروة، 90 ثانية في الـ off-peak.
المدير بيفتح الـ dashboard 18 مرة في اليوم. يعني إنت بتحرق 72 دقيقة من الـ DB يومياً على نفس الحساب اللي بيرجّع نفس النتيجة. لو 4 مدراء بيفتحوا في نفس اللحظة، الـ DB بتقفل والـ API بيرجع 504 timeout.
الفكرة بمثال محل العصير
تخيل إن في محل عصير، الزبون كل ما يطلب كوب برتقال، البائع بيعصر 8 برتقالات قدامه على الفور. المحل ساعة الذروة بياخد طلب واحد كل دقيقتين. الحل اللي البائع الذكي بيعمله: يعصر 50 كوب الصبح، يحطهم في الفريزر، وكل ما حد يطلب يديله الكوب جاهز في 5 ثواني.
الـ View العادي زي البائع اللي بيعصر كل مرة. الـ Materialized View زي البائع اللي عصر مسبقاً وحط الكوب في الفريزر. الفرق: مع Materialized View النتيجة محفوظة على القرص، مش بتتحسب من جديد لما حد يستعلم منها.
التعريف العلمي الدقيق
الـ View العادي في SQL ده عبارة عن استعلام محفوظ بالاسم. لما تستعلم منه، PostgreSQL بيستبدل اسمه بنص الاستعلام كامل و ينفّذه. Materialized View بالظبط هو snapshot من نتيجة الاستعلام، محفوظة كجدول حقيقي على القرص (relation type 'm' في pg_class). بيتحدّث صراحةً بأمر REFRESH MATERIALIZED VIEW. الفكرة دي اتقدمت أول مرة في ورقة Larson و Yang 1985 وProtocols الأكاديمية للـ deferred view maintenance، واتضافت لـ PostgreSQL في إصدار 9.3 سنة 2013.
الكود: من 240 ثانية لـ 30 مللي ثانية
الاستعلام البطيء الأصلي:
SELECT
p.category,
c.country,
SUM(o.total_amount) AS revenue,
COUNT(*) AS orders_count
FROM orders o
JOIN customers c ON c.id = o.customer_id
JOIN products p ON p.id = o.product_id
WHERE o.created_at >= NOW() - INTERVAL '90 days'
GROUP BY p.category, c.country;
-- Execution time: 241,847 ms على PostgreSQL 16، 16GB RAM