아래 링크 후속으로 만든 게시글입니다. ElasticSearch 란 무엇인가 확인하고 싶으면 아래 글을 참고하세요~
https://cwbeany.com/tip-dev/68
블로그에 대한 전반적인 내용을 확인하고 싶으면 아래 게시판 참고
https://cwbeany.com/board/blog_diary
현재 블로그 코드는 icontains 를 이용해서 블로그 글을 조회하고 가져오고 있습니다.
def get_active_filtered_posts(search: str = None,
board_urls: List[str] = None,
tag_names: List[str] = None) -> QuerySet[Post]:
q = Q()
qs = get_active_posts()
if search:
q = q & Q(title__icontains=search) | Q(body__icontains=search)
if board_urls:
q = q & Q(board__url__in=board_urls)
if tag_names:
tag_id_list = Tag.objects.filter(
tag_name__in=tag_names
).values_list(
'id',
flat=True
)
q = q & Q(tag_set__in=tag_id_list)
qs = qs.prefetch_related('tag_set')
return qs.select_related(
'board',
).filter(
q
)
위의 코드를 실행하면 대충 아래와 같은 SQL 이 나옵니다.
SELECT *
FROM "board_post"
INNER JOIN "board_board" ON ("board_post"."board_id" = "board_board"."id")
WHERE ("board_post"."is_active" AND ("board_post"."title" LIKE %Java Python% ESCAPE '\' OR "board_post"."body" LIKE %Java Python% ESCAPE '\'))
LIKE 절을 %% 로 이용하고 있습니다.
이런 경우 조회할 때, Index 를 타지 못하고 오랜 시간이 걸립니다. 물론 많은 게시글이 없으면 문제는 없겠지만 언제든지 문제가 일어날 수 있는 상황입니다.
또한 검색에서 원하는 것을 의도하지 못할 수 있습니다. 예를 들어 제 게시글에 "안녕하세요 오늘은 Java 와 Python 에 대해 알아봅시다!" 라는 문구가 있다고 합시다.
만약 Java Python 을 검색창에 입력하면 저 내용이 있는 게시판을 찾지 못할 것입니다.
정리하자면
이런 경우 ElasticSearch 를 이용하면 검색엔진으로서 아주 좋게 활용할 수 있습니다!
Django 와 ElasticSearch 를 활용하는 방법은 4가지 정도 있는 것 같습니다.
4번은 이용하려고 합니다.
설치 링크: https://www.elastic.co/guide/en/elasticsearch/reference/8.16/deb.html#deb-repo
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
sudo apt-get install apt-transport-https
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.list
sudo apt-get update && sudo apt-get install elasticsearch
# 한국어 조회 가능 플러그인 설치
sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-nori
# -> Installing analysis-nori
# -> Downloading analysis-nori from elastic
# [=================================================] 100%
# -> Installed analysis-nori
# -> Please restart Elasticsearch to activate any plugins installed
sudo systemctl start elasticsearch
sudo systemctl enable elasticsearch
sudo /bin/systemctl daemon-reload
sudo /bin/systemctl enable elasticsearch.service
# 비밀번호 설정
/usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic -i
https://localhost:9200/ 에 들어가면 설정한 비밀번호로 접속하면 잘 접속됩니다!
elasticsearch-dsl 을 사용하기 위해서 설치를 합시다.
pip install django-elasticsearch-dsl
Django 프로젝트의 settings.py
에 django_elasticsearch_dsl
을 추가합니다.
INSTALLED_APPS = [
# 기타 앱
'django_elasticsearch_dsl',
]
Django 설정 파일 (settings.py
)에 다음을 추가합니다:
ElasticSearch 설치 시, http_ca.crt 는 설치 할 때 받을 수 있습니다. 해당 파일을 프로젝트 루트에 놓습니다.
cp /etc/elasticsearch/certs/http_ca.crt 프로젝트_루트/http_ca.crt
cd 프로젝트_루트
# 실행 권한 추가
chmod 644 http_ca.crt
# settings.py
ELASTICSEARCH_DSL = {
'default': {
'hosts': [
{
'host': 'localhost',
'port': 9200,
'use_ssl': True,
'ca_certs': os.path.join(BASE_DIR, 'http_ca.crt'),
}
],
'http_auth': (ELASTICSEARCH_USERNAME, ELASTICSEARCH_PASSWORD),
},
}
이제 Elasticsearch Index 를 생성합시다.
django-elasticsearch-dsl
에서 Elasticsearch 인덱스와 Django 모델의 매핑을 정의하려면 Document
클래스를 사용합니다.
한국어 검색이 잘 되지 않는 이유는 Elasticsearch의 기본 분석기(standard analyzer
)가 영어와 같은 공백 기반 언어에 최적화되어 있기 때문입니다. 한국어와 같은 공백이 아닌 형태소 기반 언어는 적합한 분석기(Analyzer)를 사용해야 검색이 제대로 동작합니다.
Nori
를 사용합니다.
from django_elasticsearch_dsl import (
Document,
fields,
)
from django_elasticsearch_dsl.registries import registry
from board.models import Post
@registry.register_document
class PostDocument(Document):
title = fields.TextField(analyzer="korean_analyzer")
body = fields.TextField(analyzer="korean_analyzer")
post_img = fields.KeywordField()
board = fields.ObjectField(
properties={
"url": fields.TextField(),
}
)
author = fields.ObjectField(
enabled=False, # 색인하지 않음, 조회에서만 사용
properties={
"id": fields.IntegerField(),
"username": fields.KeywordField(),
"nickname": fields.KeywordField(),
}
)
tag_set = fields.NestedField(
properties={
"id": fields.IntegerField(),
"tag_name": fields.TextField(analyzer="korean_analyzer"),
}
)
class Index:
# Define the Elasticsearch index name
name = 'posts'
settings = {
'number_of_shards': 1,
'number_of_replicas': 0,
"analysis": {
"analyzer": {
"korean_analyzer": {
"type": "custom",
"tokenizer": "nori_tokenizer",
"filter": ["lowercase"]
}
}
}
}
class Django:
model = Post # Django 모델과 연결
fields = [
'id',
'like_count',
'reply_count',
'rereply_count',
'is_active',
'created_at',
'updated_at',
]
def prepare_post_img(self, instance):
"""
이미지 URL을 Elasticsearch에 저장
"""
return instance.post_img.url if instance.post_img else None
인덱스를 생성합니다. (참고로 저는 ckeditor 를 이용하고 있어서 body 를 위쪽에 뺏습니다.)
python manage.py search_index --rebuild
--rebuild 는 기존 인덱스를 삭제하고 새로 만듭니다.
Are you sure you want to delete the 'posts' indices? [y/N]: y
Deleting index 'posts'
Creating index 'posts'
Indexing 11 'Post' objects
searchs.py 파일을 만들어서 검색하는 로직을 추가합니다.
def search_posts(
query: str = None,
board_urls: List[str] = None,
tag_ids: List[int] = None,
sort_fields: List[str] = None,
) -> Search:
"""
Elasticsearch에서 게시글 검색 및 Django Paginator와 연동
"""
# 기본 정렬 설정
if sort_fields is None:
sort_fields = ["-created_at"]
# 기본 검색 쿼리 설정
if query:
q = Q(
"bool",
should=[
Q("match", title=query),
Q("match", body=query),
],
minimum_should_match=1
)
else:
q = Q("match_all") # query가 없을 경우 전체 검색
# 필터 조건 설정
filters = [Q("term", is_active=True)] # 활성화된 게시글만 필터링
if board_urls:
filters.append(Q("terms", board__url=board_urls))
if tag_ids:
filters.append(
Q(
"nested",
path="tag_set",
query=Q("terms", tag_set__id=tag_ids)
)
)
# 필터를 bool 쿼리에 추가
if filters:
q = Q("bool", must=[q], filter=filters)
return PostDocument.search().query(q).sort(*sort_fields)
from board.searchs import search_posts
search_posts('테스트')[0]
<Response: [PostDocument(index='posts', id='3')]>
search_posts('조커')[0]
<Response: [PostDocument(index='posts', id='12')]>
search_posts('신')[0]
<Response: [PostDocument(index='posts', id='12')]>
아주 한국어 잘 작동합니다~!
이제 기존 코드를 전부 이렇게 수정합니다.
Elastic Search 를 이용하고 메모리를 너무 많이 쓰고 있는 것 같다.
기본 2G 로 설정하자.
sudo vi /etc/elasticsearch/jvm.options
# 안에 들어가서 4g 를 2g 로 수정합니다.
-Xms4g
-Xmx4g
# 수정
-Xms2g
-Xmx2g
# 재실행!
sudo systemctl restart elasticsearch
수정 완료!!