複数のプロジェクトのfirebase-adminを起動して、開発用に本番データの一部を移行する


開発しているWebサービスで、本番データの一部を開発用に使いたいと思ったときの備忘録。
firebase-adminは複数のプロジェクトで使えるらしい。

開発用だけじゃなく、UIDを変更しながらのデータ移行などにも使えそう。

ローカル環境でfirebase-adminを使うための準備

準備の部分は、以前まとめたこちらを参照ください〜
- ローカルPCからfirebase-adminを使ってFirestoreを操作する(管理ツール) - くらげになりたい。

ソースはこんな感じ。

const admin = require("firebase-admin");  

// *** 移行元のプロジェクトの設定
const srcSA = require("./key/XXXXX.json");   // サービスアカウントの秘密鍵を取得
// firebase-adminの初期化
admin.initializeApp({ credential: admin.credential.cert(srcSA) }); 
const srcDB = admin.firestore();          // firestoreのインスタンスを取得

// ** 移行先のプロジェクトの設定
const destSA = require("./key/XXXXX.json");  // サービスアカウントの秘密鍵を取得
// ※同じadminを使って、別のAppを作成しないといけない※
// ※2つ目を初期化する場合は、"dest"など名前をつけないといけない※
const destAdmin = admin.initializeApp( { credential: admin.credential.cert(destSA) }, "dest");
const destDB = destAdmin.firestore();        // firestoreのインスタンスを取得

// ****************************
// * MAIN
// ****************************
async function main() {
  console.log(`***** START MAIN`);

  // 移行元からデータを取得
  const snaps = await srcDB.collection("data").get()
  const srcData = snaps.docs;

  for (let v of data) {
    try {
      // 移行先からデータを保存
      const destDataRef = destDB.collection("data").doc(v.id);
      await destDataRef.set(v.data());
    } catch (error) {
      console.errror(`Error: ${error}`, error);
    }
  }

  console.log(`***** END   MAIN`);
  process.exit(0);
}

main().then();

雛形はこんな感じ。

以前書いた以下の記事だと丸々移行するので、UIDが変わるとめんどくさいことになる。。

この方法であれば、

  • whereで一部のだけに絞り込んだり、
  • set()するときに、UIDを差し替えたり

できるので、開発用に使うにはよいかんじ。

注意1: 2つ目を初期化する場合は別の名前に

そのまんまコピペで2つのfirebase-adminを作るとエラーに。。
2つ目を初期化する場合は、"dest"など名前をつけないといけないいけないらしい。。

公式ドキュメントの「複数のアプリを初期化する」に書いてあった。

これで、2つのfirebase-adminが起動できるので、データ移行などもできそう(´ω`)

注意2: メモリを大量に使いそうな場合はオプションを指定

データが多く、メモリを大量に使いそうな場合は、
--max_old_space_sizeオプションを指定しないといけない。。

途中で失敗すると(読み込み/書き込み件数的に)悲しいことになるので、
大きめのサイズを指定しておくと良さそう。

node --max_old_space_size=8192 index.js 
注意3: 件数が多い場合は、limitを指定して再帰処理

大量のデータを一度に取得しようとすると、

Deadline Exceeded error

が出て怒られるので、少しずつ実行するのがよいかんじ。

const BATCH_SIZE = 300
async function moveData(lastItem) {
  // 移行元からデータを取得
  const colRef = srcDB.collection("data");
  let query = colRef.orderBy("createAt", "asc");
  if (lastItem != null) query = query.startAfter(lastItem.createAt);
  const snap = await query.limit(BATCH_SIZE).get();

  if (snap.docs.length === 0) return; // 0件なら終了する
  const srcData = snaps.docs;

  for (let v of data) {
    try {
      // 移行先からデータを保存
      const destDataRef = destDB.collection("data").doc(v.id);
      await destDataRef.set(v.data());
    } catch (error) {
      console.errror(`Error: ${error}`, error);
    }
  }

  // 最後の要素を渡して、そこから継続する
  await moveData(data[data.length - 1]);
}
// ****************************
// * MAIN
// ****************************
async function main() {
  console.log(`***** START MAIN`);

  await moveData(null);

  console.log(`***** END   MAIN`);
  process.exit(0);
}
注意4: 複数の場所に反映する場合は、batchやtransactionを使う

途中でエラーになる場合もあるので、batchやtransactionを使うとよさそう。

公式ドキュメントだと、このあたり
- トランザクションと一括書き込み  |  Firebase

  for (let v of data) {
    try {
      const batch = destDB.batch();

      // 移行先からデータを保存
      const destDataRef = destDB.collection("data").doc(v.id);
      batch.set(destDataRef, v.data());

      // 移行先からデータを保存: その2
      const destData2Ref = destDB.collection("data2").doc(v.id);
      batch.set(destData2Ref, v.data());

      // コミット
      await batch.commit();
    } catch (error) {
      console.errror(`Error: ${error}`, error);
    }
  }

ループ外で使うほうのもいい感じ。

ただ、一括で書き込めるドキュメント数は500までと制限があるので、
一度に書き込む範囲は適宜調整が必要。

1 回のトランザクションまたは一括書き込みでは、
最大500のドキュメントに書き込みを行うことができます。
トランザクションと一括書き込み  |  Firebase

まとめ

firebase-adminは複数のプロジェクトでも扱える。
部分的なデータ移行や一部を書き換えながらの移行もできる(´ω`)

ただ、件数が多かったり、複数の書き込みがある場合は、いろいろ必要。。

こんなのつくってます!!

積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!
積読ハウマッチは、Nuxt.js+Firebaseで開発してます!

もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ

要望・感想・アドバイスなどあれば、
公式アカウント(@MemoryLoverz)や開発者(@kira_puka)まで♪