elasticsearch@7.12의 index 생성에 대해 curl로 간단히 정리해보자. 이 때 노리 형태소 분석기와 토큰을 설정해줄 것이다.
새로운 인덱스를 생성하는 방법이다. 인덱스에는 기본적으로 settings, mappings, alias가 있을 수 있는데, 이번테스트에는 settings 속성만 작성해보자.
settings 속성은 test_setting.json에 미리작성하고 해당 json파일은 index 생성 명령하는 곳에 위치하자.
# test_settings.json
{
"settings": {
"analysis": {
"analyzer": {
"broccoli_nori_none": {
"tokenizer": "nori_none",
"type": "custom"
},
"broccoli_nori_discard": {
"tokenizer": "nori_discard",
"type": "custom"
},
"broccoli_nori_mixed": {
"tokenizer": "nori_mixed",
"type": "custom"
}
},
"tokenizer": {
"nori_none": {
"type": "nori_tokenizer",
"decompound_mode": "none"
},
"nori_discard": {
"type": "nori_tokenizer",
"decompound_mode": "discard"
},
"nori_mixed": {
"type": "nori_tokenizer",
"decompound_mode": "mixed"
}
}
}
}
}
# test_v1.0.0라는 index 생성하기
curl -XPUT localhost:9200/test_v1.0.0?pretty=true -d @test_setting.json -H "Content-Type:application/json"
# test_v1.0.0 index 삭제하기
curl -XDELETE localhost:9200/test_v1.0.0
elasticsearch
단일 document별로 고유한 url을 갖는다.
구조는 아래의 형식이다.
http://<host>:<port>/<index>/_doc/<doc_id>
첫번째 데이터를 생성해보자.
curl -X PUT localhost:9200/test_v1.0.0/_doc/1 -H "Content-Type:application/json" -d '{"text": "지리산남악제 및 군민의 날"}'
# curl localhost:9200/test_v1.0.0/_search?pretty=true
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"text" : "지리산남악제 및 군민의 날"
}
}
]
}
}
1번째 document를 가지고와 보자.
curl localhost:9200/test_v1.0.0/_doc/1?pretty=true
# result
# 결과 있을때
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"text" : "지리산남악제 및 군민의 날"
}
}
# 결과 없을때
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "1",
"found" : false
}
POST를 이용한 DOCUMENT의 생성은 id 입력이 필요없다.
curl -X POST localhost:9200/test_v1.0.0/_doc?pretty=true -H "Content-Type:application/json" -d '{"text": "지리산노고지리설악산 및 축제의 날"}'
# result // POST시에는 새로운 id가 자동으로 생성된다.
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "L1ooonkBQLgNyk-EH9Eb",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 8,
"_primary_term" : 1
}
# doc 조회: curl localhost:9200/test_v1.0.0/_search?pretty=true
{
"took" : 742,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "L1ooonkBQLgNyk-EH9Eb",
"_score" : 1.0,
"_source" : {
"text" : "지리산노고지리설악산 및 축제의 날"
}
},
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"text" : "지리산남악제 및 군민의 날"
}
}
]
}
}
document가 처음과 달리 2개로 늘어난것을 확인 할 수 있다.
동일 id로 put 요청을 하게되면 데이터는 updated 된다.
curl -X PUT localhost:9200/test_v1.0.0/_doc/1?pretty=true -H "Content-Type:application/json" -d '{"text": "지리산노고지리설악산 및 축제의 날", "author": "한잔햐"}'
# result // PUT시에는 데이터가 엎어쳐진다.
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "1",
"_version" : 8,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 10,
"_primary_term" : 1
}
# doc 조회: curl localhost:9200/test_v1.0.0/_search?pretty=true
{
"took" : 587,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "L1ooonkBQLgNyk-EH9Eb",
"_score" : 1.0,
"_source" : {
"text" : "지리산노고지리설악산 및 축제의 날"
}
},
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"text" : "지리산노고지리설악산 및 축제의 날",
"author" : "한잔햐"
}
}
]
}
}
위와 같은 경우 뜻하지 않게 데이터을 엎어칠 수 있으므로 아래와 같은 방법을 통해 해당 id가 없는 경우에만 PUT입력이 되도록 할 수 있다. 만약 1에 해당하는 id가 있다면 동작하지 않고 없으면 입력된다.
curl -X PUT localhost:9200/test_v1.0.0/_create/1?pretty=true -H "Content-Type:application/json" -d '{"text": "가을설악산행 낭만의 날", "author": "두잔햐"}'
# result // 이미 id 1이 있기 때문에 실행되지 않는다.
{
"error" : {
"root_cause" : [
{
"type" : "version_conflict_engine_exception",
"reason" : "[1]: version conflict, document already exists (current version [8])",
"index_uuid" : "h6HTOFhyR-yDBNM0sLgoaw",
"shard" : "0",
"index" : "test_v1.0.0"
}
],
"type" : "version_conflict_engine_exception",
"reason" : "[1]: version conflict, document already exists (current version [8])",
"index_uuid" : "h6HTOFhyR-yDBNM0sLgoaw",
"shard" : "0",
"index" : "test_v1.0.0"
},
"status" : 409
}
만약 id를 2로 실행 할 때는 없는 아이디이기 때문에 id 2의 doc이 추가된다.
# doc 조회: curl localhost:9200/test_v1.0.0/_search?pretty=true
{
"took" : 582,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"text" : "지리산노고지리설악산 및 축제의 날",
"author" : "한잔햐"
}
},
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "L1ooonkBQLgNyk-EH9Eb",
"_score" : 1.0,
"_source" : {
"text" : "지리산노고지리설악산 및 축제의 날"
}
},
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"text" : "가을설악산행 낭만의 날",
"author" : "두잔햐"
}
}
]
}
}
put 명령어를 통해 동일 id를 수정할수는 있으나 그런경우 모든 data가 엎어쳐진다. 원하는 필드만 변경하고 싶을때는 _update
명령어를 사용한다.
curl -X POST localhost:9200/test_v1.0.0/_update/L1ooonkBQLgNyk-EH9Eb?pretty=true -H "Content-Type:application/json" -d '{"doc": {"author": "세잔햐"}}'
# result
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "L1ooonkBQLgNyk-EH9Eb",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 12,
"_primary_term" : 1
}
# doc 조회: curl localhost:9200/test_v1.0.0/_search?pretty=true
{
"took" : 975,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"text" : "지리산노고지리설악산 및 축제의 날",
"author" : "한잔햐"
}
},
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"text" : "가을설악산행 낭만의 날",
"author" : "두잔햐"
}
},
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "L1ooonkBQLgNyk-EH9Eb",
"_score" : 1.0,
"_source" : {
"text" : "지리산노고지리설악산 및 축제의 날",
"author" : "세잔햐"
}
}
]
}
}
1번째 DOCUMENT를 삭제해보자
curl -XDELETE localhost:9200/test_v1.0.0/_doc/1?pretty=true
# result
{
"_index" : "test_v1.0.0",
"_type" : "_doc",
"_id" : "1",
"_version" : 2,
"result" : "deleted",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
index는 생성후에 직접 수정하지 않고 아래 절차에 따라 변경이 된다.
이미 test_v1.0.0 인덱스가 있으니 test_v1.0.1라는 인덱스를 가지고 위에 순서대로 진행해보자.
test_v1.0.0이 있으니 test_v1.0.1의 index를 만들자.
curl -XPUT localhost:9200/test_v1.0.1?pretty=true -d @test_setting.json -H "Content-Type:application/json"
test_v1.0.0이 있으니 test_v1.0.1의 index를 만들자.
curl -XPOST localhost:9200/_reindex?pretty=true -d '{"source":{"index": "test_v1.0.0"}, "dest":{"index":"test_v1.0.1"}}' -H "Content-Type:application/json
#result
{
"took" : 74,
"timed_out" : false,
"total" : 3,
"updated" : 0,
"created" : 3,
"deleted" : 0,
"batches" : 1,
"version_conflicts" : 0,
"noops" : 0,
"retries" : {
"bulk" : 0,
"search" : 0
},
"throttled_millis" : 0,
"requests_per_second" : -1.0,
"throttled_until_millis" : 0,
"failures" : [ ]
}
# curl localhost:9200/test_v1.0.1/_search?pretty=true
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "test_v1.0.1",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"text" : "지리산노고지리설악산 및 축제의 날",
"author" : "한잔햐"
}
},
{
"_index" : "test_v1.0.1",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"text" : "가을설악산행 낭만의 날",
"author" : "두잔햐"
}
},
{
"_index" : "test_v1.0.1",
"_type" : "_doc",
"_id" : "L1ooonkBQLgNyk-EH9Eb",
"_score" : 1.0,
"_source" : {
"text" : "지리산노고지리설악산 및 축제의 날",
"author" : "세잔햐"
}
}
]
}
}
그대로 데이터가 복사됬음을 확인할 수 있다.
알리아스를 지정하면 index의 고유명칭으로 조회할 필요없이 알리아스로도 조회가 가능하다. 설정방법은 아래와 같다.
#aliases.json
{
"actions": [
{
"remove": {
"alias":"test",
"index":"test_v1.0.0"
}
},
{
"add": {
"alias":"test",
"index":"test_v1.0.1"
}
}
]
}
# 설정명령어
curl -XPOST localhost:9200/_aliases?pretty=true -H "Content-Type:application/json" -d @aliases.json
# curl localhost:9200/test
{
"test_v1.0.1" : {
"aliases" : {
"test" : { }
},
"mappings" : {
"properties" : {
"author" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"text" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
},
"settings" : {
"index" : {
"routing" : {
"allocation" : {
"include" : {
"_tier_preference" : "data_content"
}
}
},
"number_of_shards" : "1",
"provided_name" : "test_v1.0.1",
"creation_date" : "1621924950539",
"analysis" : {
"analyzer" : {
"broccoli_nori_discard" : {
"type" : "custom",
"tokenizer" : "nori_discard"
},
"broccoli_nori_mixed" : {
"type" : "custom",
"tokenizer" : "nori_mixed"
},
"broccoli_nori_none" : {
"type" : "custom",
"tokenizer" : "nori_none"
}
},
"tokenizer" : {
"nori_discard" : {
"type" : "nori_tokenizer",
"decompound_mode" : "discard"
},
"nori_mixed" : {
"type" : "nori_tokenizer",
"decompound_mode" : "mixed"
},
"nori_none" : {
"type" : "nori_tokenizer",
"decompound_mode" : "none"
}
}
},
"number_of_replicas" : "1",
"uuid" : "mrF6fe2ZTkag_abG-tUbqA",
"version" : {
"created" : "7120199"
}
}
}
}
}
기존 index를 삭제하면 된다.
curl -XDELETE localhost:9200/test_v1.0.0?pretty=true
드디어 대장정의 막...😢😢
elasticsearch를 내가 사용한 이유는 검색을 활용하고 싶어서인데, 한국어로 검색을 활용하기 위해서는 반드시 한국어 형태소 플러그인을 설치해야한다. 공식 한국어 형태소 플로그인은 nori
라고한다.
😭😭 만약 한국어 형태소 분석 플러그인을 사용하지 않는다면 띄어쓰기 단위로만 되어있는 한국어만 검색가능하고 붙어있는 단어별로의 형태소별로의 검색은 할 수 없다.
# /usr/share/elasticsearch 에서
# nori 설치
sudo bin/elasticsearch-plugin install analysis-nori
# nori 제거
sudo bin/elasticsearch-plugin remove analysis-nori
test_v1.0.1 index를 만들때 이미 analysis를 같이 넣어서 만들었다. 맨 위 1. index 생성, 삭제 방법 참조
만약 index에 새로운 field를 넣는것은 상관없지만 이미 있는 field에 analysis를 넣고 싶다면
- 새로운버전의 인덱스에 settings에 analysis를 만들어 생성하고
- _reindex를 통해 데이터를 옮기는 과정을 통해 적용해야 한다.
#curl localhost:9200/test
# test index
{
"test_v1.0.1" : {
"aliases" : {
"test" : { }
},
"mappings" : {
"properties" : {
"author" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"text" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
},
"settings" : {
"index" : {
"routing" : {
"allocation" : {
"include" : {
"_tier_preference" : "data_content"
}
}
},
"number_of_shards" : "1",
"provided_name" : "test_v1.0.1",
"creation_date" : "1621924950539",
"analysis" : {
"analyzer" : {
"broccoli_nori_discard" : {
"type" : "custom",
"tokenizer" : "nori_discard"
},
"broccoli_nori_mixed" : {
"type" : "custom",
"tokenizer" : "nori_mixed"
},
"broccoli_nori_none" : {
"type" : "custom",
"tokenizer" : "nori_none"
}
},
"tokenizer" : {
"nori_discard" : {
"type" : "nori_tokenizer",
"decompound_mode" : "discard"
},
"nori_mixed" : {
"type" : "nori_tokenizer",
"decompound_mode" : "mixed"
},
"nori_none" : {
"type" : "nori_tokenizer",
"decompound_mode" : "none"
}
}
},
"number_of_replicas" : "1",
"uuid" : "mrF6fe2ZTkag_abG-tUbqA",
"version" : {
"created" : "7120199"
}
}
}
}
}
settings 부분에 이미 analysis가 있는 것을 확인 할 수 있다.
하지만 현재는 검색시 분석기가 동작하지 않는다.
왜냐하면 mapping에 anayisis가 없기 때문이다.
그럼 새로운 index를 만들어서 _reindex를 해보자.
아래 새로만들 index의 설정파일을 보면 mappings에 analyzer들이 있는 것을 볼 수 있다.
아래 설정파일은 기존 인덱스을 카피해서 수정한 파일임.
curl localhost:9200/test?pretty > mapping.json
- 이후 카피한 json을 바로 쓰면 에러가 발생하는데 자동생성되는 속성들을 에러메세지를 보면서 제거해서 수정하면 된다.
# 수정된 mapping.json
{
"mappings": {
"properties": {
"author": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"analyzer": "broccoli_nori_discard"
},
"text": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"analyzer": "broccoli_nori_discard"
}
}
},
"settings": {
"index": {
"routing": {
"allocation": {
"include": {
"_tier_preference": "data_content"
}
}
},
"number_of_shards": "1",
"analysis": {
"analyzer": {
"broccoli_nori_discard": {
"type": "custom",
"tokenizer": "nori_discard"
},
"broccoli_nori_mixed": {
"type": "custom",
"tokenizer": "nori_mixed"
},
"broccoli_nori_none": {
"type": "custom",
"tokenizer": "nori_none"
}
},
"tokenizer": {
"nori_discard": {
"type": "nori_tokenizer",
"decompound_mode": "discard"
},
"nori_mixed": {
"type": "nori_tokenizer",
"decompound_mode": "mixed"
},
"nori_none": {
"type": "nori_tokenizer",
"decompound_mode": "none"
}
}
},
"number_of_replicas": "1"
}
}
}
aliases.json은 버전을 변경해준다.
# aliases.json
{
"actions": [
{
"remove": {
"alias":"test",
"index":"test_v1.0.1"
}
},
{
"add": {
"alias":"test",
"index":"test_v1.0.2"
}
}
]
}
# 새인덱스 만들기
curl -XPUT localhost:9200/test_v1.0.2?pretty -d @mapping.json -H "Content-Type:application/json"
# _reindex
curl -XPOST localhost:9200/_reindex?pretty -d '{"source":{"index":"test_v1.0.1"}, "dest":{"index":"test_v1.0.2"}}' -H "Content-Type:application/json"
# _aliases
curl -XPOST localhost:9200/_aliases?pretty=true -H "Content-Type:application/json" -d @aliases.json
아래 curl명령어는 url을 인코딩해서 보내는 방식이다.
curl -G --data-urlencode "q=지리산" localhost:9200/test/_search?pretty
tokenizer를 어떤 걸로 설정했느냐에 따라 토큰이 다음과 같이 나뉘어 검색하게 된다.
# decompound_mode: mixed
# nori_mixed.json
{
"analyzer": "broccoli_nori_mixed",
"text": "한잔햐"
}
# 명령어
curl localhost:9200/broccoli/_analyze?pretty -d @nori_mixed.json -H "Content-Type:application/json"
# 결과
{
"tokens" : [
{
"token" : "한잔",
"start_offset" : 0,
"end_offset" : 2,
"type" : "word",
"position" : 0,
"positionLength" : 2
},
{
"token" : "한",
"start_offset" : 0,
"end_offset" : 1,
"type" : "word",
"position" : 0
},
{
"token" : "잔",
"start_offset" : 1,
"end_offset" : 2,
"type" : "word",
"position" : 1
},
{
"token" : "햐",
"start_offset" : 2,
"end_offset" : 3,
"type" : "word",
"position" : 2
}
]
}
# decompound_mode: discard
# nori_discard.json
{
"analyzer": "broccoli_nori_discard",
"text": "한잔햐"
}
# 결과
{
"tokens" : [
{
"token" : "한",
"start_offset" : 0,
"end_offset" : 1,
"type" : "word",
"position" : 0
},
{
"token" : "잔",
"start_offset" : 1,
"end_offset" : 2,
"type" : "word",
"position" : 1
},
{
"token" : "햐",
"start_offset" : 2,
"end_offset" : 3,
"type" : "word",
"position" : 2
}
]
}
# decompound_mode: none
# nori_none.json
{
"analyzer": "broccoli_nori_none",
"text": "한잔햐"
}
# 결과
{
"tokens" : [
{
"token" : "한잔",
"start_offset" : 0,
"end_offset" : 2,
"type" : "word",
"position" : 0
},
{
"token" : "햐",
"start_offset" : 2,
"end_offset" : 3,
"type" : "word",
"position" : 1
}
]
}