يعد الفيسبا أفضل من البحث المرن لمطابقة ملايين الرجال والنساء





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



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



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



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



ما هي المشاكل مع نظام البحث المطابق الحالي



تستخدم OkCupid محرك البحث الداخلي الخاص بها منذ سنوات. لن ندخل في التفاصيل ، ولكن على مستوى عالٍ من التجريد ، فهو إطار عمل لتقليل الخريطة على أجزاء مساحة المستخدم ، حيث يحتوي كل جزء على بعض بيانات المستخدم ذات الصلة في الذاكرة ، والتي تُستخدم عند تمكين الفلاتر والأنواع المختلفة أثناء التنقل. تتباعد مصطلحات البحث عبر جميع الأجزاء ، وفي النهاية يتم دمج النتائج لإرجاع أفضل k المرشحات. نظام الاقتران الذي كتبناه يعمل بشكل جيد ، فلماذا قررنا تغييره الآن؟



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



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



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



هذه صدفة! لماذا أصبح OkCupid صديقًا لـ Vespa



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



Elasticsearch



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



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



فيسبا



تم فتح شفرة المصدر قبل بضع سنوات فقط. أعلن المطورون عن دعم تخزين البيانات الضخمة والبحث فيها وترتيبها وتنظيمها في الوقت الفعلي. الميزات التي يدعمها Vespa:



  • ( , 40-50 . )

  • ,

  • (, TensorFlow)

  • YQL (Yahoo Query Language) REST

  • Java-


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



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



يعد Elasticsearch محركًا مشهورًا ويمكننا الاستفادة من تجربة Tinder معه ، ولكن أي خيار سيتطلب الكثير من البحث الأولي. في الوقت نفسه ، تخدم Vespa العديد من الأنظمة في الإنتاج مثل Zedge و Flickr بمليارات الصور ومنصة إعلانات Yahoo Gemini Ads مع أكثر من مائة ألف طلب في الثانية لخدمة الإعلانات لمليار مستخدم نشط شهريًا. لقد منحنا ذلك الثقة بأن نكون خيارًا فعالاً وموثوقًا تم اختباره في المعركة - في الواقع ، كان Vespa حتى قبل Elasticsearch.



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



كيف يعمل فيسبا وكيف يبدو البحث في OkCupid







قبل الغوص في مثال Vespa الخاص بنا ، إليك نظرة عامة سريعة على كيفية عمله. Vespa عبارة عن مجموعة من الخدمات العديدة ، ولكن يمكن تكوين كل حاوية Docker لتكون مضيف مسؤول / تكوين ، ومضيف حاوية Java عديم الحالة ، و / أو مضيف محتوى C ++ ذي الحالة. يمكن نشر حزمة التطبيق مع التكوين والمكونات ونموذج ML وما إلى ذلك عبر State APIفي مجموعة التكوين التي تتعامل مع تطبيق التغييرات على الحاوية ومجموعة المحتوى. تمر طلبات الخلاصة والطلبات الأخرى عبر حاوية Java عديمة الحالة (والتي تسمح بتخصيص المعالجة) عبر HTTP قبل وصول تحديثات الخلاصة إلى مجموعة المحتوى أو يتم تقسيم الطلبات إلى طبقة المحتوى حيث يحدث تنفيذ الطلب الموزع. بالنسبة للجزء الأكبر ، لا يستغرق نشر حزمة تطبيق جديدة سوى بضع ثوانٍ ، ويعالج Vespa هذه التغييرات في الوقت الفعلي في الحاوية ومجموعة المحتوى ، لذلك نادرًا ما تضطر إلى إعادة تشغيل أي شيء.



كيف يبدو البحث؟



تحتوي مستندات مجموعة فيسبا على مجموعة متنوعة من السمات الخاصة بالمستخدم. يعرّف تعريف المخطط حقول نوع المستند بالإضافة إلى ملفات تعريف التصنيف التي تحتوي على مجموعة تعبيرات التصنيف القابلة للتطبيق. لنفترض أن لدينا تعريف مخطط يمثل مستخدمًا مثل هذا:



search user {

    document user {

        field userId type long {
            indexing: summary | attribute
            attribute: fast-search
            rank: filter
        }

        field latLong type position {
            indexing: attribute
        }

        # UNIX timestamp
        field lastOnline type long {
            indexing: attribute
            attribute: fast-search
        }

        # Contains the users that this user document has liked
        # and the corresponding weights are UNIX timestamps when that like happened 
        field likedUserSet type weightedset<long> {
            indexing: attribute
            attribute: fast-search
        }
        
   }

    rank-profile myRankProfile inherits default {
        rank-properties {
            query(lastOnlineWeight): 0
            query(incomingLikeWeight): 0
        }

        function lastOnlineScore() {
            expression: query(lastOnlineWeight) * freshness(lastOnline)
        }

        function incomingLikeTimestamp() {
            expression: rawScore(likedUserSet)
        }

        function hasLikedMe() {
            expression:  if (incomingLikeTimestamp > 0, 1, 0)
        } 

        function incomingLikeScore() {
            expression: query(incomingLikeWeight) * hasLikedMe
        }

        first-phase {
            expression {
                lastOnlineScore + incomingLikeScore
            }
        }

        summary-features {
            lastOnlineScore incomingLikeScore
        }
    }
    
}


indexing: attributeيشير التدوين إلى أنه يجب تخزين هذه الحقول في الذاكرة للحصول على أفضل أداء للقراءة والكتابة لهذه الحقول.



لنفترض أننا ملأنا الكتلة بهذه المستندات المخصصة. يمكننا بعد ذلك التصفية والترتيب في أي من الحقول المذكورة أعلاه. على سبيل المثال ، تقديم طلب POST إلى محرك البحث الافتراضي http://localhost:8080/search/للعثور على مستخدمين بخلاف مستخدمنا 777، في نطاق 50 ميلاً من موقعنا ، والذين كانوا متصلين بالإنترنت منذ الطابع الزمني 1592486978، مرتبة حسب النشاط الأخير مع الاحتفاظ بأفضل مرشحين. دعنا أيضًا نحدد ميزات الملخص لمعرفة مساهمة كل تعبير تصنيف في ملف تعريف الترتيب الخاص بنا:



{
    "yql": "select userId, summaryfeatures from user where lastOnline > 1592486978 and !(userId contains \"777\") limit 2;",
    "ranking": {
        "profile": "myRankProfile",
        "features": {
            "query(lastOnlineWeight)": "50"
        }
    },
    "pos": {
        "radius": "50mi",
        "ll": "N40o44'22;W74o0'2",
        "attribute": "latLong"
    },
    "presentation": {
        "summary": "default"
    }
}


يمكننا الحصول على نتيجة مثل هذه:



{
    "root": {
        "id": "toplevel",
        "relevance": 1.0,
        "fields": {
            "totalCount": 317
        },
        "coverage": {
            "coverage": 100,
            "documents": 958,
            "full": true,
            "nodes": 1,
            "results": 1,
            "resultsFull": 1
        },
        "children": [
            {
                "id": "index:user/0/bde9bd654f1d5ae17fd9abc3",
                "relevance": 48.99315843621399,
                "source": "user",
                "fields": {
                    "userId": -5800469520557156329,
                    "summaryfeatures": {
                        "rankingExpression(incomingLikeScore)": 0.0,
                        "rankingExpression(lastOnlineScore)": 48.99315843621399,
                        "vespa.summaryFeatures.cached": 0.0
                    }
                }
            },
            {
                "id": "index:user/0/e8aa37df0832905c3fa1dbbd",
                "relevance": 48.99041280864198,
                "source": "user",
                "fields": {
                    "userId": 6888497210242094612,
                    "summaryfeatures": {
                        "rankingExpression(incomingLikeScore)": 0.0,
                        "rankingExpression(lastOnlineScore)": 48.99041280864198,
                        "vespa.summaryFeatures.cached": 0.0
                    }
                }
            }
        ]
    }
}


بعد التصفية عن طريق مطابقة ترتيب النتائج ، تم حساب التعبير المحسوب عن المرحلة الأولى ( المرحلة الأولى) لترتيب النتائج. أهمية عاد هي النتيجة الإجمالية نتيجة لتنفيذ جميع المهام الترتيب للمرحلة الأولى في ل رتبة التعريف الذي حددناه في الاستعلام لدينا، وهذا هو ranking.profile myRankProfile. ranking.featuresنحدد query(lastOnlineWeight)50 في القائمة ، والتي يشار إليها بعد ذلك من خلال تعبير الترتيب الوحيد الذي نستخدمه lastOnlineScore. تستخدم وظيفة ترتيب مضمنة freshness ، وهي رقم قريب من 1 إذا كان الطابع الزمني في السمة أحدث من الطابع الزمني الحالي. طالما أن كل شيء يسير على ما يرام ، فلا شيء معقد هنا.



على عكس المحتوى الثابت ، يمكن أن يؤثر هذا المحتوى على ما إذا كان سيتم عرضه للمستخدم أم لا. على سبيل المثال ، قد يحبونك! يمكننا فهرسة حقل مرجح likedUserSet لكل مستند مستخدم يحتوي كمفاتيح على معرفات المستخدمين الذين يحبونهم وكقيم الطابع الزمني لوقت حدوث ذلك. بعد ذلك سيكون من السهل تصفية الأشخاص الذين أعجبوا بك (على سبيل المثال ، إضافة تعبير likedUserSet contains \”777\”في YQL) ، ولكن كيف يتم تضمين هذه المعلومات أثناء التصنيف؟ كيف نزيد رقم المستخدم الذي أعجبه شخصنا في النتائج؟



في النتائج السابقة ، incomingLikeScoreكان تعبير الترتيب 0 لكلتا النتيجتين . المستخدم في 6888497210242094612الواقع أحب المستخدم777لكنها غير متوفرة حاليًا في التصنيف حتى لو وضعناها "query(incomingLikeWeight)": 50. يمكننا استخدام وظيفة الترتيب في YQL (تحدد الوسيطة الأولى والوحيدة للوظيفة rank()ما إذا كان المستند مطابقًا ، ولكن يتم استخدام جميع الوسائط لحساب درجة التصنيف) ثم استخدام dotProduct في تعبير ترتيب YQL الخاص بنا لتخزين واسترداد الدرجات الأولية (في هذه الحالة الطوابع الزمنية عندما يحبنا المستخدم) ، على سبيل المثال ، بهذه الطريقة:



{
    "yql": "select userId,summaryfeatures from user where !(userId contains \"777\") and rank(lastOnline > 1592486978, dotProduct(likedUserSet, {\"777\":1})) limit 2;",
    "ranking": {
        "profile": "myRankProfile",
        "features": {
            "query(lastOnlineWeight)": "50",
            "query(incomingLikeWeight)": "50"
        }
    },
    "pos": {
        "radius": "50mi",
        "ll": "N40o44'22;W74o0'2",
        "attribute": "latLong"
    },
    "presentation": {
        "summary": "default"
    }
}


{
    "root": {
        "id": "toplevel",
        "relevance": 1.0,
        "fields": {
            "totalCount": 317
        },
        "coverage": {
            "coverage": 100,
            "documents": 958,
            "full": true,
            "nodes": 1,
            "results": 1,
            "resultsFull": 1
        },
        "children": [
            {
                "id": "index:user/0/e8aa37df0832905c3fa1dbbd",
                "relevance": 98.97595807613169,
                "source": "user",
                "fields": {
                    "userId": 6888497210242094612,
                    "summaryfeatures": {
                        "rankingExpression(incomingLikeScore)": 50.0,
                        "rankingExpression(lastOnlineScore)": 48.97595807613169,
                        "vespa.summaryFeatures.cached": 0.0
                    }
                }
            },
            {
                "id": "index:user/0/bde9bd654f1d5ae17fd9abc3",
                "relevance": 48.9787037037037,
                "source": "user",
                "fields": {
                    "userId": -5800469520557156329,
                    "summaryfeatures": {
                        "rankingExpression(incomingLikeScore)": 0.0,
                        "rankingExpression(lastOnlineScore)": 48.9787037037037,
                        "vespa.summaryFeatures.cached": 0.0
                    }
                }
            }
        ]
    }
}


الآن تم 68888497210242094612رفع المستخدم إلى القمة ، لأنه أحب مستخدمنا incomingLikeScoreوله معنى كامل. بالطبع ، لدينا طابع زمني يوضح الوقت الذي أعجب به حتى نتمكن من استخدامه في تعبيرات أكثر تعقيدًا ، لكن في الوقت الحالي سنترك الأمر بسيطًا.



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



إعداد البرامج الوسيطة في Java



ماذا لو أردنا أن نسلك طريقًا مختلفًا ونجعل تعبير dotProduct هذا جزءًا ضمنيًا من كل طلب؟ هذا هو المكان الذي تأتي فيه طبقة حاوية Java المخصصة - يمكننا كتابة مكون باحث مخصص . يتيح لك هذا معالجة المعلمات التعسفية وإعادة كتابة الاستعلام ومعالجة النتائج بطريقة محددة. هذا مثال في Kotlin:



@After(PhaseNames.TRANSFORMED_QUERY)
class MatchSearcher : Searcher() {

    companion object {
        // HTTP query parameter
        val USERID_QUERY_PARAM = "userid"

        val ATTRIBUTE_FIELD_LIKED_USER_SET = “likedUserSet”
    }

    override fun search(query: Query, execution: Execution): Result {
        val userId = query.properties().getString(USERID_QUERY_PARAM)?.toLong()

        // Add the dotProduct clause
        If (userId != null) {
            val rankItem = query.model.queryTree.getRankItem()
            val likedUserSetClause = DotProductItem(ATTRIBUTE_FIELD_LIKED_USER_SET)
            likedUserSetClause.addToken(userId, 1)
            rankItem.addItem(likedUserSetClause)        
       }

        // Execute the query
        query.trace("YQL after is: ${query.yqlRepresentation()}", 2)
        return  execution.search(query)
    }
}


بعد ذلك ، في ملف services.xml الخاص بنا ، يمكننا تكوين هذا المكون على النحو التالي:



...       
         <search>
            <chain id="default" inherits="vespa">
                <searcher id="com.okcupid.match.MatchSearcher" bundle="match-searcher"/>
            </chain>
        </search>
        <handler id="default" bundle="match-searcher">
            <binding>http://*:8080/match</binding>
        </handler>
...


ثم نقوم فقط بإنشاء ونشر حزمة التطبيق وتقديم طلب إلى المعالج المخصص http://localhost:8080/match-?userid=777:



{
    "yql": "select userId,summaryfeatures from user where !(userId contains \"777\") and rank(lastOnline > 1592486978) limit 2;",
    "ranking": {
        "profile": "myRankProfile",
        "features": {
            "query(lastOnlineWeight)": "50",
            "query(incomingLikeWeight)": "50"
        }
    },
    "pos": {
        "radius": "50mi",
        "ll": "N40o44'22;W74o0'2",
        "attribute": "latLong"
    },
    "presentation": {
        "summary": "default"
    }
}


لقد حصلنا على نفس النتائج كما كان من قبل! لاحظ أنه في كود Kotlin ، أضفنا تتبعًا لإخراج طريقة عرض YQL بعد التغيير ، لذلك إذا تم تعيينه tracelevel=2في معلمات URL ، فسيتم عرض الاستجابة أيضًا:



...
                    {
                        "message": "YQL after is: select userId, summaryfeatures from user where ((rank(lastOnline > 1592486978, dotProduct(likedUserSet, {\"777\": 1})) AND !(userId contains \"777\") limit 2;"
                    },
...


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



كيف قمنا ببناء مجموعة فيسبا ووضعناها في حيز الإنتاج



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



المراحل الأولى للنماذج الأولية



تتم كتابة أنظمة OkCupid الخلفية بلغة Golang و C ++. من أجل كتابة مكونات منطقية مخصصة لـ Vespa ، بالإضافة إلى توفير معدلات تغذية عالية باستخدام واجهة برمجة تطبيقات Java Vespa HTTP feed client ، كان علينا التعرف قليلاً على بيئة JVM - انتهى بنا الأمر باستخدام Kotlin عند إعداد مكونات فيسبا وفي خطوط أنابيب التغذية الخاصة بنا.



استغرق الأمر عدة سنوات لنقل منطق التطبيق والكشف عن وظائف فيسبا ، والتشاور مع فريق فيسبا حسب الحاجة. تتم كتابة معظم منطق النظام الخاص بمحرك المطابقة بلغة C ++ ، لذلك أضفنا أيضًا منطقًا لترجمة نموذج بيانات المرشح وفرز البيانات الحالي إلى استعلامات YQL المكافئة التي نصدرها إلى مجموعة Vespa عبر REST. في وقت مبكر ، حرصنا أيضًا على إنشاء خط أنابيب جيد لإعادة ملء المجموعة بقاعدة كاملة من المستندات للمستخدمين ؛ يجب أن تتضمن النماذج الأولية العديد من التغييرات لتحديد أنواع الحقول المناسبة لاستخدامها ، وتتطلب دون قصد إعادة إرسال موجز المستند.



المراقبة واختبار التحمل



عندما أنشأنا مجموعة بحث Vespa ، كان علينا التأكد من أمرين: أنه يمكنه التعامل مع الحجم المتوقع لاستعلامات البحث والسجلات ، وأن التوصيات التي يقدمها النظام قابلة للمقارنة من حيث الجودة مع نظام الاقتران الحالي.



قبل اختبارات التحميل ، أضفنا مقاييس بروميثيوس في كل مكان. يوفر Vespa-exporter أطنانًا من الإحصاءات ، كما يوفر Vespa نفسه مجموعة صغيرة من المقاييس الإضافية . بناءً على ذلك ، أنشأنا العديد من لوحات معلومات Grafana للطلبات في الثانية ، والكمون ، واستخدام الموارد من خلال عمليات Vespa ، وما إلى ذلك. كما قمنا بتشغيل vespa-fbench لاختبار أداء الاستعلام. بمساعدة مطوري Vespa ، قررنا ذلك نظرًا لارتفاعه نسبيًاتكلفة طلبات ثابتة، لدينا تخطيط الجاهزة مجمعة ستوفر نتائج أسرع. في التخطيط المسطح ، تؤدي إضافة المزيد من العقد بشكل أساسي إلى تقليل تكلفة الاستعلام الديناميكي (أي جزء الاستعلام الذي يعتمد على عدد المستندات المفهرسة). يعني التخطيط المجمع أن كل مجموعة مواقع تم تكوينها ستحتوي على مجموعة كاملة من المستندات ، وبالتالي يمكن لمجموعة واحدة أن تخدم الطلب. نظرًا لارتفاع تكلفة الطلبات الثابتة ، مع الحفاظ على عدد العقد كما هو ، قمنا بزيادة الإنتاجية بشكل كبير ، وزيادة العدد من مجموعة مسطحة واحدة إلى ثلاثة. أخيرًا ، اختبرنا أيضًا "حركة مرور الظل" غير المُبلغ عنها في الوقت الفعلي عندما أصبحنا واثقين من موثوقية المعايير الثابتة.



تحسين الأداء



كان أداء الدفع أحد أكبر العقبات التي واجهناها في وقت مبكر. في البداية ، واجهتنا مشاكل في معالجة التحديثات حتى عند 1000 QPS (طلبات في الثانية). استخدمنا حقول المجموعة الموزونة على نطاق واسع ، لكنها لم تكن فعالة في البداية. لحسن الحظ ، كان مطورو Vespa سريعًا في المساعدة في حل هذه المشكلات ، بالإضافة إلى المشكلات الأخرى المتعلقة بنشر البيانات. أضافوا لاحقًا أيضًا وثائق مستفيضة حول تحجيم العلف ، والتي نستخدمها إلى حد ما: الحقول الصحيحة في مجموعات كبيرة مرجحة ، عندما يكون ذلك ممكنًا ، تسمح بالتجميع عن طريق الإعدادvisibility-delayباستخدام تحديثات شرطية متعددة والاعتماد على حقول السمات (أي في الذاكرة) ، بالإضافة إلى تقليل عدد الحزم ذهابًا وإيابًا من العملاء عن طريق ضغط ودمج العمليات في خطوط أنابيب fmdov الخاصة بنا. الآن تتعامل خطوط الأنابيب بهدوء مع 3000 QPS في حالة مستقرة ، وتعالج مجموعتنا المتواضعة تحديثات 11K QPS عندما يحدث مثل هذا الارتفاع لسبب ما.



جودة التوصيات



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



مخطط النظام



في شكل مبسط ، يبدو الرسم التخطيطي النهائي للهندسة المعمارية للنظام الجديد كما يلي:







كيف يعمل فيسبا الآن وماذا بعد



لنقارن حالة مكتشف زوج Vespa بالنظام السابق:



  • تحديثات المخطط

    • قبل: أسبوع مع مئات الأسطر الجديدة من التعليمات البرمجية ، نشر منسق بعناية مع أنظمة فرعية متعددة

    • :
  • /

    • :

    • : . , !


    • : ,

    • : , Vespa . -


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



ماذا بعد؟



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



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



OkCupid و Vespa. اذهب!



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



اتخذنا الخطوة الأولى وأحببنا مطوري Vespa. أرسلوا لنا رسالة واتضح أنها مصادفة! لم نتمكن من القيام بذلك دون مساعدة فريق فيسبا. شكر خاص لـ jobergum و geirst للتوصيات حول الترتيب ومعالجة الاستعلام ، و kkraune و vekterli لدعمهم. لقد كان مستوى الدعم والجهد الذي قدمه لنا فريق Vespa مذهلاً حقًا - بدءًا من الرؤية العميقة لحالة الاستخدام الخاصة بنا ، إلى تشخيص مشكلات الأداء وإجراء تحسينات فورية على محرك Vespa. حتى أن الرفيق vekterli سافر إلى مكتبنا في نيويورك وعمل معنا مباشرة لمدة أسبوع للمساعدة في دمج المحرك. شكرا جزيلا لفريق فيسبا!



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



All Articles