أعجوبة

البرمجيات الحُرة والمفتوحة المصدر

أدوات المستخدم

أدوات الموقع


thawab:datamodel

هيئة الملفات ونموذج البيانات

هيئة الملفات

بعد ملاحظة سرعة وفعالية sqlite3 في ثواب 2 تبين لنا أنها الحل الأفضل

وهي قاعدة بيانات RDBM مضمنة لا تحتاج إلى خادم بل تعتمد على الملفات انظر مقالة ويكيبيديا عنها

أطلقت Sqlite عام 1421 هـ (2001 م) بعد قيام مؤلفها بأبحاث لصالح البحرية للتحكم بالمدمرات الصاروخية.

وهي تدعم معايير SQL-92 بشكل كبير.

أغلب عيوبها لا تعني مشروع ثواب بل إنها تعتبر مزايا لها. لكن عيوبها التي تعني مشروعنا هي تغيير في هيئة الملف في الإصدارات الجديدة انظر

بعد التحدث مع مطوري sqlite فإن ذلك لم يحدث بين 3.5 و 3.6 حيث أن التغيير كان في بعض أجزاء الدوال وليس في هيئة الملفات

لكن حتى لو تغيّرت هيئة الملف يمكننا أن نضع ملفات .jar التي تتعامل مع الإصدار الذي نريد.

نموذج البيانات

بعد القيام بالعديد من الاختبارات الداخلية على عدد من الأمثلة إليكم نموذج البيانات الذي توصلنا له.

يجوز أن تتوزع الملفات في عدة مجلدات وتحديدا يعنينا أن يكون هناك على الأقل مكانين واحد عام للنظام لا يملك المستخدم الذي يشغل البرنامج صلاحيات التعديل فيه وآخر يفترض أن يكون له صلاحيات الكتابة حتى يحتفظ بالفهارس وأي معلومات خبيئة cached.

نموذج بيانات الميتا

يحتوي كل كتاب على معلومات عن الكتاب وليست منه تسمى ميتا. ومخطط جدول الميتا في كل كتاب هو:

CREATE TABLE "meta" (
	"cache_hash" TEXT,
	"repo" TEXT,
	"lang" TEXT,
	"kitab" TEXT,
	"version" TEXT,
	"releaseMajor" INTEGER,
	"releaseMinor" INTEGER,
	"type" INTEGER,
	"author" TEXT,
	"year" INTEGER,
	"originalAuthor" TEXT,
	"originalYear" INTEGER,
	"originalKitab" TEXT,
	"originalVersion" TEXT,
	"classification" TEXT
);

ولا يوجد فهارس لهذا الجدول في ملف الكتاب لأنه يتكون من صف واحد. يتم تجميع هذا الصف من كل الكتب ضمن إدارة البرنامج في جدول الميتا داخل النسخة الخبيئة مع إضافة الحقول التالية uri و mtime و flags والنسخة الخبيئة تحتوي على فهارس لجدول الميتا وذلك عبر

CREATE INDEX MetaURIIndex ON meta (uri);
CREATE INDEX MetaRepoIndex ON meta (repo);
CREATE INDEX MetaLangIndex ON meta (lang);
CREATE INDEX MetaKitabIndex ON meta (kitab);
CREATE INDEX MetaKitabVersionIndex ON meta (repo,kitab,version);
CREATE INDEX MetaAuthorIndex ON meta (author);
CREATE INDEX MetaYearIndex ON meta (YEAR);
CREATE INDEX MetaOriginalAuthorIndex ON meta (originalAuthor);
CREATE INDEX MetaOriginalYearIndex ON meta (originalYear);
CREATE INDEX MetaClassificationIndex ON meta (classification);

أما معاني هذه الحقول فهي

الحقل النوع المعنى
cache_hash TEXT مقطع لبصمة مميزة إن تغيرت وجب تحديث الخبيئة والفهارس
repo TEXT من أي مستودع هذا الكتاب
lang TEXT اللغة الغالبة في الكتاب
kitab TEXT معرف نصي لاسم الكتاب
version TEXT الإصدارة وهي كلما زاد يعني نسخة أحدث ومنقحة ضمن نفس المستودع وهو يزاد عند التعديلات التي تكسر الروابط
releaseMajor INTEGER رقم الإطلاق الكبير وهو يزاد كلما حدث تعديلات في المحتوى لكنها لا تكسر الروابط
releaseMinor INTEGER رقم الإطلاق الصغير وهو يزاد كلما حدث تعديل خارج المحتوي بل في الوسوم أو في الميتا
type INTEGER النوع: 0 كتاب عادي. 1 تفسير. 2. معجم أو تراجم
author TEXT معرف نصي لأول مؤلف للنص المفرغ هنا والتفاصيل تكون من جدول المؤلفين الخاص بالمستودع
year INTEGER العام الهجري لوفاته، 0 إن كان على قيد الحياة
originalAuthor TEXT معرف مؤلف الكتاب الأول بصورته الاولى في حال الكتب المترجمة
originalYear INTEGER العام الهجري لوفاة المؤلف الأول
originalKitab TEXT معرف الكتاب الذي هذا ترجمته - يترك خاليا إن لم يكن مترجما
originalVersion TEXT إصدار الكتاب الأصلي وحيث أن الروابط لا تختلف عند نفس الإصدار لا يهم من أي إطلاق
classification TEXT قائمة بالتصنيفات تفصل بين الفروع علامة / وتفصل بين التصنيفات علامة :
حقول في النسخة الخبيئة فقط
uri TEXT العنوان الفريد غالبا هو مجرد مسار لملف الكتاب
mtime FLOAT وقت آخر تعديل على ملف الكتاب كما يعرضه نظام التشغيل
flags INTEGER خيارات - حاليا 2 إن تمت الفهرسة بشكل صحيح 0 إن لم يكن هناك فهارس و 1 إن كان هناك فهرس غير صالح

تم تصميم هذه البيانات كي تترتب حسب تاريخ وفاة المؤلف وإن كان حيا فإن كان تاريخ الوفاة غير معروف تترتب تلك الكتب في آخر القائمة هجائيا. ويعود للمستخدم الاختيار بين المؤلف الأصلي أم المترجم إن كان الكتاب مترجما فإن لم يكن الكتاب مترجما يفترض أن تكون القيمتين ذاتها.

إن تغير قيمة releaseMajor يعني أن نتائج البحث من الفهرس القديم تقود لروابط صالحة إلا أنها قد تحتوي كلمات مختلفة أي أنه لا بأس بعرضها في النتائج بشكل مؤقت ريثما يتم إعادة فهرست الكتاب (ومن باب أولى releaseMinor). أما قيمة الإصدار version فتعني أن هناك أبواب جديدة أو أبواب محذوفة فلا نضمن الروابط من الفهرسة القديمة لذا يجب أن يتم إسقاطها من الفهرس وأن لا تعرض على المستخدم.
وجود القيمتان originalAuthor و originalYear لا يدلان على أن هذا الكتاب ترجمة أو تعليق على كتاب آخر بل تدل على ذلك originalKitab.

نموذج بيانات الكتاب

يتكون أي كتاب من 3 جداول أساسية إضافة إلى جدول الميتا. هذه الجداول هي:

  • جدول العقد nodes وهي عناصر محتويات الكتاب
  • جدول الوسوم tags وصفوفه هي كل الوسوم التي يمكن أن تطبق على أي عقدة
  • جدول ربط هذه بتلك nodetags وصفوفه تربط أي عقدة بعدد من الوسوم وتعين قيمها إن كانت تأخذ قيم.

وبلغة SQL المخطط هو

CREATE TABLE "nodes" (
	"idNum" INTEGER PRIMARY KEY NOT NULL,
	"content" TEXT,
	"parent" INTEGER,
	"globalOrder" INTEGER,
	"depth" INTEGER NOT NULL
);
 
CREATE TABLE "tags" (
	"idNum" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
	"name" VARCHAR NOT NULL,
	"flags" INTEGER NOT NULL,
	"comment" VARCHAR,
	"parent" INTEGER,
	"relation" INTEGER
);
 
CREATE TABLE "nodesTags" (
	"tagIdNum" INTEGER NOT NULL,
	"nodeIdNum" INTEGER NOT NULL,
	"param" VARCHAR,
	PRIMARY KEY ("tagIdNum", "nodeIdNum")
);
 
CREATE INDEX NodesParentIndex ON nodes (parent);
CREATE INDEX NodesNodesGlobalOrderIndex ON nodes (globalOrder);
CREATE INDEX NodesDepthIndex ON nodes (depth);
 
CREATE INDEX NodesTagTagIdNumIndex ON nodesTags(tagIdNum);
CREATE INDEX NodesTagNodeIdNumIndex ON nodesTags(nodeIdNum);
CREATE INDEX NodesTagParamIndex ON nodesTags(param);
CREATE INDEX TagsName ON tags (name);

تلك الحقول تعني ما يلي:

جدول العقد nodes
idNum INTEGER معرف عددي فريد تلقائي
content TEXT محتوى العقدة
parent INTEGER معرف والد العقدة في شجرة الهرمية
globalOrder INTEGER ترتيب الصف عند سبر الشجرة بطريقة العمق أولا
depth INTEGER عمق العقدة - الجذر صفر وأول أولاده 1
جدول الوسوم tags
idNum INTEGER معرف عددي فريد تلقائي
name TEXT اسم الوسم
flags INTEGER علامات تحدد نوع الوسم وسلوكه
comment TEXT تعليق يشرح للمحررين كيفية استعمال هذا الوسم
parent INTEGER والد هذا الوسم
relation INTEGER علاقة الوسم

العلامات هي

TAG_FLAGS_EXTERNAL_SOURCE محتويات خارجية أي تجلب من خارج الكتاب
TAG_FLAGS_BYBOT هذه العقدة وأحفادها مولدة آليا أي تعديلات فيها ستفقد عندما يعاد توليدها
TAG_FLAGS_HEADER هذه العقدة تمثل تبويب أي يجب أن يفهرس هو وأحفاده في حقل وثيقة فهرسة مستقلة ثم يتم استهلاك المحتويات بعد الفهرسة
TAG_FLAGS_IX_TAG هذه العقدة تحمل وسم بحث
TAG_FLAGS_IX_FIELD حقل فهرسة منفصل: أي أن هذه العقدة وأحفادها تفهرس مرة أخرى لكن في وثيقة فهرسة مستقلة أي لا يتم استهلاك المحتويات بعد الفهرسة
TAG_FLAGS_IX_SKIP تجاهل فهرسة محتويات العقدة وأحفادها
TAG_FLAGS_PAD_CONTENT تحتاج هذه العقدة أن تعزل عن سياقها بمسافة خالية whitespace إما مسافة أو سطر جديد في حالة TAG_FLAGS_FLOW_MASK
TAG_FLAGS_FLOW_BLOCK العقدة تكون في صندوق منفصل عن سير فقرة السياق
TAG_FLAGS_FLOW_FLOAT عقدة طافية
TAG_FLAGS_FLOW_FOOTER عقدة تذيل كالحواشي
TAG_FLAGS_FLOW_HIDDEN عقدة خفية. تعليق مثلا

ملاحظات

  • لا يجوز تطبيق TAG_FLAGS_HEADER على حفيد ل TAG_FLAGS_IX_FIELD لأن الباب أوسع من الحقل
  • لا يجوز تطبيق إهمال الفهرسة TAG_FLAGS_IX_SKIP و الفهرسة في حقل في نفس الوفت.
  • الهدف من حقل الفهرسة TAG_FLAGS_IX_FIELD هو تمكين البحث عن معلومة منفصلة عن بقية النص مثلا إن كنت تريد البحث عن كلمة “لهيعة” بشرط أن تكون في اسم الرواي وليس في سياق الكلام العادي فلا تخرج لك نتائج ترد فيها كلمة لهيعة في السياق.
  • حقل TAG_FLAGS_IX_FIELD يجب أن لا يطبق على تبويب مباشرة بل على أحفاده لأنها إن طبقت على التبويب نفسه تؤدي إلى فهرسة التبويب مرتين.

اختبار النموذج

حجم النموذج وصلاحيته

تم اختباره عبر عمل سكربت يحول نص بطريقة تشبه الويكي إلى قاعدة بيانات بالنموذج المختار. وكانت النتيجة كما يلي:

الملف الحجم كملف نصي حجم النموذج في الشاملة في ثواب 2
موطأ الإمام مالك 2213 ك.ب 2679 ك.ب 12870 ك.ب 2732 ك.ب
صحيح البخاري 9296 ك.ب 11275 ك.ب - 11291 ك.ب

طبعا تم تحديد الأبواب والنص دون وسم السند والمتن وشجرة الرواة …إلخ.

سرعة النموذج

استخراج جدول المحتويات كاملا لصحيح البخاري (11 ميغا) يستغرق أقل من جزء من عشرة من الثانية. علما أن الصفوف ليست مخزنة بشكل متسلسل!

time sqlite3 bukhari.db \
'SELECT * FROM nodes LEFT OUTER JOIN nodesTags ON nodes.id = nodesTags.nodeId LEFT OUTER JOIN tags on nodesTags.tagId=tags.id WHERE tags.name="header"; ' >/dev/null 

على فرض أن كتاب الإيمان يحمل المعرف node.id=3 وبه حوالي 20 باب لكل باب صفين في قاعدة البيانات عقدة تحمل وسم التبويب header وأخرى متفرعة عنها تحمل المحتويات. لاستخراج باب الإيمان كله وأحفاده

time sqlite3 bukhari.db 'SELECT nodes.id, nodes.content FROM nodes LEFT OUTER JOIN nodesTags ON nodes.id = nodesTags.nodeId LEFT OUTER JOIN tags on nodesTags.tagId=tags.id WHERE nodes.l2=3; ' >/dev/null
real	0m0.016s
user	0m0.001s
sys	0m0.003s

أي أنها أقل من جزء من مئة جزء من الثانية.

ولنفرض أن الصف رقم 31 يحتوي على باب الحياء شعبة من الإيمان لاستخراج محتوياته

time sqlite3 bukhari.db 'SELECT nodes.id, nodes.content FROM nodes LEFT OUTER JOIN nodesTags ON nodes.id = nodesTags.nodeId LEFT OUTER JOIN tags on nodesTags.tagId=tags.id WHERE nodes.l3=31; '

وقد كان الوقت أقل من جزء من ألف من الثانية.

لاحظ أننا نستخدم l2 أو l3 … بحسب عمق العقدة.

وقمت بتجربة الوقت اللازم لاستيراد كتاب بسيط من هيئة wiki النصية إلى نموذج البيانات 10 مرات ثم تم تكرار التجربة على صحيح البخاري 10 مرات هو الآخر.

بأمر يشبه ما يلي:

time for i in `seq 1 10`; do groovy source/Wiki2Db.groovy samples/test.txt; done

فكانت النتيجة

الطريقة زمن الكتاب البسيط زمن صحيح البخاري
بايثون 0.477 27.857
جروفي 11.259 151.007
جافا .class 8.869 187.752
جروفي التكرار داخلها* 1.900 178.089
جافا . class التكرار داخلها* 1.632 180.584

لكن يبدو أن سبب تأخر جافا هو الوقت اللازم لتحميل جافا فلو وضعنا الحلقة التكرارية للاستيراد 10 مرات داخل الكود يكون الزمن أقل بكثير ومع أن هذا ظلم لبايثون حيث أننا قمنا بتحميل مفسر بايثون 10 مرات إلا انها ظلت متفوقة ويمكن ملاحظة التفوق عند النظر إلى الزمن عند استيراد ملف كبير مثل صحيح البخاري حيث أصبح زمن تحميل جروفي هامشيا.

وسبب تفوق بايثون إما استعمالها لمكتبة سي الخاصة ب sqlite مباشرة أو بسبب اغلاقي للخيار isolation_level في بايثون دون جروفي

cn=sqlite3.connect(fn, isolation_level=None)

طرق بديلة

بما أن xml لا تحقق أهداف التصميم لمشروعنا حيث لا يمكن التعامل معها بطريقة تزايدية أو جزئية ومن الصعب القفز فيها. لذا علينا درساة طرق بديلة ل xml ما يهما بشكل أساسي هو عملية Serialization لعدة أسباب منها

ملاحظات على كل منها

  • حزم ASN1 متوفرة في مستودعات فيدورا وأوبنتو
  • لا يوجد أي حزمة متعلقة ب hessian لا في فيدورا ولا في أوبنتو
  • حزم protobuf متوفرة في فيدورا وأوبنتو
thawab/datamodel.txt · آخر تعديل: 2015/04/23 03:21 بواسطة 127.0.0.1

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki