クラウド道場

Firestore(Datastore モード)で日次Backupを取得してBigQueryにインポートする

Author
Lv:3 Exp:2580

最近はGolangをよく触っています。

今回の記事では GCPの主要プロダクトであるFirestore(Datastore モード)の日次Backup方法とBigQueryへのインポート方法について、GCE、GAE cronを用いて行う方法を紹介いたします。

Firestoreとは

GCPが提供するフルマネジドなNoSQLサービスです。

FirestoreにはFirebase Realtime Databaseの後継であるFirestore(ネイティブ モード)とCloud Datastoreの後継であるFirestore(Datastore モード)の二種類のモードが存在しますが、今回はFirestore(Datastore モード)について扱っていきます。

以下の記事内では混乱を避けるため、便宜上Firestore(Datastore モード)のことをDatastoreと呼ぶことにします。

Datastoreの作成方法

Firestoreの利用開始時に下記のようにどちらを作成するか、選択できるようになっています。

作成時にはDatastoreのリージョンを選択します。

2019/5/8時点では、App Engineを作成すると同じリージョンで旧Datastoreが裏側で作成されますが、その場合でもDatastoreのコンソールより、FirestoreにUpgradeすることが可能です。ただし、Datastoreにまだ一度もデータを挿入していないことが条件となります。


DatastoreのExport機能について

DatastoreにはEntityのExport/Import機能が用意されています。
今回は既に用意されているこの機能を使用し、Datastoreの日次Backupを取得していきます。

公式ドキュメント

GCS Bucket作成

DatastoreのEntityをExportする為のGCSのBucketを作成します。

BucketのリージョンはDatastore作成時に決定したリージョンと合わせる必要があります。

ナビゲーションメニューより、Storageを開き、「バケットを作成」をクリックします。
任意のBucket名を付け、場所はDatastoreのリージョンと合わせるようにし、作成をクリックします。

Backupジョブ作成

Backupを行うジョブを作成していきます。
今回紹介するのは下記の二種類です。

  • GCE + cron(GCEインスタンスのcronでgcloudコマンドを実行)
  • GAE cron(GAE cronで用意したエンドポイントを実行)

GCE + cron

サービスアカウントの準備

DatastoreのEntityをGCSへExportする必要がある為、下記の2つの権限を付けたサービスアカウントを用意します。

  • Datastoreインポート/エクスポート管理者
  • GCSオブジェクト作成者

インスタンス作成

GCEではデフォルトのサービスアカウントを指定できるので、前の手順で作成したサービスアカウントを指定します。

GCPにはAlways Free プロダクトというものがあり、制限内であればトライアル期間等関係なく、無料で利用できるサービスというものが存在します。

GCEでは特定リージョンのf1-microというマシンタイプが無料で使用できます。

GCS Bucketについては、Datastoreと同リージョンにしなければならない制限がありましたが、GCEで行うのはgcloudコマンドの定期実行だけなので、GCEインスタンス自身のリージョンについてはDatastoreと合わせる必要はありません。

今回は下記のように作成します。

cronの設定

cronの設定を行う前に、デフォルトのGCEインスタンスのTimeZoneを変更しておきます。

デフォルトはUTCになっており、そのままでも問題なくcronは動きますが、設定ミスを防ぐ為にJSTに変更しておくのがよいかと思います。

設定方法については下記が参考になります。
GCEちょい技 ~システム言語、タイムゾーンの変更方法~ メインOS全まとめ

下記cronの設定です。

0 0 * * * gcloud datastore export --kinds="KIND1,KIND2" --namespaces="(default)" gs://${Bucket}

下記のようにKindを指定しないExportもできますが、後述するBigQueryへのインポートにはKind毎にExportしておく必要があります。

0 0 * * * gcloud datastore export --namespaces="(default)" gs://${Bucket}

スケジュール後に指定したGCSのBucketにBackupがExportされていればOKです。

GAE cron

GAE cronを利用する場合は、gcloudコマンドを直接実行する術が無い為、Export用のエンドポイントを実装してやる必要があります。

GAEは複数の言語に対応していますが、最近個人的にGo言語をよく触っているので、こちらを使用してやってみたいと思います。

エンドポイント内でHTTP Clientを使用し、DatastoreのExport用のAPIにRequestを投げる形で実装をします。

GAE デフォルトサービスアカウントの役割追加

GAE cronの実行はGAEのデフォルトのサービスアカウントで行われます。

その為、GAE cronでExportを実行する際には、GCEで設定したサービスアカウントと同様権限を追加してやる必要があります。

GAEのデフォルトサービスアカウントは${PROJECT_ID}@appspot.gserviceaccount.comなので、このアカウントに下記の権限を追加します。

  • Datastoreインポート/エクスポート管理者
  • GCSオブジェクト作成者

プロジェクト作成

まずは個人的な備忘録も兼ねて、GO1.11からのModulesに慣れていないのでプロジェクト作成からやります。

# プロジェクト作成
mkdir gae-go-datastore-backup-example
cd gae-go-datastore-backup-example

# GO Modulesを有効にする
export GO111MODULE=on

# GO Modules初期化
go mod init github.com/cloud-ace/gae-go-datastore-backup-example

プロジェクトを作成したら、GAEに必要なファイルを配置していきます。

雛形としてGAE GoのQuickStartで使用されているサンプルコードを利用します。

エンドポイント実装

今回はmain.goに下記のように実装をしました。

GCPの認証についてはこちらのパッケージを利用しています。

Backup先のGCS Bucketの指定、Kindの指定はQueryパラメータから受け取るようにしています。

main.go

~~下記一部抜粋~~

func exportHandler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    ts, err := google.DefaultTokenSource(ctx,
        "https://www.googleapis.com/auth/datastore")
    if err != nil {
        log.Printf("Get token failed. err: %v", err)
    }
    client := oauth2.NewClient(ctx, ts)

    q := r.URL.Query()

    type M map[string]interface{}
    type I []interface{}
    kindI := I{}
    for _, k := range q["kind"] {
        kindI = append(kindI, k)
    }
    log.Println(q["outputUrlPrefix"])
    reqBody := M{
        "outputUrlPrefix": q["outputUrlPrefix"][0],
        "entityFilter": M{
            "kinds": kindI,
        }}
    b, err := json.Marshal(reqBody)
    if err != nil {
        log.Printf("Marshal request body failed. err: %v", err)
        return
    }

    req, err := http.NewRequest(
        "POST",
        "https://datastore.googleapis.com/v1/projects/{YOUR_PROJECT_ID}:export",
        bytes.NewBuffer(b))

    req.Header.Set("Content-Type", "application/json")

    res, err := client.Do(req)
    if err != nil {
        log.Printf("Request failed. err: %#v", err)
        return
    }

    if res.StatusCode != http.StatusOK {
        respb, _ := ioutil.ReadAll(res.Body)
        log.Printf("Request is not OK. ReponseBody: %s", string(respb))
        return
    }

    log.Printf("Request success. Response: %v", res)
    return
}

余談ですが、GAE Go1.11からコードのGAE依存が少なくなりいい感じです!

cronの設定

GAE cronの設定はcron.yamlという設定ファイルを使用して行います。

ジョブの実行日時とTimeZoneを指定しています。

cron.yaml

cron:
  - description: "Daily Cloud Datastore Export"
    url: /cloudDatastoreExport?outputUrlPrefix=gs://${Bucket}&kind=KIND1&kind=KIND2
    schedule: every day 00:00
    timezone: Asia/Tokyo

スケジュール後に指定したGCSのBucketにBackupがExportされていればOKです。

cronジョブのデプロイ

cronジョブのデプロイはcron.yamlを指定してデプロイします。

gcloud app deploy cron.yaml

cronジョブの確認

またGAEにデプロイされているcronジョブは、GCPコンソール上から確認ができます。

ナビゲーションメニューの「App Engine」 > 「cronジョブ」 に現状のcronジョブ一覧が表示されます。

ジョブの一覧が確認できるだけではなく、スケジューリングされた日時以外にもcronジョブを手動実行することも可能です。

Backupのローテーション

上記までで、DatastoreのEntityのBackupはされるようになりましたが、このままではGCS内に延々とBackupファイルが増え続けていってしまいます。

不要なBackupファイルでGCS料金が嵩んでしまってはもったいないので、必要な分だけBackupファイルを取っておくように設定してみましょう。

ここではGCS Bucketのライフサイクル機能を使用します。

ナビゲーションメニューから「Storage」をクリックし、設定を行いたいBucket配下まで移動し、「バケットロック」タブから、「ライフサイクル ルールを追加」をクリックします。

オブジェクト条件の選択で「年齢」をチェックし、任意の日数を設定、アクションを選択するで「削除」をチェックし、ルールを保存します。

Bucketを一覧から確認し、「ライフサイクル列」が有効になっていることを確認します。

ライフサイクル設定後は、設定した日数以前のBackupファイルが消えていることを確認してみてください。

公式ドキュメント

BigQueryへのインポート

DatastoreのデータをSQLを使って柔軟に解析をしたい等の用途でBigQueryにDatastoreBackupのインポートができます。

BigQueryはデフォルトでDatastoreのExportデータに対応しており、そのままインポートすることが可能です。
ただし、DatastoreのBackupはKind毎に別れている必要があります。

BigQueryへのインポートは下記の手順で行えます。

ナビゲーションメニューより、「BigQuery」をクリックして、BigQueryUIまで移動します。
左メニューからプロジェクト名を選択し、「データセットを作成」をクリックします。

任意のID、ロケーションを選択し、データセットを作成します。

作成したデータセットを選択し、「テーブルを作成」をクリックします。

テーブルの作成元に「Google Cloud Storage」を、GCSバケットからのファイルには「~kind{KIND}.export_metadata」というファイルを、ファイル形式には「Cloud Datastore バックアップ」を指定し、テーブルを作成します。

指定したデータセット配下にテーブルが作成され、データがインポートされていればOKです。

まとめ

いかがでしたでしょうか。

ソースコードは 弊社のGitHubに置いてありますので、ご参考にして頂ければ幸いです。

ご意見・ご感想お待ちしております。

次の記事を読み込んでいます
次の記事を読み込んでいます
次の記事を読み込んでいます
次の記事を読み込んでいます
次の記事を読み込んでいます