전차수들에서 엘라스틱 서치를 세팅하고 노리 형태소 분석기도 적용했습니다.
이제 데이터베이스에 있는 값을 엘라스틱 서치와 연동해서 검색해 봅시다.
일반적으로 엘라스틱 서치는 로그스태시를 사용하여 디비와 연동을 하지만, 버전을 맞춰줘야 하고 설정파일도 만져줘야 하는 등 까다롭고 m1에서 호환이 잘되지 않는 단점이 있습니다.
결국엔 로그스태시도 계속 켜놓은 상태로 일정 시간 주기로 스케줄링을 해주는 원리로 돌아가기에 배치서버 만들어서 돌리는 것과 성능에는 별 차이가 없기도 하고, 직접핸들링 하는 게 저는 더 편하기 때문에 로그스태시 같은 배치서버를 직접 만들겠습니다.
1. 서버 세팅
node 서버에 express 프레임워크 세팅해서 진행하겠습니다.
서버세팅법과 crud 부분은 아래의 링크를 참고해 주세요.
https://daily-coding-diary.tistory.com/2
[Node.js] 01. 시작하기(express)
Node.js와 React를 이용하여 나만의 Todolist를 만들어봅시다. Todolist를 이용하면 기본적인 데이터 처리 기능 CRUD(create, read, update, delete)를 구현할 수 있으므로 모든 API 시작과 끝을 맛볼 수 있게 됩니
daily-coding-diary.tistory.com
2. 디비 세팅
mysql에 foods 테이블을 생성하였습니다.
sync 컬럼은 엘라스틱 서치 인덱스와 연동 완료된 경우에는 0, 새롭게 생성해야 하거나 업데이트해야 할 것은 1, 삭제해야 할 데이터의 값을 -1로 저장하겠습니다.
간단한 crud로 테이블에 음식들을 아래와 같이 세팅했습니다.
나중에 조회가 많이 일어나는 경우 인덱싱 설정을 해주면 성능면에서 좋겠죠.
이제 "파스타파스타 2"라고 검색하면 "파스타" 키워드가 들어간 값이 나오게, 또는 "크림 파스타"가 아닌 "파스타크림" 등 키워드가 정확하게 일치하지 않아도 아래 예시처럼 형태소가 분석되어 정확히 일치하는 값뿐만 아니라 관련도가 높은 단어와 구문들도 검색되게 엘라스틱 서치와 연동할 거예요.
3. sync 컬럼 변경사항 반영
우선 api 서버에서 crud가 일어나면 create,update일 경우에는 sync를 1, delete 일 때는 -1로 값을 업데이트해 주면 되겠죠.
app.post('/add', async (req,res) => {
try {
let {name} = req.body;
const rows = await db.foods.create({
name : name,
sync : 1
})
if(!rows) throw "에러";
return res.status(200).json({"result" : "success"})
} catch (error) {
console.log(error)
res.status(200).json({"error" : "에러"})
}
})
4. 배치 서버 작업
이제 배치서버를 생성하여, 일정시간마다(개발 환경에 맞게 유동적으로 조절) 테이블에서 데이터를 조회하여 상태가 1이면 엘라스틱 서치 인덱스에 값을 생성하거나 업데이트시켜 주고,-1이면 인덱스에 있는 값을 삭제하겠습니다.
값을 생성하거나 업데이트하는 경우에는 (이미 인덱스에 있는 id값을 기준으로 새로운 값을 넣으면 값이 업데이트가 되기 때문에 업데이트하는 경우에도 생성할 때와 똑같은 방식으로 넣어주면 됩니다.) 인덱스의 정보와 들어갈 값을 넣어주면 됩니다.
값을 삭제하는 경우에는 삭제할 인덱스의 정보만 넣어주면 됩니다.
for문이 돌때마다 값을 넣거나 지우는 것은 비효율적이니 엘라스틱 서치에서 지원하는 bulk로 처리했습니다.
마지막으로 엘라스틱 서치와 연동하는 작업을 마쳤다면 생성, 수정의 경우에는 sync 컬럼의 값이 0이 되고, 삭제의 경우에는 디비에 있는 데이터를 삭제되게 해주면 간단한 배치서버 만들기는 완료입니다.
// 엘라스틱 서치와 상관없는 다른 모듈 생략
const { Client } = require('@elastic/elasticsearch');
const client = new Client({
// cloud: { id: '<cloud-id>' },
// auth: { apiKey: 'base64EncodedKey' }
node : "http://localhost:9200"
})
const Batch = async () => {
try {
let query = 'select * from foods where sync in(?,?)'
const rows = await sequelize.query(query, {
replacements: ["1","-1"],
type: QueryTypes.SELECT,
});
let update_data = [];
let del_data = [];
let dataset =[];
for(element of rows){
if(element.sync == '1'){
dataset.push({index: {_index: "reallasttest",_type: "_doc", _id: element.idx}},{name: element.name});
update_data.push(element.idx);
}else if(element.sync == '-1'){
dataset.push({delete:{_index:"reallasttest", _id: element.idx}});
del_data.push(element.idx);
}
}
if(dataset.length == 0) return
await client.bulk({
body: dataset
})
await db.foods.update({
sync : "0"
},{
where : { idx : update_data }
})
await db.foods.destroy({ where: { idx: del_data }})
} catch (error) {
console.log(error);
}
}
setInterval(() => {
Batch();
}, 2000);
위의 예시에서는 @elastic/elasticsearch (자바스크립트에서 엘라스틱서치를 사용할 수 있는 모듈)을 사용했으나,
직접 axios로 http 통신 구현하여 바로 요청을 날리셔도 됩니다.
이 방식이 다른 언어까지 고려하면 통일성은 좋긴 하지만 다른 언어들도 각자 모듈로 지원해 주기 때문에 본인 취향대로 작성하시면 됩니다.
5. 결과 확인
이제 다시 api 서버에 돌아와서 유저가 입력한 키워드를 가지고 엘라스틱 서치 인덱스를 조회해 보겠습니다.
app.get('/search', async (req,res) => {
try {
let {keyword} = req.query;
// 검색 전 최신값으로 새로고침
await client.indices.refresh({ index: 'reallasttest' })
// 검색
const result = await client.search({
index: 'reallasttest',
query: {
match: { name: keyword }
}
})
return res.status(200).json(result.hits.hits)
} catch (error) {
console.log(error);
res.status(200).json({"error" : "에러"})
}
})
처음 목표로 했던 대로 결과가 나오네요. (참고로 엘라스틱 서치 서버는 포트가 9200입니다. 엘라스틱 서치에서 제공하는 요청이 아닌 직접 작성한 서버로 요청을 보낸 것이니 헷갈리지 마시길..)
이렇게 나오면 성공입니다.^^ (간단하게 엘라스틱 서치를 익히기 위한 용도로 만든 프로젝트니 자세한 설정이나 옵션은 본인 개발 환경에 맞게 작성하시면 됩니다.)
자세한 코드는 제 깃에서 확인해 주세요 ↓
https://github.com/fkwsur/Basic_Node_Elasticsearch_With_Nori
'Elasticsearch' 카테고리의 다른 글
[Elasticsearch] 02. Elasticsearch 사용하기 (with nori) (0) | 2023.07.25 |
---|---|
[Elasticsearch] 01. 검색엔진 (0) | 2023.07.25 |