주요 컨텐츠로 이동
공학

에이전트용 설계: Spark 선언형 파이프라인이 필요한 이유

528개의 에이전트 구축 데이터 파이프라인을 벤치마크한 결과, 선언형 작성이 절차적 코드보다 더 비용 효율적이고 빠르며 간결한 것으로 나타났습니다.

작성자: Lisa Cao , Sudhanva Huruli

  • 절차적 파이프라인은 선언적 파이프라인보다 약 34배 많은 연산 시간을 소모했습니다. 이는 주로 구조적으로 잘못된 선언적 파이프라인의 오류가 전체를 실행하지 않는 저렴한 비용의 "드라이 런(dry run)"을 통해 감지되기 때문입니다.
  • 선언적 파이프라인은 동일한 파이프라인의 코드를 51% 더 적게 포함하므로 읽고 검토해야 할 양이 훨씬 적습니다.
  • 이러한 효율성이 결합되어 SDP와 같은 선언적 프레임워크가 프로덕션 파이프라인을 구축하는 자율 에이전트에게 명확한 아키텍처 선택지가 됩니다.

AI 에이전트가 프로덕션 데이터 파이프라인 작성에서 차지하는 비중이 점점 커지고 있습니다(Databricks의 SDP 파이프라인의 70% 이상이 현재 에이전트에 의해 생성됨). 이로 인해 두 가지 측면에서 중요성이 커졌습니다. (1) 에이전트의 모든 시도에는 컴퓨팅 자원과 시간이 소모되므로 반복 작업이 저렴하고 효율적으로 실행되어야 하며, (2) 코드 리뷰가 중요한 관문이 되므로 코드가 간결하고 읽기 쉬우며 정확해야 합니다. Apache Spark®는 파이프라인 작성을 위해 절차적(procedural) 모델과 선언적(declarative) 모델이라는 두 가지 프로그래밍 모델을 제공하며, 이들은 작성하는 내용, Spark가 실행 전에 검증할 수 있는 내용, 그리고 실수가 발생했을 때의 비용 측면에서 차이가 있습니다. 동일한 작업을 수행하는 동일한 에이전트를 통해 두 모델을 벤치마크한 결과, 선언적 모델이 더 안전하고 비용 효율적이며 간결한 것으로 나타났습니다.

절차적 파이프라인은 각 데이터셋을 단계별로 구축하는 방법을 Spark에 지시하는 코드입니다. 작동 여부를 확인하는 유일한 방법은 직접 실행해보는 것뿐입니다.

선언적 파이프라인은 원하는 데이터셋을 설명하고 구축 방법은 Spark에 맡기는 방식입니다. 이는 LLM에 프롬프트를 입력하면 LLM이 (올바른 스킬과 도구를 사용하여) 작업을 수행하는 방법을 알아서 처리하는 것과 비슷합니다. 간단한 오타로 인해 어떤 오류가 발생하는지 코드를 통해 보면 가장 잘 이해할 수 있습니다.

절차적 (Procedural)선언적 (Declarative)
bronze = spark.read.parquet(RAW)
bronze.write.saveAsTable("bronze")
silver = 
bronze.withColumnRenamed("revenue", 
"revenues")  # typo
silver.write.saveAsTable("silver")
gold = 
spark.read.table("silver").groupBy("revenue")
.sum("revenue")
gold.write.saveAsTable("gold")  
# runtime error
@dp.materialized_view(name="gold")
def gold():
    return 
spark.read.table("silver").groupBy("revenue")
.sum("revenue")

1. 선언적 파이프라인은 실행 전에 검증할 수 있습니다

버그에는 두 가지 유형이 있습니다. 파이프라인을 읽는 것만으로 잡을 수 있는 구조적 버그(해결되지 않은 열 참조, 아직 존재하지 않는 테이블, 허용되지 않는 설정 변경 등)와, 실행이 완료되고 잘못된 결과가 나온 후에야 출력에서 드러나는 조용한(silent) 버그가 있습니다.

선언적 파이프라인은 Rust와 같은 정적 타입 언어에서 컴파일 시점에 데이터 흐름을 검증할 수 있는 것처럼, 실행 전에 구조적 버그가 있는지 확인할 수 있습니다. 선언적 파이프라인은 실행 코드가 아니라 명세(description)이므로 Spark는 이를 읽고 다음과 같은 세 가지 유형의 질문에 답할 수 있습니다.

  • 모든 열 참조는 데이터를 읽어오는 데이터셋의 스키마를 기준으로 해결되므로, 존재하지 않는 열은 런타임 오류가 아니라 거부 처리됩니다.
  • 데이터셋 간의 모든 종속성은 그래프의 생성자와 매칭되므로, 아무것도 선언하지 않은 테이블에 대한 참조는 파이프라인이 시작되기 전에 포착됩니다.
  • 그래프의 완전성과 순서가 검증되므로, 모델이 임의로 작성한 순서를 신뢰하는 대신 엔진이 언제 무엇을 실행할지 결정합니다.

2. 선언적 파이프라인은 상용구(boilerplate) 작업을 Spark에 오프로드합니다

절차적 PySpark에서 AI 코딩 에이전트는 유효하지 않은 행 필터링 및 테이블 조인과 같은 데이터 연산을 직접 작성합니다. 또한 파이프라인의 단계를 올바른 순서로 실행하고 결과를 저장하는 코드도 작성합니다. 이 지원 코드는 파이프라인 전반에 걸쳐 동일한 상용구 코드를 반복합니다.

선언적 파이프라인에서는 AI 에이전트가 각 데이터셋과 이를 생성하는 쿼리를 정의합니다. Spark가 실행 순서를 처리하고 결과를 저장합니다. Spark가 상용구 코드를 처리하므로 에이전트는 더 적은 코드를 작성하고, 팀은 검토하고 유지 관리해야 할 코드가 줄어듭니다.

테스트 내용

저희는 22개의 데이터 엔지니어링 작업을 테스트했습니다. 각 작업은 실제 티켓과 유사하게 문제를 설명하고 파이프라인이 생성해야 하는 테이블, 열 이름, 데이터 타입을 지정했습니다. 출력이 해당 요구사항을 충족하기만 한다면 에이전트는 어떤 구현 방식이든 선택할 수 있었습니다.

각 작업마다 프로덕션 데이터에서 볼 수 있는 다양한 노이즈 값이 포함된 12개의 서로 다른 입력 데이터셋을 생성했습니다. 에이전트는 절차적 PySpark를 사용하여 각 데이터셋에 대한 새 파이프라인을 작성하고, Spark Declarative Pipelines를 사용하여 또 다른 파이프라인을 작성했습니다. 그 결과 접근 방식당 264개, 총 528개의 빌드가 생성되었습니다(22개 작업 × 12개 데이터셋 × 2가지 접근 방식). 모든 빌드에는 Opus 4.8이 사용되었습니다.

각 파이프라인을 실행하고 필수 테이블과 비교하여 출력을 확인했습니다. 자동 평가 도구(grader)는 어떤 접근 방식으로 결과가 생성되었는지 알지 못했습니다. 선언적 파이프라인은 먼저 Spark의 드라이런(dry run)을 거쳤습니다. 빌드가 실패하면 에이전트는 고정된 한도 내에서 코드를 수정하고 다시 시도할 수 있었습니다.

공용 리포지토리에서 설정, 프롬프트, 데이터셋, 평가 도구, 모든 수치 뒤에 숨겨진 결과 등 전체 과정을 직접 다시 실행해 볼 수 있습니다.

결과

두 방식 모두 거의 모든 작업을 완료했으며, 절차적 방식은 264개 중 255개, 선언적 방식은 264개 중 258개를 완료하여 어떤 방식을 사용하든 작동하는 파이프라인을 만들 수 있었습니다. 차이점은 그 외의 모든 것, 즉 실수가 드러난 시점, 수정하는 데 소요된 코드 양, 그리고 출력의 일관성입니다. 선언적 파이프라인의 세 가지 장점과 두 가지 유의 사항을 아래에서 다룹니다.

1. 실행 전 검증으로 선언적 파이프라인의 소요 시간이 약 34배 단축됨

파이프라인의 실수는 실행 전, 실행 중, 또는 아예 나타나지 않을 수 있습니다. 절차적 파이프라인은 사전 실행 단계가 없기 때문에 실행 전에 문제를 발견하지 못합니다. 구조적 오류를 찾으려면 Spark가 중단된 단계에 도달할 때까지 클러스터에서 파이프라인을 실행해야 했습니다.

측정된 빌드 전반에서 절차적 실행은 596 실행자-초(executor-seconds)를 사용한 반면, 선언적 실행은 17초를 사용했습니다. 선언적 코드의 드라이런 검증은 구조적 오류를 조기에 포착하고 컴퓨팅 자원을 34배 절감했습니다.

절차적 파이프라인

2. 선언적 파이프라인 코드는 절차적 파이프라인 크기의 절반 수준이었음

완료된 빌드 중 선언적 파이프라인은 절차적 파이프라인의 중앙값 131줄에 비해 약 절반 수준인 중앙값 64줄을 기록했습니다.

절차적 구현 방식은 편차가 더 컸습니다. 동일한 작업에 대해 어떤 방식은 Spark 변환을 사용한 반면, 어떤 방식은 데이터를 메모리에 로드하고 한 번에 한 행씩 처리했습니다. 둘 다 테스트 데이터 통과에는 성공했지만, 메모리 내 처리 방식은 데이터가 증가하면 실패할 수 있습니다. 벤치마크 전반에서 19개의 절차적 빌드가 이 지름길을 사용했습니다. 선언적 빌드 중에는 단 하나도 없었습니다.

더 짧고 일관된 코드는 팀이 검토해야 할 양을 줄여주고 유지 관리해야 할 하나의 패턴을 제공합니다.

선언적 파이프라인 코드

3. 로직 오류 측면에서는 두 방식이 비슷함

이상은 두 가지 장점입니다. 테스트를 실행하는 과정에서 두 가지 유의 사항도 확인했습니다.

첫째, 드라이런은 구조적 실수만 잡아낼 수 있습니다. 오류 없이 실행되지만 잘못된 답을 반환하는 조용한 버그는 잡아낼 수 없으며, 이는 더 위험한 유형입니다. 이 측면에서는 두 방식의 결과가 비슷했습니다. 조용한 오류 발생률의 차이가 대략 15% 이내였으며, 이는 실제적인 차이로 보기에는 너무 작은 격차입니다. 선언적 방식은 구조적 실수를 저렴하게 잡아내지만, 여전히 테스트와 리뷰가 필요한 로직 문제에는 도움을 주지 못합니다.

4. 선언적 방식은 나중에 아끼기 위해 사전에 더 많은 비용을 투입함

선언적 방식은 한 영역에서 더 많은 비용을 소모합니다. 에이전트는 파이프라인이 정상화될 때까지 드라이런을 반복하며 재시도하므로, 나중에 클러스터에서 문제를 발견하는 대신 사전에 토큰을 사용하여 더 많은 사고 과정을 거칩니다. 토큰은 파이프라인이 작성되는 동안 지불되는 고정된 일회성 비용이며, 데이터 크기나 파이프라인 실행 빈도에 따라 비용이 변하지 않습니다. 낭비되는 클러스터 컴퓨팅 자원은 그 반대입니다. 잘못된 실행마다 반복되고 데이터에 따라 증가하므로, 처리량이 많아질수록 잘못된 빌드로 인한 비용이 커집니다. 계속 증가하는 비용을 피하기 위해 사전에 고정 비용을 지불하는 방식은 파이프라인과 데이터가 커질수록 더욱 유리해집니다.

측정된 모든 지표 비교

측정된 모든 차이점은 아래와 같습니다.

차원 (Dimension)절차적 PySpark선언적 (SDP)
에이전트가 생성하는 결과물실행되는 코드검증을 거치는 명세
구조적 오류실행 중반, 부분 쓰기 이후, 또는 전혀 발견되지 않음; 출력 전에 중단된 경우 없음드라이런에서 포착됨; 264개 중 55개가 출력 전에 중단됨
비용

클러스터 시간: 약 34배 높음 (실패한 실행 시 최대 약 521 실행자-초)

LLM 토큰: 기준선

클러스터 시간: 기준선 (실패한 실행 시 약 0.5 실행자-초)

LLM 토큰: 약 2.3배 (중앙값 빌드 ≈ $0.50 대 $0.26, Opus 4.8 정가 기준)

코드 크기기준선

코드 줄 수: 약 51% 감소 (중앙값)

AST 크기: 약 44% 감소

조용한 버그 (정상 실행 후 오답)264개 중 73개 (27.7%)264개 중 85개 (32.2%)

결론

프로덕션 규모에서는 선언적 접근 방식이 더 유리합니다. LLM 토큰 비용이 약 2.3배 더 들지만, 그 대가로 애초에 작동하지 않을 파이프라인에 클러스터 시간을 낭비하지 않게 됩니다. 프로덕션 규모에서 선언적 접근 방식은 약간 더 높은 LLM 토큰 비용을 지불하는 대신 실패하는 파이프라인으로 인한 클러스터 시간 낭비를 확실하게 방지함으로써 유리함을 입증합니다. 데이터 양이 늘어날수록 컴퓨팅 낭비는 비례해서 증가하는 반면 토큰 비용은 일정하게 유지되므로, 이는 비용 효율성이 점점 더 높아집니다.

파이프라인 작업을 위해 에이전트를 평가 중이라면, 어떤 에이전트를 선택하는지만큼 에이전트가 코드를 작성하는 방식도 진지하게 고려해 보세요. 더 나은 에이전트는 실수를 적게 하고, 선언적 파이프라인은 실수가 발생하더라도 비용을 저렴하게 만들고 비용이 발생하기 전에 잡아냅니다.

저장소에는 설계, 데이터 및 전체 출처가 문서화되어 있습니다. Spark Declarative Pipelines를 사용하여 에이전트를 시작하려면 공식 프로그래밍 가이드를 참고하고 PySpark와 함께 SDP skill을 사용하세요.

FAQ

이게 작동하려면 AI 에이전트를 전적으로 신뢰해야 하나요?

아니요, 신뢰할 필요가 없다는 점이 핵심입니다. 선언적 에이전트는 자신이 원하는 테이블에 대한 설명인 선언적 파이프라인 코드만 작성하며, Spark 자체가 실행 전에 해당 설명을 검증합니다. 검증기는 플랫폼의 일부이므로, 모델이 아니라 파이프라인에 대한 Spark의 평가를 신뢰하는 것입니다. 에이전트는 직접 코드를 실행하거나 데이터를 읽지 않습니다.

이것은 오직 Spark Declarative Pipelines에만 해당되나요, 아니면 더 폭넓게 적용되나요?

"내가 원하는 것은 이것이다"와 "이제 이것을 구축해라"를 분리하여 첫 번째 부분을 자체적으로 검증할 수 있는 모든 선언적 접근 방식에 더 폭넓게 적용됩니다. SQL 자체, 인프라스트럭처-애즈-코드 도구, 그리고 스키마 우선 API에서도 이와 유사한 형태를 볼 수 있습니다. Spark Declarative Pipelines는 우리가 벤치마크를 수행한 대상이지만, 이러한 논리는 그런 방식으로 구축된 모든 도구에 적용됩니다.

선언적 에이전트는 2.3배의 토큰을 사용합니다. 이거 문제 아닌가요?

현실적인 파이프라인 규모에서는 그렇지 않습니다. Opus 4.8 정가 기준으로 2.3배라는 수치는 중간값 기준 선언적 빌드당 토큰 비용이 약 0.50달러인 반면, 절차적 빌드는 0.26달러라는 것을 의미합니다. 절차적 실행이 한 번 실패하면 약 521 executor-second가 낭비되는 반면, 거부된 선언적 실행은 약 0.5초의 비용만 발생합니다. 따라서 몇 번의 잘못된 실행만 막아도 추가 토큰 비용을 충분히 상쇄하며, 데이터 양이 늘어날수록 이 격차는 더욱 벌어집니다. 토큰 가격은 사용하는 모델 서비스에 따라 변동되지만, 컴퓨팅 비용은 상대적으로 안정적이어서 계획을 세우기가 더 쉽습니다.

(이 글은 AI의 도움을 받아 번역되었습니다. 원문이 궁금하시다면 여기를 클릭해 주세요)

관련 태그:

데이터 엔지니어링 , 오픈 소스

최신 게시물을 이메일로 받아보세요

블로그를 구독하고 최신 게시물을 이메일로 받아보세요.