• กล่าวถึงประสบการณ์การพัฒนา เว็บเสิร์ชเอนจินบนพื้นฐานของเอ็มเบดดิง 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 เดือนได้อย่างไร พร้อมครอบคลุมประเด็นสำคัญด้านการออกแบบและการเพิ่มประสิทธิภาพทั้งในระดับระบบ อัลกอริทึม และอินฟราสตรักเจอร์

ยังไม่มีความคิดเห็น

ยังไม่มีความคิดเห็น