
ในยุคที่ข้อมูลท่วมท้นมหาศาล การเข้าถึงข้อมูลที่ต้องการได้อย่างรวดเร็วและแม่นยำไม่ใช่เพียงแค่ความสะดวกสบาย แต่เป็นหัวใจสำคัญที่ขับเคลื่อนความสำเร็จของธุรกิจและสร้างประสบการณ์ผู้ใช้ที่เหนือกว่าครับ ไม่ว่าจะเป็นเว็บไซต์ E-commerce ที่ลูกค้าต้องการหาสินค้าที่ใช่ในพริบตา ระบบค้นหาเอกสารองค์กรที่พนักงานต้องเข้าถึงข้อมูลสำคัญอย่างทันท่วงที หรือแม้แต่แพลตฟอร์มโซเชียลมีเดียที่ต้องนำเสนอเนื้อหาที่ตรงใจผู้ใช้ การมีระบบค้นหาที่มีประสิทธิภาพสูงและชาญฉลาดจึงเป็นสิ่งจำเป็นอย่างยิ่งในปัจจุบัน
บ่อยครั้งที่ระบบค้นหาแบบเดิมๆ ที่พึ่งพาฐานข้อมูลเชิงสัมพันธ์ (Relational Database Management Systems – RDBMS) เพียงอย่างเดียว มักจะเผชิญกับข้อจำกัดมากมาย ไม่ว่าจะเป็นเรื่องของความเร็วในการประมวลผลเมื่อข้อมูลมีขนาดใหญ่ขึ้น ความสามารถในการจัดการการค้นหาแบบ Full-text ที่ซับซ้อน เช่น การค้นหาคำพ้อง การแก้ไขคำผิดอัตโนมัติ การจัดลำดับความเกี่ยวข้องของผลลัพธ์ หรือแม้กระทั่งการรองรับภาษาต่างๆ ที่มีลักษณะเฉพาะตัวอย่างภาษาไทย ทำให้ระบบค้นหาธรรมดาไม่สามารถตอบโจทย์ความต้องการของผู้ใช้ในยุคดิจิทัลได้อย่างเต็มที่ครับ
บทความนี้จะพาคุณเจาะลึกถึง Elasticsearch สุดยอด Search Engine ยอดนิยมที่ถูกออกแบบมาเพื่อจัดการกับความท้าทายเหล่านี้โดยเฉพาะ เราจะมาดูกันว่า Elasticsearch มีหลักการทำงานอย่างไร ทำไมถึงเป็นตัวเลือกที่ดีที่สุดสำหรับการสร้างระบบ Full-text Search ที่รวดเร็ว แม่นยำ และชาญฉลาด สามารถตอบโจทย์การค้นหาที่หลากหลายและซับซ้อนได้อย่างไร พร้อมทั้งวิธีการนำไปประยุกต์ใช้ตั้งแต่พื้นฐานไปจนถึงระดับแอดวานซ์ รวมถึงการปรับแต่งเพื่อให้รองรับการค้นหาภาษาไทยได้อย่างมีประสิทธิภาพสูงสุดครับ เตรียมตัวให้พร้อมสำหรับการสร้างระบบค้นหาที่จะยกระดับประสบการณ์ผู้ใช้ของคุณไปอีกขั้นกันเลยครับ!
สารบัญ
- ทำความเข้าใจ Full-text Search คืออะไร และทำไมถึงสำคัญ?
- Elasticsearch คืออะไร? สถาปัตยกรรมและหลักการทำงานเบื้องต้น
- เริ่มต้นใช้งาน Elasticsearch: การติดตั้งและตั้งค่าเบื้องต้น
- การเตรียมข้อมูลสำหรับ Full-text Search: Mapping และ Analyzer ที่ควรรู้
- การนำเข้าข้อมูล (Indexing) สู่ Elasticsearch
- การค้นหาข้อมูลแบบ Full-text Search ขั้นพื้นฐานและขั้นสูง
- ฟีเจอร์อัจฉริยะเพื่อประสบการณ์การค้นหาที่เหนือกว่า
- การทำ Thai Full-text Search ด้วย Elasticsearch
- การปรับแต่งประสิทธิภาพ (Performance Tuning) และการขยายระบบ (Scaling)
- เปรียบเทียบ: Elasticsearch vs. RDBMS Full-text Search
- ข้อควรระวังและแนวทางปฏิบัติที่ดี (Best Practices)
- คำถามที่พบบ่อย (FAQ)
- สรุปและ Call-to-Action
ทำความเข้าใจ Full-text Search คืออะไร และทำไมถึงสำคัญ?
Full-text Search หรือการค้นหาแบบข้อความเต็ม คือวิธีการค้นหาข้อมูลในชุดเอกสารหรือฐานข้อมูลที่ไม่ได้จำกัดแค่การจับคู่คำตรงตัว (exact match) แต่เป็นการค้นหาคำหรือวลีที่ปรากฏอยู่ในเนื้อหาทั้งหมดของเอกสารนั้นๆ รวมถึงการพิจารณาความเกี่ยวข้อง (relevancy) ของผลลัพธ์ด้วยครับ ลองนึกภาพเวลาที่คุณค้นหาบางอย่างบน Google คุณไม่ได้แค่ใส่คำที่ตรงเป๊ะกับชื่อหัวข้อใดหัวข้อหนึ่ง แต่คุณใส่คำที่อธิบายสิ่งที่คุณกำลังมองหา และ Google ก็จะพยายามหาเอกสารหรือหน้าเว็บที่ “เกี่ยวข้อง” กับคำค้นของคุณมากที่สุด นี่แหละครับคือแก่นแท้ของ Full-text Search
ทำไม Full-text Search จึงสำคัญในยุคปัจจุบัน?
ในโลกที่ข้อมูลเกิดขึ้นตลอดเวลาและมีปริมาณมหาศาล การค้นหาที่มีประสิทธิภาพจึงเป็นสิ่งจำเป็นอย่างยิ่งครับ
- ประสบการณ์ผู้ใช้ที่ดีขึ้น: ผู้ใช้คาดหวังว่าจะสามารถค้นหาสิ่งที่ต้องการเจอได้ง่ายๆ และรวดเร็ว การค้นหาที่ชาญฉลาดช่วยลดความผิดหวังและเพิ่มความพึงพอใจครับ
- ค้นพบข้อมูลที่ซ่อนอยู่: การค้นหาแบบ Full-text ไม่ได้จำกัดแค่ชื่อเรื่องหรือแท็ก แต่สามารถเจาะลึกเข้าไปในเนื้อหาของเอกสาร ทำให้ผู้ใช้ค้นพบข้อมูลสำคัญที่อาจจะซ่อนอยู่ในรายละเอียดปลีกย่อยได้
- รองรับความยืดหยุ่นของภาษา: สามารถจัดการกับความแตกต่างทางภาษาได้ เช่น คำพ้องความหมาย การลดรูปคำ (stemming) หรือการจัดการกับคำผิด (typos) ซึ่งการค้นหาแบบตรงตัวทำได้ยาก
- เพิ่มประสิทธิภาพการทำงาน: ในองค์กร การเข้าถึงข้อมูลที่ถูกต้องได้อย่างรวดเร็วช่วยให้พนักงานตัดสินใจได้ดีขึ้นและทำงานได้มีประสิทธิภาพมากขึ้น
- ความได้เปรียบทางการแข่งขัน: เว็บไซต์ E-commerce ที่มีระบบค้นหาสินค้าที่ดี ย่อมมีโอกาสในการขายมากกว่าคู่แข่งที่ลูกค้าหาสินค้าไม่เจอครับ
ข้อจำกัดของ RDBMS ในการทำ Full-text Search
ฐานข้อมูลเชิงสัมพันธ์อย่าง MySQL, PostgreSQL มีฟังก์ชัน Full-text Search ในตัวอยู่บ้าง เช่น `MATCH AGAINST` ใน MySQL หรือ `tsvector`/`tsquery` ใน PostgreSQL แต่ก็ยังมีข้อจำกัดเมื่อเทียบกับ Search Engine โดยเฉพาะครับ
- ประสิทธิภาพ: เมื่อข้อมูลมีขนาดใหญ่ขึ้น การค้นหา Full-text ใน RDBMS มักจะช้าลงอย่างเห็นได้ชัด เนื่องจากไม่ได้ถูกออกแบบมาเพื่อการค้นหาข้อความโดยเฉพาะ
- ความสามารถ: ฟังก์ชันการค้นหาใน RDBMS มักจะมีความสามารถจำกัดในการจัดการกับความซับซ้อน เช่น การให้คะแนนความเกี่ยวข้องที่ละเอียดอ่อน การจัดการคำพ้อง การแก้ไขคำผิด หรือการรองรับภาษาที่หลากหลาย
- การปรับขยาย (Scalability): การปรับขยาย RDBMS เพื่อรองรับการค้นหาที่มีปริมาณสูงทำได้ยากและซับซ้อนกว่า Search Engine ที่ออกแบบมาเพื่อการนี้โดยเฉพาะ
- การวิเคราะห์ข้อมูล (Analytics): RDBMS ไม่ได้ถูกออกแบบมาสำหรับการทำ Aggregations หรือ Faceted Search ที่ซับซ้อนเท่ากับ Search Engine
นี่จึงเป็นที่มาว่าทำไมเครื่องมือเฉพาะทางอย่าง Elasticsearch จึงก้าวเข้ามามีบทบาทสำคัญในการสร้างระบบ Full-text Search ที่ตอบโจทย์การใช้งานในปัจจุบันและอนาคตครับ
Elasticsearch คืออะไร? สถาปัตยกรรมและหลักการทำงานเบื้องต้น
Elasticsearch คือ Search Engine แบบกระจายศูนย์ (distributed), RESTful ที่มีประสิทธิภาพสูงในการจัดเก็บ, ค้นหา, และวิเคราะห์ข้อมูลในปริมาณมหาศาลแบบเรียลไทม์ครับ มันถูกสร้างขึ้นบนไลบรารี Apache Lucene ซึ่งเป็นหัวใจสำคัญที่ทำให้ Elasticsearch มีความสามารถในการค้นหา Full-text ที่ยอดเยี่ยม และเป็นส่วนหนึ่งของ Elastic Stack (หรือรู้จักกันในชื่อ ELK Stack) ซึ่งประกอบด้วย Elasticsearch, Logstash และ Kibana ครับ
แนวคิดหลักของ Elasticsearch
- Document: หน่วยข้อมูลพื้นฐานที่สุดใน Elasticsearch คือ Document ครับ ซึ่งเป็นข้อมูลในรูปแบบ JSON โดยแต่ละ Document จะแทนที่ “สิ่งหนึ่ง” เช่น ข้อมูลสินค้า, บทความ, บันทึก Log, หรือข้อมูลผู้ใช้ Document ไม่มี schema ที่ตายตัวเหมือนตารางในฐานข้อมูล แต่จะมีการ Mapping เพื่อกำหนดว่าแต่ละฟิลด์ควรจะถูกจัดเก็บและประมวลผลอย่างไร
- Index: Index เปรียบเสมือนฐานข้อมูล (database) ใน RDBMS ครับ แต่มันคือคอลเลกชันของ Document ที่มีลักษณะคล้ายกัน มักจะใช้สำหรับจัดเก็บข้อมูลประเภทเดียวกัน เช่น Index สำหรับสินค้า, Index สำหรับผู้ใช้งาน หรือ Index สำหรับ Log ของแต่ละวัน
- Type (เลิกใช้ในเวอร์ชัน 7.0+): ในเวอร์ชันเก่าๆ ของ Elasticsearch จะมีแนวคิดของ Type ซึ่งเปรียบเสมือนตาราง (table) ภายใน Index แต่ในเวอร์ชัน 7.0 เป็นต้นมา ได้มีการเลิกใช้ Type แล้ว โดยแนะนำให้ใช้แต่ละ Index แทนแต่ละ Type ครับ เพื่อให้สถาปัตยกรรมเรียบง่ายและลดความสับสน
- Shard: เนื่องจาก Elasticsearch เป็นระบบแบบกระจายศูนย์ เมื่อ Index มีขนาดใหญ่ มันจะถูกแบ่งออกเป็นส่วนย่อยๆ ที่เรียกว่า Shards ครับ ซึ่งแต่ละ Shard ก็คือ Lucene Index ตัวหนึ่ง Shard ช่วยให้ข้อมูลสามารถกระจายไปเก็บยัง Node ต่างๆ ใน Cluster ได้ ทำให้สามารถประมวลผลการค้นหาแบบขนาน (parallel processing) และรองรับข้อมูลขนาดใหญ่ได้
- Replica: เพื่อความทนทานต่อความผิดพลาด (fault tolerance) และเพิ่มประสิทธิภาพในการค้นหา Elasticsearch จะสร้างสำเนาของ Shard แต่ละตัวที่เรียกว่า Replica ครับ ถ้า Primary Shard ตัวใดตัวหนึ่งเกิดล่มไป Replica ก็จะสามารถเข้ามาทำงานแทนได้ทันที นอกจากนี้ยังสามารถใช้ Replica เพื่อกระจายภาระงานการค้นหาได้ด้วย
- Node: Node คือเซิร์ฟเวอร์หนึ่งเครื่องที่รัน Elasticsearch instance ครับ แต่ละ Node จะมีหน้าที่เก็บ Shard และเป็นส่วนหนึ่งของ Cluster
- Cluster: Cluster คือกลุ่มของ Node ตั้งแต่หนึ่งตัวขึ้นไปที่ทำงานร่วมกันเพื่อจัดเก็บข้อมูลทั้งหมดในระบบครับ Cluster เดียวสามารถมีได้หลาย Index และแต่ละ Index ก็สามารถแบ่งเป็น Shards ที่กระจายอยู่ตาม Node ต่างๆ ได้
สถาปัตยกรรมโดยรวม
+---------------------+
| Elasticsearch |
| Cluster |
+---------------------+
| | |
| +------+------+ |
| | Node 1 | |
| | | |
| | Index A | |
| | Shard 1 (P)| |
| | Shard 2 (R)| |
| | | |
| | Index B | |
| | Shard 1 (P)| |
| +-------------+ |
| | |
| +------+------+ |
| | Node 2 | |
| | | |
| | Index A | |
| | Shard 1 (R)| |
| | Shard 2 (P)| |
| | | |
| | Index B | |
| | Shard 1 (R)| |
| +-------------+ |
| | |
+---------------------+
เมื่อข้อมูลถูกนำเข้า (indexed) มันจะถูกแบ่งออกเป็น Shards และกระจายไปเก็บใน Node ต่างๆ พร้อมกับสร้าง Replica Shards เพื่อสำรองข้อมูลและการค้นหา เมื่อมีการค้นหาเกิดขึ้น Elasticsearch จะกระจายคำสั่งค้นหาไปยัง Shards ที่เกี่ยวข้องทั้งหมดใน Cluster จากนั้นรวบรวมผลลัพธ์จากแต่ละ Shard มาประมวลผลและส่งกลับไปยังผู้ใช้ครับ กระบวนการนี้เกิดขึ้นอย่างรวดเร็ว ทำให้ Elasticsearch สามารถจัดการกับการค้นหาข้อมูลปริมาณมหาศาลได้อย่างมีประสิทธิภาพครับ
เริ่มต้นใช้งาน Elasticsearch: การติดตั้งและตั้งค่าเบื้องต้น
การติดตั้ง Elasticsearch สามารถทำได้หลายวิธีครับ ขึ้นอยู่กับสภาพแวดล้อมและความต้องการของคุณ สำหรับบทความนี้ เราจะเน้นที่แนวคิดและตัวอย่างโค้ดเป็นหลัก แต่จะกล่าวถึงวิธีการติดตั้งยอดนิยมพอสังเขปครับ
วิธีการติดตั้งยอดนิยม
- Docker: เป็นวิธีที่สะดวกและรวดเร็วที่สุดสำหรับการทดลองใช้งานหรือพัฒนา สามารถรัน Elasticsearch และ Kibana ได้ภายในไม่กี่คำสั่ง
- Standalone Installation: ดาวน์โหลดไฟล์ไบนารีสำหรับระบบปฏิบัติการของคุณ (Linux, Windows, macOS) และติดตั้งด้วยตัวเอง เหมาะสำหรับ Production Environment ที่ต้องการควบคุมการตั้งค่าอย่างละเอียด
- Cloud Services: ใช้บริการ Elasticsearch บน Cloud เช่น Elastic Cloud (บริการอย่างเป็นทางการจาก Elastic), AWS Elasticsearch Service (ปัจจุบันคือ Amazon OpenSearch Service), Google Cloud หรือ Azure ซึ่งเป็นวิธีที่ง่ายที่สุดในการเริ่มต้นใช้งานใน Production โดยไม่ต้องกังวลเรื่องการจัดการ Infrastructure ครับ
ตัวอย่างการติดตั้งด้วย Docker (สำหรับทดลอง)
ถ้าคุณมี Docker ติดตั้งอยู่แล้ว คุณสามารถรัน Elasticsearch และ Kibana ได้ด้วยคำสั่งเหล่านี้ครับ
# สร้าง network สำหรับ container
docker network create elastic-net
# รัน Elasticsearch container
docker run -d \
--name elasticsearch \
--net elastic-net \
-p 9200:9200 \
-p 9300:9300 \
-e "discovery.type=single-node" \
-e "xpack.security.enabled=false" \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
elasticsearch:7.17.6
# รัน Kibana container (สำหรับ UI และจัดการข้อมูล)
docker run -d \
--name kibana \
--net elastic-net \
-p 5601:5601 \
-e "ELASTICSEARCH_HOSTS=http://elasticsearch:9200" \
kibana:7.17.6
หลังจากรันคำสั่งข้างต้น คุณสามารถเข้าถึง Elasticsearch ได้ที่ `http://localhost:9200` และ Kibana ได้ที่ `http://localhost:5601` ครับ
การตั้งค่าเบื้องต้นที่สำคัญ
ไฟล์การตั้งค่าหลักของ Elasticsearch คือ `elasticsearch.yml` (อยู่ในโฟลเดอร์ `config` ของการติดตั้ง) สำหรับ Production Environment มีการตั้งค่าบางอย่างที่คุณควรพิจารณาครับ
- `cluster.name`: ชื่อของ Cluster ควรตั้งให้ไม่ซ้ำกันในเครือข่าย เพื่อป้องกันการเข้าร่วม Cluster โดยไม่ได้ตั้งใจ
- `node.name`: ชื่อของ Node เพื่อให้ระบุได้ง่าย
- `network.host`: กำหนด IP address ที่ Elasticsearch จะผูกเข้าด้วย โดยค่าเริ่มต้นคือ `127.0.0.1` (localhost) ถ้าต้องการให้ Node อื่นๆ ในเครือข่ายเข้าถึงได้ ควรตั้งค่าเป็น IP ของเครื่องนั้นๆ หรือ `0.0.0.0`
- `http.port`: พอร์ตสำหรับ HTTP REST API (ค่าเริ่มต้นคือ 9200)
- `discovery.seed_hosts`: รายชื่อ Node อื่นๆ ใน Cluster เพื่อให้ Node ใหม่สามารถเข้าร่วม Cluster ได้
- `cluster.initial_master_nodes`: รายชื่อ Node ที่มีสิทธิ์เป็น Master Node ใน Cluster เริ่มต้น
- `xpack.security.enabled`: การเปิดใช้งานความปลอดภัย (Authentication/Authorization) ควรเปิดใช้งานใน Production
- `ES_JAVA_OPTS`: การกำหนดขนาด Heap Memory สำหรับ JVM ของ Elasticsearch ผ่านตัวแปรสภาพแวดล้อม (environment variable) เช่น `-Xms2g -Xmx2g` (กำหนด min และ max ที่ 2GB) ควรตั้งค่าประมาณครึ่งหนึ่งของ RAM ทั้งหมดของเครื่อง แต่ไม่ควรเกิน 30.5GB
ตัวอย่าง `elasticsearch.yml` (สำหรับ Production แบบ Multi-Node)
cluster.name: my-production-cluster
node.name: node-1
network.host: 192.168.1.10
http.port: 9200
# สำหรับ Node แรกใน Cluster
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]
# สำหรับ Node อื่นๆ เพื่อให้ค้นพบ Master Node
discovery.seed_hosts: ["192.168.1.10", "192.168.1.11", "192.168.1.12"]
# Memory Allocation (แนะนำให้ตั้งค่าผ่าน ES_JAVA_OPTS)
# vm.max_map_count: 262144 (ต้องตั้งค่าในระบบปฏิบัติการ Linux)
# Security (แนะนำให้เปิดใช้งานใน Production)
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
xpack.security.http.ssl.enabled: true
หลังจากตั้งค่าแล้ว คุณสามารถเริ่มต้น Elasticsearch Service และตรวจสอบสถานะของ Cluster ได้ผ่าน API ครับ เช่น `GET /_cat/health?v` หรือ `GET /_cat/nodes?v` เพื่อดูสถานะสุขภาพของ Cluster และรายการ Node ที่กำลังทำงานอยู่ครับ การเริ่มต้นด้วยการตั้งค่าที่ถูกต้องเป็นสิ่งสำคัญสำหรับประสิทธิภาพและความเสถียรของระบบในระยะยาวครับ
การเตรียมข้อมูลสำหรับ Full-text Search: Mapping และ Analyzer ที่ควรรู้
ก่อนที่เราจะเริ่มนำข้อมูลเข้าสู่ Elasticsearch เพื่อทำการค้นหาแบบ Full-text เราจำเป็นต้องเข้าใจแนวคิดสำคัญสองอย่างคือ Mapping และ Analyzer ครับ สิ่งเหล่านี้คือตัวกำหนดว่าข้อมูลของเราจะถูกจัดเก็บ วิเคราะห์ และค้นหาได้อย่างไร ซึ่งเป็นหัวใจสำคัญที่ทำให้ Full-text Search ของเราฉลาดและแม่นยำ
Mapping: การกำหนดโครงสร้างและประเภทข้อมูล
Mapping คือกระบวนการกำหนดโครงสร้าง (schema) และประเภทข้อมูล (data types) ของแต่ละฟิลด์ใน Document ของเราครับ แม้ว่า Elasticsearch จะมีความสามารถในการสร้าง Dynamic Mapping (เดาประเภทข้อมูลจากข้อมูลที่นำเข้า) ได้ แต่การกำหนด Explicit Mapping ด้วยตัวเองนั้นเป็นสิ่งจำเป็นอย่างยิ่งสำหรับการทำ Full-text Search ที่มีประสิทธิภาพและควบคุมได้
ทำไมต้องกำหนด Explicit Mapping?
- ควบคุมการวิเคราะห์ข้อมูล: เราสามารถกำหนด Analyzer ที่แตกต่างกันสำหรับแต่ละฟิลด์ได้ เช่น ฟิลด์ชื่อสินค้าอาจจะใช้ Analyzer สำหรับภาษาไทย แต่ฟิลด์รหัสสินค้าใช้ Analyzer แบบ `keyword` (ไม่แยกคำ)
- เพิ่มประสิทธิภาพการค้นหา: การกำหนดประเภทข้อมูลที่ถูกต้องจะช่วยให้ Elasticsearch จัดเก็บและค้นหาข้อมูลได้รวดเร็วขึ้น
- รองรับฟีเจอร์เฉพาะ: ฟีเจอร์บางอย่างเช่น Autocomplete หรือ Aggregations ต้องการ Mapping ที่เฉพาะเจาะจง
- ป้องกันข้อผิดพลาด: Dynamic Mapping อาจจะเดาประเภทข้อมูลผิดพลาดได้ ซึ่งจะส่งผลเสียต่อการค้นหาในระยะยาว
ประเภทข้อมูลที่สำคัญสำหรับ Full-text Search
- `text`: ใช้สำหรับฟิลด์ที่ต้องการทำ Full-text Search (จะถูกวิเคราะห์ด้วย Analyzer)
- `keyword`: ใช้สำหรับฟิลด์ที่ไม่ต้องการทำ Full-text Search แต่ต้องการค้นหาแบบตรงตัว (exact match) เช่น ID, ชื่อหมวดหมู่, แท็ก (ถ้าต้องการเก็บเป็นคำเดียว) ไม่ถูกวิเคราะห์
- `long`, `integer`, `short`, `byte`, `double`, `float`: สำหรับข้อมูลตัวเลข
- `date`: สำหรับข้อมูลวันที่และเวลา
- `boolean`: สำหรับค่า True/False
ตัวอย่างการสร้าง Index พร้อม Mapping
สมมติว่าเราต้องการสร้าง Index สำหรับบทความ (articles) ที่มีฟิลด์ `title`, `content`, `tags`, `author`, `publish_date` ครับ
PUT /articles
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "thai" // กำหนดให้ใช้ Analyzer สำหรับภาษาไทย
},
"content": {
"type": "text",
"analyzer": "thai"
},
"tags": {
"type": "keyword" // ไม่ต้องวิเคราะห์ แต่ใช้ค้นหาแบบตรงตัว
},
"author": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256 // เก็บเป็น keyword ด้วย แต่ถ้าเกิน 256 ตัวอักษรจะไม่เก็บ
}
}
},
"publish_date": {
"type": "date"
}
}
}
}
จากตัวอย่างจะเห็นว่าฟิลด์ `title` และ `content` ถูกกำหนดให้เป็น `text` และใช้ `analyzer` ชื่อ `thai` ซึ่งเราจะสร้างขึ้นมาเองในภายหลัง ส่วน `tags` ถูกกำหนดให้เป็น `keyword` และฟิลด์ `author` มีการกำหนด multi-fields คือมีทั้ง `text` สำหรับ Full-text Search และ `keyword` สำหรับการค้นหาแบบตรงตัว หรือ Aggregations ครับ
Analyzer: หัวใจสำคัญของการประมวลผลข้อความ
Analyzer คือเครื่องมือที่ทำหน้าที่ประมวลผลข้อความ (text analysis) ก่อนที่จะถูกนำไปจัดเก็บใน Inverted Index ของ Elasticsearch ครับ เพื่อให้การค้นหามีประสิทธิภาพและฉลาดขึ้น กระบวนการนี้ประกอบด้วย 3 ขั้นตอนหลักๆ
- Character Filters: ทำหน้าที่จัดการกับข้อความก่อนที่ Tokenizer จะทำงาน เช่น การลบ HTML tags, การแปลงอักขระพิเศษ, การแมปคำบางคำ
- Tokenizer: ทำหน้าที่แยกข้อความออกเป็น “คำ” หรือ “โทเค็น” (tokens) เช่น Whitespace Tokenizer จะแยกคำด้วยช่องว่าง ส่วน Standard Tokenizer จะแยกคำตามกฎภาษาทั่วไป
- Token Filters: ทำหน้าที่ปรับแต่ง Token ที่ได้จาก Tokenizer เช่น การแปลงเป็นตัวพิมพ์เล็ก (lowercase), การลบ Stop Words (คำที่ไม่สำคัญ เช่น “ที่”, “ของ”), การลดรูปคำ (stemming), การเพิ่มคำพ้องความหมาย (synonyms)
Built-in Analyzers ที่มีประโยชน์
- `standard`: Analyzer เริ่มต้น ใช้สำหรับภาษาทั่วไป แยกคำตามไวยากรณ์ แปลงเป็นตัวพิมพ์เล็ก
- `simple`: แยกคำตามตัวอักษรที่ไม่ใช่ตัวอักษร (non-letters) และแปลงเป็นตัวพิมพ์เล็ก
- `whitespace`: แยกคำตามช่องว่างเท่านั้น
- `keyword`: ไม่แยกคำ ไม่วิเคราะห์ เก็บข้อความทั้งก้อนเป็น Token เดียว
- `english`: Analyzer เฉพาะสำหรับภาษาอังกฤษ มี Stop Words และ Stemmer สำหรับภาษาอังกฤษ
- `thai`: Analyzer พื้นฐานสำหรับภาษาไทย (จะกล่าวถึงรายละเอียดเพิ่มเติมในหัวข้อ Thai Full-text Search)
การสร้าง Custom Analyzer
บ่อยครั้งที่เราต้องการ Analyzer ที่ปรับแต่งให้เหมาะกับภาษาหรือข้อมูลของเราเองครับ ตัวอย่างเช่น เราต้องการ Analyzer สำหรับภาษาไทยที่สามารถจัดการกับ Stop Words และ Synonyms ได้
PUT /my_custom_index
{
"settings": {
"analysis": {
"analyzer": {
"my_thai_analyzer": {
"tokenizer": "thai",
"filter": [
"lowercase",
"thai_stopwords",
"thai_synonym_filter"
]
}
},
"filter": {
"thai_stopwords": {
"type": "stop",
"stopwords": ["ครับ", "ค่ะ", "และ", "หรือ", "ไม่", "ใน", "ที่", "ของ"]
},
"thai_synonym_filter": {
"type": "synonym",
"synonyms": [
"รถยนต์, รถเก๋ง, รถกระบะ",
"คอมพิวเตอร์, PC, โน้ตบุ๊ก"
]
}
}
}
},
"mappings": {
"properties": {
"description": {
"type": "text",
"analyzer": "my_thai_analyzer"
}
}
}
}
ในตัวอย่างนี้ เราได้สร้าง Index ชื่อ `my_custom_index` และกำหนด `my_thai_analyzer` เอง ซึ่งใช้ `thai_tokenizer` (สำหรับแยกคำภาษาไทย) และเพิ่ม Token Filters สองตัวคือ `thai_stopwords` และ `thai_synonym_filter` ที่เราได้กำหนดไว้ในส่วน `filter` ของ `settings` ครับ
การทดสอบ Analyzer
Elasticsearch มี API สำหรับทดสอบ Analyzer โดยเฉพาะ นั่นคือ `_analyze` API ซึ่งมีประโยชน์มากในการทำความเข้าใจว่า Analyzer ของเราทำงานอย่างไรครับ
GET /my_custom_index/_analyze
{
"analyzer": "my_thai_analyzer",
"text": "รถยนต์รุ่นใหม่ล่าสุด ประหยัดน้ำมันมากครับ"
}
ผลลัพธ์ที่ได้จะแสดง Token ที่ถูกสร้างขึ้นมา ทำให้เราเห็นว่าคำว่า “ครับ” ถูกลบออกไป และคำอื่นๆ ถูกแยกออกมาอย่างถูกต้องหรือไม่ การทำความเข้าใจและปรับแต่ง Mapping และ Analyzer เป็นกุญแจสำคัญในการสร้างระบบ Full-text Search ที่มีประสิทธิภาพและตอบสนองความต้องการของผู้ใช้ได้อย่างแท้จริงครับ
การนำเข้าข้อมูล (Indexing) สู่ Elasticsearch
หลังจากที่เราได้เตรียม Index และ Mapping รวมถึง Analyzer ที่เหมาะสมแล้ว ขั้นตอนต่อไปคือการนำเข้าข้อมูล (Indexing) เข้าสู่ Elasticsearch ครับ การทำ Index คือการจัดเก็บ Document ลงใน Index เพื่อให้สามารถค้นหาได้
การนำเข้า Document เดี่ยว
คุณสามารถนำเข้า Document ทีละรายการโดยใช้ HTTP `POST` หรือ `PUT` Request ไปยัง Endpoint ของ Index นั้นๆ ครับ
- `POST /<index>/_doc`: ให้ Elasticsearch สร้าง ID ของ Document ให้เอง
- `PUT /<index>/_doc/<id>`: กำหนด ID ของ Document ด้วยตัวเอง (ถ้ามีอยู่แล้วจะเป็นการอัปเดต)
ตัวอย่างการ Index บทความ (ใช้ ID อัตโนมัติ)
POST /articles/_doc
{
"title": "Elasticsearch คืออะไร? คู่มือสำหรับผู้เริ่มต้น",
"content": "บทความนี้จะพาคุณไปทำความรู้จักกับ Elasticsearch Search Engine ยอดนิยม พร้อมวิธีใช้งานเบื้องต้น",
"tags": ["Elasticsearch", "Search Engine", "เริ่มต้น"],
"author": "สมชาย ใจดี",
"publish_date": "2023-10-26T10:00:00Z"
}
Elasticsearch จะตอบกลับด้วย Document ID ที่ถูกสร้างขึ้นมา (เช่น `_id: “abcdefg123″`)
ตัวอย่างการ Index บทความ (กำหนด ID เอง)
PUT /articles/_doc/article_001
{
"title": "Elasticsearch Full-text Search สร้างระบบค้นหาอัจฉริยะ",
"content": "เจาะลึกการสร้างระบบค้นหาด้วย Elasticsearch พร้อมเทคนิคขั้นสูงสำหรับภาษาไทย",
"tags": ["Elasticsearch", "Full-text Search", "Thai Language"],
"author": "สมหญิง เก่งมาก",
"publish_date": "2023-10-25T14:30:00Z"
}
ในกรณีนี้ เรากำหนด ID ของ Document เป็น `article_001` เองครับ ถ้ามี Document ที่มี ID นี้อยู่แล้ว มันจะถูกอัปเดต
การนำเข้าข้อมูลจำนวนมาก (Bulk API)
สำหรับการนำเข้าข้อมูลจำนวนมาก การส่ง Document ทีละรายการนั้นไม่มีประสิทธิภาพครับ Elasticsearch มี Bulk API ที่ช่วยให้คุณสามารถนำเข้า, อัปเดต, หรือลบ Document หลายๆ รายการได้ใน Request เดียว ซึ่งจะช่วยลด Overhead ของ Network และเพิ่มความเร็วในการ Index ได้อย่างมาก
รูปแบบของ Bulk API คือการส่ง JSON object สองบรรทัดสลับกันครับ บรรทัดแรกคือ `action` (เช่น `index`, `create`, `update`, `delete`) พร้อมด้วย metadata (เช่น `_index`, `_id`) และบรรทัดที่สองคือ `source` Document (สำหรับ `index` หรือ `create`) หรือ `doc` (สำหรับ `update`) ครับ
ตัวอย่างการใช้ Bulk API
POST /_bulk
{"index": {"_index": "articles", "_id": "article_002"}}
{"title": "เทคนิคการปรับแต่งประสิทธิภาพ Elasticsearch", "content": "เรียนรู้วิธีการปรับแต่ง Shard, Replica และ JVM เพื่อเพิ่มความเร็ว", "tags": ["Performance", "Optimization"], "author": "สมศักดิ์ ขยัน", "publish_date": "2023-10-27T09:00:00Z"}
{"create": {"_index": "articles", "_id": "article_003"}}
{"title": "การใช้งาน Kibana สำหรับ Visualize ข้อมูล", "content": "เริ่มต้นสร้าง Dashboard และ Report ด้วย Kibana", "tags": ["Kibana", "Visualization"], "author": "สมศรี ฉลาด", "publish_date": "2023-10-28T11:00:00Z"}
{"delete": {"_index": "articles", "_id": "article_to_delete"}}
{"update": {"_index": "articles", "_id": "article_001"}}
{"doc": {"tags": ["Elasticsearch", "Full-text Search", "Thai Language", "Advanced"]}}
ข้อสังเกตสำหรับ Bulk API:
- แต่ละบรรทัดต้องเป็น JSON ที่สมบูรณ์ และไม่มีคอมมาคั่นระหว่าง JSON Object
- ต้องลงท้ายด้วย Newline character (
\n) หลังจาก JSON object สุดท้าย - การใช้งาน `create` จะเพิ่ม Document ใหม่เท่านั้น ถ้า ID ซ้ำจะเกิด error ส่วน `index` จะเพิ่มหรืออัปเดต ถ้า ID ซ้ำจะอัปเดต Document เดิม
- การ `update` ต้องระบุ `doc` เพื่อบอกว่าฟิลด์ใดบ้างที่ต้องการอัปเดต
การจัดการกับการอัปเดตข้อมูล
Elasticsearch ไม่ได้อัปเดต Document แบบ In-place ครับ เมื่อคุณอัปเดต Document Elasticsearch จะลบ Document เก่าออกและ Index Document ใหม่ทั้งหมดพร้อม ID เดิมแทน ดังนั้นการอัปเดตบ่อยๆ อาจจะส่งผลต่อประสิทธิภาพได้ ควรพิจารณาดีๆ ว่าฟิลด์ใดบ้างที่เปลี่ยนแปลงบ่อย และจะจัดการกับการอัปเดตอย่างไรให้มีประสิทธิภาพครับ
หลังจากนำเข้าข้อมูลแล้ว ข้อมูลเหล่านี้ก็จะพร้อมสำหรับการค้นหาแบบ Full-text Search ทันทีครับ!
การค้นหาข้อมูลแบบ Full-text Search ขั้นพื้นฐานและขั้นสูง
เมื่อข้อมูลอยู่ใน Elasticsearch แล้ว เราก็สามารถเริ่มทำการค้นหาได้เลยครับ Elasticsearch มี Query DSL (Domain Specific Language) ที่ทรงพลังและยืดหยุ่นมากสำหรับการค้นหาข้อมูล
การค้นหาขั้นพื้นฐาน
การค้นหาพื้นฐานมักจะใช้ Query ประเภท `match` หรือ `multi_match` ครับ
1. `match` Query: ค้นหาคำในฟิลด์เดียว
ใช้สำหรับค้นหาคำหรือวลีในฟิลด์ที่ต้องการโดยเฉพาะครับ
GET /articles/_search
{
"query": {
"match": {
"title": "Elasticsearch คู่มือเริ่มต้น"
}
}
}
Elasticsearch จะวิเคราะห์ข้อความ “Elasticsearch คู่มือเริ่มต้น” ด้วย Analyzer ที่กำหนดไว้สำหรับฟิลด์ `title` แล้วค้นหา Document ที่มีคำเหล่านั้นครับ
2. `multi_match` Query: ค้นหาคำในหลายฟิลด์
ถ้าต้องการค้นหาคำเดียวกันในหลายๆ ฟิลด์พร้อมกัน เช่น ค้นหาทั้งใน `title` และ `content` คุณสามารถใช้ `multi_match` ครับ
GET /articles/_search
{
"query": {
"multi_match": {
"query": "Kibana Dashboard",
"fields": ["title", "content"]
}
}
}
ในตัวอย่างนี้ Document ที่มีคำว่า “Kibana” หรือ “Dashboard” ในฟิลด์ `title` หรือ `content` จะถูกค้นพบครับ
3. `query_string` Query: ค้นหาแบบ Lucene Query Syntax
`query_string` เป็น Query ที่ทรงพลังมาก เพราะรองรับ Lucene Query Syntax ซึ่งทำให้สามารถสร้างเงื่อนไขการค้นหาที่ซับซ้อนได้ครับ เช่น AND, OR, NOT, Wildcard (`*`, `?`), Fuzzy Search (`~`), Range Search (`[]`, `{}`)
GET /articles/_search
{
"query": {
"query_string": {
"query": "(Elasticsearch AND Optimization) OR Kibana",
"fields": ["title^2", "content"]
// ^2 คือการ boosting ให้ฟิลด์ title มีน้ำหนักมากกว่า
}
}
}
Query นี้จะค้นหา Document ที่มี “Elasticsearch” และ “Optimization” หรือมี “Kibana” ครับ โดยให้คะแนนความเกี่ยวข้องกับคำที่อยู่ใน `title` เป็นสองเท่า
การค้นหาขั้นสูงและการจัดการความเกี่ยวข้อง (Relevancy)
การค้นหาขั้นสูงมักจะเกี่ยวข้องกับการรวม Query เข้าด้วยกัน การปรับแต่งคะแนนความเกี่ยวข้อง (relevancy score) และการใช้ฟีเจอร์อื่นๆ เพื่อให้ได้ผลลัพธ์ที่ตรงใจผู้ใช้มากที่สุดครับ
1. `bool` Query: การรวมเงื่อนไขการค้นหา
`bool` Query เป็น Query ที่สำคัญที่สุดสำหรับการรวมหลายๆ Query เข้าด้วยกัน โดยมีเงื่อนไขดังนี้
- `must`: Document ต้องมีเงื่อนไขเหล่านี้ทั้งหมด (คล้ายกับ AND)
- `should`: Document ควรมีเงื่อนไขเหล่านี้อย่างน้อยหนึ่งข้อ (คล้ายกับ OR) ใช้ในการเพิ่มคะแนนความเกี่ยวข้อง
- `must_not`: Document ต้องไม่มีเงื่อนไขเหล่านี้ (คล้ายกับ NOT)
- `filter`: Document ต้องมีเงื่อนไขเหล่านี้ แต่จะไม่ส่งผลต่อคะแนนความเกี่ยวข้อง (ใช้สำหรับกรองผลลัพธ์ที่ตรงเป๊ะ ซึ่งมักจะเร็วกว่า `must`)
GET /articles/_search
{
"query": {
"bool": {
"must": [
{ "match": { "content": "ประสิทธิภาพ" } }
],
"should": [
{ "match": { "title": "Elasticsearch" } },
{ "match": { "tags": "Optimization" } }
],
"filter": [
{ "range": { "publish_date": { "gte": "2023-01-01" } } }
],
"must_not": [
{ "match": { "author": "สมศรี ฉลาด" } }
]
}
}
}
Query นี้จะค้นหาบทความที่มีคำว่า “ประสิทธิภาพ” ใน `content` และเผยแพร่หลังวันที่ 1 มกราคม 2023 โดยไม่รวมบทความที่เขียนโดย “สมศรี ฉลาด” พร้อมทั้งเพิ่มคะแนนความเกี่ยวข้องถ้ามีคำว่า “Elasticsearch” ใน `title` หรือ “Optimization” ใน `tags` ครับ
2. Fuzzy Query: จัดการกับคำผิด (Typos)
`fuzzy` Query ช่วยให้คุณสามารถค้นหา Document ที่มีคำที่สะกดผิดเล็กน้อยได้ โดยใช้ Levenshtein distance (จำนวนแก้ไขที่น้อยที่สุดที่จำเป็นในการเปลี่ยนคำหนึ่งให้เป็นอีกคำหนึ่ง)
GET /articles/_search
{
"query": {
"match": {
"title": {
"query": "Elasticserch", // สะกดผิด
"fuzziness": "AUTO" // หรือระบุเป็นตัวเลข เช่น "2"
}
}
}
}
3. Phrase Matching: ค้นหาวลี
ถ้าต้องการค้นหาวลีที่คำเรียงติดกันเป๊ะๆ (หรือเกือบเป๊ะ) สามารถใช้ `match_phrase` ได้ครับ
GET /articles/_search
{
"query": {
"match_phrase": {
"content": "ระบบค้นหาอัจฉริยะ"
}
}
}
คุณสามารถใช้ `slop` parameter เพื่อระบุจำนวนคำที่สามารถคั่นระหว่างคำในวลีได้
GET /articles/_search
{
"query": {
"match_phrase": {
"content": {
"query": "Elasticsearch อัจฉริยะ",
"slop": 2 // อนุญาตให้มี 2 คำคั่นกลางได้
}
}
}
}
4. Highlighting: เน้นคำที่ค้นพบ
เพื่อช่วยให้ผู้ใช้เห็นว่าคำค้นหาปรากฏที่ส่วนใดของผลลัพธ์ คุณสามารถใช้ฟีเจอร์ `highlight` ได้ครับ
GET /articles/_search
{
"query": {
"match": {
"content": "ประสิทธิภาพ"
}
},
"highlight": {
"fields": {
"content": {}
},
"pre_tags": [""],
"post_tags": [""]
}
}
ผลลัพธ์จะส่ง snippet ของข้อความพร้อมกับคำที่ค้นพบถูกหุ้มด้วยแท็ก `` และ `` ครับ
5. Pagination และ Sorting
การจัดการผลลัพธ์เป็นหน้าๆ ทำได้โดยใช้ `from` (เริ่มต้นที่ Index ไหน) และ `size` (จำนวนผลลัพธ์ต่อหน้า) ครับ และสามารถเรียงลำดับผลลัพธ์ได้ด้วย `sort`
GET /articles/_search
{
"query": {
"match_all": {}
},
"from": 10,
"size": 10,
"sort": [
{ "publish_date": { "order": "desc" } },
{ "_score": { "order": "desc" } } // เรียงตามคะแนนความเกี่ยวข้องด้วย
]
}
การทำความเข้าใจ Query DSL และการประยุกต์ใช้ฟีเจอร์เหล่านี้ จะช่วยให้คุณสามารถสร้างระบบค้นหาที่ตอบโจทย์ความต้องการของผู้ใช้ได้อย่างแม่นยำและมีประสิทธิภาพสูงสุดครับ
ฟีเจอร์อัจฉริยะเพื่อประสบการณ์การค้นหาที่เหนือกว่า
นอกจากการค้นหาแบบ Full-text Search พื้นฐานแล้ว Elasticsearch ยังมีฟีเจอร์อัจฉริยะอีกมากมายที่ช่วยยกระดับประสบการณ์การค้นหาของผู้ใช้ให้เหนือกว่าเดิม ทำให้ระบบค้นหาของคุณเป็น “ระบบค้นหาอัจฉริยะ” ได้อย่างแท้จริงครับ
Autocomplete / Suggest: แนะนำคำค้นหา
ฟีเจอร์ Autocomplete หรือ Suggestion ช่วยให้ผู้ใช้สามารถค้นหาข้อมูลได้ง่ายขึ้นและเร็วขึ้น โดยการแนะนำคำค้นหาที่เป็นไปได้ขณะที่ผู้ใช้กำลังพิมพ์ครับ Elasticsearch มี Suggesters หลายประเภท
- `completion` suggester: เหมาะสำหรับ Autocomplete ที่เน้นความเร็วและประสิทธิภาพสูง มักใช้กับฟิลด์ที่เก็บคำแนะนำ
- `term` suggester: แนะนำคำที่คล้ายกับคำที่ผู้ใช้พิมพ์ เพื่อแก้ไขคำผิด
- `phrase` suggester: แนะนำวลีที่คล้ายกับวลีที่ผู้ใช้พิมพ์ เพื่อแก้ไขคำผิดในวลี
ตัวอย่างการใช้งาน `completion` suggester
ขั้นแรก ต้องกำหนด Mapping สำหรับฟิลด์ที่จะใช้ Autocomplete เป็นประเภท `completion` ครับ
PUT /products
{
"mappings": {
"properties": {
"name": { "type": "text" },
"suggest_name": {
"type": "completion"
}
}
}
}
จากนั้น Index ข้อมูลพร้อมกับฟิลด์ `suggest_name` ครับ
PUT /products/_doc/1
{
"name": "โทรศัพท์มือถือ Samsung Galaxy S23",
"suggest_name": "Samsung Galaxy S23"
}
PUT /products/_doc/2
{
"name": "โทรทัศน์ Samsung Smart TV 65 นิ้ว",
"suggest_name": "Samsung Smart TV"
}
เมื่อต้องการใช้งาน Autocomplete
GET /products/_search
{
"suggest": {
"product_suggestion": {
"prefix": "Sam",
"completion": {
"field": "suggest_name",
"size": 5
}
}
}
}
ผลลัพธ์จะให้คำแนะนำเช่น “Samsung Galaxy S23”, “Samsung Smart TV” ครับ
Synonyms: คำพ้องความหมาย
การจัดการคำพ้องความหมาย (Synonyms) ช่วยให้ผู้ใช้ค้นพบข้อมูลได้แม้จะใช้คำที่แตกต่างกันแต่มีความหมายเดียวกัน เช่น ค้นหา “รถยนต์” ก็เจอ “รถเก๋ง” หรือ “รถกระบะ” ครับ Synonyms ถูกกำหนดในส่วนของ Analyzer
ตัวอย่างการตั้งค่า Synonyms ใน Custom Analyzer
PUT /my_synonym_index
{
"settings": {
"analysis": {
"filter": {
"my_synonym_filter": {
"type": "synonym",
"synonyms": [
"รถยนต์, รถเก๋ง, รถกระบะ, car, auto",
"คอมพิวเตอร์, PC, โน้ตบุ๊ก, computer, laptop"
]
}
},
"analyzer": {
"my_custom_analyzer": {
"tokenizer": "standard",
"filter": ["lowercase", "my_synonym_filter"]
}
}
}
},
"mappings": {
"properties": {
"description": {
"type": "text",
"analyzer": "my_custom_analyzer"
}
}
}
}
หลังจาก Index ข้อมูลโดยใช้ `my_custom_analyzer` แล้ว หากคุณค้นหา “รถเก๋ง” ก็จะเจอ Document ที่มีคำว่า “รถยนต์” หรือ “รถกระบะ” ด้วยครับ
Stemming / Lemmatization: การลดรูปคำ
Stemming หรือ Lemmatization คือกระบวนการลดรูปคำให้อยู่ในรูปพื้นฐานของมัน เช่น “วิ่ง”, “วิ่งอยู่”, “กำลังวิ่ง” จะถูกลดรูปเป็น “วิ่ง” เพื่อให้การค้นหาคำใดคำหนึ่งพบทุกรูปแบบของคำนั้นๆ ครับ สิ่งนี้สำคัญมากสำหรับภาษาที่มีการผันคำหรือเติมคำนำหน้า/ปัจจัย
สำหรับภาษาอังกฤษ มี Stemmer ในตัว (`english` analyzer) ส่วนภาษาไทยมีความท้าทายมากกว่าเล็กน้อย เนื่องจากไม่มีการแบ่งคำที่ชัดเจนและไม่มีการผันคำเหมือนภาษาอังกฤษ แต่ก็สามารถใช้ Tokenizer และ Filter ที่เหมาะสมได้ (ซึ่งจะกล่าวถึงในส่วนของ Thai Full-text Search)
Faceted Search / Aggregations: การกรองและจัดหมวดหมู่ผลลัพธ์
Faceted Search หรือการทำ Aggregations คือฟีเจอร์ที่ช่วยให้ผู้ใช้สามารถกรองผลลัพธ์การค้นหาตามหมวดหมู่, แท็ก, ช่วงราคา, หรือคุณสมบัติอื่นๆ ได้อย่างง่ายดาย ซึ่งเป็นสิ่งที่เราเห็นบ่อยๆ ในเว็บไซต์ E-commerce ที่มี Filter ด้านข้างครับ
Elasticsearch มี Aggregations หลากหลายประเภท
- `terms` aggregation: นับจำนวน Document ในแต่ละหมวดหมู่ (เช่น หมวดหมู่สินค้า, ผู้เขียน)
- `range` aggregation: จัดกลุ่ม Document ตามช่วงค่า (เช่น ช่วงราคา, ช่วงวันที่)
- `date_histogram` aggregation: จัดกลุ่ม Document ตามช่วงเวลา (เช่น จำนวนบทความรายเดือน)
- `stats` aggregation: คำนวณค่าทางสถิติ (min, max, avg, sum)
ตัวอย่างการใช้ `terms` aggregation สำหรับหมวดหมู่สินค้า
GET /products/_search
{
"query": {
"match": {
"name": "Samsung"
}
},
"aggs": {
"categories": {
"terms": {
"field": "category.keyword", // ต้องใช้ .keyword สำหรับฟิลด์ที่เป็น text เพื่อให้ไม่ถูกวิเคราะห์
"size": 10
}
},
"brands": {
"terms": {
"field": "brand.keyword",
"size": 5
}
}
}
}
ผลลัพธ์ของการค้นหาจะมาพร้อมกับข้อมูล Aggregation ที่บอกว่าสินค้า Samsung ที่ค้นพบนั้นอยู่ในหมวดหมู่ใดบ้าง และมีแบรนด์ย่อยอะไรบ้าง พร้อมจำนวนสินค้าในแต่ละกลุ่ม ทำให้ผู้ใช้สามารถเลือกกรองผลลัพธ์ได้ทันทีครับ
การประยุกต์ใช้ฟีเจอร์เหล่านี้จะช่วยให้ระบบค้นหาของคุณไม่เพียงแค่หาข้อมูลเจอ แต่ยังช่วยให้ผู้ใช้สำรวจข้อมูลและค้นหาสิ่งที่ต้องการได้อย่างมีประสิทธิภาพและชาญฉลาดมากขึ้นครับ อ่านเพิ่มเติมเกี่ยวกับ Aggregations
การทำ Thai Full-text Search ด้วย Elasticsearch
การทำ Full-text Search สำหรับภาษาไทยมีความท้าทายและข้อควรพิจารณาที่แตกต่างจากภาษาอังกฤษอย่างชัดเจนครับ เนื่องจากภาษาไทยไม่มีการเว้นวรรคระหว่างคำที่ชัดเจนเหมือนภาษาอังกฤษ การแยกคำ (tokenization) จึงเป็นหัวใจสำคัญ
ความท้าทายของการค้นหาภาษาไทย
- ไม่มีช่องว่างระหว่างคำ: ประโยคภาษาไทยเขียนติดกันเป็นพืด การแยกคำจึงต้องอาศัยพจนานุกรมและ/หรืออัลกอริทึม
- คำหลายความหมาย: คำเดียวกันอาจมีความหมายต่างกันขึ้นอยู่กับบริบท
- คำทับศัพท์: การจัดการกับคำทับศัพท์จากภาษาต่างประเทศ
- คำพ้องเสียง/พ้องรูป: แม้จะสะกดต่างกันแต่เสียงเหมือนกัน หรือสะกดเหมือนกันแต่ความหมายต่างกัน
Analyzer สำหรับภาษาไทยใน Elasticsearch
Elasticsearch มี Thai Analyzer ในตัว ซึ่งใช้ ICU (International Components for Unicode) Tokenizer และ Thai Word Tokenizer ร่วมกับ Character Filter ต่างๆ
1. Thai Analyzer พื้นฐาน
หากคุณใช้ `type: “text”` และระบุ `analyzer: “thai”` Elasticsearch จะใช้ Analyzer พื้นฐานที่มาพร้อมกับ ICU (ถ้าติดตั้ง ICU Analysis Plugin) หรือ Thai Tokenizer แบบง่าย
PUT /thai_articles
{
"settings": {
"analysis": {
"analyzer": {
"default_thai_analyzer": {
"tokenizer": "thai",
"filter": ["lowercase"]
}
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "default_thai_analyzer"
}
}
}
}
ลองทดสอบด้วย `_analyze` API ครับ
GET /thai_articles/_analyze
{
"analyzer": "default_thai_analyzer",
"text": "การทำระบบค้นหาภาษาไทยด้วย Elasticsearch นั้นง่ายมาก"
}
ผลลัพธ์อาจจะออกมาประมาณนี้:
{
"tokens": [
{ "token": "การ", "start_offset": 0, "end_offset": 3, "type": "<thai_word>", "position": 0 },
{ "token": "ทำ", "start_offset": 3, "end_offset": 5, "type": "<thai_word>", "position": 1 },
{ "token": "ระบบ", "start_offset": 5, "end_offset": 9, "type": "<thai_word>", "position": 2 },
{ "token": "ค้นหา", "start_offset": 9, "end_offset": 13, "type": "<thai_word>", "position": 3 },
{ "token": "ภาษาไทย", "start_offset": 13, "end_offset": 20, "type": "<thai_word>", "position": 4 },
{ "token": "ด้วย", "start_offset": 20, "end_offset": 24, "type": "<thai_word>", "position": 5 },
{ "token": "elasticsearch", "start_offset": 25, "end_offset": 39, "type": "<thai_word>", "position": 6 },
{ "token": "นั้น", "start_offset": 40, "end_offset": 44, "type": "<thai_word>", "position": 7 },
{ "token": "ง่าย", "start_offset": 44, "end_offset": 48, "type": "<thai_word>", "position": 8 },
{ "token": "มาก", "start_offset": 48, "end_offset": 51, "type": "<thai_word>", "position": 9 }
]
}
2. การปรับแต่ง Thai Analyzer เพื่อประสิทธิภาพที่ดียิ่งขึ้น
แม้ว่า `thai` tokenizer จะทำงานได้ดีในระดับหนึ่ง แต่เพื่อให้ได้ผลลัพธ์ที่ดีที่สุด เราอาจต้องการปรับแต่งเพิ่มเติมครับ
- Stop Words: การลบคำที่ไม่สำคัญ เช่น “ครับ”, “ค่ะ”, “และ”, “หรือ”, “ใน”, “ที่”, “ของ” ช่วยลดขนาดของ Index และเพิ่มความเกี่ยวข้องของผลลัพธ์
- Synonyms: การกำหนดคำพ้องความหมายภาษาไทย เช่น “กทม.” -> “กรุงเทพมหานคร”
- Custom Dictionary (สำหรับ Tokenizer): ในบางกรณี หาก `thai` tokenizer แยกคำไม่ถูกต้องสำหรับคำเฉพาะทางของธุรกิจเรา อาจจะต้องพิจารณาใช้ tokenizer ภายนอก หรือสร้าง custom dictionary ให้กับ tokenizer ที่รองรับ
ตัวอย่าง Custom Thai Analyzer พร้อม Stop Words และ Synonyms
PUT /thai_advanced_articles
{
"settings": {
"analysis": {
"filter": {
"thai_stopwords_filter": {
"type": "stop",
"stopwords": ["ครับ", "ค่ะ", "และ", "หรือ", "ไม่", "ใน", "ที่", "ของ", "_thai_"] // _thai_ คือชุด stopwords เริ่มต้นของภาษาไทย
},
"thai_synonym_filter": {
"type": "synonym",
"synonyms_path": "analysis/thai_synonyms.txt" // อ่านจากไฟล์ภายนอก
}
},
"analyzer": {
"custom_thai_analyzer": {
"tokenizer": "thai",
"filter": [
"lowercase",
"thai_stopwords_filter",
"thai_synonym_filter"
]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "custom_thai_analyzer"
},
"body": {
"type": "text",
"analyzer": "custom_thai_analyzer"
}
}
}
}
ในตัวอย่างนี้ เราใช้ `thai` tokenizer ร่วมกับ `lowercase` และ `thai_stopwords_filter` (ซึ่งรวมชุด stopwords ภาษาไทยเริ่มต้นและที่เราเพิ่มเข้าไป) และ `thai_synonym_filter` ที่อ่านคำพ้องความหมายจากไฟล์ `thai_synonyms.txt` ครับ
ตัวอย่างเนื้อหาในไฟล์ `analysis/thai_synonyms.txt`:
กทม., กรุงเทพ, กรุงเทพมหานคร
มือถือ, โทรศัพท์เคลื่อนที่, โทรศัพท์มือถือ
แล็ปท็อป, โน้ตบุ๊ก
ข้อควรพิจารณาเพิ่มเติมสำหรับ Thai Full-text Search
- ICU Analysis Plugin: สำหรับ Elasticsearch เวอร์ชันเก่าๆ อาจจะต้องติดตั้ง ICU Analysis Plugin เพื่อให้ได้ Thai Tokenizer ที่มีประสิทธิภาพดีที่สุด แต่ในเวอร์ชันใหม่ๆ มักจะมาพร้อมกับ Core แล้ว
- Relevancy Scoring: การปรับแต่ง `field_boosting` หรือใช้ `function_score` query เพื่อให้คะแนนความเกี่ยวข้องของผลลัพธ์ภาษาไทยแม่นยำยิ่งขึ้น
- Testing: การใช้ `_analyze` API และการทดสอบกับชุดข้อมูลจริงเป็นสิ่งสำคัญอย่างยิ่งในการปรับแต่ง Analyzer สำหรับภาษาไทยครับ
การลงทุนเวลาในการปรับแต่ง Thai Analyzer ให้เหมาะสมกับข้อมูลของคุณ จะส่งผลให้ระบบค้นหาภาษาไทยของคุณมีประสิทธิภาพและแม่นยำอย่างที่ไม่เคยมีมาก่อนครับ
การปรับแต่งประสิทธิภาพ (Performance Tuning) และการขยายระบบ (Scaling)
เมื่อระบบค้นหาของคุณเริ่มมีข้อมูลมากขึ้นและมีผู้ใช้เข้ามาค้นหาพร้อมกันจำนวนมาก การปรับแต่งประสิทธิภาพและการขยายระบบจึงเป็นสิ่งสำคัญเพื่อให้ Elasticsearch ยังคงตอบสนองได้อย่างรวดเร็วและเสถียรครับ
1. การจัดการ Shard และ Replica
นี่คือหัวใจของการ Scaling ใน Elasticsearch
- จำนวน Shard:
- กำหนด `number_of_shards` ที่เหมาะสมตั้งแต่แรก เพราะการเปลี่ยนจำนวน Shard ในภายหลังทำได้ยาก (ต้อง Reindex ข้อมูลใหม่)
- Shard ที่เล็กเกินไปมี Overhead สูง Shard ที่ใหญ่เกินไปทำให้การกระจายโหลดทำได้ไม่ดี
- ขนาดที่เหมาะสมของแต่ละ Shard ควรอยู่ที่ประมาณ 10-50 GB ครับ (แต่ก็ขึ้นอยู่กับ Use Case)
- จำนวน Shard ทั้งหมดใน Cluster ควรเหมาะสมกับจำนวน Node และทรัพยากรของแต่ละ Node
- จำนวน Replica:
- กำหนด `number_of_replicas` อย่างน้อย 1 เพื่อความทนทานต่อความผิดพลาดของ Node (ถ้า Primary Shard ล่ม Replica จะกลายเป็น Primary แทน)
- Replica สามารถใช้เพื่อกระจายภาระงานการค้นหาได้ ยิ่งมี Replica มาก ก็ยิ่งรองรับ Query ได้เยอะขึ้น
- แต่ Replica ก็ใช้พื้นที่ดิสก์และทรัพยากร Node เพิ่มขึ้นเช่นกัน
PUT /my_index
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}
หมายความว่า Index นี้จะมี 3 Primary Shards และแต่ละ Primary Shard จะมี 1 Replica รวมเป็น 6 Shards ที่ต้องจัดเก็บใน Cluster ครับ
2. ทรัพยากรฮาร์ดแวร์
- RAM: Elasticsearch ใช้ RAM เป็นจำนวนมากสำหรับ JVM Heap และ Filesystem Cache ควรจัดสรร RAM ให้เพียงพอ แนะนำให้ 50% ของ RAM ทั้งหมดของเครื่องสำหรับ JVM Heap แต่ไม่เกิน 30.5GB (เนื่องจาก Java Pointer Compression) ส่วนที่เหลือจะถูกใช้เป็น Filesystem Cache ซึ่งสำคัญมากสำหรับประสิทธิภาพการค้นหา
- CPU: การค้นหาและ Indexing ใช้ CPU ค่อนข้างมาก โดยเฉพาะเมื่อมีการทำ Aggregations หรือ Full-text Analysis ที่ซับซ้อน เลือก CPU ที่มี Core เยอะๆ และความเร็วสูง
- Disk I/O: Storage Speed มีผลอย่างมากต่อประสิทธิภาพการ Indexing และการค้นหา แนะนำให้ใช้ SSD (Solid State Drive) หรือ NVMe สำหรับ Production Environment
- Network: สำหรับ Cluster แบบ Multi-Node เครือข่ายที่เร็วและมี Latency ต่ำเป็นสิ่งสำคัญสำหรับการสื่อสารระหว่าง Node
3. การปรับแต่ง Indexing Speed
- Bulk API: ใช้วิธี Bulk Indexing เสมอเมื่อนำเข้าข้อมูลจำนวนมาก
- Refresh Interval: ค่า `index.refresh_interval` (ค่าเริ่มต้น 1 วินาที) คือความถี่ที่ข้อมูลใหม่จะถูกทำให้มองเห็นได้สำหรับการค้นหา การเพิ่มค่านี้ (เช่น 30s หรือ 60s) จะช่วยลด Overhead ของการ Refresh และเพิ่มความเร็วในการ Indexing แต่จะทำให้ข้อมูลปรากฏในการค้นหาช้าลง
- Disable Replicas (ชั่วคราว): หากคุณกำลัง Index ข้อมูลจำนวนมหาศาลครั้งแรก คุณสามารถตั้ง `number_of_replicas: 0` ชั่วคราว เพื่อลดภาระงาน จากนั้นค่อยเพิ่มกลับมาเมื่อ Indexing เสร็จสิ้น
# ลดจำนวน Replica ชั่วคราว
PUT /my_index/_settings
{
"index": {
"number_of_replicas": 0,
"refresh_interval": "30s"
}
}
# หลัง Indexing เสร็จสิ้น คืน