ClickHouse تحليلات المنتج فكونتاكتي



عند تطوير أي منتج ، سواء كان خدمة فيديو أو شريطًا أو قصصًا أو مقالات ، أريد أن أكون قادرًا على قياس "السعادة" المشروطة للمستخدم. لفهم ما إذا كنا نجعل تغييراتنا أفضل أو أسوأ ، لضبط اتجاه تطوير المنتج ، لا نعتمد على الحدس ومشاعرنا الخاصة ، ولكن على المقاييس والأرقام التي يمكن أن تؤمن بها.

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

الآن لدينا حوالي 6 مليارات حدث غذائي يوميًا ، في المستقبل القريب سوف نصل إلى 20-25 مليار. وبعد ذلك - ليس بهذه السرعة ، سنرتفع إلى ما بين 40 إلى 50 مليار بحلول نهاية العام ، عندما نصف جميع الأحداث الغذائية التي تهمنا.

1 صفوف في المجموعة. منقضي: 0.287 ثانية. تمت معالجة 59.85 مليار صف ، 59.85 جيجابايت (208.16 مليار صف / ثانية ، 208.16 جيجابايت / ثانية)

التفاصيل تحت خفض.

مقدمة


كانت الأدوات التحليلية فكونتاكتي من قبل. تم النظر في المستخدمين الفريدين ، فقد كان من الممكن إنشاء جداول الأحداث حسب الشرائح وبالتالي الوقوع في أعماق الخدمة. ومع ذلك ، كانت مسألة شرائح ثابتة مقدما ، من البيانات المجمعة ، من HLL لتلك فريدة من نوعها ، من بعض الصلابة وعدم القدرة على الإجابة بسرعة على الأسئلة أكثر تعقيدا قليلا من "كم؟"

بالطبع ، كان هناك ، هو وسوف سوف hadoop ، كما أنه مكتوب ، مكتوب وسيتم كتابته الكثير ، الكثير من سجلات استخدام الخدمات. لسوء الحظ ، تم استخدام hdfs فقط من قبل بعض الفرق لتنفيذ المهام الخاصة بهم. ومما يزيد من الحزن ، أن محركات الأقراص الصلبة (HDF) لا تتعلق بالاستعلامات التحليلية السريعة: كانت هناك أسئلة للعديد من الحقول ، كانت الإجابات التي يجب العثور عليها في التعليمات البرمجية ، وليس في الوثائق التي يمكن للجميع الوصول إليها.

لقد توصلنا إلى استنتاج مفاده أنه لم يعد من الممكن أن نعيش هكذا. يجب أن يكون لدى كل فريق بيانات ، وينبغي أن تكون الاستفسارات عليها سريعة ، ويجب أن تكون البيانات نفسها دقيقة وغنية بالمعايير المفيدة.

لذلك ، قمنا بصياغة متطلبات واضحة للنظام الجديد للإحصاءات / التحليلات:

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

في المطبخ


اقترحت التجربة أننا نحتاج إلى قاعدتي بيانات: قاعدة بيانات بطيئة ، حيث يمكننا تجميع البيانات وإثرائها ، وقاعدة بيانات سريعة ، حيث يمكننا التعامل مع هذه البيانات وإنشاء الرسوم البيانية فوقه. يعد هذا أحد أكثر الطرق شيوعًا ، حيث يتم بناء إسقاطات مختلفة في قاعدة بيانات بطيئة ، على سبيل المثال ، في hdfs - على توقعات فريدة وعلى عدد الأحداث حسب الشرائح لفترة زمنية معينة.

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

العينات الأولى


لقد قمنا بنشر مجموعة من جهازين بهذا التكوين:
2xE5-2620 v4 (32 نوى في المجموع) ، 256G من ذاكرة الوصول العشوائي ، أماكن 28T (RAID10 مع ext4).

في البداية ، كان بالقرب من التصميم ، ولكن بعد ذلك تحولنا إلى بعيد. يحتوي ClickHouse على العديد من محركات الطاولة المختلفة ، ولكن أهمها من عائلة MergeTree. اخترنا ReplicatedReplacingMergeTree مع الإعدادات التالية تقريبا:

PARTITION BY dt ORDER BY (toStartOfHour(time), cityHash64(user_id), event_microsec, event_id) SAMPLE BY cityHash64(user_id) SETTINGS index_granularity = 8192; 

منسوخ - يعني أنه يتم نسخ الجدول ، وهذا يحل أحد متطلبات الموثوقية لدينا.

الاستبدال - يدعم الجدول إلغاء البيانات المكررة بواسطة المفتاح الأساسي: بشكل افتراضي ، يتطابق المفتاح الأساسي مع مفتاح الفرز ، لذلك يخبرك قسم ORDER BY فقط بالمفتاح الأساسي.

نموذج BY - أردت أيضًا تجربة العينة: تُرجع العينة عينة عشوائية زائفة.

index_granularity = 8192 هو الرقم السحري لصفوف البيانات بين فِرَط الفهرس (نعم ، إنه متناثر) ، والذي يُستخدم افتراضيًا. نحن لم نغيره.

تم التقسيم حسب اليوم (على الرغم من افتراضيا - حسب الشهر). كان من المفترض أن يكون هناك الكثير من طلبات البيانات خلال اليوم - على سبيل المثال ، أنشئ مخططًا دقيقًا لطرق عرض الفيديو ليوم معين.

بعد ذلك ، أخذنا قطعة من السجلات الفنية وقمنا بملء الطاولة بحوالي مليار صف. ضغط ممتاز ، تجميع حسب نوع العمود Int * ، عد القيم الفريدة - كل شيء يعمل بسرعة مذهلة!

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

نظرنا إلى كل شيء وتخيلنا أنه بدلاً من عمود UInt8 ، سيكون هناك معرف بلد ، وسيتم استبدال العمود Int8 ببيانات ، على سبيل المثال ، عن عمر المستخدم. وقد أدركوا أن ClickHouse مناسب تمامًا لنا ، إذا تم كل شيء بشكل صحيح.

كتابة البيانات قوية


تبدأ فائدة ClickHouse تمامًا عند تكوين مخطط البيانات الصحيح. مثال: Platform String - bad ، قاموس Int8 + platform - جيد ، LowCardinality (String) - مناسب وجيد (سأتحدث عن LowCardinality لاحقًا).

أنشأنا فئة مولد خاصة في php ، والتي ، بناءً على الطلب ، تنشئ فئات مجمعة على الأحداث بناءً على الجداول في ClickHouse ، ونقطة إدخال واحدة للتسجيل. سأشرح مثال المخطط الذي اتضح:

  1. يصف المحلل / مهندس البيانات / المطور الوثائق: أي الحقول والقيم المحتملة والأحداث يجب تسجيلها.
  2. يتم إنشاء جدول في ClickHouse وفقًا لبنية البيانات من الفقرة السابقة.
  3. يتم إنشاء فصول التفاف للأحداث القائمة على جدول.
  4. ينفذ فريق المنتج ملء حقول كائن من هذه الفئة ، وإرسال.

لن يعمل تغيير المخطط على مستوى php ونوع البيانات المسجلة دون تغيير الجدول في ClickHouse أولاً. وهذا ، بدوره ، لا يمكن القيام به دون التنسيق مع الفريق ، والتغييرات في الوثائق ووصف الأحداث.

لكل حدث ، يمكنك ضبط إعدادين يتحكمان في النسبة المئوية للأحداث المرسلة إلى ClickHouse و hadoop ، على التوالي. هناك حاجة إلى الإعدادات بشكل أساسي للتدرج التدريجي مع إمكانية خفض التسجيل إذا حدث خطأ ما. قبل hadoop ، يتم تسليم البيانات بطريقة قياسية باستخدام Kafka. وفي ClickHouse ، ينتقلون عبر مخطط مع KittenHouse في الوضع المستمر ، والذي يضمن تسليم حدث واحد على الأقل.

يتم تسليم الحدث إلى جدول المخزن المؤقت إلى القشرة المطلوبة ، استنادًا إلى باقي تقسيم بعض التجزئة من user_id على عدد القطع الموجودة في الكتلة. بعد ذلك ، يقوم جدول المخزن المؤقت بمسح البيانات إلى ReplicatedReplacingMergeTree المحلي. وفي أعلى الجداول المحلية ، يتم سحب جدول موزع باستخدام المحرك الموزع ، والذي يسمح لك بالوصول إلى البيانات من جميع القطع.

denormalizing


ClickHouse هو عمودي DBMS. لا يتعلق الأمر بالنماذج العادية ، مما يعني أنه من الأفضل الحصول على جميع المعلومات الصحيحة في هذا الحدث بدلاً من الانضمام. هناك أيضًا انضمام ، ولكن إذا لم يكن الجدول الأيمن مناسبًا للذاكرة ، يبدأ الألم. لذلك ، اتخذنا قرارًا قوي الإرادة: يجب تخزين جميع المعلومات التي نهتم بها في الحدث نفسه. على سبيل المثال ، الجنس ، عمر المستخدم ، البلد ، المدينة ، تاريخ الميلاد - كل ذلك عبارة عن معلومات عامة يمكن أن تكون مفيدة لتحليلات الجمهور ، وكذلك جميع المعلومات المفيدة حول كائن التفاعل. على سبيل المثال ، إذا كنا نتحدث عن الفيديو ، فسيتم عرض video_id و video_owner_id وتاريخ تحميل الفيديو والطول والجودة في وقت الحدث والحد الأقصى للجودة وما إلى ذلك.

في المجموع ، لدينا في كل جدول من 50 إلى 200 عمودًا ، بينما توجد في جميع الجداول حقول خدمة. على سبيل المثال ، سجل الأخطاء هو error_log - في الواقع ، ندعو خطأ خارج نطاق النوع. في حال تجاوزت القيم الغريبة حجم الكتابة في الحقل مع تقدم العمر.

اكتب LowCardinality (T)


ClickHouse لديه القدرة على استخدام القواميس الخارجية. يتم تخزينها في الذاكرة ، وتحديثها بشكل دوري ، ويمكن استخدامها بشكل فعال في سيناريوهات مختلفة ، بما في ذلك الكتب المرجعية الكلاسيكية. على سبيل المثال ، تريد تسجيل نظام التشغيل ولديك بديلان: سلسلة أو رقم + دليل. بالطبع ، على كميات كبيرة من البيانات ، والاستعلامات التحليلية عالية الأداء ، من المنطقي أن تكتب رقمًا ، وأن تحصل على تمثيل سلسلة من القاموس عندما تحتاج:

 dictGetString('os', 'os_name', toUInt64(os_id)) 

ولكن هناك طريقة أكثر ملاءمة - لاستخدام نوع LowCardinality (سلسلة) ، الذي يقوم تلقائيًا بإنشاء قاموس. الأداء مع LowCardinality في حالة انخفاض القيم الأساسية لمجموعة القيم أعلى بشكل جذري من أداء String.

على سبيل المثال ، نستخدم LowCardinality (String) لأنواع الأحداث "play" و "pause" و "rewind". أو للنظام الأساسي: "الويب" و "android" و "iphone":

 SELECT vk_platform, count() FROM t WHERE dt = yesterday() GROUP BY vk_platform Elapsed: 0.145 sec. Processed 1.98 billion rows, 5.96 GB (13.65 billion rows/s., 41.04 GB/s.) 

لا تزال الميزة تجريبية ، لذا يجب عليك تنفيذها:

 SET allow_experimental_low_cardinality_type = 1; 

ولكن هناك شعور أنه بعد مرور بعض الوقت لن تكون تحت الإعداد.

فكونتاكتي تجميع البيانات


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

كقاعدة عامة ، عند التجميع في فاصل زمني ، فإننا دائمًا ما نستند إلى حقيقة أن عدد الصفوف لكل وحدة زمنية مساوٍ لمنتج الطاقة المقطوعة. وهذا يفرض قيودًا: تبدأ البلدان في التجميع في مجموعات مثل "روسيا" و "آسيا" و "أوروبا" و "بقية العالم" والأعمار - على فترات لتقليل البعد إلى مليون سطر مشروط في التاريخ.

التجميع بواسطة dt ، user_id


ولكن لدينا ClickHouse رد الفعل! هل يمكننا تسريع 50 إلى 100 مليون خط في موعد؟
أظهرت الاختبارات السريعة أننا نستطيع ، وفي تلك اللحظة نشأت فكرة بسيطة - ترك المستخدم في الجهاز. أي ، ليس التجميع حسب "تاريخ ، شرائح" باستخدام أدوات الشرارة ، ولكن عن طريق "التاريخ ، المستخدم" يعني بواسطة ClickHouse ، أثناء القيام ببعض "نقل" البيانات.

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

سأوضح مثالاً:



بعد التجميع (العديد من الأعمدة على اليمين):



في هذه الحالة ، يحدث التجميع بدقة بواسطة (dt ، user_id). بالنسبة للحقول التي تحتوي على معلومات المستخدم ، مع هذا التجميع ، يمكنك استخدام الدالات any ، anyHeavy (تحديد قيمة تحدث بشكل متكرر). يمكنك ، على سبيل المثال ، جمع anyHeavy (النظام الأساسي) بشكل إجمالي لمعرفة النظام الأساسي الذي يستخدمه المستخدم للجزء الأكبر من أحداث الفيديو. إذا كنت ترغب في ذلك ، يمكنك استخدام groupUniqArray (platform) وتخزين مجموعة من جميع الأنظمة الأساسية التي رفع المستخدم منها الحدث. إذا لم يكن ذلك كافيًا ، فيمكنك إنشاء أعمدة منفصلة للنظام الأساسي وتخزينها ، على سبيل المثال ، عدد مقاطع الفيديو الفريدة التي يتم عرضها على النصف من نظام أساسي محدد:

 uniqCombinedIf(cityHash64(video_owner_id, video_id), (platform = 'android') AND (event = '50p')) as uniq_videos_50p_android 

من خلال هذا النهج ، يتم الحصول على إجمالي واسع نوعًا يكون فيه كل صف مستخدمًا فريدًا ، ويحتوي كل عمود على معلومات إما عن المستخدم أو عن تفاعله مع الخدمة.

اتضح أنه من أجل حساب DAU للخدمة ، يكفي تنفيذ مثل هذا الطلب في مجموعه:

 SELECT dt, count() as DAU FROM agg GROUP BY dt Elapsed: 0.078 sec. 

أو احسب عدد الأيام التي قضاها المستخدمون في الخدمة للأسبوع:

 SELECT days_in_service, count() AS uniques FROM ( SELECT uniqUpTo(7)(dt) AS days_in_service FROM agg2 WHERE dt > (yesterday() - 7) GROUP BY user_id ) GROUP BY days_in_service ORDER BY days_in_service ASC 7 rows in set. Elapsed: 2.922 sec. 

يمكننا الإسراع من خلال أخذ العينات ، بينما في الغالب دون فقد الدقة:

 SELECT days_in_service, 10 * count() AS uniques FROM ( SELECT uniqUpTo(7)(dt) AS days_in_service FROM agg2 SAMPLE 1 / 10 WHERE dt > (yesterday() - 7) GROUP BY user_id ) GROUP BY days_in_service ORDER BY days_in_service ASC 7 rows in set. Elapsed: 0.454 sec. 

تجدر الإشارة إلى أن أخذ العينات على الفور ليس حسب النسبة المئوية للأحداث ، ولكن بالنسبة المئوية للمستخدمين - ونتيجة لذلك يصبح أداة قوية بشكل لا يصدق.

أو نفس الشيء لمدة 4 أسابيع مع أخذ عينات 1/100 - يتم الحصول على نتائج أقل دقة بنسبة 1٪.

 SELECT days_in_service, 100 * count() AS uniques FROM ( SELECT uniqUpTo(7)(dt) AS days_in_service FROM agg2 SAMPLE 1 / 100 WHERE dt > (yesterday() - 28) GROUP BY user_id ) GROUP BY days_in_service ORDER BY days_in_service ASC 28 rows in set. Elapsed: 0.287 sec. 

التجميع من ناحية أخرى


عند التجميع حسب (dt ، user_id) ، فإننا لا نفقد المستخدم ، ولا نفوت معلومات حول تفاعله مع الخدمة ، لكننا بالطبع نفقد المقاييس المتعلقة بكائن تفاعل محدد. ولكن لا يمكنك أن تفقد هذا أيضًا - دعنا نبني الوحدة بها
(dt ، video_owner_id ، video_id) ، التمسك بنفس الأفكار. نحتفظ بالمعلومات المتعلقة بالفيديو بأكبر قدر ممكن ، ولا نفتقد البيانات المتعلقة بتفاعل الفيديو مع المستخدم ، ونفتقد تمامًا المعلومات المتعلقة بالمستخدم المحدد.

 SELECT starts FROM agg3 WHERE (dt = yesterday()) AND (video_id = ...) AND (video_owner_id = ...) 1 rows in set. Elapsed: 0.030 sec 

أو أفضل 10 مرات مشاهدة الفيديو أمس:

 SELECT video_id, video_owner_id, watches FROM video_agg_video_d1 WHERE dt = yesterday() ORDER BY watches DESC LIMIT 10 10 rows in set. Elapsed: 0.035 sec. 

نتيجة لذلك ، لدينا مخطط لمجاميع النموذج:

  • التجميع حسب "التاريخ ، المستخدم" داخل المنتج ؛
  • التجميع حسب "التاريخ ، كائن التفاعل" داخل المنتج ؛
  • في بعض الأحيان تنشأ توقعات أخرى.

Azkaban و TeamCity


أخيرًا ، بضع كلمات عن البنية التحتية. تبدأ مجموعة التجميع الخاصة بنا في الليل ، بدءًا من OPTIMIZE على كل جدول من البيانات التي تحتوي على بيانات أولية لبدء دمج بيانات غير عادي في ReplicatedReplacingMergeTree. يمكن أن تستمر العملية لفترة كافية ، ومع ذلك ، فمن الضروري إزالة يأخذ ، إذا حدثت. تجدر الإشارة إلى أنه حتى الآن لم أصادف تكرارًا ، لكن لا توجد ضمانات بعدم ظهورها في المستقبل.

والخطوة التالية هي إنشاء المجاميع. هذه هي البرامج النصية bash التي يحدث فيها ما يلي:

  • أولاً نحصل على عدد القطع وبعض المضيفين من القطع:

     SELECT shard_num, any(host_name) AS host FROM system.clusters GROUP BY shard_num 
  • ثم ينفذ البرنامج النصي بالتتابع لكل شارد (clickhouse-client -h $ host) طلب النموذج (للمجاميع من قبل المستخدمين):

     INSERT INTO ... SELECT ... FROM ... SAMPLE 1/$shards_count OFFSET 1/$shard_num 

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

لدينا Azkaban كما جدولة المهام. لن أقول إن هذه أداة فائقة الملاءمة ، لكنها تتكيف مع مهمتها بشكل مثالي ، بما في ذلك عندما يتعلق الأمر ببناء خطوط أنابيب أكثر تعقيدًا بقليل وعندما يحتاج أحد النصوص إلى الانتظار حتى يكتمل عدة برامج أخرى.

الوقت الإجمالي الذي يتم إنفاقه على تحويل الأحداث الموجودة الآن إلى تجميعات هو 15 دقيقة.

تجريب


كل صباح ، نجري اختبارات تلقائية تجيب على الأسئلة المتعلقة بالبيانات الخام ، فضلاً عن جاهزية وجودة المجاميع: "تحقق من أنه لم يكن بالأمس أقل من نصف بالمائة من البيانات أو البيانات الفريدة على البيانات الخام أو في المجاميع. مقارنة بنفس اليوم قبل أسبوع. "

من الناحية التكنولوجية ، هذه اختبارات وحدة عادية باستخدام JUnit وتنفيذ برنامج تشغيل jdbc لـ ClickHouse. يتم تشغيل جميع الاختبارات في TeamCity ويستغرق حوالي 30 ثانية في 1 موضوع ، وفي حالة الإخفاقات ، نحصل على إشعارات VKontakte من روبوت TeamCity الرائع لدينا.

استنتاج


استخدم فقط إصدارات مستقرة من ClickHouse وسيكون شعرك ناعمًا وناعمًا. تجدر الإشارة إلى أن ClickHouse لا يتباطأ .

Source: https://habr.com/ru/post/ar445284/


All Articles