- กล่าวถึงประสบการณ์การพัฒนา เว็บเสิร์ชเอนจินบนพื้นฐานของเอ็มเบดดิง 300 ล้านรายการ ภายใน 2 เดือน โดยมีจุดตั้งต้นจาก คุณภาพของเสิร์ชเอนจินที่ถดถอย และความก้าวหน้าของโมเดลเอ็มเบดดิงที่อิง Transformer
- ใช้อินฟราสตรักเจอร์และอัลกอริทึมสมรรถนะสูง เช่น คลัสเตอร์ GPU 200 ชุด, distributed crawler ขนาดใหญ่, RocksDB และ HNSW เพื่อทำให้ การค้นหาแบบเข้าใจภาษาธรรมชาติแบบเรียลไทม์ เป็นจริง
- มุ่งสู่ การตอบคำถามตามเจตนา แทนการจับคู่คีย์เวิร์ด พร้อมประยุกต์ใช้เทคนิค NLP/ML หลากหลายแบบ เช่น normalization, chunking, statement chaining เพื่อการพาร์สเอกสารและการรักษาบริบท
- แนะนำการออกแบบ distributed system ขนาดใหญ่ ในแต่ละชั้น เช่น pipeline, storage, service mesh, vector index รวมถึงแนวทางแก้คอขวดและการเพิ่มประสิทธิภาพต้นทุน
- สุดท้ายอธิบายว่าระบบได้กลายเป็นเสิร์ชเอนจินแบบปรับแต่งเฉพาะบุคคลที่มี latency ต่ำมาก, กระจายศูนย์ขนาดใหญ่ และมีความแม่นยำสูง
ภาพรวมและแรงจูงใจ
- ผู้เขียนตัดสินใจสร้างเสิร์ชเอนจินขึ้นใหม่ตั้งแต่ต้น ท่ามกลางปัญหาคุณภาพของเสิร์ชเอนจินที่ลดลง, SEO spam, การเพิ่มขึ้นของคอนเทนต์ที่ไม่เกี่ยวข้อง และในช่วงเวลาที่ โมเดลเอ็มเบดดิงที่อิง Transformer มีความสามารถในการเข้าใจภาษาธรรมชาติสูงขึ้น
- ข้อจำกัดของเสิร์ชเอนจินแบบเดิมเกิดจากการขาด ความสามารถในการเข้าใจคำถามในระดับมนุษย์ และการจับคู่แบบง่าย ๆ ที่อิงคีย์เวิร์ด
- เป้าหมายคือ การจัดอันดับตามเจตนา ที่ช่วยให้คอนเทนต์คุณภาพดีขึ้นมาอยู่ด้านบนได้เสมอ และยังสำรวจผลลัพธ์ส่วนลึกของ long tail ได้อย่างทั่วถึง
- กระบวนการสร้างเว็บเสิร์ชเอนจินครอบคลุมหลายศาสตร์ ทั้ง computer science, ภาษาศาสตร์, ontology, NLP, ML, distributed systems และ performance engineering
- โปรเจ็กต์นี้คือความท้าทายในการเริ่มต้นคนเดียวโดยไม่มีอินฟราสตรักเจอร์หรือประสบการณ์ล่วงหน้า และสร้างเสิร์ชเอนจินแบบใหม่ทั้งหมดให้เสร็จภายใน 2 เดือน
โครงสร้างระบบโดยรวม
- สร้าง text embedding จำนวน 300 ล้านรายการบนคลัสเตอร์ GPU 200 ชุดโดยอิง SBERT
- crawler หลายร้อยเครื่องทำงานพร้อมกัน เก็บข้อมูลได้ 50,000 หน้า/วินาที และสร้างดัชนีรวม 280 ล้านรายการ
- จัดเก็บและทำดัชนีด้วยการ shard RocksDB และ HNSW บนเครื่องที่มี 200 คอร์, RAM 4TB และ SSD 82TB
- กำหนดเวลาแฝงรวมของการตอบสนองต่อ query ไว้ที่ประมาณ 500ms
- โครงสร้างและลำดับการทำงานโดยรวมแยกเป็นส่วน crawler, pipeline, storage, ดัชนีเวกเตอร์เอ็มเบดดิง, service mesh และส่วน front/back-end
การทดลองและการปรับปรุงการค้นหาบนพื้นฐานเอ็มเบดดิง
Neural Embedding Playground
- การค้นหาด้วยโมเดลเอ็มเบดดิงอย่าง SBERT ได้รับการยืนยันผ่านการทดลองว่ามี ความเข้าใจ query และความแม่นยำที่เป็นธรรมชาติมากกว่า การค้นหาแบบยึดคีย์เวิร์ดเดิม
- สามารถทำความเข้าใจเจตนาของ query จากบริบทและระดับประโยค แล้วดึงคำตอบที่เกี่ยวข้องจริงออกมาได้
ตัวอย่างการค้นหาแบบดั้งเดิม vs. การค้นหาแบบนิวรัล
- การค้นหาแบบเดิม: ผลลัพธ์มีความสุ่ม และเน้นการตรงกันของคีย์เวิร์ด
- การค้นหาแบบเอ็มเบดดิง: เข้าใจบริบทและเจตนาของคำถาม พร้อมให้ผลลัพธ์ที่เน้นประโยคแกนหลักหรือแนวคิดที่ถูกต้อง
- สำหรับการผสมแนวคิดที่ซับซ้อน, คำถามแบบแฝง/ผสม และ query ที่มีสัญญาณด้านคุณภาพ สามารถค้นหาคำตอบที่ถูกต้องบนพื้นฐานของ ความหมาย ได้
การพาร์สเว็บเพจและการทำ normalization
-
ตั้งเป้าดึงเฉพาะ องค์ประกอบข้อความเชิงความหมาย จาก HTML และทำ normalization เพื่อตัด noise อย่างเลย์เอาต์หรือองค์ประกอบควบคุมออก
-
รักษาโครงสร้างของตารางตามมาตรฐานอย่าง WHATWG, MDN โดยรองรับ p, table, pre, blockquote, ul, ol, dl เป็นต้น
-
ลบองค์ประกอบ chrome ของหน้าเว็บ เช่น เมนู, navigation, ความคิดเห็น และอินเทอร์เฟซ ออกทั้งหมด
-
ใช้กฎพิเศษรายเว็บไซต์ (เช่น en.wikipedia.org) เพื่อแก้ปัญหาการดึงข้อมูลมากเกินไปหรือน้อยเกินไป
-
ยังสามารถใช้ structured data เชิงความหมาย (เช่น meta, OpenGraph, schema.org) เพื่อสร้าง knowledge graph และปรับปรุง ranking ได้ด้วย
Chunking และการรักษาบริบท
การทำ chunking ระดับประโยค
- เพื่อก้าวข้ามข้อจำกัดของโมเดลเอ็มเบดดิง จึงใช้ การทำ chunking แบบอิงประโยค แทนการใช้ทั้งหน้าเว็บ
- ระหว่างทำ chunking มีการใช้ spaCy sentencizer เพื่อแยกขอบเขตประโยคอย่างแม่นยำ โดยคำนึงถึงกรณีต่าง ๆ เช่น ขอบเขตประโยคตามธรรมชาติ, ไวยากรณ์, คำย่อ, URL และสำนวนไม่เป็นทางการ
การรักษาและการเชื่อมโยงบริบท
- วิเคราะห์ ความสัมพันธ์พึ่งพาระหว่างประโยค, heading, paragraph, table ฯลฯ แล้วนำข้อมูลบริบทมาเชื่อมรวมกับเอ็มเบดดิงด้วย
- ตัวอย่างเช่น โครงสร้างตารางก็ถูกแทรกโดยเชื่อม heading/ข้อกำหนดระดับบนอย่างต่อเนื่อง เพื่อไม่ให้ความหมายของแต่ละแถวสูญหาย
การเชื่อมประโยค (Statement Chaining)
- ใช้ DistilBERT classifier วิเคราะห์ประโยคหนึ่งร่วมกับประโยคก่อนหน้า เพื่อทำ การตรวจสอบการพึ่งพาบริบทและการดึง chain โดยอัตโนมัติ
- ตอนสร้างเอ็มเบดดิง จะรวมประโยคที่เป็นตัวพึ่งพาระดับบนทั้งหมดเข้าไปด้วย เพื่อเพิ่มความสามารถในการรักษาบริบท
ผลการใช้งานต้นแบบ
- จากการทดลอง query ใช้งานจริงหลากหลายในสภาพแวดล้อม sandbox พบว่าได้ การตอบคำถามที่แม่นยำกว่ามาก (ตรงบริบท) เมื่อเทียบกับวิธีเดิม
- แม้จะเป็นกรณีคีย์เวิร์ดไม่ตรง, การละคำ, อุปมา หรือคำถามแบบผสม แอปก็ยังเข้าใจเจตนาและจับคู่กับประโยคบริบทที่ถูกต้องได้ พร้อมทั้งค้นพบความรู้และความสัมพันธ์ที่ซ่อนอยู่ได้อย่างมีประสิทธิภาพ
เว็บครอว์เลอร์ขนาดใหญ่ (อิงโหนด)
- พิจารณาทั้งเรื่องเสถียรภาพและประสิทธิภาพหลายด้าน เช่น work stealing สำหรับกระจายงาน, การควบคุม concurrency/traffic รายโดเมน, การตรวจสอบ DNS/URL/header
- crawler ใช้ Promise แบบ asynchronous I/O, กลไกที่ทนต่อ DDoS, การจัดการทรัพยากร (หน่วยความจำ, delay, backoff) และการตรวจจับโดเมนที่มี noise
- ดำเนินการเสริมความเข้มแข็งในการกรอง URL ซ้ำซ้อน/ผิดปกติ ผ่าน URL normalization, การจำกัด protocol และ port/user info รวมถึง canonicalization
Pipeline (distributed task queue)
- จัดการสถานะของแต่ละหน้าผ่าน PostgreSQL และในช่วงแรกใช้ polling/transaction โดยตรง
- เมื่อเข้าสู่สภาพแวดล้อม distributed ขนาดใหญ่ (crawler หลายพันตัว) เกิดปัญหาเรื่องการสเกลและคอขวดของคิว/lock จึงเปลี่ยนไปใช้ coordinator แบบ in-memory ที่พัฒนาด้วย Rust เพื่อจัดการสถานะคิว
- โครงสร้าง task ประกอบด้วย indexing หลายรูปแบบ เช่น ดัชนีบนพื้นฐาน hash map, binary heap, domain group, random poll และการสลับตำแหน่งด้วย
swap_remove - ใช้หน่วยความจำเพียงระดับ 100B ต่อ task ทำให้เซิร์ฟเวอร์ 128GB สามารถรองรับได้ถึง 1B task
- หลังจากนั้นยังพัฒนา queue แบบ open source ที่ใช้ RocksDB แทน SQS และรองรับ 300,000 ops/วินาทีบน 1 โหนด
การออกแบบ storage (Oracle → PostgreSQL → RocksDB)
- ระยะแรกใช้ Oracle Cloud (egress/พื้นที่จัดเก็บต้นทุนต่ำ) และภายหลังใช้ PostgreSQL (TOAST) แต่ก็เจอ ข้อจำกัดด้านการขยายการเขียนและประสิทธิภาพ
- ด้วยลักษณะของ PostgreSQL เช่น MVCC, write amplification และ WAL จึงเกิดคอขวดเมื่อทำ INSERT แบบขนานจำนวนมาก และสุดท้ายเปลี่ยนไปใช้ RocksDB ซึ่งเป็น KV store
- ใช้ความสามารถของ RocksDB เช่น การเก็บ blob แยกต่างหาก (BlobDB), ไฟล์ SST, multi-thread และ hash indexing เพื่อดึงสมรรถนะสูงสุดของ NVMe SSD
- ขยายเป็น 64 RocksDB shard โดยแต่ละ shard route ด้วย
xxHash(key)และใช้การ serialize ด้วย Serde+MessagePack - ในที่สุดรองรับการประมวลผล 200,000 ops/วินาทีจากไคลเอนต์หลายพันตัว (crawler/parser/vectorizer) พร้อมจัดเก็บ meta และ blob แยกกันพร้อมบีบอัด
Service mesh และเครือข่าย
- เมื่อโครงสร้างพื้นฐานขยายตัว จึงออกแบบบนพื้นฐาน mTLS+HTTP2 เพื่อรองรับ การค้นหา service instance อัตโนมัติและความปลอดภัยในการสื่อสาร
- ใช้ใบรับรองที่อิง root CA ในแต่ละ node, ใช้การ serialize ด้วย MessagePack โดยตรง และพัฒนา internal DNS, CoreDNS และ client SDK แบบ custom
- แม้เคยมีประสบการณ์ใช้ VPN อย่าง ZeroTier และ Tailscale มาก่อน แต่ด้วยปัญหาด้านเครือข่าย, ประสิทธิภาพ และการปฏิบัติการ จึงเลือก HTTP+mTLS ด้วยตนเอง
- รวมการจัดการผ่าน system service control (
systemd + cgroup + journald) เพื่อให้ทั้งเบาและเป็นมาตรฐานมากขึ้น
Pipeline สำหรับสร้างเอ็มเบดดิงบน GPU ขนาดใหญ่
- ในช่วงแรกใช้ OpenAI API และย้ายไปยังสภาพแวดล้อม GPU สมรรถนะสูงอย่าง Runpod ในภายหลังเพราะปัญหาด้านต้นทุน
- pipeline แยกแต่ละ stage แบบ asynchronous ทำให้ใช้ GPU ได้เกิน 90% และสร้าง embeddings ได้ 100,000 รายการ/วินาทีบน GPU 250 ตัว
- ใช้ Rust pipeline และ Python inference แล้วทำ IPC ผ่าน named pipe พร้อม backpressure แบบมีโครงสร้างเพื่อปรับทรัพยากรอัตโนมัติ
การทำ vector indexing (HNSW/sharding)
- ใช้ อัลกอริทึม HNSW สำหรับ vector search บนหน่วยความจำ และใช้ ANN (Approximate Nearest Neighbor) เพื่อให้ได้ latency ต่ำมาก
- เมื่อถึงขีดจำกัดของ RAM จึงทำการ shard แบบกระจายเท่า ๆ กันตามโหนด (64 โหนด) โดยแต่ละ shard ค้นหาแบบขนานด้วย HNSW index ของตัวเอง
- เนื่องจาก HNSW ต้องใช้ RAM จำนวนมากและมีข้อจำกัดด้าน live update จึงย้ายไปใช้ vector DB แบบ open source ที่อิงดิสก์ชื่อ CoreNN ในที่สุด
- CoreNN สามารถค้นหา 3B embeddings ได้อย่างแม่นยำสูงแม้บนโหนดเดี่ยวที่มี RAM 128GB
UX ของเสิร์ชเอนจินและการปรับ latency
- UX ของเสิร์ชเอนจินให้ความสำคัญกับ การตอบสนองแบบฉับไว (ไม่มี load indicator, ใช้ SSR แบบดั้งเดิม)
- ใช้ Cloudflare Argo ฯลฯ เพื่อเข้าใกล้ edge PoP และเลือกใช้ HTTP/3 เพื่อลด latency การส่งข้อมูลให้ต่ำที่สุด
- เตรียมข้อมูลทั้งหมดตั้งแต่ระดับ app server เพื่อลด API roundtrip รายจุด และตอบกลับหน้าที่ถูก minify และบีบอัดแล้วในทันที
สรุปนี้อธิบายอย่างเป็นรูปธรรมว่าเว็บเสิร์ชเอนจินขนาดใหญ่ที่ใช้เทคโนโลยีล่าสุดด้านการประมวลผลภาษาธรรมชาติและ ML สามารถถูกสร้างแบบ end-to-end ภายใน 2 เดือนได้อย่างไร พร้อมครอบคลุมประเด็นสำคัญด้านการออกแบบและการเพิ่มประสิทธิภาพทั้งในระดับระบบ อัลกอริทึม และอินฟราสตรักเจอร์
ยังไม่มีความคิดเห็น