팀 내 전처리 프레임워크 dagster 도입기

안정적인 문서 전처리 시스템을 위한 dagster 도입 과정

Jave • Jave Jang, AI Team.

  • 엔지니어링

안녕하세요. AI 팀의 ML 엔지니어 제이브입니다.

이번에 팀에서 워크플로우 오케스트레이션 도구인 dagster를 도입하고 운영 중에 있는데요. 이번 글에서는 어떤 이유로 dagster를 도입하게 되었는지, 그리고 도입 이후 시스템에 어떤 변화가 있었는지 공유하려 합니다.

상담 에이전트를 위한 다양한 문서 전처리

채널톡에서는 상담 에이전트인 ALF 서비스를 개발하고 운영하고 있습니다. ALF가 가진 강력한 기능 중 하나는 각 고객사가 보유하고 있는 자체적인 지식(사내 규정, 제품 매뉴얼, FAQ 등)을 ALF가 활용하여 답변하는 기능이라고 할 수 있습니다. ALF의 지식 탭에 PDF, 엑셀, 웹사이트 등 다양한 형태의 문서를 업로드하면, ALF가 지식 탭에 저장된 문서를 기반으로 답변할 수 있게 됩니다. (자세한 내용은 채널톡 가이드를 참고하세요!)

이러한 기능을 채널톡에서는 RAG(Retrieval Augmented Generation)이라고 부르는 시스템을 통해 제공하고 있습니다. RAG 시스템에서는 앞선 PDF, 엑셀, 웹사이트 등의 다양한 형식의 문서들을 언어 모델(LLM)이 이해할 수 있는 형태로 저장하는 과정이 필수적인데요. 이 부분에서 dagster를 도입함으로써 안정적인 문서 처리 파이프라인을 구성할 수 있었습니다.

이 문서 처리 파이프라인의 흐름을 간략하게 설명하면 아래와 같습니다.

  1. 파싱(parsing): 다양한 포맷의 데이터로부터 텍스트, 메타 데이터 등 언어 모델이 활용할 수 있는 형태의 데이터를 읽습니다.

  2. 청킹(chunking): 언어 모델이 효율적으로 찾고 활용할 수 있도록 적절한 크기로 분할합니다.

  3. 임베딩(embedding) 및 인덱싱(indexing): 의미론적 검색(semantic search)을 위해 벡터로 변환하는 임베딩 과정과 이후 검색 단계를 위해 벡터 디비에 저장합니다.

기존 시스템

초기 파이프라인은 위의 파이프라인 과정을 수행하기 위해 FastAPI 위에서 문서 전처리 요청을 받아 처리하는 형태로 구성하여 운영했습니다.

  1. 클라이언트로부터 문서 전처리 요청을 받습니다.

  2. 서버는 문서 전처리 작업에 필요한 메타 데이터들과 함께 FastAPI의 background tasks에 추가합니다.

  3. 해당 작업이 백그라운드로 실행되고, 실행 결과를 클라이언트로 웹훅 알림을 보냅니다.

운영하면서 발생한 문제들

트래픽이 적었던 초기에는 없었던 문제들이 릴리즈 이후 사용하는 고객사들이 증가하면서 발생하기 시작했습니다.

  1. 리소스 경합

같은 인스턴스에서 여러 문서에 대한 전처리를 동시에 처리하기 때문에 처리하는데 걸리는 시간이 늘어나 타임아웃이 발생하거나, 심한 경우 OOM(Out of Memory)으로 파드가 죽고 다시 뜨는 문제가 발생했습니다.

파드가 죽으면서 처리 중인 모든 요청이 취소되면서 불필요한 재시도 요청 또한 증가하게 되었습니다.

  1. 불안정성

같은 파일에 대해서도 문제 없이 처리되는 경우도 있었고, 위 리소스 경합 문제로 실패하는 경우도 발생했습니다. 이로 인해 고객 입장에서는 업로드한 문서가 준비 상태에 도달하는데까지 걸리는 시간을 예측하기 어렵게 만들었습니다.

  1. 디버깅의 어려움

이러한 불안정한 환경에서 문제가 발생하면 로깅 메시지에만 의존해 문제를 해결해야 했습니다. 뭔가 문제가 생겨 실행이 실패하면 로깅 메시지를 뒤져가며 문제가 발생한 지점을 파악해야 했고, 로깅 메시지가 누락된 경우 매번 해당 지점에 로깅 메시지를 남기는 로직을 추가해야 했습니다.

또한 문제가 발생한 지점을 찾더라도 해당 지점부터 재시도가 불가능하여 처음부터 다시 시도하거나 고객이 다시 파일을 업로드해야 했습니다.

워크플로우 오케스트레이션

프로덕션에서 운영하면서 이러한 문제점들을 식별하고, 고객사가 늘어나고 지원하는 문서 형식이 늘어나기 이전에 이 문제를 해결해야 다음 단계로 넘어갈 수 있다는 판단 하에 워크플로우 오케스트레이션 프레임워크들을 리서치하고 PoC를 진행하기 시작했습니다.

워크플로우 오케스트레이션이란 복잡한 데이터 처리 작업들의 실행 순서, 의존성, 스케줄링을 관리하고 자동화하는 작업을 의미합니다. 어떤 작업이 끝나면 다음 작업으로 무엇을 시작하고 실패한 경우에는 어떻게 처리될 것인지 등 전체적인 워크플로우의 흐름을 조율하고 관리합니다.

널리 알려진 프레임워크로는 다음과 같은 것들이 있습니다.

구분

Airflow

Dagster

Prefect

Temporal

핵심 철학

작업 중심 (Task-based) 스케줄링

데이터 자산 중심 (Asset-based)

파이썬 코드 중심 (Code-first), 동적 흐름

내구적 실행 (Durable Execution)

주 사용처

전통적인 ETL/ELT 데이터 파이프라인

복잡한 데이터 플랫폼, 데이터 엔지니어링

데이터 사이언스, ML 파이프라인, 일반 자동화

마이크로서비스 오케스트레이션, 비즈니스 트랜잭션

정의 방식

정적 DAG (Python)

소프트웨어 정의 자산 (Python)

Python 함수 + 데코레이터

워크플로우 코드 (Go, Java, PHP, TS, Python 등)

장점

• 압도적인 생태계와 레퍼런스• 다양한 클라우드 서비스와 연동 용이

• 강력한 테스트/디버깅 환경• 데이터 리니지(계보) 파악 용이• 개발 생산성 높음

• 가장 쉬운 초기 설정• 직관적인 파이썬 코드 스타일• 동적인 작업 처리 유연함

• 무한 재시도 및 완벽한 복구• 분산 시스템의 복잡성 제거• 다양한 언어 SDK 지원

단점

• 무겁고 복잡한 설정 (Scheduler 등)• 로컬 테스트가 어려움• 데이터 간의 주고받기가 불편함 (XCom)

• 새로운 개념(Op, Asset 등) 학습 곡선 있음• 코드가 다소 장황해질 수 있음

• 버전 간(1.0 vs 2.0) 호환성 이슈 이력• 대규모 스케일에서 유료 모델 고려 필요

• 가장 높은 학습 난이도• 별도의 서버(Cluster) 구축/운영 복잡• 단순 데이터 파이프라인엔 과소비

위 4개의 프레임워크를 기준으로 우리의 워크플로우 환경과 적합할지 비교해보았을 때 다음과 같이 요약할 수 있습니다.

  • Airflow: 배치 처리에 특화되어 있어 이벤트 기반의 실행 구조를 가진 우리의 환경과 적합하지 않음.

  • Temporal: 액티비티 간 페이로드 크기 제한이 있어 큰 데이터를 주고 받을 수 있는 우리의 워크플로우 형태에 적합하지 않음.

  • Prefect: 빠른 러닝커브로 테스트 시에 만족스러웠으나 OSS 환경에서 RBAC(Role Based Access Control) 기능을 지원하지 않아 대시보드에 접근 가능한 누구나 워크플로우 자체를 삭제해버릴 위험성이 있었음. 또한 누가 무엇을 변경했는지 추적하기도 어려움.

  • Dagster: 에셋(asset)이라고 하는 데이터 자산 중심의 DAG 표현 방식. Prefect과 동일하게 RBAC을 지원하지 않지만, 코드 로케이션이라는 형태로 워크플로우 코드가 분리되어 있어 대시보드에서 삭제될 위험성이 상대적으로 적음.

이 중 dagster가 데이터 자산 중심의 표현 방식이 우리의 문서 전처리 워크플로우와 적합하다고 판단했고, OSS로 운영 시 워크플로우 삭제 등의 위험성이 적어 채택하게 되었습니다.

변경된 시스템 구조

Dagster를 도입하면서 아래와 같은 구조로 시스템을 변경하게 되었습니다.

  1. FastAPI 기반의 서버가 클라이언트로부터 문서 전처리 요청을 받습니다. 이 서버는 일종의 게이트웨이 역할을 하면서 외부 클라이언트가 dagster 시스템을 알 필요 없이 전처리 요청만 보낼 수 있도록 합니다.

  2. 요청을 받은 서버는 내부에서 dagster 서버로 워크플로우 작업 실행 요청을 보냅니다.

  3. 워크플로우 실행이 끝나면 dagster hook을 통해 클라이언트로 결과를 보냅니다.

K8sRunLauncher를 통한 리소스 격리

사내 인프라가 쿠버네티스 기반으로 구축되어 있어 워크플로우 실행을 K8sRunLauncher로 지정하여 워크플로우 실행(Run)마다 별도의 파드를 생성하여 실행하도록 구성했습니다.

K8sRunLauncher를 사용하게 되면 워크플로우의 실행이 독립적으로 실행되기 때문에 다른 워크플로우 실행에 영향을 미치지 않게 됩니다. 따라서 특정 문서가 문제를 발생하더라도 해당 문서만 실패할 뿐 다른 문서 처리 작업은 문제 없이 수행할 수 있게 됩니다.

만약 워크플로우 실행보다 더 세밀하게 스텝 별로 리소스 격리를 원하는 경우 k8s_job_executor를 사용하면 워크플로우 내에서도 각 에셋 별로 별도의 파드에서 실행되게 구성할 수 있습니다.

현재 팀에서는 워크플로우 실행 단위의 격리로만 충분해 K8sRunLauncher를 사용하고 있지만, 추후에 다양한 워크플로우 확장을 고려하여 에셋 단위의 분리도 적극 고려하고 있습니다.

Code Location 분리

Dagster는 크게 webserver, daemon, code-location 으로 구성되어 있습니다.

컴포넌트

역할

Webserver

대시보드 UI를 제공하고, 워크플로우 실행 요청을 받는 GraphQL API 서버입니다. 사용자는 이 대시보드를 통해 워크플로우 상태를 모니터링하고 수동 실행을 트리거할 수 있습니다.

Daemon

백그라운드에서 실행되며 스케줄, 센서, Run Queue 등을 관리합니다. 예약된 워크플로우를 실행하거나, 큐에 쌓인 작업을 워커에 할당하는 역할을 합니다.

Code Location

실제 워크플로우 코드(Asset, Job, Schedule 등)가 정의된 Python 모듈입니다. Webserver와 Daemon은 Code Location을 참조하여 어떤 워크플로우가 있는지 파악하고 실행합니다. 여러 개의 Code Location을 등록하여 워크플로우를 논리적으로 분리할 수 있습니다.

여기서 상대적으로 수정이 적은 웹서버와 데몬은 쿠버네티스 환경에서 Helm Chart를 통해 배포하고, 코드 로케이션은 별도의 애플리케이션으로 분리하여 운영했습니다.

코드 로케이션은 워크플로우 코드를 포함하고 있기 때문에 잦은 변경 및 추가로 업데이트 주기가 짧습니다. 또한 별도의 애플리케이션으로 분리하여, 팀원들이 작업 후 머지하면 github actions 기반의 CI/CD 작업을 통해 코드 로케이션만 새로 배포되도록 구성하였습니다. 이를 통해 dagster 인프라 배경 없이 워크플로우 개발에만 집중할 수 있는 환경을 제공할 수 있었습니다.

Plaintext
# values.yaml

dagster:
  dagsterWebserver:
    ...
    workspace:
      enabled: true
      servers:
        - host: ...
          name: ...
          port: 4000

회고

1. 최소한의 변경으로 안정성 확보

기존 시스템은 한 인스턴스에서 여러 개의 문서 전처리 요청을 수행하기 때문에 리소스 문제가 발생할 때 마다 인스턴스의 스펙을 늘리는 방식(scale-up)으로 대응했었습니다. 이로 인해 고정 비용은 증가하면서도, 근본적인 원인은 해결하지 못해 언제든 다시 발생할 가능성이 다분했습니다.

반면 dagster를 도입하면서 아래와 같은 이유로 안정성을 확보할 수 있었습니다.

  • 워크플로우 실행 단위로 파드가 따로 떠서 실행하기 때문에 리소스 경합 문제 자체가 발생하지 않습니다.

  • 요청이 몰려도 dagster 내부적으로 큐에 작업을 쌓아놓고 처리하기 때문에 한 파드가 죽어도 다른 작업에 영향을 미치지 않습니다.

  • 인스턴스 스펙이 부족한 경우 해당 작업에 대한 리소스 할당치만 늘리면 되기 때문에 자원낭비가 적습니다.

특히 리소스 할당에 있어서 문서 크기나 포맷에 따라 유동적으로 조절할 수 있다면 엄청난 수의 요청에 대하여 많은 비용을 절약할 수 있기 때문에 큰 이점이 될 수 있었습니다.

이러한 안정성 덕분에 실제로 하루 1만 건이 넘는 무거운 전처리 요청을 수행하면서도 별 이슈 없이 처리할 수 있게 되었습니다.

2. 관측 가능성 향상

항상 로그 메시지에만 의존했던 불편한 디버깅 과정에서 dagster 대시보드를 통해 단일 혹은 전체 워크플로우에 대한 진행 상황을 한 눈에 파악할 수 있고, 각 스텝 별 소요 시간과 메타데이터까지 쉽게 추적할 수 있게 되었습니다.

3. OSS 환경의 한계

dagster를 포함한 여러 프레임워크들이 OSS 환경에서 RBAC을 지원하지 않아 대시보드에서의 접근 권한을 세밀하게 제어하기 어려워, prefect와 dagster 중 dagster를 선택하는 계기가 되었으나 최선의 선택이였는지는 조금 더 고민해볼만한 문제였습니다.

현재는 SSO 방식으로 관련된 작업자만 대시보드에 접근할 수 있도록 최소한의 방어 장치를 설치해두었으나 팀 규모가 커지거나 다른 팀까지의 확장을 고려하면, 권한 관리에 대한 심도 있는 고민이 필요하다 느꼈습니다.

4. 추가 모니터링 연동

대시보드 UI는 편리하고 많은 정보를 제공했으나, 대시보드만으로는 모든 것을 충족할 수 없었고 결국 외부 모니터링 시스템을 연동하는 과정이 필요했습니다. 초기 PoC 단계에서 대시보드가 주는 이점이 크다 생각했으나, 결국 세밀한 모니터링을 위해 추가 리소스를 피할 수는 없었습니다.

마치며

dagster 도입 과정을 거치면서 불안정한 기존 시스템의 문제를 해결하고 운영 효율성을 높일 수 있었습니다.

특히 파드 단위로 분리하여 워크플로우를 실행함으로써 리소스를 격리하고, 대시보드를 통해 관측 가능성을 얻어 안정성을 가질 수 있었던게 가장 큰 성과였습니다.

돌이켜보면 dagster를 도입하기로한 결정은 모든 상황에서의 정답보다는 최선의 선택에 가까웠습니다. 주어진 문제에서 가장 적합한 결정을 내리고, 이걸 설득하는 과정이 도입에 있어서 가장 중요한 부분이었던 것 같습니다.

조금 더 운영 기간을 거치면서 다음 번에는 더욱 더 안정적인 시스템 구조와 함께 찾아뵙겠습니다. 감사합니다!

We Make a Future Classic Product