AWS 기술 블로그

뉴넥스의 AWS 서비스를 활용한 검색 시스템 구축과 운영 사례

소개

뉴넥스(NEWNEX)는 2014년에 설립된 패션 이커머스 플랫폼 기업으로, IT 기술을 활용해 커머스와 물류 인프라를 통합하여 ‘하루배송’과 같은 최적의 쇼핑 경험을 제공하고 있습니다.

현재 1020 여성 패션 플랫폼 ‘브랜디’, 남성 쇼핑 플랫폼 ‘하이버’, 여성 브랜드 패션 플랫폼 ‘서울스토어’를 운영 중이며, 판매자에게 물류와 운영을 지원하는 통합 서비스인 ‘헬피’도 제공하고 있습니다. 뉴넥스는 2024년 기준 누적 거래액 1.8조 원, 투자 유치 1,530억 원을 달성하는 등 빠르게 성장하고 있으며 “New Thinking Next Moving(새로운 사고, 다음 단계로의 움직임)”이라는 사명 아래, ‘다음 세대의 커머스’를 만들기 위해 끊임없이 도전하고 혁신하고 있는 대한민국 대표 스타트업입니다.

검색 시스템 구축 배경

뉴넥스에서 운영하는 패션 플랫폼인 ‘브랜디’와 ‘하이버’는 기존에 SaaS 형태로 제공되는 타사의 검색 솔루션을 사용하고 있었습니다. 이는 상품 데이터를 주기적으로 JSON 파일로 추출하여 검색 솔루션과 연계하고, REST API를 통해 검색 질의를 수행하는 방식이었습니다. 이러한 접근법은 단순하고 편리한 점이 있었으나, 뚜렷한 한계점이 있었습니다.

  1. 비즈니스 및 요구사항 변화에 대한 빠른 대응의 어려움
    대표적으로 전시 정책 변경이 있습니다. 검색은 전시 정책과 밀접하게 연관되지만, 정책 변경 시 내부 시스템에는 즉시 반영되는 반면 외부 솔루션에는 즉각적인 대응이 지연되는 문제가 있었습니다.
  2. 확장성 부족
    새로운 서비스를 추가할 때, 비즈니스 특성을 빠르게 이해하고 도메인을 반영하기 위해서는 내부 인력의 긴밀한 협조가 필요했지만, 외부 솔루션 사용 시 이 과정이 더 어려웠습니다.
  3. 증분 색인 주기
    하루에 한 번 전체 색인 주기가 있었고, 30분마다 추가, 수정, 삭제된 상품들을 증분 색인을 통해 반영했습니다. 도메인에 따라 30분의 주기로 충분할 수 있지만, 패션 커머스 플랫폼에서는 빠른 수정 사항 반영이 필요합니다.

이러한 문제점들을 해결하고자, 더 유연하고 효율적으로 시스템을 운영하기 위해 내부에서 직접 검색 시스템을 구축하고 관리하기로 했습니다.

검색 시스템 솔루션 개요

데이터가 검색 결과로 노출되기까지는 대부분 데이터 추출 → 데이터 색인 → 검색 질의 및 응답 3단계로 이루어 집니다. 앞으로 데이터 추출과 데이터 색인 과정을 편의상 ‘검색 파이프라인’이라고 표현하겠습니다. 즉, 검색 데이터 파이프라인은 체계적으로 설계된 프로세스로 주기에 따라 운영됩니다.

이 과정에서 유효한 데이터를 정교하게 추출(Extract)하고, 비즈니스 인텔리전스를 적용하여 최적화된 형태로 변환(Transformation)합니다. 최종적으로 이렇게 가공된 데이터는 검색 시스템에 효율적으로 색인(Indexing)되어, 정확한 정보 검색을 가능하게 합니다. 이러한 검색 색인 과정은 전체 색인과 증분 색인 두 가지로 나눌 수 있습니다.

모든 상품 데이터를 추출하여 새로운 인덱스로 생성하는 과정을 ‘전체 색인’이라 하고, 추가, 수정, 삭제된 상품 데이터를 인덱싱하는 과정을 ‘증분 색인’이라고 합니다. 뉴넥스에서는 검색 시스템을 자체 구축하고 관리하기 위해서 각 색인 방식 별로 목표를 설정하였습니다.

1. 전체 색인

검색엔진의 성능은 색인 효율성에 크게 좌우되므로, 클러스터 확장이나 쿼리 최적화를 통해 성능을 개선할 수 있습니다. 그러나 색인의 전 단계인 데이터 추출 및 변환(ETL, Extract, Transform, Load) 과정이 가장 중요한 핵심 과제로 남아 있습니다. 브랜디의 경우, 200만 건 이상의 대용량 상품 데이터를 빠르게 추출하고, 비즈니스 로직을 정확하게 반영하는 데이터 변환 프로세스가 필수적입니다. 이를 달성하기 위해 다음과 같은 기술적 목표를 수립하였습니다.

  • 유연한 스케줄링 및 스크립트 관리
  • 데이터 병렬 처리
  • 각 ETL 단계의 명확한 책임 분리

2. 증분 색인

초기 시스템 구축 시, 기존 검색 시스템의 주기와 데이터 일관성을 유지하는 것이 중요한 목표였습니다. 이를 위해 전체 색인과 증분 색인 프로세스를 동일한 아키텍처로 설계하였으며, 전체 색인은 하루에 한 번, 증분 색인은 일정 시간마다 실행되는 방식으로 운영하였습니다. 이후 검색 시스템이 안정화된 후, 증분 색인의 주기를 더욱 단축해야 할 필요성이 대두되었고, 완전한 실시간 색인은 아니지만 초 단위의 준 실시간(Near real-time) 색인 구현을 준비하였습니다. 증분 색인은 변경된 데이터를 빠르게 적용해야 하기 때문에 실시간성이 무엇보다 중요합니다. 이를 최적화하기 위해 고려해야 할 기술적 목표는 다음과 같습니다.

  • 관리 복잡성을 최소화
  • 데이터 색인 속도 극대화

이 두 가지 색인 방식 모두 자동화된 워크플로와 코드 관리 효율성을 강화하여 유지 보수 편의성을 극대화하고, 관리 및 시스템 리소스를 최적화하는 것을 목표로 검색 시스템을 설계하였습니다.

전체 색인 검색 파이프라인

1. 전체 색인 아키텍처 및 시퀀스

AWS 서비스 설명
Amazon Managed Workflows for Apache Airflow (MWAA) Amazon MWAA는 Apache Airflow를 위한 관리형 서비스로, 이 서비스를 사용하면 현재의 익숙한 Apache Airflow 플랫폼을 사용하여 워크플로를 오케스트레이션할 수 있습니다. 기반 인프라를 관리하는 데서 오는 운영 부담 없이 확장성, 가용성 및 보안을 개선할 수 있습니다.
AWS Glue AWS Glue는 분석, 기계 학습(ML) 및 애플리케이션 개발을 위해 여러 소스에서 데이터를 쉽게 탐색, 준비, 이동 및 통합할 수 있도록 하는 확장 가능한 서버리스 데이터 통합 서비스입니다.
Amazon Simple Storage Service(Amazon S3) Amazon Simple Storage Service(Amazon S3)는 업계 최고 수준의 확장성, 데이터 가용성, 보안 및 성능을 제공하는 객체 스토리지 서비스입니다.
Amazon Opensearch Service Amazon OpenSearch Service는 애플리케이션 모니터링, 로그 분석, 관측성 및 웹 사이트 검색과 같은 사용 사례에서 비즈니스 및 운영 데이터의 실시간 검색, 모니터링 및 분석을 안전하게 지원합니다.

구성도에 따라 스케줄링 실행부터 색인 완료까지의 과정을 순서대로 살펴보겠습니다.

2. 데이터 추출

  • MWAA: 설정된 스케줄에 따라 데이터 추출을 위한 AWS의 ETL 도구인 Glue Job을 실행합니다.
  • Glue: 활성 상품 정보는 데이터베이스(Amazon Relational Database Service, RDS)에 저장되어 있으며, 이 데이터를 Apache Spark를 사용해 추출합니다.

3. 데이터 변환

  • Glue: 추출된 데이터를 바탕으로 비즈니스 로직을 적용하여, JSON 형태의 칼럼방식으로 저장하는 Apache Parquet 파일로 변환합니다.
  • S3: 변환된 Parquet 포맷의 S3 버킷에 저장됩니다.

4. 데이터 색인

  • MWAA: 데이터 추출 Glue Job이 종료되면 트리거가 활성화되어 데이터 색인을 위한 Glue Job이 실행됩니다.
  • Glue: S3 버킷에 저장된 데이터를 읽어 OpenSearch의 Bulk API를 통해 인덱싱 작업을 수행합니다.

💡앞서 아키텍처를 설계할 때의 고려사항들을 모두 충족시킬 뿐만 아니라, 아래와 같은 추가적인 이점도 제공됩니다.

  1. 스케줄링 및 스크립트 변경 용이성
    스케줄링과 스크립트 변경이 용이해야 합니다. MWAA는 워크플로 관리 도구로, 스크립트에서 스케줄링과 트리거 설정이 간편하게 이루어질 수 있습니다. 콘솔 화면을 통해 모든 Directed Acyclic Graph (DAG) 작업의 이력을 편리하게 확인할 수 있어, 검색 파이프라인 관리에 적합합니다.
  2. 수행 이력 및 로그 적재
    수행 이력과 로그를 적절히 기록하고 관리할 수 있어야 합니다. Glue는 수행 이력 확인이 가능하며, ETL 작업을 서버리스로 수행할 수 있어, 데이터 처리 과정에서 발생하는 로그와 이력을 효율적으로 관리할 수 있습니다.
  3. 자동화
    전체 프로세스를 자동화하여 인적 오류를 줄이고, 일관성 있는 데이터 처리를 보장해야 합니다. MWAA와 Glue는 앞서 아키텍처에서 확인된 바와 같이 자동화된 데이터 파이프라인을 구축하는 데 효과적입니다.
  4. 데이터 병렬 처리
    대규모 데이터 처리에서는 병렬 처리의 효율성을 최대한으로 끌어올리는 것이 핵심입니다. Spark는 데이터를 메모리 내에서 처리하며, 여러 노드에 분산하여 병렬로 작업을 수행할 수 있기 때문에 속도가 빠르고 안정성도 뛰어납니다. 테이블을 스캔할 때 직렬 처리(serial processing)를 사용하면 데이터 크기가 증가함에 따라 쿼리 처리 시간도 길어지게 됩니다. 특히 수백 기가바이트(GB) 이상의 데이터를 직렬로 처리할 경우 상당한 시간이 소요될 수 있습니다. 반면, Glue에서 Spark를 사용해 데이터를 병렬로 분산 처리하면 코어 수에 비례해 처리 시간이 획기적으로 단축되며, 동시에 시스템의 안정성도 보장됩니다. 병렬 처리는 대용량 데이터 분석을 훨씬 빠르고 효율적으로 수행할 수 있는 중요한 방법입니다.

이러한 이유로 MWAA와 Glue를 선택하게 되었고 이처럼 AWS 관리형 서비스를 적극 활용함으로써 효율적이고 관리가 용이한 파이프라인을 구축할 수 있었습니다. 또한 데이터 엔지니어링 관점에서 각 프로세스를 단계적으로 나눈 덕분에 데이터 품질을 보장할 수 있을 뿐만 아니라, 하나의 단계에서 문제가 생겨도 모든 프로세스가 실패하는 일이 없습니다. 문제가 발생한 단계만 다시 실행하면 되기 때문입니다.

5. 스크립트 예시

실제 사용하고 있는 코드를 간략화하여 MWAA와 Glue 스크립트 예시를 살펴보겠습니다.

with DAG(
        dag_id="dag_id",
        tags=["tag1", "tag2"],
        default_args={
            'owner': 'brandi',
            'start_date': datetime(2023, 9, 12, 13, 0, 0),
            'on_failure_callback': slack_alert_bot.alert_fail_message,
            'on_success_callback': slack_alert_bot.alert_success_message,
            'retries': 1
        },
        catchup=False,
        # cron 방식의 스케줄링 설정 
        schedule_interval="0 20 * * SAT"
) as dag:

    task = trigger_glue_trigger_job(
        group_id='group_1',
        job_name="extract_job",
        # 현재 작업 종료 시 실행되는 trigger job name 
        trigger_dag_id="trigger_indexing_job",
        script_args={
            '--arg_1': '1',  
            '--arg_2': '2',  
            '--arg_3': '3'
        },
        trigger_conf={
            'trigger_params_1': '1',
            'trigger_params_2': '2'
        }
    )

    task

MWAA DAG 스크립트: cron 방식의 스케줄링 설정과 해당 작업 종료 후 실행되는 트리거를 설정합니다.

# 데이터 병렬처리 시 읽어올 파티션 수를 설정하고 파티셔닝 될 때의 기준 열, 범위 설정
_products = _reader.option("dbtable", "스키마.테이블명") \
    .option("numPartitions", 10) \
    .option("partitionColumn", "id") \
    .option("lowerBound", _lower_bound) \
    .option("upperBound", _upper_bound) \
    .load() \
    .select(
    col("ID"),
    col("NAME"),
    col("DISPLAY_TYPE")
)

# 비즈니스 로직을 추가한 데이터 변환 
_product_df = _products \
    .select(
        col("ID").cast(StringType()).alias("id"),
        col("NAME").alias("product_name"),
        when(col("DISPLAY_TYPE") == '1', True).otherwise(False).alias("is_display") \

# 파티셔닝 설정 
_product_df.repartition(128).write.mode('overwrite').parquet(_output_path)

추출(Extract) Glue 스크립트: 원천 데이터를 읽어올 때 파티션을 설정하여 데이터 병렬 처리하고, 데이터 변환 후 데이터 프레임(DataFrame)을 생성합니다. 그리고 데이터 프레임을 128개로 파티셔닝하여 S3 출력 경로(output path)에 저장합니다.

_products.rdd.repartition(300)
    .mapPartitions(convert_to_json)
    .coalesce(1)
    .foreachPartition(partial(bulkApi_전송_메서드, host=검색엔진_호스트, index=인덱스명))

색인(Indexing) Glue 스크립트: 데이터 프레임을 RDD (Resilient Distributed Dataset)로 변환 후 300개의 파티션으로 분할하여 OpenSearch에 벌크로 인덱싱 진행합니다.

위의 스크립트를 간단히 살펴보면, MWAA DAG 스크립트에서 스케줄러와 작업(task)을 지정할 수 있다는 점을 확인할 수 있습니다. 또한, 데이터를 읽고 쓸 때 Glue에서 Spark를 활용해 데이터 파티셔닝을 수행하여 병렬 처리를 최적화할 수 있습니다. 인덱싱 단계에서는 검색엔진 클러스터의 사양에 맞춰 리파티셔닝(repartitioning) 파라미터를 조정하여 벌크 인덱싱 작업의 배치 크기를 효율적으로 관리할 수 있습니다.

6. 콘솔 화면 예시

Airflow 콘솔 화면 – DAG들이 각각의 작업으로 나열되어 한눈에 파악 가능

Glue 콘솔 화면 – 각 작업 별 수행이력과 로그 확인 가능

증분 색인 검색 파이프라인

증분 색인의 핵심 과제인 실시간성을 반영하기 위해 애플리케이션 알림 서비스인 Amazon Simple Notification Service (SNS)와 메시지 큐 서비스인 Amazon Simple Queue Service (SQS)를 선택했습니다. 이미 도입된 시스템이어서 추가 비용이 들지 않고, 관리 포인트가 적어 비용과 리소스를 모두 절약할 수 있다는 큰 장점이 있었기 때문입니다. 또한, SQS 이벤트가 발생할 때마다 AWS Lambda를 트리거하도록 설정해 데이터 추출과 색인을 한 번에 처리할 수 있도록 구성했습니다.

증분 색인 아키텍처

AWS 서비스 설명
Amazon Simple Notification Service (SNS) Amazon Simple Notification Service (SNS)는 A2A와 A2P의 두 가지 방식으로 알림을 전송합니다. A2A는 분산된 시스템, 마이크로서비스 및 이벤트 중심의 서버리스 애플리케이션 간에 처리량이 많은 푸시 기반의 다대다 메시징을 제공합니다. A2P 기능을 사용하면 SMS 텍스트, 푸시 알림, 이메일을 통해 고객에게 메시지를 전송할 수 있습니다.
Amazon Simple Queue Service (SQS) Amazon Simple Queue Service (SQS)를 사용하면 메시지 손실을 우려하거나 다른 서비스를 제공할 필요 없이 소프트웨어 구성 요소 간에 어떤 볼륨의 메시지든 전송, 저장 및 수신할 수 있습니다.
AWS Lambda AWS Lambda는 이벤트에 대한 응답으로 코드를 실행하고 컴퓨팅 리소스를 자동으로 관리하는 컴퓨팅 서비스로, 아이디어를 최신 프로덕션 서버리스 애플리케이션으로 전환하는 가장 빠른 방법입니다.

이제 각 시퀀스를 따라가며 과정을 살펴보겠습니다.

증분 색인 시퀀스: 상품변경 이벤트 발생 → SNS 이벤트 게시→ SQS 큐 추가 → Lambda 실행

데이터 추출

  • SNS, SQS
    변경된 상품 번호가 SQS에 메시지 큐 이벤트 형태로 추가됩니다.
  • Lambda
    큐에 추가된 변경된 상품 번호를 감지하여 실행됩니다. Lambda 함수에서 RDS에 쿼리를 실행해 해당 상품 번호에 대한 정보를 읽어옵니다. Python과 Pandas를 사용해 추출된 정보로 데이터프레임을 생성합니다.

데이터 색인

  • Lambda
    생성된 데이터프레임을 OpenSearch의 Bulk API로 전달해 색인 작업을 수행합니다.

이 구성에서 몇 가지 중요한 고려 사항이 있습니다.

  1. 변경 데이터가 수천 건 이상 발생하는 경우
  2. Lambda 함수의 동시 실행 문제

변경된 데이터가 대량으로 발생하거나, Lambda 함수의 동시 실행 요청이 과도하게 많아질 경우, 데이터베이스가 과부하되어 대량의 쿼리를 처리하기 어려워질 수 있습니다. 특히 변경 데이터가 천 단위 이상으로 증가하는 경우, 기존의 구조보다는 CDC(Change Data Capture) 방식을 사용해 색인을 구성하는 것이 더 적합합니다. 브랜디는 이 문제를 해결하기 위해 Lambda 함수 내에서 메시지큐 이벤트를 청크 단위로 분할(chunking)하여 쿼리를 전송하도록 설계했으며, 동시에 Lambda의 동시 실행 옵션을 조정하여 이슈를 해결할 수 있었습니다.
증분 색인 아키텍처를 고도화하면서, 기존 30분이었던 주기를 1분 이내로 줄이는 성과를 달성했고 Lambda 도입을 통해 서버리스(serverless) 구조의 장점을 활용하여 인프라 관리 부담을 줄이고, 빠른 처리와 운영 효율성을 극대화할 수 있었습니다. 주기가 단축됨에 따라 검색엔진의 활용 범위도 단순한 검색 메뉴를 넘어 다양한 영역으로 확장되었습니다.
이전에는 검색 기능에서만 사용되던 검색엔진이 이제는 여러 기능에서 핵심적인 역할을 담당하게 되었습니다. 실시간성을 갖추게 되면서 단순히 검색 메뉴뿐만 아니라, 카테고리 페이지에도 검색엔진을 도입해 성능을 향상시키고, 데이터베이스의 부담을 줄이는 중요한 개선을 이룰 수 있었습니다.

검색 API

지금까지는 데이터 추출과 색인에 대한 검색 파이프라인의 아키텍처만 기술하였습니다. 하지만 검색 시스템은 여기서 끝나지 않고 검색 질의 및 결과 반환 과정까지 포함해야 완성됩니다. 일반적으로 검색엔진(e.g., Opensearch)들은 엔진에 직접 쿼리를 요청하여 결과를 받는 방식으로 운영됩니다.

검색엔진에 직접 쿼리를 요청하는 방식을 사용할 수도 있지만, 이 방식에는 한 가지 큰 문제가 있었습니다. 색인 필드나 요구 사항이 변경될 때마다 검색엔진을 사용하는 모든 팀이 각각 쿼리를 수정해야 한다는 점 이었습니다.

이러한 문제를 해결하기 위해, 검색 API를 개발하게 되었습니다. 검색 API를 도입하면서 얻게 된 장점은 매우 큽니다. 복잡한 쿼리를 추상화하여 제공함으로써 사용자의 편리성이 강화되었고, 시스템 확장성도 확보할 수 있었습니다. 또한 검색엔진을 변경해도 검색 API의 클라이언트만 변경하면 되기 때문에 사용자가 이를 인지하거나 코드를 변경할 필요가 없습니다.

검색엔진 쿼리 vs 추상화된 검색 API

{
  "from": 0,
  "size": 10,
  "sort": [
    {
      "time": "desc"
    },
    "_score"
  ],
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "원피스",
            "fields": [
              "index.text",
              "index.keyword"
            ],
            "operator": "and"
          }
        }
      ],
      "filter": {
        "bool": {
          "must": [
            {
              "bool": {
                "should": [
                  {
                    "match": {
                      "category_id": "1"
                    }
                  },
                  {
                    "match": {
                      "category_id": "2"
                    }
                  }
                ],
                "minimum_should_match": 1
              }
            },
            {
                   "range" : {
                    "price": {
                            "gte": 1000,
                            "lt": 20000
                        }
                    }
            },
            {
              "match": {
                "id": "11111"
              }
            }
          ]
        }
      }
    }
  }
}

검색 쿼리 – 깊이(depth)가 깊고 복잡

{
    "from" : 0,
    "size" : 10,
    "query": "원피스",
    "filter" : {
        "category": {
            "id" : ["1", "2"]
        },
        "id" : "11111",
        "price" : {
            "min" : 1000,
            "max" : 20000
        }
    },
    "sort" : "time"
}

추상화된 검색 API – 검색 API 파라미터가 추상화되어 사용자의 편리성 증대

대부분의 검색엔진은 언어별 SDK를 제공하기 때문에 개발 작업도 비교적 수월합니다. 이로써 검색 API 개발이 완료됨에 따라, 전체 검색 시스템 구축이 완성되었습니다.

모니터링

검색 파이프라인이 모두 자동화되었지만, 운영 중에는 항상 예기치 않은 상황이 발생할 수 있습니다. 따라서 기능 개선과 문제 해결을 위해 모니터링이 필수적입니다.
그래서, 검색 시스템의 모든 서비스를 AWS 기반으로 구축하여 모니터링을 용이하게 하고, 이상 현상이 발생할 경우 빠르게 인식할 수 있도록 모든 알림을 Slack으로 통합하여 수신하도록 설정했습니다.

검색 데이터 파이프라인

전체 색인: MWAA는 워크플로우 관리 서비스이기 때문에, 실행 중인 작업에 이상이 발생하면 이를 감지할 수 있습니다. 오류가 발생할 경우, MWAA의 DAG 스크립트에 Slack을 연결해 두면, 작업이 정상적으로 종료되지 않았을 때 지정한 문구로 알림을 전송할 수 있습니다.
증분 색인: Lambda 함수에서도 Slack과 연동할 수 있으며, 람다 함수가 정상 또는 오류 결과를 반환합니다. 오류 발생 시, 스크립트에서 지정된 Slack으로 알림을 송신할 수 있습니다.

Glue Job 실패를 경고하는 Slack 알림 봇

검색엔진 클러스터 상태 및 검색 API

Datadog: Datadog은 AWS 관리형 서비스의 상태를 연동할 수 있습니다. 따라서 검색 질의가 정상적으로 들어오는지 확인할 수 있는 설정을 통해, 문제 발생 여부를 모니터링할 수 있습니다.

이렇게 모니터링과 이슈 발생 시 감지 및 대응이 편리하게 설정되어, Slack 메신저를 통해 문제를 빠르게 인식하고 조치할 수 있습니다.

클라우드 vs 온프레미스

앞서 클라우드 환경에서의 데이터 파이프라인, 검색 API, 모니터링을 포함한 검색 파이프라인의 아키텍처를 살펴보았습니다. 이 중, 데이터 파이프라인 구성 시 온프레미스로만 구축했을 경우를 비교해 보겠습니다.

AWS 기반에서의 검색 파이프라인

온프레미스 환경에서의 검색 파이프라인

온프레미스 환경에서의 검색 파이프라인 아키텍처 자체는 간단해 보이지만, 실제로는 상당한 노동력이 필요합니다. 데이터 추출 및 변환을 Spring Batch로 진행한다고 가정해 보겠습니다.

데이터베이스

100GB 이상의 데이터를 조회하는 동안 Spring Batch는 데이터베이스와 지속적으로 연결됩니다. 이를 통해 배치 작업의 시간을 조정하거나 데이터베이스의 사양을 고려하는 등의 추가적인 관리가 필요합니다.
💡 Spark를 활용하면 모든 테이블을 메모리에 올린 후 처리할 수 있어 데이터베이스의 부담을 줄일 수 있습니다.

Spring Batch

데이터 파이프라인 대부분의 작업이 Spring Batch에서 수행됩니다. 스케줄러 설정, 데이터 조회, 데이터 파싱, 비즈니스 로직 처리, 검색엔진 인덱싱 등이 포함됩니다. 간단한 예시 코드는 다음과 같습니다:

@Component
public class BatchScheduler {

    @Autowired
    private JobLauncher jobLauncher;

    @Autowired
    private Job indexUserJob;

    // 매일 새벽 1시에 실행되는 스케줄링 설정 (CRON 표현식)
    @Scheduled(cron = "0 0 1 * * ?")
    public void runBatchJob() throws Exception {
        JobParameters params = new JobParametersBuilder()
                .addLong("time", System.currentTimeMillis())
                .toJobParameters();
        
        jobLauncher.run(indexUserJob, params);
    }
}

cron 형식으로 스케줄링을 설정합니다.

@Configuration
public class BatchConfig {

    @Bean
    public JdbcCursorItemReader<Product> reader(DataSource dataSource) {
        return new JdbcCursorItemReaderBuilder<Product>()
                .name("productItemReader")
                .dataSource(dataSource)
                .sql("SELECT id, name FROM products")
                .rowMapper((rs, rowNum) -> {
                    Product product = new Product();
                    product.setId(rs.getLong("id"));
                    product.setName(rs.getString("name"));
                    return product;
                })
                .build();
    }
}

데이터베이스에서 상품(Product) 데이터를 읽어옵니다. 데이터 추출 과정입니다.

public class ProductProcessor implements ItemProcessor<Product, Product> {

    @Override
    public Product process(Product product) throws Exception {
        // 추가적인 파싱이나 데이터 처리 로직을 여기에 작성
        return product;
    }
}

데이터를 변환하는 역할을 합니다.

@Component
public class ElasticsearchItemWriter implements ItemWriter<Product> {

    @Autowired
    private RestHighLevelClient client;

    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void write(List<? extends Product> items) throws Exception {
        for (Product product : items) {
            IndexRequest request = new IndexRequest("products_index")
                    .id(product.getId().toString())
                    .source(objectMapper.writeValueAsString(product), 
                            XContentType.JSON);

            client.index(request, RequestOptions.DEFAULT);
        }
    }
}

변환한 데이터를 elasticsearch에 인덱싱 합니다.

@Configuration
@EnableBatchProcessing
public class BatchJobConfig {

    @Bean
    public Job indexProductJob(JobBuilderFactory jobs, Step step1) {
        return jobs.get("indexProductJob")
                .incrementer(new RunIdIncrementer())
                .flow(step1)
                .end()
                .build();
    }

    @Bean
    public Step step1(StepBuilderFactory stepBuilderFactory, 
                      JdbcCursorItemReader<Product> reader, 
                      ProductProcessor processor, 
                      ElasticsearchItemWriter writer) {
        return stepBuilderFactory.get("step1")
                .<Product, Product>chunk(10)
                .reader(reader)
                .processor(processor)
                .writer(writer)
                .build();
    }
}

배치 작업과 스텝을 정의합니다. 앞서 정의한 클래스들을 연결하여 추출-변환-색인을 처리합니다.

아주 단순한 기본 코드임에도 불구하고 고려해야 할 요소들이 많습니다. 예시 코드에는 작성되지 않았지만, 병렬 처리를 위한 멀티스레딩 구성을 추가해야 하며, 트랜잭션 관리와 청크 크기 설정도 중요한 부분입니다. 작업별로 철저한 예외 처리를 수행하고, 오류 발생 시 문제 발생 단계와 원인을 정확히 파악할 수 있도록 로그를 체계적으로 관리하는 것도 필수적입니다. 모든 단계가 Spring Batch에서 실행되다 보니, 데이터 추출 로직과 비즈니스 로직을 명확하게 분리하기가 어렵습니다. 또한, 요구사항이 변경되면 빌드와 배포 과정을 다시 거쳐야 하며, 오류가 발생하면 모든 작업을 처음부터 다시 시도해야 하는 문제도 발생할 수 있습니다. 시간이 지남에 따라 코드가 블랙박스화될 가능성도 있으며, 관리해야 할 작업이 1n개 이상으로 늘어나면 추가적인 인력과 리소스가 필요할 것입니다.

MWAA와 Glue를 사용하면 스케줄링과 작업을 완전히 분리할 수 있습니다. 또한 데이터 조회와 비즈니스 로직을 각각 독립적인 작업으로 분리하면 복잡도가 줄어들고 코드가 간소화됩니다. 콘솔 화면에서 작업을 확인할 수 있기 때문에 작업의 수가 확장되어도 인력이 필요하지 않습니다.

온프레미스 검색엔진

마스터 노드, 데이터 노드, 코디네이터 노드 등 클러스터의 노드 옵션을 직접 설정해야 합니다. 기본 콘솔 도구가 없기 때문에 Kibana에서 모든 옵션과 대시보드를 구성해야 합니다. 관리형 검색엔진 서비스를 사용하면 클러스터 성능을 모니터링하고 자동으로 최적화하여 성능 문제를 예방할 수 있습니다. 또한, 업그레이드와 패치를 자동으로 진행하여 관리 부담을 줄여줍니다.

즉, 온프레미스 기반의 서비스 환경에서 코드 중심으로 설계를 진행하면 관리 포인트가 많아질 뿐만 아니라, 병렬 처리, 트랜잭션 관리, 예외 처리 등 고려해야 할 사항이 많아집니다. 이러한 복잡성으로 인해 상대적으로 더 많은 인력과 리소스가 필요하게 됩니다.

마무리

뉴넥스의 검색 파이프라인은 효율적이고 편리한 관리를 통해 운영 리소스를 최소화하는 데 초점을 맞춰 설계되었습니다. 만약 AWS 관리형 서비스를 사용하지 않았다면, 각 프로세스마다 별도의 구축 및 운영 인력이 필요했을 것으로 보입니다. 예를 들어 cron 배치 시스템, 빠르고 안정적인 데이터 추출 시스템, 검색엔진 클러스터링, 모니터링 등 다양한 요소를 직접 관리해야 했을 것입니다.

그러나 AWS 서비스를 활용한 ETL 파이프라인을 구성함으로써, 데이터 처리를 유연하게 조정할 수 있었고 모든 단계를 자동화해 관리 부담을 크게 줄일 수 있었습니다. 또한, 비즈니스와 요구사항이 변동될 때에도 신속하게 대응할 수 있다는 점이 큰 장점이었습니다. 스크립트를 바로 수정하고 테스트할 수 있어, 별도의 빌드 과정 없이도 빠르게 조정이 가능하다는 점이 매우 편리했습니다.

자동화와 편리성 뿐만 아니라 각 단계의 역할을 명확히 분리하여 유지 보수의 효율을 높일 수 있었고 AWS 환경에서 병렬 처리를 적극적으로 활용하여 속도를 보장할 수 있었습니다. 이처럼 AWS 서비스를 활용하면 적은 리소스로도 충분히 자동화와 유연성을 기반으로 한 검색 데이터 파이프라인을 구축하여 안정적으로 운영할 수 있습니다.

최성조

최성조

뉴넥스 AI검색팀에서 Data Engineer로서 다양한 데이터 관련 업무를 담당하고 있습니다. 주요 역할은 상품 추천을 위한 피처 엔지니어링, 광고 성과 리포팅을 위한 데이터 파이프라인 운영, 그리고 검색 서비스에 필요한 데이터 파이프라인 구축 및 관리입니다.

신누리

신누리

뉴넥스의 AI검색팀에서 검색 엔지니어로 일하고 있습니다. 주요 업무는 브랜디, 하이버, 서울스토어의 검색 서비스를 담당하는 것입니다. 구체적으로는 검색 서비스에 필요한 데이터 파이프라인을 구축하고 관리하며, 뉴넥스의 비즈니스 인텔리전스에 적합한 검색 API를 개발하고 운영하는 일을 맡고 있습니다.

Jungseob Shin

Jungseob Shin

신정섭 Solutions Architect는 다양한 분야의 Backend 서버 개발 경험을 바탕으로 Startup 고객이 비즈니스 목표를 달성하도록 AWS 서비스의 효율적인 사용을 위한 아키텍처 설계 가이드와 기술을 지원하는 역할을 수행하고 있습니다. 컨테이너와 서버리스 기술에 관심이 많습니다.