JSONB مفيد حقًا في PostgreSQL، لكن فرق SaaS في لبنان ومنطقة MENA تلجأ إليه مبكرًا وبشكل واسع جدًا. النتيجة: بيانات غير مفهرسة وكود صعب الصيانة وديون تقنية تتراكم كل ربع سنة.
JSONB مفيد حقًا في PostgreSQL، لكن فرق SaaS تلجأ إليه مبكرًا وبشكل واسع. النتيجة: بيانات غير مفهرسة، وكويري معقدة، وديون تقنية تتراكم كل ربع سنة. هذا المقال يتناول متى يكون JSONB الخيار الصحيح ومتى يكون خطأً، بأمثلة من أنظمة إنتاجية حقيقية في لبنان ومنطقة MENA.
ما المشكلة الحقيقية التي يحلها JSONB؟
يخزن PostgreSQL نوع JSONB مستندات JSON بصيغة ثنائية مفككة تتيح البحث والفهرسة بكفاءة. الميزة موجودة لأن الأنظمة الحقيقية أحيانًا تملك بيانات متغيرة البنية:
- نظام بناء النماذج يحتاج لتخزين إجابات مختلفة لكل نموذج
- كتالوج المنتجات الضخم لا يمكنه امتلاك عمود لكل سمة ممكنة
- سجل التدقيق يحتاج لالتقاط الحالة القديمة والجديدة بغض النظر عن الأعمدة المتغيرة
هذه حالات استخدام حقيقية. المشكلة تكمن في استخدام JSONB لبيانات ذات هيكل علائقي واضح، فقط لأنه يبدو أسرع من كتابة migration.
أين يسبب JSONB ألمًا في الإنتاج
الفلترة على حقول JSON في النطاق الواسع
الخطأ الأكثر شيوعًا هو تخزين بيانات تحتاج للفلترة داخل JSONB دون التفكير في الفهارس.
في نظام SaaS متعدد المستأجرين يخزن metadata مخصصة للطلبات:
SELECT * FROM orders
WHERE workspace_id = $1
AND metadata @> '{"status": "exported"}';
بدون فهرس GIN، هذا مسح تسلسلي لكل طلبات الـ workspace. لمطعم يملك 500,000 طلب، يستغرق هذا الاستعلام ثوانٍ.
يمكن إصلاحه بفهرس GIN جزئي، لكن الفهرس يكبر مع كل مفتاح فريد في المستند ويصبح مكلف الصيانة.
تطور الـ schema يصبح غير مرئي
مع الأعمدة العلائقية، كل تغيير في الـ schema هو ملف migration واضح. مع JSONB، يتطور الـ schema بصمت في كود التطبيق. حقل يُعاد تسميته من export_status إلى sync_status عبر ستة أشهر، ونصف السجلات تستخدم المفتاح القديم والنصف الآخر الجديد.
كود Go يصبح هشًا
مع sqlc والاستعلامات المكتوبة، البيانات العلائقية تُعين مباشرة على هياكل Go. مع JSONB تحتاج json.RawMessage وإلغاء تسلسل يدوي:
type Order struct {
ID uuid.UUID
Metadata json.RawMessage
}
type OrderMetadata struct {
ExportStatus string `json:"export_status"`
SyncedAt time.Time `json:"synced_at"`
}
كل مكان استدعاء يجب أن يتذكر تحليل الـ metadata. المترجم لا يمكنه اكتشاف خطأ إملائي في json:"synced_at". مع الوقت، يتحول كائن الـ metadata إلى مستودع لكل شيء أراد الفريق تجنب كتابة migration له.
أين يكون JSONB الخيار الصحيح
schema متغير حقًا: منشئو النماذج والحقول المخصصة
إذا كان نظامك يسمح لكل workspace بتعريف هيكل حقوله الخاص، فالجداول العلائقية لا يمكنها نمذجة هذا. JSONB هو الجواب الصحيح:
CREATE TABLE form_submissions (
id UUID PRIMARY KEY,
form_id UUID NOT NULL REFERENCES forms(id),
workspace_id UUID NOT NULL,
submitted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
data JSONB NOT NULL
);
القيد الأساسي: أنت لا تحتاج للفلترة أو الترتيب حسب الحقول داخل data. تسترجع تسليمًا بمعرفه أو معرف النموذج، ثم تقرأ المستند كاملًا في كود التطبيق.
سجل التدقيق وتاريخ الأحداث
تخزين الحالة قبل وبعد تغيير الصف مناسب طبيعيًا لـ JSONB:
CREATE TABLE audit_log (
id BIGSERIAL PRIMARY KEY,
workspace_id UUID NOT NULL,
entity_type TEXT NOT NULL,
entity_id UUID NOT NULL,
before_data JSONB,
after_data JSONB,
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
تستعلم عن سجلات التدقيق بالـ entity والنطاق الزمني، لا بالحقول داخل before_data. هذا هو حالة الاستخدام التي بُني JSONB لها.
كائنات الإعدادات والتكوين
إعدادات الـ workspace ذات البنية الاختيارية العميقة مناسبة جيدة:
CREATE TABLE workspace_settings (
workspace_id UUID PRIMARY KEY REFERENCES workspaces(id),
settings JSONB NOT NULL DEFAULT '{}'
);
تقرأ الإعدادات بمعرف الـ workspace وتطبقها في كود التطبيق. لا تحتاج للاستعلام عبر الـ workspaces بقيمة إعداد معين.
قاعدة القرار في الممارسة
قبل اللجوء إلى JSONB، اسأل ثلاثة أسئلة:
1. هل ستحتاج يومًا لفلترة الصفوف بحقل داخل هذا المستند؟ إذا نعم، استخدم عمودًا علائقيًا.
2. هل سيظهر هذا الحقل في ORDER BY أو استعلامات التجميع؟ إذا نعم، استخدم عمودًا علائقيًا.
3. هل الـ schema غير معروف فعلًا في وقت التصميم؟ إذا نعم، JSONB مناسب. إذا كنت تعرف الحقول لكن تريد تجنب كتابة migration، فهذا السبب الخاطئ.
الهجرة خارج JSONB عندما تُخطئ
إذا كان لديك أعمدة JSONB يجب أن تكون علائقية، مسار الهجرة:
-- الخطوة 1: إضافة العمود الحقيقي
ALTER TABLE orders ADD COLUMN export_status TEXT;
-- الخطوة 2: ملء البيانات من JSONB
UPDATE orders
SET export_status = metadata->>'export_status'
WHERE metadata ? 'export_status';
-- الخطوة 3: إضافة فهرس
CREATE INDEX CONCURRENTLY idx_orders_export_status
ON orders (workspace_id, export_status)
WHERE export_status IS NOT NULL;
الدروس الرئيسية
الفرق التي تحصل على هذا بشكل صحيح تستخدم JSONB للبيانات المتغيرة حقًا التي لن تستعلم عنها، والأعمدة العلائقية لكل ما تحتاج لفلترته أو ترتيبه أو تجميعه. إغراء تجنب migrations بوضع البيانات في JSONB حقيقي، لكن الديون تتراكم أسرع مما تتوقع، خاصة في الأنظمة متعددة المستأجرين حيث تصل طلبات التخصيص من العملاء باستمرار.
هل تحتاج مساعدة في تصميم طبقة البيانات لنظام SaaS الخاص بك؟
Voxire تبني وتراجع معماريات البيانات للواجهات الخلفية لمنتجات SaaS في لبنان ومنطقة MENA. إذا كان schema قاعدة بياناتك ينمو بطرق تبطئ فريقك، تواصل معنا.
https://voxire.com/get-a-quote/



