เรากำลังเปิดตัว Upstash Redis Search ในอีก 1-2 สัปดาห์ข้างหน้า แต่ฉันต้องการแบ่งปันความคิดเบื้องต้นเกี่ยวกับสิ่งที่เรากำลังสร้าง และเหตุผลที่ฉันตื่นเต้นกับสิ่งนี้
ทำไมเราถึงสร้างสิ่งนี้
เราอยู่ในแวดวงการค้นหามาตั้งแต่ปี 2024 โดยเริ่มจาก Upstash Vector Vector อนุญาตให้ผู้คนใช้การค้นหาเชิงความหมาย และต่อมาเราก็เพิ่มเข้าไปในกลุ่มผลิตภัณฑ์นี้เป็นสองเท่าด้วย Upstash Search
ตัวอย่างเช่น นี่คือทวีตเปิดตัวของเราในปี 2025 ที่ประกาศ Upstash Search ซึ่งเป็นโซลูชันการค้นหาความหมายแบบเวกเตอร์ของเรา 👇

และฉันคิดว่าเราทำได้ดียิ่งขึ้นไปอีกและต่อยอดการเรียนรู้จาก Search
เราไม่พอใจกับผู้ให้บริการค้นหาที่มีอยู่ส่วนใหญ่ สำหรับฉันโดยส่วนตัวแล้ว ไม่มีโปรแกรมใดที่เหมาะกับพื้นที่ไร้เซิร์ฟเวอร์เลย มากเสียจนฉันสร้างแอปค้นหาของตัวเองโดยใช้ AWS Cloudsearch ย้อนกลับไปในปี 2023 ☺
สิ่งที่เราต้องการคือสิ่งที่:
- อาศัยอยู่ใน Redis เพราะ Redis รวดเร็ว
- ทำงานร่วมกับ Upstash Redis SDK
- ปลอดภัยต่อการพิมพ์ 100%
- รวดเร็วเพียงพอสำหรับการค้นหาแบบเรียลไทม์ขณะที่คุณพิมพ์
ดังนั้นเราจึงสร้างมันขึ้นมา
ส่วนขยายแรกของเรานอกเหนือจาก Redis API
นี่เป็นเรื่องใหญ่สำหรับเรา จนถึงขณะนี้ 01 ได้รับการแมปคำสั่ง Redis ที่ใกล้เคียง 1:1 การค้นหาคือส่วนขยายแรกของเรานอกเหนือจากนั้น
เรากำลังใช้ Tantivy ภายใต้ประทุน ซึ่งเป็นเครื่องมือค้นหาข้อความแบบเต็มที่เขียนด้วยภาษา Rust ซึ่งได้รับแรงบันดาลใจจาก Apache Lucene มันรวดเร็ว รวดเร็วจริงๆ และมันทำให้เรามีพื้นฐานทั้งหมดที่เราต้องการ เช่น โทเค็นไนเซชั่น การกั้น การจับคู่แบบคลุมเครือ ข้อความค้นหาวลี และการให้คะแนน BM25
เป้าหมายคือการทำให้สิ่งนี้ให้ความรู้สึกเป็นธรรมชาติกับ SDK และ Upstash Redis เอง หากคุณใช้ 18 ในปัจจุบัน การเพิ่มการค้นหาควรให้ความรู้สึกเหมือนเป็นส่วนขยายตามธรรมชาติ ไม่ใช่เป็นผลิตภัณฑ์แยกต่างหาก
ตัวสร้างสคีมาแบบปลอดภัย
สิ่งหนึ่งที่ฉันพอใจมากคือตัวสร้างสคีมาใหม่ เรากำหนดฟิลด์ที่สามารถค้นหาได้ด้วย API ที่มีลักษณะคล้ายโซด:
import { Redis, s } from "@upstash/redis";
const redis = Redis.fromEnv();
const schema = s.object({
name: s.string(),
description: s.string(),
sku: s.string().noTokenize(),
brand: s.string().noStem(),
price: s.number(),
inStock: s.boolean(),
});
const products = await redis.search.createIndex({
name: "products",
dataType: "json",
prefix: "product:",
schema,
});
25รหัส> และ 31รหัส> วิธีการให้เราควบคุมวิธีการประมวลผลข้อความ:
- การแปลงโทเค็น แยกข้อความเป็นคำที่สามารถค้นหาได้ เหมาะสำหรับภาษาธรรมชาติ แต่ใช้ไม่ได้กับ SKU (
45กลายเป็น57). ปิดการใช้งานสำหรับรหัส อีเมล และ UUID - การต่อกิ่ง ลดคำเป็นรูปแบบรากดังนั้น "การวิ่ง" จึงตรงกับ "การวิ่ง" ปิดใช้งานสำหรับชื่อแบรนด์และคำนามเฉพาะที่เราต้องการให้ตรงกันทุกประการ
สคีมาช่วยให้เราสามารถอนุมานข้อความค้นหาได้อย่างเต็มรูปแบบ หากเราพยายามค้นหาฟิลด์ที่ไม่มีอยู่ TypeScript จะตรวจจับได้ เรากำลังรักษาไวยากรณ์ของสคีมาให้ใกล้เคียงกับไวยากรณ์ของโซดมาก ดังนั้นจึงให้ความรู้สึกคุ้นเคยในการใช้งาน
แบบสอบถามเบื้องต้น
เรากำลังเปิดตัวพร้อมกับโอเปอเรเตอร์หลัก 5 ตัวที่เราคิดว่าครอบคลุมกรณีการใช้งานการค้นหาส่วนใหญ่:
60รหัส> สำหรับการจับคู่อัจฉริยะ
ด้วยตัวดำเนินการ $smart เราจะใช้การจับคู่อัจฉริยะโดยอัตโนมัติ โอเปอเรเตอร์นี้ควรใช้งานได้™ และเป็นวิธีที่ดีที่สุดสำหรับผู้เริ่มต้นในการเริ่มต้น
await products.query({
filter: {
name: { $smart: "wirless headphones" },
},
}); ภายใต้ประทุน สิ่งนี้จะทำงาน:
- การทำงานแบบวลีแบบตรงทั้งหมด (บูสต์สูงสุด) - เอกสารที่มี "หูฟังไร้สาย" ติดกันและเรียงตามลำดับ
- วลีที่มีคำหยาบคาย (เน้นเสียงปานกลาง) - เอกสารที่มีคำปรากฏตามลำดับแต่ไม่อยู่ติดกัน (เช่น หูฟัง bose ไร้สาย)
- การจับคู่ข้อกำหนด (เพิ่มระดับปานกลาง) - เอกสารที่มีเงื่อนไขทั้งหมด คำสั่งใดๆ
- การจับคู่ที่ไม่ชัดเจน (ไม่มีการเร่ง) - เอกสารที่มีการพิมพ์ผิด เช่น "หูฟังไร้สาย"
- คำนำหน้าคลุมเครือในคำสุดท้าย (ไม่มีการเพิ่มประสิทธิภาพ) - สำหรับสถานการณ์การค้นหาขณะพิมพ์
คะแนนจะรวมกันเพื่อให้ได้ผลลัพธ์ที่เกี่ยวข้องมากที่สุด สำหรับช่องค้นหาส่วนใหญ่ นี่คือสิ่งที่คุณต้องการอย่างแท้จริง แน่นอนคุณสามารถใช้โอเปอเรเตอร์นี้ด้วยตนเองและลองใช้การตั้งค่าได้ เพราะมันสร้างขึ้นจากโอเปอเรเตอร์พื้นฐานอื่นๆ ด้านล่าง
77รหัส> เพื่อความเท่าเทียมกัน
สำหรับช่องที่เราต้องการให้ตรงกันทุกประการ:
await products.query({
filter: {
name: { $eq: "wireless headphones" },
price: { $eq: 200 },
},
}); 80รหัส> สำหรับการจับคู่วลี
เมื่อเราต้องการให้คำปรากฏติดกันและตามลำดับ:
await products.query({
filter: { description: { $phrase: "noise cancelling" } },
});
นอกจากนี้เรายังสามารถเพิ่ม 97 ได้อีกด้วย เพื่ออนุญาตคำที่อยู่ระหว่าง:
await products.query({
filter: {
description: {
$phrase: { value: "wireless headphones", slop: 2 },
},
},
}); 108รหัส> สำหรับการทนต่อการพิมพ์ผิด
สำหรับการจับคู่แบบคลุมเครือที่มีความทนทานต่อการพิมพ์ผิดที่กำหนดค่าได้ (เช่น พิมพ์ผิด 2 ครั้ง):
await products.query({
filter: { name: { $fuzzy: "headphonse", distance: 2 } },
}); 113รหัส> สำหรับการจับคู่รูปแบบ
เมื่อเราต้องการรูปแบบนิพจน์ทั่วไป:
await products.query({
filter: { sku: { $regex: "SKU-[0-9]{5}-.*" } },
});
สิ่งหนึ่งที่ควรทราบ:regex ทำงานได้ดีที่สุดในฟิลด์ที่มี 121 เนื่องจากข้อความที่มีต้นกำเนิดไม่ตรงกับรูปแบบที่คาดหวัง
การเพิ่มประสิทธิภาพฟิลด์เฉพาะ
เราสามารถใช้บูสต์เพื่อเพิ่มน้ำหนักให้กับแมตช์บางแมตช์ให้สูงขึ้นได้:
await products.query({
filter: {
$and: [
{ name: { $smart: "wireless", $boost: 2 } },
{ description: { $smart: "wireless" } },
],
},
}); ซึ่งจะทำให้การจับคู่ชื่อมีมูลค่ามากกว่าการจับคู่คำอธิบายถึง 2 เท่า สิ่งนี้มีประโยชน์เมื่อเราต้องการให้การจับคู่ชื่อมีอันดับเหนือการจับคู่เนื้อหา
อะไรต่อไป
ทุกสิ่งที่ฉันใส่ลงในบทความนี้ยังคงเปิดกว้างสำหรับการเปลี่ยนแปลง เรายังคงขัดขอบและเขียนเอกสาร การเปิดตัวอย่างเป็นทางการคือในอีก 1-2 สัปดาห์
แต่ฉันคิดว่ามันเจ๋งจริงๆ ที่เราจะได้เห็นด้วยกันก่อน 👀
บางสิ่งที่เราอาจสำรวจหลังการเปิดตัว:
- การรวมการค้นหาเวกเตอร์ (การค้นหาแบบผสมความหมาย + คำหลัก)
- เติมข้อความอัตโนมัติและคำแนะนำ
หากคุณต้องการทดลองใช้ก่อนใครหรือมีคำถาม โปรดติดต่อฉันที่ @joshtriedcoding
ขอบคุณที่อ่าน🙌