Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AWS Glueを使った Serverless ETL の実装パターン

seiichi
July 03, 2020

AWS Glueを使った Serverless ETL の実装パターン

本セッションでは、「AWSを使ってサーバーレスなETL処理をしたいけど、どうやっていいか分からない?」といった方に向けて、AWS Glueと周辺サービスを利用した実装方法(コーディング、テスト、デプロイ、モニタリングなど)を紹介します。

seiichi

July 03, 2020
Tweet

More Decks by seiichi

Other Decks in Technology

Transcript

  1. 3 よくある話を少しする AWS IoT Core Firehose S3 Amazon Athena Amazon

    Redshift Amazon EMR Amazon QuickSight Collection Visualization Analysis
  2. 4 よくある話を少しする AWS IoT Core Firehose S3 Amazon Athena Amazon

    Redshift Amazon EMR Amazon QuickSight Collection Visualization Analysis 時系列データ を収集 分析処理 ダッシュボード で可視化
  3. 5 よくある話を少しする AWS IoT Core Firehose S3 Amazon Athena Amazon

    Redshift Amazon EMR Amazon QuickSight Collection Visualization Analysis 時系列データ を収集 分析処理 ダッシュボード で可視化 そのままでは使えない場合が多い データフォーマットがバラバラ 非構造化/半構造化データなど
  4. 7 ETL処理が必要 AWS IoT Core Firehose S3 Amazon Athena Amazon

    Redshift Amazon EMR Amazon QuickSight Collection Visualization Analysis 時系列データ を収集 分析処理 ダッシュボード で可視化 Load Transform Extract
  5. 11 AWS Glueとは? ※ https://aws.amazon.com/jp/glue/ から引用 AWS Glue は抽出、変換、ロード (ETL)

    を行う完全マネージド型のサービスで、お 客様の分析用データの準備とロードを簡単にします。AWS マネジメントコンソール で数回クリックするだけで、ETL ジョブを作成および実行できます。AWS Glue で は、AWS に保存されたデータを指定するだけで AWS Glue によるデータ検索が行 われ、テーブル定義やスキーマなどの関連するメタデータが AWS Glue データカタ ログに保存されます。
  6. 26 例えば IoTデバイスからセンサーデータがJSON形式で送信されている 項目名やデータ型のなど分析基盤側で扱いづらい状態 AWS IoT Core Firehose S3 IoT

    sensor IoT sensor Amazon Redshift E • 保存されている生デー タを取得 T • カラム名・データ型を変 換 L • 分析基盤へロード ETL
  7. 27 蓄積されているデータ AWS IoT Core Firehose S3 IoT sensor IoT

    sensor [ { "device_id": "A", "timestamp": 1593007044, "location": { "lat": "35.698362", "long": "139.773288" } } …
  8. 28 蓄積されているデータ AWS IoT Core Firehose S3 IoT sensor IoT

    sensor [ { "device_id": "A", "timestamp": 1593007044, "location": { "lat": "35.698362", "long": "139.773288" } } … 項目名を変えたい
  9. 29 蓄積されているデータ AWS IoT Core Firehose S3 IoT sensor IoT

    sensor [ { "device_id": "A", "timestamp": 1593007044, "location": { "lat": "35.698362", "long": "139.773288" } } … データ型を変えたい
  10. 37 実演の振り返り AWS IoT Core Firehose S3 IoT sensor IoT

    sensor Amazon Redshift AWS glue data catalog Crawler AWS IoT Core Firehose S3 IoT sensor IoT sensor Amazon Redshift AWS Glue Job ETL
  11. 41 Glue JobとLambdaの比較 並列度を上げて処理したい場合はLambdaを検討すべき ※2020/06/04時点での東京リージョンでの比較 AWS Glue Job (Python Shell)

    AWS Glue Job (Spark) AWS Lambda メモリ 1GB or 16GB 32GB - 128 - 3,008 MB 実行時間 デフォルトで48h デフォルトで48h 最大15min 同時実行数 アカウント内で50 アカウント内で50 リージョン毎に1,000 課金額 従量課金≒性能 * 実行時間(s) (ただし最小10分) 従量課金≒性能 * 実行時間(s) (ただし最小10分) 従量課金≒性能 * 実行時間 (100ms) 起動のオーバーヘ ッド 数十秒 - 数分 数十秒 - 数分 数ミリ秒 - 数秒 言語 Python Python or Scala Python, Node.js … など多数
  12. AWS Step Functions workflow 42 Glue Workflow と StepFunctionsの比較 多少難易度は高いが、StepFunctionのほうが柔軟

    AWS Glue Workflow AWS Step Functions 複雑さ 低 中 柔軟さ 低 高 連携先 少ない 多い
  13. 48 開発とテストのパターン LocalStack .py AWS Cloud AWS Cloud AWS Cloud

    AWS Glue 開発エンドポイント AWS Glue Job .py ① ② ③ ④ AWS Services
  14. 49 開発とテストのパターン LocalStack .py AWS Cloud AWS Cloud AWS Cloud

    AWS Glue 開発エンドポイント AWS Glue Job .py ① ② ③ ④ AWS Services 実行環境が ローカル 実行環境が AWS Glue
  15. 52 今回紹介するパターン ② ④ LocalStack .py AWS Cloud AWS Cloud

    AWS Cloud AWS Glue 開発エンドポイント AWS Glue Job .py ① ③ AWS Services
  16. 55 ② 事前のセットアップ ※ https://docs.aws.amazon.com/ja_jp/glue/latest/dg/aws-glue- programming-etl-libraries.html を参照 Version Python 3.6.9

    aws-glue-libs glue-1.0 Apache Maven 3.6.0 Apache Spark 2.4.3 boto3 1.14.13 pytest 5.4.3 AWS Cloud .py ② AWS Services ※ aws-glue-libsの不具合: https://github.com/awslabs/aws-glue- libs/issues/25
  17. 56 ② 事前のセットアップ AWS Cloud .py ② AWS Services Dockerfileを作成するのもアリ

    Version Python 3.6.9 aws-glue-libs glue-1.0 Apache Maven 3.6.0 Apache Spark 2.4.3 boto3 1.14.13 pytest 5.4.3
  18. 57 ② aws-glue-libsにPytestが用意されている Amazon Redshift Spectrum S3 AWS Cloud $

    git clone -b glue-1.0 --depth 1 https://github.com/awslabs/aws-glue-libs glue/aws-glue-libs AWS glue data catalog Crawler S3 AWS Glue Job glue/aws-glue-libs/bin/ ├── glue-setup.sh ├── gluepyspark ├── gluepytest └── gluesparksubmit .py Pytestを利用する
  19. テストコードを用意 58 ② 簡単なテストコードを用意 import sys from src.pyspark.timeseries_etl import main_job

    class TestClass(object): def test_run_job(self): # 引数を指定 args = { "--JOB_NAME": "timeseries_etl", "--DB_NAME": "arai-test- devio2020_database", "--TBL_NAME": "timeseries_data", "--OUTPUT_DEST": "s3://arai-test- devio2020/pyspark-output", } sys.argv += [item for pair in args.items() fo r item in pair] # ジョブを起動 main_job() Amazon Redshift Spectrum S3 AWS Cloud AWS glue data catalog Crawler S3 AWS Glue Job .py
  20. ソースコード 59 ② 簡単なソースコードを用意 def main_job(): # 引数取得 args =

    getResolvedOptions(sys.argv, [ "JOB_NAME", "DB_NAME", "TBL_NAME", "OUTPUT_DEST", ]) # セットアップ sc = SparkContext() glueContext = GlueContext(sc) spark = glueContext.spark_session job = Job(glueContext) job.init(args["JOB_NAME"], args) # コミット job.commit() Amazon Redshift Spectrum S3 AWS Cloud AWS glue data catalog Crawler S3 AWS Glue Job .py
  21. ソースコード 61 ② 徐々に育てていく # DynamicFrameの作成 src_timeseries_dyf = glueContext.create_dynamic_f rame.from_catalog(

    database=args["DB_NAME"], table_name=args["TBL_NAME"], transformation_ctx="src_timeseries_dyf" ) # マッピング mapping_list = [ ("device_id", "string", "device_id", "string" ), ("timestamp", "int", "timestamp", "int"), ("location.lat", "string", "latitude", "doubl e"), ("location.long", "string", "longitude", "dou ble") ] map_timeseries_dyf = ApplyMapping.apply( frame=src_timeseries_dyf, mappings=mapping_list, transformation_ctx="timeseries_map_dyf" ) Amazon Redshift Spectrum S3 AWS Cloud AWS glue data catalog Crawler S3 AWS Glue Job .py
  22. DataFrameへの変換 62 ② SparkのDataFrameも活用 # DataFrameに変換 timeseries_df = map_timeseries_dyf.toDF() #

    カラム追加 timeseries_df = timeseries_df.withColumn( "year", from_unixtime('timestamp', 'yyyy')) timeseries_df = timeseries_df.withColumn( "month", from_unixtime('timestamp', 'MM')) # 再パーティショニング timeseries_df = timeseries_df.repartition('year', 'month', 'device_id') # DataframeをDynamicFrameに変換 timeseries_dyf = DynamicFrame.fromDF( timeseries_df, glueContext, "timeseries_dyf") Amazon Redshift Spectrum S3 AWS Cloud AWS glue data catalog Crawler S3 AWS Glue Job .py
  23. S3へ保存 63 ② テスト用のバケットに出力し中身をチェック # 保存 save_timeseries_dyf = glueContext.write_dynamic_f rame.from_options(

    frame=timeseries_dyf, connection_type="s3", connection_options={ "path": args["OUTPUT_DEST"], "partitionKeys": [ "year", "month", "device_id" ] }, format="parquet", transformation_ctx="save_timeseries_dyf" ) Amazon Redshift Spectrum S3 AWS Cloud AWS glue data catalog Crawler S3 AWS Glue Job .py
  24. テストコード 64 ② テストの前後処理や結果のチェックを実装 def pytest_runtest_setup(): print('¥n-----setup-----¥n') # テストデータの作成 TEST_BUCKET.create()

    …省略 def pytest_runtest_teardown(): print('¥n-----teardown-----¥n') # テストデータの削除 TEST_BUCKET.objects.all().delete() …省略 def test_run_job(self): …省略 main() # 結果の取得 res_data = self.get_result_data() # 正解の取得 corr_data = self.get_correct_data() # 結果の確認 assert res_data == corr_data Amazon Redshift Spectrum S3 AWS Cloud AWS glue data catalog Crawler S3 AWS Glue Job .py
  25. launch.json 65 ② うまく設定すればデバッグ実行も可能 { "name": "PyTest Glue", "type": "python",

    "request": "launch", "stopOnEntry": false, "pythonPath": "${command:python.interpreterPath}", "module": "pytest", “args”: [“-svv”, "${file}", "--color", "yes"], "cwd": "${workspaceRoot}", "env": { "PYTHONPATH": "${workspaceRoot}/glue/aws-glue- libs:/usr/local/spark/python/lib/py4j-0.10.7- src.zip:/usr/local/spark/python/:${command:python.in terpreterPath}", "SPARK_CONF_DIR": "${workspaceRoot}/glue/aws- glue-libs/conf", "AWS_SECRET_ACCESS_KEY": “<your_access_key>", "AWS_ACCESS_KEY_ID": "<your_key_id>", "AWS_SESSION_TOKEN": "<your_token>" }, "console": "internalConsole", "internalConsoleOptions": "openOnSessionStart“ } Amazon Redshift Spectrum S3 AWS Cloud AWS glue data catalog Crawler S3 AWS Glue Job .py
  26. ソースコード 68 ② Pandasが便利 def main(plant_id_dpac, current_date): # 引数取得 args

    = getResolvedOptions(sys.argv, [ "JOB_NAME", "DB_NAME", "TBL_NAME", "OUTPUT_DEST", ]) # データの取得 timeseries_list = get_timeseries_list() # 該当データがなければ即終了 if not timeseries_list: logger.info("Timeseries data not found.") return # pandasのDataframeに変換 timeseries_df = pd.DataFrame(timeseries_list) # TODO: 業務処理 …省略 Amazon Redshift Spectrum S3 AWS Cloud AWS glue data catalog Crawler S3 AWS Glue Job .py
  27. テストコード 71 ④ PytestでE2Eテストを実行 def test_exec_job(self, mocker): # 引数をオーバーライドしてジョブを実行 exec_res

    = glue_client.start_job_run( JobName=pytest.JOB_NAME, Arguments={ "--DB_NAME": "arai-test- devio2020_database", "--TBL_NAME": "timeseries_data", "—-OUTPUT_DEST”: "s3://arai-test- devio2020/pyspark-output" } ) # ジョブの起動が成功していることを確認 assert exec_res["ResponseMetadata"]["HTTPStatusCo de"] == 200 job_run_id = exec_res["JobRunId"] Amazon Redshift Spectrum S3 AWS Cloud AWS glue data catalog Crawler S3 AWS Glue Job
  28. テストコード 72 ④ Glue Jobが完了するのを待って結果の確認 …省略 # ジョブが完了するまで待つ job_status =

    wait_until_job_finished(pytest.JOB_N AME, job_run_id, 300) # ジョブが成功していることを確認 assert job_status == "SUCCEEDED" # テスト結果の取得 df_test_res = self.get_result_by_dataframe() # 結果の確認 assert_frame_equal(df_test_res, df_corr_res) Amazon Redshift Spectrum S3 AWS Cloud AWS glue data catalog Crawler S3 AWS Glue Job
  29. 74 ソースコードと外部ライブラリのデプロイ AWS Cloud from setuptools import setup setup( name="pandas",

    version="0.25.1", packages=[], install_requires=['pandas==0.25.1'] ) $ python setup.py bdist_wheel S3 .py .whl AWS Glue Job 参照 Upload
  30. 75 AWS Glueリソースのデプロイ AWS Glue Job AWS glue data catalog

    Crawler AWS Cloud # Glue resource "aws_glue_catalog_database" "database" { name = "${var.system_name}_database" } resource "aws_glue_crawler" "timeseries_crawler" { name = "${var.system_name}_timeseries_crawler" database_name = aws_glue_catalog_database.database.n ame role = aws_iam_role.glue_iam_role.arn schedule = "cron(0 */1 * * ? *)" table_prefix = "timeseries_" s3_target { path = "s3://${var.system_name}/Data" } } resource "aws_glue_job" "timeseries_etl" { name = "${var.system_name}_timeseries_etl" command { script_location = "s3://${var.system_name}/glue- script/pyspark/timeseries_etl.py" python_version = 3 } role_arn = aws_iam_role.glue_iam_role.arn timeout = 60 max_capacity = 2 glue_version = "1.0" default_arguments = { "--job-language" = "python" "--enable-metrics" = "true" "--extra-py- files" = "s3://${var.system_name_prefix}-artifacts- store/glue/modules/pandas-0.25.1-py3-none-any.whl" "--job-bookmark-option" = "job-bookmark-enable" …省略
  31. 78 アラート通知 AWS Glue Job Amazon CloudWatch Events Amazon Simple

    Notification Service AWS Lambda Amazon CloudWatch Metrics …省略 "source": "aws.glue", "detail-type": "Glue Job State Change", "time": "2020-01-09T09:33:40Z", "region": "ap-northeast-1", "resources": [], "detail": { "jobName": "glue-job-for-err-notification", "severity": "ERROR", "state": "FAILED", "jobRunId": "jr_a56a071553c6038a20f1578f74a013 6c94e1ea946a3e9516322b4b2bd2e3a5f4", "message": "Command failed with exit code 1" イベントソースの定義 受け取れるイベント