
Mere 프로젝트 진행중 웹 와이어프레임에 검색기능이 있음을 확인.
출시 목적으로 진행 하고 있는 프로젝트 이기에 사용자의 검색량이 증가 함에 따라 Mysql Sql Like 문을 사용할 시에 여러가지 문제점이 발생 할 것 이라 판단 하였다.
현재 진행중인 프로젝트 이므로 추가적으로 요구사항이 있을때 마다 계속 업데이트 하면서 수정 할 예정이다.
정확도 문제 : 복잡한 텍스트 분석이나 유사도 계산 등의 기능을 제공하지 않는다.
단어의 일부를 검색하는 경우: 주로 전체 문자열 또는 문자열의 일부를 검색하는 데 사용,
단어의 일부만을 검색하는 경우 원하지 않는 결과가 나올 수 있다.
확장성 문제: 단순히 문자열 일치만을 검사하기 때문에, 추후 검색 기능을 확장하고자 할 때 제약이 될 수 있다.
이 외에도 여러가지 태그를 검색시 관련 업종의 매장이 자동 검색 되게 유도하는 등 많은 기능이 논의 되고 있었다.
따라서 해당 문제점들을 해결하기 위해 Elastic , Logstash , Kibana 를 사용 하여 검색 기능을 구현 해보자.
해당 기능들을 구현하기 위해 Docker 를 사용하기로 결정.
Elastic , Logstash , Kibana 를 Docker 로 각각 컨테이너를 관리하며 테스트 해보니 여간 까다로운게 아니였다.
따라서 Docker Compose 를 사용, 각 컨테이너를 묶어서 통합으로 관리하며 테스트 하기로 결정.


환경 설정에 큰 영향을 주는 파일들만 간략하게 설명 하겠다.
version: '3.2'
services:
elasticsearch:
container_name: elasticsearch
hostname: elasticsearch
build:
context: elasticsearch/
args:
ELK_VERSION: $ELK_VERSION
volumes: # Docker Container 과 데이터를 공유 하기 위해 Mount 작업
- type: bind
source: ./elasticsearch/config/elasticsearch.yml
target: /usr/share/elasticsearch/config/elasticsearch.yml
read_only: true
- type: volume
source: elasticsearch
target: /usr/share/elasticsearch/data
ports:
- "9200:9200"
- "9300:9300"
environment:
ES_JAVA_OPTS: "-Xmx256m -Xms256m"
discovery.type: single-node
networks: # search 네트워크 공유
- search
logstash:
container_name: logstash
hostname: logstash
build:
context: logstash/
args:
ELK_VERSION: $ELK_VERSION
volumes: # Docker Container 과 데이터를 공유 하기 위해 Mount 작업
- type: bind
source: ./logstash/config/logstash.yml
target: /usr/share/logstash/config/logstash.yml
read_only: false
- type: bind
source: ./logstash/pipeline
target: /usr/share/logstash/pipeline
read_only: false
- type: bind
source: ./logstash/config/pipelines.yml
target: /usr/share/logstash/config/pipelines.yml
read_only: false
ports:
- "5000:5000/tcp"
- "5000:5000/udp"
- "9600:9600"
environment:
LS_JAVA_OPTS: "-Xmx256m -Xms256m"
networks: # search 네트워크 공유
- search
kibana:
container_name: kibana
hostname: kibana
build:
context: kibana/
args:
ELK_VERSION: $ELK_VERSION
volumes:
- type: bind
source: ./kibana/config/kibana.yml
target: /usr/share/kibana/config/kibana.yml
read_only: false
ports:
- "5601:5601"
depends_on:
- elasticsearch
networks: # search 네트워크 공유
- search
volumes:
elasticsearch:
networks: # Docker-Compose 파일에 정의된 Container 이 동일한 네트워크를 공유하도록 정의
search:
driver: bridge
input {
tcp {
port => 5001
}
jdbc {
jdbc_driver_library => "/usr/share/logstash/pipeline/mysql-connector-java-8.0.15.jar"
jdbc_driver_class => "com.mysql.cj.jdbc.Driver"
jdbc_connection_string => "jdbc:mysql://host.docker.internal:3306/(본인의 Mysql Database Name)?useSSL=false&serverTimezone=UTC"
jdbc_user => "Mysql User 정보 입력"
jdbc_password => "Mysql Password 정보 입력"
schedule => "* * * * *"
statement => "SELECT storeId , storeName , grade FROM stores WHERE updatedAt > :sql_last_value"
tracking_column => "updatedAt"
use_column_value => true
last_run_metadata_path => "/usr/share/logstash/pipeline/last_store_update_time.yml"
lowercase_column_names => false
}
}
output {
stdout {
codec => rubydebug
}
elasticsearch {
hosts => "elasticsearch:9200"
index => "stores"
document_id => "%{storeId}"
doc_as_upsert => true # 문서가 이미 존재하는 경우 업데이트하거나 새 문서로 작성할지 여부 , 업데이트하지 않음
template => "/usr/share/logstash/pipeline/template/store_template.json" # index patttern type 지정
}
}
input {
tcp {
port => 5001
}
jdbc {
jdbc_driver_library => "/usr/share/logstash/pipeline/mysql-connector-java-8.0.15.jar"
jdbc_driver_class => "com.mysql.cj.jdbc.Driver"
jdbc_connection_string => "jdbc:mysql://host.docker.internal:3306/(본인의 Mysql Database Name)?useSSL=false&serverTimezone=UTC"
jdbc_user => "Mysql User 정보 입력"
jdbc_password => "Mysql Password 정보 입력"
schedule => "* * * * *"
statement => "SELECT * FROM tags WHERE updatedAt > :sql_last_value"
tracking_column => "updatedAt"
use_column_value => true
last_run_metadata_path => "/usr/share/logstash/pipeline/last_tag_update_time.yml"
lowercase_column_names => false
}
}
output {
stdout {
codec => rubydebug
}
elasticsearch {
hosts => "elasticsearch:9200"
index => "tags"
document_id => "%{tagId}"
doc_as_upsert => true # 문서가 이미 존재하는 경우 업데이트하거나 새 문서로 작성할지 여부 , 업데이트하지 않음
template => "/usr/share/logstash/pipeline/template/tag_template.json" # index patttern type 지정
}
}
{
"index_patterns": [
"stores"
],
"mappings": {
"properties": {
"storeId": {
"type": "long"
},
"storeName": {
"type": "text"
},
"grade": {
"type": "integer"
}
}
}
} {
"index_patterns": [
"tags"
],
"mappings": {
"properties": {
"tagId": {
"type": "long"
},
"tagName": {
"type": "text"
}
}
}
} - pipeline.id: tag_index
path.config: "/usr/share/logstash/pipeline/tag_index.conf"
- pipeline.id: store_index
path.config: "/usr/share/logstash/pipeline/store_index.conf"




type : "ngram" : 토크나이저의 유형이 N-gram임을 나타낸다.
min_gram : 1 : 생성되는 N-gram의 최소 크기를 지정하며 이 설정은 각 토큰이 최소한 한 개의 아이템을 포함해야 함을 의미한다.
max_gram : 10 : 생성되는 N-gram의 최대 크기를 지정하며 이 설정은 각 토큰이 최대 10개의 아이템을 포함할 수 있음을 의미한다.
색인 설정이 완료 되었다면 Kibana GET 요청을 통해 검색을 진행 할 수 있다.


이번 작성 마무리 하기전에 굉장히 골머리를 앓았던 문제점 에 대해서 간략히 설명 해보겠다.
Command Prompt 로 Docker 명령어를 실행했을때는
docker run -it -d -p 9200:9200 -p 9300:9300 -e ...
으로 실행했었고 동작도 잘 되었기에 명령어의 세부 옵션을 하나하나 뜯어보지는 않았다.
Docker-Compose 를 작동 후 ELK 모두 정상적으로 작동하는 것을 확인했었고
Local -> Container 로 통신이 가능했기에 Container -> Local 로도 접근이 가능할 줄 알았다.
Local 에서 작동하고 있는 Mysql 에 접근하려고 시도 했었지만 Container 에서 User 의 Localhost 로 접근하지 못하는 문제가 발생했었다.
왜 이런 문제가 발생 하는 가 에 대해서 많은 시간을 할애 했고 내린 결론은 "포트 포워딩" 문제 였다.
Docker 명령어를 Command 에 입력시에 -p 옵션을 주는데 이 옵션이 User 의 Local 과 컨테이너의 Local Port 를 연결 해주는 역할을 하는 것 이였다.
그렇다면 왜 "포트 포워딩" 을 했음에도 불구하고 Container 에서 User Local 로의 접근이 불가능 할까?
-p 옵션을 주는 순간 User Local 에서 Container 의 포트 포워딩이 이루어지지만 이는 "단방향" 이므로
Container 에서 User Local 로는 접근이 불가능 했다.
초기 Logstash conf 파일의 Jdbc Connection 설정을
jdbc_connection_string => "jdbc:mysql://localhost:3306 ... " 으로 했었고
이는 Container 의 Localhost:3306 즉 Container 의 내부의 3306 포트를 뜻했으며 User 의 Localhost:3306 과는 다르게 작동했다.
따라서 jdbc_connection_string => jdbc:mysql://host.docker.internal:3306 ... " 으로 설정 해줘야 의도한 대로 작동 한다.
처음에는 검색 기능이 단순하다고 생각했다.
그저 사용자가 원하는 정보를 키워드로 입력하면, 관련 정보를 찾아내는 것이 전부일 줄 알았으나 실제로는 그렇지 않았다.
기존에 진행했던 토이 프로젝트들은 기능이 단순하고, 고려해야 할 사항도 크게 많지 않아서 무리 없이 진행할 수 있었다.
이전의 경험을 바탕으로, 새로운 프로젝트를 진행하는데 어려움이 없을 것이라는 자신감을 가지고 있었다. 그러나, 이번에는 실제 사용을 고려하는 프로젝트에 참여하게 되었고, 이에 따라 기존의 작업 방식을 그대로 사용하면 어떤 문제가 발생할지 알 수 없었다.
특히 Docker에 관한 지식이 거의 없었기 때문에, 이를 활용한 초기 설정에 많은 어려움을 겪었다.
환경 설정을 할수록 기본 개념을 이해하고 새로운 기술을 학습하는 것의 중요성을 더욱 느끼게 되었다.
특히, 사용자의 로컬에서 Docker 컨테이너로의 접근은 가능하지만, 반대로 컨테이너에서 사용자의 로컬로의 접근은 불가능한 "양방향 포트 포워딩" 의 개념에 대해 이해하는 데에 상당한 시간이 소요되었다.
원래는 ELK를 학습하기 위해 많은 시간을 구글링하여 보낼거라 예상 했으나, 실제로는 Docker를 학습하기 위해 구글링한 시간이 훨씬 많았다.
ELK에 대한 정보를 찾는 것보다 Docker에 대한 이해를 더욱 확실히 하는 것이 중요하다는 것을 깨달았다.
아직 갈 길이 멀지만, 초기 진행을 할 수 있을 정도로 감을 잡았다고 생각한다.
검색 기능에 몇몇 필수적으로 해결해야 하는 요구 사항이 있어서, 추가적으로 매장 등급과 매핑 전략에 따른 유사도 점수 조정 기능을 보완하려고 한다.
이렇게 보완한 후에 다음 글을 작성할 계획이다.
당시 가지고 있던 기술 개념으로 작성된 글 이므로 정확하지 않을 수 있음
문제가 발견 된다면 추후 수정 예정