9 คะแนน โดย GN⁺ 2025-11-18 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • นำฐานข้อมูลเดิมมาใช้เพื่อสร้าง โครงสร้างเสิร์ชเอนจินที่ทำงานได้โดยไม่ต้องพึ่งบริการภายนอก โดยเน้นที่การทำโทเค็น น้ำหนัก และการให้คะแนน
  • แนวคิดหลักคือ ทำโทเค็นข้อความทั้งหมดแล้วจัดเก็บไว้ จากนั้นตอนค้นหาก็จับคู่โทเค็นด้วยวิธีเดียวกันเพื่อคำนวณความเกี่ยวข้อง
  • ผสาน Word, Prefix, N-Gram tokenizer เพื่อรองรับทั้งการตรงกันแบบเป๊ะ การตรงกันบางส่วน และการรับมือคำพิมพ์ผิด โดยแต่ละ tokenizer มีค่าน้ำหนักเฉพาะของตนเอง
  • ใช้ ระบบน้ำหนักและอัลกอริทึมการให้คะแนนบน SQL เพื่อประเมินทั้งความยาวเอกสาร ความหลากหลายของโทเค็น คุณภาพเฉลี่ย และปัจจัยอื่น ๆ แบบรวมกัน
  • มี ความสามารถในการขยายและความโปร่งใส สูง จึงเพิ่ม tokenizer หรือประเภทเอกสารใหม่ ปรับน้ำหนัก หรือแก้ไข scoring ได้อย่างอิสระ

เหตุผลที่ต้องสร้างเสิร์ชเอนจินเอง

  • บริการภายนอกอย่าง Elasticsearch หรือ Algolia นั้นทรงพลัง แต่ก็มาพร้อม ภาระในการเรียนรู้ API ที่ซับซ้อนและการดูแลโครงสร้างพื้นฐาน
  • หากต้องการเพียง ฟีเจอร์ค้นหาที่ทำงานร่วมกับฐานข้อมูลเดิมและดีบักได้ง่าย การสร้างเองจะมีประโยชน์มาก
  • เป้าหมายคือ เสิร์ชเอนจินแบบเรียบง่ายที่คืนผลลัพธ์ได้ตรงประเด็นโดยไม่ต้องพึ่งพาภายนอก

แนวคิดหลัก: การทำโทเค็นและการจับคู่

  • หลักการพื้นฐานคือ ทำโทเค็น (tokenize) ข้อความทั้งหมดแล้วจัดเก็บไว้ และเมื่อค้นหาก็สร้างโทเค็นด้วยวิธีเดียวกันเพื่อนำมาจับคู่
  • ในขั้นตอน indexing จะตัดเนื้อหาออกเป็นหน่วยโทเค็นและเก็บพร้อมค่าน้ำหนัก
  • ในขั้นตอน search จะทำโทเค็นกับคิวรีแบบเดียวกันเพื่อหาโทเค็นที่ตรงกันและคำนวณคะแนน
  • ในขั้นตอน scoring จะใช้ค่าน้ำหนักที่จัดเก็บไว้เพื่อคำนวณคะแนนความเกี่ยวข้อง

การออกแบบสคีมาฐานข้อมูล

  • ใช้สองตาราง: index_tokens และ index_entries
    • index_tokens: เก็บโทเค็นที่ไม่ซ้ำและค่าน้ำหนักตาม tokenizer
    • index_entries: เชื่อมโทเค็นกับเอกสาร และเก็บคะแนนสุดท้ายที่สะท้อนน้ำหนักของฟิลด์ เอกสาร และ tokenizer
  • สูตรคำนวณน้ำหนักสุดท้าย:
    field_weight × tokenizer_weight × ceil(sqrt(token_length))
  • มีการตั้งดัชนีเพื่อรองรับการค้นหาเอกสาร การค้นหาโทเค็น คิวรีตามฟิลด์ และการกรองตามน้ำหนัก

ระบบการทำโทเค็น

  • WordTokenizer: แยกตามคำ ตัดคำสั้นทิ้ง ใช้สำหรับการตรงกันแบบเป๊ะ (น้ำหนัก 20)
  • PrefixTokenizer: สร้าง prefix ของคำ ใช้สำหรับ autocomplete และการตรงกันบางส่วน (น้ำหนัก 5)
  • NGramsTokenizer: สร้างชุดอักขระความยาวคงที่ ใช้รองรับคำพิมพ์ผิดและการตรงกันบางส่วน (น้ำหนัก 1)
  • tokenizer ทั้งหมดจะทำงานร่วมกันในส่วนของ การแปลงเป็นตัวพิมพ์เล็ก การลบอักขระพิเศษ และการทำช่องว่างให้เป็นรูปแบบมาตรฐาน

ระบบน้ำหนัก

  • น้ำหนักของฟิลด์: สะท้อนความสำคัญของชื่อเรื่อง เนื้อหา คีย์เวิร์ด ฯลฯ
  • น้ำหนักของ tokenizer: Word > Prefix > N-Gram
  • น้ำหนักของเอกสาร: คำนวณโดยผสานสองปัจจัยข้างต้นเข้ากับความยาวของโทเค็น
  • ใช้ฟังก์ชัน ceil(sqrt()) เพื่อลดอิทธิพลของโทเค็นที่ยาวเกินไป และสามารถปรับเป็นฟังก์ชัน log หรือเชิงเส้นได้หากจำเป็น

บริการทำดัชนี

  • สามารถทำดัชนีได้เฉพาะเอกสารที่ implement IndexableDocumentInterface
  • เมื่อเอกสารถูกสร้างหรือแก้ไข จะทำดัชนีผ่าน event listener หรือคำสั่ง (app:index-document, app:reindex-documents)
  • ขั้นตอน:
    • ลบดัชนีเดิมก่อน แล้วสร้างโทเค็นใหม่
    • รัน tokenizer ทุกตัวกับแต่ละฟิลด์
    • ตรวจสอบว่าโทเค็นมีอยู่แล้วหรือไม่ แล้วจึงสร้าง (findOrCreateToken)
    • ใช้น้ำหนักที่คำนวณได้ทำ batch insert ลงใน index_entries
  • โครงสร้างนี้รองรับการป้องกันข้อมูลซ้ำ การเพิ่มประสิทธิภาพ และการอัปเดต

บริการค้นหา

  • ประมวลผลคิวรีด้วย tokenizer ชุดเดียวกันเพื่อให้ได้ ชุดโทเค็นที่เหมือนกับตอน indexing
  • ลบโทเค็นซ้ำ เรียงตามความยาวโดยให้โทเค็นที่ยาวกว่านำหน้า และจำกัดสูงสุด 300 รายการ
  • ใช้ SQL query เพื่อ join โทเค็นกับเอกสาร พร้อม คำนวณและจัดเรียงคะแนนความเกี่ยวข้อง
  • ส่งคืนผลลัพธ์ในรูปแบบ SearchResult(documentId, score)

อัลกอริทึมการให้คะแนน

  • คะแนนพื้นฐาน: SUM(sd.weight)
  • การปรับตามความหลากหลายของโทเค็น: LOG(1 + COUNT(DISTINCT token_id))
  • การปรับตามน้ำหนักเฉลี่ย: LOG(1 + AVG(weight))
  • ค่าปรับโทษตามความยาวเอกสาร: 1 / (1 + LOG(1 + token_count))
  • Normalization: หารด้วยคะแนนสูงสุดเพื่อปรับให้อยู่ในช่วง 0~1
  • กรองค่าน้ำหนักโทเค็นขั้นต่ำ (st2.weight >= ?) เพื่อ ตัดการตรงกันที่ไม่มีความหมายและมีน้ำหนักต่ำ

การจัดการผลลัพธ์

  • ผลการค้นหาจะส่งคืนเป็น ID เอกสารและคะแนน แล้วแปลงเป็นเอกสารจริงผ่าน repository
  • ใช้ฟังก์ชัน FIELD() เพื่อ ดึงเอกสารโดยยังคงลำดับผลการค้นหาไว้

ความสามารถในการขยายของระบบ

  • เพิ่ม tokenizer ใหม่ได้ด้วยการ implement TokenizerInterface
  • ลงทะเบียนเอกสารประเภทใหม่ได้ด้วยการ implement IndexableDocumentInterface
  • ปรับน้ำหนักหรือ logic ของ scoring ได้ เพียงแก้ SQL

บทสรุป

  • โครงสร้างนี้เป็น เสิร์ชเอนจินที่เรียบง่ายแต่ใช้งานได้จริง และให้ประสิทธิภาพเพียงพอโดยไม่ต้องมีโครงสร้างพื้นฐานภายนอก
  • จุดเด่นคือ ตรรกะที่ชัดเจน การควบคุมได้ทั้งหมด และการดีบักที่ง่าย
  • เน้นย้ำว่า เมื่อเทียบกับระบบที่ซับซ้อนแล้ว โค้ดที่เข้าใจและควบคุมได้ด้วยตัวเอง อาจมีคุณค่ามากกว่า

1 ความคิดเห็น

 
GN⁺ 2025-11-18
ความคิดเห็นจาก Hacker News
  • แนวคิดพื้นฐานของการค้นหานั้นเรียบง่ายและเป็นขอบเขตปัญหาที่น่าสนใจ
    แต่สิ่งที่ยากจริง ๆ คือการจัดการกับ ข้อมูลปริมาณมหาศาล และการรับมือกับ คำค้นที่กำกวม
    แนวทางที่อิง DBMS นั้นใช้ได้ดีกับเว็บไซต์ขนาดเล็ก แต่พอถึงระดับ Wikipedia ภาษาอังกฤษก็จะชนเพดานอย่างรวดเร็ว
    สำหรับผู้เริ่มต้น SeIRP e-book เป็นแหล่งเรียนฟรีที่ดี

    • ข้อมูลขนาดใหญ่แน่นอนว่ายาก แต่ผมคิดว่าการจัดการกับคำค้นที่กำกวมเป็นปัญหาย่อยของคำถามว่า “จะตัดสินผลลัพธ์ที่เกี่ยวข้องที่สุดอย่างไร”
      การที่ไม่มีคำตอบที่ชัดเจน นี่แหละที่ทำให้มันยากเป็นพิเศษ
      Google บางครั้งก็แสดงโฆษณาเป็น ‘ผลลัพธ์ที่เกี่ยวข้องที่สุด’ ด้วย ดังนั้น Marginalia Search จึงเป็นกรณีเปรียบเทียบที่ดี
      อยากรู้ว่าเคยอ้างอิง งานวิจัยของ TREC บ้างหรือไม่
    • ทุกวันนี้ผมกลับคิดว่า การหลบเลี่ยง SEO spam เป็นปัญหาใหญ่กว่า
      เสิร์ชเอนจินต้องต่อสู้กับ ผู้เล่นที่เป็นปฏิปักษ์ ซึ่งหวังหารายได้จากโฆษณาอยู่ตลอดเวลา
      มันกลายเป็น เกมแมวไล่จับหนูที่ไม่มีวันจบ ที่ต้องคอยเปลี่ยนตัวชี้วัดคุณภาพเพื่อไม่ให้พวกเขาหาประโยชน์จากมันได้
    • ถ้าใช้ SQLite บนเซิร์ฟเวอร์เครื่องเดียว (ราคาราวหนึ่งพันดอลลาร์) เพื่อรันกระบวนการธุรกิจที่เน้นข้อความ อยากรู้ว่าขนาด ที่เก็บเอกสาร ที่ใช้งานได้จริงจะอยู่ประมาณไหน
      ถ้าคิดจากความเร็วระดับ 5 วินาทีต่อ 1 คำค้น และประมาณ 12 คำค้นต่อนาที อยากรู้ว่าจะค้นในคอร์ปัสขนาดประมาณไหนได้
    • ผมชอบ Marginalia Search มากจริง ๆ
    • ผมคิดว่าความยากของการค้นหาไม่ได้มีแค่เรื่องขนาดข้อมูล แต่คือ ปัญหาในการตัดสินว่าจะคืนผลลัพธ์อะไร
      ตัวอย่างเช่น มันยากที่จะตัดสินว่าระหว่างบทความวิกิของ Gilligan’s Island กับแฟนบล็อก อันไหนเป็นผลลัพธ์ที่ “ดีกว่า”
      และพอมี การบิดเบือนอันดับ หรือ keyword stuffing เพิ่มเข้ามา มันก็กลายเป็นความท้าทายที่ซับซ้อนกว่าปัญหาการสเกลเสียอีก
  • การค้นหานั้นยากมากจริง ๆ
    ต่อให้เป็นบริษัทที่มีทรัพยากรและความสามารถล้นเหลืออย่าง Apple, Microsoft, OpenAI ก็ยังมี คุณภาพฟังก์ชันค้นหาต่ำ
    นี่ไม่ใช่แค่ปัญหาทางเทคนิค

    • เหตุผลที่บริษัทส่วนใหญ่ทำระบบค้นหาได้ไม่ดี เป็นเพราะ วัฒนธรรมองค์กรและวิธีพัฒนา ขัดกับการพัฒนาระบบค้นหา
      การเพิ่มคุณภาพการค้นหาต้องอาศัย การปรับจูนพารามิเตอร์การจัดอันดับอย่างละเอียด ซึ่งเป็นงานที่วางแผนผ่านระบบจัดการอย่าง sprint หรือ Jira ได้ยาก
      ท้ายที่สุดแล้ว นี่เป็นงานที่ต้องการ ความไว้วางใจและอิสระในการทำงาน ให้กับนักพัฒนา
    • แต่บางบริษัทคุณภาพต่ำก็เพียงเพราะ การค้นหาไม่ใช่เรื่องสำคัญลำดับต้น ๆ
      พวกเขาลงทุนกับโมเดล AI เป็นหลักหมื่นล้าน แต่เว็บแอปหรือระบบค้นหาถูกมองเป็นเรื่องรอง เลยออกมาเป็นแบบนั้น
  • เมื่อประมาณ 10 ปีก่อน ผมเคยทำงานกับ เพื่อนร่วมงานที่กำลังเรียนปริญญาเอกด้านการออกแบบเสิร์ชเอนจิน
    เขาพูดถึงการผสานการค้นหากับฐานข้อมูลอย่างกระตือรือร้นมาก และผมก็ได้เรียนรู้อะไรเยอะจากเขา
    สักวันหนึ่งผมอยากลงลึกกับภายในของ Apache Solr และ Lucene ให้มากกว่านี้

    • ผมเองก็ชอบเรื่องในสายงานของตัวเองมากจนพูดได้เป็นชั่วโมงเหมือนกัน แต่คนที่สนใจ รายละเอียดการติดตั้งใช้งานของระบบขนาดใหญ่ แบบนี้มีไม่มากนัก
  • สมัยก่อนยังไม่มีโซลูชันค้นหาแบบโอเพนซอร์ส จึงต้องทำเอง
    บทเรียนที่ได้จากประสบการณ์นั้นคือ “อย่าสร้างเสิร์ชเอนจินเอง
    มีผู้คนมากมายทุ่มเวลาหลายปีให้กับปัญหานี้ และถ้าทำเองจะตกไปอยู่ใน นรกแห่งการบำรุงรักษาที่ไม่สิ้นสุด
    พอมีคำขออย่าง “ช่วยเพิ่มการแก้คำสะกดหน่อย” หรือ “ปีหน้าใส่ระบบจัดหมวดหมู่ด้วย” เข้ามาต่อเนื่องก็จบเลย

  • ผมเคยสนุกมากกับคอร์สสร้างเสิร์ชเอนจินของ ศาสตราจารย์ David Evans แห่ง Virginia University
    การได้สร้าง “เสิร์ชเอนจินแบบคลาสสิก” ด้วยตัวเองเป็นโปรเจกต์ที่สนุกมาก
    ดูได้ที่ ลิงก์คอร์ส และ เพลย์ลิสต์ YouTube

    • ผมก็เคยเรียนคอร์สนั้นเหมือนกัน และมันเป็น ชั้นเรียนที่ทั้งน่าสนใจและเข้มข้นแม้สำหรับโปรแกรมเมอร์มือใหม่
  • สิ่งที่ผมหงุดหงิดกับเสิร์ชเอนจินที่ใช้บ่อยคือมันมัก ละเลยตัวย่อหรือคำที่ยาว 2~3 ตัวอักษร
    เวลาเสิร์ชคำสั้น ๆ อย่าง “mp3” หรือ “PHP” แล้วโดนตัดทิ้ง มันน่าหงุดหงิดมาก

  • ผมเคยอ่าน Programming Collective Intelligence ของ Toby Segaran แล้วได้แรงบันดาลใจจากไอเดียหลากหลายอย่าง เช่น การค้นหา ระบบแนะนำ และตัวจำแนก

    • ผมก็ชอบหนังสือเล่มนั้นเหมือนกัน แต่เห็นผู้เขียนพูดใน YouTube ภายหลังว่า “ตอนนี้มันล้าสมัยแล้ว อย่าใช้เลย”
    • มันเป็นหนังสือที่ดีมากจริง ๆ เลยสงสัยว่ามี ฉบับที่อัปเดตให้ทันปี 2025 หรือไม่
  • เป็นบทความที่น่าสนใจ
    ทำให้อยากรู้ว่าระดับการปรับแต่ง tokenizer ของเสิร์ชเอนจินยอดนิยมสูงแค่ไหน

  • อยากรู้ว่าระบบนี้จะ ทำงานได้ขยายขนาดแค่ไหน
    Elasticsearch แสดงประสิทธิภาพที่น่าประทับใจทีเดียว แม้จะเกินสเกลที่แนะนำไปแล้ว

  • การสร้างเสิร์ชเอนจินค้นหาข้อความแบบง่าย ๆ ไม่ใช่เรื่องยาก
    แต่การสร้าง เสิร์ชเอนจินที่ดี เป็นคนละเรื่องกันเลย
    แค่ทำอัลกอริทึมอย่าง BM25 ได้ยังไม่พอ
    บริษัทส่วนใหญ่ที่ผมเคยไปให้คำปรึกษาใช้โซลูชันของตัวเองอยู่พักหนึ่ง สุดท้ายก็ย้ายไป Elasticsearch หรือ Opensearch
    การทำเองตอนแรกดูเรียบง่าย แต่พอเวลาผ่านไปก็ซับซ้อนขึ้นเพราะ ปัญหาเรื่องอันดับและประสิทธิภาพที่ตกลง
    อาการอย่าง “ช้า” หรือ “ได้ผลลัพธ์เพี้ยน ๆ” จะวนกลับมาซ้ำ ๆ
    Elasticsearch แก้ปัญหาแบบนี้มาตั้งแต่ 10 ปีก่อนแล้ว และตอนนี้ก็พัฒนาไปไกลกว่านั้นมาก
    แม้จะมีคนบอกว่า “ตั้งค่ายาก” แต่ทุกวันนี้ส่วนใหญ่ คอนฟิกอัตโนมัติ กันแล้ว และก็มีบริการแบบ managed มากมาย
    มันอาจใช้ง่ายกว่า Postgres เสียอีก
    สุดท้ายสิ่งสำคัญคือ การปรับ index mapping ให้เหมาะสม
    บางคนอาจบอกว่า “ฟีเจอร์ขั้นสูงพวกนั้นไม่จำเป็น” แต่ในความเป็นจริง คุณภาพการค้นหาส่งผลตรงต่อการอยู่รอดของธุรกิจ
    ถ้าต้องการระบบค้นหาที่ดีจริง ๆ สุดท้ายก็ต้องยอมรับความซับซ้อนเหล่านี้

    • ผมเองก็ใช้ Elasticsearch เป็นค่าเริ่มต้นเหมือนกัน
      ทางเลือกใหม่ ๆ อย่าง SeekStorm ที่ถูกพูดถึงบ่อยใน HN ช่วงหลังก็ดูน่าสนใจ แต่ผมยังไม่เคยเห็นเคสใช้งานจริงในโปรดักชัน
    • เห็นด้วยกับคำพูดที่ว่า “ไม่มีฟีเจอร์ไหนที่ไม่จำเป็น”
      โดยเฉพาะ ทิปเรื่องปิด dynamic mapping และกันไม่ให้ index ฟิลด์ที่ไม่จำเป็น นั้นมีประโยชน์มาก
    • อยากรู้ว่าคิดอย่างไรกับ ManticoreSearch
      เท่าที่รู้มันเป็นโปรเจกต์ที่เก่ากว่า Lucene