데이터 인프라를 위한 적절한 최적화 프레임워크는 엔지니어링 작업을 능률화하여 플랫폼을 확장할 수 있도록 합니다. 엔지니어링 조직이 성장과 확장성을 계획할 수 있지만 빠르게 증가하는 데이터 세트는 거의 모든 데이터 팀이 결국 직면하게 될 문제입니다.
2020년에 DoorDash는 3면 시장 의 각 섹션에서 가속화된 성장을 보였습니다 . 더 많은 고객이 앱에서 주문하고, 더 많은 플랫폼 판매자가 증가했으며, 더 많은 Dasher(배송 드라이버를 일컫는 용어)가 배달을 의향이 있습니다. 이러한 성장으로 인해 더 많은 데이터 처리가 필요했지만 여전히 대시보드 소비자, 고급 SQL 작성자 및 데이터 과학자 를 포함하는 비즈니스 파트너와 동일한 SLA를 충족해야 했습니다 . SLA를 계속 충족하려면 ETL 작업을 최적화하여 증가된 로드 및 데이터 사용량을 더 잘 처리해야 했습니다.
DoorDash의 데이터 웨어하우스는 Snowflake 에 있습니다. 추가 로드를 처리하기 위해 컴퓨팅 리소스를 늘릴 수도 있었지만 대신 비용 증가 없이 SLA를 충족하기 위해 ETL 작업에 대한 최적화 프레임워크를 구현하기로 결정했습니다.
우리는 최소한의 노력과 가장 많은 비용 절감 효과가 있는 항목에 집중하여 노력의 우선순위를 정하기 위해 이 최적화 목록을 고안했습니다.
이 ETL을 폐기할 수 있습니까?
(DAG)
에서 종속성을 끊을 수 있습니까 ?
이 ETL을 점진적으로 수행할 수 있습니까?
열 수를 줄일 수 있습니까?
쿼리를 더 작은 데이터 세트로 분할하여 데이터 유출을 막을 수 있습니까?
클러스터링을 구현할 수 있습니까?
Snowflake 기능을 활용할 수 있습니까 ?
ETL 작업을 해제하면 해당 작업의 컴퓨팅 사용량이 모두 절약되는 반면 열에 클러스터링을 추가하면 효율성이 떨어지는 GROUP BY / ORDER BY SQL 기능의 속도가 빨라질 수 있습니다. Snowflake 함수를 사용하고 열 수를 줄이는 등 많은 최적화를 함께 사용할 수 있습니다. 각 최적화에 대해 구현 단계 및 관련 장단점에 대해 논의합니다.
새로운 데이터 소스와 프로세스가 나타나면 더 이상 사용하지 않는 레거시 ETL 작업을 제거하는 것이 중요합니다. DAG를 아래쪽으로 추적하면 영향 분석을 수행하여 작업을 제거할 수 있는지 확인할 수 있습니다. 영향 분석은 다음으로 구성됩니다.
제거하려는 ETL 작업에 의존하는 모든 테이블, 보고서 및 ETL 작업 찾기
작업이 어떻게 활용되는지 살펴봅니다. 예를 들어 테이블이 LEFT JOIN에서 사용되는 경우 LEFT JOIN 에는 INNER JOIN 과 같은 다른 필드에 영향을 주지 않고 제거할 수 있는 필드가 있을 수 있으므로 제거 후보가 될 수 있습니다 .
원본 테이블이 여전히 정확한 데이터를 제공하는지 확인하기 위해 업스트림을 살펴봅니다. 종종 부정확한 데이터보다 데이터를 제공하지 않는 것이 더 낫습니다.
(ETL 측면에서 DAG에 대한 자세한 설명은 이 Apache Airflow 설명서 페이지 를 확인하십시오 .)
ETL 작업을 폐기함으로써 팀은 다른 작업을 실행할 컴퓨팅 리소스를 확보할 수 있습니다. 이렇게 하면 처리되는 작업이 줄어들기 때문에 전반적인 컴퓨팅 비용이 절감되고 동시에 SLA를 충족하는 데 도움이 됩니다.
ETL을 해제할 수 없는 경우 다음 단계는 해당 종속성을 해제하는 것입니다. 예를 들어 작업 다운스트림에 공급되는 두 개의 작업이 있다고 가정해 보겠습니다. 이 두 소스 테이블은 모두 대상 테이블에 필요하며 SQL 쿼리를 다시 작성할 수 없습니다. 한 작업 dataset_a는 오전 3시에 실행되고 다른 작업 dataset_b는 매시간 실행됩니다. 다운스트림 작업인 daily_aggregate의 SLA는 오전 8시입니다. 그림 1에서 종속성이 어떻게 보이는지 확인할 수 있습니다.
그림 1: 위에서 설명한 ETL 워크플로우 다이어그램에서 dataset_b에서 daily_aggregate까지의 점선은 왼쪽 조인을 나타냅니다. dataset_b에 대한 종속성을 잠재적으로 제거하여 daily_aggregate 작업이 오전 8시 이전에 완료되도록 할 수 있습니다.
daily_aggregate가 dataset_b에 대한 LEFT JOIN이 있는 dataset_a에서 소싱된다고 가정해 보겠습니다. dataset_b의 데이터는 매시간 실행되므로 daily_aggregate가 빌드될 때까지 가장 최근에 처리된 데이터만 있으면 됩니다. 또한 dataset_b의 ETL을 실행하는 데 일반적으로 15분이 걸리지만 데이터 볼륨이 증가하면 프로세스를 완료하는 데 최대 45분이 걸릴 수 있다고 가정해 보겠습니다. 초기에 daily_aggregate는 시작하기 전에 완료하기 위해 dataset_a와 dataset_b 모두에 의존했습니다. SQL을 변경하지 않고 아래 그림 2와 같이 DAG에서 dataset_b를 제거하기만 하면 됩니다.
그림 2: 처리 시간이 비정상적으로 긴 dataset_b를 ETL 작업에서 제거하면 오전 8시 SLA를 충족하는 데 도움이 됩니다.
dataset_b가 dataset_a에 LEFT JOIN되어 있고 LEFT JOIN 작동 방식을 알고 있으므로 dataset_b에 대한 daily_aggregate의 종속성을 제거할 수 있습니다. 이렇게 하면 특정 시간에 dataset_b가 완료될 때까지 기다릴 필요가 없으므로 이제 SLA를 달성하는 데 도움이 됩니다.
DAG 구성에서 불필요한 종속성을 줄이면 지나치게 큰 DAG가 분리되고 복잡성이 줄어듭니다. 이 최적화는 SLA를 달성하는 저렴하고 효과적인 방법이기도 합니다. Snowflake 웨어하우스 확장과 같은 추가 계산 능력이 필요하지 않습니다.
지난 1년 동안 우리는 모든 데이터가 매일 처리되도록 하기 위해 많은 ETL을 수정해야 했습니다. 최적화 전에는 전체 테이블 새로 고침을 처리하도록 설계된 작업이 디스크로 유출되기 시작했습니다. 일부 데이터 세트는 단기간에 5배 증가했지만 SLA는 동일하게 유지되었습니다.
SLA를 충족하는 한 가지 방법은 많은 ETL을 증분 작업으로 전환하는 것이었습니다. 증분 ETL 을 실행 한다는 것은 전체 테이블을 바꾸는 대신 테이블에 변경된 레코드를 삽입하거나 업데이트하는 것만을 의미합니다. 증분 작업을 수행하면 변경되지 않은 데이터를 재처리하지 않아도 됩니다.
DoorDash에서 Dasher ID, 주문 수준 정보 및 주문과 관련된 수수료와 같은 Dasher 물류와 관련된 속성은 일반적으로 시간이 지나도 변경되지 않습니다. 이 경우 모든 레코드를 삭제하고 동일한 정보를 반복해서 로드할 필요가 없습니다. Snowflake가 필요한 데이터만 처리하도록 ETL 작업을 설계하면 일반적으로 처리되는 데이터가 적다는 것은 처리 시간이 빨라지기 때문에 성능을 높이는 데 도움이 됩니다.
ETL에서 사용되는 열을 정기적으로 검토하는 것은 처리되는 데이터의 양을 줄이는 좋은 방법입니다. 응용 프로그램이 변경되면 업스트림 열이 더 이상 사용되지 않을 수 있습니다. 이러한 열은 ETL에서 제거할 수 있습니다. Snowflake가 열 형식으로 데이터를 저장하는 방식을 고려할 때 모든 ETL 스크립트에서 SELECT * FROM Tablename을 사용하지 않습니다. 항상 테이블에서 필요한 열에만 SELECT를 사용하는 것이 좋습니다.
SELECT를 필요한 열로 제한하면 쿼리 성능을 향상시키는 데 여러 가지 목적이 있습니다.
테이블에서 모든 열을 선택하지 않음으로써 처리 시 데이터 세트의 볼륨이 줄어들어 성능이 향상됩니다.
이 접근 방식은 메모리에서 불필요한 데이터 캐싱을 줄입니다.
대부분의 경우 쿼리 성능은 데이터 볼륨과 데이터 볼륨이 실행 중인 클러스터의 영향을 받습니다. 클러스터 크기를 늘리는 것이 옵션이 아닌 경우 실행 시 데이터 볼륨 처리에 대해 생각해야 합니다.
이 경우 병목 현상을 식별하는 것이 쿼리 성능 향상을 위한 첫 번째 단계입니다. Snowflake의 쿼리 프로필 을 사용하여 속도 저하를 일으키는 문제를 식별했습니다.
여러 조인 또는 무거운 집계 작업으로 특정 쿼리를 실행할 때 데이터 볼륨이 컴퓨팅 메모리를 초과하고 원격 및 로컬 스토리지로 유출되기 시작할 가능성이 있습니다. 원격/로컬 저장소에 유출된 데이터에 대한 작업은 메모리에 있는 데이터에 대한 작업에 비해 훨씬 더 많은 시간이 걸립니다. 이 경우 쿼리를 더 작은 데이터 세트로 분할한 다음 결과 집합을 결합하여 데이터를 처리할 수 있습니다.
데이터 유출을 줄이면 쿼리 계산 시간이 줄어듭니다. 쿼리를 분할하면 Snowflake의 컴퓨팅 비용도 절감됩니다. 주의해야 할 한 가지는 쿼리를 너무 많은 단계로 과도하게 최적화하려는 것입니다. 이를 확인하는 좋은 방법은 쿼리 프로필을 다시 사용하여 실행 계획에서 변경 사항이 어떻게 다른지 확인하는 것입니다.
쿼리가 느리게 실행되거나 쿼리 프로필이 테이블의 모든 파티션을 스캔한다고 제안하는 경우 이는 이 테이블에 적절한 차원의 클러스터 키가 필요함을 나타냅니다. Snowflake는 대형 테이블에 대한 클러스터링 키 를 지원합니다.
클러스터 키는 정의된 차원을 기반으로 테이블을 분할하고 큰 테이블에서 파티션 스캔을 줄이는 데 도움이 됩니다. 대용량 테이블을 실행하는 빈번한 쿼리 및 조인을 분석하고 필터 및 조인 조건에서 가장 많이 사용되는 속성을 기반으로 클러스터링할 차원을 결정해야 합니다.
이 접근 방식을 사용할 때 다음 사항에 유의하는 것이 중요합니다.
클러스터링 키는 낮은 카디널리티, 하나 또는 두 개의 개별 값 또는 높은 카디널리티, 너무 많은 고유 값(예: 타임스탬프)에 정의되어서는 안 됩니다.
클러스터링에는 컴퓨팅 리소스 및 스토리지 관련 비용이 수반됩니다.
쿼리를 최적화하는 또 다른 방법은 코드 내에서 Snowflake 함수를 사용하는 것입니다. 이렇게 하면 코드 가독성이 향상되고 하드 코딩된 수식의 오류 가능성이 줄어듭니다.
우리 사업에서 자주 묻는 질문은 "X는 Y에서 얼마나 떨어져 있습니까?"입니다. 지구의 곡률을 고려할 때 거리는 SQL 에서 계산하기 까다로울 수 있습니다. 이러한 경우 Snowflake의 내장 HAVERSINE 기능을 사용합니다. Haversine은 HAVERSINE( lat1 , lon1 , lat2 , lon2 ) 형식의 경도와 위도가 주어진 구의 두 지점 사이의 큰 원 거리를 계산합니다 .
QUALIFY 는 쿼리에서 중복 값을 제거하는 또 다른 유용한 기능입니다. HAVING 이 SQL에서 GROUP BY인 것처럼 Qualify는 Snowflake 의 창 함수 에 대한 것입니다. 이를 통해 Window 함수의 결과에 따라 행을 필터링할 수 있습니다. 이는 데이터의 두 번째 통과를 방지하는 데 특히 유용합니다. 다음은 Snowflake의 예입니다.
자격 없이
Qualify로
DoorDash의 데이터 팀에게 매일 1% 향상된다는 것은 새로운 ETL을 배포하거나 기존 ETL을 지속적으로 최적화하는 것을 의미합니다. 최적화 순서 체크리스트를 사용하면 다양한 각도에서 최적화에 접근할 수 있습니다. 팀이 성장함에 따라 결과를 더 빠르고 예측 가능하게 제공하기 위해 더 많은 세부 수준을 추가할 수 있기를 바랍니다.
우리 팀은 빠르고 안정적인 데이터를 제공하는 것뿐만 아니라 데이터 파이프라인을 최적화하는 데에도 중점을 둡니다. 이를 통해 컴퓨팅 리소스 사용량을 줄이고 프로세스를 보다 효율적으로 만듭니다. 애플리케이션을 통해 데이터가 흐르는 방식과 그것이 비즈니스에 의미하는 바를 배울 수 있는 기회를 갖는 것은 우리 역할의 더 흥미로운 부분 중 하나입니다.
흥미로운 데이터 문제를 해결하는 데 관심이 있다면 우리 팀 의 역할 을 확인하십시오.
DoorDash의 비즈니스 인텔리전스 팀에 감사드리며 Snowflake 기능을 더 큰 팀에 공유한 Josh Li에게 특별한 찬사를 보냅니다.