المعمارية English

arc42 §7 — عرض النشر

الحالة: مسودة v0.1 · التاريخ: 2026-07-20 يتتبع إلى: ADR-0001 (الاستضافة)، ADR-0003 (Kubernetes)، CON-13 (إقامة البيانات)، QAS-SCL-01، QAS-OPS-01 وثيقة مرافقة: تعمّق DevOps — تفاصيل الـ container الداخلية، وCI/CD، والتحصين


7.1 نظرة عامة على البنية التحتية#

flowchart TB
    subgraph INTERNET["🌐 Internet"]
        MOB["📱 Mobile clients"]
        WEB["🌐 Web / Admin"]
        VERIFY["🔍 Public certificate<br/>verification"]
    end

    subgraph KSA["🇸🇦 Kingdom of Saudi Arabia — CON-13"]
        subgraph PRIMARY["Primary Region — Riyadh"]
            subgraph EDGE["Edge"]
                CDN["CDN"]
                WAF["WAF + DDoS"]
                LB["Load Balancer"]
            end

            subgraph K8S["Kubernetes Cluster (managed control plane)"]
                subgraph NP1["Node pool: general"]
                    API["API pods<br/><i>HPA: RPS + p95</i>"]
                    WRK["Worker pods<br/><i>HPA: queue depth</i>"]
                    SCH["Scheduler<br/><i>singleton</i>"]
                end
                subgraph NP2["Node pool: memory"]
                    PDF["Document renderer"]
                    SRCH["Search engine"]
                    AV["Malware scanner"]
                end
                subgraph NP3["Node pool: media — conditional, ADR-0005"]
                    MEDIA["Media servers<br/><i>hostNetwork</i>"]
                    TURN["TURN"]
                end
            end

            subgraph DATA["Managed data services"]
                PG[("PostgreSQL<br/>primary + replica<br/>RLS enabled")]
                RD[("Redis<br/>cache · queues · locks")]
                OBJ[("Object Storage<br/>encrypted")]
            end
        end

        subgraph DR["DR Region — Jeddah"]
            PGDR[("PostgreSQL<br/>standby")]
            OBJDR[("Object Storage<br/>replica")]
        end
    end

    MOB & WEB --> CDN --> WAF --> LB --> API
    VERIFY --> WAF
    MOB -.->|"WebRTC — bypasses ingress"| MEDIA
    MOB -.->|"signed URL — direct"| OBJ

    API --> PG & RD & OBJ & SRCH
    WRK --> PG & RD & OBJ & SRCH
    WRK --> PDF & AV
    SCH --> RD

    PG ==>|"async replication<br/><b>never leaves KSA</b>"| PGDR
    OBJ ==>|"replication"| OBJDR

    style KSA fill:#e8f5e9,stroke:#2d8659
    style DR fill:#f0f0f0,stroke:#888
    style NP3 fill:#fff4e5,stroke:#d98c1f

منطقة التعافي من الكوارث (DR) هي الخاصية الحاسمة في ADR-0001. Oracle هو المزوّد الوحيد القابل للتطبيق الذي يملك منطقتين داخل المملكة في مرحلة الإتاحة العامة (GA)، ما يجعل التعافي من الكوارث قابلًا للتحقيق دون أن تغادر بايت واحدة أرض السعودية. وكل بديل آخر يفرض أن يكون الـ DR إمّا عابرًا للحدود — فيعيد إدخال مسار عمل الامتثال للنقل عبر الحدود بأكمله — أو داخل المنطقة نفسها بتعدد مناطق الإتاحة (multi-AZ)، وهو ما لا يحمي من فقدان منطقة كاملة.


7.2 البيئات#

البيئة الغرض الحجم البيانات
Local التطوير Docker Compose بيانات اصطناعية مُبذَّرة
CI الاختبارات الآلية containers مؤقتة Fixtures
Staging التحقق قبل الإنتاج عنقود بحدّه الأدنى، بالطوبولوجيا نفسها اصطناعية فقط — لا بيانات شخصية إنتاجية إطلاقًا
Production التشغيل الحيّ كامل، بـ autoscaling حيّة

⚠️ يجب ألا تحتوي بيئة Staging على بيانات شخصية إنتاجية إطلاقًا. نسخ قاعدة بيانات إنتاجية إلى بيئة أدنى من أشيع خروقات PDPL عمليًا — فبيئة staging لديها ضوابط وصول أضعف، وعدد أكبر من الأشخاص الذين يملكون الوصول، ولا سجل تدقيق. وإذا لُزمت أحجام واقعية، فوَلِّدها.

قاعدة تكافؤ البيئات: تشغّل staging الـ manifests نفسها بعدد نسخ (replicas) مختلف وطلبات موارد مختلفة. والتباعد في الطوبولوجيا يعني أن staging تكفّ عن التنبؤ بسلوك الإنتاج، وهو غرضها الوحيد.


7.3 مجموعات العُقد (node pools) والتوزيع#

المجموعة أعباء العمل محرّك الحجم ملاحظات
general API، الـ workers، الـ scheduler توازن CPU/ذاكرة مُوسَّعة تلقائيًا؛ الأغلبية المرنة
memory مُصيِّر المستندات، البحث، فاحص البرمجيات الخبيثة الذاكرة المُصيِّر يستهلك مئات الميغابايتات لكل عملية تصيير؛ والبحث يحتفظ بفهرسه في RAM؛ وفاحص الفيروسات يحمل قاعدة تواقيع بحجم ~1 GB
media (مشروطة) خوادم الوسائط، TURN الشبكة hostNetworkpod وسائط واحد لكل عقدة. وحدة التوسّع هي العقدة، بزمن إقلاع يُقاس بالدقائق.

قيود التوزيع#

القيد السبب
توزيع pods الـ API عبر مناطق الإتاحة فشل منطقة إتاحة يجب ألا يُسقط الـ API
الـ scheduler: نسخة واحدة بالضبط المهام المجدولة المكرّرة ستُرسل الإشعارات مرتين وتخصم قيمة الاشتراكات مرتين
pods الوسائط: واحد لكل عقدة، مع anti-affinity تعارض منفذ المضيف
البحث: وحدة تخزين دائمة، مثبّتة حالة مُحتفَظ بها (stateful)
الـ workers: مفصولون حسب مدة المهمة كي لا ينتظر نشرُ إشعار مدته 60 ثانية مهمةَ PDF مدتها 600 ثانية

7.4 طبقة البيانات#

PostgreSQL#

الجانب التصميم
الطوبولوجيا Primary + standby متزامن (المنطقة نفسها)، وstandby غير متزامن (منطقة الـ DR)
RLS مُفعَّل بـ FORCE ROW LEVEL SECURITY على كل الجداول المحصورة بالمستأجر (ADR-0007)
دور التطبيق أقل الصلاحيات — لا UPDATE/DELETE على سجل التدقيق، ولا BYPASSRLS
تجميع الاتصالات (pooling) ⚠️ التجميع بوضع المعاملة (transaction-mode pooling) يتفاعل بخطورة مع متغيرات جلسة RLS — انظر ADR-0007. استخدم إعدادات محصورة بالمعاملة وأعد ضبطها في الـ middleware المُنهي.
مسار التوسّع نسخ قراءة (read replicas) لإسقاطات (projections) التقارير ← تقسيم (partitioning) الجداول عالية الحجم. مُحفِّزات موثّقة، لا ردود فعل.
النسخ الاحتياطي أرشفة WAL مستمرة + لقطات دورية، مشفّرة، ومحتفظ بها داخل المملكة

Redis#

الذاكرة المؤقتة، والطوابير، والأقفال، وعدّادات تحديد المعدل. ليس مخزنًا معمّرًا — فأي شيء يجب أن ينجو من فقدان Redis يعيش في PostgreSQL. ومهام الطوابير هي الاستثناء البارز: فقدانها محتمَل لأن المعاملة التي أنشأتها قد أُودعت (committed) بالفعل (انظر قواعد الـ pipeline).

التخزين الكائني (Object Storage)#

الحاوية (bucket) المحتويات الوصول
quarantine/ رفعات لم تُفحص بعد لا وصول للقراءة من التطبيق أو من واجهة المسؤول
documents/ الوثائق القانونية المعتمدة signed URL، بمدة صلاحية ≤ 5 دقائق، بعد التخويل فقط
templates/ مكتبة القوالب signed URL، خلف CDN
certificates/ ملفات PDF المولَّدة signed URL، للمالك والمُتحقِّق
public-assets/ ثابتة CDN، عامة

الكل مشفَّر أثناء السكون. والوثائق القانونية مشفّرة إضافيًا بتشفير مغلَّف على مستوى التطبيق (envelope encryption) (الأمن §4).


7.5 طوبولوجيا الشبكة#

flowchart TB
    subgraph PUB["Public subnet"]
        LB2["Load balancer"]
        NAT["NAT gateway"]
    end
    subgraph PRIV2["Private subnet — no inbound internet"]
        NODES["Kubernetes nodes"]
    end
    subgraph DATA2["Data subnet — no internet route at all"]
        DB2[("PostgreSQL")]
        RD2[("Redis")]
    end

    INT["🌐"] --> LB2 --> NODES
    NODES --> NAT --> INT
    NODES --> DB2 & RD2
    DB2 -.->|"❌ no route"| INT

    style DATA2 fill:#e8f5e9,stroke:#2d8659
القاعدة التفصيل
الشبكة الفرعية للبيانات بلا مسار إلى الإنترنت ليست "محجوبة بجدار ناري" — لا مسار موجود أصلًا
العُقد في شبكات فرعية خاصة الخروج عبر NAT فقط
NetworkPolicy بالرفض الافتراضي، دخولًا وخروجًا، لكل namespace مع استثناء لـ DNS على UDP و TCP 53 — ونسيان TCP هو السبب الكلاسيكي لـ "كل شيء تعطّل"
قائمة سماح للخروج نحو مزوّدي الدفع/الفيديو/الـ SMS ⚠️ الـ NetworkPolicy القياسية تأخذ نطاقات CIDR لا أسماء DNS، والمزوّدون يدوّرون عناوين anycast. يتطلب ذلك سياسة FQDN أصيلة في الـ CNI أو وكيل خروج (egress proxy) — اختر الـ CNI عن قصد بدل قبول الافتراضي.
طبقة الوسائط: مسار منفصل UDP، وhostNetwork، بلا ingress، وبلا mesh. وبقواعد جدار ناري خاصة بها.

7.6 التعافي من الكوارث#

الأهداف — مقترحة، وتتطلب تأكيد الراعي#

السيناريو RTO RPO
فشل pod / عقدة مفردة 0 (تلقائي) 0
فشل منطقة إتاحة < 5 دقائق 0
فشل منطقة كاملة < 4 ساعات < 15 دقيقة
حذف بيانات عرضي < 4 ساعات < 15 دقيقة (استعادة إلى نقطة زمنية)

⚠️ لا يُذكر أي RTO أو RPO في أي وثيقة مصدر. هذه مقترحات من المعماري مشتقة من QAS-AVL-02 (توفّر 99.5 % في ساعات العمل) ومن طبيعة عبء العمل — مواعيد مجدولة وتنزيل وثائق، لا مدفوعات لحظية. وهي تحتاج اعتمادًا صريحًا من الراعي، لأنها تقود طوبولوجيا الـ standby ومن ثمّ التكلفة.

التحويل بين المناطق#

يدويّ، وعن قصد. فالتحويل التلقائي عبر المناطق يخاطر بانقسام الدماغ (split-brain) في نظام لا يبرّر هدف توفّره ذلك. والإجراء — ترقية الـ standby في جدة، وإعادة توجيه الـ DNS، والتحقق من تأخّر تكرار التخزين الكائني — يجب أن يكون موثّقًا ومُتمرَّنًا عليه، لا نظريًا.

ما لا يغطّيه التكرار (replication)#

الخطر التخفيف
الفساد المنطقي (ترحيل خاطئ يتكرر فورًا) استعادة إلى نقطة زمنية، لا مجرد ترقية standby
الحذف العرضي PITR + حذف ناعم للكيانات عالية القيمة
برامج الفدية / اختراق بيانات الاعتماد نسخ احتياطية غير قابلة للتعديل وباعتمادات منفصلة
فقدان فهرس البحث قابل لإعادة البناء من PostgreSQL — فالفهرس ليس مرجعًا موثوقًا أبدًا

الصف الأخير خاصية معمارية مقصودة: لأن فهرس البحث projection ويُرشَّح بعديًا مقابل قاعدة البيانات (Flow 5)، فإن فقدانه كليًا حادثة أداء لا حادثة فقدان بيانات.


7.7 السعة — حجم الإطلاق#

تقديرات بترتيب المقدار فقط. الأرقام الحقيقية تتطلب عروض أسعار المزوّدين التي ما تزال معلّقة في ADR-0001.

المكوّن عند الإطلاق مُحفِّز النمو
pods الـ API 3 (الحد الأدنى)، توسّع تلقائي إلى 10 p95 > 300 ms بشكل مستمر
الـ workers 2 لكل صنف طابور عمق الطابور / عمر أقدم مهمة
المُصيِّر 1، مع التقلّص إلى صفر عند الخمول عمق طابور التصيير
البحث عقدة واحدة حجم الفهرس / زمن استجابة الاستعلام
PostgreSQL متواضع، primary + standby تشبّع الاتصالات ← نسخ قراءة
Redis صغير ضغط الذاكرة
الوسائط 0 عند الإطلاق التزامن المحجوز (تنبّؤي)

خاصيتان جديرتان بالاستغلال#

1. الطلب على الفيديو معروف مسبقًا. الاستشارات محجوزة، لذا فسعة QAS-SCL-02 مجدولة لا عشوائية. وينبغي للـ scheduler أن يُحمِّي سعة الوسائط مسبقًا انطلاقًا من تقويم المواعيد، مع HPA تفاعلي كشبكة أمان فقط. وهذا مهم لأن توسّع الوسائط يجري بحبيبة العقدة وببطء — والتفاعل بعد ثلاث دقائق من بدء استشارة مدفوعة يعني استردادًا.

2. الحمل يتركّز في ساعات العمل السعودية. من الأحد إلى الخميس، 08:00–20:00 بتوقيت AST. وينبغي تقليص السعة خارج الذروة بحزم (QAS-OPS-02)؛ وافترض نسبة ذروة إلى متوسط لا تقل عن 5:1.


7.8 البنود المفتوحة#

البند يعيق
مصفوفة خدمات OCI في me-riyadh-1 — توفّر Redis المُدار والـ registry والأسرار غير مُتحقَّق منه يحدّد ما إذا كانت عدة مكوّنات مُدارة ذاتيًا أم مُدارة من المزوّد (ADR-0001 الفجوة 1)
عروض أسعار المزوّدين نموذج التكلفة بأكمله
تسجيل OQ-08 ما إذا كانت مجموعة عُقد الوسائط موجودة، وحجمها
اختيار مزوّد ADR-0005 ما إذا كانت مجموعة عُقد media والـ TURN والـ hostNetwork موجودة أصلًا
اختيار الـ CNI ما إذا كانت سياسة الخروج بـ FQDN متاحة (§7.5)
اعتماد RTO/RPO طوبولوجيا الـ standby والتكلفة
القائد التقني · أمير هارون مسودة v0.1 · بحث وتصميم فقط