๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ–ฅ๏ธ ์ตœ์ข…ํ”„๋กœ์ ํŠธ/๐Ÿ› ๏ธ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

[ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…] @Setting์„ ์ผ๋Š”๋ฐ ์™œ ์ธ๋ฑ์Šค๊ฐ€ ์•ˆ ๋ ๊นŒ?

by carrot0911 2025. 4. 2.

๐Ÿงจ Elasticsearch ์ธ๋ฑ์Šค ์„ค์ •์ด ์•ˆ ๋œ๋‹ค?! @Setting ์‹คํŒจ ๊ธฐ๋ก

 

๐Ÿ’ฅ๋ฌธ์ œ ์ƒํ™ฉ

Spring Boot + Spring Data Elasticsearch ํ™˜๊ฒฝ์—์„œ
Elasticsearch์—์„œ ๊ฒ€์ƒ‰ ์„ฑ๋Šฅ ๊ฐœ์„ ์„ ์œ„ํ•ด n-gram, edge-ngram ๊ธฐ๋ฐ˜ ์ธ๋ฑ์Šค ์„ค์ •์„ ์ ์šฉํ•˜๋ ค๊ณ  ํ–ˆ๋‹ค.

์•„๋ž˜์™€ ๊ฐ™์ด job-opening-settings.json์„ ์ž‘์„ฑํ•˜๊ณ ,
Spring Data Elasticsearch์˜ @Setting ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ์ž๋™์œผ๋กœ ์ธ๋ฑ์Šค๋ฅผ ์ƒ์„ฑํ•˜๋ ค๊ณ  ํ–ˆ๋‹ค.

๋”๋ณด๊ธฐ

 job-opening-settings.json

{
  "settings": {
    "analysis": {
      "filter": {
        "ngram_filter": {
          "type": "ngram",
          "min_gram": 2,
          "max_gram": 3
        },
        "edge_ngram_filter": {
          "type": "edge_ngram",
          "min_gram": 1,
          "max_gram": 25
        }
      },
      "tokenizer": {
        "ngram_tokenizer": {
          "type": "ngram",
          "min_gram": 2,
          "max_gram": 3
        }
      },
      "analyzer": {
        "ngram_analyzer": {
          "type": "custom",
          "tokenizer": "ngram_tokenizer",
          "filter": ["lowercase", "ngram_filter"]
        },
        "edge_ngram_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "edge_ngram_filter"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "fields": {
          "ngram": {
            "type": "text",
            "analyzer": "ngram_analyzer"
          },
          "autocomplete": {
            "type": "text",
            "analyzer": "edge_ngram_analyzer"
          }
        }
      }
    }
  }
}

 

JobOpeningDocument์— @Setting ์–ด๋…ธํ…Œ์ด์…˜ ์ถ”๊ฐ€

JobOpeningDocument์— ์œ„ ์„ค์ •์„ ์—ฐ๊ฒฐํ•ด์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ์‹œ ์ž๋™์œผ๋กœ ์ธ๋ฑ์Šค๊ฐ€ ์ƒ์„ฑ๋˜๋„๋ก ๊ตฌ์„ฑํ–ˆ๋‹ค.

ํ•˜์ง€๋งŒ.. ์ธ๋ฑ์‹ฑ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค!

@Setting์„ ์ ์šฉํ•œ ํ›„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜์ž ์ธ๋ฑ์Šค ์ƒ์„ฑ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

Caused by: co.elastic.clients.elasticsearch._types.ElasticsearchException: 
[es/indices.create] failed: 
[illegal_argument_exception] 
unknown setting [index.settings.analysis.analyzer.ngram_analyzer.filter] 
please check that any required plugins are installed, or check the breaking changes documentation for removed settings

ํ•ด๋‹น ๋ถ„์„๊ธฐ์˜ ์„ค์ •์„ ์ •์ƒ์ ์œผ๋กœ ์ธ์‹ํ•˜์ง€ ๋ชปํ–ˆ๋‹ค...

 

 

๐Ÿ” ์‹œ๋„ํ•œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

โœ… JSON ์„ค์ • ๋ฌธ๋ฒ• ํ™•์ธ
     ๐Ÿ‘‰ key-value ๊ตฌ์กฐ, ์‰ผํ‘œ ๋ˆ„๋ฝ ์—ฌ๋ถ€ ๋“ฑ ์ „๋ฐ˜์ ์ธ ๋ฌธ๋ฒ• ์˜ค๋ฅ˜ ํ™•์ธ

โœ… filter, tokenizer, analyzer ์ˆœ์„œ ๋ฐ”๊พธ๊ธฐ
     ๐Ÿ‘‰ ํ˜น์‹œ ์ˆœ์„œ ๋ฌธ์ œ๋กœ ์ธ์‹๋˜์ง€ ์•Š์„๊นŒ ์‹ถ์–ด์„œ ๊ตฌ์กฐ๋ฅผ ์žฌ๋ฐฐ์น˜

โœ… analyzer ์ด๋ฆ„ ๋ณ€๊ฒฝํ•ด ๋ณด๊ธฐ
     ๐Ÿ‘‰
ngram_analyzer → custom_ngram_analyzer ๋“ฑ์œผ๋กœ ์ด๋ฆ„ ๋ณ€๊ฒฝ

โœ… analyzer ์„ค์ •์„ ์ตœ์†Œ ๊ตฌ์„ฑ์œผ๋กœ ์ค„์—ฌ๋ณด๊ธฐ
     ๐Ÿ‘‰ tokenizer๋งŒ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ filter ์ผ๋ถ€ ์ œ๊ฑฐ ๋“ฑ

โœ… Spring ๋ฒ„์ „ ํ™•์ธ
     ๐Ÿ‘‰ ์˜์กด์„ฑ ๋ฒ„์ „ ์ถฉ๋Œ์ด๋‚˜ ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ ํ™•์ธ

์—ฌ๋Ÿฌ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ๋ชจ๋‘ ์‹œ๋„ํ–ˆ์ง€๋งŒ.. ์—๋Ÿฌ๊ฐ€ ํ•ด๊ฒฐ๋˜์ง€ ์•Š๊ณ  ๊ณ„์†ํ•ด์„œ ๊ฐ™์€ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋ฐ˜๋ณต๋˜์—ˆ๋‹ค...

 

 

๐Ÿงช ์›์ธ ๋ถ„์„

์—ฌ๋Ÿฌ ์‚ฝ์งˆ๊ณผ ํ•จ๊ป˜
๋ธ”๋กœ๊ทธ, GitHub ์ด์Šˆ, ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐพ์•„๋ณธ ๊ฒฐ๊ณผ..

๐ŸŽฏ ๊ฒฐ๋ก : Elasticsearch์˜ ์ปค์Šคํ…€ ๋ถ„์„๊ธฐ ์„ค์ •์„ @Setting์œผ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์—†๋‹ค!

Spring Data Elasticsearch์˜ @Setting(settingPath = "...")๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ REST API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ธ๋ฑ์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š”๋ฐ, ์ด๋•Œ ์ „๋‹ฌ๋˜๋Š” JSON์€ ๋‹จ์ˆœํ•œ index.settings ์ˆ˜์ค€๊นŒ์ง€๋งŒ ์•ˆ์ •์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค.

ํ•˜์ง€๋งŒ analysis.filter, analysis.tokenizer, analysis.analyzer์ฒ˜๋Ÿผ ๋ณต์žกํ•œ ์ปค์Šคํ…€ ๋ถ„์„๊ธฐ ๊ตฌ์„ฑ์€ ํด๋ผ์ด์–ธํŠธ ๋‚ด๋ถ€์—์„œ ์ œ๋Œ€๋กœ ์ง๋ ฌํ™”๋˜์ง€ ์•Š๊ฑฐ๋‚˜, Elasticsearch์—์„œ unknown setting ์˜ค๋ฅ˜๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค.

๐Ÿ“Œ ๊ณต์‹ ๋ฌธ์„œ์—์„œ๋Š” ์ด๋ฅผ ๋ช…์‹œํ•˜์ง„ ์•Š์ง€๋งŒ, ์‹ค๋ฌด์—์„œ @Setting์œผ๋กœ ๋ณต์žกํ•œ analysis ๊ตฌ์„ฑ์ด ์ ์šฉ๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งค์šฐ ํ”ํ•˜๋ฉฐ, ์ด๋Š” Spring Data Elasticsearch GitHub ์ด์Šˆ์—์„œ๋„ ๋‹ค์ˆ˜ ๋ณด๊ณ ๋˜์—ˆ๋‹ค.

์ถ”๊ฐ€ ์ฐธ๊ณ  ์‚ฌํ•ญ

  • Elasticsearch๋Š” ๋ฒ„์ „์ด ์˜ฌ๋ผ๊ฐˆ์ˆ˜๋ก analysis ์„ค์ •์— ๋ฏผ๊ฐํ•˜๋‹ค!
  • ์ถ”๊ฐ€ ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ํ•„์š”ํ•œ ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค!
    ngram, edge-ngram์€ ๋ชจ๋‘ Elasticsearch ๊ธฐ๋ณธ ๋‚ด์žฅ analyzer ๊ตฌ์„ฑ ์š”์†Œ์ด๋‹ค.

 

 

๐Ÿงญ ์ตœ์ข… ํ•ด๊ฒฐ

๊ณ ๋ฏผ ๋์—..

@Setting๋ฅผ ํ™œ์šฉํ•œ ์ž๋™ ์ธ๋ฑ์Šค ์ƒ์„ฑ์€ ํฌ๊ธฐํ•˜๊ณ , Kibana Dev Tools ์ฝ˜์†”์„ ํ™œ์šฉํ•ด์„œ ์ธ๋ฑ์Šค๋ฅผ ์ˆ˜๋™ ์ƒ์„ฑํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค!

ํ˜„์žฌ ์ ์šฉํ•œ ๊ตฌ์กฐ

  • Spring ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ @Document(indexName = "job-opening")๋งŒ ์œ ์ง€ํ•œ๋‹ค.
  • ๊ธฐ์กด์— @Setting์€ ์ œ๊ฑฐํ•œ๋‹ค.
  • ์ธ๋ฑ์Šค๋Š” ๋ฐฐํฌ ์ „์— Kibana์—์„œ ๋ฏธ๋ฆฌ ์ˆ˜๋™ ์ƒ์„ฑํ•œ๋‹ค.

Kibana์—์„œ ์ธ๋ฑ์Šค ์ˆ˜๋™ ์ƒ์„ฑ

๋”๋ณด๊ธฐ
PUT job-opening
{
  "settings": {
    "analysis": {
      "filter": {
        "edge_ngram_filter": {
          "type": "edge_ngram",
          "min_gram": 1,
          "max_gram": 25
        },
        "ngram_filter": {
          "type": "ngram",
          "min_gram": 2,
          "max_gram": 3
        }
      },
      "analyzer": {
        "nori_analyzer": {
          "type": "custom",
          "tokenizer": "nori_tokenizer",
          "filter": ["lowercase"]
        },
        "autocomplete_analyzer": { 
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "edge_ngram_filter"]
        },
        "ngram_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "ngram_filter"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "_class": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "company": { 
        "type": "text",
        "fields": {
          "morph": {
            "type": "text",
            "analyzer": "nori_analyzer"
          },
          "autocomplete": {
            "type": "text",
            "analyzer": "autocomplete_analyzer"
          }
        }
      },
      "createdAt": {
        "type": "date",
        "format": "date_optional_time||epoch_millis"
      },
      "educationLevel": {
        "type": "keyword"
      },
      "employmentType": {
        "type": "keyword"
      },
      "hiringEndAt": {
        "type": "date",
        "format": "date_optional_time||epoch_millis"
      },
      "hiringStartAt": {
        "type": "date",
        "format": "date_optional_time||epoch_millis"
      },
      "id": {
        "type": "keyword"
      },
      "jobOpeningUrl": {
        "type": "text"
      },
      "location": {
        "type": "keyword"
      },
      "maxExperienceYears": {
        "type": "integer"
      },
      "minExperienceYears": {
        "type": "integer"
      },
      "position": {
        "type": "text"
      },
      "requiredSkills": {  
        "type": "text",
        "fields": {
          "ngram": {
            "type": "text",
            "analyzer": "ngram_analyzer"
          },
          "autocomplete": {
            "type": "text",
            "analyzer": "autocomplete_analyzer"
          }
        }
      },
      "salary": {
        "type": "integer"
      },
      "title": {  
        "type": "text",
        "fields": {
          "morph": {
            "type": "text",
            "analyzer": "nori_analyzer"
          },
          "autocomplete": {
            "type": "text",
            "analyzer": "autocomplete_analyzer"
          }
        }
      },
      "viewCount": {
        "type": "integer"
      }
    }
  }
}

 

๐Ÿ“Œ ํšŒ๊ณ : ์™œ ์ด๊ฒŒ ์ค‘์š”ํ•œ๊ฐ€?

  • Elasticsearch๋Š” ๋ฒ„์ „ ๊ฐ„ ๋ณ€ํ™”๊ฐ€ ์ •๋ง ๋งŽ๋‹ค.
    ์„ค์ • ๋ฌธ๋ฒ•, ๊ธฐ๋Šฅ ์ง€์› ์—ฌ๋ถ€, ํ”Œ๋Ÿฌ๊ทธ์ธ ์—ฌ๋ถ€ ๋“ฑ์ด ์ž์ฃผ ๋ฐ”๋€๋‹ค.
  • Spring Data Elasticsearch๋Š” ์ž๋™ํ™”๊ฐ€ ์ข‹์•„ ๋ณด์ด์ง€๋งŒ, ์‹ค์ œ๋กœ๋Š” ๋ณต์žกํ•œ ์„ค์ •์ผ์ˆ˜๋ก ์ˆ˜๋™์ด ํ›จ์”ฌ ์•ˆ์ •์ ์ด๋‹ค.
  • ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๊ฐ€ ํ•ญ์ƒ ์ •ํ™•ํ•œ ์›์ธ์„ ์•Œ๋ ค์ฃผ์ง€๋Š” ์•Š๋Š”๋‹ค..
    "unknown setting"๋งŒ ๋ณด๊ณ ๋Š” ๊ตฌ์กฐ์  ์ œํ•œ์ธ์ง€, ๋ฌธ๋ฒ• ์˜ค๋ฅ˜์ธ์ง€ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ต๋‹ค..

๊ทธ๋ž˜์„œ ๊ฒฐ๊ตญ์—”.. ์‚ฝ์งˆ์€ ๊ฑฐ์˜ ์˜ˆ์™ธ ์—†์ด ํ•„์š”ํ•˜๋‹ค ๐Ÿ˜‡

 

๐Ÿ’ก Elasticsearch๋Š” '๋œ๋‹ค'๊ณ  ํ•ด๋„ '๋ฏฟ์ง€ ๋ง๊ณ  ํ™•์ธํ•˜์ž.

 

 

โœ… ์ตœ์ข… ์ •๋ฆฌ

ํ•ญ๋ชฉ ๋‚ด์šฉ
๋ฌธ์ œ @Setting์œผ๋กœ ์ธ๋ฑ์Šค ์ž๋™ ์ƒ์„ฑ ์‹คํŒจ
์›์ธ Elasticsearch ๋ฒ„์ „ ํ˜ธํ™˜์„ฑ + ์„ค์ • ๊ตฌ์กฐ ์ฐจ์ด
์‹œ๋„ ๋‹ค์–‘ํ•œ ์„ค์ • ๋ณ€๊ฒฝ, ๊ตฌ์กฐ ์ˆ˜์ •
ํ•ด๊ฒฐ Kibana์—์„œ ์ˆ˜๋™ ์ธ๋ฑ์Šค ์ƒ์„ฑ
๊ตํ›ˆ Elasticsearch๋Š” ๋Š˜ ๋ฒ„์ „ ์ฒดํฌ๋ถ€ํ„ฐ!!