मुझे हमेशा से पता था कि मैं अपने पोर्टफोलियो में लेखों के लिए एक पूर्ण-पाठ खोज सुविधा चाहता हूं ताकि आगंतुकों को उस सामग्री तक त्वरित पहुंच प्रदान की जा सके जिसमें वे रुचि रखते हैं। कंटेंटलेयर में माइग्रेट करने के बाद, यह अब दूर की कौड़ी नहीं लगती। तो मैंने एक्सप्लोर करना शुरू किया🚀
tinysearch
से प्रेरित : एक WebAssembly पूर्ण-पाठ खोज इंजन कुछ शोध के बाद, मुझे tinysearch
नामक एक खोज इंजन मिला। यह एक स्थिर खोज इंजन है जिसे Rust और WebAssembly (Wasm) के साथ बनाया गया है। लेखक मैथियस tinysearch
ने एक अद्भुत ब्लॉग पोस्ट लिखा है कि कैसे छोटे खोज के बारे में आया।
मुझे निर्माण के समय एक न्यूनतर खोज इंजन बनाने और ब्राउज़र को अनुकूलित निम्न-स्तरीय कोड में शिपिंग करने का विचार पसंद आया। इसलिए मैंने tinysearch
को ब्लूप्रिंट के रूप में उपयोग करने और अपनी Next.js स्थिर साइट के साथ एकीकृत करने के लिए अपना स्वयं का खोज इंजन लिखने का निर्णय लिया।
मैं tinysearch
के कोडबेस को पढ़ने की अत्यधिक अनुशंसा करता हूं। बहुत अच्छा लिखा है। मेरे खोज इंजन का कार्यान्वयन इसका एक सरलीकृत संस्करण है। मूल तर्क वही है।
बहुत आसान:
आप लेख पृष्ठ पर खोज फ़ंक्शन को आज़मा सकते हैं!
इस लेख को लिखते समय, ये हैं:
पूर्ण-पाठ खोज के लिए गति के लिए भड़काने वाली स्थिर साइटों के लिए काम करने के लिए, कोड का आकार छोटा होना चाहिए।
अधिकांश आधुनिक ब्राउज़र अब WebAssembly का समर्थन करते हैं । वे जावास्क्रिप्ट के साथ-साथ देशी WebAssembly कोड और बाइनरी चलाने में सक्षम हैं।
खोज फ़ंक्शन की अवधारणा सीधी है। यह एक पैरामीटर के रूप में एक क्वेरी स्ट्रिंग में लेता है। फ़ंक्शन में, हम क्वेरी को खोज शब्दों में टोकन देते हैं। फिर हम प्रत्येक लेख को इस आधार पर एक रैंकिंग स्कोर देते हैं कि उसमें कितने खोज शब्द हैं। अंत में, हम प्रासंगिकता के आधार पर लेखों को रैंक करते हैं। स्कोर जितना अधिक होगा, उतना ही प्रासंगिक होगा।
प्रवाह इस तरह दिखता है:
लेखों को स्कोर करना वह जगह है जहाँ सबसे अधिक कंप्यूटिंग आती है। एक सरल दृष्टिकोण प्रत्येक लेख को एक हैश सेट में बदलना होगा जिसमें लेख के सभी अद्वितीय शब्द शामिल हों। हैश सेट में कितने खोज शब्द हैं, इसकी गणना करके हम स्कोर की गणना कर सकते हैं।
आप कल्पना कर सकते हैं कि हैश सेट के साथ यह सबसे अधिक मेमोरी-कुशल दृष्टिकोण नहीं है। इसे बदलने के लिए बेहतर डेटा संरचनाएं हैं: xor फ़िल्टर ।
Xor फ़िल्टर अपेक्षाकृत नई डेटा संरचनाएं हैं जो हमें यह अनुमान लगाने की अनुमति देती हैं कि कोई मान मौजूद है या नहीं। यह तेज़ और स्मृति-कुशल है इसलिए यह पूर्ण-पाठ खोज के लिए बहुत उपयुक्त है।
हैश सेट जैसे वास्तविक इनपुट मानों को संग्रहीत करने के बजाय, xor फ़िल्टर एक विशिष्ट तरीके से इनपुट मानों के फ़िंगरप्रिंट (L-बिट हैश अनुक्रम) को संग्रहीत करता है। यह देखते हुए कि क्या फ़िल्टर में कोई मान मौजूद है, यह जाँचता है कि मान का फ़िंगरप्रिंट मौजूद है या नहीं।
हालाँकि, Xor फ़िल्टर में कुछ ट्रेड-ऑफ़ हैं:
चूंकि मेरे पास कंटेंटलेयर द्वारा उत्पन्न आलेख डेटा था, इसलिए मैंने WebAssembly के निर्माण से पहले डेटा के साथ उन्हें खिलाकर xor फ़िल्टर का निर्माण किया। मैंने फिर xor फ़िल्टर को क्रमबद्ध किया और उन्हें एक फ़ाइल में संग्रहीत किया। WebAssembly में फ़िल्टर का उपयोग करने के लिए, मुझे बस इतना करना था कि स्टोरेज फ़ाइल से पढ़ना और फ़िल्टर को deserialize करना था।
फ़िल्टर पीढ़ी प्रवाह इस तरह दिखता है:
xorf
क्रेट xor फ़िल्टर कार्यान्वयन के लिए एक अच्छा विकल्प है क्योंकि यह क्रमांकन/डिसेरिएलाइज़ेशन और कुछ सुविधाएँ प्रदान करता है जो स्मृति दक्षता और झूठी-सकारात्मक दर में सुधार करती हैं। यह मेरे उपयोग के मामले में तारों के एक टुकड़े के साथ एक xor फ़िल्टर बनाने के लिए एक बहुत ही आसान HashProxy
संरचना भी प्रदान करता है। रस्ट में लिखा गया निर्माण मोटे तौर पर इस तरह दिखता है:
use std::collections::hash_map::DefaultHasher; use xorf::{Filter, HashProxy, Xor8}; mod utils; fn build_filter(title: String, body: String) -> HashProxy<String, DefaultHasher, Xor8> { let title_tokens: HashSet<String> = utils::tokenize(&title); let body_tokens: HashSet<String> = utils::tokenize(&body); let tokens: Vec<String> = body_tokens.union(&title_tokens).cloned().collect(); HashProxy::from(&tokens) }
यदि आप वास्तविक कार्यान्वयन में रुचि रखते हैं, तो आप रिपॉजिटरी में और अधिक पढ़ सकते हैं।
यहां बताया गया है कि मैंने xor फ़िल्टर जनरेशन स्क्रिप्ट और WebAssembly को Next.js के अंदर कैसे एकीकृत किया।
फ़ाइल संरचना इस तरह दिखती है:
my-portfolio ├── next.config.js ├── pages ├── scripts │ └── fulltext-search ├── components │ └── Search.tsx └── wasm └── fulltext-search
WebAssembly का समर्थन करने के लिए, मैंने WebAssembly मॉड्यूल को async मॉड्यूल के रूप में लोड करने के लिए अपने वेबपैक कॉन्फ़िगरेशन को अपडेट किया। इसे स्थिर साइट निर्माण के लिए काम करने के लिए, मुझे .next/server
निर्देशिका में WebAssembly मॉड्यूल जेनरेट करने के लिए वर्कअराउंड की आवश्यकता थी ताकि next build
स्क्रिप्ट चलाते समय स्थिर पृष्ठ सफलतापूर्वक प्री-रेंडर कर सकें।
अगला.config.js
webpack: function (config, { isServer }) { // it makes a WebAssembly modules async modules config.experiments = { asyncWebAssembly: true } // generate wasm module in ".next/server" for ssr & ssg if (isServer) { config.output.webassemblyModuleFilename = './../static/wasm/[modulehash].wasm' } else { config.output.webassemblyModuleFilename = 'static/wasm/[modulehash].wasm' } return config },
एकीकरण के लिए बस इतना ही है✨
रस्ट कोड से WebAssembly मॉड्यूल बनाने के लिए, मैं wasm-pack
का उपयोग करता हूं।
जावास्क्रिप्ट के लिए उत्पन्न .wasm
फ़ाइल और ग्लू कोड wasm/fulltext-search/pkg
में स्थित हैं। मुझे बस इतना करना था कि उन्हें गतिशील रूप से आयात करने के लिए next/dynamic
का उपयोग करना था। ऐशे ही:
घटक/Search.tsx
import React, { useState, useCallback, ChangeEvent, useEffect } from 'react' import dynamic from 'next/dynamic' type Title = string; type Url = string; type SearchResult = [Title, Url][]; const Search = dynamic({ loader: async () => { const wasm = await import('../../wasm/fulltext-search/pkg') return () => { const [term, setTerm] = useState('') const [results, setResults] = useState<SearchResult>([]) const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { setTerm(e.target.value) }, []) useEffect(() => { const pending = wasm.search(term, 5) setResults(pending) }, [term]) return ( <div> <input value={term} onChange={onChange} placeholder="🔭 search..." /> {results.map(([title, url]) => ( <a key={url} href={url}>{title}</a> ))} </div> ) } }, }) export default Search
बिना किसी अनुकूलन के, मूल Wasm फ़ाइल का आकार 114.56KB
था। मैंने कोड आकार का पता लगाने के लिए ट्विगी का इस्तेमाल किया।
Shallow Bytes │ Shallow % │ Item ───────────────┼───────────┼───────────────────── 117314 ┊ 100.00% ┊ Σ [1670 Total Rows]
कच्चे डेटा फ़ाइलों के 628KB
की तुलना में, यह मेरी अपेक्षा से बहुत छोटा था। मैं इसे पहले से ही उत्पादन के लिए भेजकर खुश था लेकिन मैं यह देखने के लिए उत्सुक था कि मैं द रस्ट एंड वेबअसेंबली वर्किंग ग्रुप की अनुकूलन अनुशंसा के साथ कितना कोड आकार ट्रिम कर सकता हूं।
पहला प्रयोग एलटीओ को टॉगल कर रहा था और विभिन्न opt-level
एस को आज़मा रहा था। निम्न कॉन्फ़िगरेशन सबसे छोटा .wasm
कोड आकार देता है:
Cargo.toml
[profile.release] + opt-level = 's' + lto = true
Shallow Bytes │ Shallow % │ Item ───────────────┼───────────┼───────────────────── 111319 ┊ 100.00% ┊ Σ [1604 Total Rows]
इसके बाद, मैंने डिफ़ॉल्ट आवंटक को wee_alloc
से बदल दिया।
wasm/fulltext-search/src/lib.rs
+ #[global_allocator] + static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
Shallow Bytes │ Shallow % │ Item ───────────────┼───────────┼───────────────────── 100483 ┊ 100.00% ┊ Σ [1625 Total Rows]
फिर मैंने Binaryen में wasm-opt
टूल आज़माया।
wasm-opt -Oz -o wasm/fulltext-search/pkg/fulltext_search_core_bg.wasm wasm/fulltext-search/pkg/fulltext_search_core_bg.wasm
Shallow Bytes │ Shallow % │ Item ───────────────┼───────────┼───────────────────── 100390 ┊ 100.00% ┊ Σ [1625 Total Rows]
यह मूल कोड आकार से 14.4%
की छूट है।
अंत में, मैं इसमें एक पूर्ण-पाठ खोज इंजन भेजने में सक्षम था:
बुरा नहीं😎
मैंने वेब- web-sys
के साथ प्रदर्शन की रूपरेखा तैयार की और कुछ डेटा एकत्र किया:
खोजों की संख्या: 208
मिनट: 0.046 एमएस
अधिकतम: 0.814 एमएस
माध्य: 0.0994 एमएस
मानक विचलन: 0.0678
एक पूर्ण-पाठ खोज करने में औसतन 0.1 एमएस से कम समय लगता है।
यह बहुत तेज़ है😎
कुछ प्रयोगों के बाद, मैं WebAssembly, Rust और xor फ़िल्टर के साथ एक तेज़ और हल्के फुल-टेक्स्ट खोज का निर्माण करने में सक्षम था। यह Next.js और स्थिर साइट निर्माण के साथ अच्छी तरह से एकीकृत है।
गति और आकार कुछ ट्रेड-ऑफ के साथ आते हैं लेकिन उपयोगकर्ता अनुभव पर उनका बड़ा प्रभाव नहीं पड़ता है। यदि आप अधिक व्यापक खोज कार्यक्षमता की तलाश में हैं, तो यहां कुछ बेहतरीन उत्पाद उपलब्ध हैं:
सास सर्च इंजन
स्थिर खोज इंजन
सर्वर आधारित खोज इंजन
इन-ब्राउज़र खोज इंजन
यह लेख डाव-चिह की वेबसाइट पर भी पोस्ट किया गया था।