<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Je-Develop</title>
    <description>The latest articles on DEV Community by Je-Develop (@je_develop).</description>
    <link>https://dev.to/je_develop</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1115318%2F1ea7b70b-802f-4cc4-b425-317b3e568bb2.png</url>
      <title>DEV Community: Je-Develop</title>
      <link>https://dev.to/je_develop</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/je_develop"/>
    <language>en</language>
    <item>
      <title>มาทำ website search service ด้วย Logstash และ Elasticsearch กันเถอะ</title>
      <dc:creator>Je-Develop</dc:creator>
      <pubDate>Sat, 08 Jul 2023 09:10:57 +0000</pubDate>
      <link>https://dev.to/je_develop/maatham-website-search-service-dwy-logstash-aela-elasticsearch-kanetha-djk</link>
      <guid>https://dev.to/je_develop/maatham-website-search-service-dwy-logstash-aela-elasticsearch-kanetha-djk</guid>
      <description>&lt;p&gt;สวัสดีครับทุกคน จริงๆที่มาของบทความนี้คือเนื่องจากได้รับมอบหมายให้ทำ search service ให้กับเว็บไซค์นึงโดยต้องใช้ &lt;br&gt;
Logstash และ Elasticsearch ในการทำงาน ไหนๆก็ได้ POC ละ ก็ลองเขียนบทความมันซะเลยจะได้เป็นการทบทวนตัวเอง หรือมีใครที่กำลังศึกษาเพื่อพอที่จะได้เป็นแนวทางให้ได้บ้าง&lt;/p&gt;
&lt;h2&gt;
  
  
  Logstash และ Elasticsearch คืออะไร
&lt;/h2&gt;

&lt;p&gt;เรามาเริ่มต้นด้วยการทำความรู้จักกับเจ้าเครื่องมือสองตัวที่เรากำลังจะนำมาใช้งานกันก่อนดีกว่าครับ เจ้า Logstash และ Elasticsearch เนื่ยเป็น 2 ใน 3 เครื่องมือที่อยู่ใน ELK Stack ซึ่งเป็น Application open source ที่ถูกจัดทำโดยทีมงาน Elastic (อีกตัวคือ Kibana แต่เรายังไม่ได้ใช้ ฮ่าๆ) มีจุดประสงค์ในการทำ data management ต่างๆเช่น การดึงข้อมูลจากแหล่งนึงมาวิเคาะร์และทำการส่งต่อไปให้แหล่งต่างๆต่อไป โดยการทำงานของ ELK Stack ก็ประมาณนี้&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M3ecO28n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tsa2hpu6psl94v22x00p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M3ecO28n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tsa2hpu6psl94v22x00p.png" alt="ELK stack work" width="700" height="513"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;ภาพที่.1 - How ELK work. (ที่มา &lt;a href="https://www.loggly.com/use-cases/what-is-the-elk-stack/"&gt;https://www.loggly.com/use-cases/what-is-the-elk-stack/&lt;/a&gt;) 
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;โดยในบทความนี้เราจะใช้ Logstash ในการ query ข้อมูลจาก Database และทำการส่งต่อไปให้ ElasticSearch ในการทำ indexing&lt;/p&gt;
&lt;h2&gt;
  
  
  Project structure
&lt;/h2&gt;

&lt;p&gt;งั้นก่อนเริ่มลงมือเรามาดูการทำงานของ search service ที่เรากำลังจะ implement กันดีกว่า สมมุติว่าเรามีเว็บไซค์อยู่เว็บไซค์นึงและมี content ต่างๆในเว็บมากมายแล้วที่นี้เราต้องการให้ user ของเรานั้นสามารถ search content ต่างๆภายในเว็บไซค์ของเราได้เราจึงทำการ implement API ขึ้นมาเพื่อรองรับการทำงานในการค้นหา content ต่างๆแบบนี้&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Z18LGp54--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a7nl1d6rlxxxpqnbl861.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z18LGp54--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a7nl1d6rlxxxpqnbl861.jpg" alt="ภาพที่ 2" width="624" height="83"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;จากรูปจะเห็นได้ว่ามีการทำงานที่ไม่ซับซ้อนเลย โดยเริ่มจากเราจะใช้ logstash ดึงข้อมูลจาก database และทำการส่งต่อไปให้ Elasticsearch ในการทำ indexing จากนั้นเราจะทำการสร้าง API ขึ้นมาหนึ่งตัวเพื่อเป็นการดึงข้อมูลจาก Elasticsearch อีกที&lt;/p&gt;
&lt;h2&gt;
  
  
  How to setup
&lt;/h2&gt;

&lt;p&gt;จริงๆแล้วในการ setup ELK stack เราจะ download จาก &lt;a href="https://www.elastic.co/"&gt;official&lt;/a&gt; มาติดตั้งภายในเครื่องก็ได้ แต่ในบทความนี้เราจะใช้ docker เป็นหลัก โดยเราจะกำหนด Docker compose ตามนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3'
services:
  postgres:
    image: postgres:15-alpine
    container_name: postgresql
    environment:
      - POSTGRES_PASSWORD=secret
      - POSTGRES_USER=admin
      - POSTGRES_DB=web_content
    ports:
      - 5432:5432
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - search-network
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.7.1
    container_name: elasticsearch
    ports:
      - 9200:9200
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
    volumes:
      - elasticsearch_data:/usr/share/elasticsearch/data
    networks:
      - search-network
  logstash:
    image: docker.elastic.co/logstash/logstash:8.7.1
    container_name: logstash
    environment:
      discovery.seed_hosts: logstash
    volumes:
      - ./logstash-config/postgresql-42.6.0.jar:/usr/share/logstash/postgresql-42.6.0.jar
      - ./logstash-config/pipelines.yml:/usr/share/logstash/config/pipelines.yml
      - ./logstash-config/web-search.conf:/usr/share/logstash/config/web-search.conf
      - ./logstash-config/template.json:/usr/share/logstash/config/template.json
    depends_on:
      - elasticsearch
      - postgres
    networks:
      - search-network
    links:
      - elasticsearch
      - postgres
volumes:
  elasticsearch_data:
  postgres_data:
networks:
  search-network:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ในที่นี้เราจะใช้ database เป็น postgresql การตั้งค่าต่างๆก็ไม่มีอะไรพิเศษใช้ตามปกติได้เลย ส่วน Elasticsearch เราจะตั้ง xpack.security เป็น false ไว้เพื่อให้ง่ายต่อการ develop&lt;/p&gt;

&lt;p&gt;มาถึงส่วนที่สำคัญและมีขั้นตอนเยอะกว่าส่วนอื่นๆ นั้นก็คือการ setup logstash อย่างแรกเลยเรามาดูการทำงานของ Logstash กันซะหน่อย ตัว Logstash จริงๆแล้วจะเรียนกว่าเป็น data processing pipeline ก็ได้ ซึ่งสิ่งที่ Logstash สามารถทำได้ก็เช่น &amp;nbsp;ingest data จาก source ต่างๆเช่น database, log file เป็นต้น tranform data จากนั้นก็ส่ง data ไปยังที่ที่ต้องการ ซึ่งการทำงานของ Logstash จะประกอบด้วย 3 ส่วนหลักๆดังนี้&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CvG8WlBE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uafazmg6hxqdolv7qcjj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CvG8WlBE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uafazmg6hxqdolv7qcjj.png" alt="ภาพที่ 3 ที่มา [Logstash](https://www.bmc.com/blogs/logstash-using-data-pipeline/)" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;ภาพที่.2 - How Logstash work. (ที่มา &lt;a href="https://www.bmc.com/blogs/logstash-using-data-pipeline/"&gt;https://www.bmc.com/blogs/logstash-using-data-pipeline/&lt;/a&gt;) 
&lt;p&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Input หรือ data ขาเข้าต่างๆ คือแหล่ง data ที่เราต้องการส่งมา process ที่ Logstash (ในที่นี้ input data ของเราจะมาจาก postgresql)&lt;/li&gt;
&lt;li&gt;Filter ส่วนนี้จะเป็นการทำ tranformation data ให้อยู่ในรูปแบบที่เราต้องการ&lt;/li&gt;
&lt;li&gt;Output เป็นที่ที่เราต้องการนำ data ไปจัดเก็บหรือส่ง data ออกไป (ในที่นี้ของเราคือ Elasticsearch)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;เราพอจะเข้าใจการทำงานคร่าวๆของ logstash แล้วงั้นมาลงมือกันเลยดีกว่าอย่างแรกทำการสร้างไฟล์ .yml ซึ่งเป็นที่ configure global settings ต่างๆของ logstash&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 - pipeline.id: web-search
2   path.config: "/usr/share/logstash/config/web-search.conf"
3   config.support_escapes: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;จาก code ด้านบน&lt;br&gt;
บรรทัดที่ 1 เรามีการ set id ให้กับ pipeline ตัวนี้ แน่นอนว่าเราสามารถมี pipeline ได้มากกว่าหนึ่งตัว&lt;br&gt;
บรรทัดที่ 2 คือ path config ตัวนี้ใช้สำหรับบอก Logsatsh ว่าจะไปเอาตัว config ของเราได้ที่ไหน (เดี๋ยวจะให้ดูต่อไปว่า config file คืออะไร) &lt;br&gt;
บรรทัดต่อมาไม่มีอะไรแค่บอก logstash ให้รองรับตัว escape sequences ต่างๆ&lt;/p&gt;

&lt;p&gt;หลังจากที่เราทำการสร้าง configure ของ Logstash แล้ว ทีนี้เรามากำหนด data processing pipelines ของ Logstash กันบ้างซึ่งจะอยู่ในรูปของ .conf file ซึ่งจะมีหน้าตาแบบนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 input{
2     jdbc{
3         jdbc_connection_string =&amp;gt; 
4   "jdbc:postgresql://postgresql:5432/web_content"
5         jdbc_user =&amp;gt; "admin"
6         jdbc_password =&amp;gt; "secret"
7         jdbc_driver_library =&amp;gt; 
8   "/usr/share/logstash/postgresql-42.6.0.jar"
9         jdbc_driver_class =&amp;gt; "org.postgresql.Driver"
10        statement =&amp;gt; "select title, description, keyword, 
11  'localhost:3000' || url_path as url from \"content\""
12     }
13  }
14
15  filter {}
16
17  output {
18      elasticsearch{
19          hosts =&amp;gt; ["elasticsearch:9200"]
20          index =&amp;gt; "content_search"
21          template =&amp;gt; 
22   "/usr/share/logstash/config/template.json"
23      }
24  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;เราจะเห็นได้ว่า file ของเราอันนี้ ก็คือการทำงานของ Logstash ที่อ้างอิงจากภาพที่ 2 นั้นเอง &lt;br&gt;
Input ของเรานั้นเราจะดึงข้อมูลมาจาก postgresql ส่วน output ของเรานั้นจะส่งข้อมูลที่ได้ไปยัง Elasticsearch นั้นเอง (filter ไม่จำเป็นต้องมีก็ได้ในกรณีนี้) &lt;br&gt;
งั้นเรามาดูกันที่ละตัว &lt;/p&gt;
&lt;h2&gt;
  
  
  Logstash input
&lt;/h2&gt;

&lt;p&gt;เราจะเห็นคำว่า JDBC (Java Database Connectivity.)อยู่ สิ่งนั้นก็คือ plugin นั้นเอง logstash Allow ให้เรานำเข้า data จากแหล่งใดๆก็ได้ ตราบเท่าที่มี plugin support ในที่นี้เราจะใช้เจ้าตัว JDBC จาก code&lt;br&gt;
บรรทัดที่ 3-6 จะเป็น database connection ของเรา&lt;br&gt;
บรรทัดที่ 7 เป็นการกำหนดที่อยู่ driver ของ JDBC สำหรับ postgresql สามารถ download ได้ที่นี่ &lt;a href="https://jdbc.postgresql.org/"&gt;JDBC DRIVER&lt;/a&gt; &lt;br&gt;
บรรทัดที่ 10 จะเป็น query statment ของเราในที่นี้จาก query จะเห็นว่ามีการเลือก title, description, และ keywords ออกมาเพราะต้องการใช้ค่าจาก field ต่างๆพวกนี้ในการทำ indexing นั้นเอง&lt;/p&gt;
&lt;h2&gt;
  
  
  Logstash output
&lt;/h2&gt;

&lt;p&gt;ในที่นี้เราจะใช้ elasticsearch plugin สำหรับส่งข้อมูลออกไปที่ Elasticsearch เรามาดู option ต่างๆของ plugin ตัวนี้กัน&lt;br&gt;
    - hosts : กำหนด Elasticsearch cluster's host(s) ว่าจะให้ไป connect ทีไหน&lt;br&gt;
    - index : ชื่อของ index &lt;br&gt;
    - template : กำหนด template ที่ใช้สำหรับการสร้าง index&lt;/p&gt;

&lt;p&gt;template คืออะไร index template ก็คือ config file ตัวนึงที่เราใช้บอก Elasticsearch ว่าเราอยากสร้าง index อย่างไร&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "index_patterns": ["content-search*"]
  "template": {
    "settings": {
      "index": {
        "analysis": {
          "analyzer": {
            "thai_analyzer": {
              "tokenizer": "thai",
              "filter": ["lowercase"]
            }
          }
        }
      }
    },
    "mappings": {
      "properties": {
        "title": {
          "type": "text",
          "analyzer": "thai_analyzer"
        },
        "description": {
          "type": "text",
          "analyzer": "thai_analyzer"
        },
        "keyword": {
          "type": "text",
          "analyzer": "thai_analyzer"
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;index template ของเรามีหน้าตาประมาณนี้ และทำไมต้องใช้ ในกรณีนี้เราอยากจะสร้าง index ด้วยการบอก Elasticsearch อยากให้มีการตัดคำแบบภาษาไทยได้ &lt;br&gt;
โดยปกติแล้ว Elasticsearch จะมีการ process ข้อความด้วยสิ่งที่เรียกว่า Analyzer เช่นการตัดคำ, เปลี่ยนเป็น lowercase, ตัด stop word &lt;br&gt;
ซึ่ง default แล้ว ค่าเริ่มต้นจะไม่รองรับภาษาไทยทำให้เวลาเราต้องการ search คำด้วยภาษาไทยต้องมีการกำหนดค่า Analyzer ภาษาไทยเข้าไปด้วย&lt;br&gt;
สมมุติว่าเรามีข้อความว่า “สวัสดีครับนี่คือการทดสอบ” ถ้าเรา search ด้วยแบบนี้ ก็จะไม่ได้ result&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://localhost:9200/my_index/_search?q=สวัสดีครับ&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;ถ้าอยากได้ result เราจำเป็นที่จะต้อง search ด้วยข้อความเต็มทั้งหมดแบบนี้&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://localhost:9200/my_index/_search?q=สวัสดีครับนี่คือการทดสอบ&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;จาก code จะเห็นว่ามีการกำหนด custom analyzer โดยใช้ชื่อว่า thai_analyzer โดยเราได้กำหนดว่า Analyzer นี้จะ process โดยการปรับข้อความเป็น lowercase และตัดคำด้วย Tokenizer ภาษาไทยของ Elasticsearch และเรามีการกำหนดว่า field, title, description, keyword ให้มีการใช้ Analyzer แบบภาษาไทย โดยใช้ตัว custom analyzer ของเรานั้นเอง &lt;code&gt;"analyzer": "thai_analyzer"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;จาก setup ทั้งหมดน่าจะเพียงพอแล้วสำหรับการ run Logstash และ Elasticsearch ถ้าพร้อมแล้วก็ docker-compse up โลดดด ถ้าไม่มี Error message ใดๆปรากฎก็แสดงว่าทำงานถูกต้อง &lt;br&gt;
แล้วเราจะรู้ได้ยังไงละว่า data ที่เราอุตส่าห์ดึงมาเข้าไปอยู่ใน Elasticsearch แล้ว วิธีที่เราสามารถเช็คได้ง่ายๆก็คือเช็คผ่านตัว Elasticsearch API นั้นเอง ตัว Elasticsearch provide API ให้เราสามารถเข้าไปเรียกได้เลย ส่วนใครอยากรู้รายละเอียดต่างๆว่ามีอะไรให้ใช้มั้ง สามารถเข้าไปดูได้ที่นี่เลย &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rest-apis.html"&gt;Elasticsearch API&lt;/a&gt;&lt;br&gt;
โอเคก่อนอื่นงั้นเรามาลองดูดีกว่า ว่า Elasticsearch ของเรานั้นมันทำงานได้แล้วจริงๆโดยการส่ง Get request หรือเปิด Browser ไปที่ &lt;code&gt;http://localhost:9200/&lt;/code&gt;&lt;br&gt;
หากไม่มีข้อผิดผลาดใดๆ เราก็จะได้รับข้อความหน้าตาประมาณนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name" : "50bc0eda2e16",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "fBx14wctQeCEWchxU9oeKQ",
  "version" : {
    "number" : "8.7.1",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "f229ed3f893a515d590d0f39b05f68913e2d9b53",
    "build_date" : "2023-04-27T04:33:42.127815583Z",
    "build_snapshot" : false,
    "lucene_version" : "9.5.0",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "You Know, for Search"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ต่อมาเพื่อเป็นการตรวจสอบว่า index ของเราสามารใช้งานได้จริงๆแล้วเราสามารถดูได้จาก &lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://localhost:9200/_cat/indices&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;เราจะได้ list index ของเราออกมา ซึ่งถ้ามีข้อความประมาณนี้ปรากฎเป็นใช้ได้ (สังเกตชื่อ index ของเราด้วยว่าตรงกับที่เราตั้งไว้ไหม ในที่นี้เราใช้ชื่อว่า content_search)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yellow open content_search V3ewHt6XS-uhSLouGrl2eA 1 1 1 0 8.3kb 8.3kb&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;งั้นเรามาทดสอบอีกซักหน่อยดีกว่าว่า Data ที่เราได้มาจาก Logstash input นั้นมันถูกส่งมาที่ Elasticsearch จริงๆหรือเปล่าโดยมาที่นี่&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://localhost:9200/{index_name}/_search.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;(Replace index_name ด้วย index name ของเรา)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "took":12,"timed_out":false,"_shards":{
        "total":1,
        "successful":1,
        "skipped":0,"failed":0
    },
    "hits":{
        "total":{
            "value":1,
            "relation":"eq"
    },
    "max_score":1.0,
    "hits":[{
        "_index":"content_search",
        "_id":"luFcC4kBpCWRwcQmXoAD",
        "_score":1.0,
        "_source":{
            "title":"สวัสดีครับ",
            "url":"localhost:3000/test",
            "@version":"1",
            "keyword":["ทดสอบ","เทส"],
            "@timestamp":"2023-06-30T08:12:29.863354418Z",
            "description":"ทดสอบเทสทดสอบเทส"
    }
     }
   ]
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ซึ่งเราจะได้รับของหน้าตาตามรูปสังเกตว่าภายใต้ hits จะมี field ที่เป็น Data ที่เราต้องการ (title, url, keyword, description) ซึ่งจะสัมพันธ์กับ query ใน statement ที่เราได้เพิ่มไปใน Logstash input &lt;/p&gt;

&lt;p&gt;เท่านี้เป็นเรียบร้อยสำหรับ setup และ ใช้งาน logstash คู่กับ Elasticsearch สำหรับทำการดึงข้อมูลจาก Postgresql มาทำ indexing บน Elasticsearch &lt;/p&gt;

&lt;h2&gt;
  
  
  API Service
&lt;/h2&gt;

&lt;p&gt;หลังจากที่เรามีข้อมูลที่เราต้องการบน ElasticSearch แล้วต่อมาเราจะทำ API ขึ้นมาหนึ่งเส้น สำหรับให้หน้าบ้านเรียกใช้ search content ซึ่งเราจะ response ออกไปตามที่หน้าบ้านต้องการ โดยในการทำ API ครั้งนี้เราจะใช้ Golang &lt;/p&gt;

&lt;p&gt;สำหรับการทำงานร่วมกับ Elasticsearch จริงๆแล้วการใช้งานตัว Elasticsearch กับ Golang นั้นเราสามารถใช้งานผ่าน HTTP requests ใน go ได้เลย แต่เพื่อความง่ายและสะดวกเราจะใช้งานผ่านตัว client library ที่ชื่อว่า go-elasticsearch &lt;a href="https://github.com/elastic/go-elasticsearch"&gt;library&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;สิ่งแรกที่เราต้องทำก็คือ connect ตัว Elasticsearch กันก่อนเลย&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 func NewElasticClient(es_end_point string) (*ElasticClient, 2 error) {
3   cfg := elasticsearch.Config{
4       Addresses: []string{
5           es_end_point,
6       },
7   }
8
9   //Connect to Elasticsearch
10  es, err := elasticsearch.NewClient(cfg)
11  if err != nil {
12      log.Fatalf("Error creating the client: %s", 
13  err)
14      return nil, err
15  }
16
17  res, err := es.Info()
18  if err != nil {
19      log.Fatalf("Error getting response: %s", err)
20      return nil, err
21  }
22
23  defer res.Body.Close()
24  log.Println(res)
25
26  return &amp;amp;ElasticClient{es}, nil
27 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;จาก code จะเห็นว่ามีการสร้าง function ขึ้นมาตัวนึงที่ใช้สำหรับการ connect ตัว Elasticsearch ชื่อว่า elasticConnect() ซึ่งจะคืนของเป็น Elasticsearch client object.&lt;br&gt;
 จากรูปที่ code &lt;br&gt;
บรรทัดที่ 3 - 7 นั้นจะเป็นการกำหนด configure สำหรับการสร้าง  Elasticsearch client. configure ในที่นี้ของเราก็จะมีแค่ตัว Addresses สำหรับกำหนดตัว Elasticsearch endpoint(s). เท่านั้น&lt;br&gt;
บรรทัดที่ 10 จะเป็นการสร้าง instant ของ Elasticsearch client ขึ้นมาโดย base on มาจากตัว config ที่เราได้สร้างไว้ในบรรทัดที่ 27 - 30&lt;br&gt;
บรรทัดที่ 17 นั้นเราจะเรียก function Info() สำหรับการเรียกดูข้อมูลต่างๆใน Cluster ของตัว Elasticsearch เช่น เวอร์ชั่น,ชื่อ, และ พวกรายละเอียดๆต่างๆ.&lt;/p&gt;

&lt;p&gt;หลังจากที่เรา connect ตัว Elasticsearch ไปแล้วเราจะมาทำการสร้าง function สำหรับการ search content ของเว็บไซค์ของเราโดยทั้งหมดจะทำงานผ่านตัว esapi ซึ่งเป็นอีก package นึงของตัว go-elasticsearch แต่ก่อนที่เราจะทำตัว search นั้นเรามาเตรียม query กันก่อนดีกว่า query คืออะไร จริงๆแล้วจะบอกว่าตัว query นี้เป็น search condition ก็ได้นะ เรามาดูหน้าตากันดีกว่า&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 type SearchRequest struct {
2   KeyWord string `form:"keyword" binding:"required"`
3 }
4
5 func setQuery(req SearchRequest) ([]byte, error) {
6
7   query := map[string]interface{}{
8       "query": map[string]interface{}{
9           "multi_match": map[string]interface{}{
10              "query":  req.KeyWord,
11              "fields": []string{"keyword", 12 "title", "description"},
13          },
14      },
15  }
16
17  jsonQuery, err := json.Marshal(query)
18  if err != nil {
19      log.Printf("Can not marshal query: %s", err)
20      return []byte{}, err
21  }
22
23  return jsonQuery, nil
24 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;จาก code จะเห็นว่ามีการสร้าง function มาหนึ่งตัวชื่อว่า setQuery มีหน้าที่ในการเตรียม query สำหรับใช้ในการ search ที่ Elasticsearch นั้นเอง&lt;br&gt;
โดยเจ้า function ตัวนี้จะรับ parameter หนึ่งตัวเป็น keyword ที่เราใช้ในการค้นหา และคืนของเป็น query statement ในรูปของ json&lt;br&gt;
mult_match    : เป็นตัวกำหนดชนิดของการ search ที่เราใส่เข้ามานั้นจะให้ match อย่างไร (multi_match ในที่นี้หมายถึงให้ match คำค้นหามากกว่า 1 field นั้นเอง)&lt;br&gt;
query              : เป็นตัวกำหนดคำค้นหานั้นเอง (keyword หรือคำ ที่เราต้องการจะใช้สำหรับการ search content)&lt;br&gt;
Field                : คือตัวกำหนดว่าจะให้ keyword ของเรา search จาก filed ไหนบ้าง (โดย Elasticsearch จะ match keyword ของเราเข้ากับ field ที่เรากำหนด) &lt;/p&gt;

&lt;p&gt;หลังจากที่เราเตรียม query เรียบร้อยก็ถึงเวลาของการทำ function search  กันแล้วโดยเราจะทำการสร้างฟังค์ชั่นมาอีกหนึ่งตัว&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 func (ec *ElasticClient) GetContentByKeyword(searchIndex 
2 string, jsonQuery []byte) (*esapi.Response, error) {
3   // Set up the search request
4   result := esapi.SearchRequest{
5       Index: []string{searchIndex},
6       Body:  bytes.NewReader(jsonQuery),
7   }
8
9   res, err := result.Do(context.Background(), ec)
10  if err != nil {
11      log.Printf("Error executing search request: 
12 %s", err)
13      return nil, err
14  }
15
16  if res.IsError() {
17      log.Printf("Search request failed: %s", 
18 res.String())
19      return nil, err
20  }
21
22  return res, nil
23 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;จากรูป ฟังค์ชั่น GetContentByKeyword ของเราจะรับของสองตัว และคืนออกไปเป็น *esapi.Response&lt;br&gt;
    - indexName คือชื่อ index ที่เราจะใช้สำหรับค้นหา (อันเดียวกับที่อยู่ใน logstash pipeline config file ส่วนของ output)&lt;br&gt;
    - jsonQuery คือ search condition ที่เราจะใช้สำหรับการค้นหา (ได้มาจาก setQuery ฟังค์ชั่น)&lt;/p&gt;

&lt;p&gt;บรรทัดที่ 4 - 7 จะเป็นการ setting search request สำหรับการค้นหา โดยจะรับของสำคัญสองตัว คือ index name และ ตัว query condition&lt;br&gt;
บรรทัดที่ 9 เราจะเรียกใช้ method Do สำหรับการ executed search request ถ้าไม่มี error ใดๆ เกิดขึ้นเราจะได้ result หน้าตาประมาณนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "took":12,"timed_out":false,"_shards":{
        "total":1,
        "successful":1,
        "skipped":0,"failed":0
    },
    "hits":{
        "total":{
            "value":1,
            "relation":"eq"
    },
    "max_score":1.0,
    "hits":[{
        "_index":"content_search",
        "_id":"luFcC4kBpCWRwcQmXoAD",
        "_score":1.0,
        "_source":{
            "title":"สวัสดีครับ",
            "url":"localhost:3000/test",
            "@version":"1",
            "keyword":["ทดสอบ","เทส"],
            "@timestamp":"2023-06-30T08:12:29.863354418Z",
            "description":"ทดสอบเทสทดสอบเทส"
    }
     }
   ]
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ซึ่งก็เป็น response หน้าตาแบบเดียวกับเวลาเราเรียกไปที่ Elasticsearch url เลย สำหรับใครที่อยากรู้รายละเอียดต่างๆของ response สามารถไปตามอ่านกันได้ที่นี่เลย &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html#search-api-response-body"&gt;Elasticsearch API&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;แต่ๆ เราคงไม่ได้อยากคืนของทั้งหมดนี่ให้กับหน้าบ้าน เพราะจริงๆแล้วหน้าบ้านแค่อยากจะได้ title, description และ keyword เท่านั้นเอง&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type SearchResponse struct {
    Url         string `json:"url"`
    Title       string `json:"title"`
    Description string `json:"description"`
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;หน้าบ้านอยากได้ของที่มีหน้าตาแบบนี้ไว้สำหรับแสดงให้ user งั้นเราจะมาเพิ่มอีกหนึ่งฟังค์ชั่น&lt;br&gt;
สำหรับการ convert ตัว espapi.response ไปเป็น SearchResponse&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func convertResponse(res *esapi.Response) ([]SearchResponse, error) {
    searchResults := make([]SearchResponse, 0)

    var esapiResponse struct {
        Hits struct {
            Hits []struct {
                Source SearchResponse `json:"_source"`
            } `json:"hits"`
        } `json:"hits"`
    }
    if err := json.NewDecoder(res.Body).Decode(&amp;amp;esapiResponse); err != nil {
        log.Fatalf("Error Decoder response: %s", err)
    }

    for _, hit := range esapiResponse.Hits.Hits {
        result := SearchResponse{
            Url:         hit.Source.Url,
            Title:       hit.Source.Title,
            Description: hit.Source.Description,
        }

        searchResults = append(searchResults, result)
    }

    return searchResults, nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;โดยจะมีหน้าตาแบบนี้ จริงๆแล้วฟังค์ชั่นนี้ก็ไม่มีอะไรมากแค่รับตัว response ที่มี type เป็น esapi.response &lt;br&gt;
แล้วทำการ loop เอา data ที่อยู่ใน Hits มา convert ให้เป็น SearchResponse แบบที่เราต้องการแล้วคืนของออกไป&lt;br&gt;
ก็น่าจะประมาณนี้สำหรับ การทำ search service api ด้วย Logstash และ Elasticsearch ของเรา&lt;/p&gt;

&lt;p&gt;สุดท้ายนี้ก็อยากจะขอขอบคุณทุกคนมากๆที่อ่านมาถึงตรงนี้ ถ้าใครพบเห็นขอผิดพลาดใดๆ มีวิธีที่ดีกว่าหรือข้อแนะนำติชมใดๆ&lt;br&gt;
ก็อยากรบกวนให้มาแชร์กันใน comment ได้เลย ยินดีน้อมรับทุกความคิดเห็นครับ &lt;br&gt;
สำหรับ code แบบ full version สามารถเข้าไปดูได้ที่ &lt;a href="https://github.com/Je-Devlop/web_search"&gt;Github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;resources;&lt;br&gt;
&lt;a href="https://www.elastic.co/?ultron=B-Stack-Trials-APJ-PHS&amp;amp;gambit=Stack-Core&amp;amp;blade=adwords-s&amp;amp;hulk=paid&amp;amp;Device=c&amp;amp;thor=elastic&amp;amp;gclid=Cj0KCQjwkqSlBhDaARIsAFJANkgleWZ02MukvIDxA9CTSlsEmyI7F0ZtI1-mnRK9bPeGzrrx-SvWSeUaAoKBEALw_wcB"&gt;1&lt;/a&gt; &lt;a href="https://medium.com/insightera/%E0%B9%80%E0%B8%A3%E0%B8%B4%E0%B9%88%E0%B8%A1%E0%B8%95%E0%B9%89%E0%B8%99%E0%B8%81%E0%B8%B2%E0%B8%A3%E0%B8%88%E0%B8%B1%E0%B8%94%E0%B9%80%E0%B8%81%E0%B9%87%E0%B8%9A%E0%B8%82%E0%B9%89%E0%B8%AD%E0%B8%A1%E0%B8%B9%E0%B8%A5%E0%B8%82%E0%B8%99%E0%B8%B2%E0%B8%94%E0%B9%83%E0%B8%AB%E0%B8%8D%E0%B9%88%E0%B8%94%E0%B9%89%E0%B8%A7%E0%B8%A2-elasticsearch-4d70dbe6a79d"&gt;2&lt;/a&gt; &lt;a href="https://www.elastic.co/logstash/"&gt;3&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
