"إذا أراد العامل أن يؤدي عمله بشكل جيد، فعليه أولاً أن يشحذ أدواته." - كونفوشيوس، "مختارات كونفوشيوس. لو لينجونج"
الصفحة الأمامية > برمجة > كاوتش جو! - تحسين CouchDB باستخدام خادم الاستعلام المكتوب في Go

كاوتش جو! - تحسين CouchDB باستخدام خادم الاستعلام المكتوب في Go

تم النشر بتاريخ 2024-08-06
تصفح:169

CouchGO! — Enhancing CouchDB with Query Server Written in Go

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

لقد أجريت بعض الأبحاث السريعة ووجدت تطبيقات مكتوبة بلغة Python أو Ruby أو Clojure. نظرًا لأن التنفيذ بأكمله لم يبدو طويلاً جدًا، فقد قررت تجربة CouchDB من خلال محاولة كتابة خادم الاستعلام المخصص الخاص بي. وللقيام بذلك، اخترت Go كلغة. لم تكن لدي خبرة كبيرة في هذه اللغة من قبل، باستثناء استخدام قوالب Go في مخططات Helm، لكنني أردت تجربة شيء جديد واعتقدت أن هذا المشروع سيكون فرصة عظيمة لذلك.

فهم خادم الاستعلام

قبل بدء العمل، قمت بمراجعة وثائق CouchDB مرة أخرى لفهم كيفية عمل خادم الاستعلام فعليًا. وفقًا للوثائق، فإن النظرة العامة عالية المستوى لخادم الاستعلام بسيطة جدًا:

خادم الاستعلام هو عملية خارجية تتواصل مع CouchDB عبر بروتوكول JSON عبر واجهة stdio وتتعامل مع جميع استدعاءات وظائف التصميم [...].

يمكن التعبير عن بنية الأوامر المرسلة بواسطة CouchDB إلى خادم الاستعلام كـ [، ] أو ["ddoc"، ، [، ]، [ ، ، …]] في حالة وثائق التصميم.

لذا، ما كان علي فعله في الأساس هو كتابة تطبيق قادر على تحليل هذا النوع من JSON من STDIO، وتنفيذ العمليات المتوقعة، وإرجاع الاستجابات كما هو محدد في الوثائق. كان هناك الكثير من أنواع الكتابة للتعامل مع مجموعة واسعة من الأوامر في كود Go. يمكن العثور على تفاصيل محددة حول كل أمر ضمن قسم بروتوكول خادم الاستعلام في الوثائق.

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

تقديم CouchGO!

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

على سبيل المثال، في CouchGO!، لا توجد وظيفة مساعدة مثل الإرسال. لإصدار القيم، ما عليك سوى إرجاعها من وظيفة الخريطة. بالإضافة إلى ذلك، تتبع كل وظيفة في مستند التصميم نفس النمط: فهي تحتوي على وسيطة واحدة فقط، وهي كائن يحتوي على خصائص خاصة بالوظيفة، ومن المفترض أن تُرجع قيمة واحدة فقط نتيجة لذلك. لا يجب أن تكون هذه القيمة بدائية؛ اعتمادًا على الوظيفة، قد يكون كائنًا أو خريطة أو حتى خطأ.

لبدء العمل مع CouchGO!، تحتاج فقط إلى تنزيل الملف الثنائي القابل للتنفيذ من مستودع GitHub الخاص بي، ووضعه في مكان ما في مثيل CouchDB، وإضافة متغير بيئة يسمح لـ CouchDB ببدء تشغيل CouchGO! عملية.

على سبيل المثال، إذا قمت بوضع ملف Couchgo القابل للتنفيذ في الدليل /opt/couchdb/bin، فيمكنك إضافة متغير البيئة التالي لتمكينه من العمل.

export COUCHDB_QUERY_SERVER_GO="/opt/couchdb/bin/couchgo"

وظائف الكتابة مع CouchGO!

للحصول على فهم سريع لكيفية كتابة الوظائف باستخدام CouchGO!، دعنا نستكشف واجهة الوظائف التالية:

func Func(args couchgo.FuncInput) couchgo.FuncOutput { ... }

كل وظيفة في CouchGO! سيتبع هذا النمط، حيث يتم استبدال Func باسم الوظيفة المناسب. حاليًا، CouchGO! يدعم أنواع الوظائف التالية:

  • خريطة
  • يقلل
  • منقي
  • تحديث
  • التحقق (validate_doc_update)

فلنفحص مثالًا لوثيقة تصميم تحدد عرضًا بالخريطة ووظائف التصغير، بالإضافة إلى وظيفة validate_doc_update. بالإضافة إلى ذلك، نحتاج إلى تحديد أننا نستخدم Go كلغة.

{
  "_id": "_design/ddoc-go",
  "views": {
    "view": {
      "map": "func Map(args couchgo.MapInput) couchgo.MapOutput {\n\tout := couchgo.MapOutput{}\n\tout = append(out, [2]interface{}{args.Doc[\"_id\"], 1})\n\tout = append(out, [2]interface{}{args.Doc[\"_id\"], 2})\n\tout = append(out, [2]interface{}{args.Doc[\"_id\"], 3})\n\t\n\treturn out\n}",
      "reduce": "func Reduce(args couchgo.ReduceInput) couchgo.ReduceOutput {\n\tout := 0.0\n\n\tfor _, value := range args.Values {\n\t\tout  = value.(float64)\n\t}\n\n\treturn out\n}"
    }
  },
  "validate_doc_update": "func Validate(args couchgo.ValidateInput) couchgo.ValidateOutput {\n\tif args.NewDoc[\"type\"] == \"post\" {\n\t\tif args.NewDoc[\"title\"] == nil || args.NewDoc[\"content\"] == nil {\n\t\t\treturn couchgo.ForbiddenError{Message: \"Title and content are required\"}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif args.NewDoc[\"type\"] == \"comment\" {\n\t\tif args.NewDoc[\"post\"] == nil || args.NewDoc[\"author\"] == nil || args.NewDoc[\"content\"] == nil {\n\t\t\treturn couchgo.ForbiddenError{Message: \"Post, author, and content are required\"}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif args.NewDoc[\"type\"] == \"user\" {\n\t\tif args.NewDoc[\"username\"] == nil || args.NewDoc[\"email\"] == nil {\n\t\t\treturn couchgo.ForbiddenError{Message: \"Username and email are required\"}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\treturn couchgo.ForbiddenError{Message: \"Invalid document type\"}\n}",
  "language": "go"
}

الآن، دعونا نقسم كل وظيفة بدءًا من وظيفة الخريطة:

func Map(args couchgo.MapInput) couchgo.MapOutput {
  out := couchgo.MapOutput{}
  out = append(out, [2]interface{}{args.Doc["_id"], 1})
  out = append(out, [2]interface{}{args.Doc["_id"], 2})
  out = append(out, [2]interface{}{args.Doc["_id"], 3})

  return out
}

في CouchGO!، لا توجد وظيفة انبعاث؛ بدلاً من ذلك، يمكنك إرجاع شريحة من مجموعات القيمة الرئيسية حيث يمكن أن يكون كل من المفتاح والقيمة من أي نوع. لا يتم تمرير كائن المستند مباشرة إلى الوظيفة كما هو الحال في JavaScript؛ بل هو ملفوف في كائن. الوثيقة نفسها هي ببساطة عبارة عن خريطة تجزئة لقيم مختلفة.

بعد ذلك، دعونا نتفحص دالة التخفيض:

func Reduce(args couchgo.ReduceInput) couchgo.ReduceOutput {
  out := 0.0
  for _, value := range args.Values {
    out  = value.(float64)
  }
  return out
}

على غرار JavaScript، فإن وظيفة التصغير في CouchGO! يأخذ المفاتيح والقيم ومعلمة إعادة التصغير، كلها ملفوفة في كائن واحد. يجب أن تقوم هذه الدالة بإرجاع قيمة واحدة من أي نوع تمثل نتيجة عملية التخفيض.

أخيرًا، دعونا نلقي نظرة على وظيفة التحقق من الصحة، والتي تتوافق مع خاصية validate_doc_update:

func Validate(args couchgo.ValidateInput) couchgo.ValidateOutput {
  if args.NewDoc["type"] == "post" {
    if args.NewDoc["title"] == nil || args.NewDoc["content"] == nil {
      return couchgo.ForbiddenError{Message: "Title and content are required"}
    }

    return nil
  }

  if args.NewDoc["type"] == "comment" {
    if args.NewDoc["post"] == nil || args.NewDoc["author"] == nil || args.NewDoc["content"] == nil {
      return couchgo.ForbiddenError{Message: "Post, author, and content are required"}
    }

    return nil
  }

  return nil
}

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

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

كاوتش جو! أداء

على الرغم من أن كتابة خادم الاستعلام الخاص بي كانت تجربة ممتعة حقًا، إلا أنه لن يكون لها أي معنى إذا لم أقارنها بالحلول الحالية. لذلك، قمت بإعداد بعض الاختبارات البسيطة في حاوية Docker للتحقق من مدى سرعة CouchGO! يستطيع:

  • فهرسة 100 ألف مستند (الفهرسة في CouchDB تعني تنفيذ وظائف الخريطة من طرق العرض)
  • تنفيذ وظيفة التصغير لـ 100 ألف مستند
  • خلاصة تغيير التصفية لـ 100 ألف مستند
  • إجراء وظيفة التحديث لعدد 1 ألف طلب

لقد قمت بزرع قاعدة البيانات بالعدد المتوقع من المستندات وأوقات الاستجابة المقاسة أو سجلات الطوابع الزمنية المختلفة من حاوية Docker باستخدام نصوص برمجية مخصصة. يمكن العثور على تفاصيل التنفيذ في مستودع GitHub الخاص بي. النتائج معروضة في الجدول أدناه.

امتحان CouchGO! CouchJS يعزز
الفهرسة 141.713ث 421.529ث 2.97×
تقليص 7672 مللي ثانية 15642 مللي ثانية 2.04x
الفلتره 28.928ث 80.594ث 2.79x
تحديث 7.742 ثانية 9.661ث 1.25x

كما ترون، يعد التعزيز الذي تم إحرازه في تنفيذ JavaScript أمرًا مهمًا: أسرع بثلاث مرات تقريبًا في حالة الفهرسة، وأكثر من الضعف في وظائف التصغير والتصفية. يعتبر التعزيز صغيرًا نسبيًا بالنسبة لوظائف التحديث، ولكنه لا يزال أسرع من جافا سكريبت.

خاتمة

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

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

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

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

بيان الافراج تم إعادة نشر هذه المقالة على: https://dev.to/kishieel/couchgo-enhancing-couchdb-with-query-server-writer-in-go-304n?1 إذا كان هناك أي انتهاك، يرجى الاتصال بـ [email protected] لحذفه
أحدث البرنامج التعليمي أكثر>

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

Copyright© 2022 湘ICP备2022001581号-3