6 คะแนน โดย GN⁺ 2023-07-13 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • การค้นหาข้อความแบบเต็มของ PostgreSQL เป็นแนวทางในการสร้างฟังก์ชันค้นหาโดยไม่ต้องใช้เสิร์ชเอนจินแยกต่างหาก ด้วยการผสาน tsvector, tsquery, @@, ts_rank และ GIN index
  • คำค้นและเอกสารถูกทำให้เป็นมาตรฐานในระดับ lexeme และสามารถแสดงคิวรีด้วยตัวดำเนินการอย่าง AND, OR, NOT, FOLLOWED BY จึงรองรับไวยากรณ์การค้นหาทั่วไปได้เป็นส่วนใหญ่
  • ในสภาพแวดล้อมตัวอย่าง GIN index ลดเวลาค้นหาจาก มากกว่า 200ms เหลือประมาณ 4ms แต่เมื่อผลลัพธ์มีจำนวนมากขึ้น ค่าใช้จ่ายในการจัดอันดับและเรียงลำดับของ ts_rank อาจกลายเป็นคอขวดได้
  • การปรับความเกี่ยวข้องทำได้โดยเพิ่มสัญญาณอย่างน้ำหนักของชื่อเรื่อง จำนวนโหวต คะแนน ประเภท และความใหม่ เข้าไปใน นิพจน์การเรียงลำดับ หรือให้น้ำหนักคอลัมน์ด้วย setweight
  • สามารถทำการรองรับคำสะกดผิด, faceted search, autocomplete, การค้นหาวลีแบบตรงตัว และ hybrid search ได้เช่นกัน แต่ใน PostgreSQL ต้องเชื่อมองค์ประกอบต่าง ๆ เอง และควรตรวจสอบขีดจำกัดด้านประสิทธิภาพกับชุดข้อมูลขนาดใหญ่

แนวทางของการค้นหาข้อความแบบเต็มใน PostgreSQL

  • PostgreSQL มี องค์ประกอบ ระดับล่างสำหรับการค้นหาข้อความแบบเต็ม และสามารถนำมาประกอบกันเพื่อสร้างฟังก์ชันแบบเสิร์ชเอนจินได้
  • วิธีนี้ยืดหยุ่น แต่ต้องลงแรงพัฒนามากกว่า Elasticsearch, Typesense และ Meilisearch ซึ่งมีการค้นหาข้อความแบบเต็มเป็นวัตถุประสงค์หลัก
  • คิวรีตัวอย่างใช้ชุดข้อมูล Wikipedia Movie Plots จาก Kaggle
    • มีชื่อภาพยนตร์ 34,000 เรื่อง
    • ขนาดไฟล์รูปแบบ CSV ประมาณ 81MB

องค์ประกอบหลัก

  • การค้นหาข้อความแบบเต็มของ PostgreSQL ทำงานโดยมีองค์ประกอบต่อไปนี้เป็นศูนย์กลาง
    • tsvector: เก็บข้อความเป้าหมายสำหรับค้นหาเป็นรายการ lexeme ที่ทำให้เป็นมาตรฐานแล้ว
    • tsquery: แสดงคิวรีค้นหาที่ทำให้เป็นมาตรฐานแล้ว
    • @@: ตัวดำเนินการ match สำหรับตรวจสอบว่า tsquery ตรงกับ tsvector หรือไม่
    • ts_rank, ts_rank_cd: คำนวณคะแนนความเกี่ยวข้องของผลการค้นหา
    • GIN index: inverted index สำหรับคิวรี tsvector อย่างมีประสิทธิภาพ

tsvector และการตั้งค่าการค้นหา

  • tsvector เก็บรายการ lexeme ที่เรียงลำดับแล้ว
    • lexeme คล้ายกับ token แต่เป็นสตริงที่ถูกทำให้เป็นมาตรฐานเพื่อให้รูปแบบต่าง ๆ ของคำเดียวกันถูกรวมเป็นรูปเดียวกัน
    • ในการตั้งค่าภาษาอังกฤษ จะทำให้ตัวพิมพ์ใหญ่เป็นตัวพิมพ์เล็กและทำให้เป็นมาตรฐานด้วยการตัด suffix
  • เมื่อใช้ to_tsvector แยกวิเคราะห์ประโยคภาษาอังกฤษ stop words อย่าง “I”, “to”, “an” จะถูกนำออก
    • “refuse” และ “Refusing” จะถูกแปลงเป็น refus ทั้งคู่
    • เครื่องหมายวรรคตอนจะถูกละเว้น
    • ตำแหน่งคำในข้อความต้นฉบับและน้ำหนักก็ถูกบันทึกไว้ด้วย
  • หากใช้การตั้งค่า simple แทนการตั้งค่าการค้นหา english คำจะถูกรวมไว้ตามรูปแบบที่พบในข้อความ
    • “refuse” และ “refusing” จะยังเป็น lexeme คนละตัว
    • การตั้งค่า simple มีประโยชน์เป็นพิเศษกับคอลัมน์ที่มี label หรือ tag
  • PostgreSQL มีการตั้งค่าการค้นหาในตัวสำหรับหลายภาษา แต่ไม่มีการตั้งค่าสำหรับ CJK (จีน·ญี่ปุ่น·เกาหลี)
    • สำหรับภาษาที่ไม่รองรับ การตั้งค่า simple อาจใช้งานได้ดีในทางปฏิบัติ
    • อย่างไรก็ตาม ยังไม่แน่ชัดว่าเพียงพอสำหรับ CJK หรือไม่

tsquery และการแสดงคิวรี

  • tsquery เป็นชนิดข้อมูลที่ใช้แสดงคิวรีค้นหาที่ทำให้เป็นมาตรฐานแล้ว
    • คำค้นต้องเป็น lexeme ที่ทำให้เป็นมาตรฐานแล้ว
    • คำค้นหลายคำสามารถเชื่อมกันด้วยตัวดำเนินการ AND, OR, NOT และ FOLLOWED BY
  • to_tsquery, plainto_tsquery, websearch_to_tsquery ช่วยเปลี่ยนข้อความที่ผู้ใช้ป้อนให้เป็น tsquery ที่เหมาะสม
    • บทบาทหลักคือทำให้คำในข้อความอินพุตเป็นมาตรฐาน
  • การใช้ websearch_to_tsquery ทำให้สร้างคิวรีที่ใกล้เคียงช่องค้นหาทั่วไปได้
    • darth vader จะถูกประมวลผลเป็น logical AND ซึ่งหมายความว่าทั้งสองคำต้องอยู่ในเอกสาร
    • สามารถค้นหาแบบ OR และตัดคำออกได้ด้วย
    • การค้นหาวลีจะแสดงรูปแบบที่คำเรียงต่อกันตามลำดับ
  • ในการตั้งค่าภาษาอังกฤษ stop words อย่าง “the” จะถูกนำออก ดังนั้นในการค้นหาวลีบางกรณี ข้อความเกือบทั้งวลีอาจหายไป
    • ในกรณีเช่นนี้ การใช้การตั้งค่า simple จะช่วยให้ได้ผลลัพธ์ตามที่คาดหวัง
  • ตัวดำเนินการ @@ ใช้เมื่อตรวจสอบว่า tsquery ตรงกับ tsvector หรือไม่

GIN index และประสิทธิภาพการค้นหา

  • GIN ย่อมาจาก Generalized Inverted Index เป็นชนิด index ที่ออกแบบมาสำหรับคิวรีที่ค้นหาค่าองค์ประกอบภายในค่าประกอบ
  • GIN ใช้ได้ทั้งกับการค้นหาข้อความและคิวรี JSON
  • สามารถสร้างคอลัมน์ tsvector ที่รวมหลายคอลัมน์ที่ต้องการให้ค้นหาได้ แล้วสร้าง GIN index บนคอลัมน์นั้น
  • ในสภาพแวดล้อมตัวอย่าง GIN index ลดเวลาค้นหาจาก มากกว่า 200ms เหลือประมาณ 4ms

การจัดอันดับและการคำนวณความเกี่ยวข้อง

  • เพื่อประสบการณ์ค้นหาที่ดี ควรเรียงผลลัพธ์ตาม ความเกี่ยวข้อง
  • PostgreSQL มีฟังก์ชันจัดอันดับที่กำหนดไว้ล่วงหน้า 2 ตัวคือ ts_rank และ ts_rank_cd
    • ทั้งสองฟังก์ชันคำนึงถึงความถี่ที่คำค้นปรากฏ
    • ts_rank_cd ยังสะท้อนความใกล้กันระหว่าง lexeme ที่ตรงกันด้วย
  • ความเกี่ยวข้องขึ้นอยู่กับลักษณะของแอปพลิเคชันอย่างมาก
    • ฟังก์ชันจัดอันดับพื้นฐานเป็นเหมือนจุดเริ่มต้น และสามารถสร้างฟังก์ชันจัดอันดับเองหรือผสานกับปัจจัยอื่นได้ตามต้องการ
  • ts_rank ต้องเข้าถึงคอลัมน์ search ของแต่ละผลลัพธ์
    • หากเงื่อนไข WHERE match กับหลายแถว PostgreSQL ต้องเข้าถึงแถวทั้งหมดเพื่อคำนวณอันดับและเรียงลำดับ
    • ในสภาพแวดล้อมตัวอย่าง คิวรีหนึ่งส่งคืนผลใน 5~7ms แต่คิวรีที่ต้องจัดอันดับผลลัพธ์มากกว่า 1,000 รายการ เช่น darth OR vader ใช้เวลาประมาณ 80ms

การปรับแต่งความเกี่ยวข้อง

  • ความเกี่ยวข้องตามความถี่ของคำเป็นค่าเริ่มต้นที่ดี แต่ข้อมูลอาจมีสัญญาณอื่นที่สำคัญกว่าความถี่
  • ในชุดข้อมูลภาพยนตร์ สามารถสะท้อนสัญญาณต่อไปนี้ในความเกี่ยวข้องได้
    • ให้ผลลัพธ์ที่ match ในชื่อเรื่องสำคัญกว่าผลลัพธ์ที่ match ในคำอธิบายหรือเรื่องย่อ
    • ดันภาพยนตร์ที่ได้รับความนิยมมากขึ้นโดยอิงจากคะแนนหรือจำนวนโหวต
    • หากผู้ใช้ชอบคอเมดี้ ให้จัดภาพยนตร์คอเมดี้ไว้สูงขึ้น
    • มองว่าชื่อเรื่องใหม่ ๆ มีความเกี่ยวข้องมากกว่าชื่อเรื่องเก่า
  • เสิร์ชเอนจินเฉพาะทางมีฟังก์ชันสำหรับกำหนดให้คอลัมน์หรือฟิลด์ต่าง ๆ ส่งผลต่ออันดับ
    • ตัวอย่างเอกสารที่เกี่ยวข้องดูได้ที่ Elastic, Typesense, Meilisearch

การ boost ตามตัวเลข วันที่ และค่าที่ตรงตัว

  • PostgreSQL ไม่ได้มีการ boost ตามคอลัมน์อื่นให้โดยตรง แต่สุดท้ายแล้วการจัดอันดับก็คือ นิพจน์การเรียงลำดับ จึงสามารถเพิ่มสัญญาณของเราเองเข้าไปได้
  • หากต้องการสะท้อนจำนวนโหวต สามารถทำได้โดยเพิ่ม boost ตามจำนวนโหวตเข้าไปในคะแนนอันดับ
    • ในตัวอย่างใช้ลอการิทึมเพื่อทำให้อิทธิพลเพิ่มขึ้นอย่างค่อยเป็นค่อยไป
    • ใช้ค่าสัมประสิทธิ์ 0.01 เพื่อปรับ booster ให้อยู่ในสเกลใกล้เคียงกับคะแนนอันดับ
  • สามารถสร้างฟังก์ชันที่ซับซ้อนขึ้นเพื่อ boost คะแนนเฉพาะเมื่อจำนวนโหวตถึงระดับหนึ่งได้
  • หากต้องการดันประเภทใดประเภทหนึ่ง สามารถใช้ฟังก์ชันอย่าง valueBooster ที่คืนค่าสัมประสิทธิ์เฉพาะเมื่อค่าตรงกับค่าคอลัมน์ที่กำหนด

น้ำหนักของคอลัมน์

  • lexeme ใน tsvector สามารถติด น้ำหนัก ได้
  • PostgreSQL รองรับน้ำหนัก 4 ระดับคือ A, B, C, D
    • A เป็นน้ำหนักสูงสุด
    • D เป็นน้ำหนักต่ำสุดและเป็นค่าเริ่มต้น
  • สามารถใช้ฟังก์ชัน setweight เพื่อควบคุมน้ำหนักเมื่อสร้างคอลัมน์ tsvector
  • เมื่อให้น้ำหนักสูงขึ้นกับคอลัมน์ชื่อเรื่อง ภาพยนตร์ที่มีคำค้นในชื่อเรื่องจะถูกดันขึ้นไปด้านบนของผลลัพธ์ และคะแนนอันดับก็เพิ่มขึ้นด้วย
  • ข้อจำกัดคือมี weight class เพียงสี่ระดับ และต้องใช้ weight ตอนคำนวณ tsvector

การรองรับคำสะกดผิดและ fuzzy search

  • PostgreSQL ไม่รองรับ fuzzy search หรือการรองรับคำสะกดผิดโดยตรงเมื่อใช้ tsvector และ tsquery
  • ภายใต้สมมติฐานว่าคิวรีมีคำสะกดผิด สามารถทำได้ด้วยวิธีต่อไปนี้
    • index lexeme ทั้งหมดจากเนื้อหาไว้ในตารางแยก
    • ค้นหาคำผู้สมัครสำหรับแต่ละคำในคิวรีด้วย similarity หรือ Levenshtein distance
    • แก้คิวรีให้รวมคำที่พบ
    • ค้นหาด้วยคิวรีที่แก้แล้ว
  • ในตัวอย่างใช้ Levenshtein distance เพราะเป็นวิธีที่เสิร์ชเอนจินต่าง ๆ ใช้สำหรับ fuzzy search
  • หลังจากได้รายการคำผู้สมัครแล้ว ต้องปรับคิวรีให้รวมคำเหล่านั้นทั้งหมด

Faceted search

  • Faceted search ใช้อย่างแพร่หลาย โดยเฉพาะในเว็บไซต์อีคอมเมิร์ซ เพื่อให้ผู้ใช้ค่อย ๆ จำกัดขอบเขตการค้นหาซ้ำ ๆ
  • ใน PostgreSQL สามารถทำได้โดยกำหนดหมวดหมู่ด้วยตนเองแล้วเพิ่มเป็นเงื่อนไข WHERE ของการค้นหา
  • อีกวิธีที่เป็นไปได้คือสร้างหมวดหมู่เชิงอัลกอริทึมจากข้อมูลที่มีอยู่
    • ในตัวอย่างสร้าง facet “Decade” จากปีของภาพยนตร์
    • สามารถคำนวณจำนวน match ของแต่ละ decade แล้วแสดงในวงเล็บได้
  • หากต้องการดึงหลาย facet ในคิวรีเดียว สามารถผสาน CTE ได้
  • วิธีนี้อาจทำงานได้ดีในชุดข้อมูลขนาดเล็กถึงกลาง แต่เมื่อเป็นชุดข้อมูลใหญ่มากอาจช้าลงได้

ขอบเขตและข้อจำกัดของเสิร์ชเอนจิน PostgreSQL

  • เมื่อนำองค์ประกอบการค้นหาข้อความแบบเต็มของ PostgreSQL มาผสานกัน ก็สามารถสร้างเสิร์ชเอนจินที่ค่อนข้างขั้นสูงได้
  • การค้นหาบน PostgreSQL ยังรองรับ join และ ACID transaction ไปพร้อมกันด้วย
    • สิ่งเหล่านี้เป็นฟีเจอร์ที่เสิร์ชเอนจินอื่นโดยทั่วไปไม่มี
  • หัวข้อการค้นหาขั้นสูงที่ยังน่ากล่าวถึงเพิ่มเติมมีดังนี้
    • คำแนะนำและ autocomplete
    • การ match วลีแบบตรงตัว
    • hybrid search ที่ผสานกับ pg-vector
  • ฟีเจอร์เหล่านี้ทำได้ด้วย PostgreSQL แต่ต้องประกอบองค์ประกอบต่าง ๆ เอง
  • ในบางกรณี ประสิทธิภาพอาจลดลงกับชุดข้อมูลขนาดใหญ่มาก
  • บทความต่อเนื่อง part 2 เปรียบเทียบระหว่างการทำ search ใน PostgreSQL กับการเพิ่ม Elasticsearch เข้าไปในโครงสร้างพื้นฐานและซิงก์ข้อมูล

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

 
GN⁺ 2023-07-13
ความคิดเห็นบน Hacker News
  • ไม่ควรพยายามทำสิ่งนี้ด้วย วิธีเลียนแบบฟีเจอร์ของ Elasticsearch
    ในยุคปี 2000 ผมเคยสร้างเสิร์ชเอนจินด้วย MySQL 3.x ที่พาร์สข้อมูล EXIF ของรูปภาพ แล้วทำดัชนีระบบหมวดหมู่ 3 ระดับพร้อมจำนวนรายการ ซึ่งเป็นสิ่งที่แม้แต่ผู้ขายราคาแพงหลายรายก็ยังทำได้ไม่ดี และ Autonomy ยังทำไม่ได้แม้กระทั่งหมวดหมู่ระดับบนสุด
    ตลอด 6 สัปดาห์ ผมแทบจะทำสำเร็จด้วยการเขียน SQL ที่เปราะบางจนแค่เปลี่ยนลำดับคอลัมน์ใน SELECT ประสิทธิภาพก็เปลี่ยน แต่ไม่อยากทำอีกแล้ว แม้ฐานข้อมูลโดยแก่นแล้วจะคล้ายกันมาก แต่ เสิร์ชเอนจินพัฒนาไปไกลกว่ามาก
    ทำเพื่อความอยากรู้อยากเห็นทางปัญญาได้ แต่การค้นหาไม่ใช่แค่ tokenize แล้วจบ ไม่นานก็จะมีความต้องการอย่างการนำทางตามหมวดหมู่, หลายภาษา, คำพ้องอัตโนมัติ, คำแนะนำสะกดแบบ "Did you mean", ประสิทธิภาพระดับใหญ่ และสุดท้ายคุณจะติดอยู่ในทางตันเอง เพื่อสุขภาพจิต ใช้เสิร์ชเอนจินจะดีกว่า และสำหรับการ sync ระหว่าง PG กับ ES ก็มีเครื่องมืออย่าง ZomboDB หรือ PGSync

    • ในเธรดนี้มีสองประเด็นที่ต่างกันปะปนอยู่ ถ้าจะสร้าง เสิร์ชเอนจินที่ลูกค้าใช้งานโดยตรง ก็ควรใช้เครื่องมือทรงพลังอย่าง Elasticsearch และไม่ต้องสร้างล้อขึ้นมาใหม่
      ในทางกลับกัน ถ้านักวิเคราะห์ข้อมูลหรือนักพัฒนาต้องการค้นหาคอลัมน์ข้อความในฐานข้อมูลขนาดใหญ่ให้ยืดหยุ่นกว่า LIKE/ILIKE การสร้าง ดัชนี/ตาราง full-text search ไว้ในฐานข้อมูลเดียวกันอาจง่ายและเร็วกว่าในการไปให้ถึง 90%
  • รออ่านภาค 2 ที่เปรียบเทียบ Postgres vs Elasticsearch แอปหนึ่งของบริษัทใช้ PG สำหรับ CRUD ของออบเจ็กต์ และใช้ Elastic สำหรับค้นหา แต่ประเมินความพยายามในการ sync data store ทั้งสองต่ำไปมาก และตอนนี้กำลังพิจารณาเอา Elasticsearch ออกจริง ๆ

    • วิธีที่เคยใช้คือ มอง Elasticsearch เป็นปลายทางที่สร้างใหม่ได้ทุกเมื่อ
      ทุก 5 นาที cron จะหาออบเจ็กต์เป้าหมายสำหรับทำดัชนีจาก DB ที่มี last_modified_at > last_indexing_started_timestamp แล้วทำดัชนีเข้า Elasticsearch จากนั้นอัปเดต last_indexing_started_timestamp เป็นเวลาที่เริ่ม sync วิธีนี้ออบเจ็กต์ที่ถูกแก้ไขระหว่างเริ่มและจบการรัน จะถูกจับได้ในการรันถัดไป
      ถ้าต้องสร้าง Elasticsearch ใหม่ ก็ล้างเวลาทำดัชนีล่าสุดแล้ว sync ใหม่ตั้งแต่ต้นได้ และเพราะมันกู้คืนตัวเองได้ การ sync จึงไม่เพี้ยน
    • ที่ทำงานเก่าเคยใช้โครงสร้างคล้ายกัน ก็ไม่ได้ยากเป็นพิเศษ ทุกครั้งที่ entity ใน PG ถูกอัปเดต จะส่งข้อความเพื่อ replicate ไปยัง ES แบบ async และฝั่ง ES จะเติมข้อมูลโดย lookup จาก PG ด้วย ID
      งาน async ก็เหมือนทั่วไปที่ต้องมี monitoring และ retry แต่ ES เสถียรและเร็ว ปัญหาจึงพบไม่บ่อย อย่างไรก็ตามข้อกำหนดด้าน consistency ค่อนข้างผ่อนคลาย แค่สถานะล่าสุดของ PG ไปถึง ES ภายในเวลาที่สมเหตุสมผลก็พอ ดังนั้นถ้าข้อกำหนดต่างกัน ผลก็อาจต่างกันได้
    • เคยไปในแนวเดียวกันคือ Postgres สำหรับ CRUD, Elastic สำหรับค้นหา แต่ไม่ได้ประเมินต่ำไปแค่การ sync สอง storage เท่านั้น ยังประเมินต่ำไปถึง การดูแล Elastic cluster ให้เสถียร ด้วยทีมและประสบการณ์ที่จำกัด
      หลังย้ายมาใช้ full-text search ของ Postgres พร้อมใส่น้ำหนักให้ดัชนีและคิวรี ก็สามารถจัดการทุกอย่างที่ต้องการภายใน Postgres ได้ด้วย update trigger และ search query ที่เร็วมาก
    • สงสัยว่าเคยลอง zombodb หรือยัง [https://www.zombodb.com/]
    • เคยทำมาแล้ว เป็นงานที่ยากแม้จะมีประสบการณ์พอสมควร และผลลัพธ์บางส่วนก็แย่ลงด้วย ส่วนใหญ่ใกล้เคียงกัน และมีส่วนน้อยมากที่ดีขึ้น
      โดยรวมถือว่าสำเร็จ และลดภาระปฏิบัติการลงมากจนคุ้มกับเวลาวิศวกรรมที่ลงไป แต่ไม่ใช่เรื่องที่ควรเริ่มแบบเบา ๆ
      แล้วแต่ความต้องการ materialized view, view ปกติ, trigger อาจดีกว่า การค้นหาข้อความในตัวอาจไม่เหมาะกับ use case และการสร้างทางเลือกเองก็ไม่ได้ยากเสมอไป
  • ถ้าไม่มี เวลา query p50/p99 ภายใต้โหลดที่สมจริง ก็ยากจะบอกว่าพิสูจน์แล้ว เสิร์ชเอนจินที่คืนผลใน 1 นาทีไม่ใช่ "ขั้นสูง" และฐานข้อมูลเชิงสัมพันธ์อย่าง Postgres แน่นอนว่าทำได้บนกระดาษ

    • ในฐานะผู้เขียน มีแผนจะเขียนบทความต่อเนื่องที่เทียบกับ Elasticsearch แต่คงไม่พยายามทำ benchmark เพราะไม่ว่าจะสร้างสถานการณ์สมจริงแบบไหน ก็อาจไม่ตรงกับ use case ของแต่ละคนอยู่ดี
      โดยรวมเห็นด้วย และถ้าเป็นสเกลใหญ่ เช่น มากกว่าหลายล้านเรคคอร์ด ก็คงไม่น่าใช้วิธีนี้ ความสนใจหลักคือดูว่าสามารถจำลองฟีเจอร์ได้มากแค่ไหน
      สำหรับ use case ค้นหาขนาดเล็ก ข้อดีคือมีอินฟราที่ต้องดูแลน้อยกว่า มี strong consistency และ join ได้ ที่ Xata ก็กำลังคิดถึงเส้นทางย้ายผ่านที่นุ่มนวลจากการใช้ Postgres ในสเกลเล็กไปสู่ Elasticsearch โดยมี breaking change ให้น้อยที่สุด
    • ตอนสัมภาษณ์กับอดีตพนักงาน Google ได้ยินมาว่าพวกเขา cache ผลการค้นหาของคำค้นที่เคยมีคนค้นทั้งหมด และเมื่ออัปเดตดัชนี ก็อัปเดตผลลัพธ์ที่ cache ไว้ด้วย
      จากมุมมองนั้น ผลการค้นหาที่เร็วเองไม่ได้ดูน่าทึ่งมาก เพราะสามารถอัปเดตผลลัพธ์ใน cache ด้วยงาน background ไว้ตลอด แล้วพอมี request ก็ส่งกลับไปได้เลย การ cache และเวลาตอบสนอง แบบนี้ดูเป็นคนละเรื่องกับความเร็วในการคำนวณผลการค้นหาจริง
    • การเรียกร้อง p50/p99 ภายใต้โหลดสมจริงมีปัญหาอยู่ ถ้าไม่ได้มีคนจำนวนมากค้นหาจริง ๆ จะสร้าง โหลดจริงที่สมจริง ของเสิร์ชเอนจินได้อย่างไร? การยิงคำค้นแบบสุ่มไม่ใช่เรื่องสมจริง
      บางคนใช้การเชื่อมต่อที่ช้า และคำค้นเฉพาะอย่างแผ่นดินไหวก็อาจพุ่งขึ้นเฉพาะในบางภูมิภาคเท่านั้น
      ถ้าคำค้นสุ่มเกินไป cache จะไม่มีผลลัพธ์ ทำให้ดูแย่กว่าความจริง แต่ถ้าไม่สุ่มพอ ก็จะดูดีกว่าความจริง
    • Lucene ซึ่งเป็นฐานที่ Elasticsearch และ Solr ใช้คือ inverted index และ GIN ในบทความก็ใช้วิธีเดียวกัน
      ดังนั้นข้อดีของ ES และเครื่องมือทำนองนั้นจึงอยู่ที่การ scale แนวนอนข้ามหลาย node หรือฟีเจอร์เสริมที่วางทับบนดัชนีหลัก
    • ใช้ full-text search ของ Postgres อยู่ และทำงานได้ดี แต่ต้องรู้ว่าจะจัดอันดับแถวในคิวรีอย่างไร
      ถ้าใช้แค่ ts_rank ก็สมบูรณ์แบบ แต่โดยปกติจะอยากปรับอันดับด้วย ตัวชี้วัดความเกี่ยวข้อง อื่น ๆ หากแก้จุดที่ไม่สามารถใช้ตัวชี้วัดนั้นเป็นเกณฑ์อันดับหลักได้ ผลลัพธ์ก็จะเร็วพอ ๆ กับการ query ตาราง DB ปกติที่มีดัชนี
  • ตอนวัยรุ่นเคยลองสร้างทั้งเสิร์ชเอนจินและฐานข้อมูลขึ้นมาเองตั้งแต่ศูนย์ ทั้งที่ยังไม่ค่อยรู้อะไรเกี่ยวกับทั้งสองอย่าง อยากดูว่าจะไปได้ไกลแค่ไหน และจะคืนผลการค้นหาที่เกี่ยวข้องได้เร็วระดับไหน
    การสร้างฐานข้อมูลและเสิร์ชเอนจินแบบพื้นฐานอย่างรวดเร็วถือว่าค่อนข้างง่าย แม้แต่สำหรับโปรแกรมเมอร์สมัครเล่น ถ้าเข้าใจอัลกอริทึมวิทยาการคอมพิวเตอร์พื้นฐาน และวิธีใช้ระบบปฏิบัติการ/ฮาร์ดแวร์ ก็ทำได้ภายในหนึ่งถึงสองเดือน
    แม้ใช้ภาษาระดับสูง ความเร็วก็ไม่ได้แย่ ประมาณ 250,000 QPS บนแล็ปท็อปปี 2003 ถ้าทำชาร์ดดิ้ง ความสามารถในการขยายก็ไม่ใช่ปัญหาใหญ่ สิ่งที่ซับซ้อนกว่าการเก็บและดึงข้อมูลคือการทำดัชนี การล็อก และความสอดคล้องของข้อมูล
    ปัญหาใหญ่จริง ๆ คือ ความเป็นอัตวิสัย ของการค้นหา พอเป็นเรื่องว่าจะหาสิ่งที่ต้องการจริง ๆ ได้อย่างไร, จะหาอย่างไรเมื่อแม้แต่ตัวเองก็ไม่รู้ว่ากำลังหาอะไร, จะป้องกันคนที่พยายามใช้ระบบในทางที่ผิดอย่างไร, และจะจัดการคิวรีกับชุดข้อมูลที่ซับซ้อนอย่างไร ระดับความยากก็เพิ่มขึ้นไปอีกหลายหลัก

    • 250,000 RPS ดูเป็นตัวเลขที่ค่อนข้างสูง เพราะแม้แต่ SQLite ทุกวันนี้ก็ยังทำระดับนั้นไม่ได้ ดัชนีกลับด้านน่าจะยิ่งแพงกว่า และ RocksDB ก็อยู่ราว 130,000 RPS ซึ่งตัวเลขนั้นก็น่าจะมาจากฮาร์ดแวร์ที่แรงกว่าแล็ปท็อปของผม หรือไม่ก็สภาพแวดล้อมของผมตั้งค่าผิด
      เลยสงสัยว่าจริง ๆ แล้วใช้ ฐานข้อมูลอเนกประสงค์ หรือเปล่า และตัวเลขนั้นถูกต้องไหม อยากรู้ว่าใช้เทคนิคอะไรถึงเอาชนะเอนจินพวกนี้ได้
    • ตอนสร้างเสิร์ชเอนจิน ปัญหาใหญ่ที่สุดไม่ใช่ QPS แต่เป็น ขนาดของชุดข้อมูล ที่ต้องทำดัชนี ถ้าโครงสร้างสำหรับค้นหาใส่ลงในหน่วยความจำของเครื่องเดียวได้ latency ก็แทบเป็นศูนย์ และให้ QPS ได้แทบไม่จำกัด
      ถ้าใหญ่กว่านั้นก็ต้องใช้วิธีแก้ที่สร้างสรรค์ และตั้งแต่นั้นเป็นต้นไปก็จะมี trade-off หลายอย่างตามมา
    • สงสัยว่าเปิดเป็นโอเพนซอร์สไว้ที่ไหนหรือเปล่า
  • หนึ่งในปัญหาหลายอย่างของการค้นหาภายใน Postgres คือการค้นหาเป็นงานที่ ใช้ CPU หนัก และแนวโน้มก็กำลังไปทางการใช้ GPU มากขึ้นด้วย ตามอุดมคติแล้ว เราอยากเหลือ CPU ของฐานข้อมูลไว้สำหรับการอัปเดตแบบทรานแซกชันของโมเดลข้อมูลหลัก
    เคยเห็นคลัสเตอร์ ES และ Solr ตอน reindex ใช้ CPU 100% บนโหนดมากกว่า 10 ตัว หรือแม้เวลาปกติก็ใช้ 30–50% บนโหนดมากกว่า 10 ตัวอยู่บ่อย ๆ ฐานข้อมูลที่สอดคล้องกันอาจเป็นเช่น instance AWS L/XL ที่มีข้อมูล 50–100GB และใช้ CPU ราว 30%
    ถ้าย้าย CPU สำหรับค้นหาทั้งหมดไปไว้ที่ DB หลัก ตอนนี้ก็ต้องชาร์ด DB แล้ว อย่างไรก็ตาม สำหรับโปรเจกต์ส่วนตัว ส่วนขยายของ PG สำหรับการค้นหา, recursive join, เวกเตอร์ ฯลฯ นั้นสนุกและเรียบง่ายดี

    • ถ้าค้นหาบน replica แบบอ่านอย่างเดียว น่าจะช่วยแก้ได้ไหม?
  • เคยทำแบบนี้จริงมาก่อน และทำให้ค่อนข้างเร็วได้
    https://austingwalters.com/fast-full-text-search-in-postgres...
    เว็บไซต์ปัจจุบันคือ https://askhn.ai

    • อนึ่ง คำโปรยของ askhn.ai ที่ว่า "Discover, Manage, Query...." มี kerning[1] ที่แย่มาก
      [1]: https://en.wikipedia.org/wiki/Kerning
  • ถ้าผสมเทคนิคแบบนี้กับ pgvector ก็จะสามารถหาเนื้อหาที่เกี่ยวข้องด้วย embedding ได้ด้วย รู้สึกเหมือนเวทมนตร์ทีเดียว

    • ช่วยอธิบายละเอียดขึ้นอีกหน่อย หรือแนะนำแหล่งข้อมูลให้อ่านได้ไหม?
    • น่าสนใจที่ผู้คนเลือกทางลัดแบบ hacky แบบนี้ ทั้งที่สามารถใช้ของอย่าง Vespa ได้ ผมมองว่า Vespa ดีกว่าหลายหลักในแง่ประสิทธิภาพ ความเกี่ยวข้องของผลลัพธ์ ความสามารถในการขยาย และประสบการณ์นักพัฒนา
  • อนึ่ง ไม่ใช่ "Dark" Vader แต่เป็น Darth Vader ผมเองตอนเด็กก็เคยคิดว่าเป็น "Dark" เหมือนกัน

    • ในบางคำแปลอย่างภาษาฝรั่งเศส ใช้ Dark Vador จริง ๆ ทำให้ “ความผิดพลาด” นั้นพบได้บ่อย
    • ตรงส่วนนั้นในบทความขัดใจมากจนโฟกัสกับเนื้อหาอื่นไม่ได้ รู้จัก quote ของ Yoda แต่ยังเรียก Dark Vader ได้ยังไง?
  • เป็นบทความที่ดีและชัดเจนมาก SQLite ก็มี ความสามารถทำดัชนีขั้นสูงและการตัดรากคำ ให้เป็นปลั๊กอินมาตรฐานเช่นกัน
    ถ้าเป็นภาษาอังกฤษ SQLite ก็ทำงานได้ดี

    • เจอข้อมูลที่เคยทดลองด้วย SQLite แล้ว
      https://github.com/daitangio/knowledge
      ลองใช้ดูน่าจะดี ค่อนข้างทรงพลัง
    • สงสัยว่าจะทำด้วย FDW ได้ไหม ถ้า replicate เฉพาะข้อมูลที่ต้องค้นหา ก็คงไม่มี worker สำหรับเขียนมากนัก จึงอาจทำงานได้ดีสำหรับข้อมูลขนาดกลาง
  • เป็นบทความที่ดี แต่ส่วนที่บอกว่า PostgreSQL ไม่รองรับ fuzzy search นั้นผิดบางส่วน ส่วนขยาย pg_trgm และดัชนี GIN trigram รองรับ use case การค้นหาแบบ fuzzy อย่างตัวอย่างในบทความนี้
    https://www.postgresonline.com/article_pfriendly/169.html
    คิวรีอาจเร็วขึ้นมาก แต่ก็มีต้นทุนคือใช้หน่วยความจำมากขึ้นและใช้เวลาทำงานมากขึ้นตอนอัปเดต