ローカルPCでfirebase-adminを使って一括削除/一括更新する

最近、Nuxt.js+Firebaseで作った積読を解消するWebアプリ「[積読ハウマッチ](https://tsundoku.site)」をリリースしました！！

そのときに、Firestoreのスキーマを一括変更したり、
不要なデータを一括削除したいと思ったときの備忘録。

ローカルPCでfirebase-adminを使うときの初期設定とかはこちらを参照
- [ローカルPCからfirebase-adminを使ってFirestoreを操作する(管理ツール) - くらげになりたい。](https://www.memory-lovers.blog/entry/2019/07/25/073000)

#### 注意点

Firestoreでは、1度に実行できる更新処理の数に制限がある。

> 一括書き込みでは最大 500 件のオペレーションを実行できます

[トランザクションと一括書き込み  |  Firebase](https://firebase.google.com/docs/firestore/manage-data/transactions)

そのため、以下にも記載されている通り、
100件ごとなど、小さなバッチに分けて実行する必要がある。

> メモリ不足エラーを避けるため、小さなバッチに分けてドキュメントを削除することをおすすめします。コレクション全体またはサブコレクションが削除されるまで、このプロセスを繰り返します。

[Cloud Firestore からデータを削除する  |  Firebase](https://firebase.google.com/docs/firestore/manage-data/delete-data#collections)


### 小さなバッチに分けて実行するソース

全体としてはこんな感じ。 

登場する3つ

1. `main()` ... メイン関数。実行したときに呼ばれる関数
2. `deleteAllUsers()` ... ユーザを一括削除する関数。mainから呼ばれる
3. `executeBatch()` ... 指定したバッチサイズで再帰的に実行する関数。deleteAllUsers()から呼ばれる

なお、コレクション`users`のなかにあるドキュメントには、
作成日時のタイムスタンプ`createAt`のドキュメントフィールドがある想定です。

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

// 配置したサービスアカウントの秘密鍵を取得
const serviceAccount = require("./key/XXXXX.json");

// firebase-adminを初期化
admin.initializeApp({ credential: admin.credential.cert(serviceAccount) });

// firestoreのインスタンスを取得
const db = admin.firestore();


/**
 * 一括実行処理の共通関数
 * @param {FirebaseFirestore.Firestore} db firestoreのインスタンス
 * @param {Number} limit 1回に実行するサイズ
 * @param {Function} queryFunc ドキュメントを検索するクエリを作成する関数。firestore.Queryを返す
 * @param {Function} executeFunc 削除や更新などを実行する関数。
 * @param {Object|undefined} last 検索でヒットした最後の要素。バッチサイズで繰り返す際に利用s
 */
async function executeBatch(db, limit, queryFunc, executeFunc, last) {

  // queryFuncを使って対象のドキュメントを取得
  const query = queryFunc(db, last);
  const items = await query.limit(limit).get();
  
  // ドキュメントが1つも見つからなければ、終了
  if (items.size === 0) return;

  // executeFuncを使ってbatchに削除/更新処理を追加＆コミット
  const batch = db.batch();
  for (let i = 0; i < items.size; i++) {
    executeFunc(db, batch, items.docs[i]);
  }
  await batch.commit();
  
  // リストの最後の要素を取得して、再帰実行
  const lastItem = items.docs[items.size - 1].data();
  return await executeBatch(db, limit, queryFunc, executeFunc, lastItem);
}


/**
 * すべてのユーザを削除
 */
async function deleteAllUsers(db, limit) {
 // 検索部分の関数: 作成日時で昇順ソートして取得
 const queryFunc = (db, last) => {
    let query = db.collection("users").orderBy("createAt", "asc");
    if (!!last) query = query.startAfter(last.createAt);
    return query;
  };
  
  // 実行部分の関数: itemで該当のドキュメントを受け取るので削除
  const executeFunc = (db, batch, item) => {
    const docRef = db.collection("users").doc(item.id);
    batch.delete(docRef);
  };
  
  return await executeBatch(db, limit, queryFunc, executeFunc, undefined);
}

// ****************************
// * MAIN
// ****************************
async function main() {
  console.log(`***** START MAIN *****`);
  // バッチサイズを100件で実行
  const limit = 100;
  await deleteAllUsers(db, limit)
  console.log(`***** END   MAIN *****`);
}

main().then();
```

他の処理でも共通的に使えるように`executeBatch()`では、
`queryFunc`と`executeFunc`の2つの関数を引数で受け取れるようにしている。

なので他の処理などを追加するときは、`deleteAllUsers()`みたいなのを増やしていけばOK!!

これで一括削除やスキーマの更新もだいぶ楽に...(*´ω｀*)


## おわりに

Webサービスは運用しはじめると色々大変なので、管理ツールなども大事...
最近リリースしたこちらのアプリでも使ってます♪

■積んでる本の総額がわかる読書管理サービス
[積読ハウマッチ](https://tsundoku.site)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/2c9872d0-101b-88aa-af2a-5bb907d09e28.png" width="20%"/>


総額がわかると少しは積ん読してる本を読もうと思ったり♪
積読総額ランキングや人気の本などもあるので、よかったらあそんでもらえれば!!

積読ハウマッチは、Nuxt.js+Firebase+ZEIT Nowで作ってます♪

# 参考にしたサイト
- [Cloud Firestore からデータを削除する  |  Firebase](https://firebase.google.com/docs/firestore/manage-data/delete-data#collections)
- [Firestoreにテストデータを流す｜shogo yamada｜note](https://note.mu/shogoyamada/n/n04d80a8d284f)
- [Firestoreの特定のコレクションのドキュメントを全削除するスクリプト(TypeScript版)](https://gist.github.com/mono0926/78c3d3e078323d566b46929c7540b15b)
- [Cloud Firestoreのバックアップ・リストア - google-cloud-jp - Medium](https://medium.com/google-cloud-jp/firestore-backup-67327a74cd54)
- [ローカルPCからfirebase-adminを使ってFirestoreを操作する(管理ツール) - くらげになりたい。](https://www.memory-lovers.blog/entry/2019/07/25/073000)