๐งจ 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์ ์ ์ฉํ ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํํ์ ์ธ๋ฑ์ค ์์ฑ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
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๋ ๋ ๋ฒ์ ์ฒดํฌ๋ถํฐ!! |