কীভাবে আমি OpenSearch এবং CJK ব্যবহার করে Typo-Tolerant সার্চ যুক্ত করলাম
কোনো ফলাফল না পাওয়া সার্চ (Zero-result queries) আমাদের ওয়াচ টাইম কমিয়ে দিচ্ছিল।
এক বছর ধরে, TopVideoHub সার্চের জন্য SQLite FTS5 ব্যবহার করত। এটি সঠিক শব্দের (exact matches) ক্ষেত্রে কাজ করত। কিন্তু ব্যবহারকারীরা বানানে ভুল করলে এটি ব্যর্থ হতো।
মানুষ "demon slayer"-এর পরিবর্তে "demon slyer" লিখে সার্চ করত। জাপানি বা কোরিয়ান শিরোনামে তারা ভুল করে অতিরিক্ত স্পেস দিয়ে দিত। যেহেতু FTS5 শুধুমাত্র সঠিক টোকেন (exact tokens) ম্যাচ করে, তাই একটি ছোট বানান ভুল মানেই ছিল কোনো ফলাফল না পাওয়া। ব্যবহারকারীরা আর পুনরায় সার্চ করত না। তারা শুধু সাইট ছেড়ে চলে যেত।
আমি আমাদের টাইটেল সার্চ OpenSearch-এ সরিয়ে নিয়েছি। বহুভাষিক দর্শকদের জন্য আমি কীভাবে এটি সমাধান করেছি তা নিচে দেওয়া হলো।
স্ট্যান্ডার্ড ফাজিনেসির (Standard Fuzziness) সমস্যা
বেশিরভাগ টিউটোরিয়ালে বানান ভুল ঠিক করার জন্য "fuzziness" ব্যবহার করতে বলা হয়। এটি ইংরেজি ভাষার জন্য কাজ করলেও চাইনিজ, জাপানি এবং কোরিয়ান (CJK) টেক্সটের ক্ষেত্রে ব্যর্থ হয়।
- CJK-এর জন্য এডিট ডিস্ট্যান্স (Edit distance) কার্যকর নয়। CJK-তে একটি বানান ভুল মানে প্রায়ই সম্পূর্ণ ভুল ক্যারেক্টার ব্যবহার করা।
- স্ট্যান্ডার্ড ফাজিনেসির কারণে অর্থহীন ফলাফল (semantic garbage) আসতে পারে। এটি "fire" এবং "water"-কে ম্যাচ করিয়ে দিতে পারে কারণ তাদের মধ্যে এডিট ডিস্ট্যান্স মাত্র এক।
- টোকেনাইজেশন (Tokenization) করা কঠিন। CJK ভাষাগুলোতে শব্দের মাঝে স্পেস ব্যবহার করা হয় না।
সমাধান: একটি মাল্টি-ফিল্ড অ্যাপ্রোচ (Multi-Field Approach)
আমি সব টেক্সটকে একইভাবে দেখা বন্ধ করেছি। আমি একটি লজিক্যাল টাইটেল ফিল্ড তৈরি করেছি যা তিনটি ভিন্ন উপায়ে ইনডেক্স করে:
- Latin এবং Romaji: আমি fuzziness সক্রিয় রেখে স্ট্যান্ডার্ড টোকেনাইজেশন ব্যবহার করেছি। আমি প্রিপিক্স লেন্থ (prefix length) ১ সেট করেছি। এটি নিশ্চিত করে যে "demon" যেন "demn"-এর সাথে ম্যাচ করে, কিন্তু "lemon" যেন "demon"-এর সাথে ম্যাচ না করে।
- CJK Text: আমি একটি CJK bigram analyzer এবং একটি ICU normalizer ব্যবহার করেছি। আমি fuzziness বন্ধ (OFF) করে দিয়েছি। পরিবর্তে, আমি ৭০% মিনিমাম ম্যাচ থ্রেশহোল্ড (minimum match threshold) ব্যবহার করেছি।
- Autocomplete: সার্চ-অ্যাজ-ইউ-টাইপ (search-as-you-type) রেজাল্ট পাওয়ার জন্য আমি একটি edge-ngram ফিল্ড ব্যবহার করেছি।
আর্কিটেকচার এবং ডেটা সুরক্ষা
আমি SQLite-কে 'সিঙ্গেল সোর্স অফ ট্রুথ' (single source of truth) হিসেবে রেখেছি। OpenSearch একটি দ্রুত এবং পুনরায় তৈরিযোগ্য (rebuildable) ইনডেক্স হিসেবে কাজ করে।
- আমি PHP ব্যবহার করে বাল্ক ব্যাচে (bulk batches) OpenSearch-এ আপডেট পাঠাই।
- আমি ইউজার রিকোয়েস্ট চলাকালীন কখনোই ইনডেক্সিং চালাই না।
- আমি "drift" চেক করার জন্য একটি Python স্ক্রিপ্ট চালাই। এটি নিশ্চিত করে যে OpenSearch-এর কাউন্ট যেন SQLite-এর কাউন্টের সাথে মিলে যায়।
ফলাফল
পরিবর্তনটি ছিল বিশাল:
- কোনো ফলাফল না পাওয়া সার্চ (Zero-result queries) ১৪% থেকে কমে ৩%-এর নিচে নেমে এসেছে।
- সার্চ সেশনগুলো দীর্ঘতর হয়েছে কারণ ব্যবহারকারীরা যা খুঁজছিলেন তা সাথে সাথে পেয়ে যাচ্ছেন।
- ল্যাটেন্সি (Latency) প্রায় 40ms-এর আশেপাশে কম রয়েছে।
আপনি যদি বহুভাষিক দর্শকদের সেবা প্রদান করেন, তবে মনে রাখবেন: typo tolerance এবং CJK ম্যাচিং দুটি ভিন্ন সমস্যা। আপনার দুটি ভিন্ন সমাধান প্রয়োজন।
