في المقالة الأولى حول بنية ملف QVD ، وصفت البنية العامة والسكن في البيانات الوصفية بتفاصيل كافية ، والثانية حول تخزين الأعمدة (الأحرف). في هذه المقالة ، سوف أصف التنسيق الخاص بتخزين المعلومات حول الأوتار وتلخيصها والتحدث عنها حول الخطط والإنجازات.
لذلك (تذكر) يتوافق ملف QVD مع الجدول العلائقي ، في ملف QVD يتم تخزين الجدول في جزأين متصلين بشكل غير مباشر:
تحتوي جداول الأحرف (الفصل الدراسي) على قيم فريدة لكل عمود في الجدول المصدر. تحدثت عنها في المادة الثانية.
يحتوي جدول الصفوف على صفوف الجدول المصدر ، ويقوم كل صف بتخزين مؤشرات قيم العمود (الحقل) للصف في جدول الرموز المقابل. إنه حول هذا الموضوع ستكون هذه المقالة.
على سبيل المثال لوحة لدينا (تذكر - من الجزء الأول)
SET NULLINTERPRET =<sym>; tab1: LOAD * INLINE [ ID, NAME 123.12,"Pete" 124,12/31/2018 -2,"Vasya" 1,"John" <sym>,"None" ];
في جدول الصفوف الخاص بملف QVD ، ستتوافق هذه التسمية مع 5 صفوف - تطابق دائمًا: عدد الصفوف الموجودة في الجدول ، وعدد الصفوف الموجودة في جدول الصفوف من ملف QVD.
يتكون صف في جدول الصف من أعداد صحيحة غير سالبة ، كل من هذه الأرقام هو فهرس في جدول الرموز المقابل. على المستوى المنطقي ، كل شيء بسيط ، يبقى توضيح الفروق الدقيقة وإعطاء مثال (تفكيك - كما يتم تقديم لوحة لدينا في QVD).
يتكون جدول الصفوف من K * N بايت ، حيث
- K - عدد الصفوف في الجدول المصدر (قيمة علامة بيانات التعريف "NoOfRecords")
- طول البايت من صف جدول الرموز (قيمة علامة البيانات التعريفية "RecordByteSize")
يبدأ جدول الخط بالإزاحة "إزاحة" (علامة بيانات التعريف) نسبة إلى بداية الجزء الثنائي من الملف.
يتم تخزين المعلومات حول جدول الصفوف (الطول ، حجم الصف ، الإزاحة) في الجزء العام من البيانات الأولية.
جميع صفوف جدول الصفوف لها نفس التنسيق وهي عبارة عن سلسلة من "الأرقام غير الموقعة". طول العدد يكفي كحد أدنى لتمثيل حقل معين: يعتمد الطول على عدد القيم الفريدة لحقل معين.
بالنسبة للحقول ذات القيمة الواحدة (كما كتبت بالفعل) ، سيكون هذا الطول صفراً (هذه القيمة هي نفسها في كل صف من الجدول المصدر ويتم تخزينها في جدول الرموز المقابل).
بالنسبة للحقول ذات القيمتين ، سيكون هذا الطول مساوياً لواحد (قيم الفهرس الممكنة في جدول الرموز هي 0 و 1) ، وهكذا.
نظرًا لأن الطول الإجمالي لصف جدول الصف يجب أن يكون مضاعف البايت ، يتم محاذاة طول "الحرف الأخير" إلى حدود البايت (انظر أدناه عندما سنقوم بتحليل اللوحة الخاصة بنا).
يتم تخزين المعلومات المتعلقة بتنسيق كل حقل في قسم البيانات التعريفية المخصص لهذا الحقل (سنقوم بالتفصيل أكثر قليلاً أدناه) ، ويتم تخزين طول تمثيل البت للحقل في علامة "BitWidth".
تخزين القيم الفارغة
كيفية تخزين القيم المفقودة؟ الامتناع عن مناقشة موضوع السبب ، سأجيب بهذه الطريقة: كما أفهمها ، فإن المجموعة التالية تتوافق مع قيم NULL
- علامة "Bias" في الحقل المقابل تأخذ القيمة "-2" (الكل في الكل ، صادفت قيمتين محتملتين لهذه العلامة - "0" و "-2")
- فهرس الحقل للصف حيث يكون هذا الحقل هو NULL هو 0
وفقًا لذلك ، يتم زيادة جميع المؤشرات الأخرى في العمود مع قيم NULL بمقدار 2 - سنرى في مثالنا أقل قليلاً.
ترتيب الحقول في الصف
يتوافق ترتيب الحقول في صف جدول الصف مع إزاحة البت للحقل ، والتي يتم تخزينها في علامة "BitOffset" الخاصة بقسم البيانات الأولية المتعلقة بهذا الحقل.
دعنا نحلل مثالنا (انظر البيانات الوصفية في الجزء الأول من هذه السلسلة).
حقل الهوية
- إزاحة البت 0 - سيكون الحقل هو "أقصى اليمين"
- طول البتة 3 - سيشغل الحقل 3 بتات في صف جدول الصفوف
- Bias هو "-2" - يحتوي الحقل على قيم NULL ، ويتم زيادة جميع الفهارس بنسبة 2
الحقل "NAME"
- إزاحة البت 3 - يقع الحقل على يسار حقل المعرف بمقدار 3 بتات
- طول البتة 5 - يشغل الحقل 5 بتات في صف جدول الصفوف (محاذاة للحد الفاصل للبايت)
- التحيز هو "0" - الحقل لا يحتوي على قيم فارغة ، وجميع المؤشرات "صادقة"
عرض لوحة لدينا.
دعونا نلقي نظرة على "الأصفار والأخرى" الحقيقية - سأقدم شظايا من ملف QVD كتمثيل ثنائي "بتنسيق سداسي عشري" (مضغوط جدًا).
أولاً ، الجزء الثنائي بالكامل (المظلل باللون الوردي ، يتم اقتطاع البيانات التعريفية - يؤلمها كثيرًا ...)

الاتفاق بما فيه الكفاية ، توافق. دعنا نلقي نظرة فاحصة - مباشرة بعد البيانات الوصفية توجد جداول الرموز (البيانات الأولية ، بالمناسبة ، في هذا الملف انتهت بتغذية سطحية وبايت صفر - من الناحية الفنية ، يحدث هذا ، صفر بايت بعد الحاجة إلى تخطي البيانات الأولية ...).
يتم تمييز جدول الرموز الأول في الشكل أدناه.

نحن نرى:
القيمة الفريدة الأولى لحقل المعرف هي
- النوع "6" (البايت الأول المخصص) هو رقم الفاصلة العائمة بسلسلة (انظر المقالة الثانية)
- بعد البايت الأول ، 8 من البايتات التالية هي رقم الفاصلة العائمة الممثلة ثنائي
- من بعدهم يأتي تمثيل السلسلة - ملائم للغاية (لا داعي للتذكر - ما هو الرقم) ، ينتهي بـ صفر بايت
القيم الفريدة الثلاثة المتبقية هي من النوع 5 (عدد صحيح مع سلسلة) - القيم هي "124" ، "-2" و "1" (يسهل رؤيتها على طول الخطوط).
في الشكل أدناه ، أبرزت جدول الرموز الثاني (لحقل "NAME")

القيمة الفريدة الأولى لحقل "NAME" هي النوع "4" (البايت الأول المخصص) - سلسلة تنتهي بصفر.
القيم الأربع الأخرى الفريدة هي أيضًا سلاسل "12/31/2018" و "Vaysa" و "John" و "None".
الآن - جدول الصفوف (الموضحة في الشكل أدناه)

كما هو متوقع - 5 بايت (5 خطوط لكل بايت واحد).
السطر الأول (الموافق للسطر 123.12 ، "بيت" من اللوحة لدينا)
قيمة السلسلة هي البايتة "02" (000000010 الثنائية).
افصلها (تذكر الوصف أعلاه)
- إلى اليمين 3 بت (ثنائي 010 ، في رأينا هو 2) - هذا فهرس في جدول الرموز لحقل "معرف"
- لدينا الحقل "معرّف" يحتوي على NULL ، لذلك يتم زيادة الفهرس بمقدار 2 ، أي الفهرس الناتج هو 0 ، والذي يتوافق مع الحرف "123.12".
- 5 بت التالية (ثنائية وعشرية 0) هي الفهرس في جدول الرموز لحقل "NAME" ، لا يحتوي على NULL ، لذلك هذا هو الفهرس "Pete" في جدول الرموز.
الصف الثاني (124.12 / 31/2018) في جدول الصف
القيمة - البايتة "0B" (ثنائي 00001011)
- إلى اليمين 3 بت (ثنائي 011 ، في رأينا هو 3) - هذا هو الفهرس في جدول الرموز لحقل "معرف"
- لدينا الحقل "معرّف" يحتوي على NULL ، لذلك يتم زيادة الفهرس بمقدار 2 ، أي الفهرس الناتج هو 1 ، والذي يتوافق مع الرمز "124".
- 5 بت التالية (ثنائية وعشرية 1) هي الفهرس في جدول الرموز لحقل "NAME" ، لا يحتوي على NULL ، لذلك هذا هو الفهرس "12/31/2018" في جدول الرموز.
حسنًا ، وما إلى ذلك ، دعنا نلقي نظرة سريعة على السطر الأخير - هناك كان لدينا ، "بلا" (أي NULL والسلسلة "بلا"):
القيمة هي البايتة "20" (ثنائي 0010000)
- اليمين 3 بت (ثنائي وعشري 0) - هذا هو الفهرس في جدول الرموز للحقل "معرف"
- لدينا الحقل "معرّف" يحتوي على NULL ، لذلك يتم زيادة الفهرس بمقدار 2 ، أي الفهرس النهائي هو -2 ، والذي يتوافق مع قيمة NULL.
- 5 بت التالية (ثنائي 100 ، رقم عشري 4) هي الفهرس في جدول الرموز لحقل "NAME" ، لا يحتوي على NULL ، لذلك هذا هو الفهرس "بلا" في جدول الرموز.
هام : لا يمكنني العثور على مثال يؤكد هذا ، لكنني صادفت ملفات تحتوي على فهرس نهائي بقيمة -1 لقيم NULL. لذلك ، في برامجي ، أراعي جميع الحقول التي يكون فهرسها النهائي سالباً.
صفوف أطول في جدول صف
في نهاية تحليل تنسيق QVD ، سأتطرق بإيجاز إلى الفروق الدقيقة المهمة - الخطوط الطويلة في حقول تخزين جدول الصفوف بالترتيب من اليمين إلى اليسار ، حيث يكون الحقل الذي يحتوي على إزاحة بتات صفرية هو أقصى اليمين (كما هو موضح أعلاه). ولكن ترتيب البايت هو عكس ، أي ستكون البايتة الأولى هي أقصى اليمين (وستحتوي على حقل "يمين" - حقل مع إزاحة صفرية بت) ، البايتة الأخيرة ستكون الأولى (أي ، تحتوي على أكثر الحقول "يسارًا" - حقل مع أقصى إزاحة بت).
يجب إعطاء مثال ، ولكن ليس محملاً بالتفاصيل. دعونا نلقي نظرة على هذه التسمية (أقتبس جزءًا - من أجل الحصول على خطوط طويلة في جدول الصفوف ، تحتاج إلى زيادة عدد القيم الفريدة).
tab2: LOAD * INLINE [ ID, VAL, NAME, PHONE, SINGLE 1, 100001, "Pete1", "1234567890", "single value" 2, 200002, "Pete2", "2234567890", "single value" ... ];
معلومات موجزة عن الحقول (الضغط من البيانات الوصفية):
- المعرف: عرض 8 بت ، إزاحة بت - 0 ، تحيز - 0
- VAL: العرض 5 بتات ، إزاحة البت - 8 ، التحيز - 0
- الاسم: عرض 6 بت ، إزاحة بت - 18 ، تحيز - 0
- الهاتف: العرض 5 بت ، إزاحة البت - 13 ، التحيز - 0
- وحيد: العرض 0 بت (له قيمة واحدة)
يتكون جدول الصف من سلاسل بطول 3 بايت ، على التوالي ، في صف جدول الصفوف ، سيتم تحليل البيانات حول الحقول بشكل منطقي على النحو التالي:
- أول 6 بت - حقل "NAME"
- 5 بتات التالية - حقل "PHONE"
- ثم 5 بت - حقل "VAL"
- 8 بت الماضية - حقل معرف
يتم تحويل التسلسل المنطقي إلى بايتات مادية بالترتيب العكسي ، أي
- يشغل حقل "المعرف" البايت الأول تمامًا (والذي في التسلسل المنطقي هو الأخير)
- يشغل الحقل "VAL" أقل 5 بت من البايت الثاني
- يشغل الحقل "PHONE" أعلى 3 بتات من البايت الثاني و 2 بت السفلي من البايت الثالث
- يشغل الحقل "NAME" الـ 6 بتات العليا للبايت الثالث
لنلقِ نظرة على الأمثلة ، إليك الشكل الذي يبدو عليه الصف الأول من جدول الصف (مظلل باللون الوردي)

قيم الحقول
- المعرف - ثنائي 00000000 ، عشري 0
- VAL - 00010 الثنائية ، العشرية 2 ، طرح 2 من التحيز - الحصول على 0
- الهاتف - 00010 الثنائية ، العشري 2 ، طرح 2 من التحيز - الحصول على 0
- الاسم - ثنائي 000000 ، عشري 0
وهذا يعني أن السطر الأول يحتوي على الأحرف الأولى من جداول الأحرف المقابلة.
بشكل عام ، من المريح بدء التحليل من السطر الأول - عادةً ما يحتوي على أصفار كمؤشر (يتم إنشاء ملف QVD بطريقة تدخل القيم من السطر الأول في جدول الأحرف أولاً).
دعونا نلقي نظرة على السطر الثاني للإصلاح

قيم الحقول
- المعرف - رقم 00000001 ، رقم عشري 1
- VAL - binary 00011 ، عشري 3 ، اطرح 2 من bias - الحصول على 1
- الهاتف - ثنائي 00011 ، رقم عشري 3 ، اطرح 2 من الانحياز - الحصول على 1
- الاسم - ثنائي 000001 ، رقم عشري 1
أي أن السطر الثاني يحتوي على الأحرف الثانية من جداول الأحرف المقابلة.
سوف أشارك القليل من الخبرة - كيف يمكنني من الناحية الفنية "قراءة" QVD.
تم كتابة الإصدار الأول في بيثون (سأقوم بتثبيته ووضعه على جيثب).
سرعان ما أصبحت المشاكل الرئيسية واضحة:
- لا يمكن قراءة جداول الرموز "في صف واحد" (من المستحيل قراءة رقم الرمز N دون قراءة جميع الأحرف السابقة)
- الملفات الحقيقية لا تنسجم مع ذاكرة الوصول العشوائي
- العمليات الأبطأ (باستثناء العمل مع الملفات) - عمليات البت (تفريغ صف جدول السلاسل)
- يتدرج الأداء بشكل كبير على ملفات QVD "الواسعة" (عندما يكون هناك العديد من الأعمدة)
يمكن حل بعض هذه المشكلات عن طريق تغيير اللغة (من بيثون إلى ج ، على سبيل المثال). جزء يتطلب بعض الإجراءات الإضافية.
يبدو التنفيذ الحالي سريعًا إلى حد ما مثل هذا - يتم تنفيذ المنطق العام في بيثون ، ويتم تنفيذ العمليات الأكثر أهمية في برامج C منفصلة تعمل على التوازي.
باختصار
- تتم كتابة جداول الرموز على الملفات ، ويتم إنشاء فهارس بالإضافة إلى ذلك لحقول النص ، وبالتالي يمكن قراءة رقم الرمز N
- العمل مع QVD والملفات مع جداول الرموز المنفذة من خلال ملفات الذاكرة المعينة (أسرع جدًا)
- أولاً ، بالتوازي (مع تحديد عدد المعالجات) ، يتم إنشاء الملفات بجداول الرموز (والفهارس)
- ثم بالتوازي (مع قيود مماثلة) يتم قراءة صفوف جدول الصفوف ويتم إنشاء ملفات CSV (في HDFS)
- الخطوة الأخيرة هي تحويل هذه الملفات إلى جدول ORC (باستخدام أدوات Hive)
- في C نفذت إنشاء ملفات مع جداول الرموز وإنشاء ملف CSV لمجموعة من الخطوط
لا أرغب في تقديم أرقام للأداء - سيتطلب الأمر ربط الأجهزة ، وعلى المستوى النوعي ، يتم نسخ ملف QVD إلى جدول ORC بسرعة سرعة نسخ البيانات عبر الشبكة. أو ، بعبارة أخرى ، أخذ البيانات من QVD أمر واقعي تمامًا (على مستوى الأسرة).
لقد قمت أيضًا بتنفيذ منطق إنشاء ملفات QVD - إنه يعمل بسرعة كبيرة على الثعبان (على ما يبدو ، لم تصل إلى وحدات تخزين كبيرة حتى الآن - ليست هناك حاجة. سأصل إلى هناك - سأقوم بإعادة كتابته بنفس طريقة إصدار "القراءة").
الخطط المستقبلية
ما التالي؟
- أخطط لوضع نسخة Python من الشفرة في github (سيتيح لك هذا الإصدار "استكشاف" ملف QVD - راجع البيانات الوصفية وقراءة وكتابة الأحرف والسلاسل. الإصدار بسيط وبطيء قدر الإمكان - بدون ملفات لجداول الأحرف ، مع قراءة متسلسلة ، واستخدام مكتبات قياسية للعمل مع بت ، الخ)
- أفكر في القيام بشيء من أجل حيوانات الباندا (مثل read_qvd ()) ، فهي تقيد أنه سيكون بطيئًا في الثعبان ، فضلاً عن حقيقة أنه من الواضح أن كل QVD لن "تتناسب" مع الذاكرة ، وبالتالي
- أفكر في جعل ملف QVD مصدر بيانات لـ Spark - يجب ألا تكون هناك مشكلة مع "عدم الدخول إلى الذاكرة" (واللغة هناك - scala - أقرب إلى الجهاز)
بدلا من الكلمة الأخيرة
لفترة طويلة حولت وحول ملفات QVD ، يبدو أن "كل شيء معقد هناك." اتضح أنه كان من الصعب ، ولكن ليس للغاية ، كان الدافع الجيد هو جيثب ، الذي ذكرته في الجزء الأول (نوع من المحفز). ثم كانت مسألة تقنية. لاحظت نفسي والجميع (تأكيد آخر) - كل شيء يمكن القيام به في البرمجة ، والسؤال هو الوقت والدافع.
آمل ألا أكون متعبًا جدًا من التفاصيل ، فأنا مستعد للإجابة على الأسئلة (في التعليقات أو بأي طريقة أخرى). إذا كان سيكون هناك استمرار ، سأكتب بالتأكيد.