本記事はZOZOテクノロジーズ #1 Advent Calendar 2020 - Qiita 22日目の記事です。
Flex Slotsの概要や導入のメリット、データ基盤における活用用途をご紹介できればと思います。
また、Flex Slotsの購入が失敗した際にオンデマンドに自動で切り替える必要性についても合わせてご紹介できればと思います。
Flex Slotsの概要と導入メリット
BigQueryの料金モデルにはオンデマンド料金と定額料金の2種類あります。
オンデマンドはクエリでスキャンしたデータに対して課金されるモデルで、定額料金は事前に専用のクエリ処理容量を購入します。
オンデマンドは従量課金です。コストメリットがある一方、パフォーマンス面で不安定になります。
Googleが管理するBigQueryのスロットが枯渇すると2000スロット以上使えなくなるような制限があります。重たい処理でマシンパワーが必要ときスロットが使えないことで集計処理が遅延します。また不用意に重たいクエリを投げて課金されてしまうこともあるんじゃないかと思います。
定額プランは安定的なパフォーマンス提供する一方、柔軟性がなくコストメリットがありませんでした。
年間契約と月次契約しかなく、使われた分だけ課金するオンデマンドと比べてBigQueryのコストが高くついてしまいました。
常に高いパフォーマンスは必要なくデータマートなどパフォーマンスが求められるタイミングでのみ使いたかったためです。
Flex Slotsが登場したことでこれまで1ヶ月が購入の最小単位でしたが、60秒単位でBigQueryのスロットが購入できるようになりました。
データ基盤における活用用途としては重い処理の前にスロットを購入し、処理が終わったらスロットを破棄するような使い方があります。費用としては500 スロット1分あたり$0.33です(1時間$20)
cloud.google.com
tech.plaid.co.jp
構成要素について
定額プランの構成要素である、「コミットメント」、「リザベーション」、「アサイメント」について簡単にご紹介できればと思います。
- コミットメント
コミットメントはBigQueryのコンピューティング容量の購入です。スロットを購入し割り当てることでBigQueryのパフォーマンスを高めることができます。
- リザベーション(予約)
リザベーションを使うことで組織にあったスロットの割り当てが可能です。
例えば月次契約で2000スロット購入して、重い処理の前にFlex Slotsで8000スロット購入したとします。
その場合10000スロットをリザベーションし、重たいデータ集計を行うプロジェクトに割り当てることが可能です。
- アサイメント(割り当て)
アサイメントではどのプロジェクトに、どの程度スロットを割り振るか決めます。組織内のどのプロジェクトにどのくらいスロットを割り当てるか定義することができます。
cloud.google.com
ワークフローの概要
先ほど書いたように重たい処理の前(データマートの集計など)にFlex Slotsを購入し、処理が終わったら購入したスロットを破棄するようにしています。
大まかに次のようなワークフローを定義してます。
+bigquery_flex_slots_up: # flex slots buying +bigquery_datamart_job: # datamart task +bigquery_flex_slots_down: # flex slots removement
オンデマンド自動切り替えの必要性
Flex Slotsですが1~2ヶ月に1度購入に失敗します。
購入に失敗するとワークフローが止まってしまうので、購入失敗時は自動でオンデマンドに切り替えるようにして運用してます。
ワークフロー(スロットの購入)
スロットが必要な重い処理の前にスロット数をあげてます。月次契約で2000スロット購入してます。Flex Slotsで7000スロット購入(コミットメント)し、9000スロットを予約(リザベーション)してます。具体的には次のようなdigファイルを作ってます。コンテナイメージをPULLしてタスクを実行してます。
(実行してるタスクの詳細は次章で書きます)
+bigquery_flex_slots_up: +bigquery_flex_slots_reserve_assignment: _retry: 3 _env: GCP_CREDENTIAL: ${secret:gcp.credential} _export: docker: image: ${docker_cloudsdk.image} pull_always: ${docker_cloudsdk.pull_always} sh>: tasks/bigquery_flex_slots_reserve_assignment.sh +bigquery_flex_slots_commitment: _retry: 3 _env: GCP_CREDENTIAL: ${secret:gcp.credential} _export: docker: image: ${docker_cloudsdk.image} pull_always: ${docker_cloudsdk.pull_always} sh>: tasks/bigquery_flex_slots_commitment.sh 7000 +bigquery_flex_slots_verification: _retry: 3 _env: GCP_CREDENTIAL: ${secret:gcp.credential} _export: docker: image: ${docker_cloudsdk.image} pull_always: ${docker_cloudsdk.pull_always} sh>: tasks/bigquery_flex_slots_verification.sh +bigquery_flex_slots_reservation: _retry: 3 _env: GCP_CREDENTIAL: ${secret:gcp.credential} _export: docker: image: ${docker_cloudsdk.image} pull_always: ${docker_cloudsdk.pull_always} sh>: tasks/bigquery_flex_slots_reservation.sh batch 9000
ワークフロー(スロットの破棄)
重たい処理が終わったらスロットを下げてます。予約した9000スロットから月次契約の2000スロットに戻してます。
+bigquery_flex_slots_down: +bigquery_flex_slots_reservation: _retry: 3 _env: GCP_CREDENTIAL: ${secret:gcp.credential} _export: docker: image: ${docker_cloudsdk.image} pull_always: ${docker_cloudsdk.pull_always} sh>: tasks/bigquery_flex_slots_reservation.sh batch 2000 +bigquery_flex_slots_removement: _retry: 3 _env: GCP_CREDENTIAL: ${secret:gcp.credential} _export: docker: image: ${docker_cloudsdk.image} pull_always: ${docker_cloudsdk.pull_always} sh>: tasks/bigquery_flex_slots_removement.sh +bigquery_flex_slots_reserve_assignment: _retry: 3 _env: GCP_CREDENTIAL: ${secret:gcp.credential} _export: docker: image: ${docker_cloudsdk.image} pull_always: ${docker_cloudsdk.pull_always} sh>: tasks/bigquery_flex_slots_reserve_assignment.sh
ワークフロータスク詳細
Digdagワークフローで実行しているタスクの詳細についてご紹介できればと思います。
スロット購入時のタスク
- bigquery_flex_slots_reserve_assignment.sh
アサイメントを行いスロットを割り当てるプロジェクトを決めます。Digdagから環境変数に入ったサービスアカウントキーを渡してコンテナ内で認証を行っています。
#!/bin/bash admin_project_id=$ADMIN_PROJECT_ID # authorization echo $GCP_CREDENTIAL > GCP_CREDENTIAL.json gcloud auth activate-service-account --key-file=GCP_CREDENTIAL.json export BIGQUERYRC=/root/.bigqueryrc # reservation_assignment assignment=$(bash tasks/bigquery_flex_slots_assignment_status.sh) if [ -z "$assignment" ]; then bq mk --reservation_assignment --project_id=${admin_project_id} --assignee_id=<project_id> --location=US --assignee_type=PROJECT --job_type=QUERY --reservation_id=${admin_project_id}:US.batch bash tasks/bigquery_flex_slots_alert_notice.sh 2 fi
- bigquery_flex_slots_assignment_status.sh
アサイメントの状態を取得してます。アサイメントは1度登録すればいいのですがオンデマンド自動切り替え時にアサイメントを削除する必要があります。
#!/bin/bash admin_project_id=$ADMIN_PROJECT_ID # authorization echo $GCP_CREDENTIAL > GCP_CREDENTIAL.json gcloud auth activate-service-account --key-file=GCP_CREDENTIAL.json export BIGQUERYRC=/root/.bigqueryrc # flex slots status bq ls --project_id=${admin_project_id} --location=US --reservation_assignment | sed 's/No reservation assignments found.//g'
- bigquery_flex_slots_commitment.sh
Flex Slotsの購入を行います。購入したスロットはリザベーションすることでアサイメント対象のプロジェクトに割り当てることができます。
#!/bin/bash admin_project_id=$ADMIN_PROJECT_ID slots=$1 # authorization echo $GCP_CREDENTIAL > GCP_CREDENTIAL.json gcloud auth activate-service-account --key-file=GCP_CREDENTIAL.json export BIGQUERYRC=/root/.bigqueryrc # commitment bq mk --project_id=${admin_project_id} --location=US --capacity_commitment --plan=FLEX --slots=${slots}
- bigquery_flex_slots_verification.sh
Flex Slotsが正常に購入できたか確認してます。購入できなかった場合stateはPENDINGとなっています。
name slotCount plan renewalPlan state commitmentEndTime -------------------------------------------- ----------- ------ ------------- --------- ------------------- <admin-project>:US.<id> 7000 FLEX PENDING
購入できなかった場合はオンデマンドに切り替えます。オンデマンドに切り替えるためにアサイメントを削除し、お金がかからないようPENDING状態のスロットを削除します。
#!/bin/bash admin_project_id=$ADMIN_PROJECT_ID # authorization echo $GCP_CREDENTIAL > GCP_CREDENTIAL.json gcloud auth activate-service-account --key-file=GCP_CREDENTIAL.json export BIGQUERYRC=/root/.bigqueryrc # verification flex_slots_status=$(bq ls --capacity_commitment --location US --format prettyjson --project_id=${admin_project_id} | jq 'map(select(.["plan"] | startswith("FLEX"))) | .[] | .state | split("/") | .[0]'| sed 's/"//g') if [ $flex_slots_status = "PENDING" ]; then # change plan from flex slots to ondemand bash tasks/bigquery_flex_slots_removement.sh bash tasks/bigquery_flex_slots_remove_assignment.sh bash tasks/bigquery_flex_slots_alert_notice.sh 1 fi
- bigquery_flex_slots_remove_assignment.sh
定額からオンデマンドに切り替えるためにアサイメントを削除します。アサイメントが削除されるとオンデマンドに切り替わります。
#!/bin/bash admin_project_id=$ADMIN_PROJECT_ID # authorization echo $GCP_CREDENTIAL > GCP_CREDENTIAL.json gcloud auth activate-service-account --key-file=GCP_CREDENTIAL.json export BIGQUERYRC=/root/.bigqueryrc # removement # --format prettyjsonオプションを使うと値が変わる # bq ls --project_id=<admin-project>--location=US --reservation_assignment assignment=$(bq ls --project_id=${admin_project_id} --location=US --reservation_assignment --format prettyjson | jq 'map(select(.["assignee"] | startswith("projects/<project_id>"))) | .[] | .name'| sed 's/projects//g' | sed 's/locations/:/g' | sed 's/reservations/./g' | sed 's/assignments/./g' | sed 's/\///g' | sed 's/"//g') bq rm --project_id=${admin_project_id} --location=US --reservation_assignment $assignment
- bigquery_flex_slots_reservation.sh
リザベーション処理です。重い処理を行う際月次契約2000スロット+ Flex Slotsで購入した7000スロットを足して9000スロットをリザベーションしてます。
重い処理が終わったタイミングで2000スロットに戻してます。
#!/bin/bash admin_project_id=$ADMIN_PROJECT_ID reservation=$1 assignment_project_slot=$2 # authorization echo $GCP_CREDENTIAL > GCP_CREDENTIAL.json gcloud auth activate-service-account --key-file=GCP_CREDENTIAL.json export BIGQUERYRC=/root/.bigqueryrc # reservation assignment=$(bash tasks/bigquery_flex_slots_assignment_status.sh) if [ -n "$assignment" ]; then bq update --project_id=${admin_project_id} --location=US --slots=${assignment_project_slot} --reservation ${reservation} fi
スロット破棄時のタスク
- bigquery_flex_slots_reservation.sh
リザベーション処理です。重い処理が終わったタイミングで2000スロットに戻してます。
#!/bin/bash admin_project_id=$ADMIN_PROJECT_ID reservation=$1 assignment_project_slot=$2 # authorization echo $GCP_CREDENTIAL > GCP_CREDENTIAL.json gcloud auth activate-service-account --key-file=GCP_CREDENTIAL.json export BIGQUERYRC=/root/.bigqueryrc # reservation assignment=$(bash tasks/bigquery_flex_slots_assignment_status.sh) if [ -n "$assignment" ]; then bq update --project_id=${admin_project_id} --location=US --slots=${assignment_project_slot} --reservation ${reservation} fi
- bigquery_flex_slots_removement.sh
購入したFlex Slotsを破棄しています。破棄することでFlex Slots購入分の課金がとまります。
#!/bin/bash admin_project_id=$ADMIN_PROJECT_ID # authorization echo $GCP_CREDENTIAL > GCP_CREDENTIAL.json gcloud auth activate-service-account --key-file=GCP_CREDENTIAL.json export BIGQUERYRC=/root/.bigqueryrc # removement assignment=$(bash tasks/bigquery_flex_slots_assignment_status.sh) if [ -n "$assignment" ]; then capacity_commitment_id=$(bq ls --capacity_commitment --location US --format prettyjson --project_id=${admin_project_id} | jq 'map(select(.["plan"] | startswith("FLEX"))) | .[] | .name | split("/") | .[5]'| sed 's/"//g') bq rm --project_id=${admin_project_id} --location=US --capacity_commitment ${admin_project_id}:US.${capacity_commitment_id} fi