見出し画像

firestore-bigquery-exportでFirestoreとエミュレートしたBigQueryでのデータ連携をローカルでやってみた

はじめに

こんにちは、SHIFTの開発部門に所属している Katayama です。

前回の記事では、Firebase Local Emulator Suiteを利用してローカルの開発環境で Firebase Extensions のfirestore-bigquery-exportを実行し、Firestore→BigQuery のデータ連携を試してみた。ただ、BigQuery の接続先はクラウドの GCP 上になっていた。

今回はAWS のサーバーレスでの開発の記事で取り上げたように、本番環境に一切接続せず、ローカルの開発環境に閉じて開発を行えるように、BigQuery のエミュレーターのセットアップをやってみたい。また、ローカルの BigQuery エミュレーターに接続して、firestore-bigquery-export を利用した Firestore→BigQuery のデータ連携もやってみたいと思う。また、今回ハマったポイントについても触れている。

※今回のデータ連携の全体を図示すると以下のようになる。

BigQuery のエミュレーターをセットアップする

BigQuery Emulatorを利用する(利用手順はここに記載がある)。今回は Docker コンテナで BigQuery のエミュレーターを立ち上げてみたいと思う。docker-compose.yaml は以下のようになる(docker run でもいいが後々サーバーなど依存する別サービスをコンテナで起動する場合を考えて docker-compose を利用している)。

version: '3.9'
services:
  bq:
    image: ghcr.io/goccy/bigquery-emulator:latest
    container_name: bq-emulator
    ports:
      - 9050:9050
    working_dir: /work
    volumes:
      - ./bq/testdata.yaml:/work/testdata.yaml
    command: |
      --project=test --data-from-yaml=./testdata.yaml
projects:
  - id: test
    datasets:
      - id: dataset1
        tables:
          - id: table_a
            columns:
              - name: id
                type: INTEGER
              - name: name
                type: STRING
              - name: createdAt
                type: TIMESTAMP
            data:
              - id: 1
                name: alice
                createdAt: '2022-10-21T00:00:00'
              - id: 2
                name: bob
                createdAt: '2022-10-21T00:00:00'

一部補足をする。

  • command の"--project"オプション
    これは GCP では projectId を指定する必要があるのでそれに倣ったもの。bq コマンド実行時に指定する projectId と同じになる。省略すると、以下のようにエラーになる。

  • command の"--data-from-yaml"オプション
    公式に書かれているが、データセット・テーブル・データを yaml であらかじめ定義し、コンテナ起動時にそれらを自動で構築してくれるオプション。

上記の設定後、docker compose up コマンドでコンテナを起動すると、以下のように 9050 ポートで REST API のエンドポイントが作成される。

あとは、以下のように bq コマンドを実行してみると、BigQuery のエミュレーターからデータが取得できる事が確認できる。

ここまでで BigQuery のエミュレーターのセットアップは完了になる。続いて、firestore-bigquery-export で BigQuery のエミュレーターにデータを insert するのをやってみたいと思う。

firestore-bigquery-export で Firestore→BigQuery にデータ連携をしてみる

firestore-bigquery-export 自体は独立した Cloud Functions なので、Firebase Local Emulator Suite で起動する際に、BigQuery のエンドポイントを設定するなどはできない。

ただ、前回の記事で、初回時には"~/.cache/firebase"以下に Extentions のソースコードがダウンロードされ、Firebase Local Emulator はこのコードを実行し、ローカル環境で Extentions を動かす、という事を書いていたように、ソースコード自体は手元に落ちてきている。

という事はそのソースコードの中で BigQuery を new している箇所を暫定的に書き換えれば、ローカルの BigQuery エミュレーターに接続するようにする事もできる。実際にやってみたいと思う。

今回はfirestore-bigquery-export@0.1.31を利用しているので、ソースコードは以下に存在する。

  • ~/.cache/firebase/extensions/firebase/firestore-bigquery-export@0.1.31/functions/lib/index.js(ビルドされる前の TypeScript だとここ

コードを読むと分かるが、"@firebaseextensions/firestore-bigquery-change-tracker"の record というメソッドが実行されている事が分かる。

const firestore_bigquery_change_tracker_1 = require("@firebaseextensions/firestore-bigquery-change-tracker");
...
const eventTracker = new firestore_bigquery_change_tracker_1.FirestoreBigQueryEventHistoryTracker({
    ...
});
...
exports.fsexportbigquery = functions.firestore
    .document(config_1.default.collectionPath)
    .onWrite(async (change, context) => {
    ...
    try {
        ...
        await eventTracker.record([
            ...
        ]);
    ...
});

そして require でインポートされているので、このモジュールは node_modules にあるはずなので、さらにコードを読んでいくと、以下に BigQuery を new しているコードがある事が分かる。

  • ~/.cache/firebase/extensions/firebase/firestore-bigquery-export@0.1.31/functions/node_modules/@firebaseextensions/firestore-bigquery-change-tracker/lib/bigquery/index.js(ビルドされる前の TypeScript だとここ

...
const bigquery = require("@google-cloud/bigquery");
...
class FirestoreBigQueryEventHistoryTracker {
    constructor(config) {
        ...
        this.bq = new bigquery.BigQuery();
        ...
    }
...
}

というわけで、上記の new bigquery.BigQuery()を以下のように書き換える事で、エンドポイントを localhost:9050 にできる(デフォルトだとここの設定にある通り、bigquery.googleapis.com になる)。

修正後のコードは以下。

class FirestoreBigQueryEventHistoryTracker {
    constructor(config) {
        ...
        this.bq = new bigquery.BigQuery({ apiEndpoint: "localhost:9050" });
        ...
    }
...
}

ただし、以下のログの通り、HTTPS のリクエストになってしまう。

 {"severity":"WARNING","message":"Failed while waiting to initialize. request to https://localhost:9050/bigquery/v2/projects/fir-cloud-functions-trial/datasets/firestore_export? failed, reason: write EPROTO 140277379065792:error:1408F10B:SSL routines:ssl3_get_record:wrong version number:../deps/openssl/openssl/ssl/record/ssl3_record.c:332:\n"}

どうしてこうなってしまうか?だが、firestore-bigquery-export が依存している"@google-cloud/bigquery"のコードに https がべた書きされているため(元の TypeScript だとここ)。

class BigQuery extends common.Service {
    constructor(options = {}) {
        options = Object.assign({}, options, {
            apiEndpoint: options.apiEndpoint || 'bigquery.googleapis.com',
        });
        const config = {
            apiEndpoint: options.apiEndpoint,
            baseUrl: `https://${options.apiEndpoint}/bigquery/v2`, // <- ここ
            scopes: ['https://www.googleapis.com/auth/bigquery'],
            packageJson: require('../../package.json'),
        };
        ...
    }

そのためローカル環境で実行できるようにするために、上記の https を http に変更する必要がある。この修正を加えて、後は Firebase Local Emulator を起動すればいい。起動後、Firestore にデータが登録されると、firestore-bigquery-export の Functions が起動し、BigQuery にデータ連携が行われる事が確認できる(ログを見れば分かるが、データセットやテーブル・ビューはなければ自動で作成される)。

ローカルの BigQuery に CLI 経由でデータを取得してみると、データが取得できるので問題なく BigQuery にデータが連携できている事が確認できる。

※BigQuery のエミュレーター側のログは以下。

※上記のコードだが、firestore-bigquery-export が依存する@google-cloud/bigquery@4.7.0と古いため https がべた書きされているが、最新の@google-cloud/bigquery@6.2.0であれば、以下のコードのようになっているので、環境変数 BIGQUERY_EMULATOR_HOST を設定するか、new BigQuery({ apiEndpoint: "http://localhost:9050" })のように apiEndpoint オプションを渡せば、自由にエンドポイントを設定できる。

...
  constructor(options: BigQueryOptions = {}) {
    let apiEndpoint = 'https://bigquery.googleapis.com';

    const EMULATOR_HOST = process.env.BIGQUERY_EMULATOR_HOST;

    if (typeof EMULATOR_HOST === 'string') {
      apiEndpoint = BigQuery.sanitizeEndpoint(EMULATOR_HOST);
    }

    if (options.apiEndpoint) {
      apiEndpoint = BigQuery.sanitizeEndpoint(options.apiEndpoint);
    }

    options = Object.assign({}, options, {
      apiEndpoint,
    });

    const baseUrl = EMULATOR_HOST || `${options.apiEndpoint}/bigquery/v2`;

    const config = {
      apiEndpoint: options.apiEndpoint!,
      baseUrl,
      ...
    };
...

まとめとして

今回は GCP の本番の BigQuery に接続せず、ローカルの開発環境に閉じて Firestore→BigQuery のデータ連携をやってみた。何らかの事情で本番の GCP へのアクセス権限などを付与できないような場面では、こうしたエミュレーターを利用した開発ができると便利だろう。

ただ、node_modules のコードを書き換えるという普段はやらない事をやる必要がある方法なので、その点はデメリットになるだろう…。

《この公式ブロガーの記事一覧》


執筆者プロフィール:Katayama Yuta
認証認可(SHIFTアカウント)や課金決済のプラットフォーム開発に従事。リードエンジニア。 経歴としては、SaaS ERPパッケージベンダーにて開発を2年経験。 SHIFTでは、GUIテストの自動化やUnitテストの実装などテスト関係の案件に従事したり、DevOpsの一環でCICD導入支援をする案件にも従事。その後現在のプラットフォーム開発に参画。

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/