AWS 기술 블로그

MIDAS IT의 DynamoDB ZeroETL과 Bedrock을 이용한 OpenSearch 자동 임베딩 고객 사례

마이다스아이티(MIDAS IT)는 건축 및 토목 엔지니어링 소프트웨어를 개발하고 수출하는 회사로 미국, 일본, 중국 등 다양한 국가에 서비스를 제공하고 있는 건설분야 CAE(Computer-Aided Engineering) 소프트웨어 세계 최대기업 입니다. 엔지니어링 소프트웨어의 특성상 전문 기술 지원 요청이 글로벌하게 들어오며, 이를 기술 지원 전담 인력이 해결하는데 최소 몇 시간에서 최대 3일 정도가 소요됩니다. 마이다스아이티는 고객에게 더 빠르고 정확한 정보를 전달하고 고객 가치를 높이기 위해 LLM을 활용하여 전문 기술 지원 챗봇 서비스를 개발해왔습니다. 이 포스트에서는 마이다스아이티가 LLM(Large Language Model) 챗봇(Chatbot) 개발에 왜 Amazon OpenSearch Service를 사용하게 되었는지, Amazon DynamoDB와 데이터 정합성 문제를 어떻게 해결하였는지 상세하게 공유합니다.

기존 아키텍처

마이다스아이티는 OpenSearch 도입 전에 아래와 같이 Pinecone VectorDB를 이용하여 RAG(Retrieval-Augmented Generation) 아키텍처를 구축하였습니다.

아키텍처 다이어그램

데이터 플로우

  1. 사용자 입력 수집
    챗봇을 통해 User Input이 들어옵니다.
  2. 사용자 입력과 데이터 저장
    User Input과 정보 등을 사용자의 재 접속시 이력 출력을 위해 DynamoDB에 저장합니다.
  3. Vector 값 추출
    User Input을 임베딩하여 Vector 값을 추출합니다.
  4. Vector 값 저장
    채팅 이력 또한 향후 검색을 위해 Vector값을 Pinecone에 저장합니다.
  5. Vector 값 검색
    Vector값을 이용하여 기존에 Pinecone에 저장되어 있는 Knowledge Base를 검색합니다.
  6. LLM 요청
    설정해 둔 유사도보다 높은 knowledge 문서들과 User Input을 함께 LLM에 보냅니다.
  7. 응답 출력
    LLM 답변을 Output으로 내보냅니다.

문제점

여기에는 기존 아키텍쳐만을 이용해서 풀 수 없는 두 가지 문제점이 있었습니다.

1. Keyword Search(Hybrid Search)의 필요성

마이다스아이티에는 건축, 토목,기계 등 분야 별로 다양한 소프트웨어 제품이 존재합니다. 그리고 이를 담당하는 각각의 팀이 존재하고 챗봇 역시 각 제품 별로 참고해야 하는 문서와 지식들이 다릅니다. 마이다스아이티 AI개발팀에서는 팀 별 요구 사항에 맞는 챗봇을 구축하기 위해 Knowledge로 쓰일 문서와 자료들을 제품 팀에서 직접 넣어보고, 편집하고, 프롬프팅(Prompting)까지 해볼 수 있는 AI 빌더 플랫폼을 사내에 제공하고 있습니다.

제품 별 Knowledge가 다양해질수록 문서 수정과 검색 빈도가 높아졌고, knowledge 검색 기능이 플랫폼에 꼭 필요했습니다. 또한 기술 문서의 특성 상 문맥의 흐름 뿐 아니라 특정 키워드를 반드시 포함해서 찾아와야 하는 경우, Hybrid Search가 필요했습니다. Pinecone도 Hybrid Search를 지원하고 있지만, 한글 Keyword 검색이 제대로 지원되지 않는 문제가 있었습니다.

OpenSearch의 경우 Hybrid Search를 지원하고 있었고, 성능 테스트에서 Pinecone Vector Search와 큰 차이가 없었습니다. 또한 한글 Keyword 검색에 최적화된 한국어 분석기 “Nori”를 플러그인으로 제공하고 있었기 때문에 Pinecone에서 OpenSearch 로 저장소를 변경하는 것으로 최종 결정하였습니다. Hybrid Search 에 대한 자세한 내용은 Hybrid Search with Amazon OpenSearch Service 블로그 내용을 참고하시기 바랍니다.

2. 데이터 정합성 문제

기존 아키텍처에서 DynamoDB와 Pinecone 두 곳에 데이터를 적재하다 보니 자연스럽게 데이터 정합성 문제가 발생하였습니다. OpenSearch 로 저장소를 옮기더라도 해결해야 할 문제였습니다. 이를 해결하기 위해 아래 2가지 방법을 고민하였습니다.

  1. DynamoDB Stream 기능과 Lambda를 이용하여 Pinecone에 데이터를 적재
  2. Amazon MSK를 이용하여 데이터 파이프라인(Pipeline) 구축

마이다스아이티는 이를 해결하고자 AWS re:Invent 2023 자료 중 Amazon DynamoDB zero-ETL integration 영상을 통해 해결책을 찾았습니다. DynamoDB와 OpenSearch의 zero-ETL 통합 기능입니다. 사용자 지정 코드나 인프라 없이 데이터를 자동으로 OpenSearch로 복제 및 변환하여 DynamoDB 의 데이터를 검색할 수 있는 기능입니다. 말 그대로 ETL 데이터 파이프라인을 구축할 필요성을 없애거나 최소화하는 것입니다. 또한 DynamoDB의 낮은대기시간과 OpenSearch의 검색과 같은 각각의 특성을 적절히 조합하여 사용 할 수 있습니다. 시간과 인력이 별로 없었던 마이다스아이티에서는 시간이 많이 걸리고 복잡한 기존 ETL 프로세스를 구축하는 것보다 해당 기능을 사용하는 것이 적합하다고 판단하였습니다.

이 기능에 대한 자세한 내용은 Amazon OpenSearch Service와 DynamoDB zero-ETL 통합Amazon DynamoDB와 함께 OpenSearch 수집 파이프라인 사용 문서 및 블로그를 참고하시기 바랍니다.

신규 아키텍처

마이다스아이티는 OpenSearch를 도입하여 Vector 검색과 Keyword 검색이 모두 가능하도록 변경하였고, zero-ETL 통합 기능을 도입하여 DynamoDB와 OpenSearch 간 데이터 정합성 문제가 없도록 하였습니다.

아키텍처 다이어그램

데이터 플로우

  1. 사용자 입력 수집
    챗봇을 통해 User Input이 들어옵니다.
  2. zero-ETL 통합
    1. (2-a) 데이터 저장
      User Input과 채팅 정보 등을 DynamoDB에 저장하면 DynamoDB가 S3로 데이터를 내보냅니다.
    2. (2-b) 데이터 수집 (Auto Sink up)
      OpenSearch가 Pipeline을 통해 지정된 S3에 있는 데이터를 수집합니다.
  3. Vector 값 저장 (Auto Embedding)
    OpenSearch ML Connector를 통해 임베딩이 자동으로 수행되고 지정해둔 vector 필드에 vector 값이 저장됩니다.
  4. 문서 검색
    Hybrid Search를 통해 User Input을 통해 요청한 유사한 문서를 검색합니다.
  5. 문서 재구성 및 재정렬
    검색된 문서들을 실제 User Input과 하나의 Context로 재구성하여 ReRanking 합니다.
  6. LLM 요청
    ReRanking알고리즘을 통해 재 정렬된 knowledge 문서들과 User Input을 함께 LLM에 보냅니다.
  7. 응답 출력
    LLM 답변을 Output으로 내보냅니다.

구현 상세

새로운 아키텍처는 다음 3가지를 구축합니다. (본 블로그는 zeroETL 을 주제로 하므로 ReRanking 에 대한 내용은 제외합니다)

  1. OpenSearch Ingestion 구축
    챗봇을 통해 들어온 채팅 내용을 Amazon DynamoDB ZeroETL Integration 을 통해 통합합니다.
  2. OpenSearch Embedding Pipeline 구축
    채팅 내용이 OpenSarch에서 자동 임베딩(Embedding) 됩니다.
  3. OpenSearch Search Pipeline 구축
    하이브리드 서치를 위한 파이프라인을 구축합니다.

1. OpenSearch Ingestion 구축

DynamoDB에 데이터가 적재되면 OpenSearch에 자동으로 Sink하기 위해 해야 할 작업은 3가지입니다.

  1. 파이프라인 역할 생성
  2. 수집 경로 지정 (S3 Bucket 만들기)
  3. 파이프라인 생성

이 중 1번과 2번은 Amazon OpenSearch Ingestion을 사용하여 도메인으로 데이터 수집 – 아마존 OpenSearch 서비스Amazon 통합 OpenSearch 파이프라인 생성 – 아마존 OpenSearch 서비스 문서에 있는 상세 내용을 참고하시기 바랍니다.

본 블로그에서는 3번의 파이프라인 생성에서 주의해야 할 점을 살펴보겠습니다.

파이프라인 생성 하기

DynamoDB와 OpenSearch 연결 파이프라인은 OpenSearch에서 생성할 수 있습니다.
파이프라인 구성은 크게 3가지 (source, processor, sink) 로 구분할 수 있습니다.

source
소스는 Data Prepper 파이프라인 내에서 데이터가 어디에서 나오는지 정의합니다. 다음은 DynamoDB table 정보와 데이터를 수집할 수집 경로인 S3 정보를 작성한 예시입니다. 미리 생성한 파이프라인 역할을 sts_role_arn에 작성합니다. 필수 역할은 Amazon 통합 OpenSearch 파이프라인 생성 – 사전 조건 및 필수 역할을 참고하시기 바랍니다.

version: "2"
dynamodb-pipeline:
  source:
    dynamodb:
      acknowledgments: true
      tables:
        # REQUIRED: Supply the DynamoDB table ARN and whether export or stream processing is needed, or both
        - table_arn: "arn:aws:dynamodb:table/"
          # Remove the stream block if only export is needed
          stream:
            start_position: "LATEST"
          # Remove the export block if only stream is needed
          export:
            # REQUIRED for export: Specify the name of an existing S3 bucket for DynamoDB to write export data files to
            s3_bucket: ""
            # Specify the region of the S3 bucket
            s3_region: ""
            # Optionally set the name of a prefix that DynamoDB export data files are written to in the bucket.
            s3_prefix: "/"
      aws:
        # REQUIRED: Provide the role to assume that has the necessary permissions to DynamoDB, OpenSearch, and S3.
        sts_role_arn: "arn:aws:iam:::role/"
        # Provide the region to use for aws credentials
        region: ""

processor

processor는 필터링, 변환, 보강 등의 작업을 데이터에 수행합니다. DynamoDB에 들어가는 데이터 중 특정 필드들만 OpenSearch에 저장하기 위해 select_entries 옵션을 통해 include_keys를 지정해주었습니다. 마이다스아이티에서는 DynamoDB에 채팅 이력 뿐 아니라 사용자의 채팅 설정 등의 메터 데이터도 관리하기 때문에 Key값 중 특정 데이터 양식들은 OpenSearch에 저장 할 필요가 없기 때문에 drop_events 옵션을 통해 조건을 설정해주었습니다.

processor:
    - select_entries:
        include_keys: ["Key"]
    - drop_events:
        drop_when: '/Key == "TEST"'  or contains(/Key, "TEMP")

OpenSearch 공식 문서에 다양한 processor 옵션들과 processor에서 제공하는 함수들을 확인할 수 있습니다. contains 함수도 processor에서 공식으로 지원하는 함수입니다. 자세한 사항은 오픈서치 파이프라인의 Processor 문서를 참고하시기 바랍니다.

sink
sink는 Data Prepper가 데이터를 쓰는 위치를 정의합니다. OpenSearch endpoints는 dashboard endpoint가 아닌 도메인 endpoint임을 유의합니다. documnet_id를 “${getMetadata(\”primary_key\”)}”로 지정할 경우 DynamoDB에 primary key가 자동으로 document_id가 됩니다. DynamoDB를 복합키(SetK+SortK)로 구성했을 경우 document_id가 “{SetK}|{SortK}” 와 같이 생성됩니다. 아래 예시처럼 action을 작성할 경우 opensearch의 모든 action에 pipeline이 sink되는 것을 확인할 수 있습니다. 특정 action만 지정하는 것도 가능합니다.

sink:
    - opensearch:
        # REQUIRED: Provide an AWS OpenSearch endpoint
        hosts:
          [
          "https://.es.amazonaws.com"
          ]
        index: "{index_name}"
        index_type: custom
        document_id: "${getMetadata(\"primary_key\")}"
        action: "${getMetadata(\"opensearch_action\")}"
        document_version: "${getMetadata(\"document_version\")}"
        document_version_type: external
        aws:
          sts_role_arn: "arn:aws:iam::{user_id}:role/{role_name}"
          region: "{region}"
        dlq:
          s3:
            bucket: "{bucket_name}"
            region: "{region}"
            # Provide a Role ARN with access to the bucket. This role should have a trust relationship with osis-pipelines.amazonaws.com
            sts_role_arn: "arn:aws:iam::{user_id}:role/{role_name}"

파이프라인 용량 설정

파이프라인을 만들 때 최소 및 최대 용량을 지정해두면 OpenSearch Ingestion은 OCU(OpenSearch Computing Unit)를 기반으로 예상 워크로드에 따라 파이프라인 용량을 자동으로 조정합니다.

수집 파이프라인의 99.9% 가용성을 보장하려면 최소 2개의 이상의 Ingestion OCU를 프로비저닝하는 것이 좋기 때문에 최소 용량은 2개로 설정하였습니다. 또한 특정 시간에 파이프라인에서 활발하게 사용 중인 Ingestion OCU에 대해서만 비용이 청구되기 때문에, 파이프라인의 최대 용량이 워크로드의 급증을 처리할 수 있을 만큼 충분히 높게 설정하는 것이 좋습니다. 따라서 상태 저장 파이프라인의 최대 용량인 48개로 Ingestion OCU를 지정하였습니다. 자세한 내용은 파이프라인 크기 조정 문서를 참고하시기 바랍니다.

2. OpenSearch Embedding Pipeline 구축

Embedding pipeline을 구축하여 OpenSearch에 text만 넣으면 임베딩이 자동으로 되어 vector value를 저장할 수 있습니다. OpenSearch Embedding Pipeline을 구성하기 위해 다음 6가지 작업을 진행해야 합니다.

  1. Amazon Bedrock Titan model access
    (Amazon Bedrock에서 제공하는 Amazon Titan Embeddings G1 – Text (amazon.titan-embed-text-v1) model을 사용하여 임베딩 하였습니다.)
  2. OpenSearch Dashboards Security>Role>ml_full_access 역할의 Backend role 추가
  3. AWS CloudFormation을 사용한 모델 배포와 커넥터 생성
  4. 배포된 모델 확인
  5. Ingest pipeline 생성
  6. Index 생성

Step 1 : Model Access

Titan Embedding Model을 사용하기 위해서는 반드시 model 권한이 먼저 부여되어야 합니다. Amazon Bedrock 콘솔의 ‘모델 액세스’를 선택해 권한을 부여할 수 있습니다.

Step 2 – Step 4

권한 설정과 pipeline생성 과정은 Amazon OpenSearch Service Integration 기능을 활용한 손쉬운 임베딩 파이프라인 구성 블로그를 참고 하시기 바랍니다.

Step 5 : Ingest pipeline 생성

“source” 혹은 “source2” 필드에 텍스트가 들어오면, 각 필드에 매칭된 “source_vector”, “source2_vector” 필드로 지정된 model을 이용해서 텍스트를 임베딩하게 됩니다.(임베딩해야 될 필드가 여러 개인 예시입니다.)

PUT _ingest/pipeline/titan-pipeline
{
   "processors" : [
   {
    "text_embedding": {
      "model_id": "{model_id}",
      "field_map": {
        "source": "source_vector",
        "source2": "source2_vector"
        }
      }
    }
  ]
}

Step 6 : Index 생성

Step 5에서 생성한 ingest pipeline을 default pipeline으로 지정해서 인덱스를 생성하게 됩니다. 마이다스아이티는 vector 검색 뿐 아니라 keyword 검색, hybrid 검색이 모두 필요한 상황으로 “source” 필드에 “analyzer”를 “nori”로 지정하여 keyword 검색에 활용될 수 있도록 하였습니다. “source_vector” 필드의 경우 vector값이 저장되어야 하기 때문에 dimension, method, type을 지정해주었습니다.

  • dimension : default_pipeline에 지정되어 있는 임베딩 모델과 값이 동일해야 합니다.
  • method : default 값으로 지정되어 있는 “nmslib” engine 에서 Efficient k-NN filtering이 지원되지 않아 저희는 추후 검색에서 필터링을 위해 “engine”을 “faiss”로 변경해주었습니다. 자세한 사항은 k-NN Index 문서를 참고하시기 바랍니다.
PUT /{index_name}
{
  "settings": {
    "index.knn": true,
    "default_pipeline": "titan-pipeline",
    "index":{
      "number_of_shards":3,
      "number_of_replicas":2
    }
  },
  "mappings":{
    "properties": {
      "SetK": {
        "type": "keyword"
      },
      "SortK": {
        "type": "keyword"
      },
      "source": {
        "analyzer": "nori",
        "type": "text"
      },
      "source_vector": {
        "dimension": 1536,
        "method": {
          "engine": "faiss",
          "space_type": "l2",
          "name": "hnsw",
          "parameters": {}
        },
        "type": "knn_vector"
      }
    }
  }
}

3. OpenSearch Search Pipeline 구축

앞선 작업들로 이제 DynamoDB에 데이터가 적재되면 자동으로 OpenSearch에 데이터가 적재됩니다. 마지막으로 저희가 구성한 Index에서 Hybrid Search를 어떻게 할 수 있는지 보겠습니다.

Step 1 : Configure a search pipeline

Hybrid Search를 하려면 Search Pipeline이 필요합니다. 각각의 옵션들은 OpenSearch 공식 문서를 참고 하시기 바랍니다. 이때 “weights”의 경우 직접 테스트를 해본 결과 0.4:0.6의 비율이 가장 효과적이었습니다.

PUT /_search/pipeline/nlp-search-pipeline
{
  "description": "Post processor for hybrid search",
  "phase_results_processors": [
    {
      "normalization-processor": {
        "normalization": {
          "technique": "min_max"
        },
        "combination": {
          "technique": "arithmetic_mean",
          "parameters": {
            "weights": [
              0.4,
              0.6
            ]
          }
        }
      }
    }
  ]
}

Step 2 : Search the index using hybrid search

Hybrid Search는 쿼리를 보낼 때 search_pipeline을 지정해야 합니다. 쿼리를 보낼 때 “_source” 인자에 활용 할 필드만 지정하면 response 용량을 줄일 수 있습니다. 챗봇의 특성 상 특정 knowledge에 있는 데이터만 검색해올 수 있도록 특정 Key를 가진 데이터들을 먼저 pre filtering한 뒤 해당 범위 내에서 검색을 수행하도록 했습니다. 자세한 내용은 하이브리드서치 문서를 참고하시기 바랍니다.

GET /{index_name}/_search?search_pipeline=nlp-search-pipeline
{
  "_source": ["source"],
  "size": 10,
  "query": {
    "hybrid": {
      "queries": [
        {
          "bool": {
            "must": [
              {
                "match": {
                  "source": "테스트 중입니다"
                }
              },
              {
                "bool": {
                  "must": [
                    {
                      "term": {
                        "Key": "TEST Key value"
                      }
                    }
                  ]
                }
              }
            ]
          }
        },
        {
          "neural": {
            "source_vector": {
              "query_text": "테스트 중입니다",
              "model_id": "{model_id}",
              "filter": {
                "bool": {
                  "must": [
                    {
                      "term": {
                        "Key": "TEST Key value"
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      ]
    }
  }
}

마무리

지금까지 OpenSearch와 DynamoDB, Bedrock Titan 모델을 통해 RAG 아키텍쳐를 구성한 마이다스아이티 챗봇 서비스 구현 과정에 대해 설명 드렸습니다. 마이다스아이티 AI개발팀은 Amazon OpenSearch Service를 활용하여 시스템의 복잡성을 줄일 수 있었고, 백터화를 위한 추가적인 서버 리소스 할당이나 개발 작업을 없앨 수 있었습니다. 앞으로 Hybrid Search와 ReRanking 알고리즘 등 다양한 방법을 통해 검색 정확도와 LLM 답변 수준을 개선해나갈 예정입니다.

이지영

이지영 개발 리드는 마이다스아이티에서 AI챗봇 서비스 설계 및 개발을 포함한 프로젝트 리딩을 담당하고 있습니다. 현재 검색 성능과 정확도를 높이고 LLM 답변 수준을 높이기 위해 노력하고 있습니다.

양성민

양성민 엔지니어는 마이다스아이티에서 AI 활용을 위한 데이터 엔지니어링에 힘쓰고 있습니다.

변창언

변창언 엔지니어는 마이다스아이티에서 AI 챗봇 서비스의 사용자 경험을 개선하기 위해 UI/UX 개발과 고도화에 힘쓰고 있습니다.

이준서

이준서 엔지니어는 마이다스아이티에서 완성도 높은 기술지원 서비스를 제공하기 위해 성능 최적화와 시스템 설계에 주력하고 있습니다.

김정빈

김정빈 엔지니어는 마이다스아이티에서 AI 챗봇 성능 개선을 위한 성능 테스트와 테스트 자동화에 주력하고 있습니다.

HyoSung Lee

HyoSung Lee

이효성 솔루션즈 아키텍트는 다양한 분야에서의 서비스 설계 및 운영 경험을 바탕으로 ISV/DNB 고객들이 AWS 를 활용해 서비스를 만들고 고도화하는 과정에서 기술적인 도움을 드리는 업무를 하고 있습니다.