はじめに
以前、負荷テストで「クエリを同時に投げているのにslotが上がらない」という事象にハマった話を書きました。その記事では原因(cache hit)と対策に焦点を当てましたが、今回はその前提となるBigQueryのslotと同時実行の仕組みそのものを、入門者向けに整理してみます。
Slotとは何か
BigQueryにおけるslotとは、クエリなどのジョブを実行するための計算リソースの単位です。クエリを実行する際、BigQueryはそのクエリのデータ量や複雑さに応じて、必要なslot数を動的に判断し、実行に割り当てます。
課金モデルとしては大きく2種類あります。
- オンデマンド課金:スキャンしたデータ量に応じて課金される。プロジェクト/組織単位でのslot上限は存在するが、基本的には都度必要な分だけ使う形
- Editions(容量ベース)課金:あらかじめ「予約(reservation)」という形でslotの枠を確保し、その枠の中でジョブを実行する形
Editionsモデルでは、予約に対してbaseline slot(常時確保される最低限のslot数) と、autoscaling slot(需要に応じて動的に増減するslot) を設定できます。baselineを0に設定した場合、常時確保されるslotは無く、クエリが実行されるたびにautoscalingが必要な分を動的に割り当てる形になります。
Autoscalingの挙動
Autoscalingは、あくまで実際にクエリの実行エンジンが必要とするslot数に応じて動きます。「同時にいくつのクエリが投げられているか」という数そのものではなく、「それぞれのクエリが実際にどれだけの計算リソースを必要としているか」がベースになっている、という点が重要です。
つまり、大量のクエリを同時に投げたとしても、それぞれのクエリが実質的にほとんど計算を必要としない(例えば結果がキャッシュされていて再計算が不要な)場合、autoscalingが感知する「slot不足のシグナル」は発生せず、slotは増えません。
逆に言うと、少数のクエリであっても、それぞれが大量のデータをスキャン・処理する重いクエリであれば、autoscalingはそれに応じてslotを増やそうとします。
同時実行数とFair Scheduling
複数のプロジェクト・複数のジョブが同じ予約(reservation)を共有している場合、BigQueryはfair schedulingという仕組みでslotを配分します。基本的な考え方は、実行中のジョブを持つプロジェクト間でslotを均等に分け合い、さらにそのプロジェクト内の複数ジョブの間でも均等に分け合う、というものです。
例えば1,000 slotの予約に対して、あるプロジェクトが1本の重いクエリを実行し、別のプロジェクトが20本の軽いクエリを同時実行している場合、状況に応じて片方のプロジェクトに多めにslotが配分されることもあれば、必要とするslot数が少なければ余ったslotがもう片方に回される、というように、需要に応じた配分が行われます。
同時実行数の上限(concurrency)を予約側で設定している場合、その上限を超えるジョブは、実行中のジョブが完了するまでキューイング(pending)されます。
キャッシュとslotの関係
BigQueryは、同一のクエリが再度実行された際に結果をキャッシュから返す仕組みを持っています。キャッシュヒットしたクエリは実質的な計算処理を行わないため、autoscalingが検知する「slotを必要としている」というシグナルが発生しません。
これは通常はコスト削減・高速化のためのありがたい仕組みですが、「同時実行時にautoscalingがどう振る舞うか」を検証したい負荷テストのような場面では、逆に検証の妨げになることがあります。テストで使うクエリがキャッシュに乗ってしまうと、実際には計算リソースをほとんど使っていないにもかかわらず、見かけ上は「同時にクエリを実行している」状態になってしまうためです。
まとめ
BigQueryのslotと同時実行の仕組みについて、要点を整理すると以下のようになります。
- slotはクエリ実行に必要な計算リソースの単位で、Editionsモデルではbaseline(常時確保)とautoscaling(動的な増減)を組み合わせて運用する
- autoscalingは「同時に実行されているクエリの数」ではなく「実際に必要とされる計算リソースの量」に応じて動く
- 複数プロジェクト・複数ジョブが予約を共有する場合、fair schedulingによって需要に応じたslot配分が行われる
- キャッシュヒットしたクエリは計算処理を行わないため、autoscalingのシグナルにはならない
この仕組みを理解しておくと、「なぜクエリを同時に投げているのにパフォーマンスが上がらないのか」といった事象を切り分ける際の手がかりになります。