OpenSearch 완전 정복: Analyzer부터 클러스터, 한국어 처리(Nori)까지 샅샅이 파헤치기

이동휘·2025년 5월 29일
0

매일매일 블로그

목록 보기
20/49

데이터베이스는 몇 가지나 알고 계신가요?" 이 질문에 MySQL, Oracle, MongoDB, Redis 등을 떠올리셨다면, 오늘 또 하나의 강력한 도구를 리스트에 추가할 시간입니다. 바로 검색 기능을 갖춘 NoSQL 데이터베이스이자 검색 엔진인 OpenSearch입니다.

오늘날 데이터는 엄청난 속도와 양, 그리고 다양한 형태로 생성됩니다. 기존 관계형 데이터베이스는 정형 데이터 처리에 강점을 보이지만, 대량의 비정형 데이터를 효율적으로 검색하고 분석하는 데는 한계가 있죠. OpenSearch는 이러한 현대 데이터 환경의 요구에 부응하며, 강력한 검색 기능을 중심으로 다양한 서비스에서 핵심적인 역할을 수행하고 있습니다.

이 글에서는 OpenSearch의 기본 개념과 핵심 기능인 Analyzer, Elasticsearch와의 관계, 클러스터 아키텍처, 그리고 한국어 처리에 특화된 Nori 분석기까지 깊이 있게 살펴보고, 실제 기업들이 어떻게 OpenSearch를 활용하여 검색 서비스를 구축하는지 그 여정을 따라가 보겠습니다.


1. OpenSearch란 무엇일까요? Elasticsearch에서 시작된 오픈소스 검색 엔진

OpenSearch는 단순히 데이터를 저장하는 것을 넘어, 저장된 데이터를 빠르고 정교하게 검색하는 데 특화된 오픈소스 NoSQL 데이터베이스이자 검색 엔진입니다.

  • 탄생 배경과 Elasticsearch와의 관계: OpenSearch는 Elastic 사에서 개발한 Elasticsearch에서 파생되었습니다. 2010년에 출시된 Elasticsearch는 강력한 검색 기능으로 널리 사용되었으나, 2021년 Elastic사가 라이선스 정책을 변경하여 일부 기능을 유료화하고 소스 코드 접근을 제한했습니다. 이에 대한 대응으로 AWS를 중심으로 커뮤니티가 Elasticsearch의 마지막 오픈소스 버전(7.10.2)을 포크(fork)하여 새롭게 시작한 프로젝트가 바로 OpenSearch입니다.
    • 유사성: 이러한 배경 때문에 OpenSearch와 Elasticsearch는 핵심 기능, API, 데이터 구조 등이 매우 유사합니다. 기존 Elasticsearch 사용 경험이 있다면 OpenSearch로의 전환이나 학습이 비교적 용이합니다.
    • 차이점: OpenSearch는 Apache License 2.0이라는 완전한 오픈소스 라이선스를 유지하며, 커뮤니티 주도로 개발되고 있습니다. 장기적으로는 독자적인 기능 추가와 개선을 통해 Elasticsearch와 차별화될 가능성이 있습니다.
  • 핵심 엔진: Apache Lucene: OpenSearch의 강력한 검색 기능은 내부적으로 Java 기반의 고성능 검색 라이브러리인 Apache Lucene을 사용하기 때문에 가능합니다. Lucene은 뛰어난 전문(Full-text) 검색, 정교한 랭킹, 다양한 분석 기능을 제공하여 대량의 텍스트 데이터를 효율적으로 색인하고 복잡한 검색 질의에도 빠른 응답을 보장합니다.
  • JSON 기반 문서 저장: OpenSearch는 데이터를 JSON 형식의 문서(Document)로 저장하고 관리합니다. 이는 반정형 데이터를 유연하게 다룰 수 있게 하며, 동적 스키마(Dynamic Schema) 및 매핑(Mapping) 설정을 통해 데이터 필드의 타입과 분석 방식을 자유롭게 정의할 수 있어 데이터 구조 변경에 유연하게 대응할 수 있습니다.

OpenSearch vs. RDBMS 용어 비교:

OpenSearchRDBMS설명
IndexTable (테이블)데이터 문서(Document)들의 논리적인 집합.
DocumentRow (행)Index에 저장되는 개별 데이터 단위. JSON 객체 형태.
FieldColumn (열)Document 내의 각 키-값 쌍에서 키에 해당. 데이터의 속성.
MappingSchema (스키마)Index 내 Document들의 구조 정의. 각 Field의 데이터 타입, 분석 방식 등을 설정.

2. OpenSearch 클러스터 아키텍처: 분산 환경에서의 동작 원리

OpenSearch는 기본적으로 분산 시스템으로 설계되어 높은 가용성과 확장성을 제공합니다. 여러 대의 서버(노드)가 모여 하나의 클러스터(Cluster)를 구성하고, 데이터는 이 클러스터 내에 분산되어 저장 및 처리됩니다.

  • 노드(Node): 클러스터를 구성하는 개별 서버 인스턴스입니다. 각 노드는 역할(Role)을 가질 수 있습니다.
    • 마스터 노드 (Master-eligible Node): 클러스터 전체를 관리하고 조율하는 역할을 담당합니다. (예: 인덱스 생성/삭제, 노드 추가/제거 등 클러스터 수준의 변경 작업). 안정적인 클러스터 운영을 위해 최소 3대의 마스터 후보 노드를 두는 것이 권장됩니다.
    • 데이터 노드 (Data Node): 실제 데이터(샤드)를 저장하고, 데이터 색인 및 검색 요청을 처리합니다. I/O 및 CPU 부하가 높은 노드입니다.
    • 인제스트 노드 (Ingest Node): 데이터가 색인되기 전에 다양한 전처리 작업을 수행하는 파이프라인을 실행합니다. (예: 필드 추가/제거, 값 변환)
    • 코디네이팅 노드 (Coordinating Node / Client Node): 클라이언트의 요청을 받아 적절한 데이터 노드들에게 분산시키고, 각 노드로부터 받은 결과를 취합하여 최종 응답을 클라이언트에게 전달합니다. 자체적으로 데이터를 저장하거나 마스터 역할을 수행하지는 않습니다. (필수는 아니지만, 대규모 클러스터에서 부하 분산을 위해 사용)
  • 샤드(Shard): 하나의 인덱스에 저장된 데이터가 너무 커질 경우, 이를 여러 개의 작은 단위로 분할하여 저장하는데, 이 분할된 조각을 샤드라고 합니다. 각 샤드는 독립적인 Lucene 인덱스처럼 동작하며, 여러 데이터 노드에 분산 배치되어 병렬 처리를 가능하게 합니다.
    • 프라이머리 샤드 (Primary Shard): 데이터의 원본 조각입니다. 모든 쓰기 작업(색인, 업데이트, 삭제)은 먼저 프라이머리 샤드에서 이루어집니다. 인덱스 생성 시 샤드 수를 지정하며, 한번 정해지면 변경하기 어렵습니다.
    • 레플리카 샤드 (Replica Shard): 프라이머리 샤드의 복제본입니다. 데이터 유실 방지를 통한 고가용성 확보 및 읽기 요청 분산을 통한 검색 성능 향상을 위해 사용됩니다. 레플리카 샤드는 항상 프라이머리 샤드와 다른 노드에 위치합니다.
  • 클러스터 상태 관리: 마스터 노드가 클러스터 전체의 메타데이터(노드 목록, 인덱스 정보, 샤드 위치 등)를 관리하며, 이 정보는 etcd와 유사한 내부 메커니즘을 통해 다른 노드들과 동기화됩니다.

🤔 꼬리 질문: OpenSearch 클러스터 설계 시 샤드 수와 레플리카 수는 어떻게 결정하는 것이 좋을까요? 너무 많거나 적게 설정했을 때 발생할 수 있는 문제점은 무엇일까요?


3. OpenSearch의 핵심: Analyzer를 활용한 똑똑한 데이터 처리 및 검색

OpenSearch가 뛰어난 검색 기능을 제공할 수 있는 비결 중 하나는 바로 Analyzer입니다. Analyzer는 데이터를 저장(색인)하거나 검색할 때, 입력된 텍스트를 일정한 규칙에 따라 의미 있는 작은 단위인 토큰(Token)으로 분리하고 가공하는 역할을 합니다. 이 과정을 통해 검색의 정확도와 효율성을 크게 높일 수 있습니다.

데이터 저장부터 검색까지의 흐름 (Analyzer 중심):

  1. 인덱스 생성 및 매핑 설정: 먼저 데이터를 저장할 Index를 생성하고, 각 Field의 타입과 사용할 Analyzer 등을 정의하는 Mapping 정보를 설정합니다.
  2. 데이터 적재 및 인덱싱: 데이터(Document)가 적재되면, text 타입으로 지정된 Field의 내용은 설정된 Analyzer를 통해 여러 개의 토큰으로 분리되고 가공된 후, 역색인(Inverted Index) 구조로 저장됩니다.
  3. 검색어 분석 및 문서 조회: 사용자가 검색어를 입력하면, 해당 검색어 또한 동일한 (또는 검색용으로 지정된) Analyzer를 통해 토큰으로 분리 및 가공됩니다. OpenSearch는 이 토큰들을 사용하여 역색인에서 일치하는 Document들을 신속하게 찾아 결과로 반환합니다.

예제: 책 검색 서비스 구현을 위한 Analyzer 활용

(이하 Analyzer 예제 설명은 이전 답변과 거의 동일하게 유지하되, 문맥상 필요한 부분만 수정합니다. 제공해주신 내용이 매우 상세하고 좋아서 핵심을 유지하는 것이 좋다고 판단됩니다.)

대량의 책 데이터를 OpenSearch에 적재하여 검색 서비스를 만든다고 가정하고, 다음과 같은 검색 요구사항이 있습니다.

  • 제목 (예: "기초부터 시작하는 Java!")
    • 일부 단어만 입력해도 검색 가능해야 함.
    • 대소문자 구분 없이 검색 가능해야 함.
    • 검색어에 특수문자가 없어도 원본 제목에 특수문자가 포함된 결과 검색 가능.
  • 저자 (예: "김철수")
    • 전체 이름을 정확하게 입력한 경우에만 검색되어야 함.
  • 출판일 (예: "2024-12-01")
    • 검색 조건으로는 활용되지만, 텍스트 분석(토큰화)은 필요 없음.

1. 인덱스 생성 및 매핑 정의 (예시):

PUT /book_example_v2 // 인덱스 이름 변경 (가독성)
{
  "mappings": {
    "properties": {
      "bookName": {
        "type": "text",
        "analyzer": "my_book_name_analyzer"
      },
      "author": {
        "type": "keyword"
      },
      "publishDate": {
        "type": "date"
      }
    }
  },
  "settings": {
    "analysis": {
      "analyzer": {
        "my_book_name_analyzer": {
          "type": "custom",
          "char_filter": [ "html_strip", "remove_special_chars" ],
          "tokenizer": "my_ngram_tokenizer",
          "filter": [ "lowercase", "trim" ]
        }
      },
      "char_filter": {
        "remove_special_chars": {
          "type": "pattern_replace",
          "pattern": "[^a-zA-Z0-9가-힣\\s]",
          "replacement": ""
        }
      },
      "tokenizer": {
        "my_ngram_tokenizer": {
          "type": "ngram",
          "min_gram": 2, "max_gram": 5,
          "token_chars": [ "letter", "digit" ]
        }
      }
    }
  }
}

2. 데이터 인덱싱 과정 (Analyzer의 역할):

(Analyzer 처리 순서: Character Filters → Tokenizer → Token Filters)

  1. Character Filters (문자 필터 - 전처리): html_strip (HTML 태그 제거), remove_special_chars (사용자 정의 특수문자 제거).
    (예: "기초부터 시작하는 Java!" → "기초부터 시작하는 Java")
  2. Tokenizer (토크나이저 - 토큰 생성): my_ngram_tokenizer (N-gram 방식).
    (예: "기초부터 시작하는 Java" → "기초", "초부", ..., "Ja", "Jav", ..., "va" 등)
  3. Token Filters (토큰 필터 - 후처리): lowercase (소문자 변환), trim (공백 제거).
    (예: "Java" → "java")

3. 인덱싱된 데이터 검색:

GET /book_example_v2/_search
{
  "query": {
    "match": {
      "bookName": "java"
    }
  }
}

검색 결과에는 연관도 점수(_score)가 포함되어 BM25 알고리즘 등에 의해 계산된 순서대로 문서가 반환됩니다.


4. 한국어 검색을 위한 필수품: Nori 분석기 🇰🇷

기본적으로 제공되는 OpenSearch(및 Elasticsearch)의 Standard Analyzer는 공백을 기준으로 단어를 분리하므로, 한국어처럼 조사가 붙고 띄어쓰기가 불규칙한 교착어에는 적합하지 않습니다. 예를 들어 "오픈서치검색엔진"이라는 텍스트는 Standard Analyzer로는 "오픈서치검색엔진"이라는 하나의 토큰으로만 분석될 가능성이 높아, "오픈서치"나 "검색엔진"으로 검색했을 때 원하는 결과를 얻기 어렵습니다.

이러한 문제를 해결하기 위해 한국어 형태소 분석기가 필요하며, OpenSearch에서는 Nori (노리) 분석기를 공식적으로 지원합니다.

  • Nori 분석기의 주요 기능:
    • 형태소 분석 (Morphological Analysis): 한국어 문장을 의미를 가지는 최소 단위인 형태소(명사, 동사, 조사, 어미 등)로 분해합니다. (예: "아버지가방에들어가신다" → "아버지", "가", "방", "에", "들어가", "시", "ㄴ다")
    • 품사 태깅 (Part-of-Speech Tagging): 분석된 각 형태소의 품사 정보를 태깅합니다.
    • 불용어 처리 (Stopword Filtering): 검색에 큰 의미가 없는 조사, 어미, 부사 등을 제거하여 검색 효율을 높일 수 있습니다. (예: "은", "는", "이", "가", "을", "를")
    • 동의어 처리 (Synonym Filtering): 사전에 정의된 동의어를 같은 토큰으로 처리하여 검색 결과의 재현율을 높입니다. (예: "오픈서치" ↔ "OpenSearch")
    • 복합 명사 분해 (Decompounding): 여러 명사가 합쳐진 복합 명사를 개별 명사로 분해합니다. (예: "검색엔진" → "검색", "엔진")
    • 사용자 사전 확장: 기본 사전에 없는 신조어나 전문용어 등을 사용자 사전에 추가하여 분석 정확도를 높일 수 있습니다.

Nori 분석기 설정 예시 (인덱스 생성 시):

PUT /korean_example
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_korean_analyzer": { // 사용자 정의 Nori 분석기
          "type": "custom",
          "tokenizer": "nori_tokenizer",
          "filter": [
            "nori_pos_filter_useful_nouns", // 명사 위주 필터 (예시)
            "lowercase"
          ]
        }
      },
      "tokenizer": { // Nori 토크나이저 사용
        "nori_tokenizer": {
          "type": "nori_tokenizer",
          "decompound_mode": "mixed", // 복합 명사 분해 방식 (discard, mixed, none)
          "user_dictionary_rules": [ // 사용자 사전 규칙 (필요시)
            "c++", "C#.NET", "씨쁠쁠", "오픈서치", "엘라스틱서치"
          ]
        }
      },
      "filter": {
        "nori_pos_filter_useful_nouns": { // 사용자 정의 Nori 품사 필터
          "type": "nori_part_of_speech",
          "stoptags": [ // 제외할 품사 태그 (예: 조사, 어미 등)
            "E", "J", "SC", "SF", "SP", "SSC", "SSO", "SY", "UNA", "UNKNOWN", "VSV", "VA", "VCP", "VCN"
            // 필요에 따라 명사(NNG, NNP 등)만 남기도록 stoptags 조정
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "korean_title": {
        "type": "text",
        "analyzer": "my_korean_analyzer", // 색인 시 사용할 분석기
        "search_analyzer": "my_korean_analyzer" // 검색 시 사용할 분석기 (일반적으로 동일하게 설정)
      }
    }
  }
}

Nori 분석기를 사용하면 "오픈서치로 검색하기"와 같은 문장을 "오픈서치", "검색" 등의 의미 있는 명사 토큰으로 분리하여 색인함으로써, 한국어 검색의 정확도와 재현율을 크게 향상시킬 수 있습니다.

🤔 꼬리 질문: 한국어 처리 시 Nori 분석기의 decompound_mode 설정(mixed, discard, none)은 각각 어떤 차이가 있으며, 어떤 경우에 어떤 설정을 사용하는 것이 유리할까요? 사용자 사전은 어떤 경우에 특히 유용하게 사용될 수 있을까요?


마치며

지금까지 OpenSearch의 기본 개념부터 Elasticsearch와의 관계, 클러스터 아키텍처, 핵심 기능인 Analyzer의 활용법, 그리고 한국어 처리를 위한 Nori 분석기까지 폭넓게 살펴보았습니다. OpenSearch는 단순한 데이터 저장을 넘어, 강력하고 유연한 검색 기능을 제공함으로써 다양한 서비스에 핵심적인 가치를 더하는 중요한 기술입니다.

Analyzer를 어떻게 설계하고 활용하느냐에 따라 검색 결과의 품질과 사용자 경험이 크게 달라질 수 있으며, 안정적이고 효율적인 색인 파이프라인 구축은 대규모 데이터를 다루는 현대 서비스의 필수 과제입니다. 이 글이 OpenSearch를 이해하고 실제 서비스에 적용하는 데 있어 유용한 정보를 제공했기를 바랍니다.

읽어주셔서 감사합니다!

0개의 댓글