如何簡單實現ELT?

在商業中,數據通常和業務、企業前景以及財務狀況相關,有效的數據管理可以幫助決策者快速有效地從大量數據中分析出有價值的信息。數據集成(Data Integration)是整個數據管理流程中非常重要的一環,它是指將來自多個數據源的數據組合在一起,提供一個統一的視圖。

數據集成可以通過各種技術來實現,本文主要介紹如何用ELT(extract, load, transform)實現數據集成。區別於傳統的ETL和其他的技術,ELT非常適合爲數據湖倉或數據集市提供數據管道,並且可以用更低的成本,根據需求,隨時對大量數據進行分析。

接下來將通過一個簡單的示例(Demo)介紹如何實現ELT流程,具體的需求是將原始的電影票房數據保存到數據倉庫,然後再對原始數據進行分析,得出相關的結果並且保存在數據倉庫,供數據分析團隊使用,幫助他們預測未來的收入。

技術棧選擇

  • Data Warehouse: Snowflake
    Snowflake是最受歡迎,最容易使用的數據倉庫之一,並且非常靈活,可以很方便地與AWS、Azure以及Google Cloud集成。

  • Extract:通過k8s的cronjob將數據庫的數據存到S3。

    這裏的技術選型比較靈活,取決於源數據庫的類型以及部署的平臺。Demo中將源數據從數據庫提取出來後放在AWS S3,原因是Snowflake可以很方便與S3集成,支持複製數據到倉庫並且自動刷新。

  • Load:通過Snowflake的External Tables將S3中的數據複製進數據倉庫。

dbt支持使用SQL來進行簡單的轉換,同時提供了命令行工具,使用dbt我們可以進行良好的工程實踐比如版本控制,自動化測試以及自動化部署。但對於比較複雜的業務場景來說,轉換的過程一般都通過自己寫代碼實現。
  • Orchestrator: Airflow

    Airflow和Oozie相比有更加豐富的監控數據以及更友好的UI界面。

下圖描述瞭如何使用上述技術棧實現ELT:

圖中使用的Logo來自snowflake,dbt和Airflow的官方網站

工具介紹

Snowflake:數據存儲

Snowflake是一個將全新的SQL查詢引擎與一個專爲雲設計的創新架構相結合的數據雲平臺,它支持更快更靈活地進行數據存儲、處理以及分析。

示例中將Snowflake作爲數據倉庫,存儲原始數據電影院票房數量以及轉換後的數據。

權限管理

和AWS類似,註冊Snowflake後會持有一個擁有所有權限的root account。 如果直接使用此賬號進行操作會非常危險,所以可以通過Snowflake提供的user和role來進行細粒度的權限管理。

最佳實踐是用root account創建新的user並通過role賦予足夠的權限,後續的操作都使用新創建的user來進行。

在下面的示例中,創建了一個名爲TRANSFORMER的role,並且賦予它足夠的權限。然後再創建一個使用這個role的user,在更方便地管理權限的同時,也實踐了最小權限原則:

-- create role

CREATE ROLE TRANSFORMER COMMENT = 'Role for dbt';

-- grant permission to the role

GRANT USAGE, OPERATE ON WAREHOUSE TRANSFORMING TO ROLE TRANSFORMER;

GRANT USAGE, CREATE SCHEMA ON DATABASE PROD TO ROLE TRANSFORMER;

GRANT ALL ON SCHEMA "PROD"."RAW" TO ROLE TRANSFORMER;

GRANT ALL ON SCHEMA "PROD"."ANALYTICS" TO ROLE TRANSFORMER;

GRANT SELECT ON ALL TABLES IN SCHEMA "PROD"."RAW" TO ROLE TRANSFORMER;

GRANT SELECT ON FUTURE TABLES IN SCHEMA "PROD"."RAW" TO ROLE TRANSFORMER;

-- create user with role TRANSFORMER

create user user_demo password='abc123' default_role = TRANSFORMER must_change_password = true;

數據結構

每一個Snowflake的數據庫都可以有多個schema,這裏我們根據常見的實踐,創建了schema RAW和ANALYTICS,分別用來存放原始數據和轉換之後的數據:

每一個schema下面都可以有table、view和stage等數據庫object。

stage是snowflake提供的一個空間,它支持我們將數據文件上傳到這裏,然後通過copy命令把外部數據導入到Snowflake。圖中MY_S3_STAGE就是Demo中用來加載存放在AWS S3中的數據文件的,我們過這個stage實現了ELT中的L(Loading)。

dbt (data build tool):原始數據轉化

在完成了原始數據的Extract和Loading後,怎樣根據需求對它們進行Transform從而獲得隱藏在數據中的有效信息呢?

這裏我們選擇dbt來進行數據的轉化,它是一個支持我們通過簡單地編寫select語句來進行數據轉換的工具,在Demo中它幫助完成了歷史票房數據的統計工作。

Model

一個model就是一個寫在.sql文件中的select語句,通常會默認使用文件名作爲transform結果的表名。下面是demo中的一個model,from語句後面跟着的是一個dbt提供的引用源數據的方法。在model目錄裏的配置文件中聲明源數據表之後,就可以直接通過source()方法來引用source table了。

select *

from {{ source('ticket_sales','annual_ticket_sales') }}

where ticket_year > ‘2010’

Jinja Function

當需求變得更復雜時,如果僅僅通過SQL實現轉換將會很困難,所以可以通過Jinja Function來實現在SQL中無法做到的事。

比如在有多個Model的dbt工程中,通常會有一些可以複用的邏輯,類似於編程語言中的函數。有了Jinja Function,就可以把要複用的邏輯提取成單獨的Model,然後在其他Model中通過表達式{{ ref() }}來引用它:

select sum(total_inflation_adjusted_office) as total_sales

from {{ ref('annual_ticket_sales') }}

Materializations

在上一部分的場景中,通常不希望把可複用的邏輯持久化在數據倉庫中。

這裏就可以引入配置Materializations來改變dbt對於model的持久化策略,比如將此配置設置爲Ephemeral

{{ config(materialized='table') }}

這樣model就僅被當作臨時表被其他model引用而不會被持久化在數據倉庫中。如果設置爲View,model就會被在數據倉庫中創建爲視圖。除此之外這個配置還支持類型:Table以及Incremental

Test

爲了防止原始數據有髒數據,所以在這裏引入測試幫助保證最後結果的正確性。dbt提供了兩種級別的測試:

  • Generic test:這是一種比較通用的測試,爲字段級別,它通常可以加在對Source和Target的聲明裏,應用於某一個字段並且可以重複使用。比如在demo中,我們希望ticket_year這個字段不爲空並且是不會重複的:
    tables:

         - name: annual_ticket_sales

           columns:

             - name: ticket_year

               description: "Which year does the sales amount stands for"

               tests:

                 - not_null

                 - unique

             - name: tickets_sold

               tests:

                 - not_null

             - name: total_box_office

               tests:

                 - not_null

  • Singular Test:它是通過一段SQL語句來定義的測試,是級別。
    比如查詢源數據表裏total_box_office小於0的記錄,當查詢不到結果時表示測試通過:
    select total_box_office

    from {{source('ticket_sales','annual_ticket_sales')}}

    where total_box_office < 0

Airflow:任務編排

有了把原始數據集成進數據倉庫的方法,也完成了數據轉化的工程, 那麼如何才能讓它們有順序地、定時地運行呢?

這裏我們選擇用Airflow進行任務的編排,它是一個支持通過編程編寫data pipeline,並且調度和監控各個任務的平臺。

DAG

第一步就是爲我們的ELT流程創建一個流水線,在Airflow中,一個DAG(Directed Acyclic Graph)就可以看作是一個pipeline。聲明它的時候需要提供一些基本的屬性,比如DAG name, 運行間隔以及開始日期等等。

Airflow支持使用Python語言編寫pipeline的代碼,因此也具有較強的擴展性。

Demo中我們設置這個DAG的開始日期是2022年5月20號,並且期望它每天運行一次:

default_args = {

   'start_date': datetime(2022, 5, 20)

}

with DAG('annual_ticket_processing', schedule_interval='@daily',

        default_args=default_args, catchup=True) as dag:

Task

流水線創建完成之後,我們需要將ELT的各個步驟加入到這個流水線中。這裏的每一個步驟被稱爲Task,Task是Airflow中的基本執行單位,類似於pipeline中的step。在Demo中,在數據倉庫中創建表、把原始數據加載到數據倉庫、測試和數據轉化分別是一個task。

在Airflow中,可以通過Operator快速聲明一個task,Operator是一個提前定義好的模版,只需要提供必要的參數比如task id,SQL語句等即可。

下面這個task的功能是在Snowflake中創建表,需要提供的是一個連接Snowflake的Connection,要運行的SQL語句以及目標database和schema:

snowflake_create_table = SnowflakeOperator(

       task_id='snowflake_create_table',

       snowflake_conn_id='love_tech_snowflake',

       sql=CREATE_TABLE_SQL_STRING,

       database='PROD',

       schema='RAW',

   )

Task dependency

當我們對於task的運行順序有特定要求時,比如爲了保證最後報告的準確性,希望在對原始數據的測試通過之後再進行數據轉化。這時可以通過定義task之間的依賴關係,來對它們的運行順序進行編排,如下的依賴關係表示先在Snowflake創建數據表,然後將原數據加載到其中,完成後對於原始數據進行測試,如果測試失敗就不會再運行後續的task:

snowflake_create_table >> copy_into_table >> dbt_test >> transform_data

Backfill

在平時的工作中,我們經常會遇到業務變動導致數據表裏新增一個字段的情況,此時就需要將原始數據重新同步一遍。這時就可以利用Airflow提供的Backfill機制,幫助我們一次性回填指定區間內缺失的所有歷史任務。

比如Demo中DAG的start date是5月20日,所以在打開開關之後,Airflow幫我們回填了start date之後的所有DAG run:

上圖中DAG是在5月25日創建的,但Airflow卻只從開始日期創建任務到24號,看起來缺失了25號的任務。原因是上圖的24號是logical date(execution date),即trigger DAG run的日期。因爲在定義DAG的時候將schedule_interval屬性設置爲daily,所以在25日(Actually Execute Date)當天只會執行24日(logical date)的任務。

監控和調試

Airflow提供了友好的UI界面讓我們可以更方便地從各種維度監控以及調試,比如查看一年的運行情況:

或者每一個task的運行時間:

以及task的log:

等等,這裏只列舉了其中幾個,大家有興趣的話可以自己探索。

Parallelism

通常我們需要把多個數據源的數據,集成到同一個數據倉庫中便於進行分析,因爲這些task之間互相沒有影響,所以可以通過同步運行它們來提高效率。

這種場景下,一方面可以通過配置參數Parallelism來控制Airflow worker的數量,也就是同時可以運行的task的數量,另一方面也需要更改Executor的類型,因爲默認的Sequential Executor只支持同時運行一個task。

假設task的依賴關係聲明爲:task_1 >> [task_3, task_2] >> task_4

,在更換到Local Executor並且設置parallelism爲5之後,啓動Airflow,可以發現Airflow會創建5個worker。這時再觸發DAG run,task2和task3就可以同時運行了:

~ yunpeng$ ps -ax | grep 'airflow worker'

 59088 ttys017    0:02.81 airflow worker -- LocalExecutor

 59089 ttys017    0:02.82 airflow worker -- LocalExecutor

 59090 ttys017    0:02.81 airflow worker -- LocalExecutor

 59091 ttys017    0:02.82 airflow worker -- LocalExecutor

 59092 ttys017    0:02.81 airflow worker -- LocalExecutor

DEMO運行結果

原始數據被加載到Snowflake的RAW schema中,dbt project可以隨時引用這些數據:

轉換結果被持久化在ANALYTICS schema裏,這些數據可以直接用來分析,也可以作爲源數據被再次引用:

Repo link

dbt project: https://github.com/littlepainterdao/dbt_development

Airflow: https://github.com/littlepainterdao/airflow

本文整體比較基礎,希望之前沒有接觸過ELT的同學可以通過這篇文章對它以及Snowflake,dbt和Airflow有初步的瞭解。


文/Thoughtworks 丁雲鵬,張倬凡
原文鏈接:https://insights.thoughtworks.cn/how-to-implement-elt/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章