GCP BigQuery Subscriptionを試す

# 概要
この記事は[QualiArts Advent Calender 2022](https://qiita.com/advent-calendar/2022/qualiarts)の13日目の記事になります。

はじめまして、23卒内定者バイトのやましーです。QualiArtsでは2ヶ月間バックエンドのタスクに取り組んでいました。

今回はゲームの行動ログのパイプラインとして、BigQuery Subscriptionを構築した際にインプットしたことをまとめてみました。初めての技術記事となりますのでお手柔らかにお願いします。

*BigQuery, Cloud Pub/Sub自体の詳しい話は書いてません

*Terraformのサンプルコードを載せていますが、IAMやサービスアカウントの構築については今回扱いません

# 前提
BigQuery SubscriptionとはCloud Pub/SubのSubscriptionの一つで、BigQueryに直接データを投入することができます。

[BigQuery Subscription公式ドキュメント](https://cloud.google.com/pubsub/docs/bigquery?hl=ja)

余談ですが、BigQuery Subscription が出る以前は、 Cloud Pub/Sub から BigQueryへデータを投入するには、 Cloud Dataflow や Cloud Functions などの別サービスを経由する必要がありました。

今後はメッセージを変換せずにBigQueryに保存したい場合はBigQuery Subscription、変換したい場合はDataflowをかませる使い分けになると思います。

[Dataflow ストリーミング テンプレート](https://cloud.google.com/dataflow/docs/guides/templates/provided-streaming?hl=ja#cloudpubsubsubscriptiontobigquery)

# 事前準備
Subscription作成前に、BigQueryのテーブル作成とpub/subのサービスアカウントに必要な権限を付与しておくとスムーズです。

### BigQueryのテーブル作成
テーブルのカラムはBigQuery Subscriptionのプロパティによって必要なものが変わります。

今回は`トピックスキーマを使用しない`、`メタデータを書き込む`のプロパティを選択することを前提としています。

*以後、Terraformのコードは大部分が公式ドキュメントからのコピペになります。

  一部、プロジェクトに寄せた書き方をしています。

[Terraform Cloud pub/sub 公式ドキュメント](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/pubsub_subscription#example-usage---pubsub-subscription-push-bq)

```bash
resource "google_bigquery_dataset" "test" {
  dataset_id = "example_dataset"
}

resource "google_bigquery_table" "test" {
  table_id   = "example_table"
  dataset_id = google_bigquery_dataset.test.dataset_id
  schema     = file("./schemas/subscription.json")

  deletion_protection = true

  time_partitioning {
    type                     = "DAY"
    field                    = "publish_time"
    require_partition_filter = true
  }
}
```

time_partitioningを設定し、検索期間の条件にバリデーションをかけることで、想定外のクエリコストを抑えることができます。

繰り返しになりますが、今回はSubscriptionのプロパティとして`トピックスキーマを使用しない`、`メタデータを書き込む`を選択しています。

> [**トピック スキーマを使用する**] オプションを選択しない場合は、BigQuery テーブルに `BYTES`
、`STRING`、または `JSON` 型の `data` という列があることを確認してください。Pub/Sub は、この BigQuery 列にメッセージを書き込みます。
> 

> [**メタデータの書き込み**] オプションを選択する場合は、BigQuery テーブルに次のテーブルに示すフィールドがあることを確認してください。
> 

上記公式ドキュメントの引用にしたがって書いたテーブルのjsonスキーマが以下になります

```json
[
  {
    "name": "subscription_name",
    "description": "サブスクリプションの名前",
    "type": "STRING",
    "mode": "NULLABLE"
  },
  {
    "name": "message_id",
    "description": "メッセージのID",
    "type": "STRING",
    "mode": "NULLABLE"
  },
  {
    "name": "publish_time",
    "description": "メッセージをパブリッシュした時刻",
    "type": "TIMESTAMP",
    "mode": "REQUIRED"
  },
  {
    "name": "data",
    "description": "メッセージの本文",
    "type": "STRING",
    "mode": "REQUIRED"
  },
  {
    "name": "attributes",
    "description": "すべてのメッセージ属性を含むJSONオブジェクト",
    "type": "STRING",
    "mode": "NULLABLE"
  }
]
```

### pub/subサービスアカウント
次にCloud Pub/SubサービスアカウントにBigQueryへのデータ編集者（`roles/bigquery.dataEditor`）とメタデータ閲覧（`roles/bigquery.metadataViewer`）を割り当てます。

[Pub/Sub サービス アカウントに BigQuery のロールを割り当てる](https://cloud.google.com/pubsub/docs/create-subscription?hl=ja#assign_bigquery_service_account)

```bash
data "google_project" "project" {
}

resource "google_project_iam_member" "viewer" {
  project = data.google_project.project.project_id
  role   = "roles/bigquery.metadataViewer"
  member = "serviceAccount:service-${data.google_project.project.number}@gcp-sa-pubsub.iam.gserviceaccount.com"
}

resource "google_project_iam_member" "editor" {
  project = data.google_project.project.project_id
  role   = "roles/bigquery.dataEditor"
  member = "serviceAccount:service-${data.google_project.project.number}@gcp-sa-pubsub.iam.gserviceaccount.com"
}
```

一点注意としてはサービスアカウントの形式が`service-**project-number**@gcp-sa-pubsub.iam.gserviceaccount.com`となる点です。

> 一部の Google Cloud サービスには、サービスがリソースにアクセスできる Google Cloud 管理サービス アカウントがあります。これらのサービス アカウントは、サービス エージェントと呼ばれます。Pub/Sub は、プロジェクトごとにサービス アカウントを `service-**project-number**@gcp-sa-pubsub.iam.gserviceaccount.com` の形式で作成して維持します。
> 

# TopicとSubscription作成
ここまでくればTopicとSubscriptionをそれぞれ作成するだけです。

今回はメタデータを書き込みたいので`write_metadata = true`とします。

```bash
resource "google_pubsub_topic" "example" {
  name = "example-topic"
}

resource "google_pubsub_subscription" "example" {
  name  = "example-subscription"
  topic = google_pubsub_topic.example.name

  bigquery_config {
    table = "${google_bigquery_table.test.project}:${google_bigquery_table.test.dataset_id}.${google_bigquery_table.test.table_id}"
	write_metadata = true
  }
}
```


TopicとSubscriptionが作成できたら、コンソールなどでデータを投入してみましょう！

# コスト
最後にコストについて触れておきます。

BigQuery Subscriptionでは$50/TiBのコストがかかり、追加の BigQuery データ取り込み料金はかかりません。

しかしこれはBigQuery Subscriptionの話で、BigQuery自体のストレージやデータ抽出などに関しては別途料金がかかります。

またBigQuery Subscriptionは他のSubscriptionと違い10 GiBの無料枠がないので注意が必要です。

> The first 10 GiB of BigQuery subscription throughput is not free.
> 

# おわりに
この記事ではBigQuery Subscriptionを構築するにあたりインプットしたことをまとめてみました。

自分自身、インフラやパブリッククラウドの経験が乏しいので今後も勉強していきたいと思います。

誤りなどあればコメント頂けると幸いです。