مرحبا يا هبر!
نستمر في دراسة مدى قابلية تطبيق مبادئ البرمجة الوظيفية في تصميم ERP. في
مقال سابق ، تحدثنا عن سبب هذا الأمر الضروري ، ووضع أسس الهندسة المعمارية ، وأظهرنا بناء تلاعب بسيطة باستخدام مثال بيان عكسي. في الواقع ، يُقترح نهج
تحديد مصادر الحدث ، ولكن نظرًا لفصل قاعدة البيانات إلى الأجزاء الثابتة والقابلة للتغيير ، نحصل في نظام واحد على مجموعة من مزايا الخريطة / تقليل التخزين ونظام إدارة قواعد البيانات في الذاكرة ، مما يحل مشكلة الأداء ومشكلة قابلية التوسع. في هذه المقالة سوف أخبر (وأعرض نموذجًا أوليًا في
وقت تشغيل TypeScript و
Deno ) كيفية تخزين سجلات الأرصدة الفورية في مثل هذا النظام وحساب التكلفة. بالنسبة لأولئك الذين لم يقرأوا المقال الأول - ملخص موجز:
1. مجلة الوثائق . نظام تخطيط موارد المؤسسات المبني على أساس RDBMS هو حالة قابلة للتغيير كبيرة مع إمكانية الوصول التنافسي ، وبالتالي فهي ليست قابلة للتوسعة ، ضعيفة السمع ، وغير موثوق بها في العملية (إنها تسمح بعدم تناسق البيانات). في نظام تخطيط موارد المؤسسات الوظيفية ، يتم تنظيم جميع البيانات في شكل مجلة مرتبة زمنياً من الوثائق الأولية غير القابلة للتغيير ، ولا يوجد شيء آخر غير هذه الوثائق. يتم حل الارتباطات من مستندات جديدة إلى مستندات قديمة بمعرف كامل (وليس العكس بالعكس) ، وجميع البيانات الأخرى (الأرصدة والسجلات والمقارنات) عبارة عن ملفوف محسوب ، أي النتائج المخزنة مؤقتًا للوظائف الخالصة في تدفق المستند. يمنحنا الافتقار إلى الحالة + وظيفة المراجعة الموثوقية المتزايدة (تناسب سلسلة المفاتيح هذا المخطط بشكل مثالي) ، وكبناء ، نحصل على تبسيط لنظام التخزين + ذاكرة التخزين المؤقت التكيفية بدلاً من الثابت (منظم على أساس الجداول).
هذه هي الطريقة التي يبدو بها جزء البيانات في ERP الخاص بنا// { "type": "person", // , "key": "person.0", // "id": "person.0^1580006048190", // + ID "erp_type": "person.retail", "name": " " } // "" { "type": "purch", "key": "purch.XXX", "id": "purch.XXX^1580006158787", "date": "2020-01-21", "person": "person.0^1580006048190", // "stock": "stock.0^1580006048190", // "lines": [ { "nomen": "nomen.0^1580006048190", // "qty": 10000, "price": 116.62545127448834 } ] }
2. الحصانة والتحول . تنقسم مجلة الوثائق إلى جزأين غير متساويين:
- يكمن الجزء الكبير غير القابل للتغيير في ملفات JSON ، وهو متاح للقراءة المتسلسلة ، ويمكن نسخه إلى عقد الخادم ، مما يضمن تزامن القراءة. يتم تخزين الذاكرة المؤقتة المحسوبة على الجزء الثابت ، وحتى يتم التغيير ، لن يتم تغيير نقاط المناعة أيضًا (على سبيل المثال ، النسخ المتماثل).
- الجزء الأصغر القابل للتغيير هو البيانات الحالية (من حيث المحاسبة - الفترة الحالية) ، حيث يمكنك تحرير وإلغاء المستندات (ولكن ليس الحذف) ، وإدراج العلاقات بأثر رجعي وإعادة تنظيمها (على سبيل المثال ، مطابقة الإيصالات بالنفقات ، وإعادة حساب التكاليف ، إلخ. ) .. يتم تحميل البيانات القابلة للتغيير في الذاكرة ككل ، والتي توفر حساب الإلتفاف السريع وآلية معاملات بسيطة نسبيًا.
3. الإلتواء . نظرًا لعدم وجود دلالات JOIN ، فإن لغة SQL غير مناسبة ، وتتم كتابة جميع الخوارزميات بأسلوب وظيفي للمرشح / التصغير ، وهناك أيضًا مشغلات (معالجات الأحداث) لأنواع معينة من المستندات. يسمى مرشح / تقليل الحساب الإلتفاف. تبدو خوارزمية الالتفاف الخاصة بمطور التطبيق وكأنها تمر بالكامل من خلال مجلة المستندات ، ومع ذلك ، فإن kernel تنفذ عملية التحسين أثناء التنفيذ - يتم الحصول على النتيجة الوسيطة المحسوبة من الجزء الثابت من ذاكرة التخزين المؤقت ثم "يتم حسابها" من الجزء القابل للتغيير. وبالتالي ، بدءًا من الإطلاق الثاني ، يتم احتساب الإلتواء بالكامل في ذاكرة الوصول العشوائي (RAM) ، والتي تأخذ كسور الثانية في مليون مستند (سنعرض هذا مع أمثلة). يتم حساب الإلتواء في كل مكالمة ، حيث أنه من الصعب للغاية تتبع جميع التغييرات في المستندات القابلة للتغيير (الطريقة الحتمية التفاعلية) ، وتكون العمليات الحسابية في ذاكرة الوصول العشوائي رخيصة ، ويتم تبسيط رمز المستخدم مع هذا النهج إلى حد كبير. يمكن للالتفاف استخدام نتائج التلفيفات الأخرى ، واستخراج المستندات حسب المعرف ، والبحث عن المستندات في ذاكرة التخزين المؤقت العليا حسب المفتاح.
4. إصدار الوثيقة والتخزين المؤقت . يحتوي كل مستند على مفتاح فريد ومعرف فريد (مفتاح + طابع زمني). يتم تنظيم المستندات التي تحتوي على نفس المفتاح في مجموعة ، يكون السجل الأخير منها حاليًا (حاليًا) ، والباقي تاريخي.
ذاكرة التخزين المؤقت هي كل ما يمكن حذفه واستعادته مرة أخرى من دفتر يومية المستندات عند بدء تشغيل قاعدة البيانات. يحتوي نظامنا على 3 ذاكرة تخزين مؤقت:
- ذاكرة التخزين المؤقت المستند مع وصول معرف. عادة ، هذه هي الدلائل والوثائق شبه الدائمة ، مثل المجلات معدل النفقات. ترتبط سمة التخزين المؤقت (نعم / لا) بنوع المستند ، وتتم تهيئة ذاكرة التخزين المؤقت في البداية الأولى لقاعدة البيانات ثم يتم دعمها بواسطة kernel.
- أعلى مخبأ للوثائق مع وصول المفتاح. يخزن أحدث إصدارات إدخالات الدليل والسجلات الفورية (مثل الأرصدة والأرصدة). ترتبط علامة الحاجة إلى التخزين المؤقت العلوي بنوع المستند ، ويتم تحديث ذاكرة التخزين المؤقت العليا بواسطة kernel عند إنشاء / تعديل أي مستند.
- ذاكرة التخزين المؤقت الإلتفاف المحسوبة من الجزء غير القابل للتغيير من قاعدة البيانات عبارة عن مجموعة من أزواج المفاتيح / القيمة. مفتاح الالتفاف هو تمثيل سلسلة لرمز الخوارزمية + القيمة الأولية المتسلسلة للمجمع (التي يتم فيها نقل معلمات حساب الإدخال) ، وتكون نتيجة الالتفاف هي القيمة النهائية المتسلسلة للمجمع (يمكن أن تكون كائنًا أو مجموعة معقدة).
تخزين الأرصدة
ننتقل إلى الموضوع الفعلي للمادة - تخزين المخلفات. أول ما يتبادر إلى الذهن هو تنفيذ الباقي كإلتفاف ، والذي سيكون معلمة الإدخال منه مجموعة من المحللين (على سبيل المثال ، المصطلح + مستودع + دفعة). ومع ذلك ، في نظام ERP ، نحتاج إلى حساب التكلفة ، والتي من الضروري مقارنة التكاليف مع الأرصدة (خوارزميات FIFO ، الدفعة FIFO ، متوسط المستودع - نظرياً يمكننا متوسط التكلفة لأي مزيج من المحللين). بمعنى آخر ، نحن بحاجة إلى الباقي ككيان مستقل ، وبما أن كل شيء مستند في نظامنا ، فإن الباقي هو أيضًا مستند.
يتم إنشاء مستند بنوع "الرصيد" من قِبل المشغل وقت نشر سطور مستندات الشراء / البيع / الحركة ، إلخ. مفتاح الرصيد هو مزيج من المحللين ، أرصدة مع نفس المفتاح تشكل مجموعة تاريخية ، يتم تخزين العنصر الأخير منها في ذاكرة التخزين المؤقت العليا ومتاحة على الفور. الأرصدة ليست منشورات ، وبالتالي لا يتم تلخيصها - السجل الأخير له صلة ، وتحتفظ السجلات الأولى بمحفوظات.
يخزن الرصيد الكمية بوحدات التخزين والكمية بالعملة الرئيسية ، ويقسم الثانية إلى الأولى - نحصل على التكلفة الفورية عند تقاطع المحلل. وبالتالي ، فإن النظام لا يخزن فقط التاريخ الكامل للباقي ، ولكن أيضًا التاريخ الكامل للتكلفة ، والذي يعد ميزة إضافية لمراجعة النتائج. الرصيد خفيف الوزن ، والحد الأقصى لعدد الأرصدة يساوي عدد سطور المستندات (في الواقع أقل إذا تم تجميع الخطوط حسب مجموعة المحللين) ، ولا يعتمد عدد سجلات الرصيد الأعلى على حجم قاعدة البيانات ، ويتم تحديده بعدد توليفات المحللين المشاركين في التحكم في الأرصدة وحساب التكلفة ، وبالتالي فإن الحجم لدينا ذاكرة التخزين المؤقت أعلى دائما يمكن التنبؤ بها.
آخر المواد الاستهلاكية
في البداية ، يتم تكوين الأرصدة بواسطة مستندات إيصال من نوع "الشراء" ويتم تعديلها بواسطة أي مستندات نفقة. على سبيل المثال ، يقوم المشغل لمستند المبيعات بما يلي:
- مقتطفات التوازن الحالي من أعلى مخبأ
- يتحقق توافر الكمية
- يحفظ رابطًا إلى الرصيد الحالي في سطر المستند ، والتكلفة الفورية
- يولد ميزانية جديدة مع انخفاض المبلغ والمبلغ
مثال على التغيير في الرصيد عند البيع
// { "type": "bal", "key": "bal|nomen.0|stock.0", "id": "bal|nomen.0|stock.0^1580006158787", "qty": 11209, // "val": 1392411.5073958784 // } // "" { "type": "sale", "key": "sale.XXX", "id": "sale.XXX^1580006184280", "date": "2020-01-21", "person": "person.0^1580006048190", "stock": "stock.0^1580006048190", "lines": [ { "nomen": "nomen.0^1580006048190", "qty": 20, "price": 295.5228788368553, // "cost": 124.22263425781769, // "from": "bal|nomen.0|stock.0^1580006158787" // - } ] } // { "type": "bal", "key": "bal|nomen.0|stock.0", "id": "bal|nomen.0|stock.0^1580006184281", "qty": 11189, "val": 1389927.054710722 }
TypeScript المستند معالج فئة رمز
import { Document, DocClass, IDBCore } from '../core/DBMeta.ts' export default class Sale extends DocClass { static before_add(doc: Document, db: IDBCore): [boolean, string?] { let err = '' doc.lines.forEach(line => { const key = 'bal' + '|' + db.key_from_id(line.nomen) + '|' + db.key_from_id(doc.stock) const bal = db.get_top(key, true)
بالطبع ، لن يكون من الممكن تخزين التكلفة مباشرة في خطوط النفقات ، ولكن يمكنك أخذها بالرجوع إليها من الميزانية العمومية ، ولكن الحقيقة هي أن الأرصدة عبارة عن مستندات ، وهناك الكثير منها ، ومن المستحيل تخزين كل شيء مؤقتًا ، والحصول على مستند عن طريق المعرف عن طريق القراءة من القرص باهظ التكلفة ( كيفية فهرسة الملفات المتسلسلة للوصول السريع - سأخبرك في المرة القادمة).
المشكلة الرئيسية التي أشار إليها المعلقون هي أداء النظام ، ولدينا كل شيء لقياسه على كميات البيانات ذات الصلة نسبياً.
توليد البيانات المصدر
سيتألف نظامنا من
5000 طرف مقابل (موردون وعملاء) ، و
3000 عنصر ، و
50 مستودعًا ، و
100 ألف مستند من كل نوع - شراء ونقل وبيع. يتم إنشاء المستندات بشكل عشوائي ، بمعدل 8.5 أسطر لكل وثيقة. تقوم خطوط الشراء والمبيعات بإنشاء معاملة واحدة (وتوازن واحد) ، وينشئ خطان للحركة ، مما ينتج عنه وثائق أساسية
بمقدار 300 ألف ، حوالي
3.4 مليون معاملة ، وهو ما يتوافق مع الحجم الشهري لتخطيط موارد المؤسسات في المقاطعات. نحن ننتج الجزء القابل للتغيير بنفس الطريقة ، فقط بحجم أقل 10 مرات.
نقوم بإنشاء المستندات باستخدام برنامج
نصي . لنبدأ بالمشتريات ، خلال بقية المستندات ، سيتحقق المشغل من الرصيد عند تقاطع العنصر والمستودع ، وإذا لم يمر سطر واحد على الأقل ، فسيحاول البرنامج النصي إنشاء مستند جديد. يتم إنشاء الأرصدة تلقائيًا بواسطة المشغلات ، والحد الأقصى لعدد مجموعات المحللين يساوي عدد التسميات * عدد المستودعات ، أي
150K.DB وذاكرة التخزين المؤقت الحجم
بعد انتهاء البرنامج النصي ، سنرى قياسات قاعدة البيانات التالية:
- جزء ثابت: 3.7kk documents (300k primary، the restances ) - ملف 770 Mb
- الجزء القابل للتغيير: 370 كيلو مستند ( 30 كيلو أساسي ، والأرصدة الباقية) - ملف 76 ميجا بايت
- أعلى ذاكرة التخزين المؤقت للمستندات: 158 كيلو مستند (مراجع + شريحة الأرصدة الحالية) - ملف 20 ميجا بايت
- ذاكرة التخزين المؤقت للمستند: مستندات 8.8 كيلو بايت (الدلائل فقط) - ملف <1 ميجا بايت
المقارنة
تهيئة القاعدة. في حالة عدم وجود ملفات ذاكرة التخزين المؤقت ، تنفذ قاعدة البيانات في البداية أول فحص كامل:
- ملف بيانات غير قابل للتغيير (تعبئة ذاكرة التخزين المؤقت لأنواع المستندات المخزنة مؤقتًا) - 55 ثانية
- ملف البيانات القابل للتغيير (تحميل البيانات بالكامل في الذاكرة وتحديث ذاكرة التخزين المؤقت العليا) - 6 ثوانٍ
عند وجود ذاكرة التخزين المؤقت ، يكون رفع القاعدة أسرع:
- ملف بيانات قابل للتغيير - 6 ثوانٍ
- أعلى ملف ذاكرة التخزين المؤقت - 1.8 ثانية
- مخابئ أخرى - أقل من 1 ثانية
أي تحويل للمستخدم (على سبيل المثال ، استخدم
البرنامج النصي لإنشاء ورقة الدوران) في المكالمة الأولى ، يقوم بإجراء مسح للملف غير القابل للتغيير ، وتم بالفعل مسح البيانات القابلة للتغيير في ذاكرة الوصول العشوائي:
- ملف البيانات غير قابل للتغيير - 55 ثانية
- مجموعة قابلة للتغيير في الذاكرة - 0.2 ثانية
في المكالمات اللاحقة ، عندما تتطابق مع معلمات الإدخال ، تقوم
دالة الإرجاع
() بإرجاع النتيجة في
0.2 ثانية ، أثناء القيام بما يلي في كل مرة:
- استخراج النتيجة من تقليل ذاكرة التخزين المؤقت حسب المفتاح (مع مراعاة المعلمات)
- مسح صفيف قابل للتغيير في الذاكرة (مستندات 370k )
- "حساب" النتيجة من خلال تطبيق خوارزمية الالتفاف على المستندات التي تمت تصفيتها ( 20 كيلو )
النتائج جذابة للغاية لمثل هذه الكميات من البيانات ، ولاب توب المحمول أحادي النواة ، والغياب الكامل لأي DBMS (لا ننسى أن هذا مجرد نموذج أولي) ، وخوارزمية ذات مسار واحد في لغة TypeScript (التي لا تزال تعتبر خيارًا تافهًا للمؤسسات - تطبيقات الخلفية).
التحسين الفني
بعد فحص أداء التعليمات البرمجية ، وجدت أن أكثر من 80٪ من الوقت يقضي في قراءة الملف وتحليل Unicode ، وهما
File.read () و
TextDecoder () . بالإضافة إلى ذلك ، فإن
واجهة الملفات عالية المستوى في Deno غير متزامنة فقط ، وكما
اكتشفت مؤخرًا ، فإن سعر
async / await مرتفع جدًا بالنسبة لمهمتي. لذلك ، كان عليّ أن أكتب
القارئ المتزامن الخاص بي ، ودون عناء حقيقي بالتحسينات ، لزيادة سرعة القراءة البحتة بمقدار 3 مرات ، أو ، إذا كنت تعول على تحليل JSON - بمقدار 2 مرات ، وفي الوقت نفسه تخلصت من عدم التزامن. ربما تحتاج إلى إعادة كتابة هذه القطعة ذات المستوى المنخفض (أو ربما المشروع بأكمله). كما أن كتابة البيانات على القرص بطيئة بشكل غير مقبول ، رغم أن هذا الأمر أقل أهمية بالنسبة للنموذج الأولي.
خطوات إضافية
1. أظهر تنفيذ خوارزميات ERP التالية بأسلوب وظيفي:
- إدارة الاحتياطي والاحتياجات المفتوحة
- تخطيط سلسلة التوريد
- حساب تكاليف الإنتاج مع مراعاة التكاليف العامة
2. قم بالتبديل إلى تنسيق التخزين الثنائي ، ربما يؤدي ذلك إلى تسريع قراءة الملف. أو حتى وضع كل شيء في Mongo.
3. نقل FuncDB في وضع متعدد المستخدمين. وفقًا لمبدأ
CQRS ، يتم إجراء القراءة مباشرة من خلال عقد الخادم التي يتم نسخ ملفات قاعدة البيانات غير القابلة للتغيير (أو البحث فيها عبر الشبكة) ، ويتم التسجيل من خلال نقطة REST واحدة تدير البيانات
القابلة للتشغيل ،
وتخزين ذاكرات مؤقتة ، والمعاملات.
4. تسريع عملية الحصول على أي مستند غير مرفق بواسطة المعرّف بسبب فهرسة الملفات المتسلسلة (التي تنتهك بالطبع مفهوم الخوارزميات أحادية المرور ، لكن وجود أي احتمال دائمًا أفضل من غيابه).
ملخص
حتى الآن ، لم أجد سببًا واحدًا للتخلي عن فكرة نظام إدارة قواعد البيانات / تخطيط موارد المؤسسات الوظيفية ، لأنه على الرغم من عدم شمولية قواعد بيانات إدارة قواعد البيانات هذه لمهمة محددة (المحاسبة والتخطيط) ، لدينا فرصة للحصول على زيادة متعددة في قابلية التوسع واستمرارية وموثوقية النظام المستهدف - كل ذلك بفضل مراعاة النظام الأساسي مبادئ FP.
رمز المشروع الكاملإذا أراد أي شخص اللعب بمفرده:
- تثبيت دينو
- استنساخ مستودع
- قم بتشغيل البرنامج النصي لإنشاء قاعدة البيانات باستخدام التحكم في المخلفات (gener_sample_database_with_balanses.ts)
- تشغيل البرامج النصية من الأمثلة 1..4 الكذب في المجلد الجذر
- توصل إلى مثال خاص بك ، وقم بترميز واختبار وتعطيني ملاحظات
PS
تم تصميم مخرجات وحدة التحكم لنظام التشغيل Linux ، ربما لن تعمل تسلسلات Windows بشكل صحيح ، ولكن ليس لدي أي شيء للتحقق منه :)
شكرا لاهتمامكم