Tag: 튜토리얼

  • 실무에서 바로 쓰는 RAG 파이프라인 구축 단계별 튜토리얼

    실무에서 바로 쓰는 RAG 파이프라인 구축 단계별 튜토리얼

    RAG(검색 증강 생성)는 LLM에 우리 회사의 문서나 최신 정보를 결합해 답하게 만드는 가장 현실적인 방법입니다. 파인튜닝보다 비용이 낮고, 출처를 제시할 수 있으며, 데이터가 바뀌어도 모델을 다시 학습할 필요가 없습니다. 이 글에서는 동작하는 RAG 파이프라인을 다섯 단계로 나눠 구축합니다.

    1단계: 문서 적재와 정제

    RAG의 품질은 입력 문서의 품질을 넘지 못합니다. PDF, HTML, 노션 등에서 텍스트를 추출할 때 머리말·바닥글·내비게이션 같은 노이즈를 제거하고, 표와 코드 블록의 구조를 최대한 보존해야 합니다. 정제되지 않은 문서를 그대로 넣으면 이후 단계가 아무리 좋아도 엉뚱한 답이 나옵니다.

    2단계: 청킹 전략

    문서를 검색 단위로 쪼개는 청킹은 RAG에서 가장 과소평가되는 단계입니다. 너무 작게 자르면 문맥이 끊기고, 너무 크게 자르면 관련 없는 내용까지 섞입니다. 일반 문서는 300~600토큰에 10~20% 오버랩을 주는 것이 무난한 출발점입니다.

    • 고정 크기 청킹: 단순하지만 문장이 중간에 끊길 수 있음
    • 문장/문단 기준 청킹: 의미 단위 보존에 유리
    • 구조 기반 청킹: 제목 계층을 활용해 섹션 단위로 분리

    3단계: 임베딩과 벡터 저장

    각 청크를 임베딩 모델로 벡터화한 뒤 벡터 DB에 저장합니다. 이때 청크 본문뿐 아니라 문서 제목, 출처 URL, 작성일 같은 메타데이터를 함께 저장해야 나중에 필터링과 출처 표시가 가능합니다. 메타데이터 설계를 미루면 운영 단계에서 반드시 후회합니다.

    chunk = {
      "text": "...본문...",
      "embedding": [0.12, -0.04, ...],
      "source": "policy_2026.pdf",
      "page": 12,
      "updated_at": "2026-03-01"
    }

    4단계: 검색과 재정렬

    사용자 질문을 임베딩해 벡터 검색으로 상위 K개 청크를 가져옵니다. 여기서 끝내지 말고, 가져온 후보를 리랭커(reranker)로 다시 정렬하면 정확도가 눈에 띄게 올라갑니다. 보통 벡터 검색으로 20~30개를 넉넉히 뽑은 뒤 리랭커로 상위 3~5개만 추리는 2단계 구성을 권장합니다.

    5단계: 생성과 출처 표기

    마지막으로 추려진 청크를 프롬프트에 문맥으로 넣고 LLM에게 답하게 합니다. 이때 “제공된 문맥에만 근거해 답하고, 근거가 없으면 모른다고 답하라”는 지침을 명시해 환각을 억제합니다. 또한 각 청크의 출처를 답변에 함께 노출해 사용자가 검증할 수 있게 합니다.

    처음부터 완벽한 파이프라인을 만들려 하지 마세요. 청킹과 검색 K값만 바꿔도 체감 품질이 크게 달라지므로, 작게 만들고 평가셋으로 반복 개선하는 것이 정석입니다.

    다음 단계로는 검색 정확도를 정량 측정하는 방법과, 답변 품질을 평가하는 지표를 도입해 보길 권합니다. 측정 없이 개선하는 RAG는 결국 감에 의존하게 됩니다.

  • 멀티모달 AI 실무 활용: 이미지와 텍스트를 함께 다루는 5가지 패턴

    멀티모달 AI 실무 활용: 이미지와 텍스트를 함께 다루는 5가지 패턴

    이제 LLM은 텍스트만 다루지 않습니다. 이미지, 표, 차트, 문서 스캔본을 함께 이해하는 멀티모달 모델이 보편화되면서, 기존에 자동화하기 어렵던 업무가 풀리고 있습니다. 이 글에서는 멀티모달 AI를 실무에 적용하는 다섯 가지 대표 패턴을 소개합니다.

    패턴 1: 문서 이해와 정보 추출

    계약서, 영수증, 송장 같은 문서는 텍스트뿐 아니라 레이아웃이 의미를 가집니다. 멀티모달 모델은 이미지 형태의 문서에서 항목과 값을 구조화해 추출할 수 있어, 기존 OCR+규칙 기반 파이프라인보다 양식 변화에 훨씬 강합니다.

    패턴 2: 표와 차트 해석

    PDF 안의 표나 차트는 텍스트 추출로는 구조가 깨지기 일쑤입니다. 멀티모달 모델은 표를 이미지로 보고 행·열 관계를 이해해 마크다운 표나 JSON으로 변환할 수 있습니다. 다만 복잡한 병합 셀이나 흐릿한 스캔본에서는 검증 단계가 반드시 필요합니다.

    패턴 3: 시각 기반 검색

    이미지를 임베딩해 “비슷한 제품 사진 찾기”나 “이 도면과 유사한 부품 찾기” 같은 검색을 구현할 수 있습니다. 텍스트와 이미지를 같은 벡터 공간에 임베딩하면, 텍스트로 이미지를 검색하거나 그 반대도 가능해집니다.

    • 제품 이미지 유사도 기반 추천
    • 텍스트 질의로 사내 도면·자산 검색
    • 불량 이미지와 정상 이미지 비교 분류

    패턴 4: 시각 정보가 포함된 RAG

    매뉴얼이나 보고서에는 그림과 그래프가 핵심 정보를 담는 경우가 많습니다. 이런 문서로 RAG를 만들 때 그림을 버리면 답변 품질이 떨어집니다. 그림을 캡션·설명 텍스트로 변환해 함께 인덱싱하거나, 멀티모달 검색을 결합하면 시각 정보까지 답변에 반영할 수 있습니다.

    패턴 5: 품질 검수 자동화

    제조·물류에서 제품 사진을 보고 결함 여부를 판정하거나, 마케팅 소재가 브랜드 가이드를 지키는지 검토하는 작업에 멀티모달 모델을 쓸 수 있습니다. 사람의 1차 스크리닝을 대체해 검수 인력의 부담을 크게 줄입니다.

    멀티모달은 강력하지만 만능이 아닙니다. 숫자가 중요한 표나 안전이 걸린 판정에는 반드시 사람 검증 단계를 두고, 모델 출력의 신뢰도를 함께 기록하세요.

    정리

    멀티모달 AI는 문서 이해, 표·차트 해석, 시각 검색, 시각 RAG, 검수 자동화의 다섯 영역에서 즉시 가치를 냅니다. 텍스트만으로 풀던 문제에 이미지 차원을 더하면 자동화 범위가 크게 넓어집니다. 우선 반복적이고 양식이 다양한 문서 업무부터 적용해 보길 권합니다.

  • LLM 추론 비용을 절반으로 줄이는 7가지 최적화 기법

    LLM 추론 비용을 절반으로 줄이는 7가지 최적화 기법

    LLM 서비스가 성공할수록 청구서도 함께 커집니다. 다행히 추론 비용은 품질을 거의 해치지 않고도 크게 낮출 수 있는 여지가 많습니다. 이 글에서는 실제로 효과가 큰 추론 비용 최적화 기법을 우선순위대로 정리합니다.

    1. 프롬프트 캐싱

    시스템 프롬프트나 공통 문맥처럼 매 요청에서 반복되는 앞부분은 캐싱하면 재처리 비용과 지연을 모두 줄입니다. 긴 고정 지침을 쓰는 서비스라면 캐싱만으로 입력 비용을 절반 가까이 줄이는 경우도 있습니다. 변하지 않는 부분을 프롬프트 앞쪽에 모으는 것이 핵심입니다.

    2. 모델 라우팅

    모든 요청에 최상위 모델을 쓸 필요는 없습니다. 간단한 분류나 짧은 답변은 작고 싼 모델로 처리하고, 복잡한 추론만 큰 모델로 보내는 라우팅을 두면 평균 비용이 크게 떨어집니다. 요청 난이도를 가볍게 판별하는 분기 로직이 핵심입니다.

    • 단순 FAQ·분류: 소형 모델
    • 요약·정리: 중형 모델
    • 복잡한 추론·코드 생성: 대형 모델

    3. 출력 길이 제어

    출력 토큰은 보통 입력보다 비쌉니다. 답변 길이를 명시적으로 제한하고, 불필요한 서론·미사여구를 줄이도록 지시하면 품질 손실 없이 비용을 줄일 수 있습니다. max_tokens 설정으로 폭주를 막는 것도 기본입니다.

    4. 배치와 비동기 처리

    실시간성이 필요 없는 대량 작업(문서 분류, 임베딩 생성 등)은 배치 API로 처리하면 단가가 크게 낮아집니다. 야간에 모아서 처리하는 식의 설계만으로도 운영 비용이 절감됩니다.

    5. 결과 재사용과 의미 캐싱

    같은 질문이 반복되는 서비스라면 동일·유사 질문의 답을 캐싱해 재사용합니다. 완전 일치뿐 아니라 임베딩 유사도로 “비슷한 질문”을 찾아 캐시 히트율을 높이는 의미 캐싱도 효과적입니다.

    6. 양자화와 자체 호스팅

    호출량이 매우 많다면 오픈 모델을 양자화해 자체 호스팅하는 것이 장기적으로 저렴할 수 있습니다. 8비트·4비트 양자화는 메모리와 비용을 줄이면서 품질 손실은 작은 편입니다. 다만 GPU 운영 역량이 전제되어야 합니다.

    비용 최적화는 측정에서 시작합니다. 어떤 엔드포인트가 토큰을 얼마나 쓰는지 모르면 어디를 줄여야 할지 알 수 없습니다. 먼저 토큰 사용량을 엔드포인트별로 로깅하세요.

    정리

    캐싱, 라우팅, 출력 제어, 배치, 의미 캐싱, 양자화를 조합하면 품질을 유지하면서 비용을 절반 수준으로 낮추는 것이 충분히 가능합니다. 가장 효과가 크고 위험이 낮은 캐싱과 라우팅부터 적용하고, 측정 지표로 효과를 검증하며 단계적으로 확장하세요.

  • 프롬프트 엔지니어링 패턴 8가지: 막연한 지시를 구조화된 프롬프트로

    프롬프트 엔지니어링 패턴 8가지: 막연한 지시를 구조화된 프롬프트로

    같은 모델이라도 프롬프트를 어떻게 쓰느냐에 따라 결과 품질은 천차만별입니다. 프롬프트 엔지니어링은 마법이 아니라, 검증된 패턴의 조합입니다. 이 글에서는 실무에서 반복적으로 효과를 내는 여덟 가지 패턴을 예시와 함께 정리합니다.

    패턴 1~2: 역할 부여와 맥락 제공

    “너는 10년 경력의 데이터 분석가다”처럼 역할을 지정하면 모델이 관점과 어휘를 그에 맞춥니다. 여기에 “독자는 비전공 임원이다” 같은 청중 맥락을 더하면 답변의 톤과 깊이가 자동으로 조정됩니다. 막연한 질문보다 역할과 맥락을 갖춘 질문이 훨씬 안정적입니다.

    패턴 3: 출력 형식 고정

    JSON, 표, 불릿 등 원하는 출력 형식을 명시하고 예시를 함께 주면 후처리가 쉬워집니다. 특히 시스템 연동 시에는 “오직 유효한 JSON만 출력하라”고 못 박아야 파싱 오류를 막을 수 있습니다.

    출력은 다음 JSON 스키마만 사용:
    {"sentiment": "긍정|부정|중립", "reason": "한 문장"}
    다른 설명은 출력하지 마라.

    패턴 4~5: 예시 제공과 단계적 사고

    몇 개의 입력-출력 예시를 보여주는 퓨샷 방식은 작업 의도를 말로 설명하는 것보다 정확합니다. 또한 복잡한 추론이 필요한 문제에서는 “단계별로 생각하라”고 유도하면 정답률이 올라갑니다. 다만 최종 사용자에게는 사고 과정을 숨기고 결론만 보여주는 분리가 필요할 수 있습니다.

    • 퓨샷: 다양한 케이스를 2~5개 제시
    • 단계적 사고: 추론 문제의 정확도 향상
    • 예시는 실제 분포를 대표하도록 선택

    패턴 6~7: 제약 조건과 거부 지침

    “근거가 없으면 모른다고 답하라”, “추측하지 마라” 같은 명시적 거부 지침은 환각을 크게 줄입니다. 또한 “100자 이내”, “전문 용어 금지” 같은 제약을 두면 출력이 일관됩니다. 모델은 하라는 것보다 하지 말라는 것을 명확히 할 때 더 안정적으로 행동합니다.

    패턴 8: 자기 검증 유도

    중요한 작업에서는 “답을 작성한 뒤 스스로 오류를 점검하고 수정하라”는 검증 단계를 넣으면 품질이 올라갑니다. 비용은 늘지만, 사실성이 중요한 작업에서는 충분히 가치가 있습니다.

    좋은 프롬프트는 한 번에 나오지 않습니다. 작은 평가셋으로 변형을 비교하며 다듬는 과정이 필요합니다. 프롬프트도 코드처럼 버전 관리하세요.

    정리

    역할·맥락·형식·예시·단계적 사고·제약·거부 지침·자기 검증, 이 여덟 패턴을 조합하면 대부분의 작업에서 안정적인 결과를 얻을 수 있습니다. 처음에는 한두 패턴부터 적용하고, 평가 지표로 효과를 확인하며 점진적으로 정교하게 다듬는 것이 좋습니다.

  • Kafka로 구축하는 실시간 스트리밍 파이프라인 입문

    Kafka로 구축하는 실시간 스트리밍 파이프라인 입문

    배치 처리는 분명 강력하지만, 사용자가 결제를 누른 순간 이상 거래를 탐지하거나, 재고가 떨어지는 즉시 알림을 보내야 하는 상황에서는 한계가 분명합니다. 데이터가 발생하는 즉시 흘려보내고 처리하는 스트리밍 파이프라인이 필요합니다. 그 중심에 가장 널리 쓰이는 분산 메시징 플랫폼 Apache Kafka가 있습니다.

    이 글은 Kafka를 처음 접하는 엔지니어를 위해 핵심 개념과 토픽 설계, 그리고 첫 파이프라인 구성을 따라가며 설명합니다. 운영 단계에서 만나는 흔한 함정도 함께 다룹니다.

    스트리밍이 푸는 문제

    전통적인 시스템에서 서비스끼리 직접 통신하면 N개 서비스 사이에 N제곱에 가까운 연결이 생깁니다. 한 서비스가 느려지면 연쇄적으로 전파됩니다. Kafka는 이 사이에 로그 기반 버퍼를 두어 생산자와 소비자를 분리합니다. 생산자는 빠르게 쓰고, 소비자는 자기 속도로 읽습니다.

    핵심은 Kafka가 메시지를 큐처럼 소비 후 삭제하지 않고, 설정된 보존 기간 동안 디스크에 순서대로 보관하는 분산 로그라는 점입니다. 덕분에 여러 소비자가 같은 데이터를 독립적으로 읽고, 장애 시 원하는 지점부터 다시 읽을 수 있습니다.

    토픽과 파티션 설계

    토픽은 메시지의 논리적 분류이고, 파티션은 그 토픽을 물리적으로 쪼갠 병렬 처리 단위입니다. 파티션 수가 곧 최대 병렬 소비자 수를 결정합니다. 처리량 목표가 초당 30만 건이고 컨슈머 한 개가 초당 3만 건을 처리한다면 최소 10개 파티션이 필요합니다.

    파티션 키 선택도 중요합니다. 같은 키를 가진 메시지는 항상 같은 파티션으로 가서 순서가 보장됩니다. 사용자 ID를 키로 쓰면 한 사용자의 이벤트 순서는 지켜지지만, 특정 헤비 유저에게 쏠리면 파티션 불균형이 생길 수 있습니다.

    프로듀서와 컨슈머 구성

    프로듀서에서 가장 중요한 설정은 acks입니다. acks=all로 두면 리더와 모든 동기화 복제본이 기록을 확인한 뒤 응답하므로 데이터 유실을 막지만 지연이 늘어납니다. 컨슈머 측에서는 오프셋 커밋 시점이 핵심입니다.

    props.put("acks", "all");
    props.put("enable.idempotence", "true");
    props.put("max.in.flight.requests.per.connection", 5);
    // 컨슈머: 처리 완료 후 수동 커밋으로 at-least-once 보장
    props.put("enable.auto.commit", "false");

    처리 전에 오프셋을 자동 커밋하면 처리 도중 장애 시 메시지가 유실됩니다. 처리 완료 후 수동 커밋하면 최소 한 번(at-least-once) 전달이 보장되며, 멱등 처리를 더하면 사실상 정확히 한 번 효과를 얻습니다.

    운영 시 주의점

    가장 흔한 장애는 컨슈머 랙(lag) 폭증입니다. 소비 속도가 생산 속도를 못 따라가면 랙이 쌓이고, 보존 기간을 넘기면 미처리 메시지가 삭제됩니다. Burrow나 Kafka Exporter로 랙을 상시 모니터링하고, 임계치 초과 시 컨슈머를 오토스케일하세요.

    • 리밸런싱 폭풍: 컨슈머 추가/제거 시 잦은 재할당, static membership으로 완화
    • 핫 파티션: 키 분포 불균형, 키 설계 재검토 또는 파티션 증설
    • 중복 소비: 멱등 키와 idempotent producer로 방어

    정리

    Kafka는 생산자와 소비자를 분리하는 분산 로그로 실시간 파이프라인의 토대를 제공합니다. 파티션 수로 병렬성을 정하고, acks와 수동 커밋으로 신뢰성을 확보하며, 컨슈머 랙을 끊임없이 관측하는 것이 운영의 핵심입니다. 작은 토픽 하나로 시작해 처리량과 신뢰성 요구에 맞춰 점진적으로 확장하길 권합니다.

  • Airflow로 데이터 파이프라인 오케스트레이션 제대로 하기

    Airflow로 데이터 파이프라인 오케스트레이션 제대로 하기

    데이터 파이프라인이 십수 개를 넘어가면 cron과 셸 스크립트로는 더 이상 감당이 안 됩니다. 어떤 작업이 실패했는지, 무엇이 먼저 끝나야 다음이 도는지, 재실행은 어떻게 하는지를 사람이 일일이 추적해야 하기 때문입니다. Apache Airflow는 이 워크플로를 코드로 정의하고 의존성과 재시도를 자동으로 관리하는 오케스트레이터입니다.

    이 글에서는 Airflow의 핵심 개념인 DAG와 태스크 의존성, 스케줄링 동작, 그리고 멱등성 있는 파이프라인을 만드는 운영 노하우를 다룹니다.

    오케스트레이션이 필요한 이유

    파이프라인은 단순히 스크립트를 시간 맞춰 실행하는 것이 아닙니다. A가 끝나야 B를 시작하고, B가 실패하면 C를 건너뛰며, 특정 작업은 세 번까지 재시도하는 식의 의존성과 조건이 얽혀 있습니다. cron은 이 관계를 표현하지 못합니다. Airflow는 작업 간 관계를 방향성 비순환 그래프(DAG)로 표현해 이 복잡성을 명시적으로 관리합니다.

    DAG와 태스크의 구조

    DAG는 노드(태스크)와 방향성 엣지(의존성)로 이뤄진 그래프입니다. 각 태스크는 오퍼레이터로 구현되며, 비트시프트 연산자로 순서를 선언합니다.

    with DAG("daily_sales", schedule="0 2 * * *",
             start_date=datetime(2026,1,1), catchup=False) as dag:
        extract = PythonOperator(task_id="extract", python_callable=pull)
        transform = PythonOperator(task_id="transform", python_callable=clean)
        load = PythonOperator(task_id="load", python_callable=write)
        extract >> transform >> load

    여기서 catchup=False는 중요한 설정입니다. True로 두면 start_date부터 현재까지의 모든 누락된 실행을 한꺼번에 채우려 들어 시스템에 부하가 집중됩니다. 의도적인 백필이 아니라면 끄는 것이 안전합니다.

    스케줄링과 execution_date

    Airflow 입문자를 가장 혼란스럽게 하는 개념이 실행 시점과 데이터 기간의 분리입니다. 일 단위 DAG는 해당 기간이 끝난 직후에 실행됩니다. 즉 6월 24일 데이터의 처리는 6월 25일 새벽에 트리거됩니다. 이 논리적 기간(data interval)을 기준으로 쿼리를 작성해야 멱등성이 보장됩니다.

    태스크 내부에서는 현재 시각이 아니라 data_interval_start와 data_interval_end를 사용해 처리 범위를 한정하세요. 그래야 재실행이나 백필 시에도 항상 같은 결과를 냅니다.

    운영과 트러블슈팅

    운영에서 가장 자주 마주치는 문제는 태스크 적체입니다. 스케줄러가 태스크를 큐에 넣었는데 워커가 부족하면 queued 상태에서 멈춥니다. 동시성 관련 파라미터를 점검하세요.

    • parallelism: 전체 동시 실행 태스크 상한
    • max_active_runs: 한 DAG의 동시 실행 인스턴스 수
    • pool: 외부 DB 커넥션 등 공유 자원 보호용 슬롯

    또한 멱등성이 깨진 태스크는 재시도 시 데이터 중복을 낳습니다. INSERT보다 파티션 단위 덮어쓰기나 MERGE를 사용해 같은 입력에 항상 같은 출력이 나오도록 설계하세요. 무거운 연산은 Airflow 워커에서 직접 돌리지 말고 Spark나 웨어하우스로 위임하는 것이 안정적입니다.

    Airflow는 작업을 실행하는 도구가 아니라 작업을 지휘하는 도구다. 무거운 연산은 외부 엔진에 맡겨라.

    정리

    Airflow는 파이프라인의 의존성, 스케줄, 재시도를 코드로 관리해 운영 복잡성을 길들입니다. DAG로 관계를 명시하고, data interval 기반으로 멱등성을 지키며, 동시성 파라미터로 자원을 보호하는 것이 안정 운영의 핵심입니다. Airflow는 지휘자 역할에 집중시키고 연산은 전문 엔진에 위임하세요.

  • dbt로 데이터 변환 모델링하기: 분석 엔지니어링의 표준

    dbt로 데이터 변환 모델링하기: 분석 엔지니어링의 표준

    웨어하우스에 데이터를 적재한 뒤 그것을 분석 가능한 형태로 변환하는 일은 오랫동안 복잡한 SQL 스크립트 더미와 수작업으로 이뤄졌습니다. 누가 어떤 테이블을 만들었는지, 의존 관계가 무엇인지 아무도 모르는 상태가 흔했습니다. dbt는 이 변환 계층(ELT의 T)을 소프트웨어 엔지니어링 원칙으로 다루게 해주는 도구입니다.

    이 글에서는 dbt의 모델, 참조 시스템, 테스트, 문서화를 차례로 살펴보고, 실무에서 모델 구조를 어떻게 잡아야 하는지 다룹니다.

    dbt가 바꾸는 것

    dbt의 출발점은 단순합니다. 모든 변환은 SELECT 문 하나로 표현되는 모델이고, dbt가 그것을 CREATE TABLE 또는 VIEW로 감싸 실행합니다. 엔지니어는 결과 테이블이 아니라 그 테이블을 정의하는 쿼리에 집중합니다. 여기에 버전 관리, 테스트, 의존성 그래프가 따라옵니다.

    핵심 마법은 ref 함수입니다. 모델끼리 직접 테이블명을 쓰지 않고 ref로 참조하면 dbt가 의존 그래프를 자동 구성하고 올바른 순서로 빌드합니다. 환경(dev, prod) 간 스키마 전환도 ref가 알아서 처리합니다.

    모델 계층 구조

    잘 설계된 dbt 프로젝트는 보통 세 계층으로 나눕니다. 이 구조는 변경에 강하고 재사용이 쉽습니다.

    • staging: 원천 테이블당 1:1, 컬럼명 정리와 타입 캐스팅만
    • intermediate: 여러 staging을 조합한 중간 로직, 재사용 단위
    • marts: 비즈니스 도메인별 최종 산출물, BI 연결
    -- models/marts/fct_orders.sql
    with orders as (
        select * from {{ ref('stg_orders') }}
    ),
    payments as (
        select * from {{ ref('stg_payments') }}
    )
    select o.order_id, o.customer_id, sum(p.amount) as total
    from orders o left join payments p using (order_id)
    group by 1, 2

    테스트와 문서화

    dbt의 진짜 강점은 데이터 품질 테스트를 선언적으로 붙일 수 있다는 점입니다. YAML에 not_null, unique, accepted_values, relationships 같은 제약을 명시하면 빌드마다 자동 검증됩니다. 기본 테스트로 부족하면 SQL로 커스텀 테스트를 작성합니다.

    같은 YAML에 컬럼 설명을 적으면 dbt docs가 자동으로 데이터 카탈로그와 계보 그래프를 생성합니다. 코드와 문서가 한곳에 있어 문서가 낡지 않는다는 점이 큰 가치입니다.

    운영과 성능

    모델이 수백 개를 넘으면 전체 빌드 시간이 부담스러워집니다. 매번 전체 테이블을 새로 만드는 대신 incremental 모델로 새 데이터만 추가 처리하세요. is_incremental 분기로 증분 조건을 걸면 빌드 시간이 수십 분에서 수 분으로 줄어듭니다.

    또한 dbt build와 함께 상태 비교 선택(state:modified)을 쓰면 변경된 모델과 그 하위만 빌드해 CI 시간을 절약할 수 있습니다. 한 가지 주의점은 증분 모델의 고유 키 설정 오류로 인한 중복인데, unique 테스트로 반드시 방어하세요.

    dbt는 분석가를 엔지니어로 만든다. 버전 관리, 테스트, 문서화가 SQL 워크플로에 자연스럽게 녹아들기 때문이다.

    정리

    dbt는 변환 계층을 모듈화된 SQL 모델로 다루며 ref 기반 의존성, 선언적 테스트, 자동 문서화를 제공합니다. staging-intermediate-marts 계층으로 구조를 잡고, 증분 모델로 성능을 확보하며, 테스트로 품질을 지키는 것이 분석 엔지니어링의 표준 워크플로입니다.

  • Spark 성능 튜닝 실전: 셔플과 스큐를 잡는 방법

    Spark 성능 튜닝 실전: 셔플과 스큐를 잡는 방법

    Spark 잡이 어제까지 10분에 끝나다가 오늘 갑자기 두 시간을 넘기는 경험은 데이터 엔지니어라면 누구나 합니다. 코드는 그대로인데 데이터 분포가 바뀌었거나 볼륨이 늘었을 때 흔히 벌어집니다. Spark 성능 문제의 대부분은 결국 두 가지로 수렴합니다. 과도한 셔플과 데이터 스큐입니다.

    이 글에서는 Spark 실행 모델을 짧게 짚고, 셔플을 줄이는 방법, 스큐를 해소하는 기법, 그리고 AQE 같은 최신 기능까지 실전 관점에서 다룹니다.

    왜 느려지는가: 셔플의 이해

    Spark는 데이터를 파티션으로 나눠 병렬 처리합니다. 그런데 groupBy, join, distinct 같은 연산은 같은 키를 한 노드로 모아야 하므로 네트워크를 통해 데이터를 재분배하는 셔플을 일으킵니다. 셔플은 디스크 쓰기와 네트워크 전송을 동반해 가장 비싼 연산입니다.

    따라서 첫 번째 원칙은 불필요한 셔플 제거입니다. 작은 테이블과의 조인은 브로드캐스트 조인으로 바꿔 큰 테이블의 셔플을 없앨 수 있습니다. spark.sql.autoBroadcastJoinThreshold를 적절히 설정하면 옵티마이저가 자동으로 처리합니다.

    데이터 스큐 진단과 해소

    스큐는 특정 키에 데이터가 쏠리는 현상입니다. 예를 들어 사용자별 집계에서 비회원(null 또는 guest)이 전체의 40퍼센트를 차지하면, 그 키를 받은 단일 태스크가 나머지 모든 태스크보다 수십 배 오래 걸립니다. 200개 태스크 중 199개는 끝났는데 1개가 한 시간을 끄는 전형적 패턴입니다.

    고전적 해법은 솔팅(salting)입니다. 편향된 키에 랜덤 접미사를 붙여 여러 파티션으로 분산한 뒤, 집계 후 다시 합칩니다.

    # 스큐 키에 0~N 솔트를 부여해 분산
    df = df.withColumn("salt", (rand() * 16).cast("int"))
    stage1 = df.groupBy("key", "salt").agg(sum("v").alias("partial"))
    result = stage1.groupBy("key").agg(sum("partial").alias("total"))

    AQE와 파티션 관리

    Spark 3 이후 도입된 적응형 쿼리 실행(AQE)은 런타임 통계를 보고 실행 계획을 조정합니다. spark.sql.adaptive.enabled를 켜면 셔플 후 파티션을 자동 병합하고, 스큐 파티션을 감지해 분할하며, 조인 전략도 동적으로 바꿉니다. 많은 스큐 문제가 AQE만으로 완화됩니다.

    파티션 수도 중요합니다. 기본값 200이 항상 옳지는 않습니다. 파티션이 너무 많으면 태스크 오버헤드가, 너무 적으면 병렬성 부족이 생깁니다. 파티션당 128MB에서 256MB를 목표로 데이터 크기에 맞춰 조정하세요.

    운영 체크리스트

    • Spark UI의 Stage 탭에서 태스크 시간 분포 확인, 롱테일이 곧 스큐
    • spilled 메모리가 크면 executor 메모리 또는 파티션 수 조정
    • collect, toPandas로 드라이버에 데이터 몰아넣는 패턴 제거
    • 캐시는 재사용되는 데이터프레임에만, 남용하면 메모리 압박

    Spark 튜닝의 90퍼센트는 Spark UI를 읽는 법을 아는 것이다. 추측하지 말고 측정하라.

    정리

    Spark 성능 문제의 핵심은 셔플과 스큐입니다. 브로드캐스트 조인으로 셔플을 줄이고, 솔팅과 AQE로 스큐를 해소하며, 파티션 크기를 데이터에 맞춰 조정하세요. 무엇보다 Spark UI로 병목을 측정한 뒤 손대는 습관이 추측에 기반한 헛수고를 막아줍니다.

  • A/B 테스트 설계 제대로 하기: 표본 크기부터 통계적 유의성까지

    A/B 테스트 설계 제대로 하기: 표본 크기부터 통계적 유의성까지

    A/B 테스트는 “느낌” 대신 “증거”로 결정하기 위한 도구입니다. 하지만 현장에서는 표본도 부족한 상태에서 “전환율이 2.1%에서 2.3%로 올랐으니 B안 채택”이라고 결론 내리는 경우가 흔합니다. 이런 결정은 동전 던지기와 다를 바 없습니다. 제대로 된 실험 설계의 핵심은 시작하기 전에 멈출 조건을 정하는 것입니다.

    분석 질문과 가설 정의

    먼저 검증할 가설을 정량적으로 적습니다. 좋은 가설은 “가입 버튼 문구를 바꾸면 가입 전환율이 오를 것이다”가 아니라 “가입 버튼 문구를 ‘무료로 시작하기’로 바꾸면 가입 전환율이 현재 5.0%에서 5.5% 이상(상대 10% 개선)으로 오를 것이다”입니다. 기대 효과 크기(MDE, 최소 검출 효과)를 명시해야 표본 크기를 계산할 수 있습니다.

    표본 크기 계산

    표본 크기는 네 가지 값으로 결정됩니다. 기준 전환율(5%), 검출하려는 최소 효과(상대 10%), 유의수준 알파(보통 0.05), 검정력(보통 0.8)입니다. 이 조건에서 한 그룹당 약 31,000명이 필요합니다. 기준 전환율이 낮을수록, 검출하려는 효과가 작을수록 필요한 표본은 급격히 늘어납니다.

    • 기준 전환율이 낮으면(예: 1%) 표본은 수십만 단위로 커진다
    • 효과를 작게 잡을수록(상대 5%) 표본은 약 4배로 증가한다
    • 일일 트래픽으로 며칠이 걸리는지 미리 계산해 실험 기간을 확정한다

    실행 단계

    사용자를 무작위로 A/B 그룹에 배정하되, 같은 사용자가 항상 같은 그룹에 들어가도록 사용자 ID 해시로 고정합니다. 실험 기간 동안에는 미리 정한 표본에 도달할 때까지 결과를 들여다보고 멈추는 “피킹(peeking)”을 하지 않습니다. 매일 결과를 보고 유의해지는 순간 멈추면 거짓 양성 비율이 5%가 아니라 20% 이상으로 치솟습니다.

    해석과 함정

    p값이 0.03이라는 것은 “B안이 옳을 확률 97%”가 아닙니다. “A와 B가 차이 없다고 가정했을 때, 이 정도 이상의 차이가 우연히 나올 확률이 3%”라는 뜻입니다. 또한 신뢰구간을 함께 보세요. 전환율 차이가 +0.5%p이고 95% 신뢰구간이 [-0.1%p, +1.1%p]라면 0을 포함하므로 유의하지 않습니다.

    유의하지 않다는 것은 “효과가 없다”가 아니라 “이 표본으로는 효과를 확인하지 못했다”는 뜻입니다.

    정리

    신뢰할 수 있는 A/B 테스트의 조건은 명확합니다. 정량적 가설과 MDE를 먼저 정하고, 표본 크기와 기간을 사전에 계산하며, 중간에 결과를 훔쳐보지 않고, p값과 신뢰구간을 올바로 해석하는 것입니다. 통계적 유의성과 실질적 중요성(비즈니스 임팩트)을 분리해서 판단하면, 실험은 의사결정을 가속하는 강력한 자산이 됩니다.

  • 코호트 분석과 리텐션: 신규 사용자가 왜 떠나는지 추적하는 법

    코호트 분석과 리텐션: 신규 사용자가 왜 떠나는지 추적하는 법

    “우리 서비스 리텐션이 40%입니다”라는 한 문장은 거의 아무것도 말해주지 않습니다. 1월에 가입한 사람과 6월에 가입한 사람을 한데 섞은 평균은, 제품이 좋아지고 있는지 나빠지고 있는지조차 숨깁니다. 코호트 분석은 사용자를 가입 시점별로 묶어 시간에 따른 행동 변화를 추적하는 기법입니다.

    분석 질문 정의

    코호트 분석으로 답하려는 질문은 보통 이렇습니다. “우리가 최근에 한 개선이 신규 사용자의 정착에 실제로 효과가 있었는가?” 이 질문에 답하려면 가입 월별로 그룹을 나누고, 각 그룹이 가입 후 1주, 2주, 4주, 8주 뒤 얼마나 남아 있는지를 표로 만듭니다.

    리텐션 표 만들기

    가입 코호트1주4주8주
    3월 가입100%32%21%
    4월 가입100%35%24%
    5월 가입(온보딩 개선 후)100%41%30%

    이 표를 세로로 읽으면 시간에 따라 제품이 개선되는지 보입니다. 5월 코호트의 4주 리텐션이 41%로 뛴 것은 온보딩 개선이 효과가 있었다는 강한 신호입니다. 가로로 읽으면 한 코호트가 시간에 따라 어떻게 이탈하는지 보입니다.

    리텐션 곡선의 모양 읽기

    • 계속 0으로 떨어지는 곡선: 제품-시장 적합성 부족, 근본 문제
    • 처음 급락 후 평평해지는 곡선(스마일): 핵심 사용자층 확보, 건강한 신호
    • 평평한 수준이 점점 올라가는 곡선: 이상적, 제품이 점점 끈끈해짐

    가장 주목할 지점은 곡선이 평평해지는 “안정 리텐션” 구간입니다. 이 값이 0보다 확실히 크면 비즈니스가 지속 가능하다는 뜻입니다.

    세그먼트로 더 깊이 파기

    전체 코호트를 다시 유입 채널, 첫날 행동, 요금제로 쪼개면 개선 지점이 드러납니다. 예를 들어 “가입 첫날 친구 3명 이상을 초대한 사용자”의 8주 리텐션이 55%인데 그렇지 않은 사용자는 12%라면, 온보딩에서 초대를 유도하는 것이 핵심 레버임을 알 수 있습니다. 이런 “아하 모먼트” 행동을 찾는 것이 코호트 분석의 가장 큰 보상입니다.

    함정과 정리

    최근 코호트는 아직 충분한 시간이 지나지 않아 데이터가 미완성이라는 점을 주의해야 합니다. 5월 코호트의 8주 리텐션은 아직 관측 기간이 부족할 수 있습니다. 또한 코호트 크기가 너무 작으면(수십 명) 변동이 커서 신뢰하기 어렵습니다. 코호트 분석은 평균이라는 거짓 위안을 걷어내고, 제품이 실제로 나아지고 있는지를 정직하게 보여주는 가장 강력한 도구입니다.