기본 정보
- ElasticSearch는 localhost:9200으로 접근할 수 있다고 가정한다.
- curl 명령을 이용해서 elasticsearch 클러스터 API를 호출할 때 body에 json 메시지를 실어보내야 하는 경우에는 -H 옵션을 이용해서 'Content-Type: application/json'을 헤더로 전달해야 한다.
- ElasticSearch는 동질의 문서들을 저장하는 공간을 index라고 부른다. (DB의 index가 아님) 일종의 DB 테이블이나 MongoDB의 collection과 같은 개념으로 생각하면 된다. 여기서는 test_index라는 index를 사용한다.
- _로 시작하는 자원은 ElasticSearch가 기본적으로 제공하는 API의 종류를 뜻한다. 주로 메타데이터 자원이라고 할 수 있다.
- _doc
- _mapping
- _count
- _search
- bash에서 json 데이터의 일부 필드를 추출하는 방법이 따로 없어서 jq를 설치해서 사용했다. brew나 apt, yum 등의 패키지 매니저를 이용하여 설치해주자.
클러스터 상태 확인
클러스터의 건강 상태를 확인해보자. 다음 API의 응답 중에서 status 필드의 값이 중요하다.
curl localhost:9200/_cluster/health
{
"cluster_name": "opensearch_homebrew",
"status": "green",
"timed_out": false,
"number_of_nodes": 3,
"number_of_data_nodes": 3,
"discovered_master": true,
"discovered_cluster_manager": true,
"active_primary_shards": 0,
"active_shards": 0,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0,
"delayed_unassigned_shards": 0,
"number_of_pending_tasks": 0,
"number_of_in_flight_fetch": 0,
"task_max_waiting_in_queue_millis": 0,
"active_shards_percent_as_number": 100.0
}
기존 인덱스 정보 확인
이미 생성되어 있는 index 목록을 조회하는 것도 필요하다.
curl localhost:9200/_cat/indices
다음과 같이 index별 상태를 확인할 수 있다.
green open test_index -pyXIxp3R4CsT_5yiB11qw 1 3 401 0 23.1kb 23.1kb
yellow open ebooks -pyXQ51ax1ctt_29flzj2m 1 3 598 0 30.5kb 30.5kb
문서 데이터 적재
이제부터는 특정 인덱스 "test_index"에 저장하는 것을 전제로 하겠다.
POST method로 임의의 json 문서를 insert할 수 있다.
curl -s -X POST localhost:9200/test_index/_doc -H "Content-Type: application/json" -d '{"num": 30}'
Bash의 while loop를 이용하여 1000건 insert하는 예제는 다음과 같다.
total_count=1000
i=0
while [ "$i" -lt "$total_count" ]; do
echo -n "."
curl -s -X POST localhost:9200/test_index/_doc -H "Content-Type: application/json" -d '{"num": '"$i"'}' > /dev\
/null
i=$((i+1))
done
_doc API에 접근하여 POST method로 다음과 같은 json 문서 데이터를 insert한다.
{ "num": 1 }
한 건씩 적재하면 성능 상 손해가 생기는데, 이 문제를 해결하는 방법으로 bulk API를 사용하면 된다. (다른 문서 curl을_이용한_ElasticSearch_bulk_API_사용 에서 자세히 다루고 있다.)
이 코드 예제는 0부터 999까지의 num 값을 가지는 문서를 Bash의 while loop를 이용하여 반복해서 insert하고 있다.
문서 건수 확인
curl -s localhost:9200/test_index/_count
_count API를 이용하여 test_index에 저장되어 있는 문서의 갯수를 확인할 수 있다. 그러나 많은 문서를 빠른 속도로 insert한 직후에 바로 count를 조회하면 정확한 값이 조회되지 않는다. 이럴 경우에 _refresh API를 이용하여 문서의 메타데이터/통계를 갱신해줄 필요가 있다.
curl -s localhost:9200/test_index/_refresh
curl -s localhost:9200/test_index/_count
검색 (pagination)
단순히 문서를 조회할 때는 _doc API를 이용해도 되지만
curl -s localhost:9200/test_index/_doc/gwbBCIgBbIjXWaPdciBv
id를 미리 정의해서 등록해두었고 그걸 확인할 수 있을 때만 가능하다.
일반적인 검색은 조건에 일치하는 문서들을 일부 검색한 다음에, pagination을 통해서 조금씩 더 조회하는 절차를 따르는데, ElasticSearch에서는 이 기능을 scroll이라고 한다. 다음과 같이 scroll을 이용할 수 있다.
- 최초 검색하면서 scroll 기능을 사용하겠다고 선언하고 page size를 정한다. 응답으로 scroll id를 받는다.
- 이후의 검색은 그 scroll id를 가지고 반복적으로 호출하면 된다.
- scroll을 이용한 검색의 응답에 대해서 error가 발생했는지, 검색된 결과가 0건보다 많은지를 확인할 필요가 있다.
최초 검색
100건씩 1분짜리 세션을 지정해서 모든 문서를 조회하는 요청은 다음과 같다. 1h로 해도 된다.
curl -s localhost:9200/test_index/_search\?scroll=1m -H 'Content-Type: application/json' -d '{"size": 100, "query": {"match_all": {} } }'
응답에서 scroll id를 얻어야 한다.
curl -s localhost:9200/test_index/_search\?scroll=1m -H 'Content-Type: application/json' -d '{"size": 100, "query": {"match_all": {} } }' | jq -r ._scroll_id
jq 명령을 추가로 설치할 필요가 있다.
scroll id는 FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFmdVRksxd0VMUUhteEl5SWxCSWxhQWcAAAAAAATk3RZDMFVpdWZUQVNhdXRKaVhpTEdRYkVB로 확인되었다.
연속 검색
curl -s localhost:9200/_search/scroll -H 'Content-Type: application/json' -d '{"scroll": "1m", "scroll_id": "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFmdVRksxd0VMUUhteEl5SWxCSWxhQWcAAAAAAATk3RZDMFVpdWZUQVNhdXRKaVhpTEdRYkVB"}'
이 요청의 응답에 대해서 error가 발생하는지, 검색결과 문서 리스트가 비어있진 않은지 확인할 필요가 있다.
Bash의 while loop를 이용해서 반복적으로 검색해보자.
page_size=100
while :; do
result=$(curl -s localhost:9200/_search/scroll -H 'Content-Type: application/json' -d '{"scroll": "1m", "scroll\
_id": "'"$scroll_id"'"}')
if echo "$result" | grep -q "error"; then
echo "error in scrolling"
break;
fi
count=$(echo "$result" | jq -r ".hits.hits | length")
if [ "$count" -gt 0 ]; then
echo -n "."
else
echo; echo "end of searching"
break
fi
done
Troubleshooting
기존 데이터 삭제
curl -s -X DELETE localhost:9200/test_index
기존 scroll 삭제
curl -s -X DELETE localhost:9200/_search/scroll -H 'Content-Type: application/json' -d '{ "scroll_id": "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFmdVRksxd0VMUUhteEl5SWxCSWxhQWcAAAAAAATk3RZDMFVpdWZUQVNhdXRKaVhpTEdRYkVB" }'
완성 버전
#!/bin/bash
total_count=2000
page_size=100
date
echo; echo "removing..."
if curl -s -X DELETE localhost:9200/test_index > /dev/null; then
echo "success in deleting data"
else
echo "error in deleting data"
exit 1
fi
date
echo; echo "loading..."
i=0
while [ "$i" -lt "$total_count" ]; do
echo -n "."
curl -s -X POST localhost:9200/test_index/_doc -H "Content-Type: application/json" -d '{"num": '"$i"'}' > /dev/null
i=$((i+1))
done
echo
echo "$i"
curl -s localhost:9200/test_index/_refresh
curl -s localhost:9200/test_index/_count
date
echo; echo "removing old scroll cursor..."
scroll_id=$(cat scroll_id.txt)
echo "$scroll_id"
curl -s -X DELETE localhost:9200/_search/scroll -H 'Content-Type: application/json' -d '{ "scroll_id": "'"$scroll_id"'" }' | jq .
echo; echo "searching and getting scroll cursor..."
scroll_id=$(curl -s localhost:9200/test_index/_search\?scroll=1m -H 'Content-Type: application/json' -d '{"size": '"$page_size"', "query": {"match_all": {} } }' | jq -r ._scroll_id)
echo "$scroll_id"
echo "$scroll_id" > scroll_id.txt
echo; echo "searching (with pagination)..."
while :; do
result=$(curl -s localhost:9200/_search/scroll -H 'Content-Type: application/json' -d '{"scroll": "1m", "scroll_id": "'"$scroll_id"'"}')
#echo "$result"
if echo "$result" | grep -q "error"; then
echo "error in scrolling"
break;
fi
count=$(echo "$result" | jq -r ".hits.hits | length")
#echo "$count"
if [ "$count" -gt 0 ]; then
echo -n "."
last_result="$result"
else
echo; echo "end of searching"
break
fi
done
echo
echo "$last_result"
date