خلال الشهر الماضي، كنت أعمل بنشاط على مشاريع إثبات المفهوم المتعلقة بـ CouchDB، واستكشف ميزاتها واستعد للمهام المستقبلية. خلال هذه الفترة، قمت بمراجعة وثائق CouchDB عدة مرات للتأكد من فهمي لكيفية عمل كل شيء. أثناء قراءتي للوثائق، عثرت على عبارة مفادها أنه على الرغم من شحن CouchDB باستخدام خادم استعلام افتراضي مكتوب بلغة JavaScript، فإن إنشاء تطبيق مخصص يعد أمرًا بسيطًا نسبيًا وتوجد حلول مخصصة بالفعل.
لقد أجريت بعض الأبحاث السريعة ووجدت تطبيقات مكتوبة بلغة Python أو Ruby أو Clojure. نظرًا لأن التنفيذ بأكمله لم يبدو طويلاً جدًا، فقد قررت تجربة CouchDB من خلال محاولة كتابة خادم الاستعلام المخصص الخاص بي. وللقيام بذلك، اخترت Go كلغة. لم تكن لدي خبرة كبيرة في هذه اللغة من قبل، باستثناء استخدام قوالب Go في مخططات Helm، لكنني أردت تجربة شيء جديد واعتقدت أن هذا المشروع سيكون فرصة عظيمة لذلك.
قبل بدء العمل، قمت بمراجعة وثائق CouchDB مرة أخرى لفهم كيفية عمل خادم الاستعلام فعليًا. وفقًا للوثائق، فإن النظرة العامة عالية المستوى لخادم الاستعلام بسيطة جدًا:
خادم الاستعلام هو عملية خارجية تتواصل مع CouchDB عبر بروتوكول JSON عبر واجهة stdio وتتعامل مع جميع استدعاءات وظائف التصميم [...].
يمكن التعبير عن بنية الأوامر المرسلة بواسطة CouchDB إلى خادم الاستعلام كـ [
لذا، ما كان علي فعله في الأساس هو كتابة تطبيق قادر على تحليل هذا النوع من JSON من STDIO، وتنفيذ العمليات المتوقعة، وإرجاع الاستجابات كما هو محدد في الوثائق. كان هناك الكثير من أنواع الكتابة للتعامل مع مجموعة واسعة من الأوامر في كود Go. يمكن العثور على تفاصيل محددة حول كل أمر ضمن قسم بروتوكول خادم الاستعلام في الوثائق.
إحدى المشكلات التي واجهتها هنا هي أن خادم الاستعلام يجب أن يكون قادرًا على تفسير وتنفيذ التعليمات البرمجية التعسفية المتوفرة في مستندات التصميم. مع العلم أن Go هي لغة مجمعة، توقعت أن أعلق عند هذه النقطة. لحسن الحظ، وجدت بسرعة حزمة Yeagi، القادرة على تفسير كود Go بسهولة. فهو يسمح بإنشاء صندوق حماية والتحكم في الوصول إلى الحزم التي يمكن استيرادها في الكود المفسر. في حالتي، قررت الكشف عن الحزمة الخاصة بي والتي تسمى Couchgo فقط، ولكن يمكن إضافة الحزم القياسية الأخرى بسهولة أيضًا.
نتيجة لعملي، تم إنشاء تطبيق يسمى CouchGO! ظهرت. على الرغم من أنه يتبع بروتوكول خادم الاستعلام، إلا أنه ليس إعادة تطبيق فردي لإصدار JavaScript حيث أن له أساليبه الخاصة في التعامل مع وظائف مستند التصميم.
على سبيل المثال، في CouchGO!، لا توجد وظيفة مساعدة مثل الإرسال. لإصدار القيم، ما عليك سوى إرجاعها من وظيفة الخريطة. بالإضافة إلى ذلك، تتبع كل وظيفة في مستند التصميم نفس النمط: فهي تحتوي على وسيطة واحدة فقط، وهي كائن يحتوي على خصائص خاصة بالوظيفة، ومن المفترض أن تُرجع قيمة واحدة فقط نتيجة لذلك. لا يجب أن تكون هذه القيمة بدائية؛ اعتمادًا على الوظيفة، قد يكون كائنًا أو خريطة أو حتى خطأ.
لبدء العمل مع CouchGO!، تحتاج فقط إلى تنزيل الملف الثنائي القابل للتنفيذ من مستودع GitHub الخاص بي، ووضعه في مكان ما في مثيل CouchDB، وإضافة متغير بيئة يسمح لـ CouchDB ببدء تشغيل CouchGO! عملية.
على سبيل المثال، إذا قمت بوضع ملف Couchgo القابل للتنفيذ في الدليل /opt/couchdb/bin، فيمكنك إضافة متغير البيئة التالي لتمكينه من العمل.
export COUCHDB_QUERY_SERVER_GO="/opt/couchdb/bin/couchgo"
للحصول على فهم سريع لكيفية كتابة الوظائف باستخدام CouchGO!، دعنا نستكشف واجهة الوظائف التالية:
func Func(args couchgo.FuncInput) couchgo.FuncOutput { ... }
كل وظيفة في CouchGO! سيتبع هذا النمط، حيث يتم استبدال Func باسم الوظيفة المناسب. حاليًا، CouchGO! يدعم أنواع الوظائف التالية:
فلنفحص مثالًا لوثيقة تصميم تحدد عرضًا بالخريطة ووظائف التصغير، بالإضافة إلى وظيفة 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! يستطيع:
لقد قمت بزرع قاعدة البيانات بالعدد المتوقع من المستندات وأوقات الاستجابة المقاسة أو سجلات الطوابع الزمنية المختلفة من حاوية 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 الخاص بك، أو ربما تستخدم بالفعل خادم استعلام مخصصًا؟ وسأكون ممتنا لسماع ذلك في التعليقات.
لا تنس مراجعة مقالاتي الأخرى للحصول على مزيد من النصائح والأفكار والأجزاء الأخرى من هذه السلسلة عند إنشائها. قرصنة سعيدة!
تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.
Copyright© 2022 湘ICP备2022001581号-3